@protoyard/blueprint 0.0.0-stage → 1.1.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 (168) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1149 -2
  3. package/dist/aggregate.d.ts +83 -0
  4. package/dist/aggregate.js +268 -0
  5. package/dist/app-model.d.ts +76 -0
  6. package/dist/app-model.js +287 -0
  7. package/dist/archetypes/index.d.ts +1 -0
  8. package/dist/archetypes/index.js +1 -0
  9. package/dist/archetypes/schema.d.ts +126 -0
  10. package/dist/archetypes/schema.js +77 -0
  11. package/dist/audit.d.ts +52 -0
  12. package/dist/audit.js +149 -0
  13. package/dist/authoring/deck-lint.d.ts +32 -0
  14. package/dist/authoring/deck-lint.js +567 -0
  15. package/dist/authoring/index.d.ts +1 -0
  16. package/dist/authoring/index.js +1 -0
  17. package/dist/authoring/select-deck-layouts.d.ts +50 -0
  18. package/dist/authoring/select-deck-layouts.js +213 -0
  19. package/dist/authoring-apis.d.ts +13 -0
  20. package/dist/authoring-apis.js +13 -0
  21. package/dist/authoring-validation.d.ts +65 -0
  22. package/dist/authoring-validation.js +267 -0
  23. package/dist/avatar.d.ts +25 -0
  24. package/dist/avatar.js +30 -0
  25. package/dist/brand.d.ts +70 -0
  26. package/dist/brand.js +294 -0
  27. package/dist/brief/archetypes.d.ts +28 -0
  28. package/dist/brief/archetypes.js +798 -0
  29. package/dist/brief/content-synthesize.d.ts +31 -0
  30. package/dist/brief/content-synthesize.js +210 -0
  31. package/dist/brief/deck-outlines.d.ts +96 -0
  32. package/dist/brief/deck-outlines.js +561 -0
  33. package/dist/brief/draft-bodies.d.ts +29 -0
  34. package/dist/brief/draft-bodies.js +224 -0
  35. package/dist/brief/fields.d.ts +19 -0
  36. package/dist/brief/fields.js +229 -0
  37. package/dist/brief/index.d.ts +8 -0
  38. package/dist/brief/index.js +8 -0
  39. package/dist/brief/kind-detection.d.ts +69 -0
  40. package/dist/brief/kind-detection.js +353 -0
  41. package/dist/brief/limits.d.ts +117 -0
  42. package/dist/brief/limits.js +217 -0
  43. package/dist/brief/page-doc-outlines.d.ts +80 -0
  44. package/dist/brief/page-doc-outlines.js +485 -0
  45. package/dist/brief/synthesize.d.ts +59 -0
  46. package/dist/brief/synthesize.js +511 -0
  47. package/dist/brief/text.d.ts +13 -0
  48. package/dist/brief/text.js +59 -0
  49. package/dist/canvas-screen.d.ts +31 -0
  50. package/dist/canvas-screen.js +33 -0
  51. package/dist/canvas-seed.d.ts +58 -0
  52. package/dist/canvas-seed.js +82 -0
  53. package/dist/computed/evaluate.d.ts +11 -0
  54. package/dist/computed/evaluate.js +101 -0
  55. package/dist/computed/index.d.ts +4 -0
  56. package/dist/computed/index.js +3 -0
  57. package/dist/computed/public.d.ts +14 -0
  58. package/dist/computed/public.js +19 -0
  59. package/dist/computed/schema.d.ts +10 -0
  60. package/dist/computed/schema.js +140 -0
  61. package/dist/computed/types.d.ts +70 -0
  62. package/dist/computed/types.js +1 -0
  63. package/dist/computed/validate.d.ts +6 -0
  64. package/dist/computed/validate.js +146 -0
  65. package/dist/csv-guard.d.ts +15 -0
  66. package/dist/csv-guard.js +29 -0
  67. package/dist/csv.d.ts +85 -0
  68. package/dist/csv.js +288 -0
  69. package/dist/files.d.ts +35 -0
  70. package/dist/files.js +246 -0
  71. package/dist/form-schema.d.ts +124 -0
  72. package/dist/form-schema.js +300 -0
  73. package/dist/generator-version.d.ts +11 -0
  74. package/dist/generator-version.js +51 -0
  75. package/dist/generator.d.ts +34 -0
  76. package/dist/generator.js +445 -0
  77. package/dist/identity.d.ts +34 -0
  78. package/dist/identity.js +91 -0
  79. package/dist/import/csv-shape.d.ts +40 -0
  80. package/dist/import/csv-shape.js +265 -0
  81. package/dist/import/json-shape.d.ts +55 -0
  82. package/dist/import/json-shape.js +491 -0
  83. package/dist/import/limits.d.ts +13 -0
  84. package/dist/import/limits.js +14 -0
  85. package/dist/import/sqlite-schema.d.ts +36 -0
  86. package/dist/import/sqlite-schema.js +729 -0
  87. package/dist/index.d.ts +60 -0
  88. package/dist/index.js +58 -0
  89. package/dist/kinds/anchors.d.ts +18 -0
  90. package/dist/kinds/anchors.js +46 -0
  91. package/dist/kinds/bodies.d.ts +6 -0
  92. package/dist/kinds/bodies.js +32 -0
  93. package/dist/kinds/brand-runtime.d.ts +19 -0
  94. package/dist/kinds/brand-runtime.js +35 -0
  95. package/dist/kinds/deck.d.ts +8 -0
  96. package/dist/kinds/deck.js +34 -0
  97. package/dist/kinds/doc.d.ts +19 -0
  98. package/dist/kinds/doc.js +41 -0
  99. package/dist/kinds/embed/index.d.ts +39 -0
  100. package/dist/kinds/embed/index.js +91 -0
  101. package/dist/kinds/envelope.d.ts +159 -0
  102. package/dist/kinds/envelope.js +78 -0
  103. package/dist/kinds/index.d.ts +11 -0
  104. package/dist/kinds/index.js +11 -0
  105. package/dist/kinds/kind.d.ts +36 -0
  106. package/dist/kinds/kind.js +55 -0
  107. package/dist/kinds/limits.d.ts +64 -0
  108. package/dist/kinds/limits.js +190 -0
  109. package/dist/kinds/page-doc-runtime.d.ts +37 -0
  110. package/dist/kinds/page-doc-runtime.js +55 -0
  111. package/dist/kinds/page.d.ts +16 -0
  112. package/dist/kinds/page.js +67 -0
  113. package/dist/kinds/parse.d.ts +18 -0
  114. package/dist/kinds/parse.js +27 -0
  115. package/dist/kinds/runtime.d.ts +128 -0
  116. package/dist/kinds/runtime.js +206 -0
  117. package/dist/kinds/services.d.ts +49 -0
  118. package/dist/kinds/services.js +249 -0
  119. package/dist/kinds/testing.d.ts +9 -0
  120. package/dist/kinds/testing.js +16 -0
  121. package/dist/migrations.d.ts +97 -0
  122. package/dist/migrations.js +351 -0
  123. package/dist/motion.d.ts +50 -0
  124. package/dist/motion.js +52 -0
  125. package/dist/notifications.d.ts +19 -0
  126. package/dist/notifications.js +177 -0
  127. package/dist/patch.d.ts +122 -0
  128. package/dist/patch.js +322 -0
  129. package/dist/policy.d.ts +23 -0
  130. package/dist/policy.js +105 -0
  131. package/dist/runtime-definition.d.ts +39 -0
  132. package/dist/runtime-definition.js +74 -0
  133. package/dist/scenarios.d.ts +9 -0
  134. package/dist/scenarios.js +49 -0
  135. package/dist/schema/blueprint.schema.json +10191 -0
  136. package/dist/schema-json.d.ts +6 -0
  137. package/dist/schema-json.js +36 -0
  138. package/dist/schema.d.ts +6348 -0
  139. package/dist/schema.js +1269 -0
  140. package/dist/search.d.ts +16 -0
  141. package/dist/search.js +96 -0
  142. package/dist/services.d.ts +89 -0
  143. package/dist/services.js +898 -0
  144. package/dist/share.d.ts +78 -0
  145. package/dist/share.js +234 -0
  146. package/dist/sheet-screen.d.ts +49 -0
  147. package/dist/sheet-screen.js +91 -0
  148. package/dist/storage.d.ts +43 -0
  149. package/dist/storage.js +179 -0
  150. package/dist/stream.d.ts +26 -0
  151. package/dist/stream.js +173 -0
  152. package/dist/untrusted.d.ts +63 -0
  153. package/dist/untrusted.js +502 -0
  154. package/dist/validation-tasks.d.ts +178 -0
  155. package/dist/validation-tasks.js +199 -0
  156. package/dist/validation.d.ts +35 -0
  157. package/dist/validation.js +312 -0
  158. package/dist/variants.d.ts +112 -0
  159. package/dist/variants.js +283 -0
  160. package/dist/workflows/index.d.ts +3 -0
  161. package/dist/workflows/index.js +3 -0
  162. package/dist/workflows/schema.d.ts +50 -0
  163. package/dist/workflows/schema.js +34 -0
  164. package/dist/workflows/transition.d.ts +15 -0
  165. package/dist/workflows/transition.js +25 -0
  166. package/dist/workflows/validate.d.ts +17 -0
  167. package/dist/workflows/validate.js +140 -0
  168. package/package.json +120 -3
