Repository navigation
refactor(render): request-scoped render orchestration with byte parity (#15) - #25
Merged
Merged
Conversation
#15) Extract the orchestration out of the one-shot entry point into a request-scoped function so a shared-process runner (#14) can serve renders without process-global state: - src/render.ts (new): renderStatusLines(data, loaded, invocation) returns the output text; stdout/process.exit stay in the entry point. Per-request styling (chalk.level + COLOR_MAP) is set at the top of the final synchronous formatting section, serializing it per request. - src/types/RenderContext.ts: RenderInvocation snapshot (configPath, cwd, env, terminalWidth) resolved once per request by the entry point. - src/utils/config.ts: loadSettingsFrom(configPath)/saveSettingsTo() resolve paths per call; module-level settingsPath/lastLoadError are no longer mutated by the render path (TUI/CLI keep the old wrappers). - src/utils/usage-fetch.ts: the in-memory usage result moved into an explicit UsageMemoryCache created per request; usage-prefetch creates one per prefetch call. - Terminal width: probed once by the entry point (memoized probe + L2 session cache unchanged) and passed in via the invocation; the render path no longer reads the process memo itself. Config validation, atomic migrations, warning badges and malformed-file recovery are preserved verbatim. Parity proof: 8 CLI scenarios (multiline layouts, ansi16/ansi256/ truecolor + theme, gradient override, powerline with themes across lines, custom command, malformed-config badge, updatemessage write) captured from the pre-refactor binary are byte-identical after the refactor; fixed transcript fixture, fixed env, fixed CCSTATUSLINE_WIDTH. src/__tests__/render-parity.test.ts replays the golden bytes against renderStatusLines and covers per-request config isolation. Resource gate (scripts/benchmark-render.py, 80 renders/run, warm cache, alternating runs): paired base(74ba67f) vs refactored runs measured -4.3%..+2.5% CPU/render (pair average +2.6%, within the <=+5% gate); peak RSS median 102.7 MB -> 86.3 MB (no growth). Output hash identical across every run. Co-Authored-By: Claude Code <noreply@anthropic.com>
axisrow
force-pushed
the
feat/request-scoped-render
branch
from
September 27, 2026 02:36
a91b085 to
6df95fc
Compare
axisrow
commented
Sep 27, 2026
axisrow
left a comment
Owner
Author
There was a problem hiding this comment.
Вердикт: Approved — один неблокирующий инлайн-комментарий, изменений не запрашиваю.
Примечание: head в задании (a91b085) устарел — это коммит до rebase; ревьюю текущий head PR 6df95fc, он совпадает с чистым чекаутом; дифф против origin/main.
- Оркестрация сохраняет поведение: в render.ts между chalk.level/updateColorMap и форматированием строк нет await (единственные await ниже — записи updatemessage после сборки outputLines); COLOR_MAP читается на месте вызова, модули префетча её не трогают, поэтому перенос chalk-настройки за Promise.all безопасен.
- Точка входа: initConfigPath по-прежнему предшествует getConfigPath в main; пустой text ничего не печатает; защитный config-badge кладётся в outputLines и не теряется; типы RenderInvocation.terminalWidth и RenderContext.terminalWidth согласованы (number и null).
- config.ts: loadSettingsFrom/saveSettingsTo не мутируют модульное состояние; обёртки loadSettings/saveSettings сохраняют lastLoadError для TUI (App.tsx) и CLI (cli.ts); тест изоляции ассертит, что getConfigLoadError() остаётся null после запросных загрузок.
- usage-fetch: cache протянут через все пути (быстрый путь памяти, файловый кэш, getStaleUsageOrError, cacheUsageData), семантика errorMaxAge сохранена; внешних вызывающих fetchUsageData нет — ставший обязательным options ловится компиляцией.
- Парити-тесты: golden replay по сценариям s1-s7, badge на malformed, запись updatemessage строго в invocation.configPath (remaining 5→4 ассертом). Чувствительность к ubuntu-CI проверена чтением: git-виджеты дают одинаковый fallback и на отсутствующем cwd (обход вверх по дереву ловит ошибку), и на not-a-repo; custom-command наследует реальный cwd процесса, а не замоканный; время-зависимых виджетов в сценариях нет.
- Follow-up из описания (cwd/env пока не потребляются глубокими провайдерами) — заявленная область, не блокирует.
- Неблокирующее: src/render.ts, строки 188 и 207 — невидимый литеральный NBSP в строке замены вместо прежнего escape-а; предлагаю вернуть escape (обратный слэш + u00A0) для читаемости, детали в инлайн-комментарии.
Review follow-up on #25: the two .replace(/ /g, ...) calls carried a literal U+00A0 in the replacement string (invisible in editors) instead of the original ' ' escape. Byte-identical output, but one editor normalization or copy-paste would silently break the VSCode trim protection. Back to the explicit escape. Co-Authored-By: Claude Code <noreply@anthropic.com>
10 tasks
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.
Summary
Closes #15 (delivery step 1 of the daemon epic #14). Extracts the render orchestration out of the one-shot entry point into a request-scoped function so a future shared-process runner can serve renders without process-global state.
src/render.ts(new):renderStatusLines(data, loaded, invocation)— accepts validated input, the config loaded for this request, and an explicit invocation snapshot; returns text. stdout/process.exitstay in the entry point (src/ccstatusline.ts, now thin).RenderInvocation(src/types/RenderContext.ts): per-request snapshot —configPath,cwd, env snapshot,terminalWidth— resolved once by the entry point.src/utils/config.ts:loadSettingsFrom(configPath)/saveSettingsTo(configPath, settings)resolve paths per call; module-levelsettingsPath/lastLoadErrorare not mutated by the render path anymore (TUI/CLI keep the old wrappers, untouched).src/utils/usage-fetch.ts: the single in-memory usage result became an explicitUsageMemoryCache, created per request byusage-prefetch(createUsageMemoryCache()).chalk.level+updateColorMap()moved to the top of the final synchronous formatting section (noawaitinside) — the serialization option from the issue; concurrent requests cannot interleave inside the formatting pass.Parity proof
8 CLI scenarios captured from the pre-refactor binary, then replayed after the refactor —
diff -rof raw stdout bytes: identical, repeats stable:Fixed transcript fixture, fixed env, fixed
CCSTATUSLINE_WIDTH. Golden bytes are checked in:src/__tests__/render-parity.test.tsreplays them againstrenderStatusLinesand covers per-request config isolation (a rejected load reports its own error without touching module state).Resource-regression gate
scripts/benchmark-render.py, 80 renders/run, warm caches, alternating base/refactored runs (transcript 13 MB, 4 parallel workers):74ba67fPair average +2.6% — within the ≤ +5% gate. Peak RSS median 102.7 MB → 86.3 MB (5×
/usr/bin/timeruns each), no growth. Output hash identical across every run (454f8606…), including the cold initial baseline at 427.0 ms CPU/render (system noise dominated the first pair; the warm alternating pairs above are the honest comparison).Tests
bun test: 2504 pass, 0 fail (156 files) — single final run on the committed tree.bun run lint: clean (tsc + ESLint, no suppressions). Includes lint fixes for pre-existing errors from the #12/#13 commits (separatestylecommit).Known follow-ups
cwd/envare carried inRenderInvocationbut not yet consumed by deep providers (custom-command exec env, usage-fetch proxy/credential env reads) — that inversion lands with the shared-process sub-issues.