Skip to content

Context folding as outlines, fold guides and streamed file slots in the TUI - #84

Merged
sidkmenon merged 19 commits into
mainfrom
codex/tui-guides-and-state
Oct 5, 2026
Merged

sidkmenon merged 19 commits into
mainfrom
codex/tui-guides-and-state

Conversation

@sidkmenon

Copy link
Copy Markdown
Contributor

Context now shows unchanged code as an outline, the TUI draws fold guides and gives every streamed file a slot, and files that fall back to a line diff keep their folds and colours.

Context plugin

  • Only changed folds open. An unchanged fold shows its signature and closer with its body folded, and every fold inside it is folded too, so each expand opens one level.
  • An unchanged fold within lines of a change stays on screen. Other unchanged folds join their neighbours in one row. A row never crosses a fold edge.
  • A changed scope opens its body, so a signature-only change shows the start of the body.
  • Only a fold with a twin on the other side folds as a unit, so rows stay aligned.
  • The plugin is one walk (Rewrite the context plugin as one walk), and the old group plugin is merged into it.

Engine and wire

  • Each side has a root fold that is the file. Folds carry indent and syntax on the wire.
  • A line-diff fallback pairs folds that sit at the same place inside one run of unchanged lines, so they share a fold_state_id.
  • Highlights come from the parse that builds the folds, so a fallback keeps its colours. DiffOptions gains syntax, DiffResult gains lhs_highlights/rhs_highlights, and project::Inputs::syntax is gone. The wire shape does not change.

Queries

  • TypeScript has its own shared and context queries: interfaces, object types and enums fold.
  • Exported JavaScript and TypeScript declarations scope from the export line.
  • The patched TSX grammar now builds through diffr-grammars as a vendored grammar.

TUI

  • AST folds draw at their declared guide positions. Guides sit at the indent of each scope's opener line. The +/- sign column is gone.
  • Every manifest file has a stable slot. The store validates results against the manifest, and scrolling keeps one reading anchor.
  • The TUI README is deleted.

Tools

  • pprint.sh prints a comparison with the TUI's own row model.

Testing

  • cargo fmt --check (both workspaces), typos, cargo test --locked, cargo test -- --ignored, cargo xtask test-plugins
  • diffr-ts: typecheck, build, 23 tests
  • TUI: typecheck, 88 tests

AI-assisted: implemented with Codex and Claude Code; the folding rule was worked out with the author through examples.

https://claude.ai/code/session_018Yz1XeLzfXEQkaRvKZ6LN8

Patch the pinned TSX grammar so imported-type arrays parse without tolerating syntax errors.

AI-assisted implementation and verification with Codex.

Agent-Session: dca03de1-4350-4b24-b376-cb54c86fcc2d
Agent-Session: 82924cb5-dcfa-42e4-abb0-ef40393249f8
Agent-Session: 01a103d9-eefe-7d11-9db3-da97527a7db7
Agent-Session: b7f96806-2bcb-4ee8-aca4-c5bedad0939a
Keep changes and useful context visible, wrap each hidden interval once, and retain nested AST folds for gradual expansion. Add file-wide plugin edits and keep unchanged TUI folds neutral.

AI-assisted implementation and verification with Codex.

Agent-Session: dca03de1-4350-4b24-b376-cb54c86fcc2d
Agent-Session: 82924cb5-dcfa-42e4-abb0-ef40393249f8
Agent-Session: 01a103d9-eefe-7d11-9db3-da97527a7db7
Agent-Session: b7f96806-2bcb-4ee8-aca4-c5bedad0939a
Revert the file-phase visit, cursor.regions() and wrap_range from the
previous commit; the host, wit, SDK and cursor are back to their prior
API. The group plugin is removed and its job moves into context, which
now runs last:

- Each sibling list is shaped when the walk enters it: leaves are cut at
  kept/hidden edges and every run of hidden siblings of two or more lines
  becomes one collapsed row, linked across sides.
- An unchanged fold hides whole when the other side holds its partner.
- Opening a row reads as an outline: scopes keep their first and last
  line with the body closed as one row, comments stay open, and folds
  holding another plugin's collapsed region stay open.
- Header lines of a changed scope, such as a doc comment above the
  signature, win over hiding an unchanged fold.
- New context queries: bindings to arrays, objects and closures,
  callback statements, if/while/do/try blocks, Rust function bodies and
  comment runs.

Not done: rows adjacent across levels where no closing line can separate
them (Python blocks), which needs join to move folds between neighbours.

AI-assisted with Claude Code; reviewed and directed by the author.

Claude-Session: https://claude.ai/code/session_01DS7xVM7t9KBr33aGFeTFqb
Agent-Session: a21f1781-9b0d-47f6-a269-e93bb179c266
Agent-Session: a366aa2e-0bed-4e42-9097-1917231c9f0a
Agent-Session: b7f96806-2bcb-4ee8-aca4-c5bedad0939a
Agent-Session: 82924cb5-dcfa-42e4-abb0-ef40393249f8
The shared Python and Rust queries folded single comments while the
context plugin folded the whole run above code. The first comment of a
run then had two fold ranges, a query conflict that kept the file from
being diffed. The shared queries now fold the run, and context tags it.

