Skip to content

feat: keep a lingering reply on the page while the writer answers#15

Open
vnnkl wants to merge 2 commits into
MaximeRivest:mainfrom
vnnkl:feat/write-over-lingering-reply-v2
Open

feat: keep a lingering reply on the page while the writer answers#15
vnnkl wants to merge 2 commits into
MaximeRivest:mainfrom
vnnkl:feat/write-over-lingering-reply-v2

Conversation

@vnnkl

@vnnkl vnnkl commented Jul 7, 2026

Copy link
Copy Markdown

Re-raise of #9, ported to v0.3.0 (clean, no conflicts).

Problem

Any pen contact during Lingering dissolves the reply before the writer can ink a stroke — you can never write your answer while reading what the diary said.

Change

  • The contact that used to dismiss a lingering reply now starts the writer's stroke instead; the reply stays on the page while they write (it still fades on its own if they never write).
  • At commit, the old reply is drunk together with the writer's ink — one absorb moment — then the new reply appears (pending_reply tracked beside the state machine, dissolved as a second region in Drinking).

Why to_png changed

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; the geometry mirrors Surface::stamp/brush_line exactly. 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

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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread riddle/src/main.rs
Comment on lines +348 to +353
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 };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Comment thread riddle/src/ink.rs
Comment on lines +131 to +132
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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant