Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,34 @@

## 0.2.3 (unreleased)

- **Turns and Calls drew the same chart.** The lane modes only ever differed by one blank cell
at each turn boundary — everything else (which events, which lanes, glyphs, colours) was
identical, so switching between them looked like nothing happened. Checking DeepSeek Harness
settled the fix: its Trajectory Overview has no mode toggle at all, marks turns with rules,
and gets "just the tool calls" from search and interval-focus rather than a mode.
- `1` / `2` now select only the **x-axis**: Duration (proportional to wall clock) or Turns
(one cell per event). `3` is gone.
- A turn boundary is drawn as a `│` **rule across all three lanes**, in both modes, instead of
a wider gap you had to measure.
- "What did I run" is now a row filter, not a mode: `f` cycles through a new **`tools-only`**,
and the lanes follow the active filter — so `tools-only` thins the rows and the lanes
together, and `no-tools` / `user-only` do too. `labeled` leaves the lanes whole (a label is
not an event), and thinking stays on the Model lane under every filter.
- A `ctree.lanes` of `"calls"` stored by an older version falls back to Turns.
- Deleted the original magnitude-column lane model (`buildLanes`, `sparkline`, `fitColumns`,
`durationWeighted`, `columnFor`) and `buildEventStrip`, superseded by the windowed layout in
0.2.2. None of it was reachable from the route; it survived only because its tests kept
passing, which is how the Turns/Calls bug shipped in the first place.

- The tree's status line shows what the provider was really sent at the row under the cursor,
right-aligned under the header gauge: `T2 reply · prompt 43.7k · 30.1k cached`. Unlike the
per-row token column — a marginal, chars/4 estimate — this is the provider's own
`tokens.input` (+ cache), so it includes the system prompt and the tool definitions, and the
two numbers stack in one column to be read against each other. A user turn shows the reply to
it; a turn with no reply yet reads `not sent yet`; branch headers have none. It is history: an
older row's figure is what went out then, and does not shrink when you crop something above it
later.

Pi's fork-from-an-earlier-message flow, ported whole:

- `⏎` on a row above where you are now opens Pi's tree-selector question — **No summary** /
Expand Down
71 changes: 60 additions & 11 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ built-in `tui.json` keybinds table only covers OpenCode's own action names):
- inside the route: `↑↓`/`j k` move · `g G` top/bottom · `shift+↑↓`/`J K` jump 20 ·
`←→`/`h l` fold/unfold branch · `⏎` go here · `b` branch · `m` merge · `c` crop mark ·
`t` result⇄turn · `a` auto-mark · `x` undo · `i` inspector · `u` consumers ·
`D` decisions · `L` label · `/` search · `f` filter cycle · `1 2 3` lane mode ·
`D` decisions · `L` label · `/` search · `f` filter cycle · `1 2` lane x-axis ·
`y` copy · `e` expand branch inline · `q`/`esc` back.

---
Expand Down Expand Up @@ -447,6 +447,24 @@ auto-compaction is the *lossy* event the user wants to pre-empt with `/crop` or
`/merge`. `sidebar_content` slot: `⎇ fix-flaky-test · open · parent "Fix flaky test"`,
active crops, decisions on path, `[/tree]`.


**The cursor's own prompt figure.** The tree's status line carries, right-aligned directly under
the header gauge, what the provider was really sent at the row you are on:
`T2 reply · prompt 43.7k · 30.1k cached`. It sums `input + cache.read + cache.write` exactly as
`contextSizeOf` does, so the two numbers stack in one column and are read against each other —
the gauge is now, this is the cursor, the gap is everything after that point. Because it is
`tokens.input`, it inherently includes the system prompt and the tool definitions, which the
per-row token column (a marginal, chars/4 estimate) never does.

`core/tree.ts#promptAtRow` resolves it: an assistant step carries its own message's report on
every one of its rows; a user turn takes the first assistant message after it, the reply whose
prompt was the first to include that turn; a branch header has none, its column already being a
subtree total. Nothing sent yet — a trailing turn, a reply in flight — reads `not sent yet`
rather than a zero. It is 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 deliberately inspecting can carry that caveat where forty scrolling ones cannot. Dropped
whole, not wrapped, when the terminal is too narrow (§7.6).

### 6.8 Compaction interplay

- The transform hook runs during compaction too, so cropped results are already
Expand All @@ -471,9 +489,9 @@ records as user messages, and can use the headless `/ctree` commands.

### 7.1 Can they be combined? Yes — they are two axes of one thing

