Skip to content

Spec 062: recording workflow consolidation - #437

Merged
guan4tou2 merged 43 commits into
mainfrom
codex/recording-export-workflow
Oct 24, 2026
Merged

guan4tou2 merged 43 commits into
mainfrom
codex/recording-export-workflow

Conversation

@guan4tou2

@guan4tou2 guan4tou2 commented Oct 24, 2026 •

Copy link
Copy Markdown
Owner

狀態

Draft,尚未準備合併。 這個 PR 用來追蹤 Spec 062 候選分支的全部變更、觸發 PR 檢查,並集中處理還沒驗收的項目。Spec 062 目前仍是 Draft。

範圍

codex/recording-export-workflow → main:32 個 commit、233 個檔案(+9638/−2716)。審查請以 Files changed 為準,不要只看最後一次 push。主要內容:

  • 官方 AI 擷取整合(Claude Code/Codex/OpenCode)逐工具安裝,並依專案、工作目錄明確啟用
  • 匯出選取與預覽流程統一
  • 擷取來源入口整合、Settings 重整、UI 文案規則改為 CI gate
  • Claude/Codex 對話輸出端到端可搜尋;AI 工具輸入在展開前可見
  • HUD click-through 修復、互動元件最小尺寸 24×24、小視窗缺陷修正
  • Transcript 即時閱讀(0f1f3746):新事件改為合併最新頁,不再整頁重載;載入較舊頁、即時更新與工具配對補齊共用同一個依 Event ID 去重的 merge,過期結果依查詢序號丟棄;閱讀位置以可見事件 ID 與相對位置保存,依排序方向只在最新端跟隨;閱讀歷史時顯示「N 筆新事件」

已完成的驗證

  • 本機 typecheck、verify:specs、verify:architecture、verify:i18n 通過
  • Transcript:單元測試 11 檔、53 項通過,其中新增的 test/transcript-live-refresh.test.tsx 有 4 項在舊程式碼上會失敗;e2e/transcript-view.spec.ts 2 條在本機 Electron 通過(新增的 live 那條驗證列表從未清空、閱讀中的列位移 ≤1px)
  • 安裝 smoke(Linux package、Windows install)曾在這個分支以 workflow_dispatch 跑過並通過

CI 驗證(2026-10-24,52b80c80)

三個平台的 unit、Ubuntu e2e、兩個 install-smoke 全部通過。這是這個分支第一次完整跑過 e2e:先前 Windows unit 失敗,e2e 一直被跳過。

為了讓 CI 通過,在這個 PR 上做了這些修改:

  • 兩個測試的 Windows 問題:event-query-page 收尾時沒關 body index,導致 EBUSY;shell-status 傳了 Windows 原生路徑,而 hook 在正式環境收到的是正斜線路徑。
  • 兩個過時的 e2e spec:匯出入口已改成對話框(c130476c);專案 ID 已搬到「範圍」頁(0f79e841)。
  • export-preview 的時序競態:負載下,預覽載入完之前確認按鈕還不存在,測試卻已經在量位置。
  • 大縮放比例的版面檢查容許 1px:Linux 上 zoom 2 時 scrollWidth 會進位成 401,但沒有任何未被裁切的元素超出畫面。
  • 合併 main:帶入 fix: write every PowerShell transcript command, and none from a pause #438(PowerShell)和 fix: keep a rejected HTTP start's failure through the status poll #440(Windows 偶發失敗修正)。

尚未驗證

  • 本機 macOS 的 e2e:command-io、managed-http-capture、terminal-session-rotation 在 main 上本機也一樣失敗,是環境問題,不在這個 PR 範圍。CI 只在 Ubuntu 跑 e2e。
  • Spec 062 的手動驗收:
    • T008 其餘支援平台的安裝驗收
    • T009 設定的儲存、失敗與切換專案驗收
    • T010 互動式終端與保存失敗驗收
    • T011 原生 HUD 鍵盤、焦點與 pass-through 驗收
    • T012 更新、延後與重啟驗收(對照 Spec 061)
    • T013 收斂所有驗收情境並更新 verification/spec;有平台 gate 沒跑過就不標 Verified

