110 Commits

Author SHA1 Message Date
Claude (dbsp-backend) a020ac669f feat(incremental): Sum and Avg through an exact sum in both backends (improvise-hrjz)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-10-10 15:14:20 -07:00
Ed L 6dbbfdb6cb chore: more updates 2026-10-10 11:52:45 -07:00
Claude (agent-adr007) 7ddc50ac62 Merge commit '62066e1e1c972218b099a36f3f10440eb93d430e' into adr007/plan
# Conflicts:
#	context/code/core.md
#	context/test-evidence/core.md
2026-09-25 10:05:02 -07:00
Edward Langley 0ae25b99c5 chore: updating missing dep 2026-09-25 01:08:55 -07:00
Claude (agent-adr007) e56148fa64 feat(core): slugify and the gensym id allocator for ADR 007 (improvise-cq1q.2)
model::id adds slugify, which case folds, turns each run of spaces into one
`_`, strips everything outside a-z, 0-9, `-` and `_`, then drops leading
underscores. It also adds IdAllocator, which keeps one increment-only counter
per document plus every id seen this session; claim admits hand-written ids
verbatim. Full case folding comes from the unicase crate. Nothing calls it yet.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-24 22:02:32 -07:00
Claude (agent-hier) e09ec75226 docs: group hierarchies and fold lines, with the evidence for each rival (improvise-felo.6)
The harness fixtures open their groups explicitly now that groups start closed.

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:23 -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 0b645a8f0e Merge fix/xd3q-reset-clobbers-pipelined-placement (75ce4230) into fix/1x9h-browser-refusal-stands 2026-09-23 23:15:08 -07:00
Ed L af44653f92 chore: setup nixos runners 2026-09-23 20:27:52 -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 cdefeeb9b8 Resolve Enter and H in the browser; decline / and n out loud
improvise-fo0w. Three black-hole keys closed over vocabulary that
already exists, each mirroring its Cmd line for line with a lockstep case:

- enter-advance (Normal Enter) resolves as cursor arithmetic over
  GridExtent; the host's bottom-right AbortChain is Ignored here, which
  loses nothing because Enter is never bound inside a Sequence.
- hide-selected-row-item (H) resolves to ModelCommand::HideItem over the
  outer row category and the selected tuple's first label, and refuses
  on a fold with the host's own FOLD_REFUSAL, now in improvise-protocol.
- search (/) and search-navigate (n) decline out loud as a pair: n needs
  the whole model, and / alone would open a box that finds nothing.

NEEDS_THE_HOST drops to exit-search-mode, force-quit and
toggle-group-under-cursor. Tests that used Enter or "Start search" as
their upstream example move to Ctrl-Q and "Exit search mode", including
shell.spec.js's not-sent case.

