@folienbaukasten/extension-toolkit 1.0.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 (63) hide show
  1. package/README.md +109 -0
  2. package/abi/README.md +20 -0
  3. package/abi/abi.json +151 -0
  4. package/abi/pack.d.ts +196 -0
  5. package/abi/pack.js +126 -0
  6. package/abi/provenance.json +5 -0
  7. package/abi/shims/jsx-runtime.js +13 -0
  8. package/abi/shims/platform.js +48 -0
  9. package/abi/shims/react.js +18 -0
  10. package/abi/types/charts/geometry.d.ts +43 -0
  11. package/abi/types/charts/option.d.ts +15 -0
  12. package/abi/types/charts/scale.d.ts +14 -0
  13. package/abi/types/charts/theme.d.ts +16 -0
  14. package/abi/types/charts/waterfall.d.ts +35 -0
  15. package/abi/types/components/Block.d.ts +31 -0
  16. package/abi/types/components/BlockArrow.d.ts +16 -0
  17. package/abi/types/components/Card.d.ts +25 -0
  18. package/abi/types/components/ChartView.d.ts +4 -0
  19. package/abi/types/components/DriverTree.d.ts +9 -0
  20. package/abi/types/components/FindingsBanner.d.ts +21 -0
  21. package/abi/types/components/HarveyBall.d.ts +12 -0
  22. package/abi/types/components/HeaderTitle.d.ts +19 -0
  23. package/abi/types/components/InsightRail.d.ts +13 -0
  24. package/abi/types/components/KpiTile.d.ts +5 -0
  25. package/abi/types/components/Matrix.d.ts +5 -0
  26. package/abi/types/components/QuoteColumn.d.ts +5 -0
  27. package/abi/types/components/SourceLine.d.ts +6 -0
  28. package/abi/types/components/Table.d.ts +5 -0
  29. package/abi/types/components/Timeline.d.ts +5 -0
  30. package/abi/types/components/slide/ContentSlide.d.ts +29 -0
  31. package/abi/types/components/slide/MissingComponent.d.ts +26 -0
  32. package/abi/types/components/slide/SlideFrame.d.ts +20 -0
  33. package/abi/types/components/slide/SlotView.d.ts +30 -0
  34. package/abi/types/components/slide/registry.d.ts +27 -0
  35. package/abi/types/components/slide/views/AgendaSlideView.d.ts +8 -0
  36. package/abi/types/components/slide/views/CardsSlideView.d.ts +12 -0
  37. package/abi/types/components/slide/views/ChaptersSlideView.d.ts +11 -0
  38. package/abi/types/components/slide/views/ChartSlideView.d.ts +3 -0
  39. package/abi/types/components/slide/views/ComponentSlideView.d.ts +19 -0
  40. package/abi/types/components/slide/views/DriverTreeSlideView.d.ts +3 -0
  41. package/abi/types/components/slide/views/FreeformSlideView.d.ts +3 -0
  42. package/abi/types/components/slide/views/KpiSlideView.d.ts +3 -0
  43. package/abi/types/components/slide/views/MatrixSlideView.d.ts +3 -0
  44. package/abi/types/components/slide/views/QuoteSlideView.d.ts +3 -0
  45. package/abi/types/components/slide/views/QuotesSlideView.d.ts +5 -0
  46. package/abi/types/components/slide/views/SplitSlideView.d.ts +3 -0
  47. package/abi/types/components/slide/views/TableSlideView.d.ts +3 -0
  48. package/abi/types/components/slide/views/TimelineSlideView.d.ts +3 -0
  49. package/abi/types/components/slide/views/TitleSlideView.d.ts +7 -0
  50. package/abi/types/components/slide/views/TwoBlockSlideView.d.ts +3 -0
  51. package/abi/types/components/slide/views/WebSlideView.d.ts +23 -0
  52. package/abi/types/components/slide/views/WhileArrowSlideView.d.ts +14 -0
  53. package/abi/types/deck/data.d.ts +127 -0
  54. package/abi/types/deck/host.d.ts +9 -0
  55. package/abi/types/deck/layouts.d.ts +685 -0
  56. package/abi/types/deck/schema.d.ts +437 -0
  57. package/abi/types/deck/web-body.d.ts +47 -0
  58. package/abi/types/lib/format.d.ts +12 -0
  59. package/abi/types/sets/platform.d.ts +45 -0
  60. package/abi/types/sets/types.d.ts +128 -0
  61. package/dist/fbk-pack.js +756 -0
  62. package/package.json +49 -0
  63. package/tsconfig.extension.json +17 -0
