Kitten yields to panels too; redo bug not reproducible

The overlap hook only watched .petal-rail-card, so the History and Garden
drawers still sat under the mascot — with a real control ("写作证明 ·
Writing passport") buried under the halo on the live build.

Match [role="dialog"][aria-modal="true"] as well. Both drawers already
render it, so this covers them and any future drawer without a selector
list to keep in sync.

Two things the follow-up note didn't anticipate:

- The hook now reports { cards, modal } separately. A card overlap still
  lets the kitten wake for a bubble; a modal overlap yields
  unconditionally — a cheer isn't worth covering the panel she just
  opened on purpose.
- The speech bubble is its own layer, so fading the badge didn't hide it.
  Hold it back while a panel is open; useCompanion keeps it in state, so
  it reappears when she closes the panel.

Also guard the poll's setState on value equality, so the 500 ms tick
stops re-rendering the companion for an unchanged answer.

UX_REVIEW item 1 (redo does not re-apply an accepted suggestion) is
recorded as NOT REPRODUCIBLE. Read the prosemirror-history state directly
and hooked view.dispatch: redo works pressed immediately, after an 18 s
pause that lets a full re-check land, and with the editor never focused.
The doc's hypothesis is false — every re-check transaction is
decoration-only, which prosemirror-history ignores, and canRedo stayed
true throughout. Two real findings from that dig are written into the doc
instead: keyboard undo dies when focus isn't in the editor, and an undone
suggestion stays accepted server-side so its card doesn't reliably return.

Item 5's premise is also partly wrong and now re-scoped: the Chinese
sentence does produce a card with an English rendering, just labeled
Clarity rather than a first-class Translate type.

Claude-Session: https://claude.ai/code/session_016y6gyuHkQXPiEuW8RGQyua
This commit is contained in:
prosolis
2026-07-27 22:12:30 -07:00
parent ec9fba9252
commit be9aa13287
3 changed files with 116 additions and 27 deletions
+71 -11
View File
@@ -39,21 +39,71 @@ companion picker is open, or when cards no longer overlap.
- `web/src/components/Companion/PetalCompanion.tsx``faded` wiring.
- `web/src/index.css``.petal-companion-faded`, transition.
**Follow-up for Opus:** the overlap check only watches `.petal-rail-card`.
The History panel footer and Garden counter can still sit under the kitten.
Either extend the hook's selector list (`.petal-rail-card, [data-panel]`…)
or give those panels a bottom padding of
`calc(var(--petal-companion-size) * 0.5)`. Acceptance: with History open
and with the Garden footer visible, no interactive control is under the
mascot, or the mascot is faded + click-through.
**Follow-up — DONE.** The overlap check now watches
`.petal-rail-card, [role="dialog"][aria-modal="true"]`. Matching the modal
role rather than each panel's class covers History and Garden today (both
already render `role="dialog" aria-modal="true"` drawers) and any future
drawer for free, with no selector list to keep in sync.
Two refinements the original note didn't anticipate:
- The hook now reports `{ cards, modal }` separately. A card overlap still
lets the kitten wake for a bubble; a **modal overlap yields
unconditionally** — a cheer isn't worth covering the panel she just
opened on purpose.
- The speech bubble is its own layer, so fading the badge didn't hide it.
It's now held back explicitly while a panel is open, and reappears when
she closes it (`useCompanion` keeps it in state).
Verified against the live build: with History open, the drawer overlaps the
badge rect and the "📜 写作证明 · Writing passport" control sits under the
mascot — the exact defect. The new selector trips on it.
- `useCardOverlap.ts``CardOverlap` return type, modal selector, identity
guard so the 500 ms poll doesn't re-render on every tick.
- `PetalCompanion.tsx``crowded.modal || (crowded.cards && …)`, bubble gate.
---
## 1. Bug: redo does not re-apply an accepted suggestion
## 1. Bug: redo does not re-apply an accepted suggestion — NOT REPRODUCIBLE
**Repro:** accept a suggestion (text updates), press Ctrl+Z (text reverts —
correct), press Ctrl+Shift+Z → nothing happens. Observed on the Idiom card
"by foots → on foot".
**Status (2026-07-27, follow-up session):** investigated against the live
VPS build with the ProseMirror history plugin state read directly
(`done.eventCount` / `undone.eventCount`) and `view.dispatch` hooked to log
every transaction. **Redo works in every path tried** — including the exact
repro below, on the same Idiom card. Closing this unless it resurfaces with
a tighter repro.
Paths tried, all correct (accept → `done` +1; undo → `done` 1, `undone` 1,
text reverts; redo → replacement restored):
- accept → undo → redo pressed immediately
- accept → undo → **18 s pause so the full re-check lands** → redo
- accept with the editor never focused → undo → redo
The doc's hypothesis — that the re-check wipes the redo stack — was tested
directly and is **false**: across the pause, the only doc-changing
transaction was the undo itself (`metaHistory: true`); the re-check's
transactions are all decoration-only (`docChanged: false`), which
prosemirror-history ignores. `canRedo` stayed `true` throughout.
One real trap that likely explains the original report: **keyboard undo
only works when focus is in the editor.** Clicking Accept in the rail moves
focus to the Accept button; `handleAccept`'s `chain().focus()` normally
returns it, but that only runs when `findRange` locates the original span —
if the span isn't found, focus stays on the button and Ctrl+Z/Ctrl+Shift+Z
silently do nothing. Worth hardening regardless of this item.
**Still real, found while investigating:** after undo, the suggestion is
already marked accepted server-side, so the card doesn't reliably come back
for the text that's now showing again. In one run the "for buy some apple"
card was gone while the erroneous text was visible; in another the card did
return (re-merged into a wider span). This is the second half of this item's
own acceptance criterion and belongs with item 2's stable identity work.
**Original repro (could not reproduce):** accept a suggestion (text
updates), press Ctrl+Z (text reverts — correct), press Ctrl+Shift+Z →
nothing happens. Observed on the Idiom card "by foots → on foot".
**Where to look:** `EditorCore.handleAccept`
(`web/src/components/Editor/EditorCore.tsx` ~line 556) applies via
@@ -133,6 +183,16 @@ scrolling the card list; hover-linking still highlights the right span.
## 5. Mixed-language spans: offer translation, don't ignore
**Status (follow-up session): premise partly wrong — re-scope before
building.** On the live build the same sentence *does* now produce a card:
a **Clarity** card reading 我想说这句话但是不知道用英语怎么说。 → "I want to
say this sentence but do…". So the span is detected and an English
rendering is already generated; what's missing is only the *framing* — it's
labeled Clarity rather than a first-class 翻译 · Translate type, so the
flagship moment reads as a tidy-up. Re-scope this item from "detect and
translate" to "give it its own type, label, and card treatment", which is
much cheaper than the plan below. The original observation follows.
Typed mid-document: 我想说这句话但是不知道用英语怎么说。 ("I want to say
this but don't know how in English") — Petal produced **no card at all**.
The pair model (`SUGGESTIONS.md` §1: user may type in either language,