@@ -0,0 +1,55 @@
1
+ import { defineKindPrototype, KIND_DEFAULT_SCENARIO, KIND_GENERATOR_VERSION, } from "./runtime.js";
2
+ /** Kind default scenario id (shared content-kind runtime). */
3
+ export const PAGES_DEFAULT_SCENARIO = KIND_DEFAULT_SCENARIO;
4
+ /** Kind generator version (shared content-kind runtime). */
5
+ export const PAGES_GENERATOR_VERSION = KIND_GENERATOR_VERSION;
6
+ /**
7
+ * Builds the shared content-kind runtime for a page document body by wrapping
8
+ * it as a `kind: "page"` blueprint and calling {@link defineKindPrototype}.
9
+ * The wrapper has no `brand` (the default theme), so it never loads brand
10
+ * resolution or the palette generator a seed brand needs.
11
+ * Lives in `@protoyard/blueprint/kinds` so `@protoyard/pages` stays free of a
12
+ * blueprint dependency (no package cycle).
13
+ */
14
+ export function definePrototypeFromPage(page, options) {
15
+ return defineKindPrototype(pageBlueprint(page, options), toKindOptions(options));
16
+ }
17
+ /**
18
+ * Builds the shared content-kind runtime for a doc document body.
19
+ */
20
+ export function definePrototypeFromDoc(doc, options) {
21
+ return defineKindPrototype(docBlueprint(doc, options), toKindOptions(options));
22
+ }
23
+ function toKindOptions(options) {
24
+ return {
25
+ namespace: options.namespace,
26
+ slug: options.slug,
27
+ version: options.version,
28
+ defaultThemeId: null,
29
+ brandTheme: null,
30
+ };
31
+ }
32
+ function pageBlueprint(page, options) {
33
+ return {
34
+ formatVersion: 1,
35
+ name: options.name ?? page.title,
36
+ description: options.description ?? page.description ?? '',
37
+ tags: options.tags ?? ['page'],
38
+ ...(options.category ? { category: options.category } : {}),
39
+ ...(options.icon ? { icon: options.icon } : {}),
40
+ kind: 'page',
41
+ page,
42
+ };
43
+ }
44
+ function docBlueprint(doc, options) {
45
+ return {
46
+ formatVersion: 1,
47
+ name: options.name ?? doc.title,
48
+ description: options.description ?? doc.description ?? '',
49
+ tags: options.tags ?? ['doc'],
50
+ ...(options.category ? { category: options.category } : {}),
51
+ ...(options.icon ? { icon: options.icon } : {}),
52
+ kind: 'doc',
53
+ doc,
54
+ };
55
+ }
@@ -0,0 +1,16 @@
1
+ import { type PageDocument } from '@protoyard/pages';
2
+ import type { z } from 'zod';
3
+ /**
4
+ * The `kind: "page"` body (ADR-036, ADR-039): `@protoyard/pages`'
5
+ * `PageDocumentSchema` (sections of blocks, nav, form refs; unique section ids
6
+ * and anchors, nav and `forms[].formId` resolved) plus the checks that span
7
+ * the whole page:
8
+ *
9
+ * - block ids, headings included, are unique across all sections (a page is
10
+ * one DOM and one form-response store, so `#id` and form ids must be too);
11
+ * - a block id never equals a section id or anchor (both are hash targets);
12
+ * - `forms[].sectionId`, when set, is the section that holds the form;
13
+ * - every `#fragment` link in a block (`cta`, `cards`) points at a section
14
+ * anchor or a block id on the page.
15
+ */
16
+ export declare const PageBodySchema: z.ZodType<PageDocument>;
@@ -0,0 +1,67 @@
1
+ import { forEachBlock } from '@protoyard/blocks';
2
+ import { PageDocumentSchema } from '@protoyard/pages';
3
+ import { blockIds, checkFragmentLinks, fragmentLinks } from "./anchors.js";
4
+ /**
5
+ * The `kind: "page"` body (ADR-036, ADR-039): `@protoyard/pages`'
6
+ * `PageDocumentSchema` (sections of blocks, nav, form refs; unique section ids
7
+ * and anchors, nav and `forms[].formId` resolved) plus the checks that span
8
+ * the whole page:
9
+ *
10
+ * - block ids, headings included, are unique across all sections (a page is
11
+ * one DOM and one form-response store, so `#id` and form ids must be too);
12
+ * - a block id never equals a section id or anchor (both are hash targets);
13
+ * - `forms[].sectionId`, when set, is the section that holds the form;
14
+ * - every `#fragment` link in a block (`cta`, `cards`) points at a section
15
+ * anchor or a block id on the page.
16
+ */
17
+ export const PageBodySchema = PageDocumentSchema.superRefine((page, ctx) => {
18
+ const sectionTargets = new Set();
19
+ for (const section of page.sections) {
20
+ sectionTargets.add(section.id);
21
+ sectionTargets.add(section.anchor ?? section.id);
22
+ }
23
+ const seen = new Map();
24
+ const blockTargets = new Set();
25
+ const formSection = new Map();
26
+ page.sections.forEach((section, s) => {
27
+ for (const { id, path } of blockIds(section.blocks, ['sections', s, 'blocks'])) {
28
+ blockTargets.add(id);
29
+ const first = seen.get(id);
30
+ if (first !== undefined) {
31
+ ctx.addIssue({
32
+ code: 'custom',
33
+ path,
34
+ message: `Block id "${id}" is already used in section "${first}"; block ids must be unique on a page`,
35
+ });
36
+ }
37
+ else {
38
+ seen.set(id, section.id);
39
+ }
40
+ if (sectionTargets.has(id)) {
41
+ ctx.addIssue({
42
+ code: 'custom',
43
+ path,
44
+ message: `Block id "${id}" is also a section id or anchor; use a different id`,
45
+ });
46
+ }
47
+ }
48
+ // Forms anywhere in the section, containers included.
49
+ forEachBlock(section.blocks, (block) => {
50
+ if (block.type === 'form' && !formSection.has(block.id))
51
+ formSection.set(block.id, section.id);
52
+ });
53
+ });
54
+ page.forms?.forEach((ref, i) => {
55
+ if (ref.sectionId === undefined)
56
+ return;
57
+ const holder = formSection.get(ref.formId);
58
+ if (holder !== undefined && holder !== ref.sectionId) {
59
+ ctx.addIssue({
60
+ code: 'custom',
61
+ path: ['forms', i, 'sectionId'],
62
+ message: `Form "${ref.formId}" is in section "${holder}", not "${ref.sectionId}"`,
63
+ });
64
+ }
65
+ });
66
+ checkFragmentLinks(fragmentLinks(page.sections, ['sections']), new Set([...sectionTargets, ...blockTargets]), ctx, 'a section anchor or block id on this page');
67
+ });
@@ -0,0 +1,18 @@
1
+ import { type DeckBlueprint, type DocBlueprint, type PageBlueprint } from './envelope.ts';
2
+ /**
3
+ * `@protoyard/blueprint/kinds/parse` — synchronous parsers for one content
4
+ * kind, for code that already knows its kind and ships its body schema
5
+ * anyway: a generated deck, page or doc prototype. Each validates exactly
6
+ * what `parseAnyBlueprint` validates for that kind (the same envelope and body
7
+ * schema) and throws a `ZodError` on failure.
8
+ *
9
+ * `parseAnyBlueprint` loads the body schema on demand. Awaiting it at a
10
+ * module's top level deadlocks a single-chunk build (the Claude artifact
11
+ * target): the dynamic import resolves to a facade that re-imports the chunk
12
+ * still waiting on it. These parsers import the schema statically instead.
13
+ * The package has no side effects, so a deck prototype keeps only the deck
14
+ * schema. The universal player never imports this module.
15
+ */
16
+ export declare function parseDeckBlueprint(input: unknown): DeckBlueprint;
17
+ export declare function parsePageBlueprint(input: unknown): PageBlueprint;
18
+ export declare function parseDocBlueprint(input: unknown): DocBlueprint;
@@ -0,0 +1,27 @@
1
+ import { DeckBodySchema } from "./deck.js";
2
+ import { DocBodySchema } from "./doc.js";
3
+ import { contentBlueprintSchema, } from "./envelope.js";
4
+ import { PageBodySchema } from "./page.js";
5
+ /**
6
+ * `@protoyard/blueprint/kinds/parse` — synchronous parsers for one content
7
+ * kind, for code that already knows its kind and ships its body schema
8
+ * anyway: a generated deck, page or doc prototype. Each validates exactly
9
+ * what `parseAnyBlueprint` validates for that kind (the same envelope and body
10
+ * schema) and throws a `ZodError` on failure.
11
+ *
12
+ * `parseAnyBlueprint` loads the body schema on demand. Awaiting it at a
13
+ * module's top level deadlocks a single-chunk build (the Claude artifact
14
+ * target): the dynamic import resolves to a facade that re-imports the chunk
15
+ * still waiting on it. These parsers import the schema statically instead.
16
+ * The package has no side effects, so a deck prototype keeps only the deck
17
+ * schema. The universal player never imports this module.
18
+ */
19
+ export function parseDeckBlueprint(input) {
20
+ return contentBlueprintSchema('deck', DeckBodySchema).parse(input);
21
+ }
22
+ export function parsePageBlueprint(input) {
23
+ return contentBlueprintSchema('page', PageBodySchema).parse(input);
24
+ }
25
+ export function parseDocBlueprint(input) {
26
+ return contentBlueprintSchema('doc', DocBodySchema).parse(input);
27
+ }
@@ -0,0 +1,128 @@
1
+ import { type PolicyDefinition, type PrototypeDefinition, type Scenario, type ScenarioDefinition, type Theme, type WorldSeed } from '@protoyard/core';
2
+ import type { ContentBlueprint } from './envelope.ts';
3
+ import type { ContentKind } from './kind.ts';
4
+ /**
5
+ * The runtime definition of a content-kind blueprint (deck, page, doc;
6
+ * ADR-036): what `definePrototypeFromBlueprint` is for apps, without the
7
+ * entity derivations. Both the player and generated kind prototypes use it,
8
+ * so a deck behaves the same at both levels.
9
+ *
10
+ * - **Storage:** three stores: `blockStates` (interactive block state, keyed
11
+ * by `stateKey(documentId, blockId)`), `formResponses` (submitted form
12
+ * blocks) and, from database version 2, `docEdits` (a doc's saved edits,
13
+ * `@protoyard/pages` `createDocEditServices`). Writes go through services,
14
+ * never components.
15
+ * - **World:** one `default` scenario and three simulated personas, a
16
+ * presenter, an audience member and a viewer, built from fixed data (no
17
+ * world generator, no Faker). `generateWorld` makes the runtime skip the
18
+ * generation context entirely.
19
+ * - **Policy:** presenters interact, answer and read every response;
20
+ * audience members interact and answer; viewers only look. Simulated, not
21
+ * security.
22
+ */
23
+ /** The scenario every content kind opens in (the only one it declares). */
24
+ export declare const KIND_DEFAULT_SCENARIO = "default";
25
+ /** Bump when the kind world (personas, organization) can change for the same scenario. */
26
+ export declare const KIND_GENERATOR_VERSION = 1;
27
+ /** The Dexie stores of a content-kind prototype. */
28
+ export declare const KIND_STORES: Readonly<{
29
+ /** Interactive block state: `key` is `stateKey(documentId, blockId)`. */
30
+ blockStates: "key, documentId, blockId, updatedAt";
31
+ /**
32
+ * Submitted form blocks — same row contract and indexes as
33
+ * `@protoyard/pages` `FORM_RESPONSES_STORE_STRING` (lead form contract).
34
+ */
35
+ formResponses: "id, formId, createdAt, personaId, documentId, [formId+createdAt]";
36
+ /**
37
+ * Saved doc edits, one row per doc and world — same row contract as
38
+ * `@protoyard/pages` `DOC_EDITS_STORE_STRING` (PLAN_008 decision D3).
39
+ */
40
+ docEdits: "key, documentId, docId, updatedAt";
41
+ }>;
42
+ /**
43
+ * The database versions of a content-kind prototype. Version 1 had no
44
+ * `docEdits`; version 2 adds it, so a browser that holds version 1 data
45
+ * upgrades in place and keeps its block states and form responses.
46
+ */
47
+ export declare const KIND_DATABASE_VERSIONS: readonly ({
48
+ version: number;
49
+ stores: {
50
+ blockStates: "key, documentId, blockId, updatedAt";
51
+ formResponses: "id, formId, createdAt, personaId, documentId, [formId+createdAt]";
52
+ };
53
+ } | {
54
+ version: number;
55
+ stores: {
56
+ blockStates: "key, documentId, blockId, updatedAt";
57
+ formResponses: "id, formId, createdAt, personaId, documentId, [formId+createdAt]";
58
+ docEdits: "key, documentId, docId, updatedAt";
59
+ };
60
+ })[];
61
+ /** The latest kind database version (the manifest's `storage.schemaVersion`). */
62
+ export declare const KIND_SCHEMA_VERSION = 2;
63
+ /**
64
+ * Capabilities the kind services assert (simulated policy, not security).
65
+ * Form caps are the canonical `@protoyard/pages` FORM_CAPABILITIES strings so
66
+ * createFormServices and createKindServices share one policy vocabulary.
67
+ */
68
+ export declare const KIND_CAPABILITIES: Readonly<{
69
+ /** Vote, answer, change a calculator, open a tab… on an interactive block. */
70
+ interact: "blocks:interact";
71
+ /** Submit a form block (`form:submit`). */
72
+ respond: "form:submit";
73
+ /** Read form responses (`form:read`). */
74
+ readResponses: "form:read";
75
+ /** Clear / administer responses (`form:admin`). */
76
+ adminResponses: "form:admin";
77
+ }>;
78
+ /** The simulated roles of a content-kind prototype. */
79
+ export declare const KIND_ROLES: Readonly<{
80
+ /** Presenter: interact, submit/read forms, and administer responses. */
81
+ presenter: ("blocks:interact" | "form:submit" | "form:read" | "form:admin")[];
82
+ /** Audience: interact and submit/read forms. */
83
+ audience: ("blocks:interact" | "form:submit" | "form:read")[];
84
+ /** Viewer: submit and read forms (no interactive block mutations). */
85
+ viewer: ("form:submit" | "form:read")[];
86
+ }>;
87
+ export type KindRole = keyof typeof KIND_ROLES;
88
+ /** Fixed persona ids (valid block viewer ids). */
89
+ export declare const KIND_PERSONAS: Readonly<{
90
+ presenter: string;
91
+ audience: string;
92
+ viewer: string;
93
+ }>;
94
+ export interface KindRuntimeOptions {
95
+ /** Storage namespace (IndexedDB names): usually the content namespace (`bp-…`). */
96
+ namespace: string;
97
+ /** Manifest slug; defaults to the namespace. */
98
+ slug?: string;
99
+ /** Manifest version (default `0.1.0`). */
100
+ version?: string;
101
+ }
102
+ /** {@link defineKindPrototype} options: the blueprint brand, already resolved. */
103
+ export interface KindPrototypeOptions extends KindRuntimeOptions {
104
+ /** The theme the prototype opens in (`kindDefaultThemeId`); `null` for the default theme. */
105
+ defaultThemeId: string | null;
106
+ /** The built brand theme to register (`kindBrandTheme`); `null` for none or a library brand. */
107
+ brandTheme: Theme | null;
108
+ }
109
+ /** Any parsed content-kind blueprint. */
110
+ /** One content blueprint of any kind (a union over kinds, not an intersection of bodies). */
111
+ export type AnyContentBlueprint = {
112
+ [K in ContentKind]: ContentBlueprint<K>;
113
+ }[ContentKind];
114
+ export declare function kindScenarios(): ScenarioDefinition[];
115
+ export declare function kindPolicy(): PolicyDefinition;
116
+ /**
117
+ * The kind world for a scenario: an organization and the three personas,
118
+ * from fixed data and the scenario's reference time only, so the same
119
+ * scenario always gives the same world.
120
+ */
121
+ export declare function kindWorld(scenario: Pick<Scenario, 'referenceTime' | 'organization'>): WorldSeed;
122
+ /**
123
+ * Builds a `definePrototype`-valid definition for a deck, page or doc whose
124
+ * brand is already resolved. Generated kind prototypes call it with the theme
125
+ * `protoyard generate` resolved, so their first chunk never loads the palette
126
+ * generator a seed brand needs; `defineKindRuntime` resolves the brand first.
127
+ */
128
+ export declare function defineKindPrototype(doc: AnyContentBlueprint, options: KindPrototypeOptions): PrototypeDefinition;
@@ -0,0 +1,206 @@
1
+ import { definePrototype, parseManifest, } from '@protoyard/core';
2
+ import { DOC_EDITS_STORE_STRING, FORM_CAPABILITIES, FORM_RESPONSES_STORE_STRING, } from '@protoyard/pages';
3
+ /**
4
+ * The runtime definition of a content-kind blueprint (deck, page, doc;
5
+ * ADR-036): what `definePrototypeFromBlueprint` is for apps, without the
6
+ * entity derivations. Both the player and generated kind prototypes use it,
7
+ * so a deck behaves the same at both levels.
8
+ *
9
+ * - **Storage:** three stores: `blockStates` (interactive block state, keyed
10
+ * by `stateKey(documentId, blockId)`), `formResponses` (submitted form
11
+ * blocks) and, from database version 2, `docEdits` (a doc's saved edits,
12
+ * `@protoyard/pages` `createDocEditServices`). Writes go through services,
13
+ * never components.
14
+ * - **World:** one `default` scenario and three simulated personas, a
15
+ * presenter, an audience member and a viewer, built from fixed data (no
16
+ * world generator, no Faker). `generateWorld` makes the runtime skip the
17
+ * generation context entirely.
18
+ * - **Policy:** presenters interact, answer and read every response;
19
+ * audience members interact and answer; viewers only look. Simulated, not
20
+ * security.
21
+ */
22
+ /** The scenario every content kind opens in (the only one it declares). */
23
+ export const KIND_DEFAULT_SCENARIO = 'default';
24
+ /** Bump when the kind world (personas, organization) can change for the same scenario. */
25
+ export const KIND_GENERATOR_VERSION = 1;
26
+ /** The Dexie stores of a content-kind prototype. */
27
+ export const KIND_STORES = Object.freeze({
28
+ /** Interactive block state: `key` is `stateKey(documentId, blockId)`. */
29
+ blockStates: 'key, documentId, blockId, updatedAt',
30
+ /**
31
+ * Submitted form blocks — same row contract and indexes as
32
+ * `@protoyard/pages` `FORM_RESPONSES_STORE_STRING` (lead form contract).
33
+ */
34
+ formResponses: FORM_RESPONSES_STORE_STRING,
35
+ /**
36
+ * Saved doc edits, one row per doc and world — same row contract as
37
+ * `@protoyard/pages` `DOC_EDITS_STORE_STRING` (PLAN_008 decision D3).
38
+ */
39
+ docEdits: DOC_EDITS_STORE_STRING,
40
+ });
41
+ /**
42
+ * The database versions of a content-kind prototype. Version 1 had no
43
+ * `docEdits`; version 2 adds it, so a browser that holds version 1 data
44
+ * upgrades in place and keeps its block states and form responses.
45
+ */
46
+ export const KIND_DATABASE_VERSIONS = Object.freeze([
47
+ {
48
+ version: 1,
49
+ stores: { blockStates: KIND_STORES.blockStates, formResponses: KIND_STORES.formResponses },
50
+ },
51
+ { version: 2, stores: { ...KIND_STORES } },
52
+ ]);
53
+ /** The latest kind database version (the manifest's `storage.schemaVersion`). */
54
+ export const KIND_SCHEMA_VERSION = 2;
55
+ /**
56
+ * Capabilities the kind services assert (simulated policy, not security).
57
+ * Form caps are the canonical `@protoyard/pages` FORM_CAPABILITIES strings so
58
+ * createFormServices and createKindServices share one policy vocabulary.
59
+ */
60
+ export const KIND_CAPABILITIES = Object.freeze({
61
+ /** Vote, answer, change a calculator, open a tab… on an interactive block. */
62
+ interact: 'blocks:interact',
63
+ /** Submit a form block (`form:submit`). */
64
+ respond: FORM_CAPABILITIES.submit,
65
+ /** Read form responses (`form:read`). */
66
+ readResponses: FORM_CAPABILITIES.read,
67
+ /** Clear / administer responses (`form:admin`). */
68
+ adminResponses: FORM_CAPABILITIES.admin,
69
+ });
70
+ /** The simulated roles of a content-kind prototype. */
71
+ export const KIND_ROLES = Object.freeze({
72
+ /** Presenter: interact, submit/read forms, and administer responses. */
73
+ presenter: [
74
+ KIND_CAPABILITIES.interact,
75
+ KIND_CAPABILITIES.respond,
76
+ KIND_CAPABILITIES.readResponses,
77
+ KIND_CAPABILITIES.adminResponses,
78
+ ],
79
+ /** Audience: interact and submit/read forms. */
80
+ audience: [
81
+ KIND_CAPABILITIES.interact,
82
+ KIND_CAPABILITIES.respond,
83
+ KIND_CAPABILITIES.readResponses,
84
+ ],
85
+ /** Viewer: submit and read forms (no interactive block mutations). */
86
+ viewer: [KIND_CAPABILITIES.respond, KIND_CAPABILITIES.readResponses],
87
+ });
88
+ /** Fixed persona ids (valid block viewer ids). */
89
+ export const KIND_PERSONAS = Object.freeze({
90
+ presenter: 'presenter',
91
+ audience: 'audience',
92
+ viewer: 'viewer',
93
+ });
94
+ const ORGANIZATION_ID = 'org_kind';
95
+ export function kindScenarios() {
96
+ return [
97
+ {
98
+ id: KIND_DEFAULT_SCENARIO,
99
+ name: 'Default',
100
+ description: 'The presenter with an audience member and a viewer to switch to.',
101
+ seed: 136_001,
102
+ referenceTime: '2026-06-01T09:00:00.000Z',
103
+ density: 'empty',
104
+ organization: { id: ORGANIZATION_ID, size: 3, ageDays: 30, subscription: 'free' },
105
+ actor: { id: KIND_PERSONAS.presenter, role: 'presenter', onboardingComplete: true },
106
+ },
107
+ ];
108
+ }
109
+ export function kindPolicy() {
110
+ return {
111
+ roles: Object.fromEntries(Object.entries(KIND_ROLES).map(([role, caps]) => [role, [...caps]])),
112
+ };
113
+ }
114
+ const PEOPLE = [
115
+ { role: 'presenter', name: 'Rowan Presenter', title: 'Presenter', avatar: 'av-012' },
116
+ { role: 'audience', name: 'Sam Audience', title: 'Audience member', avatar: 'av-047' },
117
+ { role: 'viewer', name: 'Vera Viewer', title: 'Viewer (read only)', avatar: 'av-103' },
118
+ ];
119
+ /**
120
+ * The kind world for a scenario: an organization and the three personas,
121
+ * from fixed data and the scenario's reference time only, so the same
122
+ * scenario always gives the same world.
123
+ */
124
+ export function kindWorld(scenario) {
125
+ const joinedAt = new Date(Date.parse(scenario.referenceTime) - 30 * 86_400_000).toISOString();
126
+ const organizationId = scenario.organization?.id ?? ORGANIZATION_ID;
127
+ const organization = {
128
+ id: organizationId,
129
+ name: 'Example audience',
130
+ type: 'audience',
131
+ plan: 'free',
132
+ billingStatus: 'none',
133
+ createdAt: joinedAt,
134
+ size: PEOPLE.length,
135
+ featureFlags: {},
136
+ };
137
+ const personas = PEOPLE.map((person) => ({
138
+ id: KIND_PERSONAS[person.role],
139
+ name: person.name,
140
+ email: `${person.role}@example.com`,
141
+ avatar: person.avatar,
142
+ title: person.title,
143
+ locale: 'en-US',
144
+ timezone: 'UTC',
145
+ joinedAt,
146
+ onboardingComplete: true,
147
+ preferences: {},
148
+ summary: `${person.title}. Simulated persona.`,
149
+ }));
150
+ const memberships = PEOPLE.map((person) => ({
151
+ id: `m_${person.role}`,
152
+ personaId: KIND_PERSONAS[person.role],
153
+ organizationId,
154
+ role: person.role,
155
+ joinedAt,
156
+ capabilities: [],
157
+ }));
158
+ return {
159
+ organizations: [organization],
160
+ personas,
161
+ memberships,
162
+ tables: { blockStates: [], formResponses: [], docEdits: [] },
163
+ };
164
+ }
165
+ /**
166
+ * Builds a `definePrototype`-valid definition for a deck, page or doc whose
167
+ * brand is already resolved. Generated kind prototypes call it with the theme
168
+ * `protoyard generate` resolved, so their first chunk never loads the palette
169
+ * generator a seed brand needs; `defineKindRuntime` resolves the brand first.
170
+ */
171
+ export function defineKindPrototype(doc, options) {
172
+ const slug = options.slug ?? options.namespace;
173
+ const theme = options.brandTheme;
174
+ const scenarios = kindScenarios();
175
+ const manifest = parseManifest({
176
+ id: `local.${slug}`,
177
+ slug,
178
+ kind: 'prototype',
179
+ name: doc.name,
180
+ description: doc.description ?? '',
181
+ version: options.version ?? '0.1.0',
182
+ category: doc.category ?? doc.catalog?.category ?? doc.kind,
183
+ tags: doc.tags,
184
+ ...(doc.icon ? { icon: doc.icon } : {}),
185
+ defaultScenario: KIND_DEFAULT_SCENARIO,
186
+ defaultTheme: options.defaultThemeId ?? 'violet',
187
+ preview: { viewport: 'desktop' },
188
+ storage: { namespace: options.namespace, schemaVersion: KIND_SCHEMA_VERSION },
189
+ scenarios: scenarios.map((scenario) => scenario.id),
190
+ });
191
+ return definePrototype({
192
+ manifest,
193
+ database: {
194
+ versions: KIND_DATABASE_VERSIONS.map((entry) => ({
195
+ version: entry.version,
196
+ stores: { ...entry.stores },
197
+ })),
198
+ },
199
+ scenarios,
200
+ policy: kindPolicy(),
201
+ ...(theme ? { themes: [theme] } : {}),
202
+ generate: (ctx) => kindWorld(ctx.scenario),
203
+ generateWorld: async (scenario) => kindWorld(scenario),
204
+ generatorVersion: KIND_GENERATOR_VERSION,
205
+ });
206
+ }
@@ -0,0 +1,49 @@
1
+ import { type Block, type FormBlock } from '@protoyard/blocks';
2
+ import { type BlockStateHost } from '@protoyard/blocks/interactive';
3
+ import { type PrototypeRuntime } from '@protoyard/core';
4
+ import { type FormResponse as PageFormResponse } from '@protoyard/pages';
5
+ import { z } from 'zod';
6
+ import { type AnyContentBlueprint } from './runtime.ts';
7
+ /**
8
+ * Finds a block by id anywhere in a document body (slides, sections, columns,
9
+ * tabs, reveal panels…). Interactive blocks always carry an id, and the
10
+ * document is the one the runtime was defined from, so a caller can never
11
+ * substitute its own block definition.
12
+ */
13
+ export declare function findDocumentBlock(doc: AnyContentBlueprint, blockId: string): Block | null;
14
+ /**
15
+ * One stored form submission — canonical `@protoyard/pages` FormResponse.
16
+ * `createdAt` is the submission time; `personaId` / `documentId` when present.
17
+ */
18
+ export type FormResponse = PageFormResponse;
19
+ /** A form block's fields as a strict zod schema for one submission. */
20
+ export declare function formResponseSchema(form: FormBlock): z.ZodType<FormResponse['values']>;
21
+ /** A refused form submission: the values do not fit the form, or the form does not exist. */
22
+ export declare class FormResponseError extends Error {
23
+ readonly formId: string;
24
+ /** The first message per field name; `''` holds form-level issues such as unknown fields. */
25
+ readonly issues: Record<string, string>;
26
+ constructor(formId: string, message: string, issues?: Record<string, string>);
27
+ }
28
+ export interface KindServicesOptions {
29
+ /** The document id in state keys (default: the runtime's storage namespace). */
30
+ documentId?: string;
31
+ }
32
+ export interface KindServices {
33
+ readonly documentId: string;
34
+ /** The persistent host interactive blocks render from (`read` is a cache of the table). */
35
+ readonly blockStates: BlockStateHost & {
36
+ /** Loads every stored state for this document into the cache and notifies listeners. */
37
+ refresh(): Promise<void>;
38
+ };
39
+ readonly responses: {
40
+ /** Stores one submission for a form block, as the active persona. */
41
+ submit(formId: string, values: unknown): Promise<FormResponse>;
42
+ /** Presenters see every response; everyone else sees their own. */
43
+ list(formId?: string): Promise<FormResponse[]>;
44
+ };
45
+ /** Stops following scenario loads. */
46
+ destroy(): void;
47
+ }
48
+ /** The kind services for a runtime defined by `defineKindRuntime(doc, …)`. */
49
+ export declare function createKindServices(runtime: PrototypeRuntime, doc: AnyContentBlueprint, options?: KindServicesOptions): Promise<KindServices>;