@ankhorage/devtools 1.12.1 → 1.14.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.
- package/dist/tools/skills/assets/zora-designer/SKILL.md +28 -9
- package/dist/tools/skills/assets/zora-designer/agents/openai.yaml +2 -2
- package/dist/tools/skills/assets/zora-designer/references/screen-analysis.md +115 -0
- package/dist/tools/skills/assets/zora-designer/references/screens.md +9 -0
- package/dist/tools/skills/assets/zora-designer/references/workflow.md +3 -0
- package/dist/tools/skills/assets/zora-designer/scripts/analyze-screen.ts +320 -0
- package/dist/tools/skills/assets/zora-designer/scripts/owner-api.ts +74 -5
- package/dist/tools/skills/zoraDesignerOwnerFixtures.d.ts +6 -0
- package/dist/tools/skills/zoraDesignerOwnerFixtures.js +166 -0
- package/package.json +5 -3
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: zora-designer
|
|
3
3
|
description: >
|
|
4
|
-
Configure an owner-backed application design, generate
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
Configure an owner-backed application design, generate or recognize screens, audit supplied
|
|
5
|
+
evidence, and author portable Ankhorage templates. Use for category-driven design decisions,
|
|
6
|
+
ZORA screen generation or reconstruction, visual audits, or template creation.
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# ZORA Designer
|
|
10
10
|
|
|
11
|
-
Design, audit, and author through the target repository's released owner APIs. The
|
|
12
|
-
`AppManifest` is runtime authority; `zora-designer.md` and generated screen images are
|
|
13
|
-
evidence.
|
|
11
|
+
Design, recognize, audit, and author through the target repository's released owner APIs. The
|
|
12
|
+
complete `AppManifest` is runtime authority; `zora-designer.md` and generated screen images are
|
|
13
|
+
design evidence.
|
|
14
14
|
|
|
15
15
|
## Route the request
|
|
16
16
|
|
|
@@ -20,6 +20,10 @@ evidence.
|
|
|
20
20
|
screen.
|
|
21
21
|
- `screens`: resolve the configuration, then read [screens.md](references/screens.md) and design an
|
|
22
22
|
ordered series with shared navigation, state, geometry, and tokens.
|
|
23
|
+
- `recognize`: when the user supplies an existing UI screenshot/screen design and asks to
|
|
24
|
+
reconstruct it, determine ZORA components, or convert it to a manifest screen, read
|
|
25
|
+
[screen-analysis.md](references/screen-analysis.md) and run programmatic recognition before
|
|
26
|
+
free-form visual interpretation.
|
|
23
27
|
- `audit`: read [audit.md](references/audit.md) and evaluate supplied image or runtime evidence.
|
|
24
28
|
- `template`: resolve the configuration and screen model, then author one portable template through
|
|
25
29
|
the workflow below.
|
|
@@ -42,6 +46,13 @@ types, ZORA elements, events, recipes, and version provenance. Never copy owner
|
|
|
42
46
|
schemas, token inventories, color algorithms, action types, or manifest implementations into this
|
|
43
47
|
skill.
|
|
44
48
|
|
|
49
|
+
The inspection composes metadata-only descriptors from every installed `@ankhorage/zora-*` plugin;
|
|
50
|
+
use those plugin elements exactly like ZORA core elements and keep their package provenance.
|
|
51
|
+
|
|
52
|
+
For supplied-screen reconstruction, continue through `recognize` and treat the local analyzer's
|
|
53
|
+
`ScreenSpec`, visual graph, confidence, alternatives, and unresolved diagnostics as the structural
|
|
54
|
+
evidence baseline. Do not ask an image model to redo deterministic geometry/component matching.
|
|
55
|
+
|
|
45
56
|
Compile chosen values with the same helper before composing screens. Inspect both computed modes,
|
|
46
57
|
including their resolved Surface themes and all owner diagnostics. Never hand-calculate a value the
|
|
47
58
|
owner exposes.
|
|
@@ -49,9 +60,15 @@ owner exposes.
|
|
|
49
60
|
## Preserve the complete UX
|
|
50
61
|
|
|
51
62
|
For every screen region, prefer the exact semantic ZORA element supported by current metadata.
|
|
52
|
-
Visual resemblance alone is insufficient. If
|
|
53
|
-
|
|
54
|
-
|
|
63
|
+
Visual resemblance alone is insufficient. If programmatic recognition leaves multiple plausible
|
|
64
|
+
candidates, resolve them from semantic responsibility and stated product intent while preserving the
|
|
65
|
+
ranked alternatives as evidence.
|
|
66
|
+
|
|
67
|
+
If no exact element exists, preserve the requested UX with an explicit owner-supported unresolved
|
|
68
|
+
placeholder when available, and record the capability gap. For ordinary design composition where no
|
|
69
|
+
unresolved manifest element is applicable, use an obvious supported placeholder such as a
|
|
70
|
+
secondary-surface `Box` and record the capability gap. Do not invent props, application components,
|
|
71
|
+
or successful behavior.
|
|
55
72
|
|
|
56
73
|
Bind every interaction expressible by installed Contracts and ZORA event metadata. Leave an
|
|
57
74
|
unsupported interaction visibly present and explicitly unbound without blocking unrelated design
|
|
@@ -95,6 +112,8 @@ barrels.
|
|
|
95
112
|
|
|
96
113
|
- confirm the interactive decision sequence completed or the user explicitly accepted remaining
|
|
97
114
|
recommendations;
|
|
115
|
+
- for supplied image reconstruction, run `recognize` before semantic refinement and retain its
|
|
116
|
+
confidence/alternative/gap evidence;
|
|
98
117
|
- compile light and dark independently through installed owner APIs;
|
|
99
118
|
- validate selected ZORA nodes, props, events, actions, and complete manifest contracts;
|
|
100
119
|
- run the screen composition gate from [screens.md](references/screens.md) before returning screen
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
interface:
|
|
2
2
|
display_name: 'ZORA Designer'
|
|
3
|
-
short_description: 'Design, audit, and author Ankhorage screens and templates through ZORA.'
|
|
4
|
-
default_prompt: 'Use $zora-designer to design, audit, or author this Ankhorage interface.'
|
|
3
|
+
short_description: 'Design, recognize, audit, and author Ankhorage screens and templates through ZORA.'
|
|
4
|
+
default_prompt: 'Use $zora-designer to design, recognize, audit, or author this Ankhorage interface.'
|
|
5
5
|
policy:
|
|
6
6
|
allow_implicit_invocation: true
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Supplied Screen Recognition
|
|
2
|
+
|
|
3
|
+
Read this reference when the user supplies an existing UI screenshot or screen design and asks to
|
|
4
|
+
reconstruct it, determine its ZORA components, convert it to a manifest screen, or use it as the
|
|
5
|
+
starting point for a redesigned screen.
|
|
6
|
+
|
|
7
|
+
This path analyzes supplied evidence. It does not replace `screen` or `screens` generation.
|
|
8
|
+
|
|
9
|
+
## Run deterministic recognition first
|
|
10
|
+
|
|
11
|
+
Before free-form visual interpretation, inspect the released owners and run the local analyzer from
|
|
12
|
+
the target repository:
|
|
13
|
+
|
|
14
|
+
```text
|
|
15
|
+
bun .agents/skills/zora-designer/scripts/owner-api.ts inspect
|
|
16
|
+
bun .agents/skills/zora-designer/scripts/analyze-screen.ts screen-analysis-input.json
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The analyzer uses the current composed ZORA metadata, including every installed
|
|
20
|
+
`@ankhorage/zora-*` plugin, and delegates image processing to released
|
|
21
|
+
`@ankhorage/utility/image`:
|
|
22
|
+
|
|
23
|
+
```text
|
|
24
|
+
supplied image
|
|
25
|
+
-> Sharp normalization
|
|
26
|
+
-> OpenCV geometry and visual graph
|
|
27
|
+
-> optional local Tesseract OCR
|
|
28
|
+
-> owner-metadata component matching
|
|
29
|
+
-> global tree validation
|
|
30
|
+
-> Contracts ScreenSpec
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
No remote model or API is required for this pass.
|
|
34
|
+
|
|
35
|
+
## Input
|
|
36
|
+
|
|
37
|
+
Write one temporary JSON input file in the target repository. Image paths are resolved from the
|
|
38
|
+
target repository root.
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"image": "design/screens/home.png",
|
|
43
|
+
"screen": {
|
|
44
|
+
"id": "home",
|
|
45
|
+
"name": "Home",
|
|
46
|
+
"title": "Home"
|
|
47
|
+
},
|
|
48
|
+
"minConfidence": 0.42
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
OCR is supplementary. Enable it only when local trained data is available and text evidence helps
|
|
53
|
+
distinguish visually similar components:
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"image": "design/screens/home.png",
|
|
58
|
+
"screen": { "id": "home", "name": "Home" },
|
|
59
|
+
"ocr": {
|
|
60
|
+
"langPath": "./tessdata",
|
|
61
|
+
"language": "eng"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Geometry-only analysis remains valid if OCR setup or recognition fails; the output records an OCR
|
|
67
|
+
diagnostic instead of replacing the visual graph.
|
|
68
|
+
|
|
69
|
+
## Local engine boundary
|
|
70
|
+
|
|
71
|
+
`@ankhorage/utility` owns the image implementation. The target authoring repository supplies the
|
|
72
|
+
optional native/WASM engines required by the recognition path. If they are absent, install them as
|
|
73
|
+
development tooling:
|
|
74
|
+
|
|
75
|
+
```text
|
|
76
|
+
bun add -D sharp @techstark/opencv-js
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Do not add a copied matcher, OpenCV wrapper, fallback parser, remote vision service, or alternate
|
|
80
|
+
component catalogue to the skill.
|
|
81
|
+
|
|
82
|
+
## Treat the result as evidence
|
|
83
|
+
|
|
84
|
+
The analyzer returns:
|
|
85
|
+
|
|
86
|
+
- canonical Contracts `ScreenSpec`;
|
|
87
|
+
- visual graph evidence;
|
|
88
|
+
- aggregate confidence;
|
|
89
|
+
- ranked component candidates by region;
|
|
90
|
+
- unresolved diagnostics;
|
|
91
|
+
- the exact composed component names and owner/plugin version provenance used for the pass.
|
|
92
|
+
|
|
93
|
+
Use the generated `ScreenSpec` as the structural baseline. Do not discard a high-confidence exact
|
|
94
|
+
metadata match merely because another component looks visually similar.
|
|
95
|
+
|
|
96
|
+
When confidence is low, preserve the analyzer's alternatives and resolve the ambiguity using the
|
|
97
|
+
screen's semantic responsibility and the user's stated product intent. If metadata declares an
|
|
98
|
+
unresolved element, the analyzer uses that explicit owner-owned gap instead of inventing a ZORA
|
|
99
|
+
component or prop.
|
|
100
|
+
|
|
101
|
+
## Refine only what pixels cannot prove
|
|
102
|
+
|
|
103
|
+
After recognition, reason about the parts a static image cannot establish reliably:
|
|
104
|
+
|
|
105
|
+
- purpose and primary task;
|
|
106
|
+
- actions and event bindings;
|
|
107
|
+
- navigation and back/cancel behavior;
|
|
108
|
+
- state transitions and validation;
|
|
109
|
+
- data sources and bindings;
|
|
110
|
+
- accessibility and focus behavior;
|
|
111
|
+
- responsive behavior beyond the observed viewport;
|
|
112
|
+
- loading, empty, error, offline, pressed, selected, and other unseen states.
|
|
113
|
+
|
|
114
|
+
Then run the normal `screen` composition rules and owner-backed manifest validation. A screenshot is
|
|
115
|
+
visual evidence, not proof of runtime behavior.
|
|
@@ -4,6 +4,11 @@ Read this reference for `screen` and `screens` after the interactive configurati
|
|
|
4
4
|
Generated images are concept evidence; the manifest and ZORA metadata remain implementation
|
|
5
5
|
authority.
|
|
6
6
|
|
|
7
|
+
When the starting point is a supplied existing screen image, run `recognize` first through
|
|
8
|
+
[screen-analysis.md](screen-analysis.md). Treat its canonical `ScreenSpec`, visual graph, confidence,
|
|
9
|
+
alternatives, and unresolved diagnostics as structural evidence, then use this reference for the
|
|
10
|
+
semantic and behavioral refinement that pixels cannot establish.
|
|
11
|
+
|
|
7
12
|
## Preconditions
|
|
8
13
|
|
|
9
14
|
Do not generate or implement a screen until these are resolved:
|
|
@@ -30,6 +35,10 @@ For every screen record:
|
|
|
30
35
|
6. safe areas, keyboard/overlay behavior, scroll ownership, and narrow/wide behavior;
|
|
31
36
|
7. every SVG icon and real image region, its reusable asset ID, and its intended manifest usage.
|
|
32
37
|
|
|
38
|
+
For a recognized supplied image, preserve every high-confidence metadata-backed subtree unless the
|
|
39
|
+
user's stated intent provides stronger semantic evidence. Resolve ambiguous candidates explicitly;
|
|
40
|
+
do not silently replace the analyzer's tree with a visually similar primitive decomposition.
|
|
41
|
+
|
|
33
42
|
Read [runtime-assets.md](runtime-assets.md). Produce and inspect the separate icons and images before
|
|
34
43
|
concept rendering, use them as screen references, and deliver their checked bundle with the screens.
|
|
35
44
|
|
|
@@ -18,6 +18,9 @@ navigator types, ZORA elements, or events. Resolve explicit user input before pr
|
|
|
18
18
|
existing brief values, category recommendations, and global defaults. Record the origin of every
|
|
19
19
|
resolved decision.
|
|
20
20
|
|
|
21
|
+
Installed `@ankhorage/zora-*` packages contribute their metadata-only plugin descriptors to this
|
|
22
|
+
same composed element catalog; do not maintain a second plugin list in the skill.
|
|
23
|
+
|
|
21
24
|
## 2. Ask in dependency order
|
|
22
25
|
|
|
23
26
|
Advance through this sequence. Skip only a value already supplied or reliably discovered.
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
5
|
+
import { join, resolve } from 'node:path';
|
|
6
|
+
import { pathToFileURL } from 'node:url';
|
|
7
|
+
|
|
8
|
+
import { loadOwnerApis } from './owner-api.ts';
|
|
9
|
+
|
|
10
|
+
const DEVTOOLS_PACKAGE_NAME = '@ankhorage/devtools';
|
|
11
|
+
const UTILITY_PACKAGE_NAME = '@ankhorage/utility';
|
|
12
|
+
const REQUIRED_IMAGE_ENGINES = ['sharp', '@techstark/opencv-js'] as const;
|
|
13
|
+
|
|
14
|
+
interface ScreenOcr {
|
|
15
|
+
readonly recognizeAsync: (image: Uint8Array) => Promise<readonly unknown[]>;
|
|
16
|
+
readonly terminateAsync?: () => Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface UtilityImageApi {
|
|
20
|
+
readonly analyzeScreenImageAsync: (
|
|
21
|
+
image: string,
|
|
22
|
+
options: Readonly<Record<string, unknown>>,
|
|
23
|
+
) => Promise<unknown>;
|
|
24
|
+
readonly createTesseractScreenOcrAsync: (options: {
|
|
25
|
+
readonly langPath: string;
|
|
26
|
+
readonly language?: string;
|
|
27
|
+
}) => Promise<ScreenOcr>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface AnalyzeScreenInput {
|
|
31
|
+
readonly image: string;
|
|
32
|
+
readonly screen: {
|
|
33
|
+
readonly id: string;
|
|
34
|
+
readonly name: string;
|
|
35
|
+
readonly title?: string;
|
|
36
|
+
readonly description?: string;
|
|
37
|
+
};
|
|
38
|
+
readonly minConfidence?: number;
|
|
39
|
+
readonly ocr?: {
|
|
40
|
+
readonly langPath: string;
|
|
41
|
+
readonly language?: string;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface ScreenAnalysisRuntime {
|
|
46
|
+
readonly api: UtilityImageApi;
|
|
47
|
+
readonly utilityVersion: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/*** Analyze one supplied UI screen with current owner metadata and the released Utility image pipeline. */
|
|
51
|
+
export async function analyzeScreenAsync(
|
|
52
|
+
input: unknown,
|
|
53
|
+
targetDirectory = process.cwd(),
|
|
54
|
+
): Promise<Record<string, unknown>> {
|
|
55
|
+
const parsedInput = readAnalyzeScreenInput(input);
|
|
56
|
+
const owners = await loadOwnerApis(targetDirectory);
|
|
57
|
+
const components = readComponentCatalog(owners.zoraMetadata.ZORA_COMPONENT_META);
|
|
58
|
+
const unresolvedComponentName = resolveUnresolvedComponentName(components);
|
|
59
|
+
const runtime = await loadScreenAnalysisRuntime(targetDirectory);
|
|
60
|
+
const ocr = await createOptionalOcr(runtime.api, parsedInput.ocr);
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const analysis = await runtime.api.analyzeScreenImageAsync(
|
|
64
|
+
resolve(targetDirectory, parsedInput.image),
|
|
65
|
+
{
|
|
66
|
+
screen: parsedInput.screen,
|
|
67
|
+
components,
|
|
68
|
+
...(unresolvedComponentName ? { unresolvedComponentName } : {}),
|
|
69
|
+
...(parsedInput.minConfidence === undefined
|
|
70
|
+
? {}
|
|
71
|
+
: { minConfidence: parsedInput.minConfidence }),
|
|
72
|
+
...(ocr ? { ocr } : {}),
|
|
73
|
+
},
|
|
74
|
+
);
|
|
75
|
+
assertRecord(analysis, 'Utility screen analysis result');
|
|
76
|
+
return {
|
|
77
|
+
owners: { ...owners.versions, utility: runtime.utilityVersion },
|
|
78
|
+
componentCount: components.length,
|
|
79
|
+
componentNames: components.map((component) => String(component.name)).sort(),
|
|
80
|
+
...(unresolvedComponentName ? { unresolvedComponentName } : {}),
|
|
81
|
+
...analysis,
|
|
82
|
+
};
|
|
83
|
+
} finally {
|
|
84
|
+
await ocr?.terminateAsync?.();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/*** Load Utility image analysis through the installed Devtools dependency boundary. */
|
|
89
|
+
async function loadScreenAnalysisRuntime(targetDirectory: string): Promise<ScreenAnalysisRuntime> {
|
|
90
|
+
const devtoolsManifestPath = await resolveDevtoolsManifestPath(targetDirectory);
|
|
91
|
+
const devtoolsRequire = createRequire(devtoolsManifestPath);
|
|
92
|
+
let utilityManifestPath: string;
|
|
93
|
+
try {
|
|
94
|
+
utilityManifestPath = devtoolsRequire.resolve(`${UTILITY_PACKAGE_NAME}/package.json`);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
'zora-designer screen analysis requires @ankhorage/utility 0.5.x through the installed @ankhorage/devtools dependency. Update Devtools through the normal release/Renovate workflow and retry.',
|
|
98
|
+
{ cause: error },
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
assertRequiredImageEngines(devtoolsRequire);
|
|
103
|
+
const utilityManifest: unknown = JSON.parse(await readFile(utilityManifestPath, 'utf8'));
|
|
104
|
+
assertRecord(utilityManifest, '@ankhorage/utility package manifest');
|
|
105
|
+
assertNonEmptyString(utilityManifest.version, '@ankhorage/utility version');
|
|
106
|
+
|
|
107
|
+
let modulePath: string;
|
|
108
|
+
try {
|
|
109
|
+
modulePath = devtoolsRequire.resolve(`${UTILITY_PACKAGE_NAME}/image`);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
'Installed @ankhorage/utility does not expose the required public /image subpath. Update @ankhorage/devtools so it selects Utility 0.5.x or newer and retry.',
|
|
113
|
+
{ cause: error },
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
const module: unknown = await import(pathToFileURL(modulePath).href);
|
|
117
|
+
assertUtilityImageApi(module);
|
|
118
|
+
return { api: module, utilityVersion: utilityManifest.version };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/*** Resolve the installed Devtools package manifest used as the dependency-resolution anchor. */
|
|
122
|
+
async function resolveDevtoolsManifestPath(targetDirectory: string): Promise<string> {
|
|
123
|
+
const targetRoot = resolve(targetDirectory);
|
|
124
|
+
const targetManifestPath = join(targetRoot, 'package.json');
|
|
125
|
+
const targetManifest: unknown = JSON.parse(await readFile(targetManifestPath, 'utf8'));
|
|
126
|
+
assertRecord(targetManifest, 'Target package manifest');
|
|
127
|
+
if (targetManifest.name === DEVTOOLS_PACKAGE_NAME) {
|
|
128
|
+
return targetManifestPath;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const installedManifestPath = join(
|
|
132
|
+
targetRoot,
|
|
133
|
+
'node_modules',
|
|
134
|
+
'@ankhorage',
|
|
135
|
+
'devtools',
|
|
136
|
+
'package.json',
|
|
137
|
+
);
|
|
138
|
+
try {
|
|
139
|
+
await readFile(installedManifestPath, 'utf8');
|
|
140
|
+
return installedManifestPath;
|
|
141
|
+
} catch (error) {
|
|
142
|
+
throw new Error(
|
|
143
|
+
'zora-designer screen analysis requires the target repository to have @ankhorage/devtools installed. Run the normal Devtools synchronization/install workflow and retry.',
|
|
144
|
+
{ cause: error },
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/*** Require the local deterministic engines used by Utility without making them global Devtools runtime dependencies. */
|
|
150
|
+
function assertRequiredImageEngines(devtoolsRequire: ReturnType<typeof createRequire>): void {
|
|
151
|
+
const missing = REQUIRED_IMAGE_ENGINES.filter((packageName) => {
|
|
152
|
+
try {
|
|
153
|
+
devtoolsRequire.resolve(packageName);
|
|
154
|
+
return false;
|
|
155
|
+
} catch {
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
if (missing.length > 0) {
|
|
160
|
+
throw new Error(
|
|
161
|
+
`zora-designer screen analysis requires local image engines ${missing.join(', ')}. Install the screen-analysis engines in the target repository with "bun add -D sharp @techstark/opencv-js" and retry.`,
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/*** Create supplementary local OCR while converting setup failures into evidence rather than geometry blockers. */
|
|
167
|
+
async function createOptionalOcr(
|
|
168
|
+
api: UtilityImageApi,
|
|
169
|
+
options: AnalyzeScreenInput['ocr'],
|
|
170
|
+
): Promise<ScreenOcr | undefined> {
|
|
171
|
+
if (!options) return undefined;
|
|
172
|
+
try {
|
|
173
|
+
return await api.createTesseractScreenOcrAsync(options);
|
|
174
|
+
} catch (error) {
|
|
175
|
+
const rejection = error instanceof Error ? error : new Error(String(error));
|
|
176
|
+
return { recognizeAsync: () => Promise.reject(rejection) };
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/*** Preserve the full composed owner metadata while validating the fields required by the Utility matcher. */
|
|
181
|
+
function readComponentCatalog(value: unknown): Record<string, unknown>[] {
|
|
182
|
+
assertRecord(value, 'Composed ZORA component metadata');
|
|
183
|
+
return Object.values(value)
|
|
184
|
+
.filter((component) => component !== undefined)
|
|
185
|
+
.map((component, index) => {
|
|
186
|
+
assertRecord(component, `ZORA component metadata[${index}]`);
|
|
187
|
+
assertNonEmptyString(component.name, `ZORA component metadata[${index}].name`);
|
|
188
|
+
assertComponentCategory(component.category, `ZORA component metadata[${index}].category`);
|
|
189
|
+
if (typeof component.directManifestNode !== 'boolean') {
|
|
190
|
+
throw new Error(`ZORA component metadata[${index}].directManifestNode must be a boolean.`);
|
|
191
|
+
}
|
|
192
|
+
assertStringArray(
|
|
193
|
+
component.allowedChildren,
|
|
194
|
+
`ZORA component metadata[${index}].allowedChildren`,
|
|
195
|
+
);
|
|
196
|
+
assertRecord(component.props, `ZORA component metadata[${index}].props`);
|
|
197
|
+
return component;
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/*** Resolve the owner-declared unresolved manifest component without hard-coding a ZORA component name. */
|
|
202
|
+
function resolveUnresolvedComponentName(
|
|
203
|
+
components: readonly Record<string, unknown>[],
|
|
204
|
+
): string | undefined {
|
|
205
|
+
const unresolved = components.find((component) => {
|
|
206
|
+
const policy = component.manifestPolicy;
|
|
207
|
+
return isRecord(policy) && policy.kind === 'unresolved-element';
|
|
208
|
+
});
|
|
209
|
+
return typeof unresolved?.name === 'string' ? unresolved.name : undefined;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/*** Parse and validate portable JSON input for one recognition operation. */
|
|
213
|
+
function readAnalyzeScreenInput(value: unknown): AnalyzeScreenInput {
|
|
214
|
+
assertRecord(value, 'Screen analysis input');
|
|
215
|
+
assertNonEmptyString(value.image, 'image');
|
|
216
|
+
assertRecord(value.screen, 'screen');
|
|
217
|
+
assertNonEmptyString(value.screen.id, 'screen.id');
|
|
218
|
+
assertNonEmptyString(value.screen.name, 'screen.name');
|
|
219
|
+
assertOptionalString(value.screen.title, 'screen.title');
|
|
220
|
+
assertOptionalString(value.screen.description, 'screen.description');
|
|
221
|
+
const minConfidence = readMinConfidence(value.minConfidence);
|
|
222
|
+
const ocr = readOcrOptions(value.ocr);
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
image: value.image,
|
|
226
|
+
screen: {
|
|
227
|
+
id: value.screen.id,
|
|
228
|
+
name: value.screen.name,
|
|
229
|
+
...(value.screen.title === undefined ? {} : { title: value.screen.title }),
|
|
230
|
+
...(value.screen.description === undefined ? {} : { description: value.screen.description }),
|
|
231
|
+
},
|
|
232
|
+
...(minConfidence === undefined ? {} : { minConfidence }),
|
|
233
|
+
...(ocr === undefined ? {} : { ocr }),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/*** Read one optional confidence threshold constrained to the Utility matcher interval. */
|
|
238
|
+
function readMinConfidence(value: unknown): number | undefined {
|
|
239
|
+
if (value === undefined) return undefined;
|
|
240
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value < 0 || value > 1) {
|
|
241
|
+
throw new Error('minConfidence must be a finite number from 0 through 1.');
|
|
242
|
+
}
|
|
243
|
+
return value;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/*** Read optional local OCR configuration without making OCR a geometry prerequisite. */
|
|
247
|
+
function readOcrOptions(value: unknown): AnalyzeScreenInput['ocr'] {
|
|
248
|
+
if (value === undefined) return undefined;
|
|
249
|
+
assertRecord(value, 'ocr');
|
|
250
|
+
assertNonEmptyString(value.langPath, 'ocr.langPath');
|
|
251
|
+
assertOptionalString(value.language, 'ocr.language');
|
|
252
|
+
return {
|
|
253
|
+
langPath: value.langPath,
|
|
254
|
+
...(value.language === undefined ? {} : { language: value.language }),
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/*** Narrow the released Utility image subpath to the two capabilities used by the skill. */
|
|
259
|
+
function assertUtilityImageApi(value: unknown): asserts value is UtilityImageApi {
|
|
260
|
+
assertRecord(value, '@ankhorage/utility/image');
|
|
261
|
+
if (typeof value.analyzeScreenImageAsync !== 'function') {
|
|
262
|
+
throw new Error('@ankhorage/utility/image must export analyzeScreenImageAsync.');
|
|
263
|
+
}
|
|
264
|
+
if (typeof value.createTesseractScreenOcrAsync !== 'function') {
|
|
265
|
+
throw new Error('@ankhorage/utility/image must export createTesseractScreenOcrAsync.');
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/*** Require one current ZORA component category. */
|
|
270
|
+
function assertComponentCategory(value: unknown, label: string): void {
|
|
271
|
+
if (!['foundation', 'component', 'pattern', 'layout'].includes(String(value))) {
|
|
272
|
+
throw new Error(`${label} must be a current ZORA component category.`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/*** Require an array containing only strings. */
|
|
277
|
+
function assertStringArray(value: unknown, label: string): asserts value is string[] {
|
|
278
|
+
if (!Array.isArray(value) || value.some((entry) => typeof entry !== 'string')) {
|
|
279
|
+
throw new Error(`${label} must be a string array.`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/*** Require an optional non-empty string when present. */
|
|
284
|
+
function assertOptionalString(value: unknown, label: string): asserts value is string | undefined {
|
|
285
|
+
if (value !== undefined) assertNonEmptyString(value, label);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/*** Require an object-shaped value. */
|
|
289
|
+
function assertRecord(value: unknown, label: string): asserts value is Record<string, unknown> {
|
|
290
|
+
if (!isRecord(value)) throw new Error(`${label} must be an object.`);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/*** Narrow an unknown value to a record. */
|
|
294
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
295
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/*** Require a non-empty string. */
|
|
299
|
+
function assertNonEmptyString(value: unknown, label: string): asserts value is string {
|
|
300
|
+
if (typeof value !== 'string' || value.trim() === '') {
|
|
301
|
+
throw new Error(`${label} must be a non-empty string.`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/*** Run the portable recognition command when executed directly. */
|
|
306
|
+
async function main(): Promise<void> {
|
|
307
|
+
const [inputPath] = process.argv.slice(2);
|
|
308
|
+
if (!inputPath) {
|
|
309
|
+
throw new Error('Usage: analyze-screen.ts <screen-analysis-input.json>');
|
|
310
|
+
}
|
|
311
|
+
const input: unknown = JSON.parse(await readFile(inputPath, 'utf8'));
|
|
312
|
+
console.log(JSON.stringify(await analyzeScreenAsync(input), null, 2));
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
|
|
316
|
+
main().catch((error) => {
|
|
317
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
318
|
+
process.exitCode = 1;
|
|
319
|
+
});
|
|
320
|
+
}
|
|
@@ -58,7 +58,7 @@ interface ZoraThemeApi extends Record<string, unknown> {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
interface EventMetadata extends Record<string, unknown> {
|
|
61
|
-
description
|
|
61
|
+
description?: string;
|
|
62
62
|
eventType: string;
|
|
63
63
|
label: string;
|
|
64
64
|
payloadFields?: unknown;
|
|
@@ -84,6 +84,10 @@ interface RecipeMetadata extends Record<string, unknown> {
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
interface ZoraMetadataApi extends Record<string, unknown> {
|
|
87
|
+
composeZoraPluginMetadata: (plugins: readonly Record<string, unknown>[]) => {
|
|
88
|
+
componentMeta: Record<string, ComponentMetadata | undefined>;
|
|
89
|
+
};
|
|
90
|
+
ZORA_CORE_PLUGIN_METADATA: Record<string, unknown>;
|
|
87
91
|
ZORA_COMPONENT_META: Record<string, ComponentMetadata | undefined>;
|
|
88
92
|
ZORA_THEME_RECIPE_META: Record<string, RecipeMetadata | undefined>;
|
|
89
93
|
}
|
|
@@ -119,7 +123,7 @@ const OWNER_RELEASES = {
|
|
|
119
123
|
colorTheory: { packageName: '@ankhorage/color-theory', minimumVersion: '0.3.0' },
|
|
120
124
|
contracts: { packageName: '@ankhorage/contracts', minimumVersion: '10.1.0' },
|
|
121
125
|
templates: { packageName: '@ankhorage/templates', minimumVersion: '9.3.0' },
|
|
122
|
-
zora: { packageName: '@ankhorage/zora', minimumVersion: '4.
|
|
126
|
+
zora: { packageName: '@ankhorage/zora', minimumVersion: '4.3.0' },
|
|
123
127
|
};
|
|
124
128
|
|
|
125
129
|
const OWNER_REQUIREMENTS = {
|
|
@@ -155,7 +159,12 @@ const OWNER_REQUIREMENTS = {
|
|
|
155
159
|
zoraMetadata: {
|
|
156
160
|
...OWNER_RELEASES.zora,
|
|
157
161
|
specifier: '@ankhorage/zora/metadata',
|
|
158
|
-
exports: [
|
|
162
|
+
exports: [
|
|
163
|
+
'composeZoraPluginMetadata',
|
|
164
|
+
'ZORA_COMPONENT_META',
|
|
165
|
+
'ZORA_CORE_PLUGIN_METADATA',
|
|
166
|
+
'ZORA_THEME_RECIPE_META',
|
|
167
|
+
],
|
|
159
168
|
},
|
|
160
169
|
};
|
|
161
170
|
|
|
@@ -171,27 +180,81 @@ export async function loadOwnerApis(targetDirectory = process.cwd()) {
|
|
|
171
180
|
const templates = await loadOwnerModule(targetDirectory, OWNER_REQUIREMENTS.templates);
|
|
172
181
|
const zoraTheme = await loadOwnerModule(targetDirectory, OWNER_REQUIREMENTS.zoraTheme);
|
|
173
182
|
const zoraMetadata = await loadOwnerModule(targetDirectory, OWNER_REQUIREMENTS.zoraMetadata);
|
|
183
|
+
const installedPluginMetadata = await loadInstalledZoraPluginMetadata(targetDirectory);
|
|
174
184
|
assertColorTheoryApi(colorTheory.module);
|
|
175
185
|
assertContractsApi(contracts.module);
|
|
176
186
|
assertTemplatesApi(templates.module);
|
|
177
187
|
assertZoraThemeApi(zoraTheme.module);
|
|
178
188
|
assertZoraMetadataApi(zoraMetadata.module);
|
|
189
|
+
const composedZoraMetadata = zoraMetadata.module.composeZoraPluginMetadata([
|
|
190
|
+
zoraMetadata.module.ZORA_CORE_PLUGIN_METADATA,
|
|
191
|
+
...installedPluginMetadata.map((entry) => entry.metadata),
|
|
192
|
+
]);
|
|
193
|
+
assertRecord(composedZoraMetadata, 'composed ZORA plugin metadata');
|
|
194
|
+
assertRecord(composedZoraMetadata.componentMeta, 'composed ZORA component metadata');
|
|
179
195
|
|
|
180
196
|
return {
|
|
181
197
|
colorTheory: colorTheory.module,
|
|
182
198
|
contracts: contracts.module,
|
|
183
199
|
templates: templates.module,
|
|
184
200
|
zoraTheme: zoraTheme.module,
|
|
185
|
-
zoraMetadata:
|
|
201
|
+
zoraMetadata: {
|
|
202
|
+
...zoraMetadata.module,
|
|
203
|
+
ZORA_COMPONENT_META: composedZoraMetadata.componentMeta,
|
|
204
|
+
},
|
|
186
205
|
versions: {
|
|
187
206
|
colorTheory: colorTheory.version,
|
|
188
207
|
contracts: contracts.version,
|
|
189
208
|
templates: templates.version,
|
|
190
209
|
zora: zoraTheme.version,
|
|
210
|
+
plugins: Object.fromEntries(
|
|
211
|
+
installedPluginMetadata.map((entry) => [entry.packageName, entry.version]),
|
|
212
|
+
),
|
|
191
213
|
},
|
|
192
214
|
};
|
|
193
215
|
}
|
|
194
216
|
|
|
217
|
+
/*** Load metadata-only descriptors for every installed ZORA plugin declared by the target package. */
|
|
218
|
+
async function loadInstalledZoraPluginMetadata(
|
|
219
|
+
targetDirectory: string,
|
|
220
|
+
): Promise<{ metadata: Record<string, unknown>; packageName: string; version: string }[]> {
|
|
221
|
+
const targetManifestPath = join(resolve(targetDirectory), 'package.json');
|
|
222
|
+
const targetManifest: unknown = JSON.parse(await readFile(targetManifestPath, 'utf8'));
|
|
223
|
+
assertRecord(targetManifest, 'Target package manifest');
|
|
224
|
+
const packageNames = new Set<string>();
|
|
225
|
+
if (typeof targetManifest.name === 'string') packageNames.add(targetManifest.name);
|
|
226
|
+
for (const field of ['dependencies', 'devDependencies', 'peerDependencies']) {
|
|
227
|
+
const dependencies = targetManifest[field];
|
|
228
|
+
if (!isRecord(dependencies)) continue;
|
|
229
|
+
for (const packageName of Object.keys(dependencies)) packageNames.add(packageName);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const loaded: { metadata: Record<string, unknown>; packageName: string; version: string }[] = [];
|
|
233
|
+
for (const packageName of [...packageNames].filter(isZoraPluginPackage).sort()) {
|
|
234
|
+
const minimumVersion =
|
|
235
|
+
packageName === '@ankhorage/zora-chess'
|
|
236
|
+
? '0.2.0'
|
|
237
|
+
: packageName === '@ankhorage/zora-tabletop'
|
|
238
|
+
? '0.1.0'
|
|
239
|
+
: '0.0.0';
|
|
240
|
+
const requirement: OwnerRequirement = {
|
|
241
|
+
packageName,
|
|
242
|
+
minimumVersion,
|
|
243
|
+
specifier: `${packageName}/metadata`,
|
|
244
|
+
exports: ['ZORA_PLUGIN_METADATA'],
|
|
245
|
+
};
|
|
246
|
+
const plugin = await loadOwnerModule(targetDirectory, requirement);
|
|
247
|
+
const metadata = plugin.module.ZORA_PLUGIN_METADATA;
|
|
248
|
+
assertRecord(metadata, `${packageName} ZORA plugin metadata`);
|
|
249
|
+
loaded.push({ metadata, packageName, version: plugin.version });
|
|
250
|
+
}
|
|
251
|
+
return loaded;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function isZoraPluginPackage(packageName: string): boolean {
|
|
255
|
+
return packageName.startsWith('@ankhorage/zora-') && packageName !== '@ankhorage/zora';
|
|
256
|
+
}
|
|
257
|
+
|
|
195
258
|
/*** Load only Contracts for tooling that runs before another owner package has been built. */
|
|
196
259
|
export async function loadContractsApi(targetDirectory = process.cwd()): Promise<ContractsApi> {
|
|
197
260
|
const loaded = await loadOwnerModule(targetDirectory, OWNER_REQUIREMENTS.contracts);
|
|
@@ -661,6 +724,10 @@ function assertZoraThemeApi(value: Record<string, unknown>): asserts value is Zo
|
|
|
661
724
|
|
|
662
725
|
/*** Narrow released ZORA metadata into only the fields the orchestration needs. */
|
|
663
726
|
function assertZoraMetadataApi(value: Record<string, unknown>): asserts value is ZoraMetadataApi {
|
|
727
|
+
if (typeof value.composeZoraPluginMetadata !== 'function') {
|
|
728
|
+
throw new Error('composeZoraPluginMetadata must be a function.');
|
|
729
|
+
}
|
|
730
|
+
assertRecord(value.ZORA_CORE_PLUGIN_METADATA, 'ZORA_CORE_PLUGIN_METADATA');
|
|
664
731
|
assertRecord(value.ZORA_COMPONENT_META, 'ZORA_COMPONENT_META');
|
|
665
732
|
for (const [name, metadata] of Object.entries(value.ZORA_COMPONENT_META)) {
|
|
666
733
|
assertComponentMetadata(metadata, name);
|
|
@@ -696,7 +763,9 @@ function assertEventMetadata(value: unknown, name: string): asserts value is Eve
|
|
|
696
763
|
assertRecord(value, `ZORA event metadata ${name}`);
|
|
697
764
|
assertNonEmptyString(value.eventType, `ZORA event metadata ${name}.eventType`);
|
|
698
765
|
assertNonEmptyString(value.label, `ZORA event metadata ${name}.label`);
|
|
699
|
-
|
|
766
|
+
if (value.description !== undefined) {
|
|
767
|
+
assertNonEmptyString(value.description, `ZORA event metadata ${name}.description`);
|
|
768
|
+
}
|
|
700
769
|
}
|
|
701
770
|
|
|
702
771
|
/*** Validate one theme recipe metadata entry and its supported fields. */
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function createThemeConfig(): Record<string, unknown>;
|
|
2
|
+
export declare const TEMPLATES_FIXTURE_SOURCE: string;
|
|
3
|
+
export declare const COLOR_THEORY_FIXTURE_SOURCE = "\nexport const COLOR_HARMONIES = ['monochromatic', 'complementary'];\nexport const COLOR_HARMONY_CATALOG = [\n { id: 'monochromatic', label: 'Monochromatic', description: 'One hue.' },\n { id: 'complementary', label: 'Complementary', description: 'Opposing hues.' },\n];\n";
|
|
4
|
+
export declare const CONTRACTS_FIXTURE_SOURCE = "\nexport const APP_CATEGORIES = ['business_productivity'];\nexport const NAVIGATOR_TYPES = ['stack', 'tabs', 'drawer'];\n";
|
|
5
|
+
export declare const ZORA_THEME_FIXTURE_SOURCE = "\nexport const compileZoraTheme = (themeConfig) => ({\n themeConfig,\n light: { surfaceTheme: { mode: 'light' }, diagnostics: [] },\n dark: { surfaceTheme: { mode: 'dark' }, diagnostics: [] },\n diagnostics: [],\n});\n";
|
|
6
|
+
export declare const ZORA_METADATA_FIXTURE_SOURCE = "\nexport const ZORA_COMPONENT_META = {\n Screen: {\n name: 'Screen',\n category: 'layout',\n description: 'Screen layout root',\n directManifestNode: true,\n allowedChildren: ['View', 'Box', 'Text', 'MissingElement'],\n props: {},\n },\n View: {\n name: 'View',\n category: 'layout',\n directManifestNode: true,\n allowedChildren: ['Text', 'Box'],\n props: {},\n },\n Box: {\n name: 'Box',\n category: 'foundation',\n directManifestNode: true,\n allowedChildren: [],\n props: {},\n },\n Text: {\n name: 'Text',\n category: 'component',\n directManifestNode: true,\n allowedChildren: [],\n props: { text: { type: 'string' } },\n events: {\n press: {\n eventType: 'text.press',\n label: 'Press',\n description: 'Text was pressed.',\n },\n },\n },\n MissingElement: {\n name: 'MissingElement',\n category: 'pattern',\n directManifestNode: true,\n allowedChildren: [],\n manifestPolicy: { kind: 'unresolved-element', availability: 'draft-only', releaseGate: 'blocked' },\n blueprint: { defaultProps: { requestedCapability: 'Unresolved', reason: 'No exact element.' } },\n props: {\n requestedCapability: { type: 'string' },\n reason: { type: 'string' },\n evidenceId: { type: 'string' },\n },\n },\n};\nexport const ZORA_CORE_PLUGIN_METADATA = {\n packageName: '@ankhorage/zora',\n componentMeta: ZORA_COMPONENT_META,\n extensionHosts: ['Screen', 'View', 'Box'],\n};\nexport const composeZoraPluginMetadata = (plugins) => {\n const componentMeta = {};\n const extensionHosts = new Set();\n for (const plugin of [...plugins].sort((left, right) => left.packageName.localeCompare(right.packageName))) {\n Object.assign(componentMeta, plugin.componentMeta);\n for (const host of plugin.extensionHosts ?? []) extensionHosts.add(host);\n }\n for (const plugin of plugins) {\n for (const placement of plugin.placements ?? []) {\n for (const parent of placement.parents) {\n if (!extensionHosts.has(parent)) throw new Error('Invalid extension host');\n componentMeta[parent] = {\n ...componentMeta[parent],\n allowedChildren: [...componentMeta[parent].allowedChildren, placement.child],\n };\n }\n }\n }\n return { componentMeta };\n};\nexport const ZORA_THEME_RECIPE_META = {\n Card: {\n name: 'Card',\n kind: 'component',\n fields: { variant: { type: 'choice', options: ['filled', 'outlined'] } },\n },\n};\n";
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
export function createThemeConfig() {
|
|
2
|
+
return {
|
|
3
|
+
id: 'evidence-theme',
|
|
4
|
+
name: 'Evidence Theme',
|
|
5
|
+
light: { primaryColor: '#2563EB', harmony: 'complementary' },
|
|
6
|
+
dark: { primaryColor: '#2563EB', harmony: 'complementary' },
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export const TEMPLATES_FIXTURE_SOURCE = `
|
|
10
|
+
export const CATEGORY_PRESETS = {
|
|
11
|
+
business_productivity: {
|
|
12
|
+
category: 'business_productivity',
|
|
13
|
+
label: 'Business',
|
|
14
|
+
recommendedPrimaryColors: ['#2563EB'],
|
|
15
|
+
recommendedHarmonies: ['complementary'],
|
|
16
|
+
tonePairs: { light: 'jewel-on-neutral-light', dark: 'pastel-on-neutral-dark' },
|
|
17
|
+
density: 'compact',
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
export const TONE_PAIR_CATALOG = [];
|
|
21
|
+
export const resolveTonePair = () => null;
|
|
22
|
+
export const resolveCategoryDesignPreset = (category, theme = {}) => ({ category, theme });
|
|
23
|
+
const themeConfig = ${JSON.stringify(createThemeConfig())};
|
|
24
|
+
export const compileCategoryDesign = (category) => ({
|
|
25
|
+
category,
|
|
26
|
+
themeConfig,
|
|
27
|
+
diagnostics: [],
|
|
28
|
+
computedTheme: {
|
|
29
|
+
themeConfig,
|
|
30
|
+
light: { surfaceTheme: { mode: 'light' }, diagnostics: [] },
|
|
31
|
+
dark: { surfaceTheme: { mode: 'dark' }, diagnostics: [] },
|
|
32
|
+
diagnostics: [],
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
export const composeCategoryAppManifest = (input) => ({
|
|
36
|
+
manifest: {
|
|
37
|
+
metadata: {
|
|
38
|
+
name: input.name ?? 'Generated App',
|
|
39
|
+
slug: input.slug ?? 'generated-app',
|
|
40
|
+
version: input.version ?? '1.0.0',
|
|
41
|
+
category: input.category,
|
|
42
|
+
themeId: 'evidence-theme',
|
|
43
|
+
},
|
|
44
|
+
themes: [themeConfig],
|
|
45
|
+
activeThemeId: 'evidence-theme',
|
|
46
|
+
infra: { modules: input.modules ?? [] },
|
|
47
|
+
navigator: input.navigator,
|
|
48
|
+
screens: input.screens,
|
|
49
|
+
settings: { localization: { defaultLocale: 'en', locales: ['en'] } },
|
|
50
|
+
},
|
|
51
|
+
diagnostics: [],
|
|
52
|
+
status: input.authoringState === 'release' ? 'ready' : 'blocked',
|
|
53
|
+
authoringState: input.authoringState,
|
|
54
|
+
});
|
|
55
|
+
export const validateTemplateManifest = (manifest) => ({
|
|
56
|
+
manifest,
|
|
57
|
+
diagnostics: [],
|
|
58
|
+
status: 'ready',
|
|
59
|
+
authoringState: 'release',
|
|
60
|
+
});
|
|
61
|
+
export const assertTemplateManifestReady = (composition) => composition.manifest;
|
|
62
|
+
`;
|
|
63
|
+
export const COLOR_THEORY_FIXTURE_SOURCE = `
|
|
64
|
+
export const COLOR_HARMONIES = ['monochromatic', 'complementary'];
|
|
65
|
+
export const COLOR_HARMONY_CATALOG = [
|
|
66
|
+
{ id: 'monochromatic', label: 'Monochromatic', description: 'One hue.' },
|
|
67
|
+
{ id: 'complementary', label: 'Complementary', description: 'Opposing hues.' },
|
|
68
|
+
];
|
|
69
|
+
`;
|
|
70
|
+
export const CONTRACTS_FIXTURE_SOURCE = `
|
|
71
|
+
export const APP_CATEGORIES = ['business_productivity'];
|
|
72
|
+
export const NAVIGATOR_TYPES = ['stack', 'tabs', 'drawer'];
|
|
73
|
+
`;
|
|
74
|
+
export const ZORA_THEME_FIXTURE_SOURCE = `
|
|
75
|
+
export const compileZoraTheme = (themeConfig) => ({
|
|
76
|
+
themeConfig,
|
|
77
|
+
light: { surfaceTheme: { mode: 'light' }, diagnostics: [] },
|
|
78
|
+
dark: { surfaceTheme: { mode: 'dark' }, diagnostics: [] },
|
|
79
|
+
diagnostics: [],
|
|
80
|
+
});
|
|
81
|
+
`;
|
|
82
|
+
export const ZORA_METADATA_FIXTURE_SOURCE = `
|
|
83
|
+
export const ZORA_COMPONENT_META = {
|
|
84
|
+
Screen: {
|
|
85
|
+
name: 'Screen',
|
|
86
|
+
category: 'layout',
|
|
87
|
+
description: 'Screen layout root',
|
|
88
|
+
directManifestNode: true,
|
|
89
|
+
allowedChildren: ['View', 'Box', 'Text', 'MissingElement'],
|
|
90
|
+
props: {},
|
|
91
|
+
},
|
|
92
|
+
View: {
|
|
93
|
+
name: 'View',
|
|
94
|
+
category: 'layout',
|
|
95
|
+
directManifestNode: true,
|
|
96
|
+
allowedChildren: ['Text', 'Box'],
|
|
97
|
+
props: {},
|
|
98
|
+
},
|
|
99
|
+
Box: {
|
|
100
|
+
name: 'Box',
|
|
101
|
+
category: 'foundation',
|
|
102
|
+
directManifestNode: true,
|
|
103
|
+
allowedChildren: [],
|
|
104
|
+
props: {},
|
|
105
|
+
},
|
|
106
|
+
Text: {
|
|
107
|
+
name: 'Text',
|
|
108
|
+
category: 'component',
|
|
109
|
+
directManifestNode: true,
|
|
110
|
+
allowedChildren: [],
|
|
111
|
+
props: { text: { type: 'string' } },
|
|
112
|
+
events: {
|
|
113
|
+
press: {
|
|
114
|
+
eventType: 'text.press',
|
|
115
|
+
label: 'Press',
|
|
116
|
+
description: 'Text was pressed.',
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
MissingElement: {
|
|
121
|
+
name: 'MissingElement',
|
|
122
|
+
category: 'pattern',
|
|
123
|
+
directManifestNode: true,
|
|
124
|
+
allowedChildren: [],
|
|
125
|
+
manifestPolicy: { kind: 'unresolved-element', availability: 'draft-only', releaseGate: 'blocked' },
|
|
126
|
+
blueprint: { defaultProps: { requestedCapability: 'Unresolved', reason: 'No exact element.' } },
|
|
127
|
+
props: {
|
|
128
|
+
requestedCapability: { type: 'string' },
|
|
129
|
+
reason: { type: 'string' },
|
|
130
|
+
evidenceId: { type: 'string' },
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
export const ZORA_CORE_PLUGIN_METADATA = {
|
|
135
|
+
packageName: '@ankhorage/zora',
|
|
136
|
+
componentMeta: ZORA_COMPONENT_META,
|
|
137
|
+
extensionHosts: ['Screen', 'View', 'Box'],
|
|
138
|
+
};
|
|
139
|
+
export const composeZoraPluginMetadata = (plugins) => {
|
|
140
|
+
const componentMeta = {};
|
|
141
|
+
const extensionHosts = new Set();
|
|
142
|
+
for (const plugin of [...plugins].sort((left, right) => left.packageName.localeCompare(right.packageName))) {
|
|
143
|
+
Object.assign(componentMeta, plugin.componentMeta);
|
|
144
|
+
for (const host of plugin.extensionHosts ?? []) extensionHosts.add(host);
|
|
145
|
+
}
|
|
146
|
+
for (const plugin of plugins) {
|
|
147
|
+
for (const placement of plugin.placements ?? []) {
|
|
148
|
+
for (const parent of placement.parents) {
|
|
149
|
+
if (!extensionHosts.has(parent)) throw new Error('Invalid extension host');
|
|
150
|
+
componentMeta[parent] = {
|
|
151
|
+
...componentMeta[parent],
|
|
152
|
+
allowedChildren: [...componentMeta[parent].allowedChildren, placement.child],
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return { componentMeta };
|
|
158
|
+
};
|
|
159
|
+
export const ZORA_THEME_RECIPE_META = {
|
|
160
|
+
Card: {
|
|
161
|
+
name: 'Card',
|
|
162
|
+
kind: 'component',
|
|
163
|
+
fields: { variant: { type: 'choice', options: ['filled', 'outlined'] } },
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ankhorage/devtools",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.0",
|
|
4
4
|
"description": "Shared development tools and repository standards for Ankhorage",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/ankhorage/devtools#readme",
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
"version-packages": "bun src/cli/bin/changeset.ts version"
|
|
106
106
|
},
|
|
107
107
|
"dependencies": {
|
|
108
|
-
"@ankhorage/utility": "^0.
|
|
108
|
+
"@ankhorage/utility": "^0.5.0",
|
|
109
109
|
"@changesets/cli": "^3.0.2",
|
|
110
110
|
"@eslint/compat": "^2.1.1",
|
|
111
111
|
"@eslint/js": "^10.0.1",
|
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
"eslint-plugin-import": "^2.32.0",
|
|
115
115
|
"eslint-plugin-prettier": "^5.5.6",
|
|
116
116
|
"eslint-plugin-react": "^7.37.5",
|
|
117
|
-
"eslint-plugin-react-hooks": "^7.
|
|
117
|
+
"eslint-plugin-react-hooks": "^7.0.0",
|
|
118
118
|
"eslint-plugin-react-native": "^5.0.0",
|
|
119
119
|
"eslint-plugin-security": "^4.0.1",
|
|
120
120
|
"eslint-plugin-simple-import-sort": "^14.0.0",
|
|
@@ -126,8 +126,10 @@
|
|
|
126
126
|
"devDependencies": {
|
|
127
127
|
"@ankhorage/ankh": "^0.8.11",
|
|
128
128
|
"@ankhorage/doctor": "0.10.8",
|
|
129
|
+
"@techstark/opencv-js": "^5.0.0-release.1",
|
|
129
130
|
"@types/bun": "^1.4.1",
|
|
130
131
|
"@types/node": "^26.4.1",
|
|
132
|
+
"sharp": "^0.35.4",
|
|
131
133
|
"typescript": "~6.0.3"
|
|
132
134
|
},
|
|
133
135
|
"packageManager": "bun@1.4.2"
|