Also improvise-0629: the help render test now asserts every ex row is
drawn dim, not just that its description is marked.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-23 18:01:08 -07:00
Ed Langley 0ff17c0f0e Merge pull request #240 from fiddlerwoaroof/fix/ugj4-jibp-mouse-editor
Mouse editor: seed from the target, report the first edit (improvise-ugj4, improvise-jibp)
2026-09-23 10:49:45 -07:00
Edward Langley 463bef149d Merge commit '5932ad749f0df01d4321cc961f57a488ab637dde' into wave/km71-edit-formula
# Conflicts:
#	crates/improvise-protocol/src/lib.rs
2026-09-23 08:51:45 -07:00
Edward Langley 6d5b4e328a improvise-km71: serde back in improvise-formula for FormulaId alone
tt22 removed the formula crate's serde dependency with the legacy JSON loader,
keeping serde only where a type crosses the wire. FormulaId crosses the wire, so
it is the third such type. No derive goes back on the formula AST.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-23 08:50:53 -07:00
Ed Langley 5932ad749f Merge pull request #231 from fiddlerwoaroof/fix/oplp-tile-cursor
Tile select opens on the first regular category, not _Index (oplp)
2026-09-23 08:48:09 -07:00
Edward Langley fa0af07988 Merge remote-tracking branch 'gh/main' into fix/ugj4-jibp-mouse-editor 2026-09-23 08:45:28 -07:00
Edward Langley 37a64445e0 Merge commit 'c6de77dbde8d05ba287cb69d92c230089098916f' into wave/km71-edit-formula
# Conflicts:
#	context/code/command.md
2026-09-23 08:42:50 -07:00
Ed Langley aa96cd6404 Merge pull request #224 from fiddlerwoaroof/wave/tt22-no-legacy-json
.improv is the only native format: remove the legacy JSON document loader (improvise-tt22)
2026-09-23 08:41:29 -07:00
Edward Langley 5960fe8f0a Merge gh/main (e406edc8) into fix/oplp-tile-cursor
Clean merge, no conflicts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-23 08:35:24 -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 c4877456a4 WIP red: mouse editor stale seed and touched (improvise-ugj4, improvise-jibp) 2026-09-23 08:30:51 -07:00
Ed Langley 194d8f529c Merge pull request #205 from fiddlerwoaroof/fix/ixxx-untouched-commit
An untouched cell edit writes nothing (improvise-ixxx)
2026-09-23 08:24:48 -07:00
Edward Langley 66359a2e04 Merge remote-tracking branch 'gh/main' into fix/oplp-tile-cursor
# Conflicts:
#	context/test-evidence/clients.md
2026-09-23 08:23:17 -07:00
Edward Langley 38ec60616c Merge remote-tracking branch 'gh/main' into wave/tt22-no-legacy-json
# Conflicts:
#	context/test-evidence/command.md
2026-09-23 08:18:38 -07:00
Ed Langley 0a4f906b1c Merge pull request #218 from fiddlerwoaroof/tooling/7epn-lnsh
tooling: pr-reviews polls past poll 2; stale harness lock names its fix (7epn, lnsh)
2026-09-23 08:14:05 -07:00
Edward Langley c6c2de96b2 Merge remote-tracking branch 'gh/main' into fix/ixxx-untouched-commit
# Conflicts:
#	context/test-evidence/clients.md
#	context/test-evidence/command.md
2026-09-23 08:13:40 -07:00
Edward Langley f85fddbb2f harness/opfs-contract: refresh Cargo.lock for the dropped serde features (improvise-tt22) 2026-09-23 08:13:25 -07:00
Edward Langley 2383c37f2f Merge remote-tracking branch 'gh/main' into fix/oplp-tile-cursor
# Conflicts:
#	context/test-evidence/clients.md
2026-09-23 08:10:09 -07:00
Edward Langley 27cbe2c1dd Tile select opens on the first regular category, not _Index
improvise-oplp: EnterTileSelect and the browser's resolver both opened the
tile cursor at 0, which is _Index, so `T Enter` or `T r` put _Index alone
on Row and emptied a pivot grid; the browser worker then persisted it.

