improvise-km71: edit the formula under the cursor, in place
Enter or e in the formula panel opens FormulaEdit on the formula under the cursor, seeded with its text. The editor names the formula by a FormulaId captured when it opens, and commits through a new ReplaceFormula effect and ModelCommand. The edit keeps the formula's position, renames it in place when the target changes, and refuses out loud if the formula was removed or rewritten meanwhile, if the new target belongs to another formula, or if the text does not parse. A refusal leaves the editor open over the user's text. FormulaCache now carries each formula's id, so the browser composes the same edit, and a formula-row click opens the editor on the clicked row. 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:
@@ -1232,3 +1232,54 @@ What this does **not** fix: a text cell whose text reads as a number. `"007"`
|
||||
seeds `"007"`, and `typed()` classifies that as `Number(7.0)`. The seed is
|
||||
exact, but the classifier has no way to know the cell was text. That is
|
||||
improvise-ixxx.
|
||||
|
||||
## 18. Which formula an edit means (improvise-km71)
|
||||
|
||||
A `Formula` has no stored id. It lives in `Model::formulas: Vec<Formula>` and is
|
||||
identified, in practice and by `add_formula`'s upsert, by `(target,
|
||||
target_category)`. **`FormulaId` is that pair behind one opaque type**: its
|
||||
fields are private, `Formula::id()` is its only constructor, and nothing
|
||||
outside `improvise-formula` can read a name out of it. That is deliberate. The
|
||||
stable-identity work (improvise-sboz, ADR 005) will make a formula's identity
|
||||
something other than its names, and every signature that already says
|
||||
`FormulaId` — `Model::formula`, `Model::replace_formula`,
|
||||
`ModelCommand::ReplaceFormula`, `FormulaSlot::Existing`, `FormulaEntry` — then
|
||||
changes by changing this one struct. A name pair spread across those signatures
|
||||
would have made the swap a sweep.
|
||||
|
||||
**The index is not a candidate.** A position is what `formula_cursor` is, and
|
||||
the editor must not recompute its target from the cursor at commit
|
||||
(improvise-ixxx/2wu3's lesson): another client deleting an earlier formula
|
||||
shifts every index after it, and the commit would land on a neighbour. It would
|
||||
also put a position on the wire, which `every_model_command_field_is_position_free`
|
||||
makes a compile error.
|
||||
|
||||
**An edit is its own operation, never remove-then-add and never
|
||||
`add_formula`.** `add_formula` upserts on `(target, target_category)`: fed an
|
||||
edit that changes the target, it appends a second formula and leaves the old
|
||||
one; fed an edit onto another formula's target, it silently replaces *that*
|
||||
formula. `Model::replace_formula(id, formula)` writes into the slot `id` names,
|
||||
so the position (and with it `_Measure`'s item order, since
|
||||
`measure_item_names` appends formula targets in formula order) is kept, and it
|
||||
**refuses** when the new target belongs to a different formula.
|
||||
|
||||
**A target change is a rename of the same formula.** The id travels with the
|
||||
edit, so `Profit = …` edited to `Gross = …` is the formula that was `Profit`,
|
||||
in the same place. That is the least surprising reading of "edit this row", and
|
||||
it is what ADR 005 proposes. What a rename cannot carry under name identity is
|
||||
everything else that names the old target: other formulas' references, a
|
||||
view's hidden list, a `measure_agg` entry. Those cannot follow without rewriting
|
||||
`raw` text the parser does not print back, so the edit says so out loud:
|
||||
`ReplaceFormula` reports which formulas still mention the old name
|
||||
(`Formula::mentions`, over the expression and its `WHERE`), and their cells
|
||||
show the `ref` error rather than a silent wrong number. Under a stable id those
|
||||
references are what sboz makes follow.
|
||||
|
||||
**Concurrency is checked twice.** `FormulaSlot::Existing { id, was }` is
|
||||
captured when the editor opens: the id must still resolve, and the formula must
|
||||
still read `was`. Either failing means another client removed, renamed or
|
||||
rewrote it meanwhile, and the edit is refused with the batch stopped, so the
|
||||
editor stays open over the user's text (gotcha 21). `was` is not redundant
|
||||
with a stable id: a concurrent *replace* keeps the id and changes the text,
|
||||
which only `was` can see.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user