Skip to content

Redesign stacks as faithful per-turn request/response records - #578

Merged
season179 merged 2 commits into
mainfrom
redesign/faithful-stack-records
Jun 2, 2026
Merged

Redesign stacks as faithful per-turn request/response records#578
season179 merged 2 commits into
mainfrom
redesign/faithful-stack-records

Conversation

@season179

@season179 season179 commented Jun 2, 2026

Copy link
Copy Markdown
Owner

Why

The stacks view was supposed to give clarity into exactly what nav sent to the LLM and exactly what came back, per turn, so you can reason about context management (what to drop, what to summarize). The old implementation didn't do that:

  • It was a 12-layer derived summary assembled on the Electron side, not a faithful record.
  • It was provider-blind — it counted messages and reconstructed streamed responses rather than storing the real payloads.
  • On a non-2xx it dropped the provider error body, so a failed call (e.g. gpt-5.5 + tools → HTTP 400) surfaced only http status: 400 with no explanation.

What changed

One faithful record per model call, captured on the backend:

request:  { api, url, model, body }                  // verbatim wire payload
response: { statusCode, body, error, tokenUsage }    // assembled response,
                                                      // or the captured error body
  • Error bodies are now captured. Both OpenAI adapters build the request with http_status_as_error(false) and branch on is_success(), reading and storing the provider's error body on a non-2xx. Centralized in capture_status_or_error so every adapter (completions, responses, codex-responses) behaves identically.
  • MockModel gains respond_with_trace so the offline/test path exercises the capture seam.
  • Schema bumped to v2. v1 records no longer parse and are skipped on both read and compaction — a clean cutover with no migration code (the local ~/.nav/stacks.jsonl was discarded).
  • Electron view rewritten. Per call: a scannable header (status · duration · model · HTTP status · token summary) plus collapsible Request/Response sections with pretty-printed JSON; error bodies highlighted.

Net −495 lines.

Tests

  • tests/openai_model.rs: new a_provider_error_body_is_captured_in_the_trace asserts the HTTP 400 body lands in trace.response_payload and the detail reaches the error message.
  • tests/session.rs / tests/local_backend.rs / tests/stack_store.rs: rewritten to assert on the request/response shape instead of layers.

Verification

  • cargo test → 189 passed
  • cargo clippy --all-targets → clean
  • rustfmt --edition 2024 applied
  • biome check → clean; renderer builds; npm run check:electron → 46 passed

Summary by CodeRabbit

  • New Features

    • Redesigned model-call view: collapsible Request and Response sections, always-show shortened run ID, status/duration/start-time indicators, optional model and HTTP status display, and a consolidated token-usage summary.
  • Bug Fixes

    • Improved capture and display of provider HTTP error bodies and status details.
  • Style

    • Updated stack metadata layout, JSON/error body rendering, and typography for clearer presentation.

The old stacks view was a 12-layer derived summary built on the Electron
side. It counted messages, reconstructed streamed responses, and — worst of
all — dropped the provider error body on a non-2xx, so a failed call showed
only "http status: 400" with no explanation. That made it useless for the one
thing stacks are for: seeing exactly what was sent to and returned by the
model so you can reason about context management.

Replace it with one faithful record per model call, captured on the backend:

  request:  { api, url, model, body }            // verbatim wire payload
  response: { statusCode, body, error, tokenUsage }  // assembled response or
                                                     // captured error body

- Capture error bodies: both OpenAI adapters now build the request with
  http_status_as_error(false) and branch on is_success(), reading and storing
  the provider's error body on a non-2xx instead of collapsing it into a bare
  status. Shared via capture_status_or_error so every adapter behaves the same.
- MockModel gains respond_with_trace so the offline/test path exercises capture.
- Bump the stack record schema to v2; v1 records no longer parse and are
  skipped on both read and compaction (clean cutover, no migration).
- Rewrite the Electron view: per call, a scannable header (status, duration,
  model, HTTP status, token summary) plus collapsible Request/Response showing
  pretty-printed JSON, error bodies highlighted.

Net -495 lines; tests updated to assert on the request/response shape, with a
new test covering error-body capture on HTTP 400.
@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 034953ac-a130-44f1-91df-3501ac2dd329

📥 Commits

Reviewing files that changed from the base of the PR and between 0ee51b9 and 9151fe7.

📒 Files selected for processing (2)
  • desktop/electron/renderer/src/components/StacksPage.jsx
  • desktop/electron/renderer/styles.css
🚧 Files skipped from review as they are similar to previous changes (2)
  • desktop/electron/renderer/styles.css
  • desktop/electron/renderer/src/components/StacksPage.jsx

📝 Walkthrough

Walkthrough

Model call stacks transition from multi-layer debugging metadata to simple per-call request/response records. The backend records only provider API request and response details, centralizes provider error parsing, the renderer shows collapsible request/response sections, and tests/assertions are updated end-to-end.

