Skip to content

feat(macapp): GUI correctness, safety, and accessibility hardening (epic #991) - #1021

Open
dennisonbertram wants to merge 41 commits into
mainfrom
feat/macapp-gui-hardening
Open

feat(macapp): GUI correctness, safety, and accessibility hardening (epic #991)#1021
dennisonbertram wants to merge 41 commits into
mainfrom
feat/macapp-gui-hardening

Conversation

@dennisonbertram

@dennisonbertram dennisonbertram commented Jul 30, 2026

Copy link
Copy Markdown
Owner

Summary

Implements epic #991's 8 child slices on one branch, test-first per slice, followed by an adversarial review and production-review repair:

  • U1 — Stop transcript autoscroll when the user has scrolled up.
  • U2 — Failed collection loads render an inline error with retry, instead of endless skeletons.
  • U3 — Reliable run-control acknowledgements; no call silently fails.
  • U4 — Guard conversation lifecycle (new/fork/undo) against racing an active run.
  • U5 — Delete and undo require a preview confirmation before anything destructive happens.
  • U6rewind_refused becomes a real, actionable state with its own, more severe force confirmation.
  • U7 — Cursor-aware Up/Down prompt-history navigation that doesn't destroy in-progress drafts (closes the gap left by feat(macapp): composer — submit, multiline, prompt history #927).
  • U8 — Accessibility for rows/toggles, and settings actions whose result isn't erased by their own reload.

Closes #992
Closes #993
Closes #994
Closes #995
Closes #996
Closes #997
Closes #998
Closes #999
Related: #991

Production-review repair

Exact repaired head: cec0a6654da96c7ef3ab812c489338f72c4fd6db.

The repair merged current main at b3afc7ec487c60762a91a1219ceb92c523ef0e78 and preserves #1008 persisted/live replay deduplication and #1028 failed/cancelled terminal reconciliation. It adds:

  • request-generation ownership for run start, answer, pending input, catalog, conversation, activity, rewind, open-conversation, and durable sync work;
  • one cancellable, generation-checked autoscroll task plus Reduce Motion and Jump to Latest behavior;
  • single-flight approve/deny/steer with exact draft restoration on failure;
  • rewind/lifecycle busy guards and accessible disabled reasons;
  • stale-row refresh failures with Retry;
  • correct duplicate/declined prompt-history recall bookkeeping; and
  • sealed terminal usage/cost reconciliation that survives durable-row replay and late duplicate-stream events;
  • pending-question view identity keyed to callID so prior answers cannot cross prompts;
  • activity refresh that discards only stale run-scoped todos while retaining valid tasks/runs; and
  • conversation-bound rewind refusals and force retries;
  • per-run usage/cost ownership with immediate follow-up reset and late-prior-run rejection; and
  • sealed accounting for completed, failed, and cancelled terminal events;
  • synchronous force-rewind claim before alert dismissal;
  • forced-local-cancel run identity cleanup;
  • independent tasks/runs application while todos is slow; and
  • authoritative sealed terminal cost status immune to late duplicate deltas;
  • accounting reservation bound to the exact submitted run ID;
  • reserved startup refresh ownership checks before loading-state mutation; and
  • conversation-scoped transcript pin/autoscroll identity.

External scheduled-run control identity remains #1007 and is intentionally not implemented here; external conversation-stream activity still contributes to the #995 busy guard.

Test evidence

Review evidence

  • Initial adversarial review: 15 findings confirmed, 14 fixed on-branch.
  • Independent production review at 1f2444b2480b5832139318e4fa034f4240d92b8d confirmed the remaining async/control/process gaps; the repair above closes those source and automated-test findings.
  • Impact map, engineering and intent logs, plan/residual indexes, and the master docs index are now present.
  • Hosted live-harnessd caught the terminal-accounting replay loss; a red reducer regression and the exact live test are green.
  • The final Codex passes found pending-answer identity, partial/slow activity refresh, reserved-refresh loading ownership, conversation-scoped transcript pinning, rewind conversation/confirmation ownership, forced-cancel cleanup, submitted-run/per-run accounting, failed/cancelled sealed accounting, and terminal cost-status authority gaps; each was observed red, repaired, and is covered by focused regressions at cec0a665.
  • Residual/manual obligations remain tracked in docs/residual-review-findings/feat-macapp-gui-hardening.md and fix(macapp): setCost failure status erased by its own reload + deferred manual smokes from #991 #1020.

Pending live proof

Installed-app macOS smokes and the separate Settings-specific setCost root-cause investigation remain pending. Exact-head hosted checks are green, but safe stacking still puts the already-owned #1039 / PR #1041 cleanup repair ahead of #1021. None of the pending native/Settings proof is claimed or waived, and this PR must not merge until those follow-ups are reconciled.

Deviations

  • Single PR instead of 8 independent merges. The units overlap heavily in ChatView.swift, ProjectSession.swift, and SessionsView.swift, so preserving the already-existing child commit stack in one repaired PR is safer than duplicating or splitting code now.
  • Literal caret awareness remains deferred. The macOS 14 platform floor lacks SwiftUI's newer selection binding; the implemented history behavior protects in-progress drafts. Tracked in fix(macapp): setCost failure status erased by its own reload + deferred manual smokes from #991 #1020.

Plan: docs/plans/2026-07-30-001-feat-macapp-gui-hardening-plan.md
Impact map: docs/plans/2026-07-31-pr-1021-gui-hardening-repair-impact-map.md

🤖 Generated with Compound Engineering

Behavioral tests added: R1 (auto-scroll only near bottom), reachability
wiring check.

Test runner output (expected: all failing/compile error - TranscriptScrollPin
and Layout.autoscrollPinThreshold do not exist yet):

  error: cannot find 'TranscriptScrollPin' in scope (x6, one per test)
  error: type 'Layout' has no member 'autoscrollPinThreshold' (x3)
  error: fatalError (build failed, tests could not run)

These tests will pass after the implementation in the next commit.
Implementation for tests added in 808276a.

TranscriptScrollPin is a pure value type (starts pinned; update(distanceFromBottom:)
unpins past Layout.autoscrollPinThreshold, inclusive at the boundary; re-pins on
return to 0; stays pinned on negative/overscroll distance). TranscriptView wires it
via a PreferenceKey carrying the bottom anchor's frame in a named coordinate space
attached to the ScrollView, feeding (anchorMinY - viewport height) into
pin.update(distanceFromBottom:); scrollIfPinned now guards on pin.isPinned instead
of the previously never-mutated pinnedToBottom constant.

Test runner output (expected: all passing):

  ✔ Test "a fresh pin starts pinned to the bottom" passed
  ✔ Test "staying at distance 0 keeps the pin pinned" passed
  ✔ Test "scrolling past the threshold unpins autoscroll" passed
  ✔ Test "distance exactly at the threshold is still pinned" passed
  ✔ Test "scrolling back to the bottom re-pins autoscroll" passed
  ✔ Test "overscroll bounce reports a negative distance and stays pinned" passed
  ✔ Test "transcript autoscroll is actually wired to a live scroll pin" passed
  Test run with 181 tests in 41 suites passed (up from 174/40 baseline)

Behavioral tests covered: R1 (happy/edge/boundary/overscroll cases), reachability
wiring check.
Files changed: macapp/Sources/GoCodeUI/TranscriptScrollPin.swift (new),
macapp/Sources/GoCodeUI/ChatView.swift, macapp/Sources/GoCodeUI/DesignSystem/Layout.swift
Regression test added that would fail if the geometry plumbing behind the
scroll pin in f90417b is stripped out while leaving the pin.update(...) call
site textually intact (e.g. replaced with a hardcoded distance) - a gap the
red-commit wiring test alone does not close, since it only checks the consumer
side.

Full test suite output:

  Test run with 182 tests in 41 suites passed after ~5.1 seconds
  (0 failures; up from 174 tests / 40 suites baseline)

Regression scenarios covered:
- ChatView.swift declares .coordinateSpace(name: scrollSpace) on the ScrollView
- The bottom anchor's frame is carried by TranscriptBottomAnchorKey
- pin.update(distanceFromBottom:) is fed via .onPreferenceChange(TranscriptBottomAnchorKey.self),
  not a placeholder constant
…y (macapp)

Behavioral tests added: CollectionLoadState state-table extension
(failed carries a message, showsError, showsPlaceholder excludes failed)
and ProjectSession-level stub-driven tests (per-collection error message,
retry recovery, previously-loaded rows survive a failure, per-collection
isolation), plus a CollectionErrorState reachability check.

Test runner output (expected: all failing — compile-time red, since
CollectionLoadState.failed has no associated value yet):

  error: enum case 'failed' has no associated values
  error: value of type 'CollectionLoadState' has no member 'showsPlaceholder'
  error: value of type 'CollectionLoadState' has no member 'errorMessage'
  error: value of type 'CollectionLoadState' has no member 'showsError'
  error: fatalError

These tests will pass after the implementation in the next commit.
…retry

Implementation for tests added in 728107a.

CollectionLoadState.failed now carries the server's own message, plus
showsError and showsPlaceholder(itemCount:) helpers so views stop
hand-rolling `state != .loaded` skeleton checks. A failed load is
deliberately excluded from showsPlaceholder — the bug was a failure
rendering identically to an endless loading skeleton.

New CollectionErrorState view (icon + verbatim server message + Retry),
mirroring StartupFailureView's existing "this failed, here is why, retry"
shape at inline/per-collection scale. Wired into every consumer: task/run
sections in ActivityView, the conversations list and checkpoints in
SessionsView, providers/models tabs in SettingsView, and the provider and
model lists in ModelSettingsView (which also gained a per-collection
failed(message) instead of a bare failure marker).

ProjectSession's eight `= .failed` assignments now carry
error.localizedDescription; the existing statusMessage toast write is
unchanged, so ProjectSessionActivityTests still pass unmodified in intent.

Test runner output (expected: all passing):

  swift build: Build complete!
  swift test: Test run with 192 tests in 43 suites passed after 4.935 seconds.
  swift format lint --strict --recursive Sources Tests: clean (no output)

Behavioral tests covered: R2 (CollectionLoadStateTests state-table
extension; ProjectSessionLoadStateTests per-collection message, retry
recovery, per-collection isolation, previously-loaded rows survive a
failure; CollectionErrorState reachability).

Files changed:
- macapp/Sources/GoCodeUI/DesignSystem/CollectionLoadState.swift
- macapp/Sources/GoCodeUI/ProjectSession.swift
- macapp/Sources/GoCodeUI/ActivityView.swift
- macapp/Sources/GoCodeUI/SessionsView.swift
- macapp/Sources/GoCodeUI/SettingsView.swift
- macapp/Sources/GoCodeUI/ModelSettingsView.swift
- macapp/Tests/GoCodeUITests/ProjectSessionLoadStateTests.swift (format-only)
…wiring

Regression tests added that would fail if the change in b3bb036 is
reverted.

Two angles distinct from the behavioral tests already covering
CollectionLoadState's own logic and ProjectSession's per-collection
messages:

- Per-file wiring: the existing module-wide reachability check is
  satisfied the moment any one view wires CollectionErrorState in, so a
  revert that drops the wiring from five of the six U2 consumers while
  leaving it in the sixth would pass silently. A new test pins
  ActivityView, SessionsView, SettingsView, and ModelSettingsView
  individually, each for both `CollectionErrorState(` and `.showsError`.
- A different integration point: ModelSettingsModel holds its own
  CollectionLoadState via client.modelSettings(), never exercised by the
  ProjectSession-driven tests (which cover client.models()/providers()/
  conversations()). Catches a revert of ModelSettingsView.swift's
  `.failed(error.localizedDescription)` back to a bare `.failed`.

Kept the new ModelSettingsModel test inside the existing `.serialized`
ProjectSessionLoadStateTests suite rather than a suite of its own: both
share LoadStateStubProtocol's single global handler, and a separate suite
running concurrently with this one raced it (observed one flaky failure
before the merge; stable across 3 repeated full-suite runs after).

Full test suite output:

  swift build: Build complete!
  swift test (x3 consecutive runs): Test run with 194 tests in 43 suites
    passed after ~4.6-4.9 seconds each run, 0 failures.
  swift format lint --strict --recursive Sources Tests: clean (no output)

Regression scenarios covered:
- Partial revert of the per-view CollectionErrorState wiring (any one of
  the four remaining consumer files).
- Reversion of ModelSettingsModel.load()'s failure branch to a bare
  .failed with no message.
… (macapp)

Behavioral tests added: R3 (cancel/approve/deny/answerInput surface their
server acknowledgement, pending questions clear only after the server
accepts) and R4 (an answer set is submittable only when every question has
a non-blank answer).

RunControlAckTests.swift drives RunSession against a method+path-keyed
HTTP/SSE stub (approve/deny/cancel failures surface via connectionError; a
409 on answerInput leaves pendingQuestions non-nil; a failed cancel does
not let a second press force-abandon the stream). AskUserAnswersTests.swift
covers the new AskUserAnswers.isComplete predicate directly.

Test runner output (expected: all failing -- compile-time red, since
AskUserAnswers does not exist yet):

  error: cannot find 'AskUserAnswers' in scope (x5, one per test)
  error: fatalError (build failed, tests could not run)

These tests will pass after the implementation in the next commit.
Implementation for tests added in 784bb19.

RunSession.cancel/approve/deny/answer now await the server call inside a
do/catch, matching the pattern steer() already used correctly: a
HarnessError sets connectionError to its message, a transport error sets
it to localizedDescription. A failed cancel resets cancelRequested to
false so the operator's next press retries the cooperative request rather
than escalating to a local force-kill. answer(_:) validates completeness
against pendingQuestions via the new AskUserAnswers.isComplete and clears
pendingQuestions only after answerInput returns successfully -- a
rejected answer leaves the prompt on screen.

New AskUserAnswers.isComplete(prompt:answers:) replaces
`answers.count < prompt.questions.count`, which counted a field cleared
back to "" (or left whitespace-only) as answered. Shared by RunSession's
guard and AskUserView's Send-button `disabled` predicate in ChatView.swift
so both read the same rule.

Only whitespace reformatting (swift-format) applied to the red-committed
RunControlAckTests.swift; no assertions changed.

Test runner output (expected: all passing):

  swift build: Build complete!
  swift test: Test run with 205 tests in 45 suites passed after 4.698 seconds.
  swift format lint --strict --recursive Sources Tests: clean (no output)

Behavioral tests covered: R3 (RunControlAckTests -- approve/deny/cancel
failure surfacing, answerInput 409 keeps pendingQuestions, cancel-retry
stays cooperative), R4 (AskUserAnswersTests -- completeness predicate).
Files changed: Sources/GoCodeUI/AskUserAnswers.swift (new),
Sources/GoCodeUI/RunSession.swift, Sources/GoCodeUI/ChatView.swift
…ent wiring

Regression tests added that would fail if the change in ef88bef is
reverted. Confirmed against the pre-fix source at 8f2e412: RunSession.swift
there still contains `try? await client.cancel/approve/deny/answerInput`,
and ChatView.swift there still contains `answers.count < prompt.questions.count`.

Two angles distinct from the behavioral tests already covering the
runtime behavior:
- RunControlAckTests exercises each of cancel/approve/deny/answer through a
  live stub, proving the *behavior* is fixed, but a partial revert of one
  call site back to `try?` would only be caught if that specific method's
  test happened to still be run against the reverted code -- this pins the
  absence of the bug shape across the whole file in one assertion.
- AskUserAnswersTests pins the predicate's own logic in isolation but does
  not prove AskUserView's Send button still calls it; a revert that
  reintroduces `answers.count < prompt.questions.count` while leaving
  AskUserAnswers.swift untouched (and its own tests passing) would slip
  through that suite alone.

Full test suite output:

  swift build: Build complete!
  swift test: Test run with 207 tests in 45 suites passed after 5.744 seconds
    (0 failures; up from 194 baseline / 205 after the green commit).
  swift format lint --strict --recursive Sources Tests: clean (no output)

Regression scenarios covered:
- Reversion of any of RunSession's four fixed call sites back to `try?`
- Reversion of AskUserView's Send-button predicate to the pre-fix count
  comparison
Behavioral tests added: newConversation/fork/undo refuse while a run is
active (R5); idle path unaffected; guard clears once the run completes;
deleteConversation's internal newConversation() call inherits the guard.

Test runner output (expected: 5 of 6 failing, right reason -- server calls
went through / state mutated / no refusal message):

  Test Suite 'ProjectSession lifecycle guard' failed after 0.138 seconds with 10 issues.
  ✘ newConversation refuses while a run is active -- core regression: conversationID nil != "conv_1", isBusy false != true, statusMessage nil != contains "running"
  ✘ fork refuses while a run is active and never reaches the server: fork request WAS recorded, statusMessage missing "running"
  ✘ undo refuses while a run is active and never reaches the server: undo request WAS recorded, statusMessage missing "running"
  ✔ with no active run, fork/undo/newConversation behave exactly as before (idle path unaffected -- passes today, pinned)
  ✘ a previously refused fork succeeds once the run completes: fork request WAS recorded while still busy
  ✘ deleteConversation's internal reset inherits the guard for its own busy run: conversationID reset to nil instead of refused

These tests will pass after the implementation in the next commit.
…e run

Implementation for tests added in a19fd39.

newConversation/fork/undo now refuse (with a statusMessage naming the
action) while run?.isBusy is true, at the shared ProjectSession boundary
(KTD-9) so every one of the six call sites is covered by a single guard,
including deleteConversation's own internal newConversation() call.

Test runner output (expected: all passing):

  ✔ newConversation refuses while a run is active -- core regression
  ✔ fork refuses while a run is active and never reaches the server
  ✔ undo refuses while a run is active and never reaches the server
  ✔ with no active run, fork, undo, and newConversation behave exactly as before
  ✔ a previously refused fork succeeds once the run completes -- guard is state-based, not sticky
  ✔ deleteConversation's internal reset inherits the guard for its own busy run
  Test run with 6 tests in 1 suite passed after 0.070 seconds.

Full suite: 213 tests in 46 suites passed (up from 207 baseline).

Behavioral tests covered: R5 (newConversation/fork/undo refuse during an
active run, at the shared ProjectSession boundary).
Files changed: macapp/Sources/GoCodeUI/ProjectSession.swift
Regression tests added that would fail if the guard in 540b392 is
reverted or narrowed to a subtly wrong predicate.

Full test suite output:

  Test run with 215 tests in 46 suites passed after 5.450 seconds.

Regression scenarios covered:
- The guard reads run?.isBusy == true, not run?.isBusy != false: before a
  project ever connects, run is nil, and nil must never be treated as busy
  -- newConversation/fork/undo all proceed as no-ops with no false-positive
  statusMessage.
- Each of the three refusal messages names its own action and is distinct
  from the other two, so a revert to one shared generic string would be
  caught even though it still mentions "a run is active".
…iews

Behavioral tests added: DeletePreview names the title and a known message
count and never fabricates one (R6); a very long title is bounded; UndoPreview
quotes the last user prompt or reads neutrally with none, and flattens a
multi-line prompt; lastUserPrompt finds the most recent .userPrompt item in a
transcript; reachability check that delete/undo route through the shared
destructiveConfirmation( presentation instead of firing immediately.

Test runner output (expected: compile-time red -- DeletePreview/UndoPreview
do not exist yet, matching the U1/U3 precedent for brand-new value types):

  error: cannot find 'DeletePreview' in scope (x3)
  error: cannot find 'UndoPreview' in scope (x6)
  error: fatalError (build failed, tests could not run)

These tests will pass after the implementation in the next commit.
Implementation for tests added in 9fa78d3.

Builds the shared DestructiveConfirmation presentation (KTD-4) plus
DeletePreview/UndoPreview, pure client-derived preview builders (KTD-5 -- no
server dry-run exists, so text is composed from ConversationInfo.messageCount
and the transcript's last .userPrompt, never a fabricated count). Wires all
four undo entry points (SettingsView ProjectTab, ChatView MessageActions,
ConversationChrome menu, and SessionsView's own delete flow) plus the
conversation delete menu item through the shared confirmation instead of
firing immediately. Cancel performs no mutation and no server call.

Test runner output (all passing):

  ✔ Test run with 9 tests in 1 suite passed after 0.008 seconds.
    (Suite "Destructive confirmation previews")

Full-suite check: 224 tests in 47 suites passed (0 failures), up from 215 at
the U4 baseline. swift build and swift format lint --strict --recursive
Sources Tests both clean.

Behavioral tests covered: R6 (delete/undo confirmations name what is lost).
Files changed:
- macapp/Sources/GoCodeUI/DesignSystem/DestructiveConfirmation.swift (new)
- macapp/Sources/GoCodeUI/SessionsView.swift
- macapp/Sources/GoCodeUI/SettingsView.swift
- macapp/Sources/GoCodeUI/ChatView.swift
- macapp/Sources/GoCodeUI/ConversationChrome.swift
- macapp/Tests/GoCodeUITests/DestructiveConfirmationTests.swift
…irmation wiring

Regression tests added that would fail if the wiring in 7674eaf is reverted.

The green commit's module-wide reachability test scans for "destructiveConfirmation("
anywhere in Sources/GoCodeUI, so it would still pass if three of the four
undo entry points (ChatView, ConversationChrome, SettingsView) reverted to
calling project.undo() directly while only one site kept the fix. These
tests pin each site by name (occurrence-counting confirmUndo()/confirmDelete()
rather than mere containment, since a bare containment check would still
pass against a dangling now-unused declaration -- verified by manually
reverting ChatView's undo button back to `Task { await project.undo() }`:
the new test failed with "occurrences(of: "confirmUndo()", ...) -> 1 >= 2",
then passed again once restored).

Full test suite output:

  ✔ Test run with 226 tests in 47 suites passed after 4.849 seconds.

(224 after the green commit; +2 regression tests here. 215 at the U4
baseline.) swift build and swift format lint --strict --recursive Sources
Tests both clean.

Regression scenarios covered:
- Each of the three MessageActions/ConversationHeader/ProjectTab undo call
  sites invokes its own confirmUndo() and builds its message from
  UndoPreview, not a bare project.undo() call.
- SessionsView's delete menu item invokes confirmDelete(conversation) and
  DeletePreview.message(for:), not deleteConversation() directly.
Behavioral tests added: a rewind_refused refusal is captured structurally
via ProjectSession.rewindRefusal, matched on HarnessError.code (KTD-6), not
HTTP status (R7); a generic failure sets statusMessage and offers no force
path; confirming sends force:true on the second request and clears the
refusal on success; a second refusal on the forced call sets rewindRefusal
again rather than looping or clearing silently; a successful first attempt
reports counts with no refusal (existing behaviour, pinned); reachability
check that SessionsView wires force: true only inside the refusal-
confirmation branch (2 occurrences of rewind(to:, 1 of force: true) and the
stale #951 finding-9 NOTE is gone.

Test runner output (expected: compile-time red -- ProjectSession.rewindRefusal
and RewindRefusal do not exist yet, matching the U1/U3/U5 precedent for
brand-new observable state):

  error: value of type 'ProjectSession' has no member 'rewindRefusal' (x9)
  error: fatalError (build failed, tests could not run)

These tests will pass after the implementation in the next commit.
…force confirmation

Implementation for tests added in 91d861a.

ProjectSession.swift: adds `RewindRefusal` (point + server message) and
`rewindRefusal` observable state, cleared at the start of every `rewind`
call (including its own retry) so a stale refusal from a prior point can
never bleed into a new one. `rewind`'s catch now branches on
`HarnessError.code == "rewind_refused"` (KTD-6 -- the code is the stable
contract, not the HTTP status it happens to arrive with) and records the
refusal instead of collapsing it into `statusMessage` prose; every other
`HarnessError` still lands in `statusMessage` as before. `dismissRewindRefusal()`
clears a refusal with no server call, for the confirmation's Cancel path.

SessionsView.swift: `CheckpointsView` retires the stale #951 finding-9 NOTE
and presents a second `DestructiveConfirmation` (U5's shared component) keyed
off `project.rewindRefusal` -- worded distinctly from the ordinary restore
alert (names the file-changed-outside-the-harness fact, quotes the server's
message, confirm label "Restore Anyway") and calls
`rewind(to:refusal.point, force: true)` only from that branch. Declining
calls `dismissRewindRefusal()`; nothing is ever auto-retried with force.

Also fixes a reentrant-lock deadlock in the new test file's stub: a handler
that calls `RewindStub.bodies(matching:)` to count prior attempts was being
invoked while `startLoading()` still held the same non-reentrant `NSLock`.

Test runner output (all passing):

  ✔ Test "a rewind_refused refusal is captured structurally, matched on HarnessError.code -- core regression" passed
  ✔ Test "a generic failure sets statusMessage and offers no force path" passed
  ✔ Test "confirming the refusal sends force:true, and the refusal clears once the forced call succeeds" passed
  ✔ Test "a second refusal on the forced call sets rewindRefusal again rather than looping or clearing silently" passed
  ✔ Test "a successful rewind reports the restore counts with no refusal -- existing behaviour, pinned" passed
  ✔ Test "SessionsView wires force: true only inside the refusal-confirmation branch, and the stale NOTE is gone" passed
  ✔ Test run with 232 tests in 48 suites passed after 24.561 seconds.

(226 at the U5 baseline; +6 here.) swift build and
swift format lint --strict --recursive Sources Tests both clean.

Behavioral tests covered: R7 (rewind_refused surfaced structurally, distinct
force confirmation, never auto-retried).
Files changed: macapp/Sources/GoCodeUI/ProjectSession.swift,
macapp/Sources/GoCodeUI/SessionsView.swift,
macapp/Tests/GoCodeUITests/ProjectSessionRewindTests.swift
…iring

Regression tests added that would fail if the wiring in 0c15cf3 is
reverted -- each was manually verified red against a targeted revert, then
restored and re-verified green:

- "dismissing a refusal clears it without contacting the server": reverted
  dismissRewindRefusal() to clear rewindRefusal without also asserting no
  new request fired would not have caught a Cancel path that silently
  re-issued rewind(force: false); this pins the request count too.
- "attempting a rewind on a different checkpoint clears a stale refusal
  from a prior one": reverting the `rewindRefusal = nil` at the top of
  `rewind` (leaving it cleared only via the success/failure branches) failed
  this test with "RewindRefusal(... point_a ...) == nil" while the green
  commit's own tests still passed -- they only ever act on a single point,
  so none of them exercised the start-of-call clear.
- "the force-rewind confirmation's decline path calls dismissRewindRefusal":
  blanking SessionsView's Cancel-path call to `project.dismissRewindRefusal()`
  failed this test while the green commit's module-wide "force: true"/NOTE-
  removal reachability check still passed unaffected, since that check never
  looks at the decline path at all.

Full test suite output:

  ✔ Test run with 235 tests in 48 suites passed after 5.361 seconds.

(232 after the green commit; +3 regression tests here. 226 at the U5
baseline / start of this task.) swift build and
swift format lint --strict --recursive Sources Tests both clean.

Regression scenarios covered:
- Cancelling the force-rewind confirmation never contacts the server.
- A stale refusal from a previously-refused checkpoint cannot leak into a
  fresh rewind attempt on a different checkpoint.
- The confirmation's decline path is wired to a real dismissal, not a
  dangling no-op.
Behavioral tests added: Up walks backwards through recorded prompts,
newest first; Up at the oldest entry stays put (no wraparound, no
nil-after-start); Down walks forward and restores the exact stashed
draft, including an empty one, then declines further; a half-typed
draft declines a recall and leaves the cursor unmoved (core
regression, R8); recording mid-navigation resets the cursor so the
next Up starts from the newest; empty history declines without
crashing; duplicate consecutive prompts are both recorded, not
deduped; reset clears navigation without touching recorded entries;
reachability check that ChatView wires .onKeyPress(.upArrow /
.downArrow to production recall (closes the #927/#998 gap where
recallPreviousPrompt had no call site).

Test runner output (expected: compile-time red -- PromptHistory does
not exist yet, matching the U1/U3/U5/U6 precedent for brand-new
value/observable state):

  error: cannot find 'PromptHistory' in scope (x9)
  error: fatalError (build failed, tests could not run)

These tests will pass after the implementation in the next commit.
Implementation for tests added in 9a495ce.

PromptHistory (new, Sources/GoCodeUI/PromptHistory.swift) is a pure
value type: entries + an optional cursor + a stashed pre-recall draft.
recallPrevious(currentDraft:) starts navigation only from an empty
draft (stashing it), walks backwards with no wraparound past the
oldest entry, and -- once already navigating -- declines instead of
overwriting an edit that has diverged from the entry last recalled
(KTD-7's caret-awareness approximation, since TextSelection/caret
binding is a macOS 15 API and this package floors at .macOS(.v14)).
recallNext() walks forward and restores the exact stashed draft past
the newest entry, then declines. record(_:) always resets navigation.

RunSession replaces `promptHistory: [String]` with a `PromptHistory`,
records on submit(), and exposes recallPreviousPrompt() /
recallNextPrompt() (Bool return: whether something was recalled) plus
noteManualDraftEdit() so a manual edit after a recall clears
navigation rather than letting the next arrow press silently replace
it.

ChatView's Composer wires .onKeyPress(.upArrow/.downArrow) on the
draft field, returning .handled only when the session actually
recalled something so the field's own multi-line navigation still
works otherwise, and calls noteManualDraftEdit() from the existing
onChange(of: run.draft) for any change the recall handlers did not
themselves cause -- closing the #927/#998 gap where
recallPreviousPrompt had no production call site.

Test runner output (all passing):

  Test run with 10 tests in 1 suite passed after 0.002 seconds.
  (PromptHistory cursor navigation)

Full suite: 245 tests in 49 suites passed (up from the 235-test
baseline). swift build clean. swift format lint --strict --recursive
Sources Tests clean.

Behavioral tests covered: all 9 PromptHistoryTests scenarios from the
plan's U7 section plus the mandated reachability check.
Files changed: macapp/Sources/GoCodeUI/PromptHistory.swift,
macapp/Sources/GoCodeUI/RunSession.swift,
macapp/Sources/GoCodeUI/ChatView.swift
…iring

Regression tests added that would fail if the change in c179c2c is
reverted:

- PromptHistory: Up declines without clobbering once the recalled
  entry has been edited (KTD-7's already-navigating check) --
  verified this fails when that check is removed.
- RunSession: noteManualDraftEdit() resets navigation so Down cannot
  silently overwrite an edit made after a recall. recallNext() takes
  no draft parameter by the plan's own PromptHistory signature, so
  without this reset it would restore the pre-recall stash over the
  edit instead of declining -- verified by temporarily no-opping
  noteManualDraftEdit() and confirming the test fails
  ("Expectation failed: (session.draft -> "") == "first prompt, but
  edited""), then restoring the fix and confirming it passes again.
- RunSession wiring: submit() records so a later Up recalls it, and
  recallPreviousPrompt() declines on empty history -- exercises the
  RunSession-level integration the pure PromptHistory tests can't
  reach (a single submit() only, since a second synchronous submit()
  in the same test is blocked by the isBusy guard Transcript sets
  optimistically on appendUserPrompt).

Full test suite output:

  Test run with 249 tests in 50 suites passed after 5.160 seconds.

(Baseline before U7: 235 passing. After green: 245. After this
commit: 249 -- strictly increasing per the plan's Verification
Contract.) swift build clean. swift format lint --strict --recursive
Sources Tests clean.

Regression scenarios covered:
- Up never replaces an in-navigation edit that no longer matches the
  recalled entry.
- Down never replaces an edit made after a recall, once the composer
  has reported the manual edit via noteManualDraftEdit().
…edback

Behavioral tests added: rows/toggles as real accessible controls (R9),
model-settings status feedback surviving its own reload (R10).

Test runner output (expected: failing before implementation):

  Building for debugging...
  [0/8] Write sources
  [1/8] Write swift-version--58304C5D6DBC2206.txt
  [3/7] Emitting module GoCodeUITests
  [4/7] Compiling GoCodeUITests AccessibilityReachabilityTests.swift
  [5/7] Compiling GoCodeUITests ModelSettingsFeedbackTests.swift
  /Users/dennison/develop/go-code/macapp/Tests/GoCodeUITests/ModelSettingsFeedbackTests.swift:153:42: error: argument passed to call that takes no arguments
  151 |         model.status = "some stale message"
  152 |
  153 |         await model.load(clearingStatus: true)
      |                                          `- error: argument passed to call that takes no arguments
  154 |
  155 |         #expect(model.status == nil)
  error: fatalError

  This is a genuine "feature does not exist yet" compile failure, not an
  import/typo error: the test calls ModelSettingsModel.load(clearingStatus:),
  a signature this unit adds. The whole target fails to build until the
  signature lands, exactly as the plan's own K1 risk note documents for the
  analogous CollectionLoadState.failed(String) change in U2 ("the enum
  change... breaks compilation across the six views — expected and
  intended"). The other 9 tests in these two files are plain string/behavior
  assertions against current (pre-fix) source and fail meaningfully on their
  own once compilation succeeds (verified by reading current SessionsView.swift
  line 53 .onTapGesture, SettingsView.swift line 166 .onTapGesture, and
  ModelSettingsView.swift's unconditional `status = nil` on load success and
  missing .accessibilityLabel on the exposure Toggle and confirmRemove helper).

These tests will pass after the implementation in the next commit.
… feedback

Implementation for tests added in 93867ce.

- SessionsView.swift: conversation rows are now a Button wrapping
  ConversationRow (.buttonStyle(.plain) keeps the look), replacing
  .onTapGesture, with an .accessibilityLabel naming title, message
  count, and pinned state. The context menu is unchanged.
- SettingsView.swift: ModelsTab rows are likewise a Button with an
  .accessibilityLabel naming the model id, provider, and selection
  state, replacing .onTapGesture { project.selectedModel = model.id }.
- ModelSettingsView.swift:
  - ModelSettingsModel.load(clearingStatus: Bool = true) replaces
    load(). The .task initial load keeps the default (clears any
    stale status); fetch/setExposed/setAllVisible/saveProvider/delete
    now call load(clearingStatus: false) on both success and failure
    so the message they just set survives the reload they trigger
    instead of being erased by it.
  - The exposure Toggle gains .accessibilityLabel("Show \(modelID) in
    the picker") alongside its existing .labelsHidden() + .help(...).
  - Provider "Remove" now calls a new confirmRemove(_:) that presents
    a DestructiveConfirmation (from U5) stating that removal drops the
    provider's exposed models, instead of calling model.delete
    immediately from the button action.

Test runner output (full suite, expected: all passing):

  Test run with 259 tests in 52 suites passed after 5.880 seconds.

  (249 baseline + 10 new: 5 in "ModelSettingsModel status feedback",
  5 in "Accessibility and settings-feedback reachability")

Behavioral tests covered: all 10 tests in
Tests/GoCodeUITests/ModelSettingsFeedbackTests.swift and
Tests/GoCodeUITests/AccessibilityReachabilityTests.swift.

Files changed:
- macapp/Sources/GoCodeUI/SessionsView.swift
- macapp/Sources/GoCodeUI/SettingsView.swift
- macapp/Sources/GoCodeUI/ModelSettingsView.swift
… status feedback

Regression tests added that would fail if the change in bd6d7dd is
reverted, covering angles distinct from the red-commit behavioral tests:

- ModelSettingsFeedbackTests: a failed delete()'s status message survives
  the load(clearingStatus: false) reload its own catch branch triggers --
  a different mutating method than the fetch/setExposed tests already
  covered, so a fix that special-cased only those two would still be caught.
- AccessibilityReachabilityTests: the conversation row's and model row's
  accessibility labels are attached to an actual Button wrapping the row
  content (verified via source-scan regex spanning the Button/label
  closure), not merely present somewhere else in the file as a bystander.
  The existing "no .onTapGesture" checks alone would not catch a row left
  inert with an unattached label.

Full test suite output:

  Test run with 262 tests in 52 suites passed after 5.180 seconds.

Regression scenarios covered:
- delete() failure message surviving its own reload (distinct from fetch/setExposed)
- conversation row accessibility label bound to a real Button, not a bystander
- model row accessibility label bound to a real Button, not a bystander
…ility test scaffolding

RunSession: approve/deny/steer now share one runControlTask helper for the
error-surfacing catch shape; cancel/answer keep their own do/catch since they
touch other state on failure. promptHistory drops its public accessor since
nothing outside RunSession reads it.

Tests: AccessibilityReachabilityTests and TranscriptFeatureReachabilityTests
had four near-identical copies of "read a source file / read the whole
module" helpers. Extracted to Tests/GoCodeUITests/ReachabilitySource.swift.

Baseline: 262 tests passing before and after (no behavior change).
Behavioral tests added for eight verified review findings:

F1a: answer() must guard against a second call while the first is
     still in flight (only one POST reaches the server).
F1b: a success answer must only clear pendingQuestions when it is
     still the very prompt that was answered (a newer prompt assigned
     mid-flight must survive).
F2:  a second cancel() press while the first cooperative cancel is
     still in flight must not escalate to a local force-stop.
F3:  a cancel Task that resolves after reset() must not write a stale
     connectionError for the abandoned run.
F4:  deleteConversation must refuse up front for its own busy
     conversation -- the DELETE must never reach the server.
F5:  fork/undo must re-check busyness after their server call, before
     applying rebind/reload, so a run started mid-flight is not
     clobbered.
F6:  openConversation must refuse while a run is active.
F7:  PromptHistory.recallPrevious must decline (nil) rather than
     repeat a same-value no-op at the oldest entry -- a same-value
     draft reassignment never fires SwiftUI's onChange, which left the
     composer's isRecallingHistory flag stuck and misattributed the
     next real edit.
F8:  the transcript's scroll pin must ignore geometry updates while
     its own scrollTo animation is in flight (and before a real
     viewport height is known), or it self-unpins from the very scroll
     it triggered.

Test runner output (expected: all new tests failing meaningfully,
zero regressions in the 262-test baseline):

  ✘ Suite "PromptHistory cursor navigation" failed with 1 issue.
  ✘ Suite "Transcript feature reachability" failed with 5 issues.
  ✘ Suite "ProjectSession lifecycle guard" failed with 6 issues.
  ✘ Suite "RunSession control acknowledgements" failed with 4 issues.
  Test run with 273 tests in 52 suites failed after 4.784 seconds
  with 16 issues.

  (16 issues == 4 RunControlAckTests + 6 ProjectSessionLifecycleGuardTests
  + 1 PromptHistoryTests + 5 TranscriptFeatureReachabilityTests --
  exactly and only the 11 new tests; every one of the 262 baseline
  tests still passes.)

Sample failures, each meaningful (not an import/compile error):
  - PromptHistoryTests.swift:134: recallPrevious(currentDraft: "a")
    returned "a" instead of declining with nil.
  - RunControlAckTests.swift:351: session.transcript.runState became
    .cancelled during an in-flight cooperative cancel.
  - RunControlAckTests.swift:407: a stale connectionError
    ("cancel rejected") was written after reset().
  - RunControlAckTests.swift:489/576: a second answer() sent a
    duplicate POST, and pendingQuestions was cleared to nil instead
    of surviving as call_2.
  - ProjectSessionLifecycleGuardTests.swift:268/296-298/380/426: the
    DELETE/messages/undo-reload calls reached the server, and
    fork/openConversation applied their result, while a run was busy.
  - TranscriptFeatureReachabilityTests.swift:104-105/143-150: ChatView.swift
    does not yet contain the answerInFlight-gated Send button or the
    isAutoScrolling guard (these two are source-scan tests, matching
    this file's existing pattern for view-layer state not otherwise
    assertable through a headless test on this stack).

These tests will pass after the implementation in the next commit.
Implementation for tests added in dd0ae51.

RunSession.swift:
- answer() gains an answerInFlight guard (cleared via defer on every exit
  path) so a second call while the first is in flight is a no-op; a success
  only clears pendingQuestions when it is still the prompt this call
  answered (compared by callID), so a newer question assigned mid-flight
  survives.
- cancel() replaces the cancelRequested bool (set true synchronously,
  before the request resolved) with an explicit CancelState (.idle/
  .requesting/.requested) state machine, so a second press only escalates
  once the server has actually acknowledged the first cooperative cancel.
- cancel()'s and answer()'s Tasks capture their runID and guard every
  connectionError/pendingQuestions/cancelState write on currentRunID still
  matching it, so a Task that resolves after reset() cannot write stale
  state into a new run/conversation. reset() now also resets cancelState
  and answerInFlight directly, since the guard above only stops a stale
  Task from overwriting NEW state -- it does not clear old state itself.

ProjectSession.swift:
- deleteConversation refuses up front (distinct message) when the target
  conversation is the current, busy one -- the DELETE no longer reaches
  the server before the local refusal, unlike before when the delete
  happened and only the internal newConversation() call refused.
- fork/undo re-check busyness after their server call, before applying
  rebind/reload, so a run started on the same conversation while the
  request was in flight is not clobbered.
- openConversation gains the same refuseIfBusy guard as newConversation/
  fork/undo.

PromptHistory.swift:
- recallPrevious declines (nil) instead of repeating a same-value no-op
  when already at the oldest entry and the draft already shows it. A
  same-value reassignment never fires SwiftUI's onChange, which left the
  composer's isRecallingHistory flag stuck and misattributed the next real
  edit.

ChatView.swift / DesignSystem/Motion.swift:
- TranscriptView gains an isAutoScrolling flag, set for the duration of
  scrollIfPinned's scrollTo animation (Motion.autoscrollDuration, replacing
  the prior 0.12 literal). pin.update is skipped while that flag is set or
  before scrollViewportHeight reports a real value, so the pin can no
  longer self-unpin from its own programmatic scroll. TranscriptScrollPin
  itself is untouched -- it stays a pure decision.
- AskUserView takes answerInFlight and disables Send while it is true.

Test runner output (expected: all passing):

  ✔ Suite "PromptHistory cursor navigation" passed
  ✔ Suite "Transcript feature reachability" passed
  ✔ Suite "ProjectSession lifecycle guard" passed
  ✔ Suite "RunSession control acknowledgements" passed
  Test run with 273 tests in 52 suites passed after 11.334 seconds.

swift format lint --strict --recursive Sources Tests: clean.

Behavioral tests covered: F1a, F1b, F2, F3, F4, F5, F6, F7, F8.
Files changed: Sources/GoCodeUI/RunSession.swift,
Sources/GoCodeUI/ProjectSession.swift, Sources/GoCodeUI/PromptHistory.swift,
Sources/GoCodeUI/ChatView.swift, Sources/GoCodeUI/DesignSystem/Motion.swift,
Tests/GoCodeUITests/RunControlAckTests.swift (swift-format only),
Tests/GoCodeUITests/TranscriptFeatureReachabilityTests.swift (reformat one
assertion to two format-resilient substring checks after swift-format
re-wrapped the .disabled(...) call it is scanning for).
…0d9f

Regression tests added that would fail if the changes in 4250d9f are
reverted, each covering an angle distinct from the F1-F8 behavioral tests:

- RunSession.resetClearsAnswerInFlightForLaterConversations: proves
  reset() itself clears answerInFlight, not just that answer()'s guard
  works while in flight (the existing F1a/F1b tests). A revert that keeps
  the answer() guard but drops `answerInFlight = false` from reset() would
  pass every existing test (none of them call answer() again after a
  reset()) while leaving Send permanently disabled for every conversation
  opened after one whose answer never came back. Verified this fails
  against the pre-fix reset() (manually reverted the one line, ran the
  test, restored it) -- confirms it actually exercises the fix, not a
  tautology.

- ProjectSession.deleteConversationSucceedsForUnrelatedConversationWhileAnotherIsBusy:
  proves the F4 guard is conjunctive (conversation.id == run?.conversationID
  AND busy), not a simplified run?.isBusy == true that would still pass the
  existing "refuses its own busy conversation" test while wrongly blocking
  every other conversation's delete for as long as anything is running.
  Verified this fails against that exact simplification (manually applied
  it, ran the test, restored the conjunctive guard).

Full test suite output:

  Test run with 275 tests in 52 suites passed after 5.099 seconds.

swift build: clean. swift format lint --strict --recursive Sources Tests: clean.

Regression scenarios covered:
- answerInFlight left stuck true across conversations after an abandoned
  in-flight answer.
- deleteConversation's busy guard over-blocking unrelated conversations.
RunSession.runControlTask (shared by approve/deny/steer) writes
connectionError on failure with no guard against a Task that resolves
after reset() has already moved the session onto a different (or no)
run -- the same stale-write race already fixed for cancel/answer.

Test runner output (expected: failing):

  Test "an approve Task that resolves after reset() must not surface a
  stale connectionError -- core regression" failed after 0.138 seconds
  with 1 issue.
  Expectation failed: (session.connectionError -> "approve rejected")
  == nil
  an approve response arriving after reset() must not resurrect state
  for an abandoned run

This test will pass after the guard is added to runControlTask in the
next commit.
Implementation for the test added in e8e0651.

runControlTask (shared by approve/deny/steer) now captures the runID
each call was issued for and skips writing connectionError when
currentRunID no longer matches it on completion -- the same
captured-runID guard cancel()/answer() already use, so a Task that
resolves after reset() has moved the session onto a different (or no)
run cannot resurrect state for the abandoned one.

Test runner output (expected: all passing):

  Test run with 276 tests in 52 suites passed after 5.687 seconds.

(12/12 in "RunSession control acknowledgements", including the new
regression test; full suite unaffected.)

Files changed: Sources/GoCodeUI/RunSession.swift

Copy link
Copy Markdown
Owner Author

@codex review

Please review exact head 575b670938821f9db8b5bb096e3956753b0f0e25 after the production-review repair and current-main integration. All prior inline threads have exact evidence replies and are resolved. Local gates: focused repair 93 tests / 12 suites; full Swift 303 tests / 55 suites; Swift build/strict format green; relevant Go packages green; full regression/race/coverage green at 85.6% with zero uncovered functions. Please pay particular attention to request-generation ownership, control single flight, autoscroll cancellation/generations, lifecycle/rewind guards, #1008/#1028 preservation, and the explicit #1007 boundary. Installed-app and Settings proof remains pending under #1020; do not treat it as claimed.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

if showInspector {
EnvironmentInspector(
project: project,

P2 Badge Reset answers when the pending prompt changes

If the server emits a second run.waiting_for_user before the first answer request completes, the new ownership check correctly preserves that newer prompt, but SwiftUI retains AskUserView and its @State answers because the view has no identity tied to prompt.callID. When both prompts contain a question with the same index and text, the retained dictionary satisfies the new prompt and can submit the previous answer; key this view by the call ID or explicitly clear its state when the call changes.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread macapp/Sources/GoCodeUI/ProjectSession.swift Outdated
Comment thread macapp/Sources/GoCodeUI/ProjectSession.swift Outdated

Copy link
Copy Markdown
Owner Author

@codex review

Please review the new final exact head 01268cf33c337ca0c2c759a48be33a1323875428. Hosted live-harnessd exposed and the branch now repairs terminal usage/cost loss across duplicate-stream ordering plus durable replay; the reducer test was red before the fix, the exact live suite passes, and full Swift is 304 tests / 55 suites with build and strict format green. Prior inline repairs remain present and resolved. Hosted Go race is an explicit stacking blocker on existing #1039 / green PR #1041 (bd0682c4), not a waived or duplicated fix. Installed-app and Settings proof remains pending under #1020.

Copy link
Copy Markdown
Owner Author

Hosted verification is now terminal on exact head 01268cf33c337ca0c2c759a48be33a1323875428: build-test, format, live-harnessd, test-fast, and test-race all pass. The first race attempt still exposed the existing #1039 cleanup flake, so the safe stacking order remains green PR #1041 first, then refresh/recheck #1021. No merge performed; Settings and installed-app smokes remain pending under #1020.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 01268cf33c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread macapp/Sources/HarnessKit/Transcript.swift
Comment thread macapp/Sources/HarnessKit/Transcript.swift Outdated

Copy link
Copy Markdown
Owner Author

Final review repairs are now at exact head afa92d20c7ec10438cbbde4550bb8886e2b0465a: pending-answer state is keyed by callID, stale run todos no longer abort valid activity collections, and rewind refusal/force retry is conversation-bound. Each finding was reproduced red and has focused regression coverage (13 tests / 3 suites); full Swift passes 308 tests / 55 suites, build passes, and strict recursive format lint passes. The two inline threads have exact evidence; the AskUser finding was review-body-only and is covered by askUserViewIdentityFollowsCallID. New hosted checks are running.

@codex review

Copy link
Copy Markdown
Owner Author

Exact-head hosted checks are now all green at afa92d20c7ec10438cbbde4550bb8886e2b0465a: macapp build-test, format, and live-harnessd; Go test-fast and test-race. Still not merging: installed-app smokes and the separate Settings investigation remain pending, and safe promotion order remains #1041 before refreshing/rechecking #1021.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: afa92d20c7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread macapp/Sources/GoCodeUI/DesignSystem/DestructiveConfirmation.swift
Comment thread macapp/Sources/GoCodeUI/RunSession.swift Outdated

Copy link
Copy Markdown
Owner Author

The two unresolved accounting threads are repaired at exact head 7361769b0dc59139db03a64f0e2a07f992127eb1. Per-run ownership prevents a lower-cost follow-up from inheriting prior totals/status and rejects late prior-run terminal events; sealed accounting now applies to completed, failed, and cancelled outcomes and survives durable reconciliation. Both tests were captured red before repair. Local gates: focused accounting 4 tests / 1 suite; full Swift 310 tests / 55 suites; build and strict recursive format pass; relevant Go packages pass. New hosted checks are running.

@codex review

Copy link
Copy Markdown
Owner Author

Hosted checks are terminal and all green at exact head 7361769b0dc59139db03a64f0e2a07f992127eb1: macapp build-test, format, and live-harnessd; Go test-fast and test-race. The PR remains unmerged pending installed-app/Settings proof and the safe #1041-first promotion order.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7361769b0d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread macapp/Sources/GoCodeUI/ProjectSession.swift Outdated
Comment thread macapp/Sources/HarnessKit/Transcript.swift

Copy link
Copy Markdown
Owner Author

Four final review repairs are now at exact head 5f76a74a3593a7191a9135b742503658d50ffab2: force retry claims synchronously before alert dismissal; local force cancel releases currentRunID; tasks/runs apply while todos is blocked; and sealed terminal cost status overrides/locks out older delta classification. Each was regression-driven. Local gates: focused 5 tests / 4 suites; full Swift 313 tests / 55 suites; build and strict recursive format; relevant Go packages. New hosted checks are running.

@codex review

Copy link
Copy Markdown
Owner Author

Hosted checks are terminal and all green at exact head 5f76a74a3593a7191a9135b742503658d50ffab2: macapp build-test/format/live-harnessd and Go test-fast/test-race. The PR remains unmerged pending installed-app/Settings proof and safe #1041-first promotion.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5f76a74a35

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread macapp/Sources/HarnessKit/Transcript.swift Outdated
Comment thread macapp/Sources/GoCodeUI/ProjectSession.swift
Comment thread macapp/Sources/GoCodeUI/ChatView.swift

Copy link
Copy Markdown
Owner Author

Latest exact-head repairs are at cec0a6654da96c7ef3ab812c489338f72c4fd6db: submitted-run accounting is reserved for the startRun response ID, reserved startup refreshes guard before loading-state mutation, and transcript pin/autoscroll state is scoped by conversation identity. All three were regression-driven. Local gates: focused 5 tests / 2 suites; full Swift 316 tests / 55 suites; build and strict recursive format; relevant Go packages. New hosted checks are running.

@codex review

Copy link
Copy Markdown
Owner Author

Exact-head hosted verification is terminal and all green at cec0a6654da96c7ef3ab812c489338f72c4fd6db: macapp build-test, format, and live-harnessd; Go test-fast and test-race. All three latest inline repair threads have exact regression evidence and are resolved. The exact-head Codex review request is acknowledged and remains pending. No merge performed; installed-app smokes and the separate Settings investigation remain pending, and safe promotion still requires #1041 first followed by a refresh/recheck of #1021.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cec0a6654d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// The conversation stream can deliver background/callback events
// from other runs while `startRun` is in flight. Only the id
// returned by that request may end this reservation.
return false

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Buffer reserved-run events before recording them as seen

When submitting a follow-up on an existing conversation, harnessd dispatches the run before the POST /v1/runs response is written, so the conversation SSE can deliver even the terminal event while this branch returns false. Fresh evidence at the repaired head is that RunSession.apply nevertheless inserts the event ID into seenEventIDs before calling Transcript.apply; after bindAccountingRun, the per-run replay is then discarded as duplicate. A fast run can consequently leave the transcript permanently .queued with zero accounting, so reserved events need to be buffered/replayed or not marked seen until their identity is bound.

Useful? React with 👍 / 👎.

// waiting for the new run id.
previousAccountingRunID = accountingRunID
accountingRunID = nil
awaitingAccountingRun = true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Release the accounting reservation when startup fails

If startRun throws before bindAccountingRun—including when the server accepted the run but the client lost its response—this flag remains set after markFailed() handles the local error. Every later conversation-stream run then has its queued, started, terminal, and accounting events rejected by prepareAccounting, so scheduled callbacks or other external runs cannot update busy state or usage until the operator resets or successfully submits again; the startup failure path needs to abandon or otherwise resolve this reservation.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment