Commit Graph

351 Commits

Author SHA1 Message Date
Claude (adr007-2c) e0bd0be3d5 feat(display): status messages and the view title show displays, keys keep addressing (improvise-cq1q.13)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-10-09 12:48:50 -07:00
Claude (adr007-display) ba053fdaa5 test(protocol,wasm,io): displays drawn, keys composed, in the grid, page bar, panels and CSV (improvise-cq1q.12)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-10-08 21:47:39 -07:00
Claude (adr007-display) 7a4dc07642 feat(tui,wasm,io): the grid, page bar, item rows and CSV export draw displays (improvise-cq1q.12)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-10-08 21:46:12 -07:00
Ed Langley d571abbd8a Merge pull request #322 from fiddlerwoaroof/feat/8jk6.3-date-hierarchy
feat(import): a date column imports as one hierarchical category (improvise-8jk6.3)
2026-10-02 22:32:35 -07:00
Claude (cmdlang) 8fa504fbdc Merge gh/main into fix/31oe-cmdlang; renumber cli.md §13/§14 to §14/§15
#321 added its own cli.md section 13, so this PR's sections become 14 (pipe
quoting, surplus arguments) and 15 (names the document lacks), with every
citation renumbered. The stale descriptions of the deleted unknown_* helpers in
cli.md, command.md and the test evidence now describe RequireKnown, and section
15 says a refused name stops the rest of a `:` line but not a headless one.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-10-02 22:00:38 -07:00
Claude (8jk63-fix) 581934886d Merge remote-tracking branch 'gh/main' into feat/8jk6.3-date-hierarchy
# Conflicts:
#	context/test-evidence/formula-and-io.md
#	crates/improvise-io/src/import/wizard.rs
#	src/main.rs
2026-10-02 22:00:32 -07:00
Claude (8jk63-fix) 0c56a4b9a1 fix(cli): refuse a --split-date or --axis that names nothing it built (improvise-tpm8) 2026-10-02 21:33:01 -07:00
Claude (imp321) 8f93d4e14a Merge commit '8220a065819652bb224d0b25f5724d6e3a5fbbe8' into fix/headless-import-refusals 2026-10-02 20:30:44 -07:00
Claude (cmdlang) 2e90872a40 Merge commit '8220a065819652bb224d0b25f5724d6e3a5fbbe8' into fix/31oe-cmdlang 2026-10-02 20:30:30 -07:00
Edward Langley 0f116ff582 Merge commit '8220a065819652bb224d0b25f5724d6e3a5fbbe8' into feat/8jk6.3-date-hierarchy 2026-10-02 20:30:05 -07:00
Claude (agent-adr007) 3116d524fb feat(core,protocol): a display beside the key, painted by the three panels (improvise-cq1q.3)
- `Category`, `Item` and `View` each gain a private `display` that starts equal
  to the key and changes only through `set_display` / `set_item_display`.
- `CategoryRow`, `TileEntry` and `ViewSummary` carry `display` beside their key
  field. The key stays what commands are composed from.
- The category-panel headers, the tile bar and the view list paint the display
  in the TUI and the browser. The active-view marker still compares keys.
- No production code sets a display yet, so behaviour is unchanged.
- This is the first slice of ADR 007 step 2. The grid, page bar, records labels,
  item rows and CSV export are improvise-cq1q.12; messages and the view title
  are improvise-cq1q.13.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-25 12:12:59 -07:00
Ed Langley 7dc34e8458 Merge branch 'main' into feat/nn4s-faces 2026-09-25 00:01:32 -07:00
Claude (agent-headless-import) fbe83fcc57 feat(import): --strict refuses a skipped record, and the headless suite covers the reviewed gaps (improvise-8jk6.6, improvise-8jk6.17)
- 8jk6.6, the user's ruling on PR #321 ("Add a --strict flag"): the
  default stays exit 0 with the counted warning. With --strict, any
  skipped record is a refusal that exits 1 and writes nothing. The check
  runs before the build and reads ImportPipeline::skip_warning, the same
  function the build's warning now comes from.
- 8jk6.17: the headless suite now sees three faults that only io unit
  tests caught at efaa5050:
  - naming only the first bad formula (two bad --formula flags);
  - dropping CSV decimal measures;
  - import-json reading a CSV through the JSON constructor.
