Skip to content

Read the real prompt at the cursor; make the lane modes mean something - #3

Merged
navbytes merged 2 commits into
mainfrom
claude/pi-context-tree-workflow-sjuutd
Sep 4, 2026
Merged

Read the real prompt at the cursor; make the lane modes mean something#3
navbytes merged 2 commits into
mainfrom
claude/pi-context-tree-workflow-sjuutd

Conversation

@navbytes

@navbytes navbytes commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Two fixes to the trajectory half of /tree, both from questions about what the numbers and the chart were actually saying.

1. The prompt really sent at the cursor (151ff73)

The per-row token column is marginal cost, chars/4 for anything the provider didn't cost, and it never includes the system prompt or the tool definitions — so it reads as "too small" and can't answer "how full was I here?".

The provider already reports that number, and we already read it: an assistant message's tokens.input (+ cache) is the whole prompt that produced it. contextSizeOf sums it that way for the header gauge and tokenFieldsOf carries it onto every step row for the inspector — it was just never on screen without pressing i.

The status line now ends with it, right-aligned directly under the ctx … gauge so the two stack in one column and are read against each other:

┌ Context tree · Fix flaky test · trunk                                  ctx ~46k/200k · filling
│ filter: default   5/24 rows                           T2 reply · prompt 43.7k · 30.1k cached

core/tree.ts#promptAtRow resolves which report applies: a step carries its own message's, a user turn takes the reply to it (the first prompt that included that turn), a branch header has none because its column is already a subtree total. Nothing sent yet reads not sent yet rather than a zero, and the field is dropped whole rather than wrapped when the terminal can't hold it.

Deliberately not a second per-row column: it is history — an older row's figure is what went out then, not what a later crop would send now — and one number you're inspecting can carry that caveat where forty scrolling past it cannot.

2. Turns and Calls drew the same chart (2336ba6)

Reported from real use: switching lane modes changed almost nothing. Measured on the fixture, that was exactly right — the two modes drew the same events in the same lanes with the same glyphs, differing by one blank cell per turn boundary:

BEFORE                              AFTER
turns   width=22                    turns/default        width=25
  Input  ▬······▬····▬······▬··       Input  ▬·····│·▬···│·▬·····│·▬··
  Model  ····▬····▬······▬····▬       Model  ····▬·│···▬·│·····▬·│···▬
  Tools  ··▬···········▬·······       Tools  ··▬···│·····│···▬···│····

calls   width=19  ← same pills      turns/tools-only     width=5
  Input  ▬·····▬···▬·····▬··          Input  ··│··
  Model  ····▬···▬·····▬···▬          Model  ··│··
  Tools  ··▬·········▬······          Tools  ▬·│·▬

layoutEventStrip branched on mode in only two places: duration widths, and a 2-cell gap at turn boundaries in turns. The model that did distinguish them (buildLanes — one column per turn vs one per tool call) had been superseded by the event strip and was unreachable from the route. Its tests kept passing, which is how this shipped.

DSH settled the shape. Its ui-trajectory README describes no mode toggle at all: always duration-proportional ("projects real record start/duration timing from left to right"), turns marked with rules ("Thick rules mark Turn boundaries, compact inline markers identify Steps"), and "just the tool calls" reached through wheel-zoom, drag-to-focus and search rather than a mode.

So the toggle now carries only what it can honestly carry:

  • 1 / 2 select the x-axis — Duration (wall clock) or Turns (one cell per event). 3 is gone; a stored "calls" falls back to Turns.
  • A turn boundary is a rule across all three lanes, in both modes, drawn in a gap widened to hold it rather than implied by that gap.
  • Which events are on the strip is the row filter, not a mode. f gains tools-only, and layoutEventStrip takes the filter — so it thins the rows and the lanes together, and no-tools / user-only do too.

eventAllowed documents its two deliberate mismatches with the rows' stepAllowed: labeled is a row annotation with no event of its own, and reasoning stays on the Model lane under every filter because the strip is a timeline — a minute of thinking is a thing that happened.

Deleted

buildLanes, sparkline, fitColumns, durationWeighted, columnFor, and buildEventStrip (superseded by the windowed layout in 0.2.2). None was reachable from the route; all were kept alive by tests, which is precisely how the bug shipped — leaving a second instance would have been inconsistent. test/lanes.test.ts is ported onto layoutEventStrip, keeping the assertions that carried real value (shared-axis occupancy, error glyph, compaction-as-context, empty lanes, the whole windowing/overview suite).

Docs

