@protoyard/blueprint 0.0.0-stage → 1.1.1
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.
- package/LICENSE +21 -0
- package/README.md +1145 -2
- package/dist/aggregate.d.ts +83 -0
- package/dist/aggregate.js +268 -0
- package/dist/app-model.d.ts +76 -0
- package/dist/app-model.js +287 -0
- package/dist/archetypes/index.d.ts +1 -0
- package/dist/archetypes/index.js +1 -0
- package/dist/archetypes/schema.d.ts +126 -0
- package/dist/archetypes/schema.js +77 -0
- package/dist/audit.d.ts +52 -0
- package/dist/audit.js +149 -0
- package/dist/authoring/deck-lint.d.ts +32 -0
- package/dist/authoring/deck-lint.js +567 -0
- package/dist/authoring/index.d.ts +1 -0
- package/dist/authoring/index.js +1 -0
- package/dist/authoring/select-deck-layouts.d.ts +50 -0
- package/dist/authoring/select-deck-layouts.js +213 -0
- package/dist/authoring-apis.d.ts +13 -0
- package/dist/authoring-apis.js +13 -0
- package/dist/authoring-validation.d.ts +65 -0
- package/dist/authoring-validation.js +267 -0
- package/dist/avatar.d.ts +25 -0
- package/dist/avatar.js +30 -0
- package/dist/brand.d.ts +70 -0
- package/dist/brand.js +294 -0
- package/dist/brief/archetypes.d.ts +28 -0
- package/dist/brief/archetypes.js +798 -0
- package/dist/brief/content-synthesize.d.ts +31 -0
- package/dist/brief/content-synthesize.js +210 -0
- package/dist/brief/deck-outlines.d.ts +96 -0
- package/dist/brief/deck-outlines.js +561 -0
- package/dist/brief/draft-bodies.d.ts +29 -0
- package/dist/brief/draft-bodies.js +224 -0
- package/dist/brief/fields.d.ts +19 -0
- package/dist/brief/fields.js +229 -0
- package/dist/brief/index.d.ts +8 -0
- package/dist/brief/index.js +8 -0
- package/dist/brief/kind-detection.d.ts +69 -0
- package/dist/brief/kind-detection.js +353 -0
- package/dist/brief/limits.d.ts +117 -0
- package/dist/brief/limits.js +217 -0
- package/dist/brief/page-doc-outlines.d.ts +80 -0
- package/dist/brief/page-doc-outlines.js +485 -0
- package/dist/brief/synthesize.d.ts +59 -0
- package/dist/brief/synthesize.js +511 -0
- package/dist/brief/text.d.ts +13 -0
- package/dist/brief/text.js +59 -0
- package/dist/canvas-screen.d.ts +31 -0
- package/dist/canvas-screen.js +33 -0
- package/dist/canvas-seed.d.ts +58 -0
- package/dist/canvas-seed.js +82 -0
- package/dist/computed/evaluate.d.ts +11 -0
- package/dist/computed/evaluate.js +101 -0
- package/dist/computed/index.d.ts +4 -0
- package/dist/computed/index.js +3 -0
- package/dist/computed/public.d.ts +14 -0
- package/dist/computed/public.js +19 -0
- package/dist/computed/schema.d.ts +10 -0
- package/dist/computed/schema.js +140 -0
- package/dist/computed/types.d.ts +70 -0
- package/dist/computed/types.js +1 -0
- package/dist/computed/validate.d.ts +6 -0
- package/dist/computed/validate.js +146 -0
- package/dist/csv-guard.d.ts +15 -0
- package/dist/csv-guard.js +29 -0
- package/dist/csv.d.ts +85 -0
- package/dist/csv.js +288 -0
- package/dist/files.d.ts +35 -0
- package/dist/files.js +246 -0
- package/dist/form-schema.d.ts +124 -0
- package/dist/form-schema.js +300 -0
- package/dist/generator-version.d.ts +11 -0
- package/dist/generator-version.js +51 -0
- package/dist/generator.d.ts +34 -0
- package/dist/generator.js +445 -0
- package/dist/identity.d.ts +34 -0
- package/dist/identity.js +91 -0
- package/dist/import/csv-shape.d.ts +40 -0
- package/dist/import/csv-shape.js +265 -0
- package/dist/import/json-shape.d.ts +55 -0
- package/dist/import/json-shape.js +491 -0
- package/dist/import/limits.d.ts +13 -0
- package/dist/import/limits.js +14 -0
- package/dist/import/sqlite-schema.d.ts +36 -0
- package/dist/import/sqlite-schema.js +729 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.js +58 -0
- package/dist/kinds/anchors.d.ts +18 -0
- package/dist/kinds/anchors.js +46 -0
- package/dist/kinds/bodies.d.ts +6 -0
- package/dist/kinds/bodies.js +32 -0
- package/dist/kinds/brand-runtime.d.ts +19 -0
- package/dist/kinds/brand-runtime.js +35 -0
- package/dist/kinds/deck.d.ts +8 -0
- package/dist/kinds/deck.js +34 -0
- package/dist/kinds/doc.d.ts +19 -0
- package/dist/kinds/doc.js +41 -0
- package/dist/kinds/embed/index.d.ts +39 -0
- package/dist/kinds/embed/index.js +91 -0
- package/dist/kinds/envelope.d.ts +159 -0
- package/dist/kinds/envelope.js +78 -0
- package/dist/kinds/index.d.ts +11 -0
- package/dist/kinds/index.js +11 -0
- package/dist/kinds/kind.d.ts +36 -0
- package/dist/kinds/kind.js +55 -0
- package/dist/kinds/limits.d.ts +64 -0
- package/dist/kinds/limits.js +190 -0
- package/dist/kinds/page-doc-runtime.d.ts +37 -0
- package/dist/kinds/page-doc-runtime.js +55 -0
- package/dist/kinds/page.d.ts +16 -0
- package/dist/kinds/page.js +67 -0
- package/dist/kinds/parse.d.ts +18 -0
- package/dist/kinds/parse.js +27 -0
- package/dist/kinds/runtime.d.ts +128 -0
- package/dist/kinds/runtime.js +206 -0
- package/dist/kinds/services.d.ts +49 -0
- package/dist/kinds/services.js +249 -0
- package/dist/kinds/testing.d.ts +9 -0
- package/dist/kinds/testing.js +16 -0
- package/dist/migrations.d.ts +97 -0
- package/dist/migrations.js +351 -0
- package/dist/motion.d.ts +50 -0
- package/dist/motion.js +52 -0
- package/dist/notifications.d.ts +19 -0
- package/dist/notifications.js +177 -0
- package/dist/patch.d.ts +122 -0
- package/dist/patch.js +322 -0
- package/dist/policy.d.ts +23 -0
- package/dist/policy.js +105 -0
- package/dist/runtime-definition.d.ts +39 -0
- package/dist/runtime-definition.js +74 -0
- package/dist/scenarios.d.ts +9 -0
- package/dist/scenarios.js +49 -0
- package/dist/schema/blueprint.schema.json +10191 -0
- package/dist/schema-json.d.ts +6 -0
- package/dist/schema-json.js +36 -0
- package/dist/schema.d.ts +6348 -0
- package/dist/schema.js +1269 -0
- package/dist/search.d.ts +16 -0
- package/dist/search.js +96 -0
- package/dist/services.d.ts +89 -0
- package/dist/services.js +898 -0
- package/dist/share.d.ts +78 -0
- package/dist/share.js +234 -0
- package/dist/sheet-screen.d.ts +49 -0
- package/dist/sheet-screen.js +91 -0
- package/dist/storage.d.ts +43 -0
- package/dist/storage.js +179 -0
- package/dist/stream.d.ts +26 -0
- package/dist/stream.js +173 -0
- package/dist/untrusted.d.ts +63 -0
- package/dist/untrusted.js +502 -0
- package/dist/validation-tasks.d.ts +178 -0
- package/dist/validation-tasks.js +199 -0
- package/dist/validation.d.ts +35 -0
- package/dist/validation.js +312 -0
- package/dist/variants.d.ts +112 -0
- package/dist/variants.js +283 -0
- package/dist/workflows/index.d.ts +3 -0
- package/dist/workflows/index.js +3 -0
- package/dist/workflows/schema.d.ts +50 -0
- package/dist/workflows/schema.js +34 -0
- package/dist/workflows/transition.d.ts +15 -0
- package/dist/workflows/transition.js +25 -0
- package/dist/workflows/validate.d.ts +17 -0
- package/dist/workflows/validate.js +140 -0
- 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>;
|