@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.
@@ -1,137 +1,95 @@
1
- # Design, Composition, and Template Workflow
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
- ```text
55
- bun .agents/skills/zora-designer/scripts/owner-api.mjs inspect
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
- ## 4. Design one screen or a series
7
+ ## 1. Inspect intent and owners
83
8
 
84
- For each screen define purpose, information hierarchy, primary action, navigation relationship,
85
- data needs, and relevant default/loading/empty/partial/error/offline/success/disabled/hover/pressed/
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/audit.mjs audit-input.json zora-designer.md
13
+ bun .agents/skills/zora-designer/scripts/owner-api.mjs inspect
103
14
  ```
104
15
 
105
- Invisible behavior remains `not-assessable` unless source, accessibility-tree, browser, or runtime
106
- evidence supports it. A recommended redesign baseline may use the category workflow but must be
107
- clearly separated from observed implementation.
108
-
109
- ## 6. Author a canonical template
110
-
111
- Use `composeCategoryAppManifest`, `validateTemplateManifest`, and
112
- `assertTemplateManifestReady`. Draft output may contain `MissingElement`; release output may not.
113
-
114
- When the target repository is `@ankhorage/templates`, scaffold reviewed composition output with:
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/scaffold-template.mjs scaffold-input.json
61
+ bun .agents/skills/zora-designer/scripts/owner-api.mjs compose design-input.json
118
62
  ```
119
63
 
120
- The scaffold input provides the ready manifest, category, template ID, label, description, and
121
- target root. The helper verifies the Templates repository and owner validation, creates a normal
122
- variant `manifest.ts`, `template.ts`, and `index.ts`, and updates the category registry import and
123
- definition deterministically. It refuses existing targets, unsafe identifiers, blocked manifests,
124
- and non-Templates repositories. Review and validate the generated production diff; do not hand-hide
125
- a systemic skill or owner defect.
126
-
127
- ## 7. Delivery gates
128
-
129
- Before application or release:
130
-
131
- - every visible/interactive region has an exact metadata-supported element;
132
- - every gap has evidence and a linked ZORA owner issue;
133
- - both modes compile without owner errors;
134
- - the manifest is owner-validated and release-ready;
135
- - required concept-image, runtime-capture, or supplied-image capabilities were actually available;
136
- - target and observed state are clearly separated;
137
- - `zora-designer.md` is deterministic and contains no runtime authority or copied owner catalogs.
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
- categories: Object.values(owners.templates.CATEGORY_PRESETS).map((preset) => ({
64
- category: preset.category,
65
- label: preset.label,
66
- recommendedPrimaryColors: preset.recommendedPrimaryColors,
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
- components: Object.keys(owners.zoraMetadata.ZORA_COMPONENT_META).sort(),
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
- /*** Compile category design, validate region nodes, and compose one canonical draft or release manifest. */
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 blocked = regionResult.gaps.length > 0 || composition.status === 'blocked';
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: regionResult.gaps,
153
+ applicationGate: composition.status === 'blocked' ? 'blocked' : 'pass',
154
+ blockers,
125
155
  };
126
156
  }
127
157
 
128
- /*** Resolve explicit region-to-component decisions and insert validated nodes into cloned screens. */
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 missingMeta = componentMeta.MissingElement;
174
- if (!missingMeta || missingMeta.manifestPolicy?.kind !== 'unresolved-element') {
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 props = {
180
- ...(missingMeta.blueprint?.defaultProps ?? {}),
181
- requestedCapability: region.requestedCapability,
182
- reason:
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: `missing-element:${region.id}`,
191
- scope: 'composition',
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
- ownerIssueUrl: region.ownerIssueUrl ?? null,
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: missingMeta.name, props },
202
- diagnostic: { regionId: region.id, status: 'missing', component: missingMeta.name, ...gap },
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
  }