DESIGN.md §7.3 is rewritten as "The lane x-axis, and why there is no Calls mode", with the DSH quotes as rationale. §7.1's claim that the three-lane split is "as in DSH" is softened: the primary README describes a single combined Overview, a hands-on review describes three lanes, and Appendix A shows the original research was a screenshot rather than the source. The three-lane split is ours; the event pills, duration axis and turn rule are DSH's.

Tests

243 pass, 0 fail (net +4 after removing ~9 dead-model tests). New coverage: promptAtRow/formatPromptAt, the turn rules in both modes, the filter↔lane coupling, and a tools-only row test asserting it and no-tools partition the steps exactly. Typecheck and build clean.

Not verified here: the e2e suite — opencode serve does not bind in this container (reproduced with no plugin installed at all). Both changes are unit-covered, but the e2e /tree test drives the real TUI and should cover this before release.

🤖 Generated with Claude Code

https://claude.ai/code/session_01BrwAnTfTmJSmFwVn3DDFxr


Generated by Claude Code

The per-row token column is marginal cost, chars/4 for anything the
provider did not cost, and it never includes the system prompt or the
tool definitions — so it reads as "too small" and cannot answer "how
full was I here?".

The provider already reports that number: an assistant message's
tokens.input (+ cache read/write) is the whole prompt that produced it,
system prompt and tool definitions included. contextSizeOf already sums
it that way for the header gauge, and tokenFieldsOf already carries it
onto every step row for the inspector; it was just never on screen
without pressing i.

The tree's status line now ends with it, right-aligned directly under
the `ctx …` gauge so the two stack in one column and are read against
each other: the gauge is now, this is the cursor, the gap between them
is everything after that point.

core/tree.ts#promptAtRow resolves which report applies to a row — a step
carries its own message's, a user turn takes the reply to it (the first
prompt that included that turn), a branch header has none because its
column is already a subtree total. Nothing sent yet reads "not sent yet"
rather than a zero, and the field is dropped whole rather than wrapped
when the terminal cannot hold it.

Deliberately not a second per-row column: it is history — an older row's
figure is what went out then, not what a later crop would send now — and
one number the user is inspecting can carry that caveat where forty
scrolling past it cannot. Noted in ? help, docs/USAGE.md and DESIGN.md
§6.7.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrwAnTfTmJSmFwVn3DDFxr
Turns and Calls drew the same events in the same lanes with the same
glyphs, differing by exactly one blank cell at each turn boundary — so
switching between them looked like nothing happened. Measured on the
fixture: 22 cells vs 19, identical pills.

The magnitude model that did distinguish them (buildLanes: one column
per turn vs one per tool call) had been superseded by the event strip
and was unreachable from the route. Its tests kept passing, which is how
this shipped.

DSH settled the shape. Its ui-trajectory Overview has no mode toggle at
all: always duration-proportional, "Thick rules mark Turn boundaries,
compact inline markers identify Steps", and "just the tool calls" comes
from wheel-zoom, drag-to-focus and search rather than a mode.

So the toggle now carries only what it can honestly carry:

- 1 / 2 select the x-axis — Duration (wall clock) or Turns (one cell per
  event). 3 is gone; a stored "calls" falls back to Turns.
- A turn boundary is a │ rule across all three lanes, in both modes,
  drawn in a gap widened to hold it rather than implied by that gap.
- Which events are on the strip is the row filter, not a mode. `f` gains
  tools-only, and layoutEventStrip takes the filter, so tools-only thins
  rows and lanes together and no-tools/user-only do too. eventAllowed
  documents its two deliberate mismatches with stepAllowed: labeled is a
  row annotation with no event of its own, and reasoning stays on the
  Model lane because the strip is a timeline.

Deletes buildLanes, sparkline, fitColumns, durationWeighted, columnFor
and buildEventStrip. test/lanes.test.ts is ported onto layoutEventStrip,
keeping the assertions that carried real value (shared-axis occupancy,
error glyph, compaction-as-context, empty lanes) and adding the rules
and filter coupling.

DESIGN.md §7.3 is rewritten around this, and §7.1's claim that the
three-lane split is "as in DSH" is softened: the primary README
describes a single combined Overview, a hands-on review describes three
lanes, and the original research was a screenshot, not the source.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrwAnTfTmJSmFwVn3DDFxr
@navbytes
navbytes merged commit a7824e8 into main Sep 4, 2026
1 check passed
@navbytes
navbytes deleted the claude/pi-context-tree-workflow-sjuutd branch September 4, 2026 05:12
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.

2 participants