#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
PR #316 review blockers. A cursor fallback (clear-cell, yank, paste,
drill-into-cell) now answers only for coordinates that were omitted, so
`:clear-cell Type/Clothing Month/Feb junk` is refused instead of clearing the
cursor's cell. The unknown-name checks move from `execute` to a RequireKnown
effect run at apply time, so `:create-view Second . switch-view Second` is
accepted as `improvise cmd` accepts it. Also pins that a quoted `.` is an
argument.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
This commit implements the download flow for SaveAs and ExportCsv per the
USER RULING on improvise-2mqz. The correct shape is:
- A request frame asks the host for BYTES
- ServerFrame returns the bytes (or Rejected)
- Browser triggers downloads via Blob + <a download>
Key changes:
Protocol layer (improvise-protocol):
- Add Request::DownloadDocument and Request::DownloadCsv variants
- Add ClientFrame::DownloadDocument and ClientFrame::DownloadCsv
- Add ServerFrame::DocumentBytes and ServerFrame::CsvBytes
- Add KeyOutcome::LocalThenRequest variant for operations that need
both local fold (mode change, buffer clear) AND a server request
- Update stranding() to unstrand SavePrompt and ExportPrompt modes
- Update keymap to use rebind() instead of commit() for commit-save
and commit-export (because LocalThenRequest can't be in a Sequence)
Host layer (improvise-command):
- Handle new ClientFrames in host.rs returning bytes or Rejected
- Add Rejection::ExportFailed variant for CSV export failures
- Update CommitSave and CommitExport to clear their buffers
- Make render_csv() public in improvise-io for host use
WASM client layer (improvise-wasm-client):
- Add PendingDownload type with filename, bytes, and kind
- Store pending downloads for JavaScript retrieval via take_pending_download()
Browser layer (web/app.js):
- Add triggerDownload() function using Blob + <a download>
- Poll for pending downloads after frame handling
This unstrands SavePrompt and ExportPrompt which were previously refused
by stranding() due to missing wire forms.
Refs: improvise-2mqz
Co-authored-by: Ed Langley <fiddlerwoaroof@users.noreply.github.com>
- `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
- 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
set-axis reads its axis at parse time, so an unknown keyword is an argument
error. set-axis, set-page, hide-item, show-item, toggle-group and switch-view
refuse an unknown category, item, group or view by name instead of doing
nothing, so a headless run exits non-zero.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
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
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
The wizard's private copy of the limit is gone; it reads the model's
now-public constant. Tests that fill to the cap derive it. Why 12 was
chosen and what 24 costs: context/code/core.md section 9.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
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
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
Every write the merged PRs added now names a view, chosen deliberately:
- The keyboard commit names OpenedCell::view, captured when the editor
opened, as EditTarget does for the mouse (context/code/clients.md).
- RenameRecordCoordinate names no view. Its record must hold cells, so it
resolves in the store alone: NamedRecord::Stored, as does the rename
destination records_at_stake adds. named_records replaces cells_named as
the exhaustive list records_named is read off.
- #252/#265's non_item_refusal moves into Grids::unwritable. The strict view
lookup (a missing view is RecordGone) and the Grids memo are kept.
- Test fixtures name a view their document has: Default for host() and
host_on_a_fold(), the records view for the rename pair, and the viewing
client's own view in browser_round_trip.
Evidence files: both sides kept, every heading of both parents verified
present. Test names: the merge holds every test of both parents.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
Resolutions:
- client.rs imports: EditorSession (main) and ModelCommand (#251).
- client.rs refused(): #251 keeps the popped frame to learn which record it
renamed; main (#279/3kfc) calls settle_sent() after the pop. Both kept, in
the order pop, settle_sent, replay, then #251's Opened::Retry.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
Resolutions:
- CellId: #221 gives Coordinate fields { record, category }; main (#292)
adds NonItem { category, item }. Every exhaustive match now names both
(render.rs, core.rs x2, project.rs, resolve.rs x3, wasm-client lib.rs and
render.rs); project.rs's basis arm keeps #221's Coordinate-carries-a-record.
- resolve.rs edit_or_drill: #221 drops the "renaming a coordinate" decline
(Coordinate opens an editor); main's NonItem decline arm is kept.
coordinate_at_cursor's None arm gains NonItem. reopened_at keeps #295's
declined_at shape; declined_at's editable arm names Coordinate { .. }.
- client.rs: kept main's status_seen/status_owner and #221's opened +
OpenedCoordinate; every settle site calls both settle_opened and
settle_status. Main's new rebase() also gets settle_opened, since it
rebuilds the view exactly as apply() does.
- view/mod.rs: re-export both GridEval (main) and record_row (#221).
- context/code/command.md and context/test-evidence/clients.md: both sides
appended independent sections; kept both.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
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.