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
|
Since improvise-n6t8 it shares `#bottom-dock` with the toast, which stacks
|
||||||
**above** it, so the prompt is neither covered nor displaced.
|
**above** it, so the prompt is neither covered nor displaced.
|
||||||
|
|
||||||
`RenderCell::edit_text` is the stored value, not the formatted one: seeding an
|
An editor is seeded from `edit_seed(target)`, the stored value the target
|
||||||
editor from `text` and committing turns `1200` into the string `"1,200"`. The
|
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
|
`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.
|
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
|
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
|
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
|
`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 |
|
| 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.on_frame(json)` | fold one frame, in arrival order |
|
||||||
| `client.render_model()` | everything to paint, already decided |
|
| `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_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.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 |
|
| `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
|
- **`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
|
aggregate is a drill target and drill is not on the wire yet, so it is simply
|
||||||
not offered.
|
not offered.
|
||||||
- The inline editor seeds from `RenderCell::edit_text` — the STORED value, not
|
- The inline editor seeds from `client.edit_seed(target)` — the STORED value the
|
||||||
the drawn one: `td.textContent` has been through the number format, so a stored
|
target captured, not the drawn one: `td.textContent` has been through the number
|
||||||
1200 reads `"1,200"` and committing that stores the *string* `"1,200"`. Enter
|
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:
|
removes the focused `<input>`, which fires `blur`, which calls `close` again:
|
||||||
harmless, but only by accident.
|
harmless, but only by accident.
|
||||||
- **The title bar is three fields and one joining.** `doc_name`, `doc_dirty` and
|
- **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`),
|
- **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
|
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
|
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
|
**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`
|
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 comparison stays exact: whitespace is content, and nothing trims.
|
||||||
|
|
||||||
**The mouse editor tracks no `touched`.** Its edits happen in a DOM `<input>`
|
**The mouse editor reports its first edit.** Its edits happen in a DOM
|
||||||
that Rust never sees, so on that path `untouched` compares content alone, and
|
`<input>` that Rust never sees, so the page's `input` listener, registered
|
||||||
the 2054 case survives there (improvise-jibp). The fix belongs with
|
`{ once: true }`, swaps its target for `edited_target(target)`, which is
|
||||||
`EditTarget`, for example an `input` listener that reports the first edit. A
|
`EditTarget::edited`: the same capture with `touched` set (improvise-jibp).
|
||||||
second gap sits on the same path. The `<input>` is still seeded from the
|
Before that, the mouse path compared content alone, so the 2054 case survived
|
||||||
**painted** `editText`, while `EditTarget` captures the **cache**. So a paint
|
there. The `<input>` used to be seeded from the **painted** `editText` while
|
||||||
that went stale while another editor was open shows old text against a newer
|
`EditTarget` captured the **cache**. `repaint` returns early while an editor is
|
||||||
seed and version (improvise-ugj4).
|
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
|
This does **not** change classification. `007` typed fresh is still
|
||||||
`Number(7)`, and `a_touched_browser_commit_still_writes` pins that.
|
`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.
|
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.
|
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
|
Both gaps this entry used to list are closed: the stale-paint seed
|
||||||
painted `editText`, while `EditTarget` captures the cache. A paint made stale
|
(improvise-ugj4) and the missing `touched` flag (improvise-jibp). See the
|
||||||
while another editor was open (`repaint` returns early while `editing` is set)
|
entries that follow.
|
||||||
therefore shows old text against a newer seed and version.
|
|
||||||
**improvise-jibp** is the other residue: the mouse editor has no `touched` flag.
|
### `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`
|
### `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");
|
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");
|
assert_eq!(
|
||||||
client.fold_frame(ServerFrame::Commands(vec![Command::Project(at("777", "777"))]));
|
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");
|
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!(
|
||||||
assert_eq!(client.edit_seed(&first).unwrap(), "1200.5", "a target is one capture");
|
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());
|
assert!(client.typed_cell(&later, "777").unwrap().is_none());
|
||||||
let edited = client.edited_target(&later).unwrap();
|
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_page_item",
|
||||||
"click_tile",
|
"click_tile",
|
||||||
"click_view",
|
"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
|
// improvise-2wu3/0j0f: what an editor edits, captured when it opens, so
|
||||||
// its commit names that record at that version and not a position.
|
// its commit names that record at that version and not a position.
|
||||||
"edit_target",
|
"edit_target",
|
||||||
|
// improvise-jibp: the first edit of the <input> marks the target touched.
|
||||||
|
"edited_target",
|
||||||
"on_frame",
|
"on_frame",
|
||||||
"on_key_event",
|
"on_key_event",
|
||||||
"render_model",
|
"render_model",
|
||||||
|
|||||||
Reference in New Issue
Block a user