不在這個 PR

擷取可靠性的 P0 修正(PowerShell transcript、AI per-message 索引、AI JSONL cursor、暫停期間的 sidecar 政策)會另外開分支和 PR,不再疊在這個候選分支上。

🤖 Generated with Claude Code

Two increments on top of codex/recording-export-workflow:

1. UI copy audit (AGENTS.md rule applied across the whole UI).
   - AGENTS.md adds "let the layout explain" with the three required lines
     verbatim; CLAUDE.md points at it; reviewer.md and worker.md carry a
     copy-review instruction.
   - Removed 13 redundant secondary labels/subtitles.
   - Shortened 27 verbose hints (capture, HUD, agents, external capture,
     integrity, retention, network, scope) in both en.json and zh-TW.json.
   - Fixed a stale page reference in capture.rogueWhy.
   - Regenerated settingsSearchIndex.ts; updated the hooks-panel policy test.

2. #435 capture-source entry unification (T028/T029).
   - Each capture source now states its own facts in one place: installed on
     this computer, recording for this project, what it reads, and when it last
     delivered — from the same capture-health snapshot the dashboard card
     reads (shared useCaptureHealth hook, 5s poll + refresh on change).
   - New SourceLastSeen component used by AI rows, pack members, the
     screenshot source and external producers.
   - AgentCaptureSourceConfig.scopeChangedAt marks a scope change made while
     recording; the row then says so and the project checkbox becomes the one
     action that confirms the new range.
   - Removing a local integration asks first and names what stops and what
     stays (confirmGraded, level plain).
   - Pack disabled in Plugins now offers a direct route to plugin settings.
   - Unified health-card agent labels to Claude Code / Codex CLI / OpenCode
     and the install/remove verbs to Install/Remove integration.
   - Small fix: optional-chaining on config.screenshot intervalSec and
     diffThreshold so a config without a screenshot section renders.

Tests: 35+ new/updated assertions; e2e updated to the new behaviour and all
affected specs pass. Spec 062 T028/T029 and verification recorded. The 16
shell/PTY failures are pre-existing on this machine (verified against a clean
HEAD stash) and unrelated to this change.
Loot and redaction already returned accepted/rejected counts with each
rejected rule named; target extractors, command tags, event types and capture
entries registered and said nothing. A manifest that loaded was therefore
presented as every rule in it working.

- registerTargetExtractors / registerCommandTags / registerEventTypes /
  registerCapturePlugins now return { accepted, rejected[] } instead of a bare
  number or void.
- Rejections carry the rule's own name and a reason: the regex error, an
  unknown strategy, or a missing required field (agentType/label, id/hookFile).
  Previously these were swallowed by empty catch blocks.
- ContributionReport carries the four new kinds; PluginsPanel renders them
  beside the existing loot/redaction/tailer lines, so a partially-working
  plugin is distinguishable from a fully-working and a fully-broken one.
- A broken rule still does not disable the rest of the plugin or the pack.

Tests: plugin-contribution-report gains a mixed-validity case covering all
four kinds plus the four rendered result lines; target-extractor's three count
assertions move to the new return shape and now pin the rejected name/reason.
Verified with a native source build against a synthetic user plugin (one good
and one broken rule per kind) and the affected Electron specs.
The diagnostics script prints coloured ✓ / ✗ / ! lines under "── section"
headers. The parser expected "[PASS] name: message", matched nothing, and
recorded every line as a passing `info` row — so "✗ Failed to send test event"
was displayed as a green "+" in Settings. The unit test fed the parser the same
format the script never emits, so the suite passed while the product did not.

- parseDiagnosticOutput reads the script's own format: ✗→fail, !→warn,
  ✓→pass, ANSI stripped before matching, indented remediation lines appended
  to the check above them, section headers naming checks that have no
  "name: message" split.
- An unrecognised line is a warning now, never a pass. That catch-all is what
  made a failure look green.