The rule is improvise_protocol::opening_tile, over &[TileEntry], called by
both ends. TileEntry gains `regular`, filled host-side from CategoryKind in
project::tiles_of, because a thin client cannot tell _Index from a user
category without sniffing names. _Index/_Dim stay tileable (#192).

A lockstep case presses `T Enter` on both ends and pins which category
moved; it was red at base (_Index on Row), and fails for a TUI-only revert
and for a `regular` filled true everywhere. contract.spec.js counted three
ArrowRights from tile 0 and now asserts `T` alone selects Product.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-23 08:08:54 -07:00
Ed Langley 33cf496927 Merge pull request #215 from fiddlerwoaroof/fix/5xbq-1c5m
One page rule for both ends; the help overlay shows how to page (5xbq, 1c5m)
2026-09-23 07:58:59 -07:00
Edward Langley 82bbc74c13 Merge remote-tracking branch 'gh/main' into wave/km71-edit-formula 2026-09-23 07:57:04 -07:00
Edward Langley ebf3dd2ba2 tooling: pr-reviews polls past the second poll; stale harness lock names its fix
improvise-7epn: two cases on the one-tick-per-read clock. One resolves on
poll 3, and one times out after polling up to the deadline. Replacing the
post-poll deadline check with `break` now fails exactly those two
(23 passed, 2 failed), where before it left the suite 23/23.

improvise-lnsh: when `cargo metadata --locked` refuses the opfs-contract
lock, build.sh now says not to remove --locked and names
`cargo update -w --manifest-path harness/opfs-contract/Cargo.toml`.
Checked by hand against a lock with pest pinned to 2.9.1. Evidence is in
context/test-evidence/tooling.md.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-23 07:49:40 -07:00
Edward Langley a8737f3fa5 One page rule for both ends; the help overlay shows how to page
improvise-5xbq: resolve::page_rows is now the one home of the page-scroll
rule; the TUI's Move calls it instead of repeating visible_rows * 3 / 4.
A lockstep case pages (PgDn/PgUp, Ctrl-d/Ctrl-u) on a non-square window,
8 rows by 3 columns, and pins the TUI's landing rows as well as comparing
host and client, since one home moves both ends together. Both faults
the ticket injected (page_rows = window_rows / 2; paging by window_cols)
now fail at the first PgDn, before and after the move to one home.

improvise-1c5m: the help overlay draws the mode's own hint (hint_line,
the string #hint carries) in a sticky footer, so a narrow window whose
app bar wraps under #help still says how to page. Playwright case at
600px, red at base, and red again with the footer made non-sticky.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-23 07:41:03 -07:00
Edward Langley a91db1d857 improvise-km71: edit the formula under the cursor, in place
Enter or e in the formula panel opens FormulaEdit on the formula under the
cursor, seeded with its text. The editor names the formula by a FormulaId
captured when it opens, and commits through a new ReplaceFormula effect and
ModelCommand. The edit keeps the formula's position, renames it in place when
the target changes, and refuses out loud if the formula was removed or
rewritten meanwhile, if the new target belongs to another formula, or if the
text does not parse. A refusal leaves the editor open over the user's text.

FormulaCache now carries each formula's id, so the browser composes the same
edit, and a formula-row click opens the editor on the clicked row.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-22 23:42:33 -07:00
Edward Langley 1550d390cb pr-reviews re-polls after a slow tick; the harness lock fails loudly
improvise-qnpi: `pr-reviews --wait` checked its deadline before the first
re-poll, so a second boundary between two reads of the clock skipped the
only poll (exit 1 with polls=1, reproduced under CPU load). The deadline
is now checked after each poll. A new case drives it with a PATH shim
whose `date` ticks one second per read, so it fails deterministically on
the old loop rather than under load.

improvise-mkbp: harness/opfs-contract/Cargo.lock pinned pest 2.9.1 while
its path deps require 2.9.2, so every build rewrote it. The resolved lock
is committed and build.sh starts with `cargo metadata --locked`; the
wasm-pack `-- --locked` route was tried and does not work (wasm-pack
re-resolves first). opfs.spec.js now names the fix when harness/serve/opfs
is missing, which is what running web/assemble.sh alone leaves.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-22 23:36:00 -07:00
Edward Langley 7628770d79 Merge remote-tracking branch 'gh/main' into fix/ixxx-untouched-commit
# Conflicts:
#	context/test-evidence/command.md
2026-09-22 22:31:19 -07:00
Edward Langley 4c45166b8a WIP: the mouse editor hands wasm its captured seed (improvise-ixxx)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-22 22:24:17 -07:00
Edward Langley c960713f8c The worker's status claims success only on its first frame (improvise-i0v9)
The worker transport wrote "in-page worker · <doc>" when it spawned the
worker, before anything had answered, so a tab whose worker could not start
showed a success-looking status over a blank page. It now writes "starting
the worker · <doc>…", and createShell's arm() takes an `answer` that sawFrame
writes on the first frame, but only while the watchdog is still armed. A
ReadOnly or SeedMissing notice lands BEFORE the Snapshot, and fail() has
already disarmed, so the claim cannot overwrite the precise reason.

The two-tab spec now logs every #status and denies both the premature claim
and "did not answer" before the page clock is walked. A new spec pins the
exact status sequence on the writable path. Reasoning:
context/code/clients.md, "The watchdog".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-22 22:10:29 -07:00
Ed Langley 1c184a5cc2 Merge pull request #176 from fiddlerwoaroof/fix/6qet-collapse-row
CI / Build all bundles (push) Has been cancelled
CI / Test and lint (host) (push) Has been cancelled
CI / wasm32 check (browser bundle crates) (push) Has been cancelled
CI / Browser JS unit tests (node --test) (push) Has been cancelled
CI / Knowledge base consistency (push) Has been cancelled
CI / Lint the harness crate (push) Has been cancelled
CI / Browser harness (OPFS contract + shell) (push) Has been cancelled
CI / Deploy to GitHub Pages (push) Has been cancelled
improvise-6qet: a collapsed group is one aggregate row, and z's cursor follows it
2026-09-22 21:36:41 -07:00
Edward Langley 216b14860d improvise-6qet: the grouped harness spec expects the collapsed group's row
grouped.spec.js asserted a collapsed group contributes no rows (8 data rows).
It now contributes one per Store (10), labelled Upland, and its cells are
not editable. The editable assertion was checked against cell_id without
folds_a_group: it fails with 6 editable cells where 0 are expected.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-22 21:31:06 -07:00
Edward Langley ef7775efc0 Merge remote-tracking branch 'gh/main' into wire2/j4qt
# Conflicts:
#	context/test-evidence/clients.md
2026-09-22 21:28:26 -07:00
Edward Langley 3039ce8e58 Merge gh/main into fix/df2w-i0v9-browser-page
Conflict only in context/test-evidence/clients.md, both appended at EOF:
kept df2w's two entries and ktlk's section, whole.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-22 21:12:41 -07:00
Edward Langley a77e3a9146 A read-only tab refuses edits instead of painting what it cannot save (improvise-j4qt)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-22 21:12:24 -07:00
Edward Langley 942211b921 df2w: an open, empty formula panel is drawn
views/formulas were empty-means-closed, so an open panel over a document
with no formulas built the same RenderModel as a closed one. Option<Vec<_>>
carries both facts in one field; the page hides on null.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-22 20:54:26 -07:00
Edward Langley 334e218c7b s8h7: i on an aggregate names drill, in help and in the browser
In the TUI, EditOrDrill hands an aggregated cell to DrillIntoCell, so `i`
on an aggregate is drill by another door. The help page called it only
"Edit current cell", and the browser refused it in words unrelated to `>`'s,
so a user could not tell the two were one missing feature.

- help.rs: the edit-or-drill row says it drills into an aggregate.
- help.rs: Normal-mode Enter was captioned "Edit current cell (same as i)"
  but is bound to enter-advance alone, which only moves down; the row moves
  to Cursor movement and says so.
- resolve.rs: the Aggregate arm now reads "an aggregate is edited by
  drilling in — " + not_yet("drill"), the same sentence `>` uses.
- context/code/clients.md and the evidence entry reverse the recorded
  "different sentences on purpose" rationale, which was false about the TUI.
- harness/tests/drill.spec.js follows the new wording.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-22 20:53:18 -07:00
Edward Langley c8d715346f fix(web): a narrow window must not widen the document (improvise-tg27)
The report was "a narrow window clips grid columns without usable horizontal
scroll", and the ticket suspected a flex `min-width: auto` trap swallowing
`#grid`'s overflow. It was not that: `#grid` has `overflow: auto`, which
already zeroes a flex item's automatic minimum. What could not shrink was the
app bar.

Measured at 22ab58f on the grouped harness fixture, `documentElement.scrollWidth`
read 1011 at every window narrower than 1011px — a floor, not a slope, which is
the signature of a sum of `flex: none` reserves. Below it the PAGE grew a
horizontal scrollbar, and what it scrolled to was the bar's own right-hand end
sitting outside the window, while the grid slid off to the left.

`header` takes `flex-wrap: wrap`, chosen over a `@media` breakpoint because a
flex item wraps exactly when the line cannot hold it, so the desktop bar is
untouched by construction rather than by a number someone picked. `main` gets
the `min-width: 0` the ticket asked about, and the three side panels are capped
at 40vw so the grid keeps the larger share of a phone window.

What this deliberately does NOT do: make `#grid` scroll to columns beyond the
announced extent. The browser's Viewport is a real window (gotcha 44/47), so
those columns are never projected and no scrolling can reveal them; the
keyboard already reaches every one, and a spec now asserts that.

`harness/tests/narrow.spec.js` asserts `scrollWidth <= clientWidth`, which is a
question a browser answers exactly. `body { overflow-x: hidden }` was rejected
for making that assertion vacuous.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
2026-09-22 15:50:35 -07:00
Edward Langley 6de56e6ba1 Merge branch 'main' into ui/header-stable 2026-09-22 00:17:27 -07:00
Edward Langley d750b433b8 fix: the app bar is reserved, not fitted, and a message is a toast (improvise-n6t8)
The rule is the deliverable: nothing a user expects to be fixed may move.

Four children of <header> were sized by their own content and all four
changed on the SAME event, so the bar reflowed on a mode change with no
message at all: #mode (mode_label, 4-14 chars), #hint (hint_line, one
line per mode), #doc-name (gains and loses " [+]") and #view-name, which
had :empty { display: none } and so appeared out of nothing.