Changes

Stack recording refactored to per-call request/response capture

Layer / File(s) Summary
Stack data model redefinition
src/stacks.rs, src/lib.rs, src/stack_store.rs, desktop/electron/renderer/window.d.ts
New ModelCallRequest and ModelCallResponse capture provider API/url/model/body and outcome fields; ModelCallStack now contains request/response; public re-exports and IPC typing updated; schema version bumped to v2.
Stack recording simplification
src/agent.rs
ModelCallStackOutcome reduced to status, provider_trace, token_usage, error; all recording sites updated (model failure, reply cancellation, turn completion); ModelCallCapture::record passes only provider trace and token usage.
Provider trace population and error handling
src/model.rs
Adapters use http_status_as_error(false) to preserve provider error bodies; shared helpers standardize status/request-id capture and provider error-body parsing (JSON-first, raw-text fallback); MockModel produces synthetic ProviderCallTrace for tests.
Stack data structure tests
src/stacks.rs
Tests rewritten to verify build_model_call_stack copies API/model/request-body into stack.request and provider response payload/status into stack.response; failure tests assert error propagation.
Frontend component refactoring
desktop/electron/renderer/src/components/StacksPage.jsx
StackCall now extracts request/response, displays model/HTTP status/token summary and shortened run id, adds tokenSummary helper, and renders RequestSection/ResponseSection collapsible sections instead of a layers list.
Frontend styling updates
desktop/electron/renderer/styles.css
Layer-based styles replaced with .stack-section collapsible presentation; adds .stack-call-meta, .stack-call-model, .stack-call-tokens, and unified .stack-json/.stack-error-body containers for body/error rendering.
IPC typing and integration tests
desktop/electron/renderer/window.d.ts, tests/*
window.d.ts updates sessionStacks() element shape to include request and response; integration and unit tests updated to assert request.body.messages, response.statusCode, response bodies, and provider error capture.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • season179/nav#560: Introduced the original session stacks layers shape and renderer/IPC plumbing that this PR replaces with request/response.
  • season179/nav#568: Related updates to StacksPage.jsx and renderer IPC for persisted JSONL stacks; aligns with the new request/response shape.
  • season179/nav#574: Modifies provider trace and Responses adapter behavior in src/model.rs, which is serialized into per-call stack request/response records shown in the UI.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the main architectural change: redesigning the stacks system from a complex multi-layer debugging structure to faithful per-turn request/response records.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch redesign/faithful-stack-records

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

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@desktop/electron/renderer/src/components/StacksPage.jsx`:
- Around line 134-175: Both RequestSection and ResponseSection eagerly call
stringifyJson during initial render; change them to defer serialization until
the user expands the <details>. Add local state (e.g., expanded) in
RequestSection and ResponseSection, set it via the details onToggle handler (use
event.currentTarget.open), and only call stringifyJson(request.body) /
stringifyJson(response.body) when expanded is true; remove any immediate
stringifyJson calls so large bodies are not serialized on initial render.

In `@desktop/electron/renderer/styles.css`:
- Around line 652-668: Add an accessible keyboard focus style for the disclosure
toggle by defining a visible focus state on .stack-section > summary (use :focus
and/or :focus-visible) so keyboard users see when the summary has focus; update
the selector .stack-section > summary to include a focus rule that provides a
clear contrasting outline or box-shadow and preserves existing hover/background
styles (ensure it targets the same element that had ::-webkit-details-marker
hidden so the keyboard focus is applied to the visible summary toggle).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0f47959d-81d0-4056-bf13-0639ba8c31b1

📥 Commits

Reviewing files that changed from the base of the PR and between 05fb888 and 0ee51b9.

📒 Files selected for processing (12)
  • desktop/electron/renderer/src/components/StacksPage.jsx
  • desktop/electron/renderer/styles.css
  • desktop/electron/renderer/window.d.ts
  • src/agent.rs
  • src/lib.rs
  • src/model.rs
  • src/stack_store.rs
  • src/stacks.rs
  • tests/local_backend.rs
  • tests/openai_model.rs
  • tests/session.rs
  • tests/stack_store.rs

Comment thread desktop/electron/renderer/src/components/StacksPage.jsx
Comment thread desktop/electron/renderer/styles.css
Two follow-ups on the stacks view:

- Request/Response sections now start collapsed and serialize their JSON body
  only after the user expands them (tracked via the details onToggle handler).
  A session can hold hundreds of calls, each carrying the full conversation
  context, so pretty-printing every body on first paint was wasted work.
- Add a :focus-visible style to the section summary. The disclosure marker is
  hidden, so keyboard users previously had no focus indicator; use the accent
  ring convention (inset box-shadow, since the call card clips overflow).
@season179
season179 merged commit 4e63123 into main Jun 2, 2026
1 check passed
@season179
season179 deleted the redesign/faithful-stack-records branch June 2, 2026 02:20
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