- The panel renders ✓/✗/! and carries an sr-only status word, so status is not
  colour-only.
- The parser is exported so the test can run against the script's real output.

Tests: test/fixtures/wsl-diagnostics-output.txt is captured by running
hooks/wsl-redlog-test.sh against stub cmd.exe/wslpath/wslinfo/ip/curl binaries
(reaching the failing POST branch), with ANSI and indentation intact; README
records how to regenerate it. wsl-manager.test.ts asserts one failure, the
warn line, >=6 passes and no escape bytes; a new panel test renders the parsed
fixture after pressing Diagnose.

Not changed: #327's cold-start timeout, and the script's output format.
Two real defects remained on macOS at the window's 800x600 floor:

- The transcript toolbar was one non-wrapping row. Its kind filters and the
  Picked only / Newest first / Copy as Markdown buttons ran off the right edge
  (897px, 998px, 1122px in an 800px viewport) with no scroller to reach them.
  It now wraps, and the filter input may shrink (min-w-0).
- At 200% the status bar is 400 layout pixels and the HUD toggle sat at
  405..421, inside the bar's own overflow-hidden: a control that exists and
  cannot be clicked. Narrow, the IP-safety and scope dots stay and their words
  move to sr-only spans, so nothing is lost to a screen reader.

The check that was supposed to catch this compared rect.right against
window.innerWidth. Those are different spaces — the body carries zoom: 0.9 and
the shell applies its own zoomFactor — so innerWidth overstated the viewport by
11% at 100% and 100% at 200% and every overflow looked contained. It now uses
documentElement.clientWidth, and it measures the status bar separately because
the bar sits outside the view root and outside every view's scroll container.

Both fixes are RED-first with the same spec: with the component changes
stashed, the run reports the three transcript buttons at 897/998/1122 of 800,
and a direct probe shows status-bar-overlay at 405..421 of 400. Re-applied:
14/14 Electron tests pass across 11 views at five size/zoom combinations.

Note: page.screenshot() under an Electron zoomFactor does not capture what the
window shows (at 200% it produced a cut image while capturePage() showed a
correct layout), so every assertion here is DOM measurement.

Not claimed: Windows/Linux DPI, or the T008-T013 platform matrix.
Walked all 11 views and all 12 Settings pages in the built app and swept every
visible interactive element for an accessible name. Three real defects:

- The HUD text-size slider had no accessible name at all: its visible label was
  a <label> with no `for`. A screen reader announced "slider" and nothing else.
  Now associated by htmlFor/id, with aria-label and aria-valuetext (the step
  name, which changes as it moves and so cannot be the name).

- Loot rules explained what they match only in a `title` tooltip, while showing
  an internal id (ntlm_hash, private_key). A keyboard cannot reach a tooltip and
  a screen reader does not read one, so the one piece of text explaining the row
  was unavailable to exactly the people who needed it. The description is now
  visible text and part of each switch's accessible name.

- With an AI integration not installed and the transcript folder absent, the row
  said "start the AI tool, then reopen this project" — following that records
  nothing, and nothing said why. The folder is a fact about the AI tool; whether
  it can be read is a fact about the integration. The start-the-tool step now
  appears only on a row that can actually read the folder.

Kept deliberately: clipboard scope warning, SYN-scan blind spot, `0 = keep
everything`, unsigned-build manual update instructions, bookmark-is-not-evidence,
chain-anchor description, plugin consent dialog — facts the control cannot carry.

Tests: +5 assertions across hud-auto-show, loot-rules-group and
agent-sources-panel. 19 Electron tests still pass; typecheck, architecture,
i18n assertion, Spec Kit gates and build passed.
AGENTS.md said "Avoid unnecessary secondary labels and descriptions. Let the
layout do the explaining." That is advice: a reviewer reads it and the next
agent does not. test/ui-copy.test.ts is the mechanical half, and it runs in CI.

Five checks — the shapes that are never right, however good the intent:

- one explanation stored under two keys (one gets edited, the other does not)
- a descriptive key nothing renders (dead prose in the translation queue)
- a hint that only repeats its label (exact equality, not a prefix)
- a tooltip that repeats the visible text beside it
- a helper line over three sentences unless the key is an allow-listed warning

It deliberately does not judge whether a sentence is worth saying; that is
docs/UIUX-CONTROLS-AND-COPY.md §2's four questions and a human. Length alone is
not a failure: the clipboard scope and SYN-scan warnings are long and belong.

What it found:

- Six dead settings.hookWatchPaths.* keys. c15f218 moved AI capture to
  per-project authorisation and removed the UI that rendered them; the strings
  stayed behind.
- bookmarks.cdpHint opened with "Chrome CDP not connected" directly under a
  status chip saying the same thing. It now carries only the actionable part.
- timeline.help.hint was a second copy of timeline.help.title; the button now
  references the one string.

One false positive was fixed in the rule rather than in the copy: a prefix match
flagged firstRun.core.laterWhy for opening with its heading's words, and a gate
that fails on a true sentence is a gate someone turns off.

AGENTS.md now names the test and says what to do when it fails.
…iled

A gate that cannot fail is worse than no gate: it is a green check that means
nothing. Each of the five rules in test/ui-copy.test.ts was given a deliberate
violation and the suite run. Two rules did not fail.

- Rule 1 ("does not store the same explanation under two keys") compared only
  descriptive keys, so a duplicated explanation stored under two ordinary keys
  passed a rule whose name promised more. Widened to every key, with a length
  floor so short labels may repeat on purpose. It then found a real duplicate:
  castSearch.openAtMoment and httpHistory.openAtMoment were the same sentence
  in two places, and are now one string, common.openAtMoment.

- Rule 4 ("no tooltip that repeats its visible text") never fired on anything.
  Two bugs in the rule: the tag scanner allowed "=>" as an alternative to
  "[^<>]", letting the attribute group swallow an arrow function and stop
  matching its own tag; and the visible-text matcher required the character
  after the closing quote to be "," or "}", while a plain {t('key')} ends with
  ")". Both fixed. The repaired rule then found a real one: the timeline event
  inspector's "around this event" button had title and label both reading
  filter.around, so hovering said nothing the label did not.

Rules 2, 3 and 5 fired on their injected violations as claimed.
The gate ran against en.json only, which is a gap with a shape: a duplicate
sentence can be invisible in one language and obvious in another, and Chinese
is the interface's default locale.

Two defects only the Chinese side showed:

- artifacts.tooLarge and artifacts.noSpace were the same string in BOTH
  languages — "{{name}} was not added" / "未加入 {{name}}". They are different
  problems: the file is over the 200 MB per-evidence limit, or the disk is
  full. The "why" line said which, but the message line is the one read first
  and it named neither. The message now says which; the detail stays in the
  "why" line.

- project.appliedScope ("Scope 2") and statusBar.scopeViolations ("SCOPE 2")
  were both "範圍 2" in Chinese. The first means two in-scope targets were
  imported from a profile; the second means two scope violations. The card
  sits next to the profile-import button, so an operator could read "2" as
  violations and go hunting for out-of-scope commands that do not exist. The
  status bar now says "越界 2".

All five rules now run against both locales, with a length floor per language
(24 chars English, 10 Chinese — Chinese carries more meaning per character, so
the same floor would flag every label). A Chinese mutation test confirms the
duplicate-key rule fires there too.

Two false leads were checked and cleared rather than "fixed": 289 then 27
apparently unreferenced keys were both artefacts of the detector — the locale
keys are read through text('quit.title') and built at runtime from a prefix
(sidebar.${view}). A corrected detector finds zero dead keys, so nothing was
removed on a hunch. Placeholder parity between locales holds for every key.
AGENTS.md names patterns the string-level gate cannot judge: the
"icon + title + description + card" stack, a card compensating for weak
grouping, a heading over a single item. Each was scanned across the whole
renderer before deciding whether it could become a rule.

