Repository navigation
feat(tui): opt-in dev reload that restarts the TUI in place - #4
Closed
eric-engberg wants to merge 35 commits into
Closed
eric-engberg wants to merge 35 commits into
eric-engberg wants to merge 35 commits into
Conversation
eric-engberg
force-pushed
the
feat/dev-reload
branch
from
October 3, 2026 20:54
d1fb972 to
98ec6d4
Compare
eric-engberg
force-pushed
the
fix/hotkey-modifiers
branch
from
October 3, 2026 21:04
5125a37 to
caf659d
Compare
eric-engberg
force-pushed
the
feat/dev-reload
branch
from
October 3, 2026 21:04
98ec6d4 to
cdfb506
Compare
This was referenced Oct 3, 2026
eric-engberg
force-pushed
the
feat/dev-reload
branch
from
October 3, 2026 22:50
cdfb506 to
c751ae3
Compare
eric-engberg
force-pushed
the
fix/hotkey-modifiers
branch
from
October 3, 2026 22:50
caf659d to
c4cdeb9
Compare
This was referenced Oct 4, 2026
eric-engberg
force-pushed
the
feat/dev-reload
branch
from
October 4, 2026 06:03
c751ae3 to
3c12407
Compare
|
|
❌ The last analysis has failed. |
eric-engberg
force-pushed
the
fix/hotkey-modifiers
branch
from
October 6, 2026 16:47
d089667 to
dd3dbdf
Compare
eric-engberg
force-pushed
the
feat/dev-reload
branch
from
October 6, 2026 16:47
61adeb4 to
93d2f00
Compare
The Testing bullet said 6 test files / ~40 test cases (actual today: 127 files, ~1,470 cases) and the Available Widgets list names about 25 of the 85 registered widget types. Rather than updating the numbers and letting them drift again, drop the hardcoded counts and point at the code: - Testing: describe test layout instead of counting files - Available Widgets: mark the list as highlights and point to src/utils/widget-manifest.ts for the full, current list Claude-Session: https://claude.ai/code/session_019YuAUKeC6FjaSczoPWQkuP Co-authored-by: Claude Opus 5 (1M context) <[email protected]> Co-authored-by: Matthew Breedlove <[email protected]>
Picking up a code change meant quitting the TUI and redoing any unsaved setup and navigation by hand. With CCSTATUSLINE_DEV_RELOAD=1 the interactive TUI runs under a small supervisor process. ctrl+r writes a snapshot (unsaved and original settings, current screen, selected line, menu positions, line editor cursor) and exits with code 75; the supervisor starts a fresh process on the latest code, which resumes from the snapshot. Screens that carry their own flow state (confirm dialogs, install/update, import/export) resume on the main menu. If the new code fails to start, the supervisor keeps the snapshot and offers Enter to retry once it's fixed, or q to quit. Without the variable nothing changes: no supervisor, no key, and the module is only loaded through a dynamic import in the interactive branch, so the status line render path is unaffected.
The dev reload cursor test checked the first frame and the cursor callback after fixed 25 ms delays, which a busy machine can outrun. It now retries the assertions until they pass (up to 3 s) with a waitFor helper. Once they pass, waitFor gives React two setImmediate turns: React draws a frame, then attaches the new input listener in a scheduled effect, so a key sent the moment the frame shows could reach no listener.
The App always passes the line selector its settings. The hotkey test now does too, so it matches real use and keeps compiling if they become required.
…resume - The retry prompt takes the terminal and the supervisor takes the function that runs each child, so both run in tests: Enter retries, q gives up, other keys wait, and the supervisor relaunches the same command with a snapshot path and removes the snapshot at the end. - A test renders the App as a reloaded child: it opens on the snapshot's line editor with the unsaved settings, and ctrl+r writes a new snapshot of where it is. Startup checks on Claude Code, package managers and fonts are stubbed.
The supervisor kept its snapshot at os.tmpdir()/ccstatusline-dev-reload- <pid>.json and removed it only when it returned normally. The TUI it starts restores whatever is at that path, so: - a snapshot left by a supervisor that was killed came back in a later run that got the same PID, as current unsaved settings, and ctrl+s then wrote it over settings.json; - on a shared /tmp another local user could plant the file, e.g. with a Custom Command widget, and have it restored the same way. Each run now creates its own directory with mkdtemp (new, mode 0700) and keeps the snapshot inside, so the first child can only find what this run wrote. The whole directory is removed when the supervisor ends.
The supervisor ran the TUI with spawnSync and handled no signals. SIGTERM, SIGINT or SIGHUP sent to the supervisor alone (e.g. kill <pid>) ended it but not the TUI, which kept running with PPID 1, out of the foreground; under Bun it later printed an EIO stack trace over the shell prompt. The snapshot was left behind too, also when closing the terminal ended both. The TUI now runs as an asynchronous child, so the supervisor can handle signals while it runs. On any of the three it passes the signal on, waits for the TUI to exit (so the shell gets the terminal back after the TUI has restored it), removes the snapshot directory and exits with 128 + the signal number. A TUI that ends this way is not a crash, so no retry prompt is shown. At the prompt there is no TUI to wait for, so the supervisor cleans up and exits straight away. The supervisor's process (prompt terminal, signals, exit) is a parameter defaulting to process, like the child launcher, so tests can drive it.
main now has the same helper in src/tui/__tests__/helpers/wait-for-ink.ts, so the file's own copy of waitFor and letReactCatchUp goes.
The registry's `version` went unchecked into the install commands the TUI shows, and into the update it runs. For npm on Windows that runs npm.cmd through cmd.exe, so a version such as `2.3.0 & calc` would have run a second command. checkForUpdates now treats anything but a plain semver version as a registry failure, and runGlobalPackageInstall refuses one.
Both parsers backtracked quadratically on input a repository controls: - A `.git` file is read in every folder above the session's directory. `gitdir:\s*(.+)$` could split a run of whitespace between `\s*` and the path at every position, rescanning the line each time: a 100 KB file took about 5 seconds per render. The path now has to start at a non-space. - A remote URL's path had its slashes trimmed with `/^\/+|\/+$/g`, which rescans a run of slashes from every position in it. The path is now split on `/` and its empty segments dropped, with the same result.
… line The usage and status-page requests set `timeout`, which only covers an idle socket. Behind HTTPS_PROXY the request has no socket until the proxy answers CONNECT, so a proxy that accepts the connection and never answers held the render forever. Destroying the request wouldn't end it either: the proxy agent opens its own connection and keeps it until the proxy answers, and that connection kept the process alive after printing. - Both requests now have an overall deadline (the same 5 seconds). At the deadline the request is destroyed and the agent's connection to the proxy is closed through an AbortSignal (`createProxyAgent`). - jj calls get git's 5 second timeout; they had none.
List new Custom Command shell commands in full before applying an import. Require confirmation for Replace All and Merge, with Cancel selected by default. Imports that add no new commands continue to apply immediately. Escape control characters in preview values, paths, and commands so imported text cannot hide commands or manipulate the terminal.
Summarize the latest security improvements, safer imports, proxy handling, cache fixes, and text rendering improvements. Add a standalone v2.2.32 Recent Updates section, synchronize usage and development documentation, and bump the package version to 2.2.32.
eric-engberg
force-pushed
the
feat/dev-reload
branch
from
October 9, 2026 19:12
151e566 to
53d4815
Compare
eric-engberg
marked this pull request as ready for review
October 9, 2026 19:19
Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 4.1.11 to 5.0.0. - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md) - [Commits](https://github.com/vitest-dev/vitest/commits/v5.0.0/packages/vitest) --- updated-dependencies: - dependency-name: vitest dependency-version: 5.0.0 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…loc#722) Bumps [react-devtools-core](https://github.com/react/react/tree/HEAD/packages/react-devtools-core) from 7.0.1 to 8.0.0. - [Release notes](https://github.com/react/react/releases) - [Changelog](https://github.com/react/react/blob/main/CHANGELOG.md) - [Commits](https://github.com/react/react/commits/[email protected]/packages/react-devtools-core) --- updated-dependencies: - dependency-name: react-devtools-core dependency-version: 8.0.0 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 7.0.0 to 7.1.0. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](actions/setup-node@8207627...949feb2) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: 7.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…ith 6 updates (sirmalloc#733) Bumps the dev-dependencies group with 6 updates in the / directory: | Package | From | To | | --- | --- | --- | | [@remotion/cli](https://github.com/remotion-dev/remotion) | `4.0.532` | `4.0.534` | | [eslint](https://github.com/eslint/eslint) | `10.11.0` | `10.12.0` | | [globals](https://github.com/sindresorhus/globals) | `17.12.0` | `17.13.0` | | [remotion](https://github.com/remotion-dev/remotion) | `4.0.532` | `4.0.534` | | [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.70.1` | `8.71.1` | | [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) | `5.0.0` | `5.0.3` | Updates `@remotion/cli` from 4.0.532 to 4.0.534 - [Release notes](https://github.com/remotion-dev/remotion/releases) - [Commits](remotion-dev/remotion@v4.0.532...v4.0.534) Updates `eslint` from 10.11.0 to 10.12.0 - [Release notes](https://github.com/eslint/eslint/releases) - [Commits](eslint/eslint@v10.11.0...v10.12.0) Updates `globals` from 17.12.0 to 17.13.0 - [Release notes](https://github.com/sindresorhus/globals/releases) - [Commits](sindresorhus/globals@v17.12.0...v17.13.0) Updates `remotion` from 4.0.532 to 4.0.534 - [Release notes](https://github.com/remotion-dev/remotion/releases) - [Commits](remotion-dev/remotion@v4.0.532...v4.0.534) Updates `typescript-eslint` from 8.70.1 to 8.71.1 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.71.1/packages/typescript-eslint) Updates `vitest` from 5.0.0 to 5.0.3 - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md) - [Commits](https://github.com/vitest-dev/vitest/commits/v5.0.3/packages/vitest) --- updated-dependencies: - dependency-name: "@remotion/cli" dependency-version: 4.0.534 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-dependencies - dependency-name: eslint dependency-version: 10.12.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-dependencies - dependency-name: globals dependency-version: 17.13.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-dependencies - dependency-name: remotion dependency-version: 4.0.534 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-dependencies - dependency-name: typescript-eslint dependency-version: 8.71.1 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-dependencies - dependency-name: vitest dependency-version: 5.0.3 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-dependencies ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…rmalloc#695) Claude Code 2.1.251 added rate_limits.spend_limit to the status line payload for developers behind a Claude apps gateway. Parse it and show it with two widgets that read the payload only and never call the usage API. Closes sirmalloc#572 Co-authored-by: Matthew Breedlove <[email protected]>
…#710) * fix: draw the text cursor as a bar so trailing spaces show The label editor quotes the label so a trailing space stays visible, but the cursor was a highlighted block: over a character, or a highlighted space after the text when at the end. So a label ending in a space with the cursor on it ("Weekly:" + block) looked the same as a label with no space and the cursor past the end ("7d" + block), and "7d" read as "7d ". Draw the cursor as a bar between characters instead. It never covers a character, so a trailing space shows as a gap before it, and the end of the text adds nothing that looks like a space. Custom Text shares the cursor, so it gets the bar too. * feat(tui): preview label edits live and blink editor cursors --------- Co-authored-by: Matthew Breedlove <[email protected]>
…el clash (sirmalloc#708) * fix(usage): call Session Usage Block Usage, ending its "Session:" label clash Three widgets printed the label "Session: " and meant two different things by it. Session Clock and Session Name mean the Claude Code conversation; Session Usage meant the 5-hour usage window. On one line, "Session: 42% | Session: 1hr 5m" reads as two facts about the same session when they aren't. The 5-hour window already has a name here: Block Timer and Block Reset Timer call it the block. Session Usage becomes Block Usage, labeled "Block: ", with the description "Shows the current 5-hour block's usage percentage". Its type stays session-usage, so saved configs keep working, and anyone who prefers the old label can set "Session: " back with the label editor (e). The docs follow; past release notes keep the name they shipped with. * fix(usage): label Block Usage "5h: " rather than "Block: " "Block: " is already Block Timer's label, for the block's elapsed time ("Block: 3hr 45m"), so Block Usage beside it would print two "Block:" figures. Label it "5h: " instead, which says which window the percent is of and pairs with Weekly Usage's "Weekly: ". The name stays Block Usage, and a "Session: " override still sets the old label back. --------- Co-authored-by: Matthew Breedlove <[email protected]>
…es whole (sirmalloc#704) * fix(ansi): strip 8-bit color codes as well as ESC [ ones The ANSI parser reads the 8-bit CSI (0x9b) as an escape sequence, but stripSgrCodes only matched color codes written with ESC [. Text colored with \x9b31m kept its colors where they should go: a global foreground override left a preserveColors widget's own colors in place, and a Custom Command cut by its max width never got its closing reset, so its color ran into the separators and widgets after it. Match both introducers. restoreBackgroundAfterResets shares the pattern, so an 8-bit reset now gets the background re-applied after it too; its parameter parsing skips a one-character introducer for those. * fix(sanitize): keep 8-bit color codes and drop other 8-bit sequences whole The terminal sanitizer dropped 8-bit C1 controls one character at a time. For a sequence started by one, that removed only the introducer and left the rest as text: a command printing \x9b31mred\x9b39m showed "31mred39m", and an 8-bit hyperlink or title string showed its URL or payload. Read each 8-bit introducer as the ESC sequence it stands for: CSI (0x9b) as ESC [, OSC (0x9d) as ESC ], and DCS, SOS, PM and APC likewise, with ST (0x9c) ending strings. Color codes and safe hyperlinks are kept, in their 7-bit form, which every terminal reads and the rest of the renderer parses; any other sequence is dropped whole. Lone 8-bit controls that start no sequence, such as NEL or a stray ST, are still dropped.
…irmalloc#701) * feat(claude-status): optionally link the widget to the status page Claude Status shows "minor" or "major" when something's wrong, but the details are on status.claude.com, which you have to look up. Git Branch and Git Root Dir can already link to the repo or IDE. Press `l` on Claude Status to make the whole widget an OSC 8 hyperlink to https://status.claude.com, the site it reads the status from: label, status and the history strip, and an unknown "?" too, when the page is most worth a look. It's off by default, like the other links, and the editor row shows "status link". Terminals without OSC 8 support show the text as before. * fix(preview): preserve enabled widget hyperlinks --------- Co-authored-by: Matthew Breedlove <[email protected]>
# Conflicts: # src/tui/App.tsx # src/tui/components/ItemsEditor.tsx
The supervisor hands every TUI it starts the same snapshot path, including the one it relaunches when Enter is pressed at "Reload failed". The snapshot was only ever written by ctrl+r and never refreshed or removed afterwards, so it outlived any save made after that ctrl+r. So after ctrl+r, a save, and then a crash, the retry restored the settings from before the save, paired with the original from before the save. The TUI showed widgets that had been deleted and saved, offered Save & Exit for changes that weren't there, and the next save wrote the older settings back over the newer file. Each change to originalSettings after the first load follows a write to the settings file (ctrl+s, Save & Exit, or an install, update or uninstall recording itself), so the TUI now deletes the snapshot when that happens. A retry after such a write loads the file. A crash before any write, the case the retry is for, still resumes from the snapshot in place.
…ith 9 updates (sirmalloc#745) Bumps the dev-dependencies group with 9 updates in the / directory: | Package | From | To | | --- | --- | --- | | [@remotion/cli](https://github.com/remotion-dev/remotion) | `4.0.534` | `4.0.538` | | [@types/bun](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/bun) | `1.4.2` | `1.4.4` | | [eslint](https://github.com/eslint/eslint) | `10.12.0` | `10.13.0` | | [react](https://github.com/react/react/tree/HEAD/packages/react) | `19.3.0` | `19.3.1` | | [react-dom](https://github.com/react/react/tree/HEAD/packages/react-dom) | `19.3.0` | `19.3.1` | | [remotion](https://github.com/remotion-dev/remotion) | `4.0.534` | `4.0.538` | | [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.71.1` | `8.71.2` | | [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) | `5.0.3` | `5.0.5` | | [zod](https://github.com/colinhacks/zod) | `4.6.5` | `4.6.7` | Updates `@remotion/cli` from 4.0.534 to 4.0.538 - [Release notes](https://github.com/remotion-dev/remotion/releases) - [Commits](remotion-dev/remotion@v4.0.534...v4.0.538) Updates `@types/bun` from 1.4.2 to 1.4.4 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/bun) Updates `eslint` from 10.12.0 to 10.13.0 - [Release notes](https://github.com/eslint/eslint/releases) - [Commits](eslint/eslint@v10.12.0...v10.13.0) Updates `react` from 19.3.0 to 19.3.1 - [Release notes](https://github.com/react/react/releases) - [Changelog](https://github.com/react/react/blob/main/CHANGELOG.md) - [Commits](https://github.com/react/react/commits/v19.3.1/packages/react) Updates `react-dom` from 19.3.0 to 19.3.1 - [Release notes](https://github.com/react/react/releases) - [Changelog](https://github.com/react/react/blob/main/CHANGELOG.md) - [Commits](https://github.com/react/react/commits/v19.3.1/packages/react-dom) Updates `remotion` from 4.0.534 to 4.0.538 - [Release notes](https://github.com/remotion-dev/remotion/releases) - [Commits](remotion-dev/remotion@v4.0.534...v4.0.538) Updates `typescript-eslint` from 8.71.1 to 8.71.2 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.71.2/packages/typescript-eslint) Updates `vitest` from 5.0.3 to 5.0.5 - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md) - [Commits](https://github.com/vitest-dev/vitest/commits/v5.0.5/packages/vitest) Updates `zod` from 4.6.5 to 4.6.7 - [Release notes](https://github.com/colinhacks/zod/releases) - [Commits](colinhacks/zod@v4.6.5...v4.6.7) --- updated-dependencies: - dependency-name: "@remotion/cli" dependency-version: 4.0.538 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-dependencies - dependency-name: "@types/bun" dependency-version: 1.4.4 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-dependencies - dependency-name: eslint dependency-version: 10.13.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-dependencies - dependency-name: react dependency-version: 19.3.1 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-dependencies - dependency-name: react-dom dependency-version: 19.3.1 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-dependencies - dependency-name: remotion dependency-version: 4.0.538 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-dependencies - dependency-name: typescript-eslint dependency-version: 8.71.2 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-dependencies - dependency-name: vitest dependency-version: 5.0.5 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-dependencies - dependency-name: zod dependency-version: 4.6.7 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-dependencies ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…00% (sirmalloc#697) * feat(usage): let Block Reset Timer hide until session usage hits 100% * feat(usage): fetch the session percent for Block Reset Timer's under-limit state * test(usage): compare Block Reset Timer's preview with the state off, not a fixed sample * fix(usage): read Block Reset Timer's under-limit percent from the usage API The under-limit hide state read the session percent from the status payload's rate_limits whenever it had one. That percent comes from the session's own last API response, so it stops moving while the session sits idle: with several sessions open, one left idle while another used up the 5-hour limit kept its timer hidden at the limit. After a reset it could also keep showing a timer from the old window's 100%. The state now fetches the usage API's percent even when the payload has one, and keeps it apart from the merged data as apiSessionUsage, so Session Usage and every other widget still show the payload's percent. The API's answer is the account's, cached for 3 minutes and shared by every session, so this costs at most one request per 3 minutes. When the fetch fails, the payload's percent stands in, without an error. * docs(usage): say "block" for the 5-hour window in the under-limit state The under-limit state's checklist label read "when session usage is under 100%", but ccstatusline also says "session" for the Claude Code conversation (Session Clock, Session Cost), and the timers for the 5-hour window say "block". The label now reads "when the 5-hour block is under 100%", and the docs, comments and test names say the block's percent too. "Session" stays where it means a Claude Code session, such as a session left idle. Code names that follow the usage data's `sessionUsage` field keep it. * docs(usage): refer to the usage percent without Session Usage's name Another change renames Session Usage to Block Usage. Say "usage percent" where the under-limit state's docs and two test comments named it, so they read right with either name. * fix(usage): ignore a cached block percent once its block has reset The usage API's answer is cached for 3 minutes, and kept for as long as the API keeps failing. Across a reset, under-limit took that answer's 100% for the new block's, so the reset countdown showed at the start of a fresh block (at 40%, say) until a fetch succeeded. The API's percent now counts only while its block hasn't reset; otherwise the payload's percent stands in, as when the fetch fails. * fix(usage): don't let a stale usage cache decide the under-limit state While the usage API can't be reached (a 429 backoff, a failed request, no credentials), fetchUsageData serves the last good cache however old it is, and nothing tells it apart from a fresh answer: unlike a failed fetch it carries no error. Block Reset Timer's under-limit state took that cache's 5-hour percent as the API's current answer, so the payload's percent never stood in. Through a backoff, which a Retry-After can stretch to an hour, a cache written at 97% kept the reset countdown hidden after the payload had reached 100% in the same block. fetchUsageData now marks the stale fallback it returns, including when a later call serves it again from memory. The prefetch takes the block's API percent only from the API or a current cache, so a stale fallback counts as the API being unreachable, like a failed fetch, and the payload's percent decides, as the under-limit state's docs describe. Only a cache that has outlived a refresh is marked: one older than the 3-minute cache lifetime plus the 30-second lock a fetch holds while in flight. Another session's ordinary refresh serves the cache through that lock too, seconds past its 3 minutes and as current as the shared cache ever is, and marking it would let an idle session's frozen payload percent override the API's for that render. * fix(usage): keep rejected API percentages out of reset timer fallback The under-limit hide state preferred apiSessionUsage and fell back to the merged sessionUsage field. When the status payload omitted its percentage, that merged field retained the API value even if prefetch rejected it as stale or from a block that had already reset. An old cached 100% could therefore show a new block's countdown from a reset-only payload. Read the fallback percentage directly from the original status payload in the render context. Continue preferring the API percentage accepted by prefetch, and treat missing or null payload percentages as unknown so the timer stays hidden. Keep general usage merging and other widgets unchanged. Add prefetch-to-render regression coverage for expired and stale cached 100% readings with missing, null, zero, 97%, and 100% payload percentages, plus an entirely absent payload. Verify that merged usage is preserved while rejected API values cannot decide visibility. Update widget fixtures to supply their fallback percentages through the original payload. Validation: - Both new regression cases reproduced the erroneous countdown before the fix. - Targeted prefetch and Block Reset Timer tests: 96 passed. - bun run lint: passed (TypeScript and ESLint). - bun test: 3,191 passed; two local proxy listener tests failed because the sandbox blocked binding to 127.0.0.1. Both passed outside the sandbox (the status-service file: 30 passed; the usage proxy timeout test: 1 passed). --------- Co-authored-by: Matthew Breedlove <[email protected]>
sirmalloc#698) * fix(renderer): end a merge at a hidden widget that isn't merged onward A merged widget joined the next widget that rendered anything, skipping over widgets that rendered nothing. So with Session Usage merged into a widget that hides itself, the hidden widget took the merge with it to whatever followed: the separator vanished and, in Powerline, every later segment shifted one theme color. A merge now carries past an empty widget only when that widget is merged onward too; otherwise it ends there and the usual boundary returns. The Powerline, plain and auto-align paths all follow the same rule. * fix(renderer): end a merge at a hidden widget in the next-line counters too The Powerline renderer now ends a merge at a widget that renders nothing and isn't merged onward: A (merged), an empty unmerged widget, then B draws a separator between A and B and gives B its own theme color. The per-line counters that tell the next line where to pick up the separator and theme-color cycles still followed the old rule and carried the merge past every empty widget, so they counted one separator and one color fewer than the line drew. On a multi-line Powerline status line that put the next line one step behind: its first separator repeated the previous glyph and, with Continue Theme Across Lines on, its first segment reused the color of the segment before it. Git Branch merged with Git Ahead/Behind hits this whenever the branch is in sync. The TUI preview uses the same counters, so it showed the same. countSeparatorSlots and assignPowerlineThemeSlots now drop the pending merge when they skip an empty widget that isn't merged onward, matching the renderer. A merge still carries past empty widgets that are merged onward themselves. * refactor(renderer): count separator slots one widget at a time countSeparatorSlots had grown a branch per kind of widget inside its loop. Each widget's effect on the count now lives in advanceSeparatorSlots, which keeps the loop to one call and puts the rules for separators, flex separators, hidden widgets and shown widgets side by side. No change in behavior.
|
Owner
Author
|
Merged upstream as sirmalloc#700. |
eric-engberg
added a commit
that referenced
this pull request
Oct 27, 2026
#4 merged upstream as sirmalloc#700, with sirmalloc's guard and live menu selections, so this branch now builds on main rather than #4's branch. #4's ItemsEditor effect that reports the selected row came in twice, once from main and once from this branch, where the picker code moved it; one copy stays.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Why
To pick up a code change while working on the TUI, you had to quit and then redo any unsaved setup and navigation by hand. That's slow when you keep separate windows for plain and powerline setups.
Usage
The header shows
dev reload: ctrl+r. Press ctrl+r to restart on the latest code exactly where you were. The--configfile is optional; it just keeps each window's saved setup separate from your real config.How it works
bun run startbecomes a small supervisor. It relaunches the same command (runtime, script and original arguments) as a child process, and the child runs the TUI.EX_TEMPFAIL). The supervisor then starts a fresh process, which resumes from the snapshot. The snapshot holds:Reload failed (exit N). Fix the code, then press Enter to retry, or q to quit.and keeps the snapshot.kill, or closing the terminal), it passes the signal on to the TUI, waits for it, removes the directory and exits with 128 + the signal, so no TUI is left running on its own.Without the variable nothing changes: no supervisor, no key, and no header note. The module is only loaded through a dynamic import in the interactive branch. The status-line render path loads the same code as before, about 0.7 KB more for the environment check in the entry.
ItemsEditorgains optionalinitialSelectedIndexandonSelectedIndexChangeprops, so the cursor survives a reload. Entering a line still starts at the top, as before.Update: Drop the snapshot once the settings file is written
discardDevReloadSnapshotinsrc/utils/dev-reload.tswheneveroriginalSettingschanges after the first load. After that load, it only changes once the settings file has been written: ctrl+s, Save & Exit, or an install, update or uninstall recording itself. One effect covers all of those writes without touching each save path, so the change stays inside the dev reload code. A retry after a write loads the file. A crash before any write, which is the case the retry exists for, still resumes from the snapshot on the same screen.Demo
Update
Dev reload (CCSTATUSLINE_DEV_RELOAD=1) on the demo's default four-widget line, with sample data only. The flow: delete Git Changes, ctrl+r, delete Git Branch, ctrl+s, then the TUI is killed to stand in for a crash and Enter at "Reload failed" retries. Before, the retry reopens on Edit Line 1 with Git Branch back, and the main menu offers Save & Exit for changes that were already saved. After, it opens the saved line (Model, Context Length) on the main menu with nothing unsaved.
Powerline, before and after:
Plain, before and after:
Testing
bun test: 2466 pass.bun run lint: clean.dist/under Node) exits with 143 and leaves no TUI process or snapshot directory; the same at the retry prompt; closing the tmux session (SIGHUP) leaves nothing behindUpdate:
src/tui/__tests__/App-dev-reload.test.ts: 'keeps the snapshot for a crash after the reload, but drops it once the settings are saved'. It starts from a snapshot, checks the snapshot survives the load, presses ctrl+s and checks it is gone. Without the fix it fails at that last step. With the fix deliberately changed to drop the snapshot on the first load, both this test's first assertion and the existing resume test fail. The file'sbeforeEachnow also pointsCLAUDE_CONFIG_DIRat the temp dir, because a save syncs widget hooks into Claude Code's settings.bun test(scratch HOME and CLAUDE_CONFIG_DIR): 3189 pass, 0 fail, 9144expect()calls across 180 files.bun run lint: exit 0 (tsc --noEmitand ESLint with--max-warnings=0).node node_modules/vitest/vitest.mjs run src/tui/__tests__/App-dev-reload.test.ts src/utils/__tests__/dev-reload.test.ts: 2 files, 20 tests passed.