- Docs record the user's three answers.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-24 23:27:45 -07:00
Claude (agent-faces) 3583359a9c fix(faces): minibuffer faces inherit mode.text-entry, as ADR 004 says (improvise-nn4s.2)
Answers ChatGPT Codex's P2 on PR #319.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-24 23:02:55 -07:00
Claude (agent-hier) 6b910889c0 Merge commit '44718e8a' into feat/8jk6.3-date-hierarchy
# Conflicts:
#	crates/improvise-io/src/import/wizard.rs
#	tests/headless_cli.rs
2026-09-24 22:47:28 -07:00
Claude (agent-hier) 44718e8ab3 Merge commit 'c91cc055c8ad81afd8f055b71c74af6819020637' into feat/felo6-group-parents
# Conflicts:
#	context/test-evidence/formula-and-io.md
2026-09-24 22:42:49 -07:00
Claude (agent-cmdlang) 2c6b751edd Merge commit 'c91cc055c8ad81afd8f055b71c74af6819020637' into fix/31oe-cmdlang
# Conflicts:
#	context/code/cli.md
#	context/test-evidence/cli.md
2026-09-24 22:33:04 -07:00
Claude (agent-hier) 195f3b44b8 feat(import): a date column imports as one hierarchical category, the flat split a choice (improvise-8jk6.3)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-24 22:15:14 -07:00
Claude (agent-headless-import) 9b8b061a93 Merge commit 'c91cc055c8ad81afd8f055b71c74af6819020637' into fix/headless-import-refusals
# Conflicts:
#	context/test-evidence/cli.md
#	context/test-evidence/formula-and-io.md
#	crates/improvise-io/src/import/wizard.rs
#	src/main.rs
#	tests/folder_import_views.rs
#	tests/headless_cli.rs
2026-09-24 22:08:36 -07:00
Claude (agent-faces) e376034453 Merge commit 'c91cc055c8ad81afd8f055b71c74af6819020637' into feat/nn4s-faces 2026-09-24 22:05:35 -07:00
Claude (agent-faces) f7d2730736 style(faces): rustfmt the face migration (improvise-nn4s.2)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-24 22:05:24 -07:00
Claude (agent-faces) 12d976b432 feat(faces): the TUI draws axis, wizard-field, mode and minibuffer colours through face_style (improvise-nn4s.2)
axis_fg, minibuf_fg, field_kind_fg and mode_style are gone, and AxisColor
leaves improvise-core. The category-add prompt turns green, ADR 004
clause 8's one declared exception.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-24 21:58:01 -07:00
Claude (agent-headless-import) 8bd4b1a77b fix(import): headless import refuses instead of silently succeeding (improvise-8jk6.4, improvise-8jk6.5, improvise-8jk6.6)
- 8jk6.4: the CSV parser yields source text (CsvRecords), and only the
  role reads it as a number. A forced category keeps 00565796 and
  220944e2 verbatim; measures and analysis read the text through
  csv_parser::number_in. CsvRecords reaches a pipeline only through
  ImportPipeline::from_csv, so handing CSV text to the JSON constructor
  no longer compiles.
- 8jk6.5: an import formula that does not parse is refused by name with
  its parse error: the build refuses (headless exits non-zero and writes
  nothing), and the wizard keeps it out of the list with the text still
  in the buffer.
- 8jk6.6: an import that kept no record refuses and says why, from the
  build and from the wizard's review step. A partial skip stays a
  warning with a count and exit 0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-24 21:54:39 -07:00
