Skip to content

Mark up a live slide: the pen, and three deck fixes - #195

Merged
HamptonMakes merged 2 commits into
mainfrom
hampton/slide-deck-ux-issues-c8bf70
Aug 23, 2026
Merged

Mark up a live slide: the pen, and three deck fixes#195
HamptonMakes merged 2 commits into
mainfrom
hampton/slide-deck-ux-issues-c8bf70

Conversation

@HamptonMakes

Copy link
Copy Markdown
Collaborator

Three reported deck bugs, and the thing they turned into: a presenter can now mark up a live slide.

The pen

Mid-show there are two ways to put the room's eyes on something, and they share one drag:

  • Stowed — a drag highlights text in the deck's own accent as inverse video. Not the OS selection tint: this has to read from the back of a room, on a projector, in every theme.
  • d arms the pen — a drag paints, and each stroke fades on its own a couple of seconds later. Nothing is saved, nothing is broadcast, there is no undo. A laser pointer with a short memory.

Either way a still click stays "next", and the mark goes with the slide it was drawn on. A small badge bottom-left (pen glyph + d) mirrors the slide number's corner — without it, "why did my drag stop selecting text?" is a mystery. Escape peels one layer at a time: popover → pen → end show.

The pen lives in engine/app/javascript/coplan/deck_ink.js, owned by coplan--deck-presenter, which keeps the gesture arbitration.

Fixes

Giant Mermaid expand icon on a slide. .mermaid-diagram svg is a descendant selector, so it also matched the expand button's own icon and sized it to the canvas — 356×356 on a stage slide. Scoped to > svg; the icon is 16×16 again with the chip at 26×26. Same over-reach fixed in document mode. While there: deck diagrams were painting the document's framed panel from the host's --color-* tokens, which flip with the reader's light/dark mode, putting an app-dark box behind a light deck's diagram.

"Attachments" in the left column reloaded the page and left you at the top. turbo-rails 2.0.23's Navigator#locationWithActionIsSamePage is return false unconditionally, so every same-page fragment link is a full visit — and the scroll it then performs races the deck's async slide layout. The back-matter links now jump in place, the way the outline links above them already do.

The deck outline listed only slide 1's headings. buildToc scanned the first .markdown-rendered; a document renders one, a deck renders one per slide. Found on the way; mechanical fix only — the outline still lists headings, not slide numbers, which is the intended design.

Theme tokens

The pen's color is a new required token, --deck-pen, plus optional --deck-title-pen. The design system's rule is that deck colors come only from --deck-* tokens, so a hardcoded ink would have broken the contract. Rose on coplan, amber on graphite, teal on poster — each picked so it can't be mistaken for that theme's accent. Poster's title canvas is its accent, so the pen there is the paper color. docs/SLIDE_SPEC.md's token table is updated; all three shipped themes verified on screen.

Two gotchas worth knowing

Both cost me a real bug during verification:

  1. preventDefault() on pointerdown suppresses the compatibility mousedown. The presenter's drag-vs-click guard measures mousedown→click travel, so it was blind to a pen stroke — a drag drew and advanced the slide. The pen now reports its own gesture (DeckInk#consumePainted).
  2. Don't create the stroke node on pointerdown. A tap then paints a one-frame dot under every advance click. The node appears only once the gesture has travelled past the slop.

Verification

New spec/system/deck_ux_spec.rb — 6 browser examples covering all of it: drag-highlights / click-advances, the expand chip staying chip-sized, the attachments jump asserting no turbo:visit fires, and the pen (paints-and-holds-then-fades, tap-advances-with-no-dot, Escape-stows-without-ending).

Full suite 1772 examples, 0 failures. Also driven by hand in a real browser: all three themes, document mode, the deck scroll view, and present mode.

Three reported bugs on slide decks, plus the presenter's pen.

Hovering a Mermaid diagram on a slide showed a full-canvas expand icon:
`.mermaid-diagram svg` is a descendant selector, so it also matched the
expand button's own icon and sized it to the slide (356x356 on a stage
slide, now 16x16). Same over-reach fixed in document mode. While there,
deck diagrams were painting the document's framed panel from the host's
--color-* tokens, putting an app-dark box behind a light deck.