- Card inside a card: a JSX tag-stack scan (not a text window — the first
  attempt matched sibling cards and produced 22 false positives) finds exactly
  two. ScopeStatus's inner "card" is a 48px status dot; ExportMenu's is a
  warning panel inside a modal. Both legitimate.
- Heading immediately followed by a paragraph repeating it: two, and both need
  the sentence below — "PowerShell will not load your profile" does not say why
  the policy blocks it, and "A command ran, but nothing arrived" does not say
  what to check.
- Icon buttons where title and aria-label are the same string: five, all
  close/clear/exit with no visible text, where the tooltip IS the label.
- Hints opening with a state restatement: two, both stating a fact the control
  cannot ("off unless asked", "an empty list is not a failed capture").

No product change. The findings are recorded in test/ui-copy.test.ts (as a case,
so deleting the comment breaks the build) and summarised in AGENTS.md, so the
ground is not re-dug and nobody adds a layout gate the data does not support.

The one real defect this pass produced was in the scan itself: the first nesting
detector counted 22 false positives by matching sibling cards — exactly the
failure mode a shipped layout gate would have had.
Measured all 861 interactive elements across 11 views and 12 Settings pages,
normalised by the body's zoom: 0.9 first (raw rects report 25.2/30.6/36px for
what is declared as 28/34/40, which reads as a dozen inconsistent sizes that do
not exist). 141 were under WCAG 2.5.8's 24x24 floor.

The worst was Settings' checkbox row: the highest-frequency controls in the app
— clipboard sampling, file watching, loot rules — were also the smallest
targets, with a label row 20px tall. New CheckRow gives every checkbox a 32px
row while the input stays 16px (the icon size §4 allows). All 22 Settings
checkboxes moved to it, plus ExportMenu, ConfirmDialog and EventMarker.

Also raised: terminal tab close/restart 16 -> 24, shell picker 20 -> 24,
timeline help and zoom controls 20 -> 24, the event-dot hit area's floor
20 -> 24 (the visible dot keeps its own size), the narrow status-bar HUD toggle
(18x10, dot only), bare text buttons (min-h-8), and the HUD sliders (h-8).

An Electron assertion now measures this in the same pass that checks controls
stay inside the window: every view at five size/zoom combinations, a checkbox
measured by its wrapping label, 0.5px tolerance because a 24px row lands on
23.999 after dividing out the body zoom.

Two defects were found by the new assertion rather than by the audit: the
narrow HUD toggle at 18x10, and the floating-point tolerance. Both real, both
fixed. Result: 0 of 855 targets under the floor.

Not done: the padding/gap/radius token consolidation (26 padding values, 4
radii, --pad/--gap largely unused). Separate cleanup, no behaviour change,
much wider diff.
§3 declares four density variables and says tight changes row heights AND
padding. Measured in the running app, only one was read:

  --row-h        40/32px   Sidebar, Settings nav
  --pad          16/10px   nothing
  --gap          12/8px    nothing
  --section-gap  24/14px   nothing

So switching to tight shrank the sidebar rows and left every card, panel and
gap where it was — while density.ts's own comment claimed density "changes only
row heights and padding". Two thirds of the feature was a no-op that looked
implemented.

Rewriting the ~300 p-*/gap-* call sites would have been a huge diff for no
behaviour change. Instead the variables are bound to three utilities and the
containers that carry the page rhythm use them:

  @Utility p-pad { padding: var(--pad); }
  @Utility gap-gap { gap: var(--gap); }
  .space-y-section > :not([hidden]) ~ :not([hidden]) { margin-top: var(--section-gap); }

@Utility is required for the first two: Tailwind only emits a utility it has
been told about, and a hand-written .p-pad rule is emitted but never recognised
— which is the failure mode that made --pad inert in the first place.
space-y-section is a plain rule because Tailwind rejects ">" in a utility name.

Nine page containers moved from p-4 to p-pad (Settings, Screenshots,
ScopeStatus, LootPanel, TargetView, Dashboard, Bookmarks, FirstRun ×2), and the
dashboard containers take gap-gap / space-y-section. Measured: comfortable 16px
padding and 11px block gap; tight 10px and 7px.