> **Revised after the 0.1.1 UX review.** The lanes are an *event strip*, as in DSH: one pill per
> event on one shared axis across Input / Model / Tools, one cell of gap between neighbours (two at
> a turn boundary in Turns mode), width proportional to duration in Duration mode, categorical
> **Revised after the 0.1.1 UX review.** The lanes are an *event strip*: one pill per
> event on one shared axis across Input / Model / Tools, one cell of gap between neighbours (three at
> a turn boundary, holding the `│` rule), width proportional to duration in Duration mode, categorical
> colours (input green / context grey, model purple, tools orange, error red), the selected step
> inverted. Nothing is scaled by token count — height-as-magnitude produced flat or solid lanes on
> real sessions; tokens live in the row column. When the strip does not fit, the newest events are
Expand All @@ -487,6 +505,15 @@ records as user messages, and can use the headless `/ctree` commands.
> a one-line overview track under the lanes shows the window's position and red ticks at failed
> tool calls, so global orientation survives without giving up pill fidelity.

**On the DSH comparison.** The three-lane split is ours. DSH's own `ui-trajectory` README
describes a *single* combined Overview ("A fixed Overview above the ledger projects real record
start/duration timing from left to right; Assistant spans divide recorded TTFT from decoding")
above a vertical ledger of User/Assistant/Tool/Subtool records; a hands-on review describes
"input, model and tool lanes across the top", so the secondhand sources conflict and the
original research here (Appendix A) was a screenshot and write-ups, not the source. What we do
take from DSH directly is the event-pill idea, the always-available duration axis, and the turn
rule (§7.3).

DSH's Trajectory tab and Pi's `/tree` both render the *same* append-only event
stream. DSH orders it by **time** and annotates each step with **cost and duration**
(three lanes on top, an inspector on the right, role badges, turn markers). Pi orders
Expand Down Expand Up @@ -542,15 +569,37 @@ squashed and its ◆ record is T3 on the trunk; `fix-flaky-test` is where you ar
is expanded under its anchor; the `bun test` result is 4.7k and flagged as a crop
candidate; the inspector shows the selected `ls -la` call with DSH's five facets.

### 7.3 Modes (the `Duration | Turns | Calls` toggle)
### 7.3 The lane x-axis (`1` / `2`), and why there is no "Calls" mode

They change the *x-scale of the minimap* and the *grouping of rows*:
> **Revised (0.2.3).** There were three modes — `Duration | Turns | Calls` — and Turns and Calls
> drew the *same events in the same lanes*, differing only by one blank cell at each turn
> boundary. Checking DSH settled it: its Trajectory Overview has **no mode toggle at all**. It
> is always duration-proportional ("projects real record start/duration timing from left to
> right"), marks turns with **rules** ("Thick rules mark Turn boundaries, compact inline markers
> identify Steps"), and reaches "just the tool calls" through zoom, drag-to-focus and search
> rather than a mode.

| Mode | Minimap x-axis | Rows |
|---|---|---|
| **Turns** (default) | one column per user turn | one row per step, grouped under `T<n>` markers (what the mockup shows) |
| **Calls** | one column per tool call | tool rows only (assistant text folded into the turn header) — the "what did I run" view, also the natural crop view |
| **Duration** | proportional to wall-clock (`time.start`/`end`) | rows carry `+12.3s` gaps; long gaps (user thinking, permission waits) are drawn as `┆ 4m idle` separators |
So the toggle now carries only what it can honestly carry — the **x-scale** — and the two other
jobs move to the mechanisms that already existed:

| | What it is |
|---|---|
| **`1` Duration** | cells proportional to wall clock (`time.start`/`end`), so a 3-minute `bash` is visibly wider than a 0.2 s `read`. DSH's only axis. |
| **`2` Turns** (default) | one cell per event: an event *count* axis, where a busy turn is wide because it did a lot, not because it took long. |
| **turn boundaries** | drawn as a `│` rule across all three lanes, in **both** modes — DSH's thick rule. Never a mode of its own, and never just a wider gap you have to measure. |
| **which events** | the row `Filter` (`f`, §7.5), not a mode. `tools-only` is the "what did I run" view and thins the rows *and* the lanes together; `no-tools` is its mirror; `user-only` leaves the prompts. |

`core/lanes.ts#eventAllowed` is that coupling, with two deliberate mismatches against the rows'
`stepAllowed`: `labeled` is an annotation on a row rather than a property of an event, so the
lanes read it as no filter; and reasoning stays on the Model lane under every filter, because
folding thinking into its assistant row is a *reading* convenience while the strip is a
timeline — a minute of thinking is a thing that happened.

What this deletes: the `calls` `LaneMode`, the `3` keybind, and the whole original
magnitude-column model (`buildLanes`, `sparkline`, `fitColumns`, `durationWeighted`,
`columnFor`) plus `buildEventStrip`, which the windowed layout superseded in 0.2.2. None of it
was reachable from the route; it survived only because its tests kept passing, which is exactly
how the Turns/Calls bug shipped.

### 7.4 Secondary views (`u`, `D`, crop mode) — from `pi-context-tree`

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ Keys inside `/tree` (vim-aligned): `j k` `ctrl+d ctrl+u` `gg G` move · `[ ]` ho
branches · `h l` `Tab` fold/unfold · `⏎` go (the footer says what it will do for the selected row)
· `b` branch · `m` merge · `c` crop mode (`space` mark, `a` auto, `t` result⇄turn, `⏎` apply) ·
`u` undo (`x` too) · `/` live search, `n N` next/prev · `f` filter picker · `i` inspector ·
`1 2 3` lanes, `0` off · `s` what's filling the context · `D` decisions · `L` label · `y` copy ·
`1 2` lanes, `0` off · `s` what's filling the context · `D` decisions · `L` label · `y` copy ·
`?` help · `q`.

## Commands
Expand Down
20 changes: 18 additions & 2 deletions docs/USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ e.g. `{ "keybinds": { "open": "ctrl+t", "up": "k,up", "copy": "none" } }` — na
`open up down jump_up jump_down half_up half_down first last prev_branch next_branch fold
unfold toggle go branch label filter_pick filter_prev search search_next search_prev back
crop crop_toggle_mode mark auto undo merge inspector consumers copy mode_duration mode_turns
mode_calls lanes_off decisions export help`.
lanes_off decisions export help`.

## Upgrading

Expand Down Expand Up @@ -113,7 +113,7 @@ appended to the trunk as a normal message.*
| `D` `E` | decisions panel, export `ctree-decisions.md` |
| `s` | consumers: what is filling the context (`⏎` opens a bucket, `space` marks one entry for crop) |
| `i` | inspector pane on/off (auto-hidden under 110 columns) |
| `1 2 3` `0` | timeline lanes by duration / turns / tool calls; `0` off |
| `1 2` `0` | timeline lanes, x-axis by duration / one cell per event; `0` off. `│` marks a turn boundary, and the lanes show whatever the `f` filter shows — so `f` → `tools-only` is the "what did I run" view in both the rows and the lanes |
| `L` | label the selected message |
| `f` `F` | filter picker (default → no-tools → user-only → labeled → all); `F` steps back |
| `/` `n` `N` | live search: typing re-filters the rows, `⏎` keeps the filter, `esc` clears; `n` `N` next / previous match |
Expand Down Expand Up @@ -179,6 +179,22 @@ Palette: **Context tree**, **Branch here**, **Merge branch**, **Decisions**, **L
- Tokens: a leading `~` means estimated (chars/4); assistant steps use the model's own counts.
Step durations and lane heights are read from the same data — estimated wherever the `~` is.
- `⚠` ≥10k tokens, `✂` cropped, `✗` tool error, `◆` decision record, `◇` branch summary.

The right end of the status line (second line, under the `ctx …` gauge) is the same figure for
the row your cursor is on: `T2 reply · prompt 43.7k · 30.1k cached` — the whole prompt the
provider was actually sent at that point, **system prompt and tool definitions included**,
because that is what `tokens.input` covers. Read it against the gauge above it: the gauge is
now, this is where the cursor is, and the gap between them is everything after that point.

It comes from the provider, not an estimate, so it has no `~`. An assistant step reports its
own message's prompt; a user turn reports the reply *to* it (the first prompt that included
it); a turn with no reply yet says `not sent yet`. Branch headers have none — their token
column is already a subtree total. On a terminal too narrow to hold both, the figure is
dropped rather than wrapped.

One caveat: it is **history**. An older row's figure is what went out at the time, so it does
not shrink when you later crop or merge something above it — the estimated per-row column does,
because it is recomputed from the transcript each time.
- A branch you just made says `just branched, nothing here yet` — there is nothing to unfold.
- The gauge on the prompt line: `⎇ fix-flaky · ctx ▓▓░░░ ~46k/200k · filling · 95% cached
▲+24% (bash)` — the context of the next prompt (the same figure as OpenCode's own sidebar),
Expand Down
Loading
Loading