Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
206 commits
Select commit Hold shift + click to select a range
fd77317
feat(web): recall sent prompts with the up arrow (#9173)
t3dotgg Sep 5, 2026
c7dc3cb
fix(server): keep mise-owned npm packages manual-only (#9927)
juliusmarminge Sep 5, 2026
f47a3fe
fix(settings): share restart continuation across environments (#9933)
maria-rcks Sep 5, 2026
7a089b2
fix(server): capture turn checkpoints after all edits finish (#9841)
juliusmarminge Sep 5, 2026
2fa5ef4
fix(web): load file grammar before enabling edits (#9947)
juliusmarminge Sep 5, 2026
e5a87e8
fix(web): deduplicate PR project filter choices (#9948)
juliusmarminge Sep 5, 2026
07d2497
test(web): keep provider field readers private (#9952)
juliusmarminge Sep 5, 2026
a399473
test(mobile): keep composer selection helper private (#9953)
juliusmarminge Sep 5, 2026
f530d7b
test(client-runtime): keep scoped key implementation private (#9955)
juliusmarminge Sep 5, 2026
6b87ce3
fix(web): reveal reselected diff files (#9951)
juliusmarminge Sep 5, 2026
eae770b
refactor(web): remove obsolete changed-files preview helpers (#9956)
juliusmarminge Sep 5, 2026
bc8584b
fix(web): give the browser keybinding notice breathing room (#9964)
juliusmarminge Sep 5, 2026
126ea5c
chore: configure Knip workspace audits (#9958)
juliusmarminge Sep 5, 2026
4a42fc6
refactor(web): prune unused UI and provider code (#9959)
juliusmarminge Sep 5, 2026
2759ef0
chore(mobile): remove obsolete widget wiring script (#9960)
juliusmarminge Sep 5, 2026
00d4618
chore: remove redundant root tooling dependencies (#9961)
juliusmarminge Sep 5, 2026
d2c3e2e
ci: reject unused files and dependencies with Knip (#9962)
juliusmarminge Sep 5, 2026
dfc70a3
test(contracts): keep driver default lookup private (#9968)
juliusmarminge Sep 5, 2026
32142cf
test(server): remove Azure permissions constant snapshot (#9973)
juliusmarminge Sep 5, 2026
1568b3f
refactor(shared): remove unused viewport formatters (#9970)
juliusmarminge Sep 5, 2026
c1e279e
refactor(mobile): remove unused provider option summary (#9971)
juliusmarminge Sep 5, 2026
1782a2a
refactor(client-runtime): remove unused connection phase message (#9972)
juliusmarminge Sep 5, 2026
eda0cec
refactor(mobile): remove unused layout calculations (#9974)
juliusmarminge Sep 5, 2026
044a6e1
refactor(client-runtime): remove unused file position predicate (#9976)
juliusmarminge Sep 5, 2026
5fe29c8
refactor(mobile): remove unused font size steppers (#9975)
juliusmarminge Sep 5, 2026
b3f8dd9
test(server): cover thread lookup through command invariants (#9978)
juliusmarminge Sep 5, 2026
2fc630c
test(server): remove provider equality wrapper fixture (#9979)
juliusmarminge Sep 5, 2026
1550f1b
test(server): assert the dispatched welcome thread model (#9980)
juliusmarminge Sep 5, 2026
14da634
refactor(desktop): remove unused keyring remediation text (#9981)
juliusmarminge Sep 5, 2026
a9caf7b
refactor(desktop): remove test-only Electron error predicates (#9982)
juliusmarminge Sep 5, 2026
ea0487c
refactor(web): remove unused pull request state label (#9984)
juliusmarminge Sep 5, 2026
aefef95
perf(web): keep timeline row reuse engaged while text streams (#9909)
juliusmarminge Sep 5, 2026
3e13333
fix(web): reset markdown widgets when the previewed file changes (#9910)
juliusmarminge Sep 5, 2026
50027bb
fix(mobile): keep highlighting review diffs after a long line (#9911)
juliusmarminge Sep 5, 2026
fc1f543
fix(marketing): align the endorsement carousel with its heading (#9912)
juliusmarminge Sep 5, 2026
f87ecf0
fix(client): keep warm thread resumes live instead of flashing sync (…
juliusmarminge Sep 5, 2026
371e323
test(server): remove authorization prompt snapshots (#9985)
juliusmarminge Sep 5, 2026
10421bc
test(server): remove static OAuth page snapshots (#9986)
juliusmarminge Sep 5, 2026
9867eb1
test(server): remove provider label identity assertion (#9987)
juliusmarminge Sep 5, 2026
94d1fa7
test(server): consolidate agent activity opt-in coverage (#9988)
juliusmarminge Sep 5, 2026
aca2afc
refactor(shared): remove unused preview URL predicate (#9989)
juliusmarminge Sep 5, 2026
c6410d3
refactor(shared): remove unused mention path serializer (#9990)
juliusmarminge Sep 5, 2026
da1bebb
refactor(shared): remove retired PATH capture parser (#9991)
juliusmarminge Sep 5, 2026
3bcde91
refactor(client-runtime): remove unused subagent selectors (#9992)
juliusmarminge Sep 5, 2026
487d176
refactor(web): test the live usage column builder (#9993)
juliusmarminge Sep 5, 2026
c7e93f5
refactor(web): remove unused aspect ratio reconciler (#9994)
juliusmarminge Sep 5, 2026
bf1c1b0
refactor(web): remove obsolete cloud listing helpers (#9995)
juliusmarminge Sep 5, 2026
4f1dc55
test(web): remove composer control style snapshots (#9996)
juliusmarminge Sep 5, 2026
11cb88e
test(web): keep the preview profile label helper private (#9997)
juliusmarminge Sep 5, 2026
3fedf52
test(server): cover raw OpenCode deltas through the adapter (#9977)
juliusmarminge Sep 5, 2026
ba873b8
refactor(web): remove obsolete pull request link opener (#9983)
juliusmarminge Sep 5, 2026
ac93fbf
test(relay): keep the stage slug helper private (#9998)
juliusmarminge Sep 5, 2026
1e24b43
refactor(mobile): keep project selection helper private (#9999)
juliusmarminge Sep 5, 2026
1c59d3b
refactor(mobile): keep review default ID helper private (#10000)
juliusmarminge Sep 5, 2026
3382b26
refactor(mobile): remove unused native style constants (#10001)
juliusmarminge Sep 5, 2026
393d1ff
refactor(mobile): test terminal palettes through public theme API (#1…
juliusmarminge Sep 5, 2026
8d48a31
refactor(mobile): remove unused file tree walkers (#10003)
juliusmarminge Sep 5, 2026
1584076
refactor(shared): keep persisted settings helpers private (#10004)
juliusmarminge Sep 5, 2026
4e5e17f
test(mobile): remove mocked UUID shape assertions (#10006)
juliusmarminge Sep 5, 2026
1449dec
refactor(mobile): test final connection status presentation (#10007)
juliusmarminge Sep 5, 2026
4e59b06
test(web): keep pull request menu items private (#10016)
juliusmarminge Sep 5, 2026
3e544f8
refactor(shared): test favicon selection through public API (#10005)
juliusmarminge Sep 5, 2026
270e021
refactor(server): remove test-only pricing normalizer (#10017)
juliusmarminge Sep 5, 2026
93d4dfa
refactor(web): remove unused desktop update visibility helper (#10014)
juliusmarminge Sep 5, 2026
c9b76e6
refactor(web): remove obsolete provider update helpers (#10015)
juliusmarminge Sep 5, 2026
b7fc81d
refactor(web): remove unused terminal context preview formatter (#10009)
juliusmarminge Sep 5, 2026
82c2b7f
refactor(web): test environment-scoped draft promotion (#10010)
juliusmarminge Sep 5, 2026
6270a6f
fix(web): retain wrapped row heights during edits (#10018)
juliusmarminge Sep 5, 2026
cb58dfd
refactor(shared): remove unused Clerk hostname predicate (#10008)
juliusmarminge Sep 5, 2026
1d58f2e
refactor(tailscale): keep package internals private (#10011)
juliusmarminge Sep 5, 2026
cd92a7e
ci: reject unused tailscale exports with Knip (#10012)
juliusmarminge Sep 5, 2026
eced382
fix(web): keep chat media at a stable size while it loads (#9938)
juliusmarminge Sep 5, 2026
31fb210
refactor(server): keep manifest age parsing private (#10028)
juliusmarminge Sep 5, 2026
37bf4e6
refactor(mobile): remove unused awareness relay URL normalizer (#10029)
juliusmarminge Sep 5, 2026
f5d9d12
refactor(server): remove unused startup heartbeat launcher (#10030)
juliusmarminge Sep 5, 2026
86f0799
refactor(shared): keep search ranking comparator private (#10031)
juliusmarminge Sep 5, 2026
68aa7aa
refactor(server): keep telemetry identity errors private (#10032)
juliusmarminge Sep 5, 2026
83a2897
refactor(mobile): test composer persistence through the live decoder …
juliusmarminge Sep 5, 2026
5cb6965
refactor(web): remove unused sidebar selectors (#10034)
juliusmarminge Sep 5, 2026
ad3721e
refactor(server): keep Cursor fallback models private (#10038)
juliusmarminge Sep 5, 2026
07fb04d
refactor(web): remove unused xterm link range helpers (#10040)
juliusmarminge Sep 5, 2026
c059d09
refactor(mobile): remove obsolete review list builder (#10039)
juliusmarminge Sep 5, 2026
a21c0d7
test(server): remove duplicate VCS error constructor checks (#10042)
juliusmarminge Sep 5, 2026
5e828bf
refactor(mobile): keep appearance calculations private (#10043)
juliusmarminge Sep 5, 2026
5a4287c
refactor(web): remove unused sidebar menu action (#10044)
juliusmarminge Sep 5, 2026
6615d3d
refactor(web): test live Ghostty link resolution directly (#10041)
juliusmarminge Sep 5, 2026
df370c3
test(server): exercise Codex prompts through public assembly (#10045)
juliusmarminge Sep 5, 2026
62e74cd
refactor(shared): remove unused elapsed-time adapter (#10046)
juliusmarminge Sep 5, 2026
91ba05e
refactor(web): remove unused preview thread reset helper (#10049)
juliusmarminge Sep 5, 2026
56a2f42
refactor(desktop): remove test-only error predicates (#10047)
juliusmarminge Sep 5, 2026
c2aff91
refactor(mobile): keep review reset hashing private (#10048)
juliusmarminge Sep 5, 2026
a98dad7
test(web): remove AppRoot element order snapshot (#10052)
juliusmarminge Sep 5, 2026
62e4ae4
refactor(codex): keep app-server client internals private (#10035)
juliusmarminge Sep 5, 2026
4631000
ci: reject unused Codex client exports with Knip (#10036)
juliusmarminge Sep 5, 2026
45f5a5f
refactor(server): simplify native telemetry error internals (#10057)
juliusmarminge Sep 5, 2026
5b7f6bc
refactor(mobile): remove write-only terminal font cache (#10058)
juliusmarminge Sep 5, 2026
80f775a
refactor(web): remove obsolete HSL theme generator (#10061)
juliusmarminge Sep 5, 2026
d524eb9
refactor(mobile): remove obsolete native diff token stream (#10062)
juliusmarminge Sep 5, 2026
bc24d98
test(server): remove title prompt editorial snapshots (#10063)
juliusmarminge Sep 5, 2026
160e337
test(server): remove repeated runtime prompt interpolation cases (#10…
juliusmarminge Sep 5, 2026
43700b8
refactor(web): observe preview tests through the live registry (#10064)
juliusmarminge Sep 5, 2026
fedf84a
test(server): remove keybinding default assignment snapshot (#10065)
juliusmarminge Sep 5, 2026
4db2c54
refactor(mobile): remove obsolete whole-file review highlighters (#10…
juliusmarminge Sep 5, 2026
0acf05f
test(server): cover CLI runner detection through command suggestions …
juliusmarminge Sep 5, 2026
ee150e1
refactor(mobile): remove unused cloud relay URL normalizer (#10068)
juliusmarminge Sep 5, 2026
009c13f
refactor(web): test live keybinding resolvers directly (#10069)
juliusmarminge Sep 5, 2026
25cbcd6
test(server): cover Grok skill parsing through discovery (#10070)
juliusmarminge Sep 5, 2026
38812c1
test(web): remove mocked diff view prop snapshot (#10073)
juliusmarminge Sep 5, 2026
19ea3c5
test(web): remove mocked annotation options snapshot (#10074)
juliusmarminge Sep 5, 2026
688e594
refactor(web): keep pending action labels private (#10075)
juliusmarminge Sep 5, 2026
b92a812
refactor(mobile): remove unused cloud pending-status mapper (#10071)
juliusmarminge Sep 5, 2026
ab0933a
refactor(web): remove unused model picker hint helpers (#10072)
juliusmarminge Sep 5, 2026
c843c19
fix(server): resume checkpointing after git init (#10078)
Yash-Singh1 Sep 5, 2026
09aac71
feat(web): first-run welcome wizard with agent setup and project impo…
t3dotgg Sep 5, 2026
2271a27
fix(server): keep Homebrew mise shims manual-only (#10085)
juliusmarminge Sep 5, 2026
39802c0
fix(ssh): report remote package installation failures accurately (#10…
juliusmarminge Sep 5, 2026
bd7f7ea
fix(web): keep bulk thread deletion going after failures (#4615)
m-de-graaff Sep 5, 2026
be7796d
fix(web): scale agent spawn rows with interface font (#10092)
juliusmarminge Sep 5, 2026
485782b
fix(web): prevent sidebar tooltip title clipping (#10086)
UtkarshUsername Sep 5, 2026
c1d27e5
fix(web): keep the composer expanded until the thread can scroll (#9965)
t3dotgg Sep 5, 2026
761d4ba
fix(web): preserve original mention text in the composer (#10100)
juliusmarminge Sep 5, 2026
6a8f4d3
refactor(mobile): remove unused pairing redaction wrapper (#10147)
juliusmarminge Sep 5, 2026
47e250a
test(web): drop provider banner styling assertions (#10148)
juliusmarminge Sep 5, 2026
29c3a54
refactor(client-runtime): remove unused relay token waiter (#10151)
juliusmarminge Sep 5, 2026
a324cab
test(web): drop sidebar artwork styling snapshots (#10152)
juliusmarminge Sep 5, 2026
3fbc497
refactor(ssh): keep package internals private (#10144)
juliusmarminge Sep 5, 2026
62ed748
ci: reject unused SSH exports with Knip (#10145)
juliusmarminge Sep 5, 2026
cb9a694
refactor(acp): keep protocol implementation exports private (#10165)
juliusmarminge Sep 5, 2026
2c301fd
fix(shared): validate cloudflared with the version subcommand (#9880)
maria-rcks Sep 5, 2026
60e1b73
fix(desktop): separate LAN and Tailscale pairing endpoints (#9882)
maria-rcks Sep 5, 2026
311f05c
fix(server): install pinned runtime when pnpm node lacks npm (#9923)
maria-rcks Sep 5, 2026
8d3c56b
fix(web): hide sidebar search shortcut on mobile (#9932)
maria-rcks Sep 5, 2026
89bd637
fix(web): align tool disclosure chevrons with expanded state (#9935)
maria-rcks Sep 5, 2026
6349a0e
fix(antigravity): distinguish session initialization auth failures (#…
maria-rcks Sep 5, 2026
0d8a91a
fix(cursor): cache successful model discovery between refreshes (#9918)
maria-rcks Sep 5, 2026
4ca7146
fix(opencode): revert from the first removed assistant message (#9924)
maria-rcks Sep 5, 2026
d92dca7
fix(web): resume imported custom-provider threads (#10184)
juliusmarminge Sep 5, 2026
f8b4c46
test(web): cancel pending highlight fixture frames during cleanup (#1…
juliusmarminge Sep 5, 2026
e2e6ce6
feat(server): report image dimensions with signed asset URLs (#10198)
juliusmarminge Sep 5, 2026
7451d17
fix(mobile): size a chat image's frame before its bytes arrive (#10199)
juliusmarminge Sep 5, 2026
d0f855b
fix(web): size the chat image slot from server-reported dimensions (#…
juliusmarminge Sep 5, 2026
0671e34
refactor(shared): remove unused runtime helpers and exports (#10166)
juliusmarminge Sep 5, 2026
dbfd517
refactor(client-runtime): remove unused runtime exports and helpers (…
juliusmarminge Sep 5, 2026
5716dec
refactor(contracts): keep RPC implementation exports private (#10168)
juliusmarminge Sep 5, 2026
50bc62a
refactor(contracts): trim unused client API runtime exports (#10169)
juliusmarminge Sep 5, 2026
2ae3b71
refactor(contracts): keep unused settings and relay exports private (…
juliusmarminge Sep 5, 2026
5fe5c6f
ci: enforce runtime exports while allowing types and schemas (#10171)
juliusmarminge Sep 5, 2026
54441e6
fix(web): copy provider update commands from compact rows (#9888)
MatthewFeroz Sep 5, 2026
3be90ce
fix(web): align provider header action sizes and spacing (#9890)
MatthewFeroz Sep 5, 2026
3fb8942
fix(mobile): restore live tool shimmer and add a Thinking row (#10173)
juliusmarminge Sep 5, 2026
1cb49c3
fix(mobile): even out the working pill's spacing (#10209)
juliusmarminge Sep 5, 2026
7eda989
fix(mobile): only make work rows expandable when the body adds someth…
juliusmarminge Sep 5, 2026
579a775
fix(mobile): fold subagent lifecycle rows into one batch per spawn (#…
juliusmarminge Sep 5, 2026
89cc743
fix(mobile): stop clipping expanded tool groups (#10212)
juliusmarminge Sep 5, 2026
c2cfe59
fix(web): defer browser discovery in integrations (#9797)
zortos293 Sep 5, 2026
88fc41c
refactor(web): test file cache identity through its public helpers (#…
juliusmarminge Sep 5, 2026
748fe0f
refactor(web): test favicon fallback through the component (#10220)
juliusmarminge Sep 5, 2026
183c343
feat: show provider usage limits with /usage-limits (#9875)
chrisdeeming Sep 5, 2026
585ce2c
refactor(web): test formatted timestamps instead of formatter options…
juliusmarminge Sep 5, 2026
be53bbd
feat(usage): show remaining quota instead of used (#9889)
chrisdeeming Sep 5, 2026
f12d393
fix(ui): unify loading and refresh feedback across clients (#9561)
StiensWout Sep 5, 2026
bd16b86
fix(client-runtime): report terminated thread loads (#10216)
juliusmarminge Sep 6, 2026
050690d
fix(server): settle threads using actual pull request terminal timest…
maria-rcks Sep 6, 2026
eee0557
fix(clients): persist project icons across reloads and reconnects (#1…
StiensWout Sep 6, 2026
b438447
fix(mobile): use selected theme across input forms and controls (#10239)
juliusmarminge Sep 6, 2026
bfba778
fix(mobile): keep the new-task draft when switching environment (#10247)
juliusmarminge Sep 6, 2026
2c33535
fix(web): keep timestamp tooltip dates in English (#10256)
Lucenx9 Sep 6, 2026
3da9399
fix(web): let authorized clients scrolling reach settings (#10080)
flamboh Sep 6, 2026
add8c3a
fix(web): remember usage page selection (#10189)
extoci Sep 6, 2026
c8872fd
fix(server): keep the Antigravity Google sign-in across server restar…
t3dotgg Sep 6, 2026
ab67795
fix(antigravity): load user skills from ~/.gemini for every project (…
t3dotgg Sep 6, 2026
84aebb7
fix(web): respect reduced motion in shared disclosures (#10258)
saphid Sep 6, 2026
b2e1518
feat(marketing): add a Windows 95 landing page (#10286)
t3dotgg Sep 6, 2026
fdcc491
refactor(web): remove unused runtime wrappers and exports (#10225)
juliusmarminge Sep 6, 2026
226abe5
refactor(web): keep feature component helpers private (#10226)
juliusmarminge Sep 6, 2026
1200f53
refactor(web): keep app utilities private and remove dead helpers (#1…
juliusmarminge Sep 6, 2026
da2ba5b
ci: enforce unused runtime exports in the web app (#10228)
juliusmarminge Sep 6, 2026
76f686d
test(desktop): cover Clerk setup through the service (#10284)
juliusmarminge Sep 6, 2026
cabac78
test(desktop): cover WSL hashes through runtime resolution (#10285)
juliusmarminge Sep 6, 2026
181e451
test(desktop): cover password store through startup (#10287)
juliusmarminge Sep 6, 2026
0c200c5
test(desktop): cover WSL paths through public behavior (#10289)
juliusmarminge Sep 6, 2026
b4040d9
test(desktop): exercise WSL cache safety through public scripts (#10301)
juliusmarminge Sep 6, 2026
f93aafc
test(web): cover file classification through diff ordering (#10304)
juliusmarminge Sep 6, 2026
a9fc4dc
test(web): focus command palette tests on search behavior (#10302)
juliusmarminge Sep 6, 2026
fc7ad2e
fix(web): add project settings to legacy sidebar project menu (#10021)
hwanseoc Sep 6, 2026
b972f1c
test(web): keep Markdown gutter styling private (#10306)
juliusmarminge Sep 6, 2026
9f40b2f
feat(settings): add shared project defaults and scoped overrides (#9754)
maria-rcks Sep 6, 2026
420fd76
feat(connections): balance new threads across connected machines (#9895)
maria-rcks Sep 6, 2026
f1e84c2
test(web): keep settings viewport comparison private (#10307)
juliusmarminge Sep 6, 2026
4f782be
fix(web): prevent file tree search focus ring clipping (#10175)
GuilhermeVieiraDev Sep 6, 2026
b7465a3
fix(mobile): stop the work log flickering during subagent runs and fa…
juliusmarminge Sep 6, 2026
a495385
fix(mobile): save linked media from chat (#10271)
juliusmarminge Sep 6, 2026
272d6d7
feat(markdown): show the GitHub mark for github.com links (#10324)
juliusmarminge Sep 6, 2026
d924fe2
fix(marketing): show a real preview card when t3.codes is shared (#10…
t3dotgg Sep 6, 2026
b273d1c
feat(usage): pool subscription limits per provider across accounts an…
juliusmarminge Sep 6, 2026
00d6109
chore(web): remove usage limits demo fixtures (#10330)
juliusmarminge Sep 6, 2026
127efae
fix(web): expose error disclosure state (#10125)
saphid Sep 6, 2026
bc02873
fix(web): name the editor picker accurately (#10124)
saphid Sep 6, 2026
aea9ecb
fix(web): make task row states readable (#10128)
saphid Sep 6, 2026
8268978
fix(web): explain hosted connection prerequisites (#10129)
saphid Sep 6, 2026
5533383
fix(web): name combobox chip removal targets (#10127)
saphid Sep 6, 2026
e5d086c
fix(marketing): present the Git workflow as an illustration (#10130)
saphid Sep 6, 2026
b155c21
feat(mobile): pool usage limits across selected environments (#10334)
juliusmarminge Sep 6, 2026
7544d3d
fix(release): space automatic nightlies at least six hours apart (#10…
t3dotgg Sep 6, 2026
b6ee6fe
Merge upstream main into the orchestration-v2 base
colonelpanic8 Sep 6, 2026
d27c7a0
feat: client-only desktop mode, local server pairing, and environment…
colonelpanic8 Sep 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
44 changes: 44 additions & 0 deletions .github/scripts/check-nightly-release.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const MINIMUM_RELEASE_GAP_MS = 6 * 60 * 60 * 1000;

// Runs after the workflow acquires the nightly concurrency lock.
async function shouldReleaseNightly({ github, context, core, now = Date.now() }) {
const releases = await github.paginate(github.rest.repos.listReleases, {
...context.repo,
per_page: 100,
});
const lastNightly = releases
.filter(
(release) =>
!release.draft &&
release.published_at &&
(/^v.*-nightly\./.test(release.tag_name) || release.tag_name.startsWith("nightly-v")),
)
.sort((a, b) => Date.parse(b.published_at) - Date.parse(a.published_at))[0];

if (!lastNightly) {
core.info("No published nightly found. Proceeding with release.");
return true;
}

if (now - Date.parse(lastNightly.published_at) < MINIMUM_RELEASE_GAP_MS) {
core.info(`Nightly ${lastNightly.tag_name} was published less than six hours ago. Skipping.`);
return false;
}

const { data: comparison } = await github.rest.repos.compareCommitsWithBasehead({
...context.repo,
basehead: `${lastNightly.tag_name}...${context.sha}`,
per_page: 1,
});
if (comparison.status !== "ahead") {
core.info(
`Candidate commit is ${comparison.status} relative to ${lastNightly.tag_name}. Skipping.`,
);
return false;
}

core.info(`New commits since ${lastNightly.tag_name}, and the six-hour gap has passed.`);
return true;
}

module.exports = { shouldReleaseNightly };
101 changes: 101 additions & 0 deletions .github/scripts/check-nightly-release.test.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
const assert = require("node:assert/strict");
const test = require("node:test");
const { shouldReleaseNightly } = require("./check-nightly-release.cjs");

const now = Date.parse("2026-09-05T12:00:00Z");
const hour = 60 * 60 * 1000;
const nightly = (hoursAgo, overrides = {}) => ({
tag_name: "v1.0.1-nightly.20260905.123",
draft: false,
published_at: new Date(now - hoursAgo * hour).toISOString(),
...overrides,
});

function fixture({ releases = [nightly(7)], comparisonStatus = "ahead" } = {}) {
const calls = [];
return {
calls,
options: {
now,
context: { repo: { owner: "example", repo: "app" }, sha: "new" },
core: { info() {} },
github: {
rest: {
repos: {
listReleases() {},
async compareCommitsWithBasehead(params) {
calls.push(params);
return { data: { status: comparisonStatus } };
},
},
},
async paginate() {
return releases;
},
},
},
};
}

test("releases the first nightly when no nightly is published", async () => {
const { options } = fixture({
releases: [nightly(0, { tag_name: "v1.0.0" }), nightly(0, { draft: true })],
});
assert.equal(await shouldReleaseNightly(options), true);
});

test("waits six hours after publication, including manual nightlies", async () => {
for (const age of [0, 3, 6 - 1 / 3600]) {
const { options, calls } = fixture({ releases: [nightly(age)] });
assert.equal(await shouldReleaseNightly(options), false);
assert.equal(calls.length, 0);
}
});

test("releases new commits at six hours and after an idle period", async () => {
for (const age of [6, 7, 24]) {
const { options } = fixture({ releases: [nightly(age)] });
assert.equal(await shouldReleaseNightly(options), true);
}
});

test("skips unchanged commits after the gap", async () => {
const { options } = fixture({ comparisonStatus: "identical" });
assert.equal(await shouldReleaseNightly(options), false);
});

test("uses publication time, not release order or the tagged commit date", async () => {
const { options } = fixture({
releases: [nightly(10), nightly(1), nightly(20, { tag_name: "nightly-v0.9.0" })],
});
assert.equal(await shouldReleaseNightly(options), false);
});

test("ignores stable releases and drafts when checking the gap", async () => {
const { options } = fixture({
releases: [nightly(0, { tag_name: "v1.0.0" }), nightly(0, { draft: true }), nightly(7)],
});
assert.equal(await shouldReleaseNightly(options), true);
});

test("compares against the published tag, including legacy nightly tags", async () => {
const tag = "nightly-v0.9.0";
const { options, calls } = fixture({ releases: [nightly(7, { tag_name: tag })] });
assert.equal(await shouldReleaseNightly(options), true);
assert.equal(calls[0].basehead, `${tag}...new`);
});

test("fails instead of releasing when GitHub cannot supply release state", async () => {
const { options } = fixture();
options.github.paginate = async () => {
throw new Error("GitHub unavailable");
};
await assert.rejects(shouldReleaseNightly(options), /GitHub unavailable/);
});

for (const status of ["behind", "diverged"]) {
test(`skips a candidate commit that is ${status} relative to the last nightly`, async () => {
const { options } = fixture({ comparisonStatus: status });
assert.equal(await shouldReleaseNightly(options), false);
});
}
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ jobs:
- name: Ensure Electron runtime is installed
run: vp run --filter @t3tools/desktop ensure:electron

# Files/dependencies are repo-wide; export checks cover clean workspaces only.
- name: Check unused code
run: vp run knip:check

- name: Check
run: vp check

Expand Down Expand Up @@ -111,6 +115,9 @@ jobs:
sudo sed -i 's|http://|https://|g' /etc/apt/blacksmith-ubuntu-mirrors.txt /etc/apt/sources.list.d/ubuntu.sources
sudo apt-get update && sudo apt-get install -y libsecret-1-dev pkg-config

- name: Test nightly release checks
run: node --test .github/scripts/check-nightly-release.test.cjs

- name: Test
run: vp run --parallel --concurrency-limit 4 --filter '!t3' --filter '!@t3tools/monorepo' test

Expand Down
44 changes: 14 additions & 30 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ on:
- "v*.*.*"
- "!v*-nightly.*"
schedule:
# Off minute zero: GitHub delays scheduled runs most at the top of the hour.
- cron: "38 */3 * * *"
# Avoid minute zero, when GitHub scheduled jobs are busiest.
- cron: "8,38 * * * *"
workflow_dispatch:
inputs:
channel:
Expand All @@ -28,7 +28,7 @@ on:
# own group so a nightly never blocks them. Running publishers are never
# canceled, and queue: max keeps every pending run instead of the default
# newest-wins single slot, so a queued stable tag can never be silently
# dropped. Queued nightlies with no new commits skip via check_changes.
# dropped. Automatic nightlies recheck the release gap after leaving the queue.
concurrency:
group: release-${{ (github.event_name == 'schedule' || inputs.channel == 'nightly') && 'nightly' || 'stable' }}
cancel-in-progress: false
Expand All @@ -40,41 +40,25 @@ permissions:

jobs:
check_changes:
name: Check for changes since last nightly
name: Check automatic nightly release
if: github.event_name == 'schedule'
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 5
outputs:
has_changes: ${{ steps.check.outputs.has_changes }}
has_changes: ${{ steps.check.outputs.result }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
sparse-checkout: |
/*
!/.repos/
sparse-checkout-cone-mode: false
sparse-checkout: .github/scripts

- id: check
name: Compare HEAD to last nightly tag
run: |
last_nightly_tag=$(git tag --list 'v*-nightly.*' 'nightly-v*' --sort=-creatordate | head -n 1)
if [[ -z "$last_nightly_tag" ]]; then
echo "No previous nightly tag found. Proceeding with release."
echo "has_changes=true" >> "$GITHUB_OUTPUT"
exit 0
fi

last_nightly_sha=$(git rev-parse "$last_nightly_tag^{commit}")
head_sha=$(git rev-parse HEAD)

if [[ "$last_nightly_sha" == "$head_sha" ]]; then
echo "No changes on main since last nightly release ($last_nightly_tag). Skipping."
echo "has_changes=false" >> "$GITHUB_OUTPUT"
else
echo "Changes detected on main since $last_nightly_tag ($last_nightly_sha → $head_sha). Proceeding."
echo "has_changes=true" >> "$GITHUB_OUTPUT"
fi
name: Check release gap and new commits
uses: actions/github-script@v8
with:
script: |
const { shouldReleaseNightly } = require('./.github/scripts/check-nightly-release.cjs');
return await shouldReleaseNightly({ github, context, core });

preflight:
name: Preflight
Expand Down Expand Up @@ -228,7 +212,7 @@ jobs:
name: Resolve T3 Connect public config
# Consumes only the commit SHA, not preflight's resolved version, so it runs
# alongside preflight instead of after it. The condition mirrors preflight's:
# check_changes is skipped on non-schedule events (skipped is neither failure
# check_changes is skipped on manual and tag releases (skipped is neither failure
# nor success, so success() would be wrong here).
needs: [check_changes]
if: |
Expand Down
Loading
Loading