Skip to content

Commit eb91fa2

Browse files
HamptonMakesclaude
andcommitted
Present mode: slide-aware fragment links, focused controls own their keys
Two Codex review findings on the presenter, both real: A same-document link mid-show pointed at a display: none slide — the browser's fragment jump showed nothing and clobbered the #present-N resume hash. The click handler now resolves the target's owning slide and navigates the show there; fragment targets outside the deck (the footnote back matter, hidden behind the overlay) are swallowed as no-ops instead of corrupting state. Space with a task checkbox focused advanced the deck and prevented the toggle. The keydown path now mirrors the click pass-through: a focused control (typing targets, buttons, summaries) owns its keys, with Escape remaining the exit everywhere. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 80964b5 commit eb91fa2

1 file changed

Lines changed: 27 additions & 0 deletions

File tree

engine/app/javascript/controllers/coplan/deck_presenter_controller.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,12 @@ export default class extends Controller {
137137
return
138138
}
139139

140+
// The keyboard mirror of the click pass-through below: a focused
141+
// control on a slide owns its keys, so Space toggles the checkbox it
142+
// sits on instead of advancing. Escape stays the exit everywhere.
143+
const tag = event.target.tagName
144+
if (event.key !== "Escape" && (this._typing(event.target) || tag === "BUTTON" || tag === "SUMMARY")) return
145+
140146
switch (event.key) {
141147
case "ArrowRight":
142148
case "ArrowDown":
@@ -178,6 +184,27 @@ export default class extends Controller {
178184

179185
const deck = this.element.querySelector(".deck")
180186
const onCanvas = deck && deck.contains(event.target)
187+
188+
// A same-document link mid-show: the target's slide is display: none,
189+
// so the browser's fragment jump would show nothing — and would clobber
190+
// the #present-N resume hash. Navigate the show to the slide that owns
191+
// the target instead; targets outside the deck (the footnote back
192+
// matter, hidden behind the overlay) are swallowed as no-ops.
193+
const anchor = onCanvas && event.target.closest('a[href^="#"]')
194+
if (anchor) {
195+
event.preventDefault()
196+
event.stopPropagation()
197+
let id = anchor.getAttribute("href").slice(1)
198+
try { id = decodeURIComponent(id) } catch {}
199+
const target = id ? deck.querySelector(`#${CSS.escape(id)}`) : null
200+
const slide = target?.closest("section.deck-slide")
201+
if (slide) {
202+
this._show(this._slides(deck).indexOf(slide))
203+
target.scrollIntoView({ block: "nearest" })
204+
}
205+
return
206+
}
207+
181208
// Links, checkboxes, and buttons on a slide still work; everything
182209
// else — canvas, letterbox, the page hidden behind the overlay —
183210
// advances (and is swallowed so hidden controls can't be hit).

0 commit comments

Comments
 (0)