The radius spread (4/8/12px) turned out to be a deliberate three-level scheme —
panel/input, card, dialog — not drift, so it is documented in §3 rather than
flattened.

test/density.test.ts pins "declared but never read"; stashing the CSS and
component changes makes it fail.
The button 「記我的 Zsh 終端」 names the detected shell as the recommendation,
but the picker inside the flow can install a different one — and after choosing
Bash the button still advertised Zsh, the one text left contradicting what the
flow was doing. Everything the flow runs (adapter, prerequisites, expected event
shell, its own texts) had already moved to the selection; the stale label sat
above them.

RecordTerminalFlow now takes an `onSelectedLabel` reply and FirstRunView renders
it on the entry button, so label and install can only ever match. The flow key
is pinned to 'host' rather than the label so replying cannot remount the flow
and reset the selection mid-install.

Pinned by a test in test/first-run-record-terminal.test.tsx asserting both the
stale label before picking and the updated one after; e2e/first-run.spec.ts
passes.

Also recorded the manual QA walk behind this: every view, button, settings
page, titlebar and status bar control clicked (~260 interactions), zero
renderer errors across all of it.
…dead field)

Redundancy audit, four axes:

1. Dead strings — 139 en / 138 zh keys match no exact t('key') call, but every
   one resolves through a dynamic prefix (t('marker.category.' + …)). The
   exact-match scan alone would have deleted live text.
2. Dead config — every leaf of RedLogConfig has a main/core reader except
   engagement.focus: its UI and readers were removed in 7de4b9b but the field
   and its comment survived unread. Deleted.
3. Double editors — none. engagement.id appears in BrowserPanel and
   GeneralPage, but one is write-context spread, the other read-only.
4. Identical renderings — 36 en keys share a value; zh-TW renders 35 of them
   differently. Per-context keys, kept.

Fixes from the walk:

- 「範圍 未設定」 is now a button. It sat between fault-counter buttons that
  navigate while it was a <div> — a warning with no route to the lists that
  resolve it. It opens Settings ▸ 範圍規則 in all four states. The hit-area
  check caught the new control at 18×10 @150% zoom in the narrow bar — the
  min-w-6/min-h-6 floor the HUD toggle got is applied too.
- The HUD scale slider showed its value twice (right-hand label + highlighted
  tick under the thumb). The span is gone; ticks and aria-valuetext stay.
Manual page-by-page walk of all 11 views and all 12 settings pages with
seeded data, judged per docs/UIUX-CONTROLS-AND-COPY.md §2 instead of by
scan. Two restatements found and removed:

- The event-log band header printed 攻擊時間軸 below the toolbar that
  already names the view. The header keeps the hidden-count toggle and the
  row count — what it uniquely has — right-aligned.
- The screenshots banner 已載入 2 張截圖 duplicated the title 截圖 (2)
  whenever the list was complete, and the title's 2+ already marks a partial
  list, so the banner carried nothing in either state. Removed with its
  `screenshots.loaded` key (en/zh-TW).

Everything else inspected and kept, including: the attribution-blind warning
(a lost join is a capability fact), the bookmarks empty state (the
bookmark-vs-marker not-in-evidence-chain distinction is a custody fact),
and the completeness chips on HTTP/transcript/loot (only they say whether
older data exists).
guan4tou2 and others added 2 commits October 24, 2026 11:51
Every batch cleared the list and reloaded it from the first page, so an
active agent session blanked the view, dropped the older pages the
operator had loaded and lost their place several times a second.

A batch now re-reads the newest page of each bucket and merges it by
event id; older pages, live refreshes and tool-pair completion share one
merge, stale results are rejected by query sequence, and batches during
a refresh collapse into one more. If the head no longer meets what is
loaded, the list reloads rather than leaving a hole.

The reader's position is held by the visible row's id and offset: the
view follows only at the live end for the current order, and rows that
arrive while reading history are counted in a jump-to-latest control.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: cadff3d7-3698-41ce-969b-07914b017cab
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