Claude (agent-lpzu) c43ee72402 fix(import): a header-only or measureless file keeps its identity and a view of its own (improvise-lpzu, improvise-8jk6.13, improvise-8jk6.12)
ImportPipeline::from_merge seeds the File items and every file's columns
from Merged::sources, so a header-only CSV keeps its File item and its
columns (unseen columns are proposed as Labels, as an all-null column
is). A file with no measure column gets a records view filtered to it
instead of paging onto another file's hidden measure. Four per-file-view
rules the review found untested now each have a test that fails when
the rule is broken.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-24 21:47:11 -07:00
Claude (agent-hier) e497aef110 feat: nested group runs reach the wire, and views persist expanded(N) and collapsed folds (improvise-felo.6)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-24 21:43:55 -07:00
Claude (agent-cmdlang) 197ce61d65 fix(command): read |pipe quoting| and refuse surplus arguments (improvise-31oe.3)
The command-line lexer reads |…| as one argument through the shared
unescape_pipe_body, anywhere in a word, so a coordinate can quote each half.
Double quotes stay verbatim. Every registration now declares MaxArgs, and the
registry refuses arguments beyond it, naming them, on both arms.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-24 21:43:18 -07:00
Claude (agent-lpzu) 005b7a769a feat(import): a folder of one CSV keeps its File category and view (improvise-lpzu)
The user's ruling on PR #314: a folder always merges, so even a single
CSV in it records its file identity. A CSV named directly still imports
alone. The "(none)" question is answered as no position, per the user.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-24 21:36:00 -07:00
Claude (agent-lpzu) bb505f8faf feat(import): give each merged file a view filtered to it (improvise-lpzu)
A CSV merge now records each file's columns, and the build adds one view
per file: File on Filter at that file's item, its own categories seated
as a fresh import seats them, the rest on None, and other files'
measures hidden. The merged Default view stays the initial view. The
headless and wizard paths both carry the sources.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-24 20:48:16 -07:00
Claude (agent-lpzu) e65d87fda9 feat(import): import a folder of CSVs as one sparse model (improvise-lpzu, improvise-8jk6.2)
A folder argument expands to its *.csv documents through Storage::list,
and a record with no value for a category keeps its cell without that
coordinate instead of being skipped.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-24 20:43:31 -07:00
Edward Langley 7b9e868bd2 test+docs: derive #304's cap fixtures from MAX_CATEGORIES; record the coupling found (improvise-felo.1)
The duplicate step's cap check already reads the one constant after the
merge; its tests filled 12 categories and now fill to the cap. An
injected literal-12 check fails keeping_duplicates_separate_is_refused_at_the_category_cap.
core.md section 9 gains the inventory of what degrades as categories grow.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-24 19:26:41 -07:00
Edward Langley 22cbfa679e Wizard text is clipped to the panel and measured in cells; the list header says what is shown (review B1, B2, Codex P2 on #304)
Every wizard write goes through Panel, which clips to the panel and saturates
its height arithmetic, so no step panics on a small terminal. The bottom row
truncates in cells, so a wrapped message with a multibyte character is drawn
whole. A scrolling list's range is named on its header row, where no item can
cover it. Tests: a multibyte message at every width, wrap bytes vs cells, a
no-panic sweep of every step at 20-100 x 3-20, the first item filling the list.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-24 19:11:42 -07:00
Edward Langley 92233cff14 Merge remote-tracking branch 'gh/main' into feat/cgqt-import-collision-step
# Conflicts:
#	context/test-evidence/tui.md
2026-09-24 18:43:40 -07:00
Edward Langley 1a2952058d Import asks what to do with records that share every coordinate (improvise-cgqt)
When source records collide on every coordinate, the import wizard now asks:
sum them, keep them separate with a generated occurrence category, or go
back. Collisions are found before the build by the same placement pass the
build uses, and are carried to the TUI as structured data (WizardCache) and
listed in full, scrollable. At the 12-category cap, "keep separate" is shown
disabled with its reason and remedy.

Every wizard warning and message now wraps to the panel instead of being
truncated, and long lists scroll.

Headless paths no longer sum silently: `import --no-wizard` takes
`--on-duplicate refuse|sum|separate` (default refuse), and `:import-json`
refuses and points at `:import`.

Reasoning: context/code/formula-and-io.md, "Colliding records are a
question, not a default"; context/code/tui.md; context/code/cli.md §11.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-24 18:43:19 -07:00
Edward Langley 241cc5f384 fix(tui): the grid and the tile bar scroll by the width they are drawn in (improvise-gtn7.1)
Grid: App::cmd_context counted visible_cols from term_width while
draw_content gave the grid term_width minus the 32-column side panel, so
with any panel open the cursor was scrolled past the last drawn column.
viewport::grid_width, reached through App::grid_width, is now the one
answer: draw_content splits on it and the keypress path counts on it.

Tile bar: the scroll measured from the end of the prefix while the draw
began one column later, two once scrolled (the left arrow), so the
selected tile could be dropped. tiles_left(start) is now where both the
scroll (first_tile) and the draw begin.

The grid defect dates from b5418f2e; it became the launch state when
50e4a372 opened the category panel by default, which is why it read as
a regression. Mixed-width columns still lose the cursor with no panel
open: improvise-gtn7.2. Reasoning: context/code/tui.md.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-24 18:32:47 -07:00
Edward Langley b945a7f881 test: the TUI cursor stays drawn beside a side panel and on the tile bar (improvise-gtn7.1)
Both fail on gh/main: the grid at column 4 with any side panel open, the
tile bar at width 42.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-24 18:25:40 -07:00
Edward Langley 46896a7512 Merge PR #250 (3e417729) into the 2026-09-24 integration wave
Conflicts with #294 in render.rs and project.rs resolved as tested:
#250's side of every hunk (levelled GroupEnd { level }, groups()/group_of,
axis_frame's carried headers, the try_from level check), then #294's shape
(Item { tuple, .. }, FrameEntry::item(..), the DataItem { tuple, collapsed }
arm, and GroupEnd { .. } in folds). Evidence EOF appends keep both sides.
2026-09-24 02:49:32 -07:00
Edward Langley f39a95709e Merge PR #279 (044e8d2d) into the 2026-09-24 integration wave
# Conflicts:
#	context/test-evidence/clients.md
2026-09-24 02:47:40 -07:00
Ed Langley 088d469fbb Merge branch 'main' into fix/oyaa-host-list-complete 2026-09-24 01:57:22 -07:00
Edward Langley e44b1491ca Merge gh/main (cc95cfbf) into fix/e265-levelled-groups
# Conflicts:
#	context/test-evidence/command.md
#	context/test-evidence/core.md
2026-09-23 23:26:55 -07:00
Edward Langley 1e49bc0df7 H refuses on every fold the host refuses on; carry the fold flag
improvise-4k1j, from PR #275's review. hidden_row_item refused on a fold
only when the NEAREST group header was collapsed. Under two row
categories that header can be the inner one's: with Region's Coast
collapsed and Product's Kit expanded, the client composed
HideItem{Region, "Coast"} and a twin host wrote a group name into the
document, where the host refuses.

The frame cannot answer this on gh/main: FrameGroup names no level and
an inner GroupEnd reads as an outer one. So FrameEntry::Item now carries
the layout's own DataItem::collapsed flag (a fold, or under one, at any
level) and AxisFrame::folds reads it, the same bit
GridLayout::row_folds_a_group reads. FrameEntry::item builds an
unfolded one, as AxisEntry::item does.

