docs(web): from https the address must be wss://, and the server does not speak it

The page and the README told the reader that a ws:// address is blocked from
an https origin, and then gave them a ws:// address. improvise-ws-server has
no TLS at all -- no rustls, no native-tls, nothing -- so wss:// is not
obtainable from it and the documented pairing could not work from the
deployed site.

Both now say the address must be wss:// from the deployed page, that the
server speaks plain ws:// only so TLS has to be terminated in front of it,
and that the ws:// form works when the page is served over http://localhost.
The missing capability is filed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
This commit is contained in:
Edward Langley
2026-09-19 20:54:04 -07:00
parent b7dcde56d5
commit 4e1a79fe5f
3 changed files with 19 additions and 7 deletions
+3 -1
View File
@@ -33,10 +33,11 @@
{"_type":"issue","id":"improvise-km8","title":"1.2 Fix Cargo.toml metadata","description":"Add missing package fields: description, repository, homepage, documentation, readme, keywords, categories, license. Description: 'Terminal pivot-table modeling in the spirit of Lotus Improv'. Keywords: tui, pivot, spreadsheet, data, improv. Categories: command-line-utilities, visualization.","status":"closed","priority":1,"issue_type":"task","assignee":"Edward Langley","owner":"el-github@elangley.org","created_at":"2026-04-09T04:06:48Z","created_by":"Edward Langley","updated_at":"2026-07-30T23:06:31Z","closed_at":"2026-04-09T07:20:17Z","close_reason":"Metadata already present: description, license, repository, homepage, readme, keywords, categories all populated.","dependency_count":0,"dependent_count":2,"comment_count":0} {"_type":"issue","id":"improvise-km8","title":"1.2 Fix Cargo.toml metadata","description":"Add missing package fields: description, repository, homepage, documentation, readme, keywords, categories, license. Description: 'Terminal pivot-table modeling in the spirit of Lotus Improv'. Keywords: tui, pivot, spreadsheet, data, improv. Categories: command-line-utilities, visualization.","status":"closed","priority":1,"issue_type":"task","assignee":"Edward Langley","owner":"el-github@elangley.org","created_at":"2026-04-09T04:06:48Z","created_by":"Edward Langley","updated_at":"2026-07-30T23:06:31Z","closed_at":"2026-04-09T07:20:17Z","close_reason":"Metadata already present: description, license, repository, homepage, readme, keywords, categories all populated.","dependency_count":0,"dependent_count":2,"comment_count":0}
{"_type":"issue","id":"improvise-902","title":"1.1 Audit and remove or update context/SPEC.md","description":"Compare context/SPEC.md against actual code. If spec contradicts code significantly, delete it. Optionally move salvageable conceptual content to docs/design-notes.md with a staleness disclaimer. Do not try to bring spec into sync.","status":"closed","priority":1,"issue_type":"task","assignee":"Edward Langley","owner":"el-github@elangley.org","created_at":"2026-04-09T04:06:46Z","created_by":"Edward Langley","updated_at":"2026-07-30T23:06:31Z","closed_at":"2026-04-09T07:10:05Z","close_reason":"Audited SPEC.md against code. Found minor inaccuracies (storage internals, wizard step count, search mode representation) but no major architectural contradictions. Deleted SPEC.md since it was largely redundant with repo-map.md and design-principles.md. Salvaged product vision and non-goals into docs/design-notes.md with staleness disclaimer.","dependency_count":0,"dependent_count":1,"comment_count":0} {"_type":"issue","id":"improvise-902","title":"1.1 Audit and remove or update context/SPEC.md","description":"Compare context/SPEC.md against actual code. If spec contradicts code significantly, delete it. Optionally move salvageable conceptual content to docs/design-notes.md with a staleness disclaimer. Do not try to bring spec into sync.","status":"closed","priority":1,"issue_type":"task","assignee":"Edward Langley","owner":"el-github@elangley.org","created_at":"2026-04-09T04:06:46Z","created_by":"Edward Langley","updated_at":"2026-07-30T23:06:31Z","closed_at":"2026-04-09T07:10:05Z","close_reason":"Audited SPEC.md against code. Found minor inaccuracies (storage internals, wizard step count, search mode representation) but no major architectural contradictions. Deleted SPEC.md since it was largely redundant with repo-map.md and design-principles.md. Salvaged product vision and non-goals into docs/design-notes.md with staleness disclaimer.","dependency_count":0,"dependent_count":1,"comment_count":0}
{"_type":"issue","id":"improvise-tik","title":"Phase 1: Repository hygiene","description":"Raise the quality floor of what a stranger sees when they land on the repo. Subtasks cover spec audit, Cargo.toml metadata, publish-readiness, CSV audit, and example files.","status":"closed","priority":1,"issue_type":"feature","owner":"el-github@elangley.org","created_at":"2026-04-09T04:05:45Z","created_by":"Edward Langley","updated_at":"2026-04-09T07:22:34Z","closed_at":"2026-04-09T07:22:34Z","close_reason":"All 5 subtasks complete: SPEC.md audited, Cargo.toml metadata fixed, publish dry-run clean, CSV audit done, example files created.","dependencies":[{"issue_id":"improvise-tik","depends_on_id":"improvise-2fr","type":"blocks","created_at":"2026-04-08T23:37:31Z","created_by":"Edward Langley","metadata":"{}"},{"issue_id":"improvise-tik","depends_on_id":"improvise-902","type":"blocks","created_at":"2026-04-08T23:37:30Z","created_by":"Edward Langley","metadata":"{}"},{"issue_id":"improvise-tik","depends_on_id":"improvise-bv1","type":"blocks","created_at":"2026-04-08T23:37:32Z","created_by":"Edward Langley","metadata":"{}"},{"issue_id":"improvise-tik","depends_on_id":"improvise-km8","type":"blocks","created_at":"2026-04-08T23:37:30Z","created_by":"Edward Langley","metadata":"{}"},{"issue_id":"improvise-tik","depends_on_id":"improvise-n1h","type":"blocks","created_at":"2026-04-08T23:37:32Z","created_by":"Edward Langley","metadata":"{}"}],"dependency_count":5,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"improvise-tik","title":"Phase 1: Repository hygiene","description":"Raise the quality floor of what a stranger sees when they land on the repo. Subtasks cover spec audit, Cargo.toml metadata, publish-readiness, CSV audit, and example files.","status":"closed","priority":1,"issue_type":"feature","owner":"el-github@elangley.org","created_at":"2026-04-09T04:05:45Z","created_by":"Edward Langley","updated_at":"2026-04-09T07:22:34Z","closed_at":"2026-04-09T07:22:34Z","close_reason":"All 5 subtasks complete: SPEC.md audited, Cargo.toml metadata fixed, publish dry-run clean, CSV audit done, example files created.","dependencies":[{"issue_id":"improvise-tik","depends_on_id":"improvise-2fr","type":"blocks","created_at":"2026-04-08T23:37:31Z","created_by":"Edward Langley","metadata":"{}"},{"issue_id":"improvise-tik","depends_on_id":"improvise-902","type":"blocks","created_at":"2026-04-08T23:37:30Z","created_by":"Edward Langley","metadata":"{}"},{"issue_id":"improvise-tik","depends_on_id":"improvise-bv1","type":"blocks","created_at":"2026-04-08T23:37:32Z","created_by":"Edward Langley","metadata":"{}"},{"issue_id":"improvise-tik","depends_on_id":"improvise-km8","type":"blocks","created_at":"2026-04-08T23:37:30Z","created_by":"Edward Langley","metadata":"{}"},{"issue_id":"improvise-tik","depends_on_id":"improvise-n1h","type":"blocks","created_at":"2026-04-08T23:37:32Z","created_by":"Edward Langley","metadata":"{}"}],"dependency_count":5,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"improvise-o8fa","title":"improvise-ws-server speaks ws:// only, so the deployed page cannot reach it","description":"Found by opening the deployed page (2026-09-20). The guidance the page and the README carried was WRONG in a way that sends someone down a dead end.\n\nFROM AN https ORIGIN A ws:// ADDRESS IS BLOCKED AS MIXED CONTENT, so the deployed GitHub Pages site needs wss://. improvise-ws-server has NO TLS AT ALL -- no rustls, no native-tls, nothing in the crate or its dependencies -- so wss:// is not obtainable from it. The text said \"note that from an https origin a ws:// address is blocked\", which correctly named the symptom and then offered an address that can never work.\n\nTEXT CORRECTED ALREADY: both web/index.html and README.md now say the address must be wss:// from the deployed page, that the server speaks plain ws:// only, and that the ws:// form works when the page is served over http://localhost. This ticket is the missing CAPABILITY.\n\nTHREE WAYS TO PROVIDE IT, and they are not equivalent:\n * TERMINATE TLS IN FRONT (nginx, caddy, cloudflared, ngrok). Zero code, works today, and is what the corrected text points at. But it means the documented path to sharing a TUI-hosted document involves a second piece of software, which is a poor answer for a tool whose selling point is that it is one binary.\n * NATIVE wss:// IN improvise-ws-server -- rustls plus a cert. Real work, and it raises the certificate question: a self-signed cert makes the browser refuse the socket with no usable error, so this is only pleasant with a real cert or an explicitly trusted local CA (mkcert).\n * DECIDE THE DEPLOYED PAGE DOES NOT PAIR WITH A LOCAL SERVER at all, and say so. Defensible: the standalone worker deployment is the point of the Pages site, and ws:// pairing is a LOCAL development convenience that works fine from the local page. Cheapest, and it makes the docs shorter rather than longer.\n\nThe third is probably right for now and the first is the escape hatch; the second only earns its place if sharing a TUI-hosted document to a remote browser becomes a real use case. Do not implement the second by default.","status":"open","priority":2,"issue_type":"feature","owner":"el-github@elangley.org","created_at":"2026-09-20T03:53:54Z","created_by":"Edward Langley","updated_at":"2026-09-20T03:53:54Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"improvise-955n","title":"[bug] Host::commands applies a batch partially before rejecting","description":"Found by Copilot on PR #13 (the retro review of the unreviewed batch), DECLINED there with reasons on the thread, and filed rather than patched.\n\nADR 001 is emphatic that a stale batch is never applied, NOT EVEN PARTIALLY -- and improvise-ondj went to the trouble of a TYPE (Checked) to make the unchecked path unconstructible. But Checked is PER-COMMAND: Checked::against answers for one Command against the App AS IT STANDS. A ClientFrame::Commands is a SEQUENCE of them, and Host::commands applies in order and abandons at the first rejection. So a frame carrying SwitchView followed by a stale Command::Model has already applied the SwitchView.\n\nSO ADR 001 GUARANTEE IS CURRENTLY TRUE OF A SINGLE COMMAND AND FALSE OF A BATCH.\n\nA SECOND-ORDER EFFECT NOBODY HAD WRITTEN DOWN, found while declining it: Worker::on_frame skips persist() when ANY frame in the answer is Rejected. So a batch that DID apply a SwitchView before being refused leaves Workbook::active_view moved in memory and NOT WRITTEN BACK -- the document on disk and the document in the worker disagree, silently.\n\nWHY IT WAS DECLINED RATHER THAN PATCHED: closing it means the basis check moving from the COMMAND to the BATCH -- every command answered before any is applied. That is an ADR 001 design change with a real question inside it: WHAT DOES A BATCH BASIS MEAN WHEN ITS LATER COMMANDS WERE COMPOSED AGAINST ITS EARLIER ONES EFFECTS? A client resolving one keypress produces SetSelected plus offsets computed from the selection it just set. Answer that before writing code.\n\nREACHABILITY IS BOUNDED TODAY: no client in this workspace composes a batch containing more than one basis-carrying Command::Model -- the batches improvise-bpq2 produces are view commands, which carry no basis. Do this before the second tranche of client-side resolution creates a reason to batch two model commands.\n\nSupersedes the earlier ticket filed from bpq2 if there is one -- dedupe.","status":"open","priority":2,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-20T03:35:27Z","created_by":"Edward Langley","updated_at":"2026-09-20T03:35:27Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"improvise-955n","title":"[bug] Host::commands applies a batch partially before rejecting","description":"Found by Copilot on PR #13 (the retro review of the unreviewed batch), DECLINED there with reasons on the thread, and filed rather than patched.\n\nADR 001 is emphatic that a stale batch is never applied, NOT EVEN PARTIALLY -- and improvise-ondj went to the trouble of a TYPE (Checked) to make the unchecked path unconstructible. But Checked is PER-COMMAND: Checked::against answers for one Command against the App AS IT STANDS. A ClientFrame::Commands is a SEQUENCE of them, and Host::commands applies in order and abandons at the first rejection. So a frame carrying SwitchView followed by a stale Command::Model has already applied the SwitchView.\n\nSO ADR 001 GUARANTEE IS CURRENTLY TRUE OF A SINGLE COMMAND AND FALSE OF A BATCH.\n\nA SECOND-ORDER EFFECT NOBODY HAD WRITTEN DOWN, found while declining it: Worker::on_frame skips persist() when ANY frame in the answer is Rejected. So a batch that DID apply a SwitchView before being refused leaves Workbook::active_view moved in memory and NOT WRITTEN BACK -- the document on disk and the document in the worker disagree, silently.\n\nWHY IT WAS DECLINED RATHER THAN PATCHED: closing it means the basis check moving from the COMMAND to the BATCH -- every command answered before any is applied. That is an ADR 001 design change with a real question inside it: WHAT DOES A BATCH BASIS MEAN WHEN ITS LATER COMMANDS WERE COMPOSED AGAINST ITS EARLIER ONES EFFECTS? A client resolving one keypress produces SetSelected plus offsets computed from the selection it just set. Answer that before writing code.\n\nREACHABILITY IS BOUNDED TODAY: no client in this workspace composes a batch containing more than one basis-carrying Command::Model -- the batches improvise-bpq2 produces are view commands, which carry no basis. Do this before the second tranche of client-side resolution creates a reason to batch two model commands.\n\nSupersedes the earlier ticket filed from bpq2 if there is one -- dedupe.","status":"open","priority":2,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-20T03:35:27Z","created_by":"Edward Langley","updated_at":"2026-09-20T03:35:27Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"improvise-08wb","title":"[bug] A refused ClientFrame::Commands keeps the commands before the refusal","description":"Found by improvise-bpq2 (2026-09-20), stated on the variant, and currently UNREACHABLE from any client in this workspace -- filed so it is a decision rather than a discovery.\n\nClientFrame::Commands(Vec\u003cCommand\u003e) batches, because one cursor key resolved client-side is SetSelected plus the offsets viewport_effects returns, and a host that folds the selection but not the offset projects the wrong window. Host::commands applies IN ORDER and ABANDONS AT THE FIRST REJECTION.\n\nTHAT LEAVES A HOLE ADR 001 DOES NOT HAVE. ADR 001 is emphatic that a stale batch is never applied, NOT EVEN PARTIALLY -- and improvise-ondj went to the trouble of a type (Checked) to make the unchecked path unconstructible. But Checked is per-COMMAND, and a ClientFrame::Commands is a sequence of them, so a frame whose third command is refused has already applied the first two.\n\nWHY IT IS NOT LIVE: no client in this workspace composes a Commands frame containing more than one basis-carrying Command::Model -- the batches bpq2 produces are view commands (SetSelected + offsets), which carry no basis.\n\nTHE FIX IS TO MOVE THE BASIS CHECK TO THE BATCH: check every Command::Model in the frame before applying any of them. That is the shape ADR 001 already describes for an effect batch, one level up, and it would make Host::commands match the guarantee the ADR states.\n\nDo this before a client has reason to batch two model commands -- which the second tranche of client-side resolution (the ~15 bindings bpq2 left) may well create.","status":"open","priority":2,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-20T02:36:42Z","created_by":"Edward Langley","updated_at":"2026-09-20T02:36:42Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"improvise-08wb","title":"[bug] A refused ClientFrame::Commands keeps the commands before the refusal","description":"Found by improvise-bpq2 (2026-09-20), stated on the variant, and currently UNREACHABLE from any client in this workspace -- filed so it is a decision rather than a discovery.\n\nClientFrame::Commands(Vec\u003cCommand\u003e) batches, because one cursor key resolved client-side is SetSelected plus the offsets viewport_effects returns, and a host that folds the selection but not the offset projects the wrong window. Host::commands applies IN ORDER and ABANDONS AT THE FIRST REJECTION.\n\nTHAT LEAVES A HOLE ADR 001 DOES NOT HAVE. ADR 001 is emphatic that a stale batch is never applied, NOT EVEN PARTIALLY -- and improvise-ondj went to the trouble of a type (Checked) to make the unchecked path unconstructible. But Checked is per-COMMAND, and a ClientFrame::Commands is a sequence of them, so a frame whose third command is refused has already applied the first two.\n\nWHY IT IS NOT LIVE: no client in this workspace composes a Commands frame containing more than one basis-carrying Command::Model -- the batches bpq2 produces are view commands (SetSelected + offsets), which carry no basis.\n\nTHE FIX IS TO MOVE THE BASIS CHECK TO THE BATCH: check every Command::Model in the frame before applying any of them. That is the shape ADR 001 already describes for an effect batch, one level up, and it would make Host::commands match the guarantee the ADR states.\n\nDo this before a client has reason to batch two model commands -- which the second tranche of client-side resolution (the ~15 bindings bpq2 left) may well create.","status":"open","priority":2,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-20T02:36:42Z","created_by":"Edward Langley","updated_at":"2026-09-20T02:36:42Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"improvise-anr9","title":"Project the five grid-frame facts: unblocks H, z, gz and fixes Food/Alice at once","description":"Two tickets turn out to be ONE piece of work, noticed by improvise-bpq2 while walking the bindings.\n\nimprovise-s1xw (multi-level grid labels -- the browser shows Food/Alice where the TUI shows two columns) and the bindings H / z / gz need THE SAME FIVE FACTS, which gotcha 44 already enumerates as the grid frame the cache does not carry:\n row_cats / col_cats -- how many header levels\n row_items -- multi-level label TUPLES and inline group headers\n col_group_for -- column group spans\n is_group_collapsed -- the collapse indicator\n(record_col is the sixth and is CORRECT to keep off: session-local, keys pending drill edits.)\n\nSo:\n * hide-selected-row-item (H) needs to know WHICH CATEGORY a row label belongs to -- impossible while row_labels slash-joins a tuple.\n * toggle-group / gz need group membership and collapsed state.\n * And the browser renders the slash-joined form, which the TUI own test asserts must not appear.\n\nDOING THEM SEPARATELY MEANS TOUCHING row_labels TWICE. It is a co-ordinated type change either way -- improvise-wasm-client/src/render.rs consumes both row_labels and col_labels -- so the cheap moment is once.\n\nDO NOT add row_levels beside row_labels. Two answers to \"what are the labels\" is the disease (improvise-edp), and bpq2 has just spent a wave demonstrating what one shared derivation buys.\n\nSupersedes/absorbs improvise-s1xw -- decide which id survives.","status":"open","priority":2,"issue_type":"task","owner":"el-github@elangley.org","created_at":"2026-09-20T02:36:42Z","created_by":"Edward Langley","updated_at":"2026-09-20T02:36:42Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"improvise-anr9","title":"Project the five grid-frame facts: unblocks H, z, gz and fixes Food/Alice at once","description":"Two tickets turn out to be ONE piece of work, noticed by improvise-bpq2 while walking the bindings.\n\nimprovise-s1xw (multi-level grid labels -- the browser shows Food/Alice where the TUI shows two columns) and the bindings H / z / gz need THE SAME FIVE FACTS, which gotcha 44 already enumerates as the grid frame the cache does not carry:\n row_cats / col_cats -- how many header levels\n row_items -- multi-level label TUPLES and inline group headers\n col_group_for -- column group spans\n is_group_collapsed -- the collapse indicator\n(record_col is the sixth and is CORRECT to keep off: session-local, keys pending drill edits.)\n\nSo:\n * hide-selected-row-item (H) needs to know WHICH CATEGORY a row label belongs to -- impossible while row_labels slash-joins a tuple.\n * toggle-group / gz need group membership and collapsed state.\n * And the browser renders the slash-joined form, which the TUI own test asserts must not appear.\n\nDOING THEM SEPARATELY MEANS TOUCHING row_labels TWICE. It is a co-ordinated type change either way -- improvise-wasm-client/src/render.rs consumes both row_labels and col_labels -- so the cheap moment is once.\n\nDO NOT add row_levels beside row_labels. Two answers to \"what are the labels\" is the disease (improvise-edp), and bpq2 has just spent a wave demonstrating what one shared derivation buys.\n\nSupersedes/absorbs improvise-s1xw -- decide which id survives.","status":"open","priority":2,"issue_type":"task","owner":"el-github@elangley.org","created_at":"2026-09-20T02:36:42Z","created_by":"Edward Langley","updated_at":"2026-09-20T02:36:42Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"improvise-ir7l","title":"[bug] A formula cell is offered as editable and silently reverts","description":"Pre-existing and consistent with the TUI, but improvise-bck just put it on the DEMO FRONT PAGE, which changes who meets it: the first thing a stranger sees is Profit beside Revenue and Cost.\n\nA formula target cell projects CellId::Record, so render.rs::editable says true, so the browser offers an editor. Type into Profit and the write lands -- then recompute_formulas overwrites it on the next pass and the value REVERTS with no explanation.\n\nWHY IT IS NOT SIMPLY A BUG IN editable: a formula target IS a stored coordinate; the cell is a record. What makes it unwritable is not its identity but that a FORMULA OWNS IT. So the fix is a fact the projection does not currently carry -- something like \"this cell is derived\" -- and it belongs beside numeric/error on CellDisplay, which already exists precisely because the SERVER is the side that knows.\n\nCHECK THE TUI BEFORE CHANGING ANYTHING: it has the same behaviour, so whatever is decided should land in both, or the divergence becomes the new bug (gotcha 44 is what that looks like). And note ADR 002 deliberately made settability a fact about the CellId VARIANT -- adding a second, independent \"but not really\" flag is exactly the two-answers-to-one-question shape this codebase keeps curing. Consider instead whether a formula-owned cell should project a different CellId at all.","status":"open","priority":2,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-20T02:29:51Z","created_by":"Edward Langley","updated_at":"2026-09-20T02:29:51Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"improvise-ir7l","title":"[bug] A formula cell is offered as editable and silently reverts","description":"Pre-existing and consistent with the TUI, but improvise-bck just put it on the DEMO FRONT PAGE, which changes who meets it: the first thing a stranger sees is Profit beside Revenue and Cost.\n\nA formula target cell projects CellId::Record, so render.rs::editable says true, so the browser offers an editor. Type into Profit and the write lands -- then recompute_formulas overwrites it on the next pass and the value REVERTS with no explanation.\n\nWHY IT IS NOT SIMPLY A BUG IN editable: a formula target IS a stored coordinate; the cell is a record. What makes it unwritable is not its identity but that a FORMULA OWNS IT. So the fix is a fact the projection does not currently carry -- something like \"this cell is derived\" -- and it belongs beside numeric/error on CellDisplay, which already exists precisely because the SERVER is the side that knows.\n\nCHECK THE TUI BEFORE CHANGING ANYTHING: it has the same behaviour, so whatever is decided should land in both, or the divergence becomes the new bug (gotcha 44 is what that looks like). And note ADR 002 deliberately made settability a fact about the CellId VARIANT -- adding a second, independent \"but not really\" flag is exactly the two-answers-to-one-question shape this codebase keeps curing. Consider instead whether a formula-owned cell should project a different CellId at all.","notes":"VERIFIED CONCRETELY during the PR #13 review (2026-09-20), and the diagnosis is sharper than when this was filed.\n\nTHE ROOT CAUSE: project::cell_id decides Record vs Aggregate purely on whether a regular category sits on Axis::None. IT NEVER ASKS WHETHER THE _Measure COORDINATE NAMES A FORMULA TARGET. So a formula cell is a Record, render.rs::editable says true, and the browser offers an editor.\n\nMEASURED AGAINST THE SHIPPED DEMO:\n col 2 label=\"Profit\" is_record=true text=\"12,200\"\n after applying SetCell(key, Number(999.0)), Profit reads \"12,200\"\n\nThe write is ACCEPTED AND STORED. Model::evaluate_aggregated checks formula_cache BEFORE raw data (model/types.rs:409), so the typed value is shadowed forever with nothing telling the user. Not a revert -- a permanent silent shadow.\n\nAND THE TEST THAT COVERS THIS AREA IS LYING BY ITS NAME. every_visible_cell_in_the_web_demo_is_settable PASSES while its name is FALSE for the Profit column -- a third of the landing-page grid. It was proved discriminating against the one thing it CAN see (the aggregate at (0,0) in examples/demo.improv) and is structurally blind to this. That is exactly the shape AGENTS.md calls this repo's characteristic find, arriving in the test written to prevent it.\n\nTHE DECISION IS STILL THE DECISION, and ADR 002 is where it goes:\n * TEACH cell_id ABOUT FORMULA TARGETS -- changes what EVERY client calls a record, and record identity is also ADR 001's unit of atomicity, so this reaches the basis check too.\n * OR GIVE editable A SECOND INPUT -- but that is the conflation gotcha 42 exists to forbid, and ADR 002 deliberately made settability a fact about the CellId VARIANT precisely so there could not be a second answer.\n\nThe first is probably right and is not small. Until it lands, the demo's front page teaches a visitor that typing into Profit does nothing.","status":"open","priority":2,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-20T02:29:51Z","created_by":"Edward Langley","updated_at":"2026-09-20T03:35:39Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"improvise-vhcz","title":"[bug] OpfsFs::list disagrees with the other backends on path shape","description":"Found by improvise-9x6 (2026-09-20) and currently PAPERED OVER by always passing the flat root.\n\nOpfsFs::list returns BARE NAMES; MemoryFs and PhysicalFs return dir-JOINED ones. So Path::new(\"\") is the ONE dir at which all three agree, and the only one at which storage_contract actually describes OpfsFs.\n\nTHE TRAP: a worker that organised documents under docs/ would get an EMPTY PICKER in a browser and a FULL ONE in every test. The harness cannot see the disagreement because it never runs OpfsFs -- improvise-xf1 is deferred -- so this is precisely the class of defect the missing browser harness exists to catch, discovered by reading instead.\n\nTWO FIXES:\n * Make OpfsFs::list join dir, matching the other two. Straightforward, and makes storage_contract describe all three at every dir.\n * Or document the flat root as PART OF THE CONTRACT and assert it in storage_contract, so the constraint is stated rather than discovered.\n\nThe first is better unless there is a reason OPFS cannot -- note gotcha 28 says backends that are not trees treat dir as a NAME PREFIX, which is already the trait doc contemplating this, so check whether the doc or the impl is the thing that is wrong.","status":"open","priority":2,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-20T02:10:14Z","created_by":"Edward Langley","updated_at":"2026-09-20T02:10:14Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"improvise-vhcz","title":"[bug] OpfsFs::list disagrees with the other backends on path shape","description":"Found by improvise-9x6 (2026-09-20) and currently PAPERED OVER by always passing the flat root.\n\nOpfsFs::list returns BARE NAMES; MemoryFs and PhysicalFs return dir-JOINED ones. So Path::new(\"\") is the ONE dir at which all three agree, and the only one at which storage_contract actually describes OpfsFs.\n\nTHE TRAP: a worker that organised documents under docs/ would get an EMPTY PICKER in a browser and a FULL ONE in every test. The harness cannot see the disagreement because it never runs OpfsFs -- improvise-xf1 is deferred -- so this is precisely the class of defect the missing browser harness exists to catch, discovered by reading instead.\n\nTWO FIXES:\n * Make OpfsFs::list join dir, matching the other two. Straightforward, and makes storage_contract describe all three at every dir.\n * Or document the flat root as PART OF THE CONTRACT and assert it in storage_contract, so the constraint is stated rather than discovered.\n\nThe first is better unless there is a reason OPFS cannot -- note gotcha 28 says backends that are not trees treat dir as a NAME PREFIX, which is already the trait doc contemplating this, so check whether the doc or the impl is the thing that is wrong.","status":"open","priority":2,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-20T02:10:14Z","created_by":"Edward Langley","updated_at":"2026-09-20T02:10:14Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"improvise-hmws","title":"Storage cannot expose an async seam through Box\u003cdyn Storage\u003e","description":"Found by improvise-9x6 (2026-09-20). Every worker host will hit this, so it is worth solving once rather than in each.\n\nOpfsFs::open / attach / refresh / commit_deletes are on the STRUCT, not on the Storage trait -- deliberately, per gotcha 28: Storage stays synchronous because createSyncAccessHandle yields a blocking handle, and only four operations are genuinely async. But App::storage is a Box\u003cdyn Storage\u003e, so HANDING THE BACKEND TO THE App PUTS ALL FOUR PERMANENTLY OUT OF REACH. The App owns the only copy and the trait object cannot name them.\n\nAND THE OBVIOUS FIX IS REFUSED BY THE COMPILER: impl Storage for Rc\u003cOpfsFs\u003e is two foreign types, so the orphan rule forbids it. improvise-9x6 worked around it with a local newtype (bindings::Shared) holding an Rc\u003cOpfsFs\u003e and forwarding the four sync methods. That works but every worker must write it again.\n\nTWO CANDIDATE FIXES:\n * Ship the shared wrapper FROM improvise-io, so the newtype exists once where OpfsFs does and the orphan rule is satisfied.\n * Or have OpfsFs hand out an attachable handle -- separate the thing the App holds from the thing the message loop awaits, which is arguably the more honest modelling since they have different lifetimes and different call sites.\n\nPrefer whichever keeps Storage synchronous; that part is not negotiable (gotcha 28 is the argument).","status":"open","priority":2,"issue_type":"task","owner":"el-github@elangley.org","created_at":"2026-09-20T02:10:13Z","created_by":"Edward Langley","updated_at":"2026-09-20T02:10:13Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"improvise-hmws","title":"Storage cannot expose an async seam through Box\u003cdyn Storage\u003e","description":"Found by improvise-9x6 (2026-09-20). Every worker host will hit this, so it is worth solving once rather than in each.\n\nOpfsFs::open / attach / refresh / commit_deletes are on the STRUCT, not on the Storage trait -- deliberately, per gotcha 28: Storage stays synchronous because createSyncAccessHandle yields a blocking handle, and only four operations are genuinely async. But App::storage is a Box\u003cdyn Storage\u003e, so HANDING THE BACKEND TO THE App PUTS ALL FOUR PERMANENTLY OUT OF REACH. The App owns the only copy and the trait object cannot name them.\n\nAND THE OBVIOUS FIX IS REFUSED BY THE COMPILER: impl Storage for Rc\u003cOpfsFs\u003e is two foreign types, so the orphan rule forbids it. improvise-9x6 worked around it with a local newtype (bindings::Shared) holding an Rc\u003cOpfsFs\u003e and forwarding the four sync methods. That works but every worker must write it again.\n\nTWO CANDIDATE FIXES:\n * Ship the shared wrapper FROM improvise-io, so the newtype exists once where OpfsFs does and the orphan rule is satisfied.\n * Or have OpfsFs hand out an attachable handle -- separate the thing the App holds from the thing the message loop awaits, which is arguably the more honest modelling since they have different lifetimes and different call sites.\n\nPrefer whichever keeps Storage synchronous; that part is not negotiable (gotcha 28 is the argument).","status":"open","priority":2,"issue_type":"task","owner":"el-github@elangley.org","created_at":"2026-09-20T02:10:13Z","created_by":"Edward Langley","updated_at":"2026-09-20T02:10:13Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"improvise-9mo9","title":"[bug] Geometry effects that renumber the grid but never reset the cursor","description":"Found by the code review of improvise-gvnb (2026-09-19). A PRE-EXISTING gap that gvnb inherited rather than introduced -- but gvnb is what makes it matter, because the epoch is now what tells every client their cursor is invalid.\n\nApp::reset_cursor bumps cursor_epoch; Sessions::adopt_cursor_epoch watches the COUNTER and broadcasts Command::ResetCursor to all. Nothing declares which effects renumber the grid -- deliberately, per gotcha 31 -- which means a geometry effect that forgets reset_cursor is INVISIBLE IN BOTH DIRECTIONS: the host own cursor stays stale AND no client is told.\n\nToday only TransposeAxes and CycleAxis call it. These also renumber rows or columns and do not:\n SetAxis, HideItem, ShowItem, ToggleGroup, TogglePruneEmpty, AddCategory, RemoveCategory\n (plus AddItem/RemoveItem, which are CellData rather than LayoutGeometry)\n\nSetAxis IS THE SHARPEST CASE: CycleAxis is literally SetAxis to Axis::next() and resets; SetAxis does not.\n\nWHY THE REVIEW DID NOT JUST PATCH IT: SetAxis is used by DrillIntoCell (cmd/grid.rs:365-471) to build the _Drill view, and resetting there could discard the user position in the view they drilled FROM, depending on effect order. That is a decision about effect ordering and intended UX, not a patch.\n\nKEEP THE NEGATIVE ARM. a_model_command_that_does_not_reshape_the_grid_leaves_cursors_alone uses SetNumberFormat -- a LayoutGeometry effect that must NOT reset. Without it, \"reset on every model command\" would pass the positive test. Whatever you do here, do not lose it.","status":"open","priority":2,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-20T01:05:15Z","created_by":"Edward Langley","updated_at":"2026-09-20T01:05:15Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"improvise-9mo9","title":"[bug] Geometry effects that renumber the grid but never reset the cursor","description":"Found by the code review of improvise-gvnb (2026-09-19). A PRE-EXISTING gap that gvnb inherited rather than introduced -- but gvnb is what makes it matter, because the epoch is now what tells every client their cursor is invalid.\n\nApp::reset_cursor bumps cursor_epoch; Sessions::adopt_cursor_epoch watches the COUNTER and broadcasts Command::ResetCursor to all. Nothing declares which effects renumber the grid -- deliberately, per gotcha 31 -- which means a geometry effect that forgets reset_cursor is INVISIBLE IN BOTH DIRECTIONS: the host own cursor stays stale AND no client is told.\n\nToday only TransposeAxes and CycleAxis call it. These also renumber rows or columns and do not:\n SetAxis, HideItem, ShowItem, ToggleGroup, TogglePruneEmpty, AddCategory, RemoveCategory\n (plus AddItem/RemoveItem, which are CellData rather than LayoutGeometry)\n\nSetAxis IS THE SHARPEST CASE: CycleAxis is literally SetAxis to Axis::next() and resets; SetAxis does not.\n\nWHY THE REVIEW DID NOT JUST PATCH IT: SetAxis is used by DrillIntoCell (cmd/grid.rs:365-471) to build the _Drill view, and resetting there could discard the user position in the view they drilled FROM, depending on effect order. That is a decision about effect ordering and intended UX, not a patch.\n\nKEEP THE NEGATIVE ARM. a_model_command_that_does_not_reshape_the_grid_leaves_cursors_alone uses SetNumberFormat -- a LayoutGeometry effect that must NOT reset. Without it, \"reset on every model command\" would pass the positive test. Whatever you do here, do not lose it.","status":"open","priority":2,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-20T01:05:15Z","created_by":"Edward Langley","updated_at":"2026-09-20T01:05:15Z","dependency_count":0,"dependent_count":0,"comment_count":0}
@@ -236,6 +237,7 @@
{"_type":"issue","id":"improvise-l36","title":"3.2 Publish to crates.io","description":"After cargo publish --dry-run is clean and user confirms, run cargo publish. Verify crate appears at crates.io/crates/improvise and cargo install improvise works.","status":"open","priority":3,"issue_type":"task","owner":"el-github@elangley.org","created_at":"2026-04-09T04:07:58Z","created_by":"Edward Langley","updated_at":"2026-09-19T20:28:48Z","dependencies":[{"issue_id":"improvise-l36","depends_on_id":"improvise-11a","type":"blocks","created_at":"2026-04-08T21:09:29Z","created_by":"Edward Langley","metadata":"{}"},{"issue_id":"improvise-l36","depends_on_id":"improvise-2fr","type":"blocks","created_at":"2026-04-08T21:09:28Z","created_by":"Edward Langley","metadata":"{}"}],"dependency_count":2,"dependent_count":2,"comment_count":0} {"_type":"issue","id":"improvise-l36","title":"3.2 Publish to crates.io","description":"After cargo publish --dry-run is clean and user confirms, run cargo publish. Verify crate appears at crates.io/crates/improvise and cargo install improvise works.","status":"open","priority":3,"issue_type":"task","owner":"el-github@elangley.org","created_at":"2026-04-09T04:07:58Z","created_by":"Edward Langley","updated_at":"2026-09-19T20:28:48Z","dependencies":[{"issue_id":"improvise-l36","depends_on_id":"improvise-11a","type":"blocks","created_at":"2026-04-08T21:09:29Z","created_by":"Edward Langley","metadata":"{}"},{"issue_id":"improvise-l36","depends_on_id":"improvise-2fr","type":"blocks","created_at":"2026-04-08T21:09:28Z","created_by":"Edward Langley","metadata":"{}"}],"dependency_count":2,"dependent_count":2,"comment_count":0}
{"_type":"issue","id":"improvise-11a","title":"3.1 Configure cargo dist","description":"Run cargo dist init targeting x86_64-unknown-linux-gnu, aarch64-apple-darwin, x86_64-apple-darwin. Skip Windows and musl. Commit generated .github/workflows/release.yml and Cargo.toml additions. Test with v0.1.0-rc1 tag, verify builds, delete rc tag.","status":"closed","priority":3,"issue_type":"task","owner":"el-github@elangley.org","created_at":"2026-04-09T04:07:55Z","created_by":"Edward Langley","updated_at":"2026-07-30T23:06:31Z","closed_at":"2026-04-11T07:49:57Z","close_reason":"Closed","dependencies":[{"issue_id":"improvise-11a","depends_on_id":"improvise-abz","type":"blocks","created_at":"2026-04-08T21:09:28Z","created_by":"Edward Langley","metadata":"{}"}],"dependency_count":1,"dependent_count":3,"comment_count":0} {"_type":"issue","id":"improvise-11a","title":"3.1 Configure cargo dist","description":"Run cargo dist init targeting x86_64-unknown-linux-gnu, aarch64-apple-darwin, x86_64-apple-darwin. Skip Windows and musl. Commit generated .github/workflows/release.yml and Cargo.toml additions. Test with v0.1.0-rc1 tag, verify builds, delete rc tag.","status":"closed","priority":3,"issue_type":"task","owner":"el-github@elangley.org","created_at":"2026-04-09T04:07:55Z","created_by":"Edward Langley","updated_at":"2026-07-30T23:06:31Z","closed_at":"2026-04-11T07:49:57Z","close_reason":"Closed","dependencies":[{"issue_id":"improvise-11a","depends_on_id":"improvise-abz","type":"blocks","created_at":"2026-04-08T21:09:28Z","created_by":"Edward Langley","metadata":"{}"}],"dependency_count":1,"dependent_count":3,"comment_count":0}
{"_type":"issue","id":"improvise-0s6","title":"Phase 3: Distribution","description":"Configure cargo dist, publish to crates.io, tag v0.1.0 release. Produces prebuilt binaries for Linux x86_64 and macOS (Intel + Apple Silicon).","status":"open","priority":3,"issue_type":"feature","owner":"el-github@elangley.org","created_at":"2026-04-09T04:05:51Z","created_by":"Edward Langley","updated_at":"2026-09-19T20:28:48Z","dependencies":[{"issue_id":"improvise-0s6","depends_on_id":"improvise-11a","type":"blocks","created_at":"2026-04-08T23:37:39Z","created_by":"Edward Langley","metadata":"{}"},{"issue_id":"improvise-0s6","depends_on_id":"improvise-3tj","type":"blocks","created_at":"2026-04-08T23:37:41Z","created_by":"Edward Langley","metadata":"{}"},{"issue_id":"improvise-0s6","depends_on_id":"improvise-l36","type":"blocks","created_at":"2026-04-08T23:37:40Z","created_by":"Edward Langley","metadata":"{}"}],"dependency_count":3,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"improvise-0s6","title":"Phase 3: Distribution","description":"Configure cargo dist, publish to crates.io, tag v0.1.0 release. Produces prebuilt binaries for Linux x86_64 and macOS (Intel + Apple Silicon).","status":"open","priority":3,"issue_type":"feature","owner":"el-github@elangley.org","created_at":"2026-04-09T04:05:51Z","created_by":"Edward Langley","updated_at":"2026-09-19T20:28:48Z","dependencies":[{"issue_id":"improvise-0s6","depends_on_id":"improvise-11a","type":"blocks","created_at":"2026-04-08T23:37:39Z","created_by":"Edward Langley","metadata":"{}"},{"issue_id":"improvise-0s6","depends_on_id":"improvise-3tj","type":"blocks","created_at":"2026-04-08T23:37:41Z","created_by":"Edward Langley","metadata":"{}"},{"issue_id":"improvise-0s6","depends_on_id":"improvise-l36","type":"blocks","created_at":"2026-04-08T23:37:40Z","created_by":"Edward Langley","metadata":"{}"}],"dependency_count":3,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"improvise-taxx","title":"[bug] The socket close listener overwrites a precise error with disconnected","description":"Found during the PR #13 review, reported and not fixed -- same family as the watchdog bug Copilot found, different question.\n\nweb/app.js socket close listener writes \"disconnected\" UNCONDITIONALLY, so a connection that failed loudly (the error listener already wrote \"cannot reach ... -- is improvise-ws-server running?\") is immediately followed by a close event that replaces it with the vaguer message. The user sees the least informative of the two things that happened.\n\nTHE WATCHDOG BUG WAS THE SAME SHAPE AND WAS FIXED: a timer fired 8s later and overwrote whatever a loud failure had already written; setShellFailure now clears it. This one was left because the question is genuinely different -- what should the page say when a connection BOTH failed AND went away? -- and because suppressing close unconditionally could hide a GENUINE MID-SESSION DISCONNECT, which is the one thing that listener is really for.\n\nLIKELY ANSWER: distinguish \"never connected\" from \"was connected and dropped\", which the socket readyState at close time can tell you. Not \"skip close if error fired\".\n\nUntestable here (no browser, no headless DOM), which is the standing caveat on every web/*.js finding -- see the browser harness ticket.","status":"open","priority":4,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-20T03:35:28Z","created_by":"Edward Langley","updated_at":"2026-09-20T03:35:28Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"improvise-7v62","title":"Checked does not bind the App it was checked against","description":"Residual hole in improvise-ondj, found and documented by the agent that closed it. Not reachable today; filed so it is a decision rather than a discovery.\n\nChecked\u003ca\u003e is a private-field newtype over a \u0026Command whose only constructor, Checked::against(\u0026App, \u0026Command), runs the basis check. But the resulting Checked carries no evidence of WHICH App it was checked against, so a multi-document host could in principle check against one App and apply to another -- and the versions would be meaningless, since document_generation, layout_version and record_versions all start at zero in a fresh App and are per document (ADR 001 scopes them that way deliberately).\n\nWHY IT IS NOT REACHABLE TODAY: a Sessions is bound to one document (connect/apply/project_all all take the App as a parameter), and Registry hands out one (App, Sessions) pair per DocumentId. It is documented on the type.\n\nTHE OPTIONS, in increasing cost: leave it documented (defensible -- this is a coordination check, not a security boundary, and the misuse requires a host to actively mix two documents); give Checked a lifetime tied to the \u0026App so the borrow checker forbids the mix; or carry a DocumentId. The middle one is probably free and worth trying before accepting the first.\n\nWON T-FIX IS A LEGITIMATE OUTCOME -- but say so deliberately, because improvise-j31x made this a multi-document server on purpose and ADR 001 notes that a document is never evicted precisely so a reconnecting client cannot have a stale basis accepted as current. The invariant is load-bearing.","status":"open","priority":4,"issue_type":"task","owner":"el-github@elangley.org","created_at":"2026-09-20T01:54:31Z","created_by":"Edward Langley","updated_at":"2026-09-20T01:54:31Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"improvise-7v62","title":"Checked does not bind the App it was checked against","description":"Residual hole in improvise-ondj, found and documented by the agent that closed it. Not reachable today; filed so it is a decision rather than a discovery.\n\nChecked\u003ca\u003e is a private-field newtype over a \u0026Command whose only constructor, Checked::against(\u0026App, \u0026Command), runs the basis check. But the resulting Checked carries no evidence of WHICH App it was checked against, so a multi-document host could in principle check against one App and apply to another -- and the versions would be meaningless, since document_generation, layout_version and record_versions all start at zero in a fresh App and are per document (ADR 001 scopes them that way deliberately).\n\nWHY IT IS NOT REACHABLE TODAY: a Sessions is bound to one document (connect/apply/project_all all take the App as a parameter), and Registry hands out one (App, Sessions) pair per DocumentId. It is documented on the type.\n\nTHE OPTIONS, in increasing cost: leave it documented (defensible -- this is a coordination check, not a security boundary, and the misuse requires a host to actively mix two documents); give Checked a lifetime tied to the \u0026App so the borrow checker forbids the mix; or carry a DocumentId. The middle one is probably free and worth trying before accepting the first.\n\nWON T-FIX IS A LEGITIMATE OUTCOME -- but say so deliberately, because improvise-j31x made this a multi-document server on purpose and ADR 001 notes that a document is never evicted precisely so a reconnecting client cannot have a stale basis accepted as current. The invariant is load-bearing.","status":"open","priority":4,"issue_type":"task","owner":"el-github@elangley.org","created_at":"2026-09-20T01:54:31Z","created_by":"Edward Langley","updated_at":"2026-09-20T01:54:31Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"improvise-q0vb","title":"Pad the import wizard's hint line to the panel width","description":"Pre-existing, found by Copilot on PR #12 and deliberately NOT fixed there -- see the pushback at https://github.com/fiddlerwoaroof/improvise/pull/12#discussion_r4055516143\n\nFour buf.set_string(x, hint_y, ...) sites in src/ui/import_wizard_ui.rs write the hint without padding to the panel width, so a SHORTER message leaves the tail of the longer previous string visible.\n\nTHE REPRODUCTION IS ALREADY IN THE SUITE, as an EXPECTATION: the DefineFormulas characterization test asserts \"|something went wrongcel\" -- the \"cel\" being the tail of \"Esc: cancel\". The artifact is currently pinned as correct output.\n\nWHY IT WAS NOT FIXED IN THE MIGRATION PR: those exact-frame characterization tests were that PR's ONLY evidence that the widget migration changed nothing (doctrine(characterization_survives_the_move)). Rewriting them in the same change would have destroyed the evidence for the change. The sibling finding in the same file (the non-ASCII truncate panic) WAS applied, precisely because it is byte-identical on ASCII and therefore costs no evidence. The two findings differ in what they cost, not in whether they are right.\n\nACCEPTANCE: the tail is gone AND the affected characterization expectations are updated IN THE SAME COMMIT, so the frame change is the point of the commit rather than a side effect of one.","status":"open","priority":4,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-20T01:26:46Z","created_by":"Edward Langley","updated_at":"2026-09-20T01:26:46Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"improvise-q0vb","title":"Pad the import wizard's hint line to the panel width","description":"Pre-existing, found by Copilot on PR #12 and deliberately NOT fixed there -- see the pushback at https://github.com/fiddlerwoaroof/improvise/pull/12#discussion_r4055516143\n\nFour buf.set_string(x, hint_y, ...) sites in src/ui/import_wizard_ui.rs write the hint without padding to the panel width, so a SHORTER message leaves the tail of the longer previous string visible.\n\nTHE REPRODUCTION IS ALREADY IN THE SUITE, as an EXPECTATION: the DefineFormulas characterization test asserts \"|something went wrongcel\" -- the \"cel\" being the tail of \"Esc: cancel\". The artifact is currently pinned as correct output.\n\nWHY IT WAS NOT FIXED IN THE MIGRATION PR: those exact-frame characterization tests were that PR's ONLY evidence that the widget migration changed nothing (doctrine(characterization_survives_the_move)). Rewriting them in the same change would have destroyed the evidence for the change. The sibling finding in the same file (the non-ASCII truncate panic) WAS applied, precisely because it is byte-identical on ASCII and therefore costs no evidence. The two findings differ in what they cost, not in whether they are right.\n\nACCEPTANCE: the tail is gone AND the affected characterization expectations are updated IN THE SAME COMMIT, so the frame change is the point of the commit rather than a side effect of one.","status":"open","priority":4,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-20T01:26:46Z","created_by":"Edward Langley","updated_at":"2026-09-20T01:26:46Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"improvise-hkt6","title":"Three small totality gaps found in review","description":"Three PLAUSIBLE/minor findings from the 2026-09-19 wave review, each unreachable today and each one refactor away from not being. Grouped because none is worth a ticket alone.\n\n1. DataStore::matching_positions panics on an empty partial -- it indexes sets[0]. Private, and both callers guard with `if partial.is_empty()`, so it is unreachable. But THE GUARD NOW LIVES ONE LEVEL AWAY FROM THE INDEXING, which is how this class of bug returns. A sets.first()?-shaped total function costs nothing.\n\n2. invalidate_relabelled does not blank `totals` the way Invalidated does. By gotcha 33(b) own argument -- preserve-by-position is only right where the label did not change -- a RENAMED column should drop its total. Unreachable in practice: grid_updates diffs mirror.totals != desired.totals and emits Totals after ColLabels in the same batch, so a changed total is always sent. Worth fixing anyway, because 33(b) exists PRECISELY to stop `apply` trusting producers.\n\n3. improvise-gp mark_features indexes used[*feat] without a bound, where expr_text uses .get().expect(...) for the same precondition. Same failure, less legible.","status":"open","priority":4,"issue_type":"task","owner":"el-github@elangley.org","created_at":"2026-09-20T01:05:16Z","created_by":"Edward Langley","updated_at":"2026-09-20T01:05:16Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"improvise-hkt6","title":"Three small totality gaps found in review","description":"Three PLAUSIBLE/minor findings from the 2026-09-19 wave review, each unreachable today and each one refactor away from not being. Grouped because none is worth a ticket alone.\n\n1. DataStore::matching_positions panics on an empty partial -- it indexes sets[0]. Private, and both callers guard with `if partial.is_empty()`, so it is unreachable. But THE GUARD NOW LIVES ONE LEVEL AWAY FROM THE INDEXING, which is how this class of bug returns. A sets.first()?-shaped total function costs nothing.\n\n2. invalidate_relabelled does not blank `totals` the way Invalidated does. By gotcha 33(b) own argument -- preserve-by-position is only right where the label did not change -- a RENAMED column should drop its total. Unreachable in practice: grid_updates diffs mirror.totals != desired.totals and emits Totals after ColLabels in the same batch, so a changed total is always sent. Worth fixing anyway, because 33(b) exists PRECISELY to stop `apply` trusting producers.\n\n3. improvise-gp mark_features indexes used[*feat] without a bound, where expr_text uses .get().expect(...) for the same precondition. Same failure, less legible.","status":"open","priority":4,"issue_type":"task","owner":"el-github@elangley.org","created_at":"2026-09-20T01:05:16Z","created_by":"Edward Langley","updated_at":"2026-09-20T01:05:16Z","dependency_count":0,"dependent_count":0,"comment_count":0}
+7 -3
View File
@@ -207,9 +207,13 @@ document open in the TUI is shared — start the websocket server and pass
cargo run -p improvise-ws-server -- examples/demo.improv --port 8787 cargo run -p improvise-ws-server -- examples/demo.improv --port 8787
``` ```
then reload with `?ws=ws://127.0.0.1:8787`. From an https origin a `ws://` then reload with `?ws=ws://127.0.0.1:8787`.
address is blocked as mixed content, so that pairing is most easily done with
the local page above. **That form works only from a page served over `http://localhost`.** From the
deployed https page the address must be `wss://`, because a `ws://` one is
blocked as mixed content — and `improvise-ws-server` speaks plain `ws://`
only, so reaching it from https means terminating TLS in front of it (a
reverse proxy or a tunnel). Native `wss://` is not implemented.
Caveat worth stating plainly: **no line of the browser JavaScript has ever Caveat worth stating plainly: **no line of the browser JavaScript has ever
run.** There is no browser and no headless DOM in the development environment, run.** There is no browser and no headless DOM in the development environment,
+9 -3
View File
@@ -29,7 +29,8 @@
?doc= and ?seed= name the document and the file it is first seeded from. ?doc= and ?seed= name the document and the file it is first seeded from.
To talk to a separate `improvise-ws-server` process instead — which is To talk to a separate `improvise-ws-server` process instead — which is
how a document open in the TUI is shared — pass ?ws=ws://host:port. how a document open in the TUI is shared — pass ?ws=wss://host:port
(or ws:// when this page is served over http://localhost).
--> -->
<header> <header>
<h1>improvise</h1> <h1>improvise</h1>
@@ -59,8 +60,13 @@
<code>?seed=url</code>. To open a document hosted by a separate <code>?seed=url</code>. To open a document hosted by a separate
process instead, run process instead, run
<code>improvise-ws-server model.improv --port 8787</code> and reload <code>improvise-ws-server model.improv --port 8787</code> and reload
with <code>?ws=ws://127.0.0.1:8787</code> — note that from an https with <code>?ws=wss://host:port</code>. From this https page the
origin a <code>ws://</code> address is blocked as mixed content. address must be <code>wss://</code> — a <code>ws://</code> one is
blocked as mixed content — and
<code>improvise-ws-server</code> speaks plain <code>ws://</code> only,
so it needs TLS terminated in front of it. Served over
<code>http://localhost</code> the same page takes
<code>?ws=ws://127.0.0.1:8787</code> directly.
</p> </p>
<main> <main>