Skip to content

Nod chat pane: transcript, work cards, composer and failure banners - #591

Merged
scgopi merged 8 commits into
mainfrom
feat/nod-chat-pane
Oct 2, 2026
Merged

scgopi merged 8 commits into
mainfrom
feat/nod-chat-pane

Conversation

@scgopi

@scgopi scgopi commented Oct 2, 2026 •

Copy link
Copy Markdown
Owner

Summary

Nod's chat pane: a loop whose backend surface is .chat opens a chat where a CLI loop's terminal would be, with the same tab strip and splits around it. The layout follows the design's recommendation: 1a's reading column as the base, plus 1b's folded Work block and queued-message row. Stacked on #589 (feat/nod-setup, itself on #583), because the pane uses NodSetup's NodModelCatalog, resolvedModel(for:tier:) and addAllowlistPattern. Rebase onto main once those merge. Retarget this PR before deleting #589's branch, or GitHub closes it. No wire-contract change: NodProtocol.swift and PROTOCOL.md are untouched.

Changes

  • NodTranscript: a pure reducer that folds NodEventRecords into turns and cards. It handles:

    • prompts written before or after turnStarted
    • queued messages that start the next turn
    • steers that land in the running turn
    • streamed text, tool results, hunk and permission resolutions
    • goal checks, usage and cost totals, compaction, activity and failures

    Replayed records are dropped. A sessionStarted newer than anything already seen starts a new run, and a turn left open by the old run is closed as interrupted.

  • NodChatPresentation: pure view logic.

    • A turn with more than five tool calls folds them into one Work block. Failed calls stay outside the fold.
    • Banners: sign-in expired, context nearly full (from 80%) with Compact now, spend cap.
    • The goal header's verdict, and the cost label: dollars, or premium requests on Copilot.
    • Composer / commands and @ mentions. A running loop is messaged and a finished one has its transcript attached; ⇥ swaps the two.
  • NodChatFeature (TCA):

    • ⏎ queues, ⌘⏎ steers, esc closes an open menu first and only then stops Nod.
    • Attachments: files, images, loop transcripts.
    • Card actions: hunk Accept, Reject, or Comment with a note; Allow once, Always or Deny on permission asks; Mark done anyway; fork; Run here, which takes the human-edited plan steps when given; Compact now.
    • Model chip lists NodModelCatalog for the engine and sends setModel. Before the first sessionStarted, the chat shows the model Settings would launch, from resolvedModel(for:tier:).
    • Requests that belong to other streams go up as Delegate actions: Open in zsh tab, edit goal, sign in, raise cap, fork as sibling, run as Composite, /handoff /ask /promote.
    • "Always in project" on a shell ask is saved with NodSettings.addAllowlistPattern, because the runtime keeps Always for the session only.
    • When the runtime refuses fork, sendDraft or runPlan, that action shows disabled rather than as an error.
  • NodClient: tails events.jsonl from a byte offset and holds back a torn last line. Sends one NodCommand per connection over control.sock, with send and receive timeouts. A dependency, so tests and renders replay a scripted log.

  • Views: goal header pinned with the verdict, which opens to show the clauses. Work cards:

    • tool call, collapsed or open, with Open in zsh tab and Copy output
    • per-hunk diff
    • permission ask
    • native goal check with clauses and evidence
    • fork menu

    Also the composer and its menus, the failure banners, and the steered and queued rows. The composer is an NSTextView because SwiftUI focus cannot take first responder from a Ghostty surface in the same window.

  • NodGraphSlots: where NodGraphLayer plugs in, agreed with that stream. Slots: context strip, after-turn, above-composer, inbound mail or handoff in place of the prompt bubble, after-goal-check (handoff offer), plan card, mail draft. All default to nothing.

  • Workspace: LoopWorkspaceView draws NodChatPaneView in the agent pane for chat loops. "Open in zsh tab" reuses or creates a plain shell tab and types the command at its prompt without running it (TerminalSurfaceClient.typeText).

Review renders

NodChatRenderTests renders 8 states headless with NSHostingView. Every run lays them out; PNGs are written only when /tmp/nod-chat-renders exists. The states:

  1. running pane
  2. goal header open, tool card open
  3. Work block folded
  4. Work block open, commenting on a hunk
  5. goal holds, fork menu open
  6. the three failure banners
  7. composer with attachments, / menu, @ menu
  8. graph-layer slots

Test plan

RED: xcodebuild test -only-testing:graphcodeTests/NodTranscriptTests, with the first dedupe rule that let any sessionStarted reset seq -> replayedRecordsAreDroppedButANewRunStartsItsOwnSequence failed: transcript.turns.map(.number) was [1, 1, 2], expected [1, 2], exit 65
GREEN: xcodebuild test for the 7 Nod suites (NodTranscriptTests NodEventTailTests NodComposerMenuTests NodChatFeatureTests NodWorkspaceWiringTests NodControlSocketTests NodChatRenderTests) -> pass, 50 tests on 6254836 plus the state-directory test on c909504
REGRESSION: xcodebuild test, full graphcode scheme, private DerivedData, on c909504 -> pass, 2111 tests in 233 suites, exit 0; on 6254836 graphcode-cli and graphcoded builds exit 0, scripts/cli-smoke.sh exit 0, make check exit 0 (c909504 adds one test only)

Checklist

  • I have read the Contributing Guidelines
  • I have signed off my commits (git commit -s) per the DCO
  • Tests pass locally (full xcodebuild test with private DerivedData, numbers above)
  • Code follows the existing style (make check)
  • I added the test/contract before the implementation and observed the intended RED failure (tests came with the code; the RED above is a real failure against my first implementation)

scgopi and others added 6 commits October 1, 2026 22:03
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: scgopi <scgopireddy@gmail.com>
The transcript is folded from events.jsonl by a pure NodTranscript reducer;
work cards, the composer and failure banners send NodCommands over
control.sock through NodClient. NodGraphSlots is where the graph layer plugs
in. Open in zsh tab types the command into a plain shell tab.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: scgopi <scgopireddy@gmail.com>
…pane headless

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: scgopi <scgopireddy@gmail.com>
…d actions to Nod's chat

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: scgopi <scgopireddy@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: scgopi <scgopireddy@gmail.com>
The model chip lists NodModelCatalog, the chat starts on
resolvedModel(for:tier:), and Always in project goes through
addAllowlistPattern, so the pane and Settings never disagree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: scgopi <scgopireddy@gmail.com>
@scgopi
scgopi force-pushed the feat/nod-chat-pane branch from 58f938c to 6254836 Compare October 2, 2026 05:09
@scgopi
scgopi changed the base branch from feat/nod-foundation to feat/nod-setup October 2, 2026 05:09
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: scgopi <scgopireddy@gmail.com>
@scgopi
scgopi changed the base branch from feat/nod-setup to main October 2, 2026 16:13
Signed-off-by: scgopi <scgopireddy@gmail.com>
@scgopi
scgopi merged commit 26f5f74 into main Oct 2, 2026
21 of 22 checks passed
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