package/README.md ADDED
@@ -0,0 +1,109 @@
1
+ # `fbk-pack` — the extension toolkit
2
+
3
+ Builds and checks a Folienbaukasten extension. **It needs no checkout of the
4
+ app.**
5
+
6
+ ```sh
7
+ node ../toolkit/bin/fbk-pack.ts check . # everything knowable before install
8
+ node ../toolkit/bin/fbk-pack.ts pack . # → dist/<id>-<version>.fbkset
9
+ node ../toolkit/bin/fbk-pack.ts abi # what platform this toolkit targets
10
+ ```
11
+
12
+ Every extension in this repo calls it through its own `npm run pack` /
13
+ `npm run check`.
14
+
15
+ How this fits together, and how to build and publish an extension end to
16
+ end, is [the repository README](../README.md).
17
+
18
+ ## Why it exists
19
+
20
+ Packaging used to run out of the app's source tree: the packer, the runtime
21
+ shims, the pack format's rules and the theme token contract all lived there,
22
+ so building a `.fbkset` meant having the app checked out at a path your
23
+ `tsconfig.json` knew about. That is the wrong dependency. An extension is
24
+ built against a **published platform**, not against someone's working copy —
25
+ and an extension author is not necessarily an app developer.
26
+
27
+ So the parts a packer needs are emitted from the app as data and checked in
28
+ here, under [`abi/`](abi/README.md):
29
+
30
+ | In the bundle | What it is |
31
+ | --- | --- |
32
+ | `abi.json` | ABI major, pack format, the design tokens a theme owes, the named box styles, the swatch tokens, the platform's exports, the layout vocabulary, the built-in theme ids |
33
+ | `pack.js` / `pack.d.ts` | the app's own `src/sets/pack.ts`, compiled — so the manifest rules enforced here are the *same code* the app runs at install, not a second implementation that can disagree |
34
+ | `shims/*.js` | the `react`, `react/jsx-runtime` and `@folienbaukasten/platform` shims a pack is built against |
35
+ | `types/` | declarations for `@folienbaukasten/platform`, so `tsconfig.json` maps the platform at the ABI rather than at a path into the app |
36
+ | `provenance.json` | which app build emitted the bundle — for a person, never read to decide anything, and excluded from the drift check so that check fires on a changed *contract* rather than on a changed commit |
37
+
38
+ Refresh it from the app repo, whose `npm run check` also fails if this copy
39
+ has drifted from what it would emit:
40
+
41
+ ```sh
42
+ cd <app repo> && npm run abi:emit
43
+ ```
44
+
45
+ ## What `check` checks
46
+
47
+ Packing runs all of it first: a pack that would not install is not written.
48
+ Everything is reported in one pass — a tool that reports one problem per
49
+ attempt is how an extension author gives up.
50
+
51
+ **The manifest** — id shape, an id that shadows no built-in theme, semver,
52
+ the declared platform ABI, template ids, `provides`, and component kinds
53
+ namespaced by the pack id. This is `manifestProblems` from the bundle: the
54
+ function the app runs at install time.
55
+
56
+ **The theme contract** — every custom property the platform reads without a
57
+ fallback, and all ten named box styles. *Nothing checked this for an
58
+ out-of-repo theme before.* A missing token silently degraded the rendering,
59
+ on the user's machine, with no message anywhere. A missing swatch is a
60
+ warning: the theme installs, the chooser just shows it without its palette.
61
+
62
+ **The components** —
63
+
64
+ - each `schemaType` resolves and generates a JSON Schema from the author's
65
+ TypeScript;
66
+ - each `example` validates against the schema generated from its own type —
67
+ a pack whose own example fails its own schema does not build;
68
+ - each `glyph` exists, is an SVG, and is under 16 KB;
69
+ - `pack.json` and `index.ts` agree: no kind declared but unregistered (the
70
+ app would offer the slide type and then blank the body), none registered
71
+ but undeclared (no description, schema or glyph — so the model never writes
72
+ it), no `webOnly` that differs between the two (a web-only component
73
+ declared PPTX-safe exports as a silently blank region), and the set's own
74
+ `id` equal to the pack's.
75
+
76
+ That last group needs the built module, so `check` builds one into a
77
+ temporary file and imports it with a stand-in for the set runtime. If the
78
+ module's top-level code throws, the group degrades to a warning rather than
79
+ blocking the pack.
80
+
81
+ **Intent** — warnings, not errors: a theme without `production: true`
82
+ installs but never appears in the chooser; a pack without a `publisher` gives
83
+ the install dialog no name to show; a template naming another theme opens in
84
+ that other theme.
85
+
86
+ ## What it does not do
87
+
88
+ Rendering. Screenshots, PPTX export and installing into a running app all go
89
+ through the app itself:
90
+
91
+ ```sh
92
+ PPT_SETS_ROOT=$PWD/.dev-sets node <app>/tools/install-set.ts dist/<pack>.fbkset
93
+ PPT_SETS_ROOT=$PWD/.dev-sets node <app>/tools/export.ts --deck templates/x.json --out out/deck.pptx
94
+ ```
95
+
96
+ That is the honest split: *packaging* is a contract, and the contract is in
97
+ this repo; *rendering* is the app, and only the app has it.
98
+
99
+ ## Reproducibility
100
+
101
+ Two builds of an unchanged extension are byte-identical: file dates are
102
+ fixed, and no directory entries are written (JSZip stamps those with the
103
+ current time whatever date you ask for — which is what used to make packs
104
+ differ run to run; install skips directory entries anyway).
105
+
106
+ The dependency versions in `package.json` are pinned to the ones the app's
107
+ packer used, `esbuild` above all: a different minifier is a different
108
+ `set.js`. Every extension in this repo was migrated onto this toolkit and
109
+ verified to produce byte-identical pack contents.
package/abi/README.md ADDED
@@ -0,0 +1,20 @@
1
+ # Generated — do not edit
2
+
3
+ This directory is the extension ABI bundle, emitted from the app by
4
+ `node tools/emit-abi.ts --out <here>`. It is what lets an extension be
5
+ built and checked without a checkout of the app.
6
+
7
+ Platform ABI 1, pack format 2.
8
+ Which app build it came from is in provenance.json — deliberately not in
9
+ abi.json, so the drift check fires on a changed contract and not on a
10
+ changed commit.
11
+
12
+ Refresh it after any change to the platform surface, the pack format,
13
+ the runtime shims, or the design tokens the platform reads:
14
+
15
+ ```sh
16
+ cd <app repo> && npm run abi:emit
17
+ ```
18
+
19
+ Anything an extension needs that is missing here is a gap in the ABI,
20
+ not a reason to reach back into the app source.
package/abi/abi.json ADDED
@@ -0,0 +1,151 @@
1
+ {
2
+ "platformAbi": 1,
3
+ "packFormatVersion": 2,
4
+ "supportedPackFormats": [
5
+ 1,
6
+ 2
7
+ ],
8
+ "platformExports": [
9
+ "Block",
10
+ "BlockArrow",
11
+ "Card",
12
+ "ChartView",
13
+ "ContentSlide",
14
+ "DriverTree",
15
+ "FindingsBanner",
16
+ "HARVEY_SIZE_PX",
17
+ "HARVEY_STROKE_PX",
18
+ "HarveyBall",
19
+ "InsightRail",
20
+ "KpiTile",
21
+ "LABEL_MIN_GAP",
22
+ "Matrix",
23
+ "QuoteColumn",
24
+ "SLIDE_HEIGHT",
25
+ "SLIDE_WIDTH",
26
+ "SlideFrame",
27
+ "SourceLine",
28
+ "Table",
29
+ "Timeline",
30
+ "bandCentre",
31
+ "bandCentreX",
32
+ "bandCentreY",
33
+ "formatNumber",
34
+ "headerTitle",
35
+ "platformDefaults",
36
+ "spreadLabels",
37
+ "valueY"
38
+ ],
39
+ "requiredTokens": [
40
+ "--banner-height",
41
+ "--border-width",
42
+ "--box-dir",
43
+ "--box-gap",
44
+ "--box-padding",
45
+ "--chart-1",
46
+ "--chart-2",
47
+ "--chart-3",
48
+ "--chart-4",
49
+ "--chart-5",
50
+ "--chart-6",
51
+ "--chart-neutral",
52
+ "--chrome-header-max-h",
53
+ "--color-accent",
54
+ "--color-accent-deep",
55
+ "--color-accent-soft",
56
+ "--color-bg",
57
+ "--color-box-bg",
58
+ "--color-box-border",
59
+ "--color-edge-muted",
60
+ "--color-highlight",
61
+ "--color-highlight-deep",
62
+ "--color-highlight-soft",
63
+ "--color-negative",
64
+ "--color-positive",
65
+ "--color-text",
66
+ "--color-text-faint",
67
+ "--color-text-inverse",
68
+ "--color-text-muted",
69
+ "--edge-h",
70
+ "--edge-w",
71
+ "--font-family",
72
+ "--gradient-accent-from",
73
+ "--gradient-accent-to",
74
+ "--gradient-soft-from",
75
+ "--gradient-soft-to",
76
+ "--line-height",
77
+ "--radius",
78
+ "--radius-lg",
79
+ "--radius-md",
80
+ "--shadow-lifted",
81
+ "--shadow-soft",
82
+ "--space-1",
83
+ "--space-12",
84
+ "--space-2",
85
+ "--space-3",
86
+ "--space-4",
87
+ "--space-6",
88
+ "--space-8",
89
+ "--text-2xl",
90
+ "--text-lg",
91
+ "--text-md",
92
+ "--text-sm",
93
+ "--text-xl",
94
+ "--text-xs",
95
+ "--weight-bold",
96
+ "--weight-medium",
97
+ "--weight-regular"
98
+ ],
99
+ "optionalTokens": [
100
+ "--font-display"
101
+ ],
102
+ "requiredBoxStyles": [
103
+ "box-card",
104
+ "box-card-emphasis",
105
+ "box-card-light",
106
+ "box-callout",
107
+ "box-stat",
108
+ "box-edge",
109
+ "box-title",
110
+ "box-body",
111
+ "box-banner-label",
112
+ "box-banner-body"
113
+ ],
114
+ "swatchTokens": {
115
+ "bg": "--color-bg",
116
+ "surface": "--color-box-bg",
117
+ "ink": "--color-text",
118
+ "accent": "--color-accent",
119
+ "highlight": "--color-highlight"
120
+ },
121
+ "layouts": [
122
+ "agenda",
123
+ "cards",
124
+ "chapters",
125
+ "chart",
126
+ "component",
127
+ "driver-tree",
128
+ "freeform",
129
+ "kpis",
130
+ "matrix",
131
+ "quote",
132
+ "quotes",
133
+ "split",
134
+ "table",
135
+ "timeline",
136
+ "title",
137
+ "two-block",
138
+ "web",
139
+ "while-arrow"
140
+ ],
141
+ "builtInSetIds": [
142
+ "consulting",
143
+ "corporate",
144
+ "editorial",
145
+ "forest",
146
+ "heritage",
147
+ "keynote",
148
+ "picnic",
149
+ "signal"
150
+ ]
151
+ }
package/abi/pack.d.ts ADDED
@@ -0,0 +1,196 @@
1
+ /**
2
+ * `.fbkset` theme packs — the installable form of a component set (ui.md A9).
3
+ *
4
+ * A pack is a zip, the same move `.fbk` makes for decks (project/bundle.ts):
5
+ * one file that can be mailed, double-clicked, or downloaded. Installing it
6
+ * extracts it under `userData/sets/<id>/<major>/`; from there it is served at
7
+ * `/sets/<id>/…` and `src/sets/loader.ts` loads it beside the compiled-in
8
+ * sets, returning the same `ComponentSet` the renderer already consumes.
9
+ *
10
+ * manifest.json what this file describes — id, version, ABI, publisher
11
+ * set.js prebuilt ESM, default-exporting a ComponentSet
12
+ * set.css the theme's tokens (the `import './theme.css'` output)
13
+ * guide.md optional LLM guidance, appended to GUIDE.md when active
14
+ * templates/*.json optional starter decks, offered in the New Deck dialog
15
+ * preview.png optional picture for the chooser
16
+ * signature.json optional Ed25519 signature over the other files
17
+ *
18
+ * This module is types and pure functions only: main validates packs with
19
+ * it at install time, the renderer reads the installed index with it, and
20
+ * tools/pack-set.ts writes manifests with it. Nothing here touches fs or
21
+ * electron.
22
+ */
23
+ /** Bumped when the pack layout itself changes shape. v2 added `provides`,
24
+ * per-component metadata and web-only declarations; v1 packs still install — they are
25
+ * simply themes that provide no components. */
26
+ export declare const PACK_FORMAT_VERSION = 2;
27
+ /** Formats this app still reads. */
28
+ export declare const SUPPORTED_PACK_FORMATS: readonly number[];
29
+ /**
30
+ * The platform ABI a pack is built against: the `ComponentSet` interface
31
+ * plus the `data-pptx` tagging contract the exporter measures
32
+ * (src/export/types.ts). A set that renders untagged markup produces a
33
+ * broken PPTX, which is why this is versioned separately from the app and
34
+ * checked at install time rather than discovered at export time.
35
+ *
36
+ * Major changes break packs; minor additions do not. A pack declares the
37
+ * major it was built for and is accepted while that major still matches.
38
+ */
39
+ export declare const PLATFORM_ABI = 1;
40
+ export declare const PACK_EXT = ".fbkset";
41
+ /** Files every pack must contain. A pack that provides a theme additionally
42
+ * needs `set.css` — see `requiredPackFiles`. */
43
+ export declare const REQUIRED_PACK_FILES: readonly ['manifest.json', 'set.js'];
44
+ /** What an extension provides. Absent in a manifest = `['theme']`, which is
45
+ * every v1 pack. */
46
+ export type PackProvides = 'theme' | 'components';
47
+ /** One extension-supplied slide component, as the manifest declares it. */
48
+ export interface PackComponentMeta {
49
+ /** Namespaced kind: `<pack id>/<component-name>`. */
50
+ kind: string;
51
+ /** One line for the sidebar and the LLM prompt. */
52
+ description?: string;
53
+ /** JSON Schema for the component's props, generated from the author's
54
+ * TypeScript by tools/pack-set.ts. Absent = unvalidated. */
55
+ schema?: unknown;
56
+ /**
57
+ * Worked example props, valid against `schema` — the pack ships the *data*,
58
+ * never a picture. The app
59
+ * renders it through its own thumbnail path to show the component in the
60
+ * editor, and hands it to the model as a usage example. Absent costs the
61
+ * component its preview and nothing else.
62
+ */
63
+ example?: unknown;
64
+ /**
65
+ * Pack-relative path to a stylized SVG for the components view — the
66
+ * author's own diagram of what this component draws, in the app's palette. Preferred over the
67
+ * rendered example: it is legible at card size, costs no render, and is the
68
+ * only thing a catalogue can show for an extension that is not installed.
69
+ *
70
+ * It is displayed as an *image*, never inlined into the editor's DOM —
71
+ * scripts do not run and external references do not load in that context,
72
+ * which is what makes author-supplied markup safe here at all.
73
+ */
74
+ glyph?: string;
75
+ /** The component is interactive, animated or otherwise unmeasurable: it
76
+ * only validates in a Web Mode deck. */
77
+ webOnly?: boolean;
78
+ }
79
+ export interface SetPackManifest {
80
+ formatVersion: number;
81
+ /** Folder name and `meta.componentSet` value. Lowercase, url-safe. */
82
+ id: string;
83
+ name: string;
84
+ description?: string;
85
+ /** Semver. The major decides which install slot the pack occupies. */
86
+ version: string;
87
+ /** Platform ABI major this pack was built against. */
88
+ platformAbi: number;
89
+ /** Who made it — shown in the install dialog beside the trust verdict. */
90
+ publisher?: string;
91
+ /** Offer it in the theme chooser. Absent = installed but not promoted. */
92
+ production?: boolean;
93
+ /** Palette preview for the chooser, same five colours the catalog carries. */
94
+ swatch?: PackSwatch;
95
+ /** Starter decks shipped with the theme, in the order they are offered. */
96
+ templates?: PackTemplate[];
97
+ /** Per-layout advice that replaces the platform description in the prompt. */
98
+ layoutDescriptions?: Record<string, string>;
99
+ /** What the pack ships. Absent = `['theme']` (every v1 pack). A
100
+ * components-only pack carries no `set.css` and no chrome, and is never
101
+ * offered as a theme — its components render under any theme. */
102
+ provides?: PackProvides[];
103
+ /** Slide components the pack ships, when `provides` includes 'components'. */
104
+ components?: PackComponentMeta[];
105
+ /**
106
+ * The theme keeps every "Rules that keep HTML ↔ PPTX identical" rule and is
107
+ * safe for PowerPoint-mode decks. Absent = true. A web-only theme
108
+ * (`pptxSafe: false`) uses CSS the exporter cannot measure and is offered
109
+ * only to Web Mode decks.
110
+ */
111
+ pptxSafe?: boolean;
112
+ }
113
+ /** Whether a manifest provides the given capability. */
114
+ export declare function packProvides(manifest: SetPackManifest, what: PackProvides): boolean;
115
+ /** The files this particular pack must contain. */
116
+ export declare function requiredPackFiles(manifest: SetPackManifest): string[];
117
+ export interface PackSwatch {
118
+ bg: string;
119
+ surface: string;
120
+ ink: string;
121
+ accent: string;
122
+ highlight: string;
123
+ }
124
+ export interface PackTemplate {
125
+ /** File under templates/, without the .json. */
126
+ id: string;
127
+ name: string;
128
+ description?: string;
129
+ }
130
+ /** Ed25519 over the sorted `sha256(file)` digests of every other pack file. */
131
+ export interface PackSignature {
132
+ algorithm: 'ed25519';
133
+ /** Key id, so a rotated key can still verify what the old one signed. */
134
+ keyId: string;
135
+ /** Base64 signature over `signingPayload()`. */
136
+ signature: string;
137
+ }
138
+ /**
139
+ * What a signature covers: every file in the pack except the signature
140
+ * itself, as `path\tsha256hex` lines, sorted. Signing the digests rather
141
+ * than the zip means a pack stays verifiable after extraction — which is
142
+ * what lets the app re-check an *installed* set, not just an incoming file.
143
+ */
144
+ export declare function signingPayload(digests: Record<string, string>): string;
145
+ /**
146
+ * Whether a pack's code is known to come from a publisher we trust.
147
+ *
148
+ * This is a *label*, never a gate: an unsigned pack installs and runs with
149
+ * exactly the same capability as a signed one. The distinction the user is
150
+ * shown is honest about what it means — a theme is code running in the
151
+ * renderer, and a signature says who wrote it, not that it is harmless.
152
+ */
153
+ export type PackTrust = 'verified' | 'untrusted';
154
+ /** An installed pack, as the app and the renderer see it. */
155
+ export interface InstalledSet {
156
+ id: string;
157
+ name: string;
158
+ description?: string;
159
+ version: string;
160
+ publisher?: string;
161
+ production?: boolean;
162
+ swatch?: PackSwatch;
163
+ templates?: PackTemplate[];
164
+ layoutDescriptions?: Record<string, string>;
165
+ provides?: PackProvides[];
166
+ components?: PackComponentMeta[];
167
+ pptxSafe?: boolean;
168
+ trust: PackTrust;
169
+ /** Root-relative base every pack file is served under: `/sets/<id>`. */
170
+ base: string;
171
+ /** ISO timestamp of the install, for the manage list. */
172
+ installedAt: string;
173
+ /** Pack ships a guide.md the system prompt should pick up. */
174
+ hasGuide?: boolean;
175
+ }
176
+ /** Served at `/sets/index.json`; the loader's whole view of what is installed. */
177
+ export interface SetsIndex {
178
+ sets: InstalledSet[];
179
+ }
180
+ /** Ids the platform owns; a pack may not shadow one of the compiled-in sets. */
181
+ export declare function reservedIds(builtIn: readonly string[]): Set<string>;
182
+ export declare function majorOf(version: string): number;
183
+ /** Compare two semvers; >0 when `a` is newer. Pre-release tags are ignored. */
184
+ export declare function compareVersions(a: string, b: string): number;
185
+ /**
186
+ * Everything wrong with a manifest, in one pass — an install dialog that
187
+ * reports one problem per attempt is how a theme author gives up.
188
+ * Empty array = installable.
189
+ */
190
+ export declare function manifestProblems(value: unknown, builtIn?: readonly string[]): string[];
191
+ /** The `InstalledSet` view of a manifest, once it is on disk. */
192
+ export declare function installedSetOf(manifest: SetPackManifest, extra: {
193
+ trust: PackTrust;
194
+ installedAt: string;
195
+ hasGuide: boolean;
196
+ }): InstalledSet;
package/abi/pack.js ADDED
@@ -0,0 +1,126 @@
1
+ // src/sets/pack.ts
2
+ var PACK_FORMAT_VERSION = 2;
3
+ var SUPPORTED_PACK_FORMATS = [1, 2];
4
+ var PLATFORM_ABI = 1;
5
+ var PACK_EXT = ".fbkset";
6
+ var REQUIRED_PACK_FILES = ["manifest.json", "set.js"];
7
+ function packProvides(manifest, what) {
8
+ return (manifest.provides ?? ["theme"]).includes(what);
9
+ }
10
+ function requiredPackFiles(manifest) {
11
+ return [...REQUIRED_PACK_FILES, ...packProvides(manifest, "theme") ? ["set.css"] : []];
12
+ }
13
+ function signingPayload(digests) {
14
+ return Object.entries(digests).filter(([file]) => file !== "signature.json").map(([file, hash]) => `${file} ${hash}`).sort().join("\n");
15
+ }
16
+ var ID_RE = /^[a-z][a-z0-9-]*(?:\.[a-z0-9][a-z0-9-]*)*[a-z0-9]$/;
17
+ var ID_MAX = 40;
18
+ var SEMVER_RE = /^(\d+)\.(\d+)\.(\d+)(?:-[0-9a-z.-]+)?$/i;
19
+ function reservedIds(builtIn) {
20
+ return new Set(builtIn);
21
+ }
22
+ function majorOf(version) {
23
+ const m = SEMVER_RE.exec(version);
24
+ return m ? Number(m[1]) : 0;
25
+ }
26
+ function compareVersions(a, b) {
27
+ const pa = SEMVER_RE.exec(a);
28
+ const pb = SEMVER_RE.exec(b);
29
+ if (!pa || !pb) return a.localeCompare(b);
30
+ for (let i = 1; i <= 3; i++) {
31
+ const d = Number(pa[i]) - Number(pb[i]);
32
+ if (d !== 0) return d;
33
+ }
34
+ return 0;
35
+ }
36
+ function manifestProblems(value, builtIn = []) {
37
+ const out = [];
38
+ if (typeof value !== "object" || value === null) return ["manifest.json is not an object"];
39
+ const m = value;
40
+ if (!SUPPORTED_PACK_FORMATS.includes(m.formatVersion)) {
41
+ out.push(
42
+ `manifest formatVersion is ${String(m.formatVersion)}, this app reads ` + SUPPORTED_PACK_FORMATS.join(" and ")
43
+ );
44
+ }
45
+ if (typeof m.id !== "string" || !ID_RE.test(m.id) || m.id.length > ID_MAX) {
46
+ out.push(
47
+ `"${String(m.id)}" is not a usable extension id \u2014 lowercase letters, digits and dashes, optionally as publisher.name, at most ${ID_MAX} characters`
48
+ );
49
+ } else if (reservedIds(builtIn).has(m.id)) {
50
+ out.push(`"${m.id}" is the id of a theme built into the app`);
51
+ }
52
+ if (typeof m.name !== "string" || m.name.trim() === "") out.push("manifest has no name");
53
+ if (typeof m.version !== "string" || !SEMVER_RE.test(m.version)) {
54
+ out.push(`"${String(m.version)}" is not a version number (e.g. 1.0.0)`);
55
+ }
56
+ if (m.platformAbi !== PLATFORM_ABI) {
57
+ out.push(
58
+ `theme was built for platform ${String(m.platformAbi)}; this app provides ${PLATFORM_ABI}`
59
+ );
60
+ }
61
+ for (const t of m.templates ?? []) {
62
+ if (typeof t?.id !== "string" || !/^[a-z0-9][a-z0-9-]*$/.test(t.id)) {
63
+ out.push(`"${String(t?.id)}" is not a usable template id`);
64
+ }
65
+ }
66
+ if (m.provides !== void 0) {
67
+ if (!Array.isArray(m.provides) || m.provides.length === 0) {
68
+ out.push('manifest "provides" must be a non-empty array of "theme" / "components"');
69
+ } else {
70
+ for (const p of m.provides) {
71
+ if (p !== "theme" && p !== "components") {
72
+ out.push(`"${String(p)}" is not something a pack can provide`);
73
+ }
74
+ }
75
+ }
76
+ }
77
+ const providesComponents = (m.provides ?? ["theme"]).includes("components");
78
+ if (providesComponents && !(Array.isArray(m.components) && m.components.length > 0)) {
79
+ out.push("the pack claims to provide components but declares none");
80
+ }
81
+ for (const c of m.components ?? []) {
82
+ if (typeof c?.kind !== "string" || !c.kind.startsWith(`${String(m.id)}/`)) {
83
+ out.push(
84
+ `component kind "${String(c?.kind)}" must be namespaced by the pack id ("${String(m.id)}/\u2026")`
85
+ );
86
+ } else if (!/^[a-z][a-z0-9.-]*\/[a-z][a-z0-9-]*$/.test(c.kind)) {
87
+ out.push(`"${c.kind}" is not a usable component kind (lowercase letters, digits, dashes)`);
88
+ }
89
+ }
90
+ return out;
91
+ }
92
+ function installedSetOf(manifest, extra) {
93
+ return {
94
+ id: manifest.id,
95
+ name: manifest.name,
96
+ ...manifest.description ? { description: manifest.description } : {},
97
+ version: manifest.version,
98
+ ...manifest.publisher ? { publisher: manifest.publisher } : {},
99
+ ...manifest.production ? { production: true } : {},
100
+ ...manifest.swatch ? { swatch: manifest.swatch } : {},
101
+ ...manifest.templates?.length ? { templates: manifest.templates } : {},
102
+ ...manifest.layoutDescriptions ? { layoutDescriptions: manifest.layoutDescriptions } : {},
103
+ ...manifest.provides ? { provides: manifest.provides } : {},
104
+ ...manifest.components?.length ? { components: manifest.components } : {},
105
+ ...manifest.pptxSafe === false ? { pptxSafe: false } : {},
106
+ trust: extra.trust,
107
+ base: `/sets/${manifest.id}`,
108
+ installedAt: extra.installedAt,
109
+ ...extra.hasGuide ? { hasGuide: true } : {}
110
+ };
111
+ }
112
+ export {
113
+ PACK_EXT,
114
+ PACK_FORMAT_VERSION,
115
+ PLATFORM_ABI,
116
+ REQUIRED_PACK_FILES,
117
+ SUPPORTED_PACK_FORMATS,
118
+ compareVersions,
119
+ installedSetOf,
120
+ majorOf,
121
+ manifestProblems,
122
+ packProvides,
123
+ requiredPackFiles,
124
+ reservedIds,
125
+ signingPayload
126
+ };
@@ -0,0 +1,5 @@
1
+ {
2
+ "app": "0.5.0",
3
+ "commit": "12bedfc",
4
+ "emittedAt": "2026-09-13T17:50:33.133Z"
5
+ }
@@ -0,0 +1,13 @@
1
+ // Injected in place of `react/jsx-runtime` and `react/jsx-dev-runtime`.
2
+ // See tools/set-shims/react.js for why a pack may not bundle its own.
3
+ const runtime = globalThis.__FBK_SET_RUNTIME__;
4
+ if (!runtime) {
5
+ throw new Error('This theme was loaded without the Folien Baukasten set runtime.');
6
+ }
7
+ const jsxRuntime = runtime.jsxRuntime;
8
+ export const Fragment = jsxRuntime.Fragment;
9
+ export const jsx = jsxRuntime.jsx;
10
+ export const jsxs = jsxRuntime.jsxs;
11
+ // The dev runtime's extra arguments are ignored by the production one, which
12
+ // is what a pack is always built against.
13
+ export const jsxDEV = jsxRuntime.jsx;
@@ -0,0 +1,48 @@
1
+ // Injected in place of `@folienbaukasten/platform` — the surface a theme pack
2
+ // is allowed to build against (src/sets/platform.ts). Resolved at load time
3
+ // from the app's own module instances, so a pack shares the platform's React
4
+ // components rather than carrying a snapshot of them.
5
+ //
6
+ // The lists below have to name every VALUE export of src/sets/platform.ts —
7
+ // ESM named exports cannot be spread from a runtime object. Miss one and
8
+ // `npm run pack:set` fails on that name, which is the intended failure: it
9
+ // is loud, and it happens to whoever added the export.
10
+ const runtime = globalThis.__FBK_SET_RUNTIME__;
11
+ if (!runtime) {
12
+ throw new Error('This theme was loaded without the Folien Baukasten set runtime.');
13
+ }
14
+ const platform = runtime.platform;
15
+
16
+ // Every value export of src/sets/platform.ts, named so it can be re-exported.
17
+ const NAMES = [
18
+ 'Block', 'BlockArrow', 'Card', 'ChartView', 'ContentSlide', 'DriverTree',
19
+ 'FindingsBanner', 'HarveyBall', 'HARVEY_SIZE_PX', 'HARVEY_STROKE_PX',
20
+ 'InsightRail', 'KpiTile', 'Matrix', 'QuoteColumn', 'SlideFrame',
21
+ 'SourceLine', 'Table', 'Timeline', 'formatNumber', 'headerTitle',
22
+ 'platformDefaults', 'SLIDE_HEIGHT', 'SLIDE_WIDTH',
23
+ 'bandCentre', 'bandCentreX', 'bandCentreY', 'spreadLabels', 'valueY',
24
+ 'LABEL_MIN_GAP',
25
+ ];
26
+
27
+ // A theme built against a NEWER app than the one loading it destructures
28
+ // `undefined` here and dies later, inside React, as a blank slide with no
29
+ // clue attached. Fail at load instead, naming what is missing: the loader
30
+ // already has a path for a theme that will not load (the deck renders in
31
+ // the default theme and the slides say so), and "the app is older than the
32
+ // theme" is something a person can act on.
33
+ const missing = NAMES.filter((name) => platform[name] === undefined);
34
+ if (missing.length > 0) {
35
+ throw new Error(
36
+ `This theme needs platform features this app does not have: ${missing.join(', ')}. ` +
37
+ 'The app is older than the theme — update the app, or install a build of the ' +
38
+ 'theme made for it.',
39
+ );
40
+ }
41
+
42
+ export const {
43
+ Block, BlockArrow, Card, ChartView, ContentSlide, DriverTree, FindingsBanner,
44
+ HarveyBall, HARVEY_SIZE_PX, HARVEY_STROKE_PX, InsightRail, KpiTile, Matrix,
45
+ QuoteColumn, SlideFrame, SourceLine, Table, Timeline, formatNumber,
46
+ headerTitle, platformDefaults, SLIDE_HEIGHT, SLIDE_WIDTH,
47
+ bandCentre, bandCentreX, bandCentreY, spreadLabels, valueY, LABEL_MIN_GAP,
48
+ } = platform;