@texuli/deck 0.0.0-stage → 0.4.0

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.
Files changed (139) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +182 -2
  3. package/dist/authoring/builder.d.ts +351 -0
  4. package/dist/authoring/builder.js +64 -0
  5. package/dist/authoring/index.d.ts +6 -0
  6. package/dist/authoring/index.js +6 -0
  7. package/dist/authoring/lint.d.ts +36 -0
  8. package/dist/authoring/lint.js +325 -0
  9. package/dist/authoring/outlines.d.ts +34 -0
  10. package/dist/authoring/outlines.js +255 -0
  11. package/dist/authoring/patch.d.ts +23 -0
  12. package/dist/authoring/patch.js +166 -0
  13. package/dist/authoring/select-layouts.d.ts +19 -0
  14. package/dist/authoring/select-layouts.js +90 -0
  15. package/dist/engine/auto-slide.d.ts +32 -0
  16. package/dist/engine/auto-slide.js +67 -0
  17. package/dist/engine/canvas.d.ts +56 -0
  18. package/dist/engine/canvas.js +48 -0
  19. package/dist/engine/controller.d.ts +126 -0
  20. package/dist/engine/controller.js +248 -0
  21. package/dist/engine/controls.d.ts +128 -0
  22. package/dist/engine/controls.js +460 -0
  23. package/dist/engine/hash.d.ts +42 -0
  24. package/dist/engine/hash.js +86 -0
  25. package/dist/engine/index.d.ts +88 -0
  26. package/dist/engine/index.js +88 -0
  27. package/dist/engine/messages.d.ts +138 -0
  28. package/dist/engine/messages.js +140 -0
  29. package/dist/engine/motion.d.ts +64 -0
  30. package/dist/engine/motion.js +112 -0
  31. package/dist/engine/navigation.d.ts +87 -0
  32. package/dist/engine/navigation.js +156 -0
  33. package/dist/engine/presenter.d.ts +58 -0
  34. package/dist/engine/presenter.js +157 -0
  35. package/dist/index.d.ts +9 -0
  36. package/dist/index.js +9 -0
  37. package/dist/markdown/attributes.d.ts +24 -0
  38. package/dist/markdown/attributes.js +94 -0
  39. package/dist/markdown/attrs-closed.d.ts +52 -0
  40. package/dist/markdown/attrs-closed.js +520 -0
  41. package/dist/markdown/blocks.d.ts +112 -0
  42. package/dist/markdown/blocks.js +766 -0
  43. package/dist/markdown/frontmatter.d.ts +30 -0
  44. package/dist/markdown/frontmatter.js +165 -0
  45. package/dist/markdown/index.d.ts +29 -0
  46. package/dist/markdown/index.js +29 -0
  47. package/dist/markdown/issues.d.ts +68 -0
  48. package/dist/markdown/issues.js +83 -0
  49. package/dist/markdown/json.d.ts +21 -0
  50. package/dist/markdown/json.js +236 -0
  51. package/dist/markdown/limits.d.ts +32 -0
  52. package/dist/markdown/limits.js +32 -0
  53. package/dist/markdown/parse.d.ts +31 -0
  54. package/dist/markdown/parse.js +283 -0
  55. package/dist/markdown/scan.d.ts +26 -0
  56. package/dist/markdown/scan.js +138 -0
  57. package/dist/markdown/slides.d.ts +32 -0
  58. package/dist/markdown/slides.js +854 -0
  59. package/dist/markdown/text.d.ts +76 -0
  60. package/dist/markdown/text.js +447 -0
  61. package/dist/markdown/twin.d.ts +41 -0
  62. package/dist/markdown/twin.js +374 -0
  63. package/dist/markdown/write.d.ts +17 -0
  64. package/dist/markdown/write.js +688 -0
  65. package/dist/math/index.d.ts +14 -0
  66. package/dist/math/index.js +14 -0
  67. package/dist/math/parse.d.ts +9 -0
  68. package/dist/math/parse.js +648 -0
  69. package/dist/math/symbols.d.ts +44 -0
  70. package/dist/math/symbols.js +333 -0
  71. package/dist/math/types.d.ts +59 -0
  72. package/dist/math/types.js +69 -0
  73. package/dist/model/backgrounds/contrast.d.ts +77 -0
  74. package/dist/model/backgrounds/contrast.js +186 -0
  75. package/dist/model/backgrounds/index.d.ts +7 -0
  76. package/dist/model/backgrounds/index.js +7 -0
  77. package/dist/model/backgrounds/resolve.d.ts +86 -0
  78. package/dist/model/backgrounds/resolve.js +215 -0
  79. package/dist/model/backgrounds/schema.d.ts +178 -0
  80. package/dist/model/backgrounds/schema.js +203 -0
  81. package/dist/model/codes.d.ts +73 -0
  82. package/dist/model/codes.js +67 -0
  83. package/dist/model/deck.d.ts +392 -0
  84. package/dist/model/deck.js +338 -0
  85. package/dist/model/index.d.ts +19 -0
  86. package/dist/model/index.js +19 -0
  87. package/dist/model/layouts/catalog.d.ts +433 -0
  88. package/dist/model/layouts/catalog.js +495 -0
  89. package/dist/model/layouts/index.d.ts +5 -0
  90. package/dist/model/layouts/index.js +5 -0
  91. package/dist/model/layouts/props.d.ts +302 -0
  92. package/dist/model/layouts/props.js +272 -0
  93. package/dist/model/loader.d.ts +37 -0
  94. package/dist/model/loader.js +317 -0
  95. package/dist/model/migrate.d.ts +34 -0
  96. package/dist/model/migrate.js +453 -0
  97. package/dist/model/motion/index.d.ts +6 -0
  98. package/dist/model/motion/index.js +6 -0
  99. package/dist/model/motion/transition.d.ts +96 -0
  100. package/dist/model/motion/transition.js +127 -0
  101. package/dist/model/parse.d.ts +57 -0
  102. package/dist/model/parse.js +307 -0
  103. package/dist/model/plain.d.ts +40 -0
  104. package/dist/model/plain.js +275 -0
  105. package/dist/model/shared.d.ts +63 -0
  106. package/dist/model/shared.js +86 -0
  107. package/dist/model/steps/index.d.ts +7 -0
  108. package/dist/model/steps/index.js +7 -0
  109. package/dist/model/steps/phase.d.ts +34 -0
  110. package/dist/model/steps/phase.js +50 -0
  111. package/dist/model/steps/slide-steps.d.ts +129 -0
  112. package/dist/model/steps/slide-steps.js +256 -0
  113. package/dist/model/style/index.d.ts +8 -0
  114. package/dist/model/style/index.js +8 -0
  115. package/dist/model/style/resolve.d.ts +87 -0
  116. package/dist/model/style/resolve.js +174 -0
  117. package/dist/model/style/schema.d.ts +85 -0
  118. package/dist/model/style/schema.js +53 -0
  119. package/dist/model/style/theme.d.ts +69 -0
  120. package/dist/model/style/theme.js +84 -0
  121. package/dist/schema/builders.d.ts +146 -0
  122. package/dist/schema/builders.js +285 -0
  123. package/dist/schema/diagnostics.d.ts +78 -0
  124. package/dist/schema/diagnostics.js +81 -0
  125. package/dist/schema/index.d.ts +15 -0
  126. package/dist/schema/index.js +14 -0
  127. package/dist/schema/internal.d.ts +40 -0
  128. package/dist/schema/internal.js +209 -0
  129. package/dist/schema/json-schema.d.ts +12 -0
  130. package/dist/schema/json-schema.js +213 -0
  131. package/dist/schema/types.d.ts +166 -0
  132. package/dist/schema/types.js +1 -0
  133. package/dist/schema/validate.d.ts +51 -0
  134. package/dist/schema/validate.js +765 -0
  135. package/dist/templates/index.d.ts +2 -0
  136. package/dist/templates/index.js +2 -0
  137. package/dist/templates/templates.d.ts +11 -0
  138. package/dist/templates/templates.js +47 -0
  139. package/package.json +60 -4
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Deck backgrounds (model v2 §3.5), ported from Protoyard: 13 presets and a
3
+ * spec union (`solid`, `gradient`, `pattern`, `image`/`image-full`,
4
+ * `video`/`video-full`). Colors come only from the caller's palette, from a
5
+ * validated `#rrggbb`, or from numbers computed in `resolve.ts`, so no author
6
+ * string ever reaches CSS. iframe backgrounds are not part of the model.
7
+ *
8
+ * Media sources are checked for syntax here (`MEDIA_SRC_PATTERN`, plus the
9
+ * image/video kind rule below); the stage applies the URL policy at the
10
+ * host's trust tier when it paints.
11
+ */
12
+ import { type Infer } from '../../schema/index.js';
13
+ /** The background diagnostic codes this module raises. */
14
+ export declare const BACKGROUND_CODES: Readonly<{
15
+ readonly unknownBackground: "unknown-background";
16
+ readonly solidConflict: "solid-conflict";
17
+ readonly mediaKind: "media-kind";
18
+ readonly invalidPaletteColor: "invalid-palette-color";
19
+ }>;
20
+ /**
21
+ * The palette a background reads: semantic role → color. Role names mirror
22
+ * `SEMANTIC_ROLES` in `@texuli/tokens` (the stage passes the active theme's
23
+ * resolved roles, converted to hex). `inverse` is not a Texuli role; it
24
+ * defaults to `foreground`. Values are `#rgb`/`#rrggbb` or `rgb()`/`rgba()`
25
+ * (composited over `background`); an unreadable value is replaced and
26
+ * reported (`invalid-palette-color`), never guessed at.
27
+ */
28
+ export declare const DECK_PALETTE_ROLES: readonly ["background", "foreground", "surface", "primary", "accent", "muted", "border", "info", "success", "warning", "destructive"];
29
+ export type DeckPaletteRole = (typeof DECK_PALETTE_ROLES)[number];
30
+ export type DeckBackgroundPalette = {
31
+ readonly [R in DeckPaletteRole]: string;
32
+ } & {
33
+ /** Defaults to `foreground`. */
34
+ readonly inverse?: string;
35
+ };
36
+ /** Palette roles a `solid` background may name (`background` is the theme page color). */
37
+ export declare const DECK_SOLID_TOKENS: readonly ["background", "surface", "primary", "accent", "muted", "inverse"];
38
+ export type DeckSolidToken = (typeof DECK_SOLID_TOKENS)[number];
39
+ /** Gradient stops, as palette roles (Protoyard's five). */
40
+ export declare const DECK_GRADIENTS: Readonly<{
41
+ readonly brand: readonly ["primary", "info"];
42
+ readonly dusk: readonly ["foreground", "primary"];
43
+ readonly dawn: readonly ["warning", "destructive"];
44
+ readonly forest: readonly ["success", "info"];
45
+ readonly mist: readonly ["muted", "accent", "surface"];
46
+ }>;
47
+ export type DeckGradient = keyof typeof DECK_GRADIENTS;
48
+ export declare const DECK_GRADIENT_IDS: readonly ["brand", "dusk", "dawn", "forest", "mist"];
49
+ export declare const DECK_PATTERNS: readonly ["dots", "grid"];
50
+ export type DeckPattern = (typeof DECK_PATTERNS)[number];
51
+ export declare const DECK_MEDIA_TONES: readonly ["dark", "light"];
52
+ export declare const DECK_MEDIA_FITS: readonly ["cover", "contain"];
53
+ export declare const DECK_MEDIA_POSITIONS: readonly ["center", "top", "bottom", "left", "right"];
54
+ /** The default gradient angle, in degrees. */
55
+ export declare const DECK_GRADIENT_ANGLE = 135;
56
+ /** `#rrggbb` (any case) for `solid {color}`; normalized to lowercase when resolved. */
57
+ export declare const HEX_COLOR_PATTERN = "^#[0-9a-fA-F]{6}$";
58
+ /** A background spec (the object form), told apart by `kind`. */
59
+ export declare const deckBackgroundSpec: import("../../index.js").UnionSchema<readonly [import("../../index.js").RefineSchema<import("../../index.js").ObjectSchema<{
60
+ readonly kind: import("../../index.js").LiteralSchema<"solid">;
61
+ readonly token: import("../../index.js").OptionalSchema<import("../../index.js").EnumSchema<"accent" | "background" | "surface" | "primary" | "muted" | "inverse">, false>;
62
+ readonly color: import("../../index.js").OptionalSchema<import("../../index.js").StringSchema, false>;
63
+ }>>, import("../../index.js").ObjectSchema<{
64
+ readonly kind: import("../../index.js").LiteralSchema<"gradient">;
65
+ readonly gradient: import("../../index.js").EnumSchema<"brand" | "dusk" | "dawn" | "forest" | "mist">;
66
+ readonly angle: import("../../index.js").OptionalSchema<import("../../index.js").NumberSchema, false>;
67
+ }>, import("../../index.js").ObjectSchema<{
68
+ readonly kind: import("../../index.js").LiteralSchema<"pattern">;
69
+ readonly pattern: import("../../index.js").EnumSchema<"dots" | "grid">;
70
+ }>, import("../../index.js").RefineSchema<import("../../index.js").ObjectSchema<{
71
+ readonly tone: import("../../index.js").OptionalSchema<import("../../index.js").EnumSchema<"dark" | "light">, false>;
72
+ readonly overlay: import("../../index.js").OptionalSchema<import("../../index.js").NumberSchema, false>;
73
+ readonly position: import("../../index.js").OptionalSchema<import("../../index.js").EnumSchema<"center" | "top" | "bottom" | "left" | "right">, false>;
74
+ readonly kind: import("../../index.js").EnumSchema<"image" | "image-full">;
75
+ readonly src: import("../../index.js").StringSchema;
76
+ readonly alt: import("../../index.js").OptionalSchema<import("../../index.js").StringSchema, false>;
77
+ readonly fit: import("../../index.js").OptionalSchema<import("../../index.js").EnumSchema<"cover" | "contain">, false>;
78
+ }>>, import("../../index.js").RefineSchema<import("../../index.js").ObjectSchema<{
79
+ readonly tone: import("../../index.js").OptionalSchema<import("../../index.js").EnumSchema<"dark" | "light">, false>;
80
+ readonly overlay: import("../../index.js").OptionalSchema<import("../../index.js").NumberSchema, false>;
81
+ readonly position: import("../../index.js").OptionalSchema<import("../../index.js").EnumSchema<"center" | "top" | "bottom" | "left" | "right">, false>;
82
+ readonly kind: import("../../index.js").EnumSchema<"video" | "video-full">;
83
+ readonly src: import("../../index.js").StringSchema;
84
+ readonly poster: import("../../index.js").OptionalSchema<import("../../index.js").StringSchema, false>;
85
+ }>>]>;
86
+ export type DeckBackgroundSpec = Infer<typeof deckBackgroundSpec>;
87
+ export type DeckBackgroundKind = DeckBackgroundSpec['kind'];
88
+ /** The 13 presets, each a spec. */
89
+ export declare const DECK_BACKGROUND_PRESETS: Readonly<{
90
+ readonly 'solid-color': Readonly<{
91
+ kind: "solid";
92
+ token: "background";
93
+ }>;
94
+ readonly 'solid-surface': Readonly<{
95
+ kind: "solid";
96
+ token: "surface";
97
+ }>;
98
+ readonly 'solid-primary': Readonly<{
99
+ kind: "solid";
100
+ token: "primary";
101
+ }>;
102
+ readonly 'solid-accent': Readonly<{
103
+ kind: "solid";
104
+ token: "accent";
105
+ }>;
106
+ readonly 'solid-muted': Readonly<{
107
+ kind: "solid";
108
+ token: "muted";
109
+ }>;
110
+ readonly 'solid-inverse': Readonly<{
111
+ kind: "solid";
112
+ token: "inverse";
113
+ }>;
114
+ readonly 'gradient-brand': Readonly<{
115
+ kind: "gradient";
116
+ gradient: "brand";
117
+ }>;
118
+ readonly 'gradient-dusk': Readonly<{
119
+ kind: "gradient";
120
+ gradient: "dusk";
121
+ }>;
122
+ readonly 'gradient-dawn': Readonly<{
123
+ kind: "gradient";
124
+ gradient: "dawn";
125
+ }>;
126
+ readonly 'gradient-forest': Readonly<{
127
+ kind: "gradient";
128
+ gradient: "forest";
129
+ }>;
130
+ readonly 'gradient-mist': Readonly<{
131
+ kind: "gradient";
132
+ gradient: "mist";
133
+ }>;
134
+ readonly 'pattern-dots': Readonly<{
135
+ kind: "pattern";
136
+ pattern: "dots";
137
+ }>;
138
+ readonly 'pattern-grid': Readonly<{
139
+ kind: "pattern";
140
+ pattern: "grid";
141
+ }>;
142
+ }>;
143
+ export type DeckBackgroundPresetId = keyof typeof DECK_BACKGROUND_PRESETS;
144
+ export declare const DECK_BACKGROUND_PRESET_IDS: readonly ["solid-color", "solid-surface", "solid-primary", "solid-accent", "solid-muted", "solid-inverse", "gradient-brand", "gradient-dusk", "gradient-dawn", "gradient-forest", "gradient-mist", "pattern-dots", "pattern-grid"];
145
+ export declare const DEFAULT_DECK_BACKGROUND: DeckBackgroundPresetId;
146
+ /** A preset id; an unknown id is the error `unknown-background` (v2; Protoyard only warned). */
147
+ export declare const deckBackgroundPreset: import("../../index.js").EnumSchema<"solid-color" | "solid-surface" | "solid-primary" | "solid-accent" | "solid-muted" | "solid-inverse" | "gradient-brand" | "gradient-dusk" | "gradient-dawn" | "gradient-forest" | "gradient-mist" | "pattern-dots" | "pattern-grid">;
148
+ /** `style.background` and `slides[].background`: a preset id or a spec. */
149
+ export declare const deckBackground: import("../../index.js").UnionSchema<readonly [import("../../index.js").EnumSchema<"solid-color" | "solid-surface" | "solid-primary" | "solid-accent" | "solid-muted" | "solid-inverse" | "gradient-brand" | "gradient-dusk" | "gradient-dawn" | "gradient-forest" | "gradient-mist" | "pattern-dots" | "pattern-grid">, import("../../index.js").UnionSchema<readonly [import("../../index.js").RefineSchema<import("../../index.js").ObjectSchema<{
150
+ readonly kind: import("../../index.js").LiteralSchema<"solid">;
151
+ readonly token: import("../../index.js").OptionalSchema<import("../../index.js").EnumSchema<"accent" | "background" | "surface" | "primary" | "muted" | "inverse">, false>;
152
+ readonly color: import("../../index.js").OptionalSchema<import("../../index.js").StringSchema, false>;
153
+ }>>, import("../../index.js").ObjectSchema<{
154
+ readonly kind: import("../../index.js").LiteralSchema<"gradient">;
155
+ readonly gradient: import("../../index.js").EnumSchema<"brand" | "dusk" | "dawn" | "forest" | "mist">;
156
+ readonly angle: import("../../index.js").OptionalSchema<import("../../index.js").NumberSchema, false>;
157
+ }>, import("../../index.js").ObjectSchema<{
158
+ readonly kind: import("../../index.js").LiteralSchema<"pattern">;
159
+ readonly pattern: import("../../index.js").EnumSchema<"dots" | "grid">;
160
+ }>, import("../../index.js").RefineSchema<import("../../index.js").ObjectSchema<{
161
+ readonly tone: import("../../index.js").OptionalSchema<import("../../index.js").EnumSchema<"dark" | "light">, false>;
162
+ readonly overlay: import("../../index.js").OptionalSchema<import("../../index.js").NumberSchema, false>;
163
+ readonly position: import("../../index.js").OptionalSchema<import("../../index.js").EnumSchema<"center" | "top" | "bottom" | "left" | "right">, false>;
164
+ readonly kind: import("../../index.js").EnumSchema<"image" | "image-full">;
165
+ readonly src: import("../../index.js").StringSchema;
166
+ readonly alt: import("../../index.js").OptionalSchema<import("../../index.js").StringSchema, false>;
167
+ readonly fit: import("../../index.js").OptionalSchema<import("../../index.js").EnumSchema<"cover" | "contain">, false>;
168
+ }>>, import("../../index.js").RefineSchema<import("../../index.js").ObjectSchema<{
169
+ readonly tone: import("../../index.js").OptionalSchema<import("../../index.js").EnumSchema<"dark" | "light">, false>;
170
+ readonly overlay: import("../../index.js").OptionalSchema<import("../../index.js").NumberSchema, false>;
171
+ readonly position: import("../../index.js").OptionalSchema<import("../../index.js").EnumSchema<"center" | "top" | "bottom" | "left" | "right">, false>;
172
+ readonly kind: import("../../index.js").EnumSchema<"video" | "video-full">;
173
+ readonly src: import("../../index.js").StringSchema;
174
+ readonly poster: import("../../index.js").OptionalSchema<import("../../index.js").StringSchema, false>;
175
+ }>>]>]>;
176
+ export type DeckBackground = Infer<typeof deckBackground>;
177
+ /** True for one of the 13 preset ids (own keys only; `__proto__` and friends are not presets). */
178
+ export declare function isDeckBackgroundPreset(value: unknown): value is DeckBackgroundPresetId;
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Deck backgrounds (model v2 §3.5), ported from Protoyard: 13 presets and a
3
+ * spec union (`solid`, `gradient`, `pattern`, `image`/`image-full`,
4
+ * `video`/`video-full`). Colors come only from the caller's palette, from a
5
+ * validated `#rrggbb`, or from numbers computed in `resolve.ts`, so no author
6
+ * string ever reaches CSS. iframe backgrounds are not part of the model.
7
+ *
8
+ * Media sources are checked for syntax here (`MEDIA_SRC_PATTERN`, plus the
9
+ * image/video kind rule below); the stage applies the URL policy at the
10
+ * host's trust tier when it paints.
11
+ */
12
+ import { t } from "../../schema/index.js";
13
+ import { line, MEDIA_SRC_PATTERN, mediaSrc } from "../shared.js";
14
+ /** The background diagnostic codes this module raises. */
15
+ export const BACKGROUND_CODES = Object.freeze({
16
+ unknownBackground: 'unknown-background',
17
+ solidConflict: 'solid-conflict',
18
+ mediaKind: 'media-kind',
19
+ invalidPaletteColor: 'invalid-palette-color',
20
+ });
21
+ /**
22
+ * The palette a background reads: semantic role → color. Role names mirror
23
+ * `SEMANTIC_ROLES` in `@texuli/tokens` (the stage passes the active theme's
24
+ * resolved roles, converted to hex). `inverse` is not a Texuli role; it
25
+ * defaults to `foreground`. Values are `#rgb`/`#rrggbb` or `rgb()`/`rgba()`
26
+ * (composited over `background`); an unreadable value is replaced and
27
+ * reported (`invalid-palette-color`), never guessed at.
28
+ */
29
+ export const DECK_PALETTE_ROLES = [
30
+ 'background',
31
+ 'foreground',
32
+ 'surface',
33
+ 'primary',
34
+ 'accent',
35
+ 'muted',
36
+ 'border',
37
+ 'info',
38
+ 'success',
39
+ 'warning',
40
+ 'destructive',
41
+ ];
42
+ /** Palette roles a `solid` background may name (`background` is the theme page color). */
43
+ export const DECK_SOLID_TOKENS = [
44
+ 'background',
45
+ 'surface',
46
+ 'primary',
47
+ 'accent',
48
+ 'muted',
49
+ 'inverse',
50
+ ];
51
+ /** Gradient stops, as palette roles (Protoyard's five). */
52
+ export const DECK_GRADIENTS = Object.freeze({
53
+ brand: ['primary', 'info'],
54
+ dusk: ['foreground', 'primary'],
55
+ dawn: ['warning', 'destructive'],
56
+ forest: ['success', 'info'],
57
+ mist: ['muted', 'accent', 'surface'],
58
+ });
59
+ export const DECK_GRADIENT_IDS = ['brand', 'dusk', 'dawn', 'forest', 'mist'];
60
+ export const DECK_PATTERNS = ['dots', 'grid'];
61
+ export const DECK_MEDIA_TONES = ['dark', 'light'];
62
+ export const DECK_MEDIA_FITS = ['cover', 'contain'];
63
+ export const DECK_MEDIA_POSITIONS = ['center', 'top', 'bottom', 'left', 'right'];
64
+ /** The default gradient angle, in degrees. */
65
+ export const DECK_GRADIENT_ANGLE = 135;
66
+ /** `#rrggbb` (any case) for `solid {color}`; normalized to lowercase when resolved. */
67
+ export const HEX_COLOR_PATTERN = '^#[0-9a-fA-F]{6}$';
68
+ /** True when a media source is the right kind (`data:` type, avatars are images). */
69
+ function mediaFits(src, kind) {
70
+ if (src.startsWith('avatar:'))
71
+ return kind === 'image';
72
+ if (src.startsWith('data:'))
73
+ return src.startsWith(`data:${kind}/`);
74
+ return true;
75
+ }
76
+ const solidSpec = t.refine(t.object({
77
+ kind: t.literal('solid'),
78
+ token: t.optional(t.enum(DECK_SOLID_TOKENS)),
79
+ color: t.optional(t.string({ pattern: HEX_COLOR_PATTERN, max: 7 })),
80
+ }), {
81
+ code: BACKGROUND_CODES.solidConflict,
82
+ description: '`token` and `color` are mutually exclusive; neither means `background`.',
83
+ check(value, context) {
84
+ if (value.token !== undefined && value.color !== undefined) {
85
+ context.issue({
86
+ message: 'A solid background takes a palette `token` or a hex `color`, not both.',
87
+ path: ['color'],
88
+ fix: 'Remove `token` or `color`.',
89
+ });
90
+ }
91
+ },
92
+ });
93
+ const gradientSpec = t.object({
94
+ kind: t.literal('gradient'),
95
+ gradient: t.enum(DECK_GRADIENT_IDS),
96
+ angle: t.optional(t.int({ min: 0, max: 359 })),
97
+ });
98
+ const patternSpec = t.object({
99
+ kind: t.literal('pattern'),
100
+ pattern: t.enum(DECK_PATTERNS),
101
+ });
102
+ const mediaShape = {
103
+ tone: t.optional(t.enum(DECK_MEDIA_TONES)),
104
+ overlay: t.optional(t.number({ min: 0, max: 0.9 })),
105
+ position: t.optional(t.enum(DECK_MEDIA_POSITIONS)),
106
+ };
107
+ /** A poster is always an image: `MEDIA_SRC_PATTERN`, at most 2,000,000 code points. */
108
+ const posterSrc = t.string({ min: 1, max: 2_000_000, pattern: MEDIA_SRC_PATTERN });
109
+ const imageSpec = t.refine(t.object({
110
+ kind: t.enum(['image', 'image-full']),
111
+ src: mediaSrc,
112
+ alt: t.optional(line(200)),
113
+ fit: t.optional(t.enum(DECK_MEDIA_FITS)),
114
+ ...mediaShape,
115
+ }), {
116
+ code: BACKGROUND_CODES.mediaKind,
117
+ description: 'An image `src` is not a `data:video/…` URI.',
118
+ check(value, context) {
119
+ if (!mediaFits(value.src, 'image')) {
120
+ context.issue({
121
+ message: 'An image background needs an image source.',
122
+ path: ['src'],
123
+ fix: 'Use an image path, a `data:image/…` URI or an avatar, or `kind: "video"`.',
124
+ });
125
+ }
126
+ },
127
+ });
128
+ const videoSpec = t.refine(t.object({
129
+ kind: t.enum(['video', 'video-full']),
130
+ src: mediaSrc,
131
+ poster: t.optional(posterSrc),
132
+ ...mediaShape,
133
+ }), {
134
+ code: BACKGROUND_CODES.mediaKind,
135
+ description: 'A video `src` is not an image `data:` URI or an avatar; a `poster` is an image.',
136
+ check(value, context) {
137
+ if (!mediaFits(value.src, 'video')) {
138
+ context.issue({
139
+ message: 'A video background needs a video source.',
140
+ path: ['src'],
141
+ fix: 'Use a video path or a `data:video/…` URI, or `kind: "image"`.',
142
+ });
143
+ }
144
+ if (value.poster !== undefined && !mediaFits(value.poster, 'image')) {
145
+ context.issue({
146
+ message: 'A video poster must be an image.',
147
+ path: ['poster'],
148
+ fix: 'Use an image path, a `data:image/…` URI or an avatar.',
149
+ });
150
+ }
151
+ },
152
+ });
153
+ /** A background spec (the object form), told apart by `kind`. */
154
+ export const deckBackgroundSpec = t.union([solidSpec, gradientSpec, patternSpec, imageSpec, videoSpec], {
155
+ discriminator: 'kind',
156
+ code: BACKGROUND_CODES.unknownBackground,
157
+ description: 'A background spec: solid, gradient, pattern, image(-full) or video(-full).',
158
+ });
159
+ /** The 13 presets, each a spec. */
160
+ export const DECK_BACKGROUND_PRESETS = Object.freeze({
161
+ 'solid-color': Object.freeze({ kind: 'solid', token: 'background' }),
162
+ 'solid-surface': Object.freeze({ kind: 'solid', token: 'surface' }),
163
+ 'solid-primary': Object.freeze({ kind: 'solid', token: 'primary' }),
164
+ 'solid-accent': Object.freeze({ kind: 'solid', token: 'accent' }),
165
+ 'solid-muted': Object.freeze({ kind: 'solid', token: 'muted' }),
166
+ 'solid-inverse': Object.freeze({ kind: 'solid', token: 'inverse' }),
167
+ 'gradient-brand': Object.freeze({ kind: 'gradient', gradient: 'brand' }),
168
+ 'gradient-dusk': Object.freeze({ kind: 'gradient', gradient: 'dusk' }),
169
+ 'gradient-dawn': Object.freeze({ kind: 'gradient', gradient: 'dawn' }),
170
+ 'gradient-forest': Object.freeze({ kind: 'gradient', gradient: 'forest' }),
171
+ 'gradient-mist': Object.freeze({ kind: 'gradient', gradient: 'mist' }),
172
+ 'pattern-dots': Object.freeze({ kind: 'pattern', pattern: 'dots' }),
173
+ 'pattern-grid': Object.freeze({ kind: 'pattern', pattern: 'grid' }),
174
+ });
175
+ export const DECK_BACKGROUND_PRESET_IDS = [
176
+ 'solid-color',
177
+ 'solid-surface',
178
+ 'solid-primary',
179
+ 'solid-accent',
180
+ 'solid-muted',
181
+ 'solid-inverse',
182
+ 'gradient-brand',
183
+ 'gradient-dusk',
184
+ 'gradient-dawn',
185
+ 'gradient-forest',
186
+ 'gradient-mist',
187
+ 'pattern-dots',
188
+ 'pattern-grid',
189
+ ];
190
+ export const DEFAULT_DECK_BACKGROUND = 'solid-color';
191
+ /** A preset id; an unknown id is the error `unknown-background` (v2; Protoyard only warned). */
192
+ export const deckBackgroundPreset = t.enum(DECK_BACKGROUND_PRESET_IDS, {
193
+ code: BACKGROUND_CODES.unknownBackground,
194
+ description: 'A background preset id.',
195
+ });
196
+ /** `style.background` and `slides[].background`: a preset id or a spec. */
197
+ export const deckBackground = t.union([deckBackgroundPreset, deckBackgroundSpec], {
198
+ description: 'A background preset id, or a background spec object.',
199
+ });
200
+ /** True for one of the 13 preset ids (own keys only; `__proto__` and friends are not presets). */
201
+ export function isDeckBackgroundPreset(value) {
202
+ return typeof value === 'string' && Object.hasOwn(DECK_BACKGROUND_PRESETS, value);
203
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Every diagnostic code deck model v2 reports (model-v2 §3.9), with its usual
3
+ * severity and a one-line meaning. JSON validation, the cross-field passes,
4
+ * the untrusted loader, migration and the host hooks all use these codes, so
5
+ * authors, agents and docs read one table. Codes are stable kebab-case.
6
+ */
7
+ import type { DeckDiagnostic, DiagnosticSeverity } from '../schema/index.js';
8
+ export interface DeckCodeInfo {
9
+ /** The severity the engine reports this code with. */
10
+ readonly severity: DiagnosticSeverity;
11
+ /** Where it comes from. */
12
+ readonly group: 'structure' | 'cross-field' | 'policy' | 'version' | 'loader' | 'host' | 'style';
13
+ readonly meaning: string;
14
+ }
15
+ /** The documented code table. `too-many` and `too-deep` are shared by the kit and the loader. */
16
+ export declare const DECK_CODES: Readonly<{
17
+ readonly 'invalid-type': DeckCodeInfo;
18
+ readonly required: DeckCodeInfo;
19
+ readonly 'unknown-key': DeckCodeInfo;
20
+ readonly 'too-short': DeckCodeInfo;
21
+ readonly 'too-long': DeckCodeInfo;
22
+ readonly 'too-few': DeckCodeInfo;
23
+ readonly 'too-many': DeckCodeInfo;
24
+ readonly 'out-of-range': DeckCodeInfo;
25
+ readonly 'not-integer': DeckCodeInfo;
26
+ readonly 'not-finite': DeckCodeInfo;
27
+ readonly pattern: DeckCodeInfo;
28
+ readonly reserved: DeckCodeInfo;
29
+ readonly enum: DeckCodeInfo;
30
+ readonly 'unknown-kind': DeckCodeInfo;
31
+ readonly 'unsafe-text': DeckCodeInfo;
32
+ readonly 'unsafe-key': DeckCodeInfo;
33
+ readonly 'too-deep': DeckCodeInfo;
34
+ readonly cycle: DeckCodeInfo;
35
+ readonly 'too-large': DeckCodeInfo;
36
+ readonly 'invalid-input': DeckCodeInfo;
37
+ readonly 'unknown-layout': DeckCodeInfo;
38
+ readonly 'layout-props': DeckCodeInfo;
39
+ readonly 'invalid-block': DeckCodeInfo;
40
+ readonly 'unknown-background': DeckCodeInfo;
41
+ readonly 'solid-conflict': DeckCodeInfo;
42
+ readonly 'media-kind': DeckCodeInfo;
43
+ readonly 'invalid-palette-color': DeckCodeInfo;
44
+ readonly 'unknown-theme-kind': DeckCodeInfo;
45
+ readonly 'embedded-theme': DeckCodeInfo;
46
+ readonly 'unknown-theme': DeckCodeInfo;
47
+ readonly 'unknown-font-pair': DeckCodeInfo;
48
+ readonly 'unknown-code-theme': DeckCodeInfo;
49
+ readonly 'duplicate-id': DeckCodeInfo;
50
+ readonly 'duplicate-block-id': DeckCodeInfo;
51
+ readonly 'unknown-slide': DeckCodeInfo;
52
+ readonly 'section-overlap': DeckCodeInfo;
53
+ readonly 'stack-not-contiguous': DeckCodeInfo;
54
+ readonly 'start-hidden': DeckCodeInfo;
55
+ readonly 'all-hidden': DeckCodeInfo;
56
+ readonly 'fragment-conflict': DeckCodeInfo;
57
+ readonly 'too-many-steps': DeckCodeInfo;
58
+ readonly 'step-unrevealed': DeckCodeInfo;
59
+ readonly 'step-missing': DeckCodeInfo;
60
+ readonly 'unknown-component': DeckCodeInfo;
61
+ readonly 'component-props': DeckCodeInfo;
62
+ readonly 'block-props': DeckCodeInfo;
63
+ readonly 'unsupported-version': DeckCodeInfo;
64
+ readonly migrated: DeckCodeInfo;
65
+ readonly 'invalid-json': DeckCodeInfo;
66
+ readonly 'hidden-characters': DeckCodeInfo;
67
+ readonly 'invalid-deck': DeckCodeInfo;
68
+ }>;
69
+ export type DeckCode = keyof typeof DECK_CODES;
70
+ /** The deck diagnostic, narrowed to the documented codes. */
71
+ export type DeckIssue = DeckDiagnostic<DeckCode>;
72
+ /** True when `value` is a documented code (own keys only). */
73
+ export declare function isDeckCode(value: unknown): value is DeckCode;
@@ -0,0 +1,67 @@
1
+ function code(severity, group, meaning) {
2
+ return Object.freeze({ severity, group, meaning });
3
+ }
4
+ /** The documented code table. `too-many` and `too-deep` are shared by the kit and the loader. */
5
+ export const DECK_CODES = Object.freeze({
6
+ // Structure (the schema kit and the layout catalog)
7
+ 'invalid-type': code('error', 'structure', 'The value has the wrong JSON type.'),
8
+ required: code('error', 'structure', 'A required key is missing.'),
9
+ 'unknown-key': code('error', 'structure', 'The object has a key the model does not define.'),
10
+ 'too-short': code('error', 'structure', 'The text is shorter than its minimum (code points).'),
11
+ 'too-long': code('error', 'structure', 'The text is longer than its maximum (code points).'),
12
+ 'too-few': code('error', 'structure', 'The list has fewer items than its minimum.'),
13
+ 'too-many': code('error', 'structure', 'The list, record or deck has more items than its maximum.'),
14
+ 'out-of-range': code('error', 'structure', 'The number is outside its allowed range.'),
15
+ 'not-integer': code('error', 'structure', 'The number must be a whole number.'),
16
+ 'not-finite': code('error', 'structure', 'NaN and Infinity are not data.'),
17
+ pattern: code('error', 'structure', 'The text does not match the required syntax.'),
18
+ reserved: code('error', 'structure', 'The value is reserved in this position.'),
19
+ enum: code('error', 'structure', 'The value is not one of the listed values.'),
20
+ 'unknown-kind': code('error', 'structure', 'A union `kind` names no known variant.'),
21
+ 'unsafe-text': code('error', 'structure', 'The text holds control, line-break or bidi-override characters.'),
22
+ 'unsafe-key': code('error', 'structure', '`__proto__`, `constructor` and `prototype` are refused.'),
23
+ 'too-deep': code('error', 'structure', 'The value nests deeper than allowed.'),
24
+ cycle: code('error', 'structure', 'The value contains itself.'),
25
+ 'too-large': code('error', 'loader', 'The input is larger than the byte or node budget.'),
26
+ 'invalid-input': code('error', 'loader', 'The input is not plain JSON data (accessors, class instances, functions…).'),
27
+ 'unknown-layout': code('error', 'structure', 'The slide `layout` is not a catalog id or slide kind.'),
28
+ 'layout-props': code('error', 'structure', 'A layout props rule failed (e.g. comparison row width).'),
29
+ 'invalid-block': code('error', 'structure', 'A free-slide item is not a block shape (`type`, `id`, step fields).'),
30
+ // Style, theme and backgrounds
31
+ 'unknown-background': code('error', 'style', 'The background preset or spec kind is unknown.'),
32
+ 'solid-conflict': code('error', 'style', 'A solid background sets both `token` and `color`.'),
33
+ 'media-kind': code('error', 'style', 'An image background has a video source, or the reverse.'),
34
+ 'invalid-palette-color': code('warning', 'style', 'A palette color could not be read; replaced.'),
35
+ 'unknown-theme-kind': code('error', 'style', 'A theme reference `kind` is not catalog, embedded or seed.'),
36
+ 'embedded-theme': code('error', 'style', 'An embedded theme is not an object.'),
37
+ 'unknown-theme': code('warning', 'style', 'The catalog theme id is unknown to the host; default used.'),
38
+ 'unknown-font-pair': code('warning', 'style', 'The font pairing is unknown to the host; theme font used.'),
39
+ 'unknown-code-theme': code('warning', 'style', 'The code theme is unknown to the host; default used.'),
40
+ // Cross-field (parseDeck)
41
+ 'duplicate-id': code('error', 'cross-field', 'A slide or section id is used twice.'),
42
+ 'duplicate-block-id': code('error', 'cross-field', 'A block id is used twice in the deck.'),
43
+ 'unknown-slide': code('error', 'cross-field', 'A section or `navigation.start` names no slide.'),
44
+ 'section-overlap': code('error', 'cross-field', 'A slide is listed in more than one section.'),
45
+ 'stack-not-contiguous': code('error', 'cross-field', 'A vertical-stack section lists slides that are not adjacent in `slides`.'),
46
+ 'start-hidden': code('error', 'cross-field', '`navigation.start` names a hidden slide.'),
47
+ 'all-hidden': code('error', 'cross-field', 'Every slide is hidden; nothing would present.'),
48
+ 'fragment-conflict': code('error', 'cross-field', 'A step marker sets both `fragment` and `reveal`.'),
49
+ 'too-many-steps': code('error', 'cross-field', 'A slide has more than 32 distinct steps.'),
50
+ 'step-unrevealed': code('warning', 'cross-field', 'A step marker sits where the layout reveals nothing.'),
51
+ 'step-missing': code('warning', 'cross-field', 'A props marker sets `fragment` or `reveal` but no `step`; it is ignored.'),
52
+ // Host phase (parse options)
53
+ 'unknown-component': code('error', 'host', 'The component id is not in the host registry.'),
54
+ 'component-props': code('error', 'host', 'The host registry refused the component props.'),
55
+ 'block-props': code('error', 'host', 'The host block validator refused a block.'),
56
+ // Version and migration
57
+ 'unsupported-version': code('error', 'version', '`formatVersion` is not 2.'),
58
+ migrated: code('warning', 'version', 'The input was converted to model v2; review the note.'),
59
+ // Untrusted loader
60
+ 'invalid-json': code('error', 'loader', 'The text is not valid JSON (line and column given).'),
61
+ 'hidden-characters': code('warning', 'loader', 'Invisible or reordering characters were removed from a string.'),
62
+ 'invalid-deck': code('error', 'loader', 'The input is not a recognized deck shape.'),
63
+ });
64
+ /** True when `value` is a documented code (own keys only). */
65
+ export function isDeckCode(value) {
66
+ return typeof value === 'string' && Object.hasOwn(DECK_CODES, value);
67
+ }