diff --git a/context/code/clients.md b/context/code/clients.md index edd147d8..ff0eb935 100644 --- a/context/code/clients.md +++ b/context/code/clients.md @@ -1411,6 +1411,12 @@ than a check; or this client has not been told the document's basis and so has nothing truthful to say about what it composed against (`BasisCache::claim` withholds rather than invents). +A **fifth** `None` is not a failure: the text is the seed the editor captured +when it opened (improvise-ixxx). The page passes that seed as `typed_cell`'s +fourth argument, and `resolve::untouched`, the one home beside `typed()`, +decides. The argument is in `protocol.md` §3, "An untouched editor writes +nothing". + **Nothing is folded there.** The new value comes back as a `GridUpdate::Cells` from the host, which is the side that formats it; guessing it locally is how the two ends drift apart. @@ -1565,7 +1571,7 @@ whether a typed string is a number. Five calls are the whole harness: | `client.set_viewport(rows, cols)` | the handshake frame, and every resize; also records the window so keys resolve here | | `client.on_frame(json)` | fold one frame, in arrival order | | `client.render_model()` | everything to paint, already decided | -| `client.typed_cell(r, c, s)` | the frame for "the user typed s here" | +| `client.typed_cell(r, c, s, seed)` | the frame for "the user typed s here", or none if `s` is still the `seed` the editor opened on | | `client.on_key_event(k,c,a,s,m)` | the frame for a keypress, folded locally first where this client may answer it | **The two rules this file must not break are ordering and framing**: frames are diff --git a/context/test-evidence/clients.md b/context/test-evidence/clients.md index ab543305..950132a3 100644 --- a/context/test-evidence/clients.md +++ b/context/test-evidence/clients.md @@ -4332,6 +4332,15 @@ with no `SetBuffer`, then presses the keys: Enter must compose `an_edit_buffer_the_user_has_emptied_is_not_refilled_from_the_cell` fails, as it did against the same rival in `render.rs`. +**Amended by improvise-ixxx.** Its first `Enter` is untouched now, so it +composes no write. The assertion that it "must not clear" is therefore +`committed == None`, where `committed` is the first `Command::Model` of any +kind. It still fails against the pre-ktlk tree: +that tree's buffer is `""`, and `settle_editor` never ran, so no seed was +captured and the empty buffer composes `ClearCell`. The typed-`5` half is +unchanged. `settle_editor` now captures `OpenedEditor` beside the buffer, +because the editor opens at that moment. + ## improvise-dsxq — the browser's `yy` copies the value, not a string PR #161 (improvise-lut7) made the browser's `yank` arm compose @@ -4491,3 +4500,77 @@ normal edges only, because a dev edge would unify the feature back in which fails naming that crate while the two tests above pass. Then removed from `improvise-ws-server` alone, which fails naming it. The native path runs without `--target`, so the two arms are different invocations. + +### `an_untouched_browser_commit_writes_nothing_and_still_advances` + +**improvise-ixxx**, browser keyboard. The fixture is a 3×1 grid with the cursor +on row 1. For each seed in `"007"`, `"1e3"`, `"inf"`, `"ERR:boom"` and +`"1.23456789"`, the test folds `i`, checks the buffer is that seed, and resolves +`Enter`. The composed commands must contain no `Command::Model`. They must also +contain `SetSelected { row: 2, col: 0 }`, the re-entry +`ChangeMode(editing)`, and `SetOpenedEditor` capturing `"800"`, the cell it +landed on. + +Failed before the fix: `"007"` composed `SetCell(Number(7.0))`. **Rival:** +`resolve::commit` returning `[ChangeMode(Normal)]` when untouched, which skips +the advance along with the write. This test fails alone. + +### `an_untouched_browser_edit_does_not_overwrite_a_write_made_while_it_was_open` + +The captured-seed constraint, in the browser. The test opens the editor on +`"007"`, then folds a `GridUpdate::Cells` that moves the cell to `"5"`. That is +the projection another client's write produces. `Enter` with nothing typed must +compose no `Command::Model`. + +**Rival:** `written` comparing against `edit_seed(state)`, which re-reads the +cache at commit time, instead of `ViewState::opened_editor`. This test fails +alone, composing `SetCell(Number(7.0))`. The basis claim is taken when the +commit is composed, so it would not have refused that write. + +### `a_touched_browser_commit_still_writes` + +Scope guard. The reviewer's fourth constraint says classification is out of +scope. `Backspace` over the seed `"7"` and then typing `007` must still compose +`SetCell(Number(7.0))`. It passes before and after. It is here so that a fix +which suppressed number-shaped text in general, rather than untouched text, +fails. + +### `an_untouched_browser_edit_of_a_misread_value_writes_nothing` + +The same shape as `an_untouched_browser_edit_stores_the_identical_value`, with +the values `typed()` misreads: `Text("007")`, `Text("1e3")`, `Text("inf")` and a +raw `Error("boom")`. It runs through the resolver and into a real host. Both +tests share `untouched_browser_edit`, which now also asserts that the buffer +after `i` is `value.edit_text()`. That assertion keeps the umi4 test +discriminating once untouched commits write nothing (see +`command.md`, `an_untouched_pivot_edit_of_a_misread_value_writes_nothing`). +With `project.rs` seeding from `to_string()`, both umi4 browser tests fail. +**Rival:** `untouched` always `false`. This test fails. + +### `a_mouse_commit_of_the_untouched_seed_composes_nothing` + +**improvise-ixxx**, browser mouse. `web/app.js`'s `edit()` holds `original`, +captured when the editor opened, and used to call `typed_cell` without it. So +an untouched click-edit re-sent the seed through `typed()`. Now +`typed_cell_frame` takes the `OpenedEditor` and returns `None` for +`"007"`, `"ERR:boom"` and `"1.23456789"` given as their own seeds. The control +is `"007"` against a seed of `"7"`, which must compose a write, so a +`typed_cell_frame` that never composes cannot pass. + +### `a_mouse_commit_is_judged_against_the_seed_it_opened_with` + +The captured-seed constraint, on the mouse path. The editor opened on `"1200"`. +A projected write then moves the cell to `"5"`, and a guard asserts the cache +really holds `"5"`. Committing `"1200"` must compose nothing. **Rival:** +`typed_cell_frame` ignoring the passed seed and comparing against the cache's +`edit_text`. This test and `a_mouse_commit_of_the_untouched_seed_composes_nothing` +both fail. + +The page half is `harness/tests/contract.spec.js`, "an untouched mouse editor +sends no write, and a touched one does". It wraps `Worker.prototype.postMessage`, +opens the editor on cell 4, and presses `Enter`. No `SetCell` may be posted. +Then it types `1` and presses `Enter`, and exactly one must be. **Rivals, run +against the served `app.js`:** passing `input.value` as the seed, and passing +no seed at all. Both fail the control with 0 writes. The second fails because +wasm-bindgen throws on the missing `String`, and the page swallows the throw +inside `close`. diff --git a/context/test-evidence/command.md b/context/test-evidence/command.md index cf418736..3f9d7133 100644 --- a/context/test-evidence/command.md +++ b/context/test-evidence/command.md @@ -5138,3 +5138,66 @@ exactly that, because West had no record to drill into. It then asserts that the first message does not mention drilling. At `0ed2e41` it failed with the old text. The rival is restoring that text. Real drill paste, which means staging a typed `CellValue`, is still open as improvise-48jd. + +### `an_untouched_pivot_edit_of_a_misread_value_writes_nothing` +### `an_untouched_records_edit_of_a_misread_value_writes_nothing` + +**improvise-ixxx.** After umi4 the seed was exact, but the commit still ran it +through `typed()`, which reads text and not the cell the text came from. So +`i` then `Enter` with nothing typed stored `Number(7.0)` over `Text("007")`, +and `Text("ERR:boom")` over a raw `Error("boom")`. The values are the ones +where `typed(edit_text(v)) != v`: `"007"`, `"1e3"`, `"inf"`, `"2024"` and a raw +`Error`. They use `no_op_commit`, the umi4 tests' own path: the real key path, +on (West, Revenue), which is row 1 and never the first cell. + +Both failed before the fix. With the list as written they failed on +`Text("007")` stored as `Number(7.0)`. With `Error("boom")` moved to the front +they failed on it, stored as `Text("ERR:boom")`, so the error case is red on +its own and not only behind the text one. **Rival:** `untouched` answering +`false` always, i.e. the fix removed. Both fail again. + +`no_op_commit` now also asserts that the buffer after `i` is +`value.edit_text()`. **That assertion is what keeps the umi4 tests above +alive.** With ixxx fixed, an untouched commit writes nothing, so a seed +regressed to the rounded `Display` is never written either, and +`an_untouched_{pivot,records}_edit_stores_the_identical_value` pass with umi4 +reverted. That was run: with the seed assertion removed and +`display_value_in` seeding from `to_string()`, all six untouched tests pass. +With the assertion in, both umi4 tests fail. + +### `an_untouched_edit_does_not_overwrite_a_write_made_while_it_was_open` + +The reviewer's first constraint on improvise-ixxx: the seed is the one +**captured when the editor opened**. The test opens the editor on +`Text("007")`, then applies a `SetCell(Number(5.0))` directly, standing in for +another client's write that lands while the editor is open. Then it presses +`Enter` with nothing typed. The cell must still be `5`. + +Failed before the fix: the commit wrote `Number(7.0)`. **Rival:** +`commit_at_cursor` taking the seed from `ctx.display_value`, which is +recomputed at commit time, instead of `ctx.opened_editor`. This test fails +alone, with `Number(7.0)`, and every other untouched test passes. So it is the +only guard on the choice of seed, and a fixture where the cell does not move +cannot tell the two seeds apart. + +### `an_untouched_commit_still_advances_and_reopens_the_editor` + +The reviewer's second constraint: only the **write** is skipped. The fixture +puts `Text("007")` at (West, Cost), row 1 and column 0. `Enter` there advances +to (0, 1) rather than aborting at the corner, which is where (West, Revenue) +sits in pivot mode. The test asserts four things: the cell is unchanged, the +cursor moved to (0, 1), the mode is editing again, and the reopened editor +captured `"2"`, the cell it landed on. + +**Rival:** `CommitAndAdvance::execute` returning straight after the mode change +when untouched, which skips the advance along with the write. This test fails, +and so do `tab_on_an_empty_new_measure_column_still_starts_a_record_row` and +`ui::app::tests::tab_on_bottom_right_of_records_inserts_below`. Those two are +the records Tab contract: the empty `+` column's seed is `""`, so an empty Tab +there is now an untouched commit too. + +The buffer after `Enter` is **not** asserted. The keymap welds `clear-buffer` +after every commit key, so it empties the buffer `EnterEditAtCursor` has just +seeded, and the reopened editor opens blank while its captured seed is `"2"`. +That is existing behaviour (`enter_commits_the_cell_advances_and_re_enters_editing` +in `clients.md`). It is reported on improvise-ixxx rather than fixed here.