feat: keep a lingering reply on the page while the writer answers#15
feat: keep a lingering reply on the page while the writer answers#15vnnkl wants to merge 2 commits into
Conversation
Any pen contact during Lingering dissolved the reply before the writer could ink a stroke — you could never write your answer while reading what the diary said. Now the contact that used to dismiss the reply starts the writer's stroke instead; the reply stays put and is drunk together with their ink at commit time (it still fades on its own if they never write). The oracle snapshot must not photograph the lingering reply, so to_png now replays the stroke model into a clean offscreen buffer instead of reading the screen. Erases already edit the stroke model (forget_near), so the replay is faithful to the visible ink; geometry mirrors Surface::stamp/brush_line. Adds a test that screen-only pixels cannot leak into the oracle image.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f0653ea5bb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if let State::Lingering { region, .. } = state { | ||
| if !region.is_empty() { | ||
| pending_reply.add(region.x0, region.y0, 0); | ||
| pending_reply.add(region.x1, region.y1, 0); | ||
| } | ||
| state = State::Listening { last_pen: None }; |
There was a problem hiding this comment.
Don't drop lingering state before there is ink to commit
When the first contact on a lingering reply is the eraser, or when the user starts a stroke and then erases/cancels all of it, this transition leaves Lingering and only stashes pending_reply; because pending_reply is cleared only in the later oracle commit path, user_ink.is_empty() prevents that path from ever running and Tom's old reply no longer has a fade timer. That leaves stale reply pixels on the page until an unrelated future turn happens to drink them.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 7bb6e3c. Both no-ink paths now hand the reply to FadingReply after the idle timeout: contact-without-a-stroke (the new first match arm) and stroke-fully-erased (the region_all_white branch now checks pending_reply before returning to Listening).
| for stroke in self.strokes.iter().chain(std::iter::once(&self.current)) { | ||
| replay_stroke(&mut page, cw, ch, x0 as i32, y0 as i32, stroke); |
There was a problem hiding this comment.
Don't replay erased stroke segments into snapshots
For fast/sparse strokes, an eraser can whiten the brushed segment between two stored points while forget_near keeps both endpoints because neither point center is within the eraser radius. Replaying the whole stroke model here (instead of sampling surf) redraws that visually erased segment into /tmp/riddle-page.png, so the oracle may read text the user erased; either preserve erased gaps in the model or continue masking/referencing the surface for erasures.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 7bb6e3c. forget_near now also breaks adjacency when the segment between two surviving points passes within the eraser radius (point-to-segment distance check), so the replay can't redraw a whitened line between sparse points. Regression test added (erasing_between_sparse_points_splits_the_stroke).
- A touch that keeps a lingering reply but leads to no committed ink (an eraser tap, or a stroke fully erased) left the reply with no fade timer, stale on the page until some future turn drank it. Both paths (never-inked and all-erased) now hand the reply to FadingReply after the idle timeout. - forget_near removed POINTS inside the eraser but kept adjacency between two surviving points whose connecting SEGMENT the eraser whitened (fast strokes store sparse points), so the offscreen replay resurrected visually erased ink for the oracle. Adjacency now also breaks when the segment passes within the eraser radius; regression test included.
Re-raise of #9, ported to v0.3.0 (clean, no conflicts).
Problem
Any pen contact during
Lingeringdissolves the reply before the writer can ink a stroke — you can never write your answer while reading what the diary said.Change
pending_replytracked beside the state machine, dissolved as a second region inDrinking).Why to_png changed
The oracle snapshot must not photograph the lingering reply, so
to_pngnow replays the stroke model into a clean offscreen buffer instead of reading the screen. Erases already edit the stroke model (forget_near), so the replay is faithful to the visible ink; the geometry mirrorsSurface::stamp/brush_lineexactly. Includes a test asserting screen-only pixels cannot leak into the oracle image.Tested
Daily-driven on a Paper Pro (Ferrari, OS 3.27.3.0, takeover, gpt-5.5): many write-over-reply turns; replies respond only to the writer's words even when written directly over Tom's text.
🤖 Generated with Claude Code