@classic-homes/slides 0.5.6 → 0.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,14 +1,40 @@
1
1
  import {
2
2
  DeckSchema,
3
+ Image,
4
+ Item,
3
5
  LAYOUTS,
4
6
  LAYOUT_CATEGORIES,
5
7
  baseSlide,
6
8
  getLayout
7
- } from "./chunk-36736WP4.js";
9
+ } from "./chunk-ILE46F7Y.js";
8
10
 
9
11
  // src/catalog.ts
10
12
  import { z } from "zod";
11
- import { zodToJsonSchema } from "zod-to-json-schema";
13
+ import { ignoreOverride, zodToJsonSchema } from "zod-to-json-schema";
14
+ var SHORTHAND = /* @__PURE__ */ new Map([
15
+ [Item._def.schema, "Shorthand for { text }."],
16
+ [Image._def.schema, "Shorthand for { src }."]
17
+ ]);
18
+ function toJsonSchema(schema, name) {
19
+ return zodToJsonSchema(schema, {
20
+ ...name ? { name } : {},
21
+ $refStrategy: "none",
22
+ override: (def) => {
23
+ const inner = def.schema;
24
+ const isEffects = def.typeName === "ZodEffects";
25
+ const hint = isEffects ? SHORTHAND.get(inner) : void 0;
26
+ if (!hint) return ignoreOverride;
27
+ const object = zodToJsonSchema(inner, {
28
+ $refStrategy: "none"
29
+ });
30
+ delete object.$schema;
31
+ return {
32
+ anyOf: [{ type: "string", description: hint }, object],
33
+ ...def.description ? { description: def.description } : {}
34
+ };
35
+ }
36
+ });
37
+ }
12
38
  function listLayouts() {
13
39
  return LAYOUTS.map((l) => ({
14
40
  name: l.name,
@@ -26,12 +52,12 @@ function describeLayout(name) {
26
52
  description: layout.description,
27
53
  whenToUse: layout.whenToUse,
28
54
  defaults: layout.defaults ?? {},
29
- schema: zodToJsonSchema(schema, { $refStrategy: "none" }),
55
+ schema: toJsonSchema(schema),
30
56
  example: layout.example
31
57
  };
32
58
  }
33
59
  function deckJsonSchema() {
34
- return zodToJsonSchema(DeckSchema, { name: "ClassicDeck", $refStrategy: "none" });
60
+ return toJsonSchema(DeckSchema, "ClassicDeck");
35
61
  }
36
62
  var AUTHORING_GUIDE = `# Writing a Classic Homes deck
37
63
 
@@ -104,4 +130,4 @@ export {
104
130
  deckJsonSchema,
105
131
  AUTHORING_GUIDE
106
132
  };
107
- //# sourceMappingURL=chunk-73QTNWG4.js.map
133
+ //# sourceMappingURL=chunk-AJFBVFYM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/catalog.ts"],"sourcesContent":["import { z } from 'zod';\nimport { ignoreOverride, zodToJsonSchema } from 'zod-to-json-schema';\nimport {\n LAYOUT_CATEGORIES,\n LAYOUTS,\n getLayout,\n type LayoutCategory,\n type LayoutName,\n} from './layouts/index.js';\nimport { baseSlide, Image, Item } from './schema/common.js';\nimport { DeckSchema } from './schema/deck.js';\n\n/**\n * Item and Image accept a bare string as shorthand (\"Q4 best\" → { text }, \"a.jpg\" → { src }).\n * zod-to-json-schema only sees the object behind the preprocess, so advertise both forms\n * or agents reading the schema think the shorthand in our own examples is invalid.\n */\nconst SHORTHAND = new Map<unknown, string>([\n [(Item._def as { schema: unknown }).schema, 'Shorthand for { text }.'],\n [(Image._def as { schema: unknown }).schema, 'Shorthand for { src }.'],\n]);\n\nfunction toJsonSchema(schema: z.ZodTypeAny, name?: string) {\n return zodToJsonSchema(schema, {\n ...(name ? { name } : {}),\n $refStrategy: 'none',\n override: (def) => {\n const inner = (def as { schema?: unknown }).schema;\n const isEffects = (def as { typeName?: string }).typeName === 'ZodEffects';\n const hint = isEffects ? SHORTHAND.get(inner) : undefined;\n if (!hint) return ignoreOverride;\n const object = zodToJsonSchema(inner as z.ZodTypeAny, {\n $refStrategy: 'none',\n }) as Record<string, unknown>;\n delete object.$schema;\n return {\n anyOf: [{ type: 'string', description: hint }, object],\n ...((def as { description?: string }).description\n ? { description: (def as { description?: string }).description }\n : {}),\n };\n },\n });\n}\n\nexport interface LayoutSummary {\n name: string;\n category: LayoutCategory;\n description: string;\n whenToUse: string;\n}\n\nexport function listLayouts(): LayoutSummary[] {\n return LAYOUTS.map((l) => ({\n name: l.name,\n category: LAYOUT_CATEGORIES[l.name as LayoutName],\n description: l.description,\n whenToUse: l.whenToUse,\n }));\n}\n\n/** Full description of one layout: fields as JSON Schema plus a working example. */\nexport function describeLayout(name: string) {\n const layout = getLayout(name);\n if (!layout) return undefined;\n const schema = z.object({ layout: z.literal(layout.name), ...layout.shape, ...baseSlide });\n return {\n name: layout.name,\n description: layout.description,\n whenToUse: layout.whenToUse,\n defaults: layout.defaults ?? {},\n schema: toJsonSchema(schema),\n example: layout.example,\n };\n}\n\n/** JSON Schema for a whole deck (editor tooling, `$schema` in deck files). */\nexport function deckJsonSchema() {\n return toJsonSchema(DeckSchema, 'ClassicDeck');\n}\n\n/** Authoring guidance shared by the CLI, MCP server and docs. */\nexport const AUTHORING_GUIDE = `# Writing a Classic Homes deck\n\nA deck is a YAML (or JSON) file: deck-level fields plus a list of slides. Each slide\npicks a **layout** and fills its fields. Layouts own all styling — there is no way to\nset colours, fonts or sizes directly, which is what keeps every deck on-brand.\n\n## Principles\n- **One idea per slide.** The headline states the point. Detail goes in \\`notes\\`.\n- **Short copy.** Titles under ~70 characters, bullets a few words, at most 6 bullets.\n \\`validate\\` warns when a slide gets crowded — split it rather than shrinking text.\n- **Speaker notes on every content slide.** Presenters press N (or P for presenter view).\n- **Real photos over decoration.** Every image needs \\`alt\\` text.\n- **Tracks** colour the accent: \\`value\\` (red, default) for the homeowner/value story,\n \\`build\\` (teal) for construction/process, \\`neutral\\` (navy) for framing slides.\n- **Backgrounds**: \\`paper\\` (default) for content; solid \\`navy\\`/\\`red\\`/\\`teal\\`/\\`plum\\`/\\`grey\\`/\\`stone\\`\n for dividers. \\`backgroundImage\\` + \\`overlay\\` puts a photo behind title/section/statement slides.\n\n## Picking a layout\n- **Numbers:** \\`stats\\` (2–6 headline figures, optional trend + sparkline), \\`chart\\` (a real chart —\n put the takeaway in the title), \\`table\\` (exact figures people compare).\n- **Processes & structure:** \\`flow\\` (ordered steps with arrows), \\`timeline\\` (dated milestones, phases),\n \\`cycle\\` (things that repeat), \\`hierarchy\\` (org chart / tree), \\`matrix\\` (2×2), \\`tiers\\`\n (funnel or pyramid), \\`walkthrough\\` (photo per stage), \\`journey\\` (deck agenda).\n- **Media:** \\`split\\` (text + photos), \\`photos\\`, \\`image\\` (with numbered \\`hotspots\\` for floor plans\n and site maps), \\`compare\\` (before/after slider), \\`video\\` (YouTube/Vimeo), \\`embed\\` (any https page:\n maps, dashboards, 3D tours, forms).\n- **Words:** \\`statement\\`, \\`bullets\\`, \\`cards\\`, \\`comparison\\`, \\`quote\\`.\n- **Wrap-up:** \\`qr\\` (send people somewhere), \\`contacts\\`, closing \\`statement\\`.\n\n## Charts\n\\`chart.labels\\` are the categories; each series has one value per label. Pie, donut, funnel and\ngauge use the first series; gauge needs \\`max\\`; scatter uses two series as x and y. Use\n\\`highlight\\` to emphasise one category and \\`format\\`/\\`unit\\` instead of typing symbols into numbers.\n\n## Video & embeds\nThey need internet while presenting. Offline, in print and in PDF they show a poster (or\n\\`fallback\\` screenshot) with a QR code. Some sites refuse to be framed — the \"Open ↗\" link\nalways works; add a \\`fallback\\` image for anything critical.\n\n## Brand\n\\`brand.profile\\` picks who the deck speaks for (default \\`classic-homes\\`). You may override\n\\`name\\`, \\`tagline\\`, \\`logo\\`, \\`logoOnDark\\`, \\`favicon\\` and \\`footer\\`; colours come from the profile.\n\\`settings\\` controls slide numbers, logo placement, a confidential label and kiosk auto-advance.\n\n## Presenting\nArrow keys / Space advance (and step through \\`build: true\\` reveals). N notes · P presenter window\n(current + next slide, notes, timer) · O overview · F fullscreen · \\`?kiosk=15\\` on the URL loops\nunattended. \\`hidden: true\\` skips a slide without deleting it.\n\n## Inline text\n\\`**bold**\\`, \\`*highlight*\\` (accent colour), \\`\\\\\\`code\\\\\\`\\`, \\`[label](https://…)\\`. No HTML.\n\n## Journey decks\nSet \\`journey.steps\\` at deck level to get a numbered agenda. Then:\n- a \\`journey\\` slide shows the full map,\n- \\`step: 3\\` on any slide draws the vertical STEP rail,\n- \\`showJourney: true\\` adds the compact \"you are here\" strip (great on \\`section\\` slides).\n\n## Typical shape\n1. \\`title\\`\n2. \\`statement\\` or \\`journey\\` (why we're here / the agenda)\n3. per part: \\`section\\` → 2–5 content slides\n4. \\`qr\\` (where to go next) → \\`contacts\\` → closing \\`statement\\`\n`;\n"],"mappings":";;;;;;;;;;;AAAA,SAAS,SAAS;AAClB,SAAS,gBAAgB,uBAAuB;AAgBhD,IAAM,YAAY,oBAAI,IAAqB;AAAA,EACzC,CAAE,KAAK,KAA6B,QAAQ,yBAAyB;AAAA,EACrE,CAAE,MAAM,KAA6B,QAAQ,wBAAwB;AACvE,CAAC;AAED,SAAS,aAAa,QAAsB,MAAe;AACzD,SAAO,gBAAgB,QAAQ;AAAA,IAC7B,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;AAAA,IACvB,cAAc;AAAA,IACd,UAAU,CAAC,QAAQ;AACjB,YAAM,QAAS,IAA6B;AAC5C,YAAM,YAAa,IAA8B,aAAa;AAC9D,YAAM,OAAO,YAAY,UAAU,IAAI,KAAK,IAAI;AAChD,UAAI,CAAC,KAAM,QAAO;AAClB,YAAM,SAAS,gBAAgB,OAAuB;AAAA,QACpD,cAAc;AAAA,MAChB,CAAC;AACD,aAAO,OAAO;AACd,aAAO;AAAA,QACL,OAAO,CAAC,EAAE,MAAM,UAAU,aAAa,KAAK,GAAG,MAAM;AAAA,QACrD,GAAK,IAAiC,cAClC,EAAE,aAAc,IAAiC,YAAY,IAC7D,CAAC;AAAA,MACP;AAAA,IACF;AAAA,EACF,CAAC;AACH;AASO,SAAS,cAA+B;AAC7C,SAAO,QAAQ,IAAI,CAAC,OAAO;AAAA,IACzB,MAAM,EAAE;AAAA,IACR,UAAU,kBAAkB,EAAE,IAAkB;AAAA,IAChD,aAAa,EAAE;AAAA,IACf,WAAW,EAAE;AAAA,EACf,EAAE;AACJ;AAGO,SAAS,eAAe,MAAc;AAC3C,QAAM,SAAS,UAAU,IAAI;AAC7B,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,OAAO,IAAI,GAAG,GAAG,OAAO,OAAO,GAAG,UAAU,CAAC;AACzF,SAAO;AAAA,IACL,MAAM,OAAO;AAAA,IACb,aAAa,OAAO;AAAA,IACpB,WAAW,OAAO;AAAA,IAClB,UAAU,OAAO,YAAY,CAAC;AAAA,IAC9B,QAAQ,aAAa,MAAM;AAAA,IAC3B,SAAS,OAAO;AAAA,EAClB;AACF;AAGO,SAAS,iBAAiB;AAC/B,SAAO,aAAa,YAAY,aAAa;AAC/C;AAGO,IAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;","names":[]}
@@ -3719,39 +3719,52 @@ function fromZod(issue) {
3719
3719
  }
3720
3720
  return { level: "error", path: issue.path, message };
3721
3721
  }
3722
+ function slideIssues(slide, i) {
3723
+ const issues = [];
3724
+ const layout = getLayout(slide.layout);
3725
+ for (const url of layout.prepare?.(slide)?.qr ?? []) {
3726
+ try {
3727
+ if (url.length > 1e4) throw new Error("URL too long");
3728
+ QRCode2.create(url, { errorCorrectionLevel: "M" });
3729
+ } catch {
3730
+ issues.push({
3731
+ level: "error",
3732
+ path: ["slides", i, "url"],
3733
+ message: "This URL is too long for a QR code. Use a shorter URL."
3734
+ });
3735
+ }
3736
+ }
3737
+ for (const issue of layout.lint?.(slide) ?? []) {
3738
+ issues.push({ ...issue, path: ["slides", i, ...issue.path] });
3739
+ }
3740
+ if (collectImageSources(slide).has(PLACEHOLDER_IMAGE)) {
3741
+ issues.push({
3742
+ level: "warning",
3743
+ path: ["slides", i],
3744
+ message: "Replace the placeholder photo before presenting."
3745
+ });
3746
+ }
3747
+ return issues;
3748
+ }
3722
3749
  function validateDeck(input, options = {}) {
3723
3750
  const parsed = DeckSchema.safeParse(input);
3724
3751
  if (!parsed.success) {
3725
- return { ok: false, issues: parsed.error.issues.map(fromZod) };
3752
+ const issues2 = parsed.error.issues.map(fromZod);
3753
+ const raw = input?.slides;
3754
+ if (Array.isArray(raw)) {
3755
+ raw.forEach((candidate, i) => {
3756
+ const slide = SlideSchema.safeParse(candidate);
3757
+ if (slide.success) issues2.push(...slideIssues(slide.data, i));
3758
+ });
3759
+ }
3760
+ return { ok: false, issues: issues2 };
3726
3761
  }
3727
3762
  const deck = parsed.data;
3728
3763
  const issues = [];
3729
3764
  const steps = deck.journey?.steps.length ?? 0;
3730
3765
  const seen = /* @__PURE__ */ new Set();
3731
3766
  deck.slides.forEach((slide, i) => {
3732
- const layout = getLayout(slide.layout);
3733
- for (const url of layout.prepare?.(slide)?.qr ?? []) {
3734
- try {
3735
- if (url.length > 1e4) throw new Error("URL too long");
3736
- QRCode2.create(url, { errorCorrectionLevel: "M" });
3737
- } catch {
3738
- issues.push({
3739
- level: "error",
3740
- path: ["slides", i, "url"],
3741
- message: "This URL is too long for a QR code. Use a shorter URL."
3742
- });
3743
- }
3744
- }
3745
- for (const issue of layout.lint?.(slide) ?? []) {
3746
- issues.push({ ...issue, path: ["slides", i, ...issue.path] });
3747
- }
3748
- if (collectImageSources(slide).has(PLACEHOLDER_IMAGE)) {
3749
- issues.push({
3750
- level: "warning",
3751
- path: ["slides", i],
3752
- message: "Replace the placeholder photo before presenting."
3753
- });
3754
- }
3767
+ issues.push(...slideIssues(slide, i));
3755
3768
  if (slide.id) {
3756
3769
  if (seen.has(slide.id))
3757
3770
  issues.push({
@@ -3901,6 +3914,8 @@ function externalizeDeckImages(input, upload, options = {}) {
3901
3914
  }
3902
3915
 
3903
3916
  export {
3917
+ Image,
3918
+ Item,
3904
3919
  baseSlide,
3905
3920
  BrandPaletteSchema,
3906
3921
  BrandProfileSchema,
@@ -3963,4 +3978,4 @@ export {
3963
3978
  inlineDeckImages,
3964
3979
  externalizeDeckImages
3965
3980
  };
3966
- //# sourceMappingURL=chunk-36736WP4.js.map
3981
+ //# sourceMappingURL=chunk-ILE46F7Y.js.map