web/demo.improv has no groups and one category per visible axis, deliberately
(gotcha 46), so the multi-level header, the group span, the collapse indicator
and the inline group row -- exactly the code improvise-anr9 added -- had never
been rendered anywhere a test could see (improvise-plcx).
harness/fixtures/grouped.improv is served beside the shipped demo by
harness/build.sh; web/demo.improv is untouched. Two categories on each visible
axis, three row groups with one collapsed, two column groups.
A missing seed is NOT an error -- web/worker.js answers a 404 with an empty
model -- so every assertion is about labels the fixture really carries, never
that a grid exists.
Five injections, five distinct failures, patched in harness/serve so no
rebuild stands between the fault and the run: slash-joining the level tuples;
colSpan = 1 on the group headers; a stray cell on the inline group row;
indicator(false) hard-coded; and the collapse removed from the document.
One thing the ticket did not predict: the row frame's FIRST group header is
never drawn, so the spec asserts two group rows where the document has three,
with improvise-0o6x named on the line.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
scripts/check-comment-runs.py is Python, so running it leaves
scripts/__pycache__/*.pyc — interpreter-version-specific bytecode that a wave
agent then committed, because nothing ignored it. Found by review on PR #41.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three pieces of the standalone deployment, all in web/.
improvise-djm — app.js picks its peer instead of assuming a socket. The
wasm bundle is the same artifact either way (improvise-xks6), so the
difference is confined to one factory: `?ws=` selects the websocket, its
absence spawns `new Worker('./worker.js?doc=&seed=', {type:'module'})`.
The websocket path is KEPT, because improvise-ws-server is how a
TUI-hosted document is shared and nothing else can reach it; the presence
of the parameter is the whole selection mechanism, since a second knob
naming the same fact is a second thing to get wrong.
improvise-bck — web/demo.improv, built against an assertion rather than
by flipping an axis. examples/demo.improv leaves Customer/Date on None,
so every cell is an Aggregate and nothing is editable. The new demo puts
exactly one category on each visible axis (Product on row, _Measure on
column, Quarter on page) and carries Profit = Revenue - Cost, so a
visitor sees the formula move beside the cell they typed in, with no
keystroke and no scrolling. Two tests hold it: tests/web_demo_model.rs
projects the whole grid through grid_of and asserts every CellId is
Record (ADR 002 — that is what `editable` means), and post_message.rs
drives the shipped bytes through the real seed -> VFS -> open -> Snapshot
chain. The first was proved discriminating by pointing it at
examples/demo.improv, where it names the aggregate at (0,0).
One category per axis is asserted too: GridCache slash-joins a
multi-level tuple (gotcha 44), so a second category on an axis puts that
known gap on the front page.
improvise-hwpi — all four. A keydown listener on window feeds
Client::on_key_event, so the classifier is reachable at last; a frame
that will not fold now writes to #status instead of only the console; the
socket has an error listener, so "not running" no longer reads like
"still connecting"; and close() has a re-entry guard. Plus one the ticket
could not have predicted: a worker whose start() rejects is caught inside
worker.js and fires no `error` on the main thread, so the same silence
was about to be rebuilt in the new path — a first-frame watchdog covers
both deployments, because "the peer never answered" is the one condition
that means the same thing on each.
Verified: cargo test --workspace 1159 passing (1155 + 4), clippy clean,
fmt clean, actionlint clean, ./web/build.sh end to end (345 KB / 123 KB
gzipped client, 1.33 MB / 498 KB gzipped worker), every call present in
the generated .d.ts with the right arity, every relative reference in the
assembled site resolving. NOT verified: there is no browser here. No line
of JavaScript in this commit has ever run.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
improvise-v9w: the root `improvise` crate's [dependencies] listed pest,
pest_derive and dirs, none of which appear anywhere under src/. pest and
pest_derive remain genuine dependencies of improvise-formula and
improvise-io (persistence/improv.pest); examples/gen-grammar.rs reaches
pest_meta through [dev-dependencies], which examples do get, so the
examples still build. Verified with cargo build --workspace, cargo build
--examples, cargo test --workspace and the wasm check. Cargo.lock loses
only dirs and its transitive deps.
improvise-wg1: add the GNU Global tag files (GTAGS/GRTAGS/GPATH/GSYMS) and
the root graphviz artifacts (/graph.dot, /graph.html, /graph.svg) to
.gitignore. Named exactly rather than globbed so no checked-in asset is
swallowed; `git ls-files | git check-ignore --stdin` prints nothing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
Review of #7 found the swap could corrupt or lose the database: the move
of the live database out of the way was unchecked, so if it failed the
next move put the new database *inside* the old directory and still
reported success; the restore was unchecked too, and the `rm -rf "$TMP"`
that followed could then delete the only remaining copy.
All three are real. The root cause was doing the critical swap across
filesystems, where a move is a copy that can half-finish.
install_tracker() removes the class instead of patching the cases. The
staged copy is brought alongside the live one first, so every step after
that is a same-filesystem rename: it either happens or it does not. The
previous database is removed only once the new one is in place, and every
move is checked, with a loud message if a restore fails and leaves it
parked. The caller collapses to one condition, which also removed a
duplicated pair of warning branches.
Proved rather than asserted, since this block has now been wrong twice.
`mv` was stubbed to fail at each point in turn:
- old-move fails -> rc=1, live database intact, no nesting, no leftovers
- final rename fails -> rc=1, live database restored, no leftovers
- missing source -> rc=1, live database intact
A first attempt at the third case used chmod, which proves nothing as
root; the stub is what actually exercises the path.
The transient .incoming/.previous directories are gitignored so a crash
mid-swap cannot leave untracked noise.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DyHK7L8S1XDX3EihTnSKaX
Three findings from the review, plus a bug the first one uncovered.
Sync on every session, not only when the database is missing. Cloud
containers reuse a cached workspace, so "already present" does not mean
"still current" — the two runs in testing came back 169 and then 168
issues, which is precisely the drift that makes a skipped sync serve
stale data. Installing bd and warming the build still check first, being
genuinely one-time.
Making the sync unconditional exposed a worse failure than the one it
fixed. `bd init` exits non-zero when the remote is unreachable but still
leaves a .beads/embeddeddolt behind — an empty database. The old check
tested only for that directory, so a failed sync swapped an empty
tracker over a good one and logged success. Verified against a bogus
remote: the good tracker was destroyed. The sync now checks bd's exit
status and confirms the result actually carries issues before installing
it, and the swap parks the old database in the temp directory until the
move succeeds, so a failure keeps the previous tracker rather than
leaving none. Re-verified: the 168-issue tracker survives a failed sync
and the warning says so.
An empty remote tracker is indistinguishable from a failed clone here.
Keeping what we already have is the safe way to be wrong.
The bd installer pipeline redirected only `bash`, so a curl failure (DNS,
403, TLS) left /tmp/bd-install.log empty — exactly when it is read. The
whole pipeline is redirected now.
Also ignore *.gate.lock*: bd drops .beads.gate.lock beside .beads/ on
every run, which otherwise shows up as untracked noise in every session.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DyHK7L8S1XDX3EihTnSKaX
AGENTS.md tells agents to load the `kb` Prolog session at session start,
but prolog-sessions/ was fully gitignored, so the KB could never survive a
fresh clone -- every session started from an empty knowledge base.
Narrow the ignore to prolog-sessions/* with an exception for kb.pl, and
check in a seed KB built from context/repo-map.md and
context/design-principles.md: crate/layer/coupling rules, the
"where do I change X" location table, the Cmd -> Effect pipeline,
the load-bearing invariants, testing doctrine, and the beads workflow.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V69PGQfMJuGRmtSWpfVAqn
40-row CSV with obviously-fake sales data (fictional companies like
Acme Corp, Wonka Industries, Cyberdyne Systems). demo.improv generated
via headless import with Profit formula and a default view showing
Region+Product on rows, Date_Month+Measure on columns. Added the
import command to the README quick-start section.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>