Skip to content

fix(deck): activate body.single override on standalone slides#100

Open
crimsondhaks wants to merge 1 commit into
nexu-io:mainfrom
crimsondhaks:fix/deck-viewer-single-slide-class
Open

fix(deck): activate body.single override on standalone slides#100
crimsondhaks wants to merge 1 commit into
nexu-io:mainfrom
crimsondhaks:fix/deck-viewer-single-slide-class

Conversation

@crimsondhaks

@crimsondhaks crimsondhaks commented Jun 1, 2026

Copy link
Copy Markdown

Closes #74

Why

Most deck skills hide inactive slides with .slide { position:absolute; opacity:0 }, and ship a paired body.single .slide { position:relative; opacity:1; transform:none } override for the case where one slide is rendered alone (which is what the deck viewer does). The override just never fired — parseDeck was copying the body class verbatim and never adding single. So every standalone slide was sitting at opacity:0 and the alignment people saw was just whatever the absolute-positioned .slide happened to do for that particular inner-HTML shape.

You suggested either a wrapper requirement on the skill side or a wrapper injected by the parser. This goes the parser route but uses the body.single hatch the skill authors already wrote, so it doesn't add a new wrapper element.

What changed

One line in parseDeck to append single to the body class on every standalone slide. For the 14 deck skills that use position:absolute slides the override fires; for the 6 that don't (deck-guizang-editorial, deck-magazine-web, deck-open-slide-canvas, deck-replit, deck-simple, deck-swiss-international) single is just an inert class.

Plus a deck.test.ts with the mixed-children fixture you mentioned — same 3 shapes the reporter described (one wrapper, multiple direct children, nested + sibling).

Verification

guard, typecheck (next + e2e), 173 tests, build — all green locally.

Probed all 3 mixed-children slides through parseDeck in headless chromium:

before fix (with single stripped):

slide 1: top=0 1280x720 position=absolute opacity=0
slide 2: top=0 1280x720 position=absolute opacity=0
slide 3: top=0 1280x720 position=absolute opacity=0

after:

slide 1: top=0 1280x720 position=relative opacity=1
slide 2: top=0 1280x720 position=relative opacity=1
slide 3: top=0 1280x720 position=relative opacity=1

after fix — slides render at native size, consistent vertical alignment

deck-slide-1-after-fix deck-slide-2-after-fix deck-slide-3-after-fix

before fix — slides stuck at opacity:0

deck-slide-1-before-fix deck-slide-2-before-fix deck-slide-3-before-fix

Closes nexu-io#74

The deck viewer extracts each <section class="slide"> into a standalone
HTML document (parseDeck in src/lib/deck.ts) so the deck strip can
render slides one-by-one. The standalone HTML copied the original body
class verbatim — no 'single' token.

But every deck skill that uses position:absolute slides ships an
escape-hatch rule:

  body.single .slide {
    position: relative;
    width: 100vw; height: 100vh;
    opacity: 1; transform: none;
    pointer-events: auto;
  }

That rule is paired with the inactive-slide hidden state
(.slide { position:absolute; inset:0; opacity:0; ... }), which is
correct for the runtime deck (where one slide is .is-active and the
rest are hidden) but leaves a single-slide standalone iframe stuck in
the hidden state. The override exists precisely to handle this
standalone-render path — it just never fired because parseDeck didn't
add 'single' to the body class.

This appends 'single' to the body class on every standalone slide. For
the 14 bundled deck skills that use position:absolute slides, the
override now fires and the slide renders at its native size with
opacity:1 and consistent vertical alignment regardless of inner-HTML
structure (the user's reported drift was a side effect of the absolute
+ flex + opacity:0 base state interacting with different child shapes).
For the 6 skills that don't use absolute-positioned slides
(deck-guizang-editorial, deck-magazine-web, deck-open-slide-canvas,
deck-replit, deck-simple, deck-swiss-international), 'single' is an
inert class that no rule selects.

Adds src/lib/__tests__/deck.test.ts pinning:
  - every standalone slide body carries 'single'
  - original body classes (e.g. skill-scoped hooks) are preserved
  - empty original class produces a clean ['single'] (no leading space)
  - mixed-children slides (the issue's exact failure mode) all parse
  - speaker notes are still stripped from the rendered slide
  - non-deck docs still return isDeck:false
@lefarcen lefarcen requested a review from Siri-Ray June 1, 2026 05:02
@lefarcen lefarcen added size/M Medium change: 100-299 lines risk/medium Medium risk change type/bugfix Bug fix labels Jun 1, 2026
@lefarcen

lefarcen commented Jun 2, 2026

Copy link
Copy Markdown

Hey @Siri-Ray — gentle nudge on this one when you have a window. 🙏

Small surface: one line in parseDeck to append single to the standalone-slide body class, plus a deck.test.ts covering the three mixed-children shapes from #74. @crimsondhaks's verification trail (before/after position + opacity probe, screenshots) lines up cleanly with the root cause, so it should be a quick read.

No rush if you're heads-down — just flagging it's been waiting since yesterday.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk/medium Medium risk change size/M Medium change: 100-299 lines type/bugfix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PPT viewer shows inconsistent vertical alignment across slides

2 participants