hiding_agrees_with_the_host_on_every_row_of_nested_groups presses H on
all 25 rows of four nested grouped grids, in two windows each. It was
red on the nearest-header check at rows 4 and 5, as the review found.

Also fixes the fmt gate the review found red: fmt-workspaces.sh is a
check, not a formatter, and its diff output was misread as success.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-23 18:35:38 -07:00
Edward Langley 9eb83538fe A message beside its open editor stands in the browser too (improvise-1x9h)
The toast hid every status after six seconds, so a refusal the user was
still fixing vanished with its prompt open: su4u's bug on a timer. The
browser now asks the TUI's question. EditorSession moves into
improvise-protocol, ClientState records which editor a status was
written in, and RenderModel::status_stands tells the toast to hold it
until that editor closes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-23 18:08:26 -07:00
Edward Langley 09db8eb0cd The same mode is not the same editor: a message does not follow a commit (improvise-sx7h)
#257 restored a stale message whenever the mode after a key equalled the
mode before it. Editing and RecordsEditing commit with CommitAndAdvance,
which re-opens an equal AppMode on the next cell. So the message followed
the editor from cell to cell until Esc. The reviewer's repro was R, o, 5,
Tab: "Added new record row", or o's "That record row is already here"
refusal, survived the Tab.

press now compares an EditorSession, made of the things an opening sets:
- the whole AppMode, not its variant;
- the cursor cell, which is a cell editor's target;
- the editor's seed, which EnterEditAtCursor re-seeds from the cell, so a
  commit that re-opens on the same cell (Tab in the last column) is new.

A generation counter was rejected: editors are opened by more than one
effect, and a counter in each is a hand-kept list. The view was left out:
no minibuffer key can switch it and stay.

Tests:
- four press scenarios: the two records repros, Enter in a plain grid,
  and Tab in the last column;
- a direct check that ItemAdd on another category is another editor.