Every child that changes with a keystroke now has an allocated width, and
#hint is the single elastic child, so nothing is positioned by it. The
mode pill's reserve is a FLOOR and deliberately not a cap: a cap absorbs
an over-long label inside a box that never grows, which is a reserve that
hides its own failure. AppMode's exhaustive-match methods make the
compiler demand mode_label for a new variant; nothing checked the CSS
still fit it. harness/tests/header.spec.js is that check.

The spec found what the ticket did not predict: the compounding one was
not the pill. #status was flex: 0 1 auto beside a flex: 1 1 auto hint, so
a longer hint took the shrink out of #status, which WRAPPED to two and
three lines and grew the header by a row -- 52.5px to 71.0px. Reserving
the pill alone would have left that untouched.

ViewState::status_msg is now a toast in a position: fixed dock anchored at
its bottom, so it takes part in no layout and stacks ABOVE the minibuffer:
the prompt is neither covered nor displaced. composeStatus is gone -- the
two facts go to two elements, so a repaint reaches onModel and cannot
address onShell at all. That is gotcha 46's rule made structural rather
than kept by a join's convention; its unit test was replaced, not deleted.

Dismissal keys off message IDENTITY, not a timer. status_msg is STATE and
repaint() re-offers it on every revision, so a setTimeout alone resurrects
the toast on the next keypress. Cost stated rather than hidden: two
identical messages in a row show once, because nothing carries a sequence
and the alternative reading of an ambiguous offer is exactly the bug.

Nine of seventeen modes are reachable in a browser and the spec names them
individually; three injected faults are recorded in the evidence file.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
2026-09-22 00:17:22 -07:00
Edward Langley 260f6e0b48 test: the page-menu spec reads the demo under the decimal default
`harness/tests/pages.spec.js` arrived on `main` with #123 while this branch was
open, and asserts `toHaveText("20,300")` against `web/demo.improv`. The merge
was clean and the combination was wrong — design-principles §7's third case
exactly: `git merge-tree` answers "can git combine these texts", not "does the
combination do what both sides meant". CI caught it on the merge ref, which is
the only place the two sides had ever been run together.

The assertion is unchanged in what it discriminates: it exists so that a menu
moving only the label fails, and `"20,300.00"` separates Q4 from Q1's
`"18,400.00"` exactly as the old literal did.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
2026-09-21 23:59:35 -07:00