fix(slideshow): finish remaining split-PR review findings#1594
Conversation
Genuinely-open findings from the #1580/#1590/#1591/#1592 reviews (the rest were already fixed on this branch: CSP handlers, manifest version, UUID ids, float keys, presenter 1s-timer): core (#1580): - isManifest rejects a non-object/array manifest (e.g. [42,null]) explicitly - resolveSlideshow flags duplicate slideSequence ids instead of silent overwrite player (#1590): - present() window.open uses noopener,noreferrer (audience syncs via channel) - BroadcastChannel name is per-deck (keyed on pathname) to avoid same-origin cross-talk between decks - add observedAttributes + attributeChangedCallback so runtime sound/mode toggles re-render studio (#1591/#1592): - persistSlideshowManifest no-op gate (skip write when HTML is unchanged) - surface persist failures (console.error) instead of silent .catch(()=>{}) - confirm before deleting a branch sequence (data-loss + dangling hotspots) + tests for the collision + non-object-manifest rejection. 20 core / 106 player / 53 studio pass; tsc/lint/fmt/fallow clean; deck still renders.
The larger items from the #1580/#1590/#1591/#1592 reviews (the rest landed in #1585): core (#1580): - dedup isSceneLikeCompositionId — shared slideshow/sceneId.ts, used by both the lint rule and the runtime scene-window computation (no more mirror-and-drift) player (#1590 / #1592): - onKey: when multiple decks share a page, drop the unfocused-convenience so a key drives only the focused deck - slow-iframe recovery: if the scene timeline posts after the wait times out (empty scenes), re-init once so sceneId slides resolve instead of being dropped studio (#1591): - persistSlideshowManifest validates the built island round-trips before writing - reorderBranchSlide helper + BranchTree up/down controls (parallel to main-line reorder), with a branch-position indicator + tests for reorderBranchSlide. core 228 / player 106 / studio (panel) 46 pass; tsc/lint/fmt/fallow clean.
miga-heygen
left a comment
There was a problem hiding this comment.
Review — PR #1594: fix(slideshow): finish remaining split-PR review findings
Solid cleanup PR. Systematically ticks off the outstanding review items from the split-PR stack. Here is a per-finding scorecard and a few observations on the new code.
Findings addressed in this PR
| Finding | Source | Status | Notes |
|---|---|---|---|
Duplicated isSceneLikeCompositionId |
#1580 | Fixed | Extracted to slideshow/sceneId.ts, imported by both timeline.ts and lint rule. Single source of truth — drift eliminated. |
Shallow isManifest() (passes [42, null]) |
#1580 | Fixed | Array.isArray(v) guard added; test added for the array case. |
| Sequence-id collision (silent overwrite) | #1580 | Fixed | resolveSlideshow now pushes an error on duplicate ids. Test added. Note: it warns but still keeps the last one — the error message says "only the last definition is kept" which is honest, but the caller doesn't get the chance to reject the manifest. Acceptable for a lint-time check; worth considering a hard throw if this ever gates a write path. |
| Multi-instance key conflict | #1590 | Fixed | document.querySelectorAll("hyperframes-slideshow").length > 1 disables ambient keyboard handling. Functional, though it's a DOM query on every keydown — fine for a keyboard handler's cadence. |
| BroadcastChannel name unhardened | #1590 | Fixed | slideshowChannelName() keys on location.pathname. Good. Tests updated. Exported so tests can use the same derivation. |
Missing observedAttributes |
#1590 | Fixed | static get observedAttributes returns ["sound", "mode"]; attributeChangedCallback re-renders. |
present() lacks noopener,noreferrer |
#1590 | Fixed | Third arg added to window.open. |
No-op gate in persistSlideshowManifest |
#1591 | Fixed | if (after === current) return; — clean. |
Silent .catch(()=>{}) on persist failures |
#1591 | Fixed | Both the debounced notes persist and the onPersist wrapper now console.error. |
| No delete confirmation on branch sequences | #1592 | Fixed | window.confirm with count and label. Good UX copy. |
New functionality added
reorderBranchSlidehelper + UI buttons: Refactored the swap logic into a sharedswapSlidefunction, reused by bothreorderMainLineSlideand the newreorderBranchSlide. Tests cover both directions + boundary/unknown cases. Clean.- Slow-iframe recovery: Re-init listener when scenes arrive late (empty at first
waitForScenestimeout). Thegen === this.initGenerationguard is good — prevents stale listeners from re-initing after a subsequent call has already taken over. - Write-time validation:
persistSlideshowManifestround-trips the built island HTML throughparseSlideshowManifestbefore writing. Belt-and-suspenders — catches malformed edits before they corrupt the composition file.
Observations / minor items
-
handleReorderBranchSlideswallows persist errors (SlideshowPanel.tsx:299):applyManifest(reorderBranchSlide(manifestRef.current, sequenceId, sceneId, dir)).catch( () => {}, );
This is the same
.catch(() => {})pattern that was flagged on #1591 and fixed for notes persist and theonPersistwrapper a few lines above. TheapplyManifestcall does now haveconsole.errorinside it via the try/catch on line 226, so the outer.catchis probably a dead path — but a bare swallow is still code-smell-adjacent. Same applies tohandleDeleteSequenceat line 361. Considerconsole.errorfor consistency, or remove the outer.catchentirely ifapplyManifestis guaranteed not to reject. -
handleDeleteSequencealso swallows (SlideshowPanel.tsx:361):
Same pattern —applyManifest(...).catch(() => {}). Same note as above. -
Multi-instance detection queries the DOM on every keydown (
hyperframes-slideshow.ts:347):const multiInstance = document.querySelectorAll("hyperframes-slideshow").length > 1;
Not a perf concern at keyboard-event cadence, but if this ever matters, a static counter incremented/decremented in
connectedCallback/disconnectedCallbackwould be O(1). Non-blocking. -
slideshowChannelNameuseslocation.pathnameonly — two decks on the same path but different query params (e.g.?v=1vs?v=2) share a channel. Probably fine since the audience window inherits the presenter's full URL, but worth a comment if the assumption is intentional. -
Duplicate sequence id: warn but keep — As noted above, the duplicate id detection in
resolveSlideshowpushes an error string but still overwrites. This is fine for lint/resolution — the caller can inspecterrors— but ifpersistSlideshowManifestever callsresolveSlideshowas part of its validation, duplicates would persist to disk with only a console warning. Currently not an issue since the write-time validation usesparseSlideshowManifest(which doesn't check sequence id uniqueness), but worth noting for future hardening.
Findings from the original reviews NOT in scope for this PR (claimed resolved on #1585 or by-design)
These are noted as resolved in the PR description. I have not re-verified them here since they're on main already:
- innerHTML full rebuild every render (#1590) — described as by-design
- CSP-unsafe inline handlers (#1590) — fixed on #1585
- Missing
versionfield on SlideshowManifest (#1591) — fixed on #1585 Date.now()for IDs (#1592) — fixed on #1585- Suppressed exhaustive-deps lint (#1592) — fixed on #1585
- Float values as React keys (#1592) — fixed on #1585
Verdict
The PR addresses the outstanding findings correctly and introduces well-structured new code (shared swapSlide, write-time validation, slow-iframe recovery). Test coverage is solid for the new paths. The few items above are minor and non-blocking.
Recommend approval once the .catch(() => {}) inconsistency in the two new handlers is addressed (items 1-2 above) — or if you're fine with it given applyManifest's internal try/catch already covers it, that's a reasonable call too.
Review by Miga
new URL(...).pathname yields a leading-slash drive path ("/D:/...") on Windows,
which broke:
- packages/player/vitest.config.ts — the @hyperframes/core/slideshow alias
resolved to a nonexistent path, failing the player slideshow tests on the
Windows render-verification CI (passed on macOS/Linux where pathname is clean)
- packages/cli/src/utils/compositionServer.ts helperDir — same bug in the
play/present bundle-path resolution
fileURLToPath converts file:// URLs to correct OS paths on all platforms.
Player slideshow tests pass; present serves + resolves bundles.
DEFAULT_RENDERS_DIR used new URL(import.meta.url).pathname, which is "/D:/..." on Windows and resolves to a bogus path — affects the Windows render pipeline. Last of the .pathname -> fileURLToPath fixes (repo-wide src sweep now clean).
Follow-up to #1585 (merged). Addresses the remaining open findings from the #1580/#1590/#1591/#1592 reviews — the items that weren't already fixed on #1585. Branches off
mainsince the slideshow stack is merged.Findings addressed
core (#1580)
isSceneLikeCompositionId— extracted toslideshow/sceneId.ts, imported by both the lint rule and the runtime scene-window computation (was a mirror-and-drift copy).isManifestrejects non-object/array manifests;resolveSlideshowflags duplicate sequence ids.player (#1590 / #1592)
onKeydrops the unfocused-convenience so a key drives only the focused deck.waitForScenestimes out (empty scenes → sceneId slides dropped), re-init once when it finally arrives.present()noopener,noreferrer; per-deck BroadcastChannel name;observedAttributes/attributeChangedCallback.studio (#1591)
persistSlideshowManifestconfirms the built island round-trips to a valid manifest before writing.reorderBranchSlide— new helper + BranchTree up/down controls (parallel to main-line reorder) with a branch-position indicator.Notes
mainvia the combined merge; this PR + fix(slideshow): address code-review findings #1580-1584 #1585 carry the fixes.version,Date.now→crypto.randomUUID, float React keys, presenter 1s-timer. (unused SceneInfowas stale — it's used throughout.)#1590.1(full re-render everyonChange) is the component's by-design model; the timer + fs-glyph hotspots are fixed.core 228 / player 106 / studio 46 (panel) tests pass; tsc / oxlint / oxfmt / fallow clean.
🤖 Generated with Claude Code