AI-assisted with Claude Code.

Agent-Session: a366aa2e-0bed-4e42-9097-1917231c9f0a
Agent-Session: 82924cb5-dcfa-42e4-abb0-ef40393249f8
Agent-Session: 01a1061e-c424-7fa2-8db0-b8e0c93f5d16
Agent-Session: b7f96806-2bcb-4ee8-aca4-c5bedad0939a
A fold query with @fold.open must now also capture @fold.indent: the
node the body's indent is read from, usually its first statement. In Go
the capture skips a leading label, which gofmt outdents. Each fold keeps
that position and, for a fold with an opener, its exact span between the
opener and the closer. Two patterns on one node must agree on both.

AI-assisted with Claude Code.

Agent-Session: a366aa2e-0bed-4e42-9097-1917231c9f0a
Agent-Session: 82924cb5-dcfa-42e4-abb0-ef40393249f8
Agent-Session: 01a1061e-c424-7fa2-8db0-b8e0c93f5d16
Agent-Session: b7f96806-2bcb-4ee8-aca4-c5bedad0939a
Each fold now carries `indent`, where its content starts, and `syntax`,
its span from the opener to the closer before rounding to lines. A
client can then place a collapsed row at its body's column without
reading the source text. A fold made by a plugin's join takes its parent
fold's indent. The plugin interface does not change.

AI-assisted with Claude Code.

Agent-Session: a366aa2e-0bed-4e42-9097-1917231c9f0a
Agent-Session: 82924cb5-dcfa-42e4-abb0-ef40393249f8
Agent-Session: 01a1061e-c424-7fa2-8db0-b8e0c93f5d16
Agent-Session: b7f96806-2bcb-4ee8-aca4-c5bedad0939a
`./pprint.sh <base> <head>` prints the rows the TUI would draw, as plain
text. Collapsed rows sit at their fold's indent. `--mock-summaries`
runs the summarizer against a local stand-in for Gemini.

AI-assisted with Claude Code.

Agent-Session: a366aa2e-0bed-4e42-9097-1917231c9f0a
Agent-Session: 82924cb5-dcfa-42e4-abb0-ef40393249f8
Agent-Session: 01a1061e-c424-7fa2-8db0-b8e0c93f5d16
Agent-Session: b7f96806-2bcb-4ee8-aca4-c5bedad0939a
Each side's tree now has one root: a fold over the whole file. Both roots
share a fold_state_id, and the root's visibility replaces the file
record's own, so a hidden file is a collapsed root. Plugins visit the
root like any fold. The removed-runs plugin no longer treats the root as
a removed run's owner.

AI-assisted with Claude Code.

Agent-Session: a366aa2e-0bed-4e42-9097-1917231c9f0a
Agent-Session: 01a1061e-c424-7fa2-8db0-b8e0c93f5d16
Agent-Session: b7f96806-2bcb-4ee8-aca4-c5bedad0939a
Agent-Session: 82924cb5-dcfa-42e4-abb0-ef40393249f8
Entering an unchanged fold hides it. Entering a leaf cuts it where kept
lines start and stop. Leaving a fold folds each run of hidden children
into one row. The root makes the top level one more fold, so no node
shapes its siblings ahead of the walk.

AI-assisted with Claude Code.

Agent-Session: a366aa2e-0bed-4e42-9097-1917231c9f0a
Agent-Session: 01a1061e-c424-7fa2-8db0-b8e0c93f5d16
Agent-Session: b7f96806-2bcb-4ee8-aca4-c5bedad0939a
Agent-Session: 82924cb5-dcfa-42e4-abb0-ef40393249f8
Project syntax openers, fold labels, closing suffixes and enclosing guides from existing wire metadata. Preserve leaf correspondence while masking folded cells, and tint only changed lines within paired leaves.

Add Paper-style projection fixtures and scope hover accents.

AI-assisted: implemented and validated with Codex.
Agent-Session: a366aa2e-0bed-4e42-9097-1917231c9f0a
Agent-Session: 01a1061e-c424-7fa2-8db0-b8e0c93f5d16
Agent-Session: b7f96806-2bcb-4ee8-aca4-c5bedad0939a
Agent-Session: 82924cb5-dcfa-42e4-abb0-ef40393249f8
Agent-Session: 01a1084c-5827-7552-b4d8-d53dd96563af
The store places each streamed file in its manifest slot and rejects a
file outside the manifest or one that arrives twice. The viewer keys all
state by manifest index, so a pending file has a scrollable slot with a
spinner, and previous/next buttons skip to loaded files.

The scroll position is one anchor: a row key and offset, plus the fold
and source line it showed. Geometry changes resolve the anchor again
instead of writing corrective state. Fold toggles read the current
state, so quick repeated clicks alternate.