Rivals: mode-only (#257 as pushed), no cell, no seed and discriminant
each fail a distinct step.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-23 17:18:44 -07:00
Edward Langley 2b1c5bf7ad A minibuffer refusal stands while the user fixes it (improvise-su4u)
#233 clears the status message at the start of every TUI keypress. In a
text-entry mode the message sits beside the prompt, and a formula that
does not parse keeps the editor open with the error. So the error
vanished on the first correcting key.

press now remembers the mode and the message. It clears the message as
before and dispatches the key. It puts the stale message back only if
the key left the user in the same text-entry mode and wrote no message
of its own. Editing keys keep the refusal on screen, a key's own message
still wins, and leaving the minibuffer (a successful commit or Esc)
drops it.

The mode check is observed, not predicted: which keys commit is the
keymap's knowledge, and the TUI loop does not copy it.

Rivals, each injected into press and restored: never restoring (#233),
restoring over a key's own message, and restoring whatever the mode each
fail a distinct step of the test.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-23 11:33:00 -07:00
Edward Langley d6e366dceb Group runs nest: markers name their level, pruning re-marks (improvise-e265)
On an axis with two grouped categories, runs nest: North in Upper holds
Jan in Q1, and after Q1 ends North/Mar is still in Upper. The markers
carried no level, and every reader was flat, so the nearest marker
decided. Pruning an inner header dropped its GroupEnd too, and a
surviving row changed group. The unpruned answer was lossy as well:
North/Mar was in no group while it was drawn under Upper.

The user ruled per-level membership, with z toggling the innermost group
(the one nearest the cursor). So:

- AxisEntry::GroupHeader and GroupEnd carry a level. expand_category
  reads it off its prefix, which holds one item per outer category.
  Membership is one open run per level, and a row's group is the
  innermost one (row_group_for/col_group_for, hence z).
- prune_axis_items re-marks the survivors instead of filtering the
  markers. Before each kept item it emits the markers that make the
  output's open runs equal the input's, closing deeper runs innermost-out
  first. So each survivor keeps exactly its runs, no header is kept over
  nothing, no trailing GroupEnd is left, and when nothing is pruned the
  output equals the input. This replaces the pending/close_runs_of/
  run_open machinery.
- The wire: FrameGroup gains level (an index into AxisFrame::cats), and
  FrameEntry::GroupEnd becomes GroupEnd { level }. groups()/group_of
  read the same per-level rule, and group_runs is unchanged (h0iv).
- axis_frame carries the open run at each level above a scrolled
  window, not the single nearest header, so a window opening inside
  North/Q1 still knows Upper.

Tests:
- an enumeration over every funded mask x collapse subset of a two- and
  a three-level fixture (about 4,000 cases), asserting that pruning
  moves no row's group, that no header is drawn over nothing and no axis
  ends on a marker, and that nothing-pruned is marker-for-marker equal;
- the ruling at the layout and through z;
- frame/layout agreement at every window offset;
- the nested window carry.

Rivals, each injected and restored, are in the evidence files: flat
membership, a pruner that owes no GroupEnd, ends passed through eagerly,
headers kept eagerly, nearest-only window carry, and a flat frame.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-23 11:20:03 -07:00
Ed Langley b8a4849622 Merge pull request #233 from fiddlerwoaroof/fix/cq3d-status-expiry
A TUI status message lasts until the next key, then the hint returns (cq3d)
2026-09-23 10:48:51 -07:00
Edward Langley e5b18d3499 Merge remote-tracking branch 'gh/main' into wave/km71-edit-formula
# Conflicts:
#	context/test-evidence/command.md
#	crates/improvise-protocol/src/reduce.rs
2026-09-23 08:32:01 -07:00
Edward Langley d9ea594604 A TUI status message lasts until the next key, then the hint returns
improvise-cq3d: nothing in production cleared status_msg, and draw_status
prints it instead of the derived hint, so after the first message of a
session the TUI never drew the key hint again, in any mode.

run_tui now sends each key through draw.rs::press, which applies
SetStatus("") and then dispatches: a message a key produces is drawn after
that key and expires at the next one, vim-style, so an error is never
wiped before it is drawn. The expiry lives in the TUI front end rather than
App::dispatch_key, which the lockstep harness shares with the browser. The
browser already shows messages transiently (a TOAST_MS toast) beside an
always-drawn #hint. Reasoning: context/code/tui.md.

The test drives press: a stale message must give way to TileSelect's hint,
a message must survive its own key, and give way at the next. Red at base
at the first step; clearing after dispatch fails the second.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-23 08:21:59 -07:00
Edward Langley 5e37973fc0 The TUI help footer's keys come off the keymap
improvise-17ak: render_footer hand-wrote l/Tab, h/S-Tab and q/Esc, a key
table no check could read, and already short of the keymap (n, → and ? were
bound and not advertised). It now draws the page indicator followed by
hint_line(Help), truncated in cells.

Derived rather than deleted: the status row carries the same hint, but
draw_status prints status_msg instead of it whenever a message is standing,
and nothing clears the message on entering Help, so deleting the footer's
keys would leave a user who saved and then pressed ? with none.

The guard renders the popup at 140 columns on every page and requires the
footer row to contain hint_line(Help). Red at base; and a literal copy of
today's string fails it as soon as the keymap drops a binding.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-23 07:51:50 -07:00
Edward Langley 0f3d06afa2 WIP improvise-km71: edit a formula from the panel
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-22 23:31:24 -07:00
Edward Langley f24882766e Merge remote-tracking branch 'gh/main' into fix/help-keys-checks
# Conflicts:
#	context/test-evidence/clients.md
2026-09-22 21:57:17 -07:00