open-design/skills/open-design-landing-deck/schema.ts
Tom Huang aefba56a3f
feat(skills): open-design-landing rename, kami skills, landing OG (#428)
* feat(skills): open-design-landing rename, kami skills, landing OG

- Rename editorial-collage skills to open-design-landing and -deck; refresh examples and compose script layout
- Add kami-deck and kami-landing skills with HTML examples
- Landing page: og.astro, index wiring, and style tweaks; package.json bump
- Web i18n: German and Russian copy for renamed and new skills
- Daemon test: update skill-asset-rewrite expectations for new paths
- Design systems: README and atelier-zero doc touch-ups
- Cross-skill SKILL.md reference updates

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs(landing-page): document version-slot invariant and deprecation timeline

Address P3 review notes on PR #428:
- Note the `data-github-version` wrapper invariant (version string only)
  near the canonical URL block in `app/page.tsx`.
- Expand the `formatVersion` helper comment in `app/pages/index.astro`
  with concrete `release.name` / `tag_name` example shapes for each
  branch of the regex fallback.
- Tighten the `EditorialCollageDeckInputs` deprecation in
  `skills/open-design-landing-deck/schema.ts` to a specific removal
  version (v0.4.0) and add a "Migrating from editorial-collage-deck"
  section to the skill README.

Generated-By: looper 0.4.0 (runner=fixer, agent=claude-code)

* docs(landing-page, skills): clarify version slot script and rename migrations

- Describe GitHub version slots as driven by the inline enhancement script,
  not React hydration.
- Add editorial-collage → open-design-landing migration notes; fix README
  link copy (Astro static landing app).
- Extend deck README migration table with shared asset path renames.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(daemon): alias deprecated editorial-collage skill ids

The PR renames the editorial-collage / editorial-collage-deck skills
to open-design-landing / open-design-landing-deck, but the daemon
persists exact skill_id strings on projects and resolves them via
listSkills().find((s) => s.id === storedId). After the rename, any
project saved against an old id silently composes without the intended
skill prompt because the listing no longer exposes that id.

Add a SKILL_ID_ALIASES map in skills.ts plus a findSkillById() helper
that rewrites deprecated ids to their current canonical form, then
route every server-side lookup (skill detail, example HTML, asset
proxy, system-prompt composer) through it. Cover the alias map, the
resolver, and end-to-end resolution against a temp skills directory
with a regression test.

Generated-By: looper 0.4.0 (runner=fixer, agent=claude-code)

* fix(kami-deck): route host od:slide messages through local go()

The host bridge classifies kami-deck as class-driven because go() toggles
.slide.active, but the visible slide is moved by deck.style.transform
which the bridge cannot drive. Listen for od:slide messages and dispatch
them through the local go() so toolbar next/prev and initialSlideIndex
restore actually shift the deck.

Generated-By: looper 0.4.0 (runner=fixer, agent=claude-code)

* fix(kami-deck): sync deck transform with host-driven .active changes

The previous fix added a local od:slide listener but the host bridge in
apps/web/src/runtime/srcdoc.ts also listens for the same message and
calls setActive() (toggles .slide.active) without driving the deck
transform. Both listeners fired, the bridge re-read the just-toggled
active class, and overshot by one — and the bridge's restoreInitialSlide
path could move .active without a message at all, leaving the deck on
the original transform.

Stop the bridge from double-handling by calling stopImmediatePropagation
in the local listener (registered first because the bridge script is
appended to </body>), and add a MutationObserver that pulls the deck
transform onto whichever slide currently carries .active so the bridge's
direct setActive calls (notably the initial-slide restore) move the deck
too.

Generated-By: looper 0.4.0 (runner=fixer, agent=claude-code)

* fix(i18n): align French content with renamed/new skills

PR #434 (French localization) merged into main with French copy for the
old editorial-collage / editorial-collage-deck skill ids; this branch
renamed those to open-design-landing / open-design-landing-deck and
added kami-deck and kami-landing. Update content.fr.ts to track the
rename and add French copy for the new kami skills so the
LOCALIZED_CONTENT_IDS coverage test passes once main is merged.

Generated-By: looper 0.4.0 (runner=fixer, agent=claude-code)

* fix(open-design-landing-deck): sync deck transform with host-driven .active changes

Apply the same fix that landed in skills/kami-deck/example.html
(commits 96b255b, 8cbca30) to the open-design-landing-deck composer
runtime: the host bridge classifies this deck as class-driven because
go() toggles .slide.active, but the visible slide is moved by
deck.style.transform which the bridge can't drive. Add an od:slide
message listener that calls stopImmediatePropagation() and routes nav
through the local go(), plus a MutationObserver that pulls the deck
transform onto whichever slide carries .active so the bridge's direct
setActive calls (notably restoreInitialSlide) move the deck too.

Regenerates example.html via scripts/compose.ts; the regeneration also
picks up upstream nav-cta and brand-meta CSS additions in the sister
open-design-landing styles.css that the example had drifted from.

Generated-By: looper 0.4.0 (runner=fixer, agent=claude-code)

* docs(open-design-landing): align deploy story with Astro landing app

- Update SKILL contract: apps/landing-page is Astro static; clarify
  nextjs-app output_format as a historical enum label and <out>/nextjs
  as a legacy folder name.
- Replace optional-deploy section with fork + pnpm --filter landing-page build.
- Fix styles.css header and regenerate landing + deck example.html so
  inlined comments match.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(deck-runtime): bypass interaction lock for host/observer slide sync

The slide deck runtimes for kami-deck and open-design-landing-deck
gate go() behind a 700ms `lock` so wheel/key/touch input bursts can't
overshoot the transform transition. But applying the same gate to the
host bridge's od:slide messages and the MutationObserver watching
`.slide.active` creates a startup race: go(0) at the end of init sets
lock=true, and any host-driven `.active` change inside that window
(notably restoreInitialSlide) fires the observer, which calls go(i),
which exits at the lock guard — leaving the visible deck on slide 1
while the host counter advances to N.

Split the actual state update into an unthrottled `applySlide(n)`
helper that updates transform, `.active`, dot nav, and the progress
bar. Keep `lock` only on the user-input path through `go()`. Route
the message listener, the MutationObserver, and the initial render
through `applySlide` directly so host-driven sync always reaches the
deck transform regardless of the throttle state.

Generated-By: looper 0.4.0 (runner=fixer, agent=claude-code)

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-04 19:22:46 +08:00

128 lines
3.8 KiB
TypeScript

/**
* open-design-landing-deck — input schema.
*
* Sister skill to `open-design-landing`. Produces a single-file slide
* deck (horizontal swipe pagination, magazine-style) in the Atelier
* Zero visual language, reusing the same `styles.css` + the same
* 16-slot image library.
*
* The schema is intentionally smaller than the landing page schema:
* a deck is an ordered array of typed slides, each driving one
* viewport-height/width frame. Brand identity is shared across slides.
*/
import type { MixedText, BrandBlock, ImageryConfig } from '../open-design-landing/schema';
export type { MixedText, BrandBlock, ImageryConfig };
/* ---------- slide variants ---------- */
/** Cover slide — title plate at the start of the deck. */
export interface CoverSlide {
kind: 'cover';
/** Eyebrow above the title — `'Open Design · Vol. 01'`. */
eyebrow: string;
/** Display title; encoded as `MixedText` for italic-serif rhythm. */
title: MixedText;
/** Optional sub-title under the title. */
subtitle?: string;
/** Lead paragraph below the title. */
lead: string;
/** Optional image slot id (`hero` | `cta` | …) from `image-manifest.json`. */
image_slot?: string;
/** Bottom-left meta line — date / location / coords. */
meta?: string;
}
/** Section divider — Roman numeral plate between chapters. */
export interface SectionSlide {
kind: 'section';
roman: string;
/** Section title; rendered huge with italic-serif emphasis. */
title: MixedText;
/** Optional one-line description under the title. */
lead?: string;
}
/** Content slide — eyebrow + title + body (+ optional bullets + image). */
export interface ContentSlide {
kind: 'content';
eyebrow?: string;
title: MixedText;
/** Body paragraph; can include `<code>` raw HTML. */
body?: string;
/** Optional bullet list. */
bullets?: string[];
/** Optional image slot id from `image-manifest.json`. */
image_slot?: string;
/** Layout: `left` puts copy left of art, `right` flips it, `full` centers. */
layout?: 'left' | 'right' | 'full';
}
/** Stats slide — eyebrow + title + 3-4 large stat rings. */
export interface StatsSlide {
kind: 'stats';
eyebrow?: string;
title: MixedText;
stats: { value: string; label: string; sub?: string }[];
/** Caption under the stat row. */
caption?: string;
}
/** Quote slide — full-bleed pull quote. */
export interface QuoteSlide {
kind: 'quote';
quote: MixedText;
author: { initial: string; name: string; title: string };
/** Optional image slot for the right-side portrait. */
image_slot?: string;
}
/** CTA slide — closing pitch with primary action. */
export interface CTASlide {
kind: 'cta';
eyebrow?: string;
title: MixedText;
body?: string;
primary: { label: string; href: string };
/** Optional secondary action. */
secondary?: { label: string; href: string };
}
/** End slide — huge italic kicker word and footer signature. */
export interface EndSlide {
kind: 'end';
/** The huge kicker — `'Open Design.'`. */
mega: MixedText;
/** Footer text under the kicker — `'Apache-2.0 · MMXXVI · Berlin'`. */
footer?: string;
}
export type Slide =
| CoverSlide
| SectionSlide
| ContentSlide
| StatsSlide
| QuoteSlide
| CTASlide
| EndSlide;
/* ---------- top-level ---------- */
export interface OpenDesignLandingDeckInputs {
$schema?: string;
brand: BrandBlock;
/** Deck-wide title shown in the HUD — `'Open Design · Vol. 01'`. */
deck_title: string;
slides: Slide[];
imagery: ImageryConfig;
}
/**
* @deprecated Use `OpenDesignLandingDeckInputs`.
*
* Backwards-compat alias kept for the v0.3.x line and removed in the next
* minor (v0.4.0). Migration steps live in `README.md` under
* "Migrating from `editorial-collage-deck`".
*/
export type EditorialCollageDeckInputs = OpenDesignLandingDeckInputs;