Add a saved-stream visual review script.

AI-assisted: implemented with Codex, simplified with Claude Code.
Agent-Session: a366aa2e-0bed-4e42-9097-1917231c9f0a
Agent-Session: 01a1061e-c424-7fa2-8db0-b8e0c93f5d16
Agent-Session: b7f96806-2bcb-4ee8-aca4-c5bedad0939a
Agent-Session: 82924cb5-dcfa-42e4-abb0-ef40393249f8
Agent-Session: 01a1084c-5827-7552-b4d8-d53dd96563af
AI-assisted with Claude Code.

Agent-Session: 01a1061e-c424-7fa2-8db0-b8e0c93f5d16
Agent-Session: b7f96806-2bcb-4ee8-aca4-c5bedad0939a
Agent-Session: 82924cb5-dcfa-42e4-abb0-ef40393249f8
Agent-Session: 01a1084c-5827-7552-b4d8-d53dd96563af
The engine parses each side once. With `syntax` set it collects highlight
captures from that tree, before the match runs, so a file that falls back
to a line diff still has its syntax as well as its folds. The CLI no
longer parses a second time to highlight.

AI-assisted with Claude Code.

Agent-Session: 01a1061e-c424-7fa2-8db0-b8e0c93f5d16
Agent-Session: b7f96806-2bcb-4ee8-aca4-c5bedad0939a
Agent-Session: 82924cb5-dcfa-42e4-abb0-ef40393249f8
Agent-Session: 01a1084c-5827-7552-b4d8-d53dd96563af
TypeScript used the JavaScript queries, which have no rule for type
declarations, so an interface had no fold and its closing brace joined
the unchanged lines after it. A shared TypeScript query adds the JavaScript
structure plus interface, object type and enum bodies. Context makes those
declarations scopes, matched on their export when exported, so a change
inside keeps the header and the closing brace.

AI-assisted with Claude Code.

Agent-Session: 01a1061e-c424-7fa2-8db0-b8e0c93f5d16
Agent-Session: b7f96806-2bcb-4ee8-aca4-c5bedad0939a
Agent-Session: 82924cb5-dcfa-42e4-abb0-ef40393249f8
Agent-Session: 01a1084c-5827-7552-b4d8-d53dd96563af
A file diffed by line had no fold pairs, so context could not hide any
fold whole and left one-line headers and closers between nested rows.
A left and a right fold now pair when both lie inside one run of
unchanged rows over the same lines of it: the two sides of such a run
are the same text, so the folds are twins.

AI-assisted with Claude Code.

Claude-Session: https://claude.ai/code/session_018Yz1XeLzfXEQkaRvKZ6LN8
Agent-Session: b7f96806-2bcb-4ee8-aca4-c5bedad0939a
Agent-Session: 82924cb5-dcfa-42e4-abb0-ef40393249f8
The bare declaration starts after `export `, mid-line, so its fold
began a line late and merged with the body. Bare declarations are now
matched only where they are not exported; the export rule covers the
rest.

AI-assisted with Claude Code.

Claude-Session: https://claude.ai/code/session_018Yz1XeLzfXEQkaRvKZ6LN8
Agent-Session: b7f96806-2bcb-4ee8-aca4-c5bedad0939a
Agent-Session: 82924cb5-dcfa-42e4-abb0-ef40393249f8
An unchanged fold is one item: its signature and closer show and its
body folds, with every fold inside folded too, so opening it shows one
more level. Near a change it stays on screen; otherwise it joins its
neighbours' row. A changed scope opens its body even when the body is
unchanged. Only a fold with a twin on the other side folds as an item,
so rows stay aligned.

AI-assisted with Claude Code.

Claude-Session: https://claude.ai/code/session_018Yz1XeLzfXEQkaRvKZ6LN8
Agent-Session: b7f96806-2bcb-4ee8-aca4-c5bedad0939a
Agent-Session: 82924cb5-dcfa-42e4-abb0-ef40393249f8
AI-assisted with Claude Code.

Claude-Session: https://claude.ai/code/session_018Yz1XeLzfXEQkaRvKZ6LN8
Agent-Session: b7f96806-2bcb-4ee8-aca4-c5bedad0939a
Agent-Session: 82924cb5-dcfa-42e4-abb0-ef40393249f8
paired, opens_with_scope, near_change and fold_once each had one caller,
so reading the rule meant jumping between them. hidden also re-checked
the root and a changed scope's header, which item and kept already
cover. No behaviour change.

AI-assisted with Claude Code.

Claude-Session: https://claude.ai/code/session_018Yz1XeLzfXEQkaRvKZ6LN8
Agent-Session: b7f96806-2bcb-4ee8-aca4-c5bedad0939a
Agent-Session: 82924cb5-dcfa-42e4-abb0-ef40393249f8
@sidkmenon
sidkmenon enabled auto-merge (squash) October 5, 2026 06:58
@sidkmenon
sidkmenon merged commit 313fffd into main Oct 5, 2026
35 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