@ankhorage/devtools 1.10.10 → 1.10.12
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/dist/tools/skills/assets/ankhorage-project-structure/SKILL.md +21 -32
- package/dist/tools/skills/assets/ankhorage-project-structure/references/studio.md +94 -48
- package/dist/tools/skills/assets/zora-designer/SKILL.md +63 -56
- package/dist/tools/skills/assets/zora-designer/references/artifact.md +6 -4
- package/dist/tools/skills/assets/zora-designer/references/screens.md +90 -0
- package/dist/tools/skills/assets/zora-designer/references/workflow.md +84 -126
- package/dist/tools/skills/assets/zora-designer/scripts/generate-template-catalog.mjs +89 -0
- package/dist/tools/skills/assets/zora-designer/scripts/owner-api.mjs +62 -39
- package/dist/tools/skills/assets/zora-designer/scripts/scaffold-template.mjs +59 -139
- package/package.json +3 -3
- package/dist/tools/skills/assets/ankhorage-project-structure/references/expo-apps.md +0 -43
- package/dist/tools/skills/assets/ankhorage-project-structure/references/hexagonal-architecture.md +0 -120
- package/dist/tools/skills/assets/ankhorage-project-structure/references/repository-profiles.md +0 -91
- package/dist/tools/skills/assets/ankhorage-project-structure/references/ui-libraries.md +0 -79
|
@@ -1,137 +1,95 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
Use this workflow for `interactive`, `screen`, `screens`, and `template`. Audit uses the same
|
|
4
|
-
compiled target baseline when a redesign is requested; it never fills unknown observed values with
|
|
5
|
-
presets and pretends they were measured.
|
|
6
|
-
|
|
7
|
-
## 1. Establish mode, intent, and evidence
|
|
8
|
-
|
|
9
|
-
Record the audience, primary task, requested screen or ordered series, platform, input modes,
|
|
10
|
-
viewport constraints, target theme modes, available source artifacts, and requested deliverable.
|
|
11
|
-
Inspect the current manifest, theme, installed packages, ZORA metadata, fonts, and existing
|
|
12
|
-
`zora-designer.md` before prompting.
|
|
13
|
-
|
|
14
|
-
For an input image or series, decompose each screen into semantic regions. Give each region a stable
|
|
15
|
-
evidence ID, purpose, content/data responsibility, state and interaction needs, accessibility needs,
|
|
16
|
-
and its relationship to other regions. Preserve image order and original dimensions. Do not map by
|
|
17
|
-
appearance alone.
|
|
18
|
-
|
|
19
|
-
## 2. Build the design in dependency order
|
|
20
|
-
|
|
21
|
-
Interactive questions and compiler calls follow this exact order. Skip a question only when the
|
|
22
|
-
answer is already supplied or reliably discovered.
|
|
23
|
-
|
|
24
|
-
1. **Category and intent.** Read `APP_CATEGORIES` and `CATEGORY_PRESETS` from Templates. Show the
|
|
25
|
-
preset's ordered primary-color and font-family recommendations and explain that they are starting
|
|
26
|
-
points, not user stereotypes. Defer font activation until its availability and owning loader are
|
|
27
|
-
verified.
|
|
28
|
-
2. **Primary color.** Offer the preset's first recommended primary color and any verified project
|
|
29
|
-
brand color. Let the user accept or supply a color. The owner normalizes and validates it.
|
|
30
|
-
3. **Harmony.** Only after primary is resolved, show `recommendedHarmonies` and the installed owner
|
|
31
|
-
harmony catalog. Preserve the selected canonical identifier.
|
|
32
|
-
4. **Per-mode tone combination.** Show the preset recommendation first, then read all valid choices
|
|
33
|
-
from `TONE_PAIR_CATALOG`. Resolve light and dark independently with `resolveTonePair`; do not copy
|
|
34
|
-
a list into prose or code.
|
|
35
|
-
5. **Generated colors.** Call `resolveCategoryDesignPreset` or `compileCategoryDesign`. Inspect
|
|
36
|
-
generated owner roles and diagnostics; never hand-calculate secondary, accent, severity,
|
|
37
|
-
background, neutral, or on-colors.
|
|
38
|
-
6. **Computed Surface output.** Inspect `computedTheme.light.surfaceTheme` and
|
|
39
|
-
`computedTheme.dark.surfaceTheme`, including `ThemeTokens.colors`, `ThemeSemantics`, provenance,
|
|
40
|
-
selections, and diagnostics. Record `GeneratedColorRole` and `SemanticColorToken` names exactly.
|
|
41
|
-
7. **Composition.** Resolve density, shape, layout, navigation, content hierarchy, states, and exact
|
|
42
|
-
ZORA elements from metadata. Typography recommendations remain advisory until the chosen font is
|
|
43
|
-
verified and the owning app/module can load it; never install or bundle a font implicitly.
|
|
44
|
-
8. **Confirmation and output.** Show high-impact choices, their origins, diagnostics, gaps, and the
|
|
45
|
-
requested artifact. Persist runtime state only through the canonical manifest/contracts.
|
|
46
|
-
|
|
47
|
-
Use separate light/dark tone pairs and owner compilation even when the two modes share a primary
|
|
48
|
-
seed. Never generate dark mode by inversion.
|
|
49
|
-
|
|
50
|
-
## 3. Inspect installed owner APIs
|
|
51
|
-
|
|
52
|
-
Run from the target repository:
|
|
1
|
+
# Owner-Backed Interactive and Template Workflow
|
|
53
2
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
The output reports installed versions, category presets, tone pairs, component metadata, recipe
|
|
59
|
-
metadata, and required exports. An error names the missing package/export, minimum owner release,
|
|
60
|
-
and update action. There is no fallback calculation.
|
|
61
|
-
|
|
62
|
-
For deterministic composition, provide JSON to:
|
|
63
|
-
|
|
64
|
-
```text
|
|
65
|
-
bun .agents/skills/zora-designer/scripts/owner-api.mjs compose design-input.json
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
The input contains `category`, optional `theme` overrides, `navigator`, `screens`, and optional
|
|
69
|
-
`regions`. Each region has `id`, `requestedCapability`, `screenId`, `parentNodeId`, an optional exact
|
|
70
|
-
`component`, explicit `props`, and `evidenceId`. The helper:
|
|
71
|
-
|
|
72
|
-
- compiles the category through Templates and the released ZORA compiler;
|
|
73
|
-
- validates exact component names and props against `ZORA_COMPONENT_META`;
|
|
74
|
-
- emits metadata-derived `MissingElement` nodes for unresolved regions;
|
|
75
|
-
- composes the canonical manifest in draft mode while a gap exists;
|
|
76
|
-
- reports `applicationGate: blocked` and owner actions for every gap;
|
|
77
|
-
- preserves Templates and ZORA diagnostics.
|
|
78
|
-
|
|
79
|
-
The agent remains responsible for semantic matching. A requested component name is an explicit
|
|
80
|
-
decision to validate, not a fuzzy-search request.
|
|
3
|
+
Use this workflow for `interactive`, `screen`, `screens`, and `template`. Ask only about unresolved
|
|
4
|
+
decisions, one decision at a time. Show the owner recommendation first with a short reason. Do not
|
|
5
|
+
silently accept later recommendations because an earlier answer was brief.
|
|
81
6
|
|
|
82
|
-
##
|
|
7
|
+
## 1. Inspect intent and owners
|
|
83
8
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
selected/focus states. Define narrow and wide behavior where applicable.
|
|
87
|
-
|
|
88
|
-
For a series also define the shared shell, canonical route topology, state continuity, back/cancel
|
|
89
|
-
behavior, shared recipes and terminology, and one common theme. A concept image series must remain
|
|
90
|
-
ordered and visually coherent. Label every generated image as a concept; replace it with an actual
|
|
91
|
-
runtime capture only after running the manifest.
|
|
92
|
-
|
|
93
|
-
## 5. Audit a URL, image, or series
|
|
94
|
-
|
|
95
|
-
For a URL, capture the relevant modes, viewports, and states using a browser/runtime tool. For one
|
|
96
|
-
image or a series, retain original dimensions and input order. Every evidence item records location,
|
|
97
|
-
observation, evidence level, confidence factor, reproduction, and limitations.
|
|
98
|
-
|
|
99
|
-
Read [audit.md](audit.md), create a criterion input JSON, and run:
|
|
9
|
+
Read repository instructions, an existing manifest or `zora-designer.md`, installed fonts, platform
|
|
10
|
+
configuration, and relevant screens. Run:
|
|
100
11
|
|
|
101
12
|
```text
|
|
102
|
-
bun .agents/skills/zora-designer/scripts/
|
|
13
|
+
bun .agents/skills/zora-designer/scripts/owner-api.mjs inspect
|
|
103
14
|
```
|
|
104
15
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
16
|
+
The output is the choice source. Do not use remembered categories, color options, tone pairs,
|
|
17
|
+
navigator types, ZORA elements, or events. Resolve explicit user input before project state,
|
|
18
|
+
existing brief values, category recommendations, and global defaults. Record the origin of every
|
|
19
|
+
resolved decision.
|
|
20
|
+
|
|
21
|
+
## 2. Ask in dependency order
|
|
22
|
+
|
|
23
|
+
Advance through this sequence. Skip only a value already supplied or reliably discovered.
|
|
24
|
+
|
|
25
|
+
1. **Category and intent.** Resolve the canonical app category, product name, audience, and primary
|
|
26
|
+
task. For `screen` and `screens`, audience and primary task are required.
|
|
27
|
+
2. **Platform and theme coverage.** Resolve mobile, desktop, or responsive scope; technology;
|
|
28
|
+
input modes; target viewport or content constraints; light, dark, or both; and the default mode
|
|
29
|
+
when relevant.
|
|
30
|
+
3. **Primary color.** Show the category recommendations in owner order and any verified project
|
|
31
|
+
brand color. Let the user accept one or supply a CSS color. Preserve the brand seed; the owner
|
|
32
|
+
compiler selects accessible functional steps.
|
|
33
|
+
4. **Harmony.** After primary is resolved, show the category recommendation first and only the
|
|
34
|
+
harmony identifiers reported by Color Theory. Explain the visual energy and complexity using the
|
|
35
|
+
owner catalog descriptions.
|
|
36
|
+
5. **Tone pairs.** After harmony is resolved, offer the category's separate light and dark
|
|
37
|
+
recommendations, followed by mode-compatible owner entries. Explain that `X-on-Y` means accent
|
|
38
|
+
family on foundation family; structural foregrounds remain independently resolved semantic
|
|
39
|
+
tokens.
|
|
40
|
+
6. **Typography and profile.** Resolve a verified body font, optional heading pairing, density,
|
|
41
|
+
shape, motion, and contrast target. Do not install fonts. Offer the category/profile
|
|
42
|
+
recommendation first and allow the user to accept the recommended system settings together.
|
|
43
|
+
7. **Screens.** Ask for the ordered screen list and purpose of each screen. Then establish the
|
|
44
|
+
primary action, essential content, required data states, and continuity across the series. Do not
|
|
45
|
+
generate images yet.
|
|
46
|
+
8. **Navigator.** After the screen topology exists, offer only Contracts navigator types and map
|
|
47
|
+
every route, initial route, hidden/detail route, back/cancel path, and primary-navigation label.
|
|
48
|
+
9. **Compile and confirm.** Compile both theme modes through the owner helper. Summarize high-impact
|
|
49
|
+
decisions, origins, diagnostics, unsupported capabilities, screen order, and route topology. Ask
|
|
50
|
+
for confirmation before creating screen images, code, or a template.
|
|
51
|
+
|
|
52
|
+
If the user changes an earlier decision, invalidate and revisit only dependent later decisions.
|
|
53
|
+
Changing primary invalidates harmony-derived compilation and tone output but does not erase the
|
|
54
|
+
screen brief. Changing the screen list invalidates navigator confirmation.
|
|
55
|
+
|
|
56
|
+
## 3. Compile owner output
|
|
57
|
+
|
|
58
|
+
Provide the resolved `category`, theme overrides, navigator, screens, and region decisions to:
|
|
115
59
|
|
|
116
60
|
```text
|
|
117
|
-
bun .agents/skills/zora-designer/scripts/
|
|
61
|
+
bun .agents/skills/zora-designer/scripts/owner-api.mjs compose design-input.json
|
|
118
62
|
```
|
|
119
63
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
-
|
|
132
|
-
|
|
133
|
-
-
|
|
134
|
-
|
|
135
|
-
-
|
|
136
|
-
|
|
137
|
-
|
|
64
|
+
Use owner-returned theme configuration, generated roles, computed Surface themes, diagnostics,
|
|
65
|
+
component metadata, and manifest composition. Light and dark are independent compilations; never
|
|
66
|
+
derive dark mode by inversion. A failed owner diagnostic remains visible. Do not replace it with
|
|
67
|
+
local math or an undocumented token.
|
|
68
|
+
|
|
69
|
+
For every region, validate its exact component name, props, allowed-parent relationship, and event
|
|
70
|
+
metadata. Use supported navigation and actions when available. Represent missing capability without
|
|
71
|
+
removing the intended flow or inventing a contract.
|
|
72
|
+
|
|
73
|
+
## 4. Produce the requested deliverable
|
|
74
|
+
|
|
75
|
+
- `interactive`: write or return the confirmed `zora-designer.md`; do not produce screen images or
|
|
76
|
+
implementation unless requested.
|
|
77
|
+
- `screen` or `screens`: read [screens.md](screens.md), create the screen specification, then produce
|
|
78
|
+
only the requested image, code, or manifest deliverable.
|
|
79
|
+
- `template`: author a complete `AppManifest`, validate it in release mode, generate or extract every
|
|
80
|
+
required runtime image, and run the Templates scaffolder.
|
|
81
|
+
|
|
82
|
+
For design-first templates, store reference images under `assets/screens/` and crop only real image
|
|
83
|
+
content into `assets/images/`. For direct authoring, generate application images directly under
|
|
84
|
+
`assets/images/`. Runtime media never references a complete screen image.
|
|
85
|
+
|
|
86
|
+
## 5. Delivery checks
|
|
87
|
+
|
|
88
|
+
- every decision is explicit, discovered, or carries a recorded default origin;
|
|
89
|
+
- both theme modes compile without hidden local fallback calculations;
|
|
90
|
+
- the screen model and navigator agree;
|
|
91
|
+
- every selected ZORA node and prop is metadata-supported;
|
|
92
|
+
- supported events/actions are bound and unsupported behavior is recorded;
|
|
93
|
+
- screen generation passes the composition gate;
|
|
94
|
+
- release templates contain one default-exported `createAppManifest()` and bundled media paths;
|
|
95
|
+
- the complete manifest validates against the installed owner contract.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { access, readdir, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { join, resolve } from 'node:path';
|
|
5
|
+
|
|
6
|
+
import { loadOwnerApis } from './owner-api.mjs';
|
|
7
|
+
|
|
8
|
+
const CATEGORY_ROOT = 'src/templates/categories';
|
|
9
|
+
|
|
10
|
+
/*** Regenerate the portable template catalog from canonical template directories. */
|
|
11
|
+
export async function generateTemplateCatalog(targetDirectory = process.cwd(), appCategories) {
|
|
12
|
+
const root = resolve(targetDirectory);
|
|
13
|
+
const canonicalAppCategories =
|
|
14
|
+
appCategories ?? (await loadOwnerApis(root)).contracts.APP_CATEGORIES;
|
|
15
|
+
const categoriesRoot = join(root, CATEGORY_ROOT);
|
|
16
|
+
const definitions = [];
|
|
17
|
+
|
|
18
|
+
for (const categoryEntry of await readDirectories(categoriesRoot)) {
|
|
19
|
+
const category = categoryEntry.name.replaceAll('-', '_');
|
|
20
|
+
if (!canonicalAppCategories.includes(category)) {
|
|
21
|
+
throw new Error(`Unknown app category directory: ${categoryEntry.name}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const categoryRoot = join(categoriesRoot, categoryEntry.name);
|
|
25
|
+
for (const templateEntry of await readDirectories(categoryRoot)) {
|
|
26
|
+
const manifestPath = join(categoryRoot, templateEntry.name, 'createAppManifest.ts');
|
|
27
|
+
if (!(await pathExists(manifestPath))) continue;
|
|
28
|
+
definitions.push({
|
|
29
|
+
category,
|
|
30
|
+
categoryDirectory: categoryEntry.name,
|
|
31
|
+
slug: templateEntry.name,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
definitions.sort((left, right) =>
|
|
37
|
+
`${left.category}/${left.slug}`.localeCompare(`${right.category}/${right.slug}`),
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const imports = definitions
|
|
41
|
+
.map(
|
|
42
|
+
(definition, index) =>
|
|
43
|
+
`import createAppManifest${index} from './categories/${definition.categoryDirectory}/${definition.slug}/createAppManifest';`,
|
|
44
|
+
)
|
|
45
|
+
.join('\n');
|
|
46
|
+
const entries = definitions
|
|
47
|
+
.map(
|
|
48
|
+
(definition, index) => ` {
|
|
49
|
+
category: '${definition.category}',
|
|
50
|
+
slug: '${definition.slug}',
|
|
51
|
+
sourceRoot: '${CATEGORY_ROOT}/${definition.categoryDirectory}/${definition.slug}',
|
|
52
|
+
createAppManifest: createAppManifest${index},
|
|
53
|
+
},`,
|
|
54
|
+
)
|
|
55
|
+
.join('\n');
|
|
56
|
+
const definitionsSource = definitions.length === 0 ? '[]' : `[\n${entries}\n]`;
|
|
57
|
+
|
|
58
|
+
const source = `${imports}${imports ? '\n\n' : ''}import type { TemplateDefinition } from './catalog';
|
|
59
|
+
|
|
60
|
+
export const TEMPLATE_DEFINITIONS: readonly TemplateDefinition[] = ${definitionsSource};
|
|
61
|
+
`;
|
|
62
|
+
|
|
63
|
+
const outputPath = join(root, 'src/templates/catalog.generated.ts');
|
|
64
|
+
await writeFile(outputPath, source, 'utf8');
|
|
65
|
+
return { outputPath, templateCount: definitions.length };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/*** Read child directories in stable lexical order. */
|
|
69
|
+
async function readDirectories(directory) {
|
|
70
|
+
return (await readdir(directory, { withFileTypes: true }))
|
|
71
|
+
.filter((entry) => entry.isDirectory())
|
|
72
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/*** Return whether a filesystem path exists. */
|
|
76
|
+
async function pathExists(filePath) {
|
|
77
|
+
try {
|
|
78
|
+
await access(filePath);
|
|
79
|
+
return true;
|
|
80
|
+
} catch {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (import.meta.main) {
|
|
86
|
+
const targetDirectory = process.argv[2] ?? process.cwd();
|
|
87
|
+
const result = await generateTemplateCatalog(targetDirectory);
|
|
88
|
+
console.log(`Generated ${result.templateCount} template catalog entries.`);
|
|
89
|
+
}
|
|
@@ -5,11 +5,23 @@ import { dirname, join, parse, resolve } from 'node:path';
|
|
|
5
5
|
import { pathToFileURL } from 'node:url';
|
|
6
6
|
|
|
7
7
|
const OWNER_RELEASES = {
|
|
8
|
+
colorTheory: { packageName: '@ankhorage/color-theory', minimumVersion: '0.3.0' },
|
|
9
|
+
contracts: { packageName: '@ankhorage/contracts', minimumVersion: '8.2.0' },
|
|
8
10
|
templates: { packageName: '@ankhorage/templates', minimumVersion: '8.0.0' },
|
|
9
11
|
zora: { packageName: '@ankhorage/zora', minimumVersion: '4.0.0' },
|
|
10
12
|
};
|
|
11
13
|
|
|
12
14
|
const OWNER_REQUIREMENTS = {
|
|
15
|
+
colorTheory: {
|
|
16
|
+
...OWNER_RELEASES.colorTheory,
|
|
17
|
+
specifier: '@ankhorage/color-theory',
|
|
18
|
+
exports: ['COLOR_HARMONIES', 'COLOR_HARMONY_CATALOG'],
|
|
19
|
+
},
|
|
20
|
+
contracts: {
|
|
21
|
+
...OWNER_RELEASES.contracts,
|
|
22
|
+
specifier: '@ankhorage/contracts',
|
|
23
|
+
exports: ['APP_CATEGORIES', 'NAVIGATOR_TYPES'],
|
|
24
|
+
},
|
|
13
25
|
templates: {
|
|
14
26
|
...OWNER_RELEASES.templates,
|
|
15
27
|
specifier: '@ankhorage/templates',
|
|
@@ -45,10 +57,14 @@ export async function loadOwnerApis(targetDirectory = process.cwd()) {
|
|
|
45
57
|
}
|
|
46
58
|
|
|
47
59
|
return {
|
|
60
|
+
colorTheory: loaded.colorTheory.module,
|
|
61
|
+
contracts: loaded.contracts.module,
|
|
48
62
|
templates: loaded.templates.module,
|
|
49
63
|
zoraTheme: loaded.zoraTheme.module,
|
|
50
64
|
zoraMetadata: loaded.zoraMetadata.module,
|
|
51
65
|
versions: {
|
|
66
|
+
colorTheory: loaded.colorTheory.version,
|
|
67
|
+
contracts: loaded.contracts.version,
|
|
52
68
|
templates: loaded.templates.version,
|
|
53
69
|
zora: loaded.zoraTheme.version,
|
|
54
70
|
},
|
|
@@ -58,22 +74,36 @@ export async function loadOwnerApis(targetDirectory = process.cwd()) {
|
|
|
58
74
|
/*** Return installed catalogs and metadata names without copying owner definitions. */
|
|
59
75
|
export async function inspectOwnerApis(targetDirectory = process.cwd()) {
|
|
60
76
|
const owners = await loadOwnerApis(targetDirectory);
|
|
77
|
+
const componentMetadata = Object.values(owners.zoraMetadata.ZORA_COMPONENT_META);
|
|
61
78
|
return {
|
|
62
79
|
versions: owners.versions,
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
recommendedHarmonies: preset.recommendedHarmonies,
|
|
68
|
-
tonePairs: preset.tonePairs,
|
|
69
|
-
})),
|
|
80
|
+
appCategories: owners.contracts.APP_CATEGORIES,
|
|
81
|
+
categoryPresets: owners.templates.CATEGORY_PRESETS,
|
|
82
|
+
harmonyIds: owners.colorTheory.COLOR_HARMONIES,
|
|
83
|
+
harmonies: owners.colorTheory.COLOR_HARMONY_CATALOG,
|
|
70
84
|
tonePairs: owners.templates.TONE_PAIR_CATALOG,
|
|
71
|
-
|
|
85
|
+
navigatorTypes: owners.contracts.NAVIGATOR_TYPES,
|
|
86
|
+
components: componentMetadata.map((meta) => meta.name).sort(),
|
|
87
|
+
events: componentMetadata
|
|
88
|
+
.flatMap((meta) =>
|
|
89
|
+
Object.values(meta.events ?? {}).map((event) => ({
|
|
90
|
+
component: meta.name,
|
|
91
|
+
eventType: event.eventType,
|
|
92
|
+
label: event.label,
|
|
93
|
+
description: event.description,
|
|
94
|
+
payloadFields: event.payloadFields,
|
|
95
|
+
})),
|
|
96
|
+
)
|
|
97
|
+
.sort((left, right) =>
|
|
98
|
+
`${left.component}:${left.eventType}`.localeCompare(
|
|
99
|
+
`${right.component}:${right.eventType}`,
|
|
100
|
+
),
|
|
101
|
+
),
|
|
72
102
|
themeRecipes: Object.keys(owners.zoraMetadata.ZORA_THEME_RECIPE_META).sort(),
|
|
73
103
|
};
|
|
74
104
|
}
|
|
75
105
|
|
|
76
|
-
/***
|
|
106
|
+
/*** Compose one design without turning missing runtime/UI capabilities into a design blocker. */
|
|
77
107
|
export async function composeDesign(input, targetDirectory = process.cwd()) {
|
|
78
108
|
const owners = await loadOwnerApis(targetDirectory);
|
|
79
109
|
assertRecord(input, 'Design input');
|
|
@@ -90,7 +120,6 @@ export async function composeDesign(input, targetDirectory = process.cwd()) {
|
|
|
90
120
|
const design = owners.templates.compileCategoryDesign(input.category, input.theme ?? {});
|
|
91
121
|
const { computedTheme, ...resolvedDesign } = design;
|
|
92
122
|
const requestedAuthoringState = input.authoringState === 'release' ? 'release' : 'draft';
|
|
93
|
-
const authoringState = regionResult.gaps.length === 0 ? requestedAuthoringState : 'draft';
|
|
94
123
|
const composition = owners.templates.composeCategoryAppManifest({
|
|
95
124
|
category: input.category,
|
|
96
125
|
name: input.name,
|
|
@@ -103,14 +132,14 @@ export async function composeDesign(input, targetDirectory = process.cwd()) {
|
|
|
103
132
|
modules: input.modules,
|
|
104
133
|
modulesConfig: input.modulesConfig,
|
|
105
134
|
theme: input.theme,
|
|
106
|
-
authoringState,
|
|
135
|
+
authoringState: requestedAuthoringState,
|
|
107
136
|
});
|
|
108
137
|
const ownerDiagnostics = [
|
|
109
138
|
...design.diagnostics,
|
|
110
139
|
...computedTheme.diagnostics,
|
|
111
140
|
...composition.diagnostics,
|
|
112
141
|
];
|
|
113
|
-
const
|
|
142
|
+
const blockers = ownerDiagnostics.filter((diagnostic) => diagnostic.severity === 'error');
|
|
114
143
|
|
|
115
144
|
return {
|
|
116
145
|
owners: owners.versions,
|
|
@@ -118,14 +147,15 @@ export async function composeDesign(input, targetDirectory = process.cwd()) {
|
|
|
118
147
|
computedTheme,
|
|
119
148
|
composition,
|
|
120
149
|
regionDiagnostics: regionResult.diagnostics,
|
|
150
|
+
capabilityGaps: regionResult.gaps,
|
|
121
151
|
ownerDiagnostics,
|
|
122
152
|
requestedAuthoringState,
|
|
123
|
-
applicationGate: blocked ? 'blocked' : 'pass',
|
|
124
|
-
blockers
|
|
153
|
+
applicationGate: composition.status === 'blocked' ? 'blocked' : 'pass',
|
|
154
|
+
blockers,
|
|
125
155
|
};
|
|
126
156
|
}
|
|
127
157
|
|
|
128
|
-
/*** Resolve explicit region
|
|
158
|
+
/*** Resolve explicit region decisions using exact metadata or a visible non-blocking Box placeholder. */
|
|
129
159
|
export function resolveRegionNodes(screens, regions, componentMeta) {
|
|
130
160
|
const resolvedScreens = structuredClone(screens);
|
|
131
161
|
const diagnostics = [];
|
|
@@ -152,7 +182,6 @@ export function resolveRegionNodes(screens, regions, componentMeta) {
|
|
|
152
182
|
return { screens: resolvedScreens, diagnostics, gaps };
|
|
153
183
|
}
|
|
154
184
|
|
|
155
|
-
/*** Resolve one exact metadata-backed component or the owner-defined MissingElement placeholder. */
|
|
156
185
|
function resolveRegionNode(region, componentMeta) {
|
|
157
186
|
const component = typeof region.component === 'string' ? region.component : null;
|
|
158
187
|
const meta = component === null ? null : componentMeta[component];
|
|
@@ -170,36 +199,30 @@ function resolveRegionNode(region, componentMeta) {
|
|
|
170
199
|
};
|
|
171
200
|
}
|
|
172
201
|
|
|
173
|
-
const
|
|
174
|
-
if (!
|
|
175
|
-
throw new Error(
|
|
176
|
-
'Installed @ankhorage/zora metadata does not expose the canonical MissingElement contract.',
|
|
177
|
-
);
|
|
202
|
+
const placeholderMeta = componentMeta.Box;
|
|
203
|
+
if (!placeholderMeta?.directManifestNode) {
|
|
204
|
+
throw new Error('Installed @ankhorage/zora metadata does not expose Box as a manifest node.');
|
|
178
205
|
}
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
typeof region.reason === 'string' && region.reason.trim() !== ''
|
|
184
|
-
? region.reason
|
|
185
|
-
: `No exact metadata-supported ZORA element was selected for region "${region.id}".`,
|
|
186
|
-
...(typeof region.evidenceId === 'string' ? { evidenceId: region.evidenceId } : {}),
|
|
187
|
-
};
|
|
188
|
-
assertSupportedProps(props, missingMeta, region.id);
|
|
206
|
+
const reason =
|
|
207
|
+
typeof region.reason === 'string' && region.reason.trim() !== ''
|
|
208
|
+
? region.reason
|
|
209
|
+
: `No exact metadata-supported ZORA element was selected for region "${region.id}".`;
|
|
189
210
|
const gap = {
|
|
190
|
-
id: `
|
|
191
|
-
scope: '
|
|
192
|
-
owner: '@ankhorage/zora',
|
|
211
|
+
id: `capability-gap:${region.id}`,
|
|
212
|
+
scope: 'capability',
|
|
193
213
|
regionId: region.id,
|
|
194
214
|
requestedCapability: region.requestedCapability,
|
|
195
215
|
evidenceId: region.evidenceId ?? null,
|
|
196
|
-
|
|
197
|
-
reason: props.reason,
|
|
198
|
-
unblockCondition: 'Replace MissingElement with a released exact ZORA element and revalidate.',
|
|
216
|
+
reason,
|
|
199
217
|
};
|
|
200
218
|
return {
|
|
201
|
-
node: { id: region.id, type:
|
|
202
|
-
diagnostic: {
|
|
219
|
+
node: { id: region.id, type: placeholderMeta.name, props: {} },
|
|
220
|
+
diagnostic: {
|
|
221
|
+
regionId: region.id,
|
|
222
|
+
status: 'placeholder',
|
|
223
|
+
component: placeholderMeta.name,
|
|
224
|
+
...gap,
|
|
225
|
+
},
|
|
203
226
|
gap,
|
|
204
227
|
};
|
|
205
228
|
}
|