Evidence, docs and the bundle surface for the mouse editor's seed and first edit (improvise-ugj4, improvise-jibp)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
This commit is contained in:
+11
-6
@@ -1573,8 +1573,10 @@ Four smaller decisions that would be re-derived wrongly:
|
||||
Since improvise-n6t8 it shares `#bottom-dock` with the toast, which stacks
|
||||
**above** it, so the prompt is neither covered nor displaced.
|
||||
|
||||
`RenderCell::edit_text` is the stored value, not the formatted one: seeding an
|
||||
editor from `text` and committing turns `1200` into the string `"1,200"`. The
|
||||
An editor is seeded from `edit_seed(target)`, the stored value the target
|
||||
captured, never from `text`: seeding from the formatted text and committing
|
||||
turns `1200` into the string `"1,200"`. `RenderCell` carries no `edit_text`
|
||||
since improvise-ugj4, because a paint can be stale while an editor is open. The
|
||||
`numeric`/`error` flags are the server's answers, echoed — the client must not
|
||||
re-derive them from the text, which is how the two ends drift.
|
||||
|
||||
@@ -1617,7 +1619,7 @@ The browser half of improvise-cr3, kept as thin as it can be made, now talking
|
||||
to either of two peers (improvise-djm). **The boundary is JSON strings in and
|
||||
out, so this file holds no protocol knowledge at all**: it never inspects a
|
||||
`Command`, never builds a `CellId`, never decides whether a cell is editable or
|
||||
whether a typed string is a number. Five calls are the whole harness:
|
||||
whether a typed string is a number. These calls are the whole harness:
|
||||
|
||||
| call | what it is |
|
||||
|---|---|
|
||||
@@ -1625,6 +1627,8 @@ whether a typed string is a number. Five calls are the whole harness:
|
||||
| `client.on_frame(json)` | fold one frame, in arrival order |
|
||||
| `client.render_model()` | everything to paint, already decided |
|
||||
| `client.edit_target(r, c)` | what an editor opened here edits (the record, the version seen, and the seed it opened on): an opaque string the page holds, or `null` |
|
||||
| `client.edit_seed(target)` | the text the target's editor opens on: the seed it captured, never the paint (improvise-ugj4) |
|
||||
| `client.edited_target(target)` | the same target marked edited, so retyping the seed still writes (improvise-jibp) |
|
||||
| `client.typed_cell(target, s)` | the frame for "the user typed s into that cell", by identity (improvise-2wu3), or none if `s` is still the target's seed (improvise-ixxx) |
|
||||
| `client.on_key_event(k,c,a,s,m)` | the frame for a keypress, folded locally first where this client may answer it |
|
||||
|
||||
@@ -1832,9 +1836,10 @@ Neither arm leaves one armed.
|
||||
- **`editable` is the server's answer relayed through Rust**: a stored cell. An
|
||||
aggregate is a drill target and drill is not on the wire yet, so it is simply
|
||||
not offered.
|
||||
- The inline editor seeds from `RenderCell::edit_text` — the STORED value, not
|
||||
the drawn one: `td.textContent` has been through the number format, so a stored
|
||||
1200 reads `"1,200"` and committing that stores the *string* `"1,200"`. Enter
|
||||
- The inline editor seeds from `client.edit_seed(target)` — the STORED value the
|
||||
target captured, not the drawn one: `td.textContent` has been through the number
|
||||
format, so a stored 1200 reads `"1,200"` and committing that stores the *string*
|
||||
`"1,200"`. Enter
|
||||
removes the focused `<input>`, which fires `blur`, which calls `close` again:
|
||||
harmless, but only by accident.
|
||||
- **The title bar is three fields and one joining.** `doc_name`, `doc_dirty` and
|
||||
|
||||
@@ -699,7 +699,9 @@ for that, beside `typed()`. Every commit path reaches it:
|
||||
- **Browser mouse.** The seed is a field of #188's `EditTarget` (`opened`),
|
||||
captured by `edit_target_at` from the cache's `edit_text` beside the record
|
||||
and its version. `typed_target_frame` starts with the `untouched` check, and
|
||||
the page passes no seed (`typed_cell(target, text)`).
|
||||
the page passes no seed (`typed_cell(target, text)`). The `<input>` shows
|
||||
`edit_seed(target)`, so what the page shows and what it compares against are
|
||||
one capture (improvise-ugj4).
|
||||
|
||||
**The seed is captured at open, never recomputed at commit.** Where the editor
|
||||
opened is where `OpenedEditor` is written. For the TUI that is `EditOrDrill`
|
||||
@@ -742,14 +744,16 @@ deliberately do **not** mark it:
|
||||
|
||||
The comparison stays exact: whitespace is content, and nothing trims.
|
||||
|
||||
**The mouse editor tracks no `touched`.** Its edits happen in a DOM `<input>`
|
||||
that Rust never sees, so on that path `untouched` compares content alone, and
|
||||
the 2054 case survives there (improvise-jibp). The fix belongs with
|
||||
`EditTarget`, for example an `input` listener that reports the first edit. A
|
||||
second gap sits on the same path. The `<input>` is still seeded from the
|
||||
**painted** `editText`, while `EditTarget` captures the **cache**. So a paint
|
||||
that went stale while another editor was open shows old text against a newer
|
||||
seed and version (improvise-ugj4).
|
||||
**The mouse editor reports its first edit.** Its edits happen in a DOM
|
||||
`<input>` that Rust never sees, so the page's `input` listener, registered
|
||||
`{ once: true }`, swaps its target for `edited_target(target)`, which is
|
||||
`EditTarget::edited`: the same capture with `touched` set (improvise-jibp).
|
||||
Before that, the mouse path compared content alone, so the 2054 case survived
|
||||
there. The `<input>` used to be seeded from the **painted** `editText` while
|
||||
`EditTarget` captured the **cache**. `repaint` returns early while an editor is
|
||||
open, so a paint that went stale then showed old text against a newer seed and
|
||||
version (improvise-ugj4). The page now seeds from `edit_seed(target)`, and
|
||||
`RenderCell` no longer carries `edit_text`, so the paint cannot be asked.
|
||||
|
||||
This does **not** change classification. `007` typed fresh is still
|
||||
`Number(7)`, and `a_touched_browser_commit_still_writes` pins that.
|
||||
|
||||
@@ -4933,11 +4933,50 @@ 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.
|
||||
|
||||
**Known gap, filed as improvise-ugj4.** The `<input>` is still seeded from the
|
||||
painted `editText`, while `EditTarget` captures the cache. A paint made stale
|
||||
while another editor was open (`repaint` returns early while `editing` is set)
|
||||
therefore shows old text against a newer seed and version.
|
||||
**improvise-jibp** is the other residue: the mouse editor has no `touched` flag.
|
||||
Both gaps this entry used to list are closed: the stale-paint seed
|
||||
(improvise-ugj4) and the missing `touched` flag (improvise-jibp). See the
|
||||
entries that follow.
|
||||
|
||||
### `a_mouse_target_marked_edited_writes_its_own_seed`
|
||||
|
||||
**improvise-jibp**, the Rust half. A target on the seed `"1200"` commits
|
||||
`"1200"` and composes nothing: that is the control. `edited()` must leave the
|
||||
seed where it was, and the same text committed through the edited target must
|
||||
compose a write. **Rival:** `edited()` leaving `touched` false fails this test
|
||||
and `the_page_opens_its_editor_on_what_the_target_captured`, and nothing else.
|
||||
|
||||
### `the_page_opens_its_editor_on_what_the_target_captured`
|
||||
|
||||
**improvise-ugj4 and improvise-jibp**, through the wasm-bindgen surface the
|
||||
page calls. The cell is drawn `"1,200.50"` and stored `"1200.5"`, and
|
||||
`edit_seed` must return `"1200.5"`. A projected write then moves the cell to
|
||||
`"777"`. A target taken afterwards seeds `"777"`, and the first target still
|
||||
seeds `"1200.5"`, because a target is one capture. `typed_cell(later, "777")`
|
||||
composes nothing, and the same text through `edited_target(later)` composes a
|
||||
write. **Rival:** as above, `edited()` leaving `touched` false fails here. I
|
||||
wrote no rival for an `edit_seed` that re-reads the cache. `EditTarget` names a
|
||||
record, not a position, so that rival would need a lookup the client does not
|
||||
have.
|
||||
|
||||
### a mouse editor typed back to its own seed still writes — `harness/tests/contract.spec.js`
|
||||
|
||||
**improvise-jibp**, the page half. The editor opens on cell 4, and the test
|
||||
types the seed over the selection and presses Enter. Exactly one `SetCell` must
|
||||
be posted. Before the fix the page sent `typed_cell(target, seed)` with an
|
||||
untouched target, which composes nothing. **Rival:** delete the page's `input`
|
||||
listener. This test fails, and the rest of `contract.spec.js` passes (16).
|
||||
|
||||
### an editor opened after an unpainted write shows and commits what it captured — `harness/tests/contract.spec.js`
|
||||
|
||||
**improvise-ugj4.** The test opens an editor on cell 0, so `repaint` stops.
|
||||
While it is open, it delivers a synthetic frame that moves cell 4 to `777`. It
|
||||
builds that frame from the last `Cells` entry the page actually received for
|
||||
cell 4, changing only `text`, `edit_text` and `value`, so the record and the
|
||||
version are real. Then it opens cell 4. The `<input>` must show `777`, and
|
||||
Enter with no edit must post no `SetCell`. **Rival:** seed the `<input>` from the
|
||||
paint (`drawn.replaceAll(",", "")`). This test fails on the value, and so does
|
||||
"an untouched mouse editor sends no write, and a touched one does", because the
|
||||
drawn text is not the seed. The other 15 pass.
|
||||
|
||||
### `a_browser_editor_typed_back_to_its_seed_still_writes`
|
||||
|
||||
|
||||
@@ -313,11 +313,25 @@ mod tests {
|
||||
],
|
||||
});
|
||||
let first = client.edit_target(0, 0).unwrap().expect("a record cell");
|
||||
assert_eq!(client.edit_seed(&first).unwrap(), "1200.5", "not the drawn text");
|
||||
client.fold_frame(ServerFrame::Commands(vec![Command::Project(at("777", "777"))]));
|
||||
assert_eq!(
|
||||
client.edit_seed(&first).unwrap(),
|
||||
"1200.5",
|
||||
"not the drawn text"
|
||||
);
|
||||
client.fold_frame(ServerFrame::Commands(vec![Command::Project(at(
|
||||
"777", "777",
|
||||
))]));
|
||||
let later = client.edit_target(0, 0).unwrap().expect("a record cell");
|
||||
assert_eq!(client.edit_seed(&later).unwrap(), "777", "not the cache at open");
|
||||
assert_eq!(client.edit_seed(&first).unwrap(), "1200.5", "a target is one capture");
|
||||
assert_eq!(
|
||||
client.edit_seed(&later).unwrap(),
|
||||
"777",
|
||||
"not the cache at open"
|
||||
);
|
||||
assert_eq!(
|
||||
client.edit_seed(&first).unwrap(),
|
||||
"1200.5",
|
||||
"a target is one capture"
|
||||
);
|
||||
|
||||
assert!(client.typed_cell(&later, "777").unwrap().is_none());
|
||||
let edited = client.edited_target(&later).unwrap();
|
||||
|
||||
@@ -80,9 +80,13 @@ test("the page still reaches the client through a handful of calls", () => {
|
||||
"click_page_item",
|
||||
"click_tile",
|
||||
"click_view",
|
||||
// improvise-ugj4: the editor shows the seed its target captured, not the paint.
|
||||
"edit_seed",
|
||||
// improvise-2wu3/0j0f: what an editor edits, captured when it opens, so
|
||||
// its commit names that record at that version and not a position.
|
||||
"edit_target",
|
||||
// improvise-jibp: the first edit of the <input> marks the target touched.
|
||||
"edited_target",
|
||||
"on_frame",
|
||||
"on_key_event",
|
||||
"render_model",
|
||||
|
||||
Reference in New Issue
Block a user