Clicking "Attachments" in the left column reloaded the page and left you
at the top. turbo-rails 2.0.23's `locationWithActionIsSamePage` returns
false unconditionally, so every same-page fragment link is a full visit —
and the scroll it then performs races the deck's async slide layout. The
back-matter links now jump in place, the way the outline links already do.

Marking up a slide mid-show, two gestures that share one drag:

- Stowed, a drag highlights text in the deck's own accent as inverse
  video — it has to read from the back of a room, on a projector, in
  every theme.
- `d` arms the pen (DeckInk): a drag paints, and each stroke fades on its
  own a couple of seconds later. Nothing is saved, nothing is broadcast,
  there is no undo. A laser pointer with a short memory.

Either way a still click stays "next", and the mark goes with the slide
it was drawn on. Two gotchas worth knowing: preventing pointerdown's
default suppresses the compatibility mousedown, so a pen stroke leaves no
travel for the drag guard to measure (the pen reports its own gesture);
and the stroke node must not be created until the gesture has travelled,
or a tap paints a one-frame dot under every advance click.

The pen's color is a new required theme token, --deck-pen (plus optional
--deck-title-pen), because the design system's rule is that deck colors
come only from --deck-* tokens: rose on coplan, amber on graphite, teal
on poster — each picked so it can't be mistaken for that theme's accent.

Also fixes a bug found on the way: the deck outline listed only slide 1's
headings, because buildToc scanned the first .markdown-rendered and a deck
renders one per slide.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@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: 32c3772a57

ℹ️ 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 on lines +78 to +81
if (this.armed) return

this.armed = true
// Capture phase on the document: a stroke must not depend on what the

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Route pen events through Stimulus actions

Move these stable document-level pointer bindings into the presenter’s Stimulus data-action declarations (for example, pointermove@document) and delegate to DeckInk. The dynamically created stroke paths do not receive these listeners, so the documented exception does not apply; this imperative wiring bypasses the repository’s required controller binding convention.

AGENTS.md reference: AGENTS.md:L90-L90

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Not changed — leaving this one for @hamptonlintorncatlin to rule on, since it is a deliberate deviation from a written convention rather than an oversight.

The pen listens on document, and its listeners exist only while the pen is armed: arm() adds them, stow() removes them, so nothing pen-related runs when the pen is stowed. data-action bindings are always live, so complying would mean an always-bound pointermove@document on every plan page with a deck, plus four pass-through methods on the presenter whose only job is to satisfy the binding style. (The CPU cost of that is negligible — the argument is lifecycle scoping, not perf.)

It also matches the controller it lives in: deck_presenter_controller already adds and removes document-level keydown, click, mousedown, and mouseup in capture phase for exactly the same reason — the show owns the keyboard and pointer only while it is running. Happy to convert if you would rather the convention hold strictly here; it is a mechanical change.

🤖 Addressed by Claude Code

Comment thread engine/app/javascript/controllers/coplan/deck_presenter_controller.js Outdated
Comment thread engine/app/javascript/coplan/deck_ink.js
Two ways the presenter's drag guard could swallow a click that wasn't a
drag, both from state outliving the gesture that set it.

The mouse origin was never cleared. After any physical click it stayed
set, so the next click synthesized without coordinates — Space on a
focused checkbox, or assistive technology — measured (0, 0) against the
last mouse position, read as a drag, and was swallowed before the
interactive-control pass-through could run. Ticking a task box mid-show
silently did nothing. The origin is now consumed by the click its own
press produced.

A cancelled pointer claimed a click that never came. pointercancel ran
the same path as pointerup and set the painted flag, but a cancellation
synthesizes no click, so the flag sat there until some later unrelated
click ate it instead of advancing the show. Only a lifted pointer claims
a click now, which also fixes the same leak on Escape mid-stroke; a
cancelled stroke is discarded, because it was never made.

Both are pinned by system specs that fail without the fix.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@HamptonMakes
HamptonMakes merged commit ee2bb4b into main Aug 23, 2026
4 checks passed
@HamptonMakes
HamptonMakes deleted the hampton/slide-deck-ux-issues-c8bf70 branch August 23, 2026 19:39
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