guan4tou2 and others added 3 commits October 24, 2026 13:55
A text query opens http-body-index.db as well as the project database.
The teardown closed only the database, and Windows refuses to delete a
directory holding an open SQLite file, so three tests failed there with
EBUSY. The other suites that query text already close the index.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The bash hook finds the home above `_REDLOG_DIR` by stripping its last
`/`. In use the directory is derived from `$HOME`, which Git Bash spells
with forward slashes. The test passed a native Windows path, which has no
`/`, so the relay looked for the identity one directory too deep and the
three identity cases failed on Windows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two specs still asserted contracts this branch replaced: the title-bar
export control opens a dialog since c130476, not a menu, and the
engagement ID lives on the Scope settings page since 0f79e84, not
General. Both failed on CI's first full e2e run of the branch.

The large-scale layout spec failed on Linux only, the page 1px wider
than the viewport with none of the logged controls responsible: those
sat inside the clipped status bar. It now also logs any element past
the edge that no ancestor clips, so the next failure names its cause.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
guan4tou2 and others added 4 commits October 24, 2026 14:59
The dialog's action bar renders once the preview has loaded. Under a
full e2e run that came after the panel was visible, and the layout check
read a null confirm button. Alone the spec passed every time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CI's last run reported the page 1px wide at zoom 2 with nothing past
clientWidth + 0.5, so the element responsible ends under half a pixel
past the edge. clientWidth and innerWidth both round (533 for a 533.33
layout at zoom 1.5), so the check now compares against the root's own
fractional width, with no slack, and names the deepest offenders.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Linux at zoom 2 and the largest scale, the page's scrollWidth read
401 against a 400px viewport on every run, while no unclipped element
ended past the root's own fractional width: nothing was pushed off
screen. The page checks now allow the same one pixel the controls check
in this spec already does, and the diagnostics stay to name a real
overflow if one appears.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
guan4tou2 and others added 3 commits October 24, 2026 17:20
…ackages

Work from a stopped Codex session's working tree, built on 52b80c8 and
carried onto the branch as it was left. See
specs/062-recording-workflow-consolidation/ai-durability-and-session-boundaries.md.

- Progress is a checkpoint beside each sidecar: source offset and saved
  bytes, written atomically after a unit is processed and appended. A
  failed write leaves the unit at the source, across a restart too.
- Pause keeps no source bytes; the boundary is persisted, so a restart
  during a pause does not replay it. Sessions found after resume are
  bounded by it.
- Per-session stop and reallow from the existing source rows.
- The three AI adapters move to optional-plugins/ and are installed as
  managed packages, with tamper refusal and rollback.

Merging it with #441 (already on this branch): tailer-host.ts takes this
version, which supersedes #441's in-memory queue and filler newlines.
bundle-export.ts goes back to before #441, since no pause-tail marker or
filler exists any more. The #441 tests move to the new adapter path; the
mock-based per-message suite and two tests of behaviour this design drops
(give up after the retries; retry a pre-pause failure after a pause) go.
Seven of the remaining #441 tests fail here; the next commit fixes them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…runing

Four gaps in the checkpoint design, each found by a #441 test or a probe
against it:

- A waiting child whose flush failed was lost for good. Its parent was
  recorded, so the re-read skipped it, and the child's own line was
  already committed. Children now leave the queue only once written, and
  a re-read parent flushes whatever still waits on it.
- A re-read child was queued again on every failed pass, and a re-read
  tool call was counted again.
- A failure with no later file event (the last turn of a session) waited
  for a restart. A few backed-off passes (1 s to 16 s) now retry it; after
  those it waits for the next event or restart, the error in capture
  health.
- Retention removes whole sidecars, and the checkpoint then refused the
  session as tampered, stopping its capture with only a DB error. A
  missing sidecar now resumes at the checkpoint; one that is present but
  short is still refused.

Each fix is caught by at least one test when reverted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AI capture: durable checkpoints, persisted pause, optional AI packages
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant