@sdd-method/maintain-cli 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/NOTICE +17 -0
- package/README.md +6 -2
- package/dist/lib/adr-origin.js +59 -0
- package/dist/lib/agent-harness.js +28 -0
- package/dist/lib/briefing/index.js +13 -0
- package/dist/lib/briefing/render.js +123 -0
- package/dist/lib/briefing/variables.js +92 -0
- package/dist/lib/bundle.js +123 -0
- package/dist/lib/catalogue/aggregate.js +2404 -0
- package/dist/lib/catalogue/aggregation-manifest-loader.js +173 -0
- package/dist/lib/catalogue/application-archetype/expectations.js +119 -0
- package/dist/lib/catalogue/application-archetype/vocabulary.js +177 -0
- package/dist/lib/catalogue/archetype/maps/android.yaml +66 -0
- package/dist/lib/catalogue/archetype/maps/swiftui.yaml +58 -0
- package/dist/lib/catalogue/archetype/maps/uikit.yaml +48 -0
- package/dist/lib/catalogue/archetype/maps/web-generic.yaml +86 -0
- package/dist/lib/catalogue/archetype/openui-archetype-vocabulary.yaml +46 -0
- package/dist/lib/catalogue/archetype/resolver.js +103 -0
- package/dist/lib/catalogue/archetype/wireframe.js +418 -0
- package/dist/lib/catalogue/baseline-check.js +151 -0
- package/dist/lib/catalogue/build.js +1502 -0
- package/dist/lib/catalogue/builders/adr-supersedes.js +103 -0
- package/dist/lib/catalogue/builders/adrs.js +222 -0
- package/dist/lib/catalogue/builders/application-conformance-coverage.js +47 -0
- package/dist/lib/catalogue/builders/application-data-plane-bindings.js +115 -0
- package/dist/lib/catalogue/builders/c4-components.js +229 -0
- package/dist/lib/catalogue/builders/c4-containers.js +240 -0
- package/dist/lib/catalogue/builders/c4-entity-rollups.js +87 -0
- package/dist/lib/catalogue/builders/c4-external-systems.js +194 -0
- package/dist/lib/catalogue/builders/c4-l1-candidates.js +107 -0
- package/dist/lib/catalogue/builders/c4-systems.js +98 -0
- package/dist/lib/catalogue/builders/capabilities.js +488 -0
- package/dist/lib/catalogue/builders/capability-domain-model.js +74 -0
- package/dist/lib/catalogue/builders/capability-edges.js +256 -0
- package/dist/lib/catalogue/builders/capability-spec-walker.js +99 -0
- package/dist/lib/catalogue/builders/capability-spec.js +348 -0
- package/dist/lib/catalogue/builders/container-dependency.js +183 -0
- package/dist/lib/catalogue/builders/contracts.js +402 -0
- package/dist/lib/catalogue/builders/data-items.js +344 -0
- package/dist/lib/catalogue/builders/data-store-mapping.js +113 -0
- package/dist/lib/catalogue/builders/domain-models.js +175 -0
- package/dist/lib/catalogue/builders/domains.js +119 -0
- package/dist/lib/catalogue/builders/edge-label-merge.js +19 -0
- package/dist/lib/catalogue/builders/entities-manifest.js +160 -0
- package/dist/lib/catalogue/builders/entities-mermaid.js +189 -0
- package/dist/lib/catalogue/builders/entity-persistence.js +102 -0
- package/dist/lib/catalogue/builders/entity-slug.js +47 -0
- package/dist/lib/catalogue/builders/event-catalog-walker.js +186 -0
- package/dist/lib/catalogue/builders/event-edges.js +327 -0
- package/dist/lib/catalogue/builders/events.js +168 -0
- package/dist/lib/catalogue/builders/evidence-class.js +43 -0
- package/dist/lib/catalogue/builders/evidence-target-repo.js +67 -0
- package/dist/lib/catalogue/builders/external-system-exemption.js +120 -0
- package/dist/lib/catalogue/builders/feature-contract.js +77 -0
- package/dist/lib/catalogue/builders/feature-domain-model.js +50 -0
- package/dist/lib/catalogue/builders/feature-grounding-walker.js +254 -0
- package/dist/lib/catalogue/builders/feature-grounding.js +391 -0
- package/dist/lib/catalogue/builders/feature-groups.js +63 -0
- package/dist/lib/catalogue/builders/feature-subscription-channels.js +228 -0
- package/dist/lib/catalogue/builders/feature-tree-walker.js +295 -0
- package/dist/lib/catalogue/builders/features.js +300 -0
- package/dist/lib/catalogue/builders/glossary-xref.js +378 -0
- package/dist/lib/catalogue/builders/glossary.js +204 -0
- package/dist/lib/catalogue/builders/implementations.js +214 -0
- package/dist/lib/catalogue/builders/index.js +64 -0
- package/dist/lib/catalogue/builders/integration-components.js +344 -0
- package/dist/lib/catalogue/builders/integrations.js +181 -0
- package/dist/lib/catalogue/builders/persona-c4system.js +149 -0
- package/dist/lib/catalogue/builders/persona-container.js +133 -0
- package/dist/lib/catalogue/builders/persona-journey-edges.js +133 -0
- package/dist/lib/catalogue/builders/personas.js +152 -0
- package/dist/lib/catalogue/builders/product-edges.js +106 -0
- package/dist/lib/catalogue/builders/product-platform-dependencies.js +405 -0
- package/dist/lib/catalogue/builders/products.js +162 -0
- package/dist/lib/catalogue/builders/quality-scores.js +635 -0
- package/dist/lib/catalogue/builders/repo-capability-exemption.js +61 -0
- package/dist/lib/catalogue/builders/repo-dependency.js +127 -0
- package/dist/lib/catalogue/builders/repo-edges.js +340 -0
- package/dist/lib/catalogue/builders/repositories.js +458 -0
- package/dist/lib/catalogue/builders/scaffolds.js +197 -0
- package/dist/lib/catalogue/builders/screen-tree.js +222 -0
- package/dist/lib/catalogue/builders/screens.js +396 -0
- package/dist/lib/catalogue/builders/service-capability-exemption.js +63 -0
- package/dist/lib/catalogue/builders/service-contract-consumes.js +186 -0
- package/dist/lib/catalogue/builders/service-contract.js +74 -0
- package/dist/lib/catalogue/builders/service-entity-tables.js +187 -0
- package/dist/lib/catalogue/builders/services.js +232 -0
- package/dist/lib/catalogue/builders/system-external-dependency.js +115 -0
- package/dist/lib/catalogue/builders/user-journeys.js +253 -0
- package/dist/lib/catalogue/canonical-schema.yaml +4117 -0
- package/dist/lib/catalogue/cli-version.js +10 -0
- package/dist/lib/catalogue/contract-id.js +49 -0
- package/dist/lib/catalogue/contract-path-resolution.js +149 -0
- package/dist/lib/catalogue/coverage-predicates.js +603 -0
- package/dist/lib/catalogue/coverage-render.js +552 -0
- package/dist/lib/catalogue/coverage-run.js +115 -0
- package/dist/lib/catalogue/csv-reader.js +97 -0
- package/dist/lib/catalogue/csv-writer.js +96 -0
- package/dist/lib/catalogue/data-item-coverage.js +142 -0
- package/dist/lib/catalogue/data-plane-coherence.js +77 -0
- package/dist/lib/catalogue/ddl-generator.js +233 -0
- package/dist/lib/catalogue/dedupe-keyed-tables.js +0 -0
- package/dist/lib/catalogue/engine-validator.js +239 -0
- package/dist/lib/catalogue/ext-id.js +76 -0
- package/dist/lib/catalogue/feature-grounding-aliases-loader.js +65 -0
- package/dist/lib/catalogue/feature-repo-zero-resolution-summary.js +31 -0
- package/dist/lib/catalogue/finding-identity.js +183 -0
- package/dist/lib/catalogue/findings.js +46 -0
- package/dist/lib/catalogue/fk-validator.js +136 -0
- package/dist/lib/catalogue/freshness.js +69 -0
- package/dist/lib/catalogue/gate-checks.js +338 -0
- package/dist/lib/catalogue/graph-projection.js +346 -0
- package/dist/lib/catalogue/graph-run.js +64 -0
- package/dist/lib/catalogue/impact-run.js +228 -0
- package/dist/lib/catalogue/manifest-loader.js +435 -0
- package/dist/lib/catalogue/platform-scope.js +7 -0
- package/dist/lib/catalogue/population-checks.js +87 -0
- package/dist/lib/catalogue/relationship-semantics.js +66 -0
- package/dist/lib/catalogue/reverse-product-quality.js +99 -0
- package/dist/lib/catalogue/rubric-coverage-map.yaml +871 -0
- package/dist/lib/catalogue/runway-vocabulary-loader.js +56 -0
- package/dist/lib/catalogue/scaffold-delta.js +261 -0
- package/dist/lib/catalogue/schema-compat.js +52 -0
- package/dist/lib/catalogue/schema-loader.js +87 -0
- package/dist/lib/catalogue/scores-coverage.js +573 -0
- package/dist/lib/catalogue/screen-validation-loader.js +89 -0
- package/dist/lib/catalogue/service-architecture-parser.js +193 -0
- package/dist/lib/catalogue/service-contract-aliases-loader.js +109 -0
- package/dist/lib/catalogue/staleness-verdicts.js +345 -0
- package/dist/lib/catalogue/traceability-loader.js +350 -0
- package/dist/lib/catalogue/validate.js +200 -0
- package/dist/lib/catalogue/version.js +18 -0
- package/dist/lib/ci-binding/index.js +220 -0
- package/dist/lib/ci-binding/module.js +204 -0
- package/dist/lib/conformance/binding.js +70 -0
- package/dist/lib/context-pull/index.js +151 -0
- package/dist/lib/context-pull/resolve.js +70 -0
- package/dist/lib/generate-briefing/index.js +77 -0
- package/dist/lib/hooks-seed/copilot.js +167 -0
- package/dist/lib/hooks-seed/entries.js +47 -0
- package/dist/lib/hooks-seed/index.js +17 -0
- package/dist/lib/hooks-seed/lifecycle-mapping.js +28 -0
- package/dist/lib/hooks-seed/merge.js +128 -0
- package/dist/lib/hooks-seed/types.js +20 -0
- package/dist/lib/init/ci-workflow.js +98 -0
- package/dist/lib/list/discover.js +100 -0
- package/dist/lib/list/format.js +84 -0
- package/dist/lib/list/index.js +16 -0
- package/dist/lib/lock-file.js +437 -0
- package/dist/lib/mcp-seed/entries.js +14 -0
- package/dist/lib/mcp-seed/index.js +28 -0
- package/dist/lib/mcp-seed/merge.js +99 -0
- package/dist/lib/mcp-seed/types.js +13 -0
- package/dist/lib/pack/builder.js +303 -0
- package/dist/lib/pack/index.js +87 -0
- package/dist/lib/pack/leakage.js +144 -0
- package/dist/lib/pack/log.js +18 -0
- package/dist/lib/pack/publish-transform.js +233 -0
- package/dist/lib/pack/reference-closure.js +125 -0
- package/dist/lib/pack/schema.js +121 -0
- package/dist/lib/pack/seed.js +182 -0
- package/dist/lib/pack/validate.js +49 -0
- package/dist/lib/package-version.js +92 -0
- package/dist/lib/repo-kind.js +129 -0
- package/dist/lib/scripts.js +7 -0
- package/dist/lib/snapshot/catalogue-diff.js +219 -0
- package/dist/lib/snapshot/manifest.js +206 -0
- package/dist/lib/sync/adr-transitions.js +250 -0
- package/dist/lib/sync/apply.js +91 -0
- package/dist/lib/sync/briefing-merge.js +90 -0
- package/dist/lib/sync/bundle-cache.js +109 -0
- package/dist/lib/sync/bundle-source.js +61 -0
- package/dist/lib/sync/config.js +72 -0
- package/dist/lib/sync/copilot-hooks-merge.js +108 -0
- package/dist/lib/sync/diff.js +251 -0
- package/dist/lib/sync/index.js +818 -0
- package/dist/lib/sync/settings-merge.js +216 -0
- package/dist/lib/sync/yaml-block-merge.js +120 -0
- package/dist/lib/validate/aggregate.js +51 -0
- package/dist/lib/validate/bundle-coherence.js +74 -0
- package/dist/lib/validate/dispatch.js +243 -0
- package/dist/lib/validate/index.js +110 -0
- package/dist/lib/validate/plan.js +54 -0
- package/dist/lib/validate/runner.js +85 -0
- package/dist/lib/validate/yaml-parse.js +112 -0
- package/dist/maintain-bin.js +8 -0
- package/dist/maintain-program.js +91 -0
- package/dist/maintain-version.json +4 -0
- package/dist/verbs/catalogue-maintain.js +110 -0
- package/dist/verbs/catalogue-verify.js +114 -0
- package/dist/verbs/context.js +39 -0
- package/dist/verbs/generate-briefing.js +29 -0
- package/dist/verbs/list.js +45 -0
- package/dist/verbs/pack-build.js +31 -0
- package/dist/verbs/pack-validate.js +17 -0
- package/dist/verbs/path-option.js +25 -0
- package/dist/verbs/render.js +87 -0
- package/dist/verbs/sync.js +110 -0
- package/dist/verbs/validate.js +48 -0
- package/dist/verify-program.js +122 -0
- package/dist/verify-version.json +4 -0
- package/package.json +32 -4
- package/scripts/validate-app-dependencies.sh +664 -0
- package/scripts/validate-app-docs.sh +199 -0
- package/scripts/validate-integration-docs.sh +505 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Default archetype map — web-generic (ADR 0169 §4.2). Method-owned.
|
|
2
|
+
#
|
|
3
|
+
# Keys off component NAMES (which the builder already parses), not design-system source.
|
|
4
|
+
# Vendor-standard / common-DS tokens ONLY — never an adopter's bespoke vocabulary.
|
|
5
|
+
# Resolution order (ADR 0169 §4.3): adopter overlay override -> by_token -> by_suffix_heuristic -> default.
|
|
6
|
+
# Selected per screen by `platform: web-generic` (or web) frontmatter.
|
|
7
|
+
|
|
8
|
+
map_version: "1.0.0"
|
|
9
|
+
platform: web-generic
|
|
10
|
+
|
|
11
|
+
# Stripped (case-insensitive) before token lookup, so `MuiButton`/`ChButton` -> `Button`.
|
|
12
|
+
strip_prefixes: [Mui, Ch, Ds, Ui, App, Base, Core, V]
|
|
13
|
+
|
|
14
|
+
by_token:
|
|
15
|
+
Container: grid
|
|
16
|
+
Grid: grid
|
|
17
|
+
Row: row
|
|
18
|
+
Col: col
|
|
19
|
+
Column: col
|
|
20
|
+
Stack: stack
|
|
21
|
+
Flex: row
|
|
22
|
+
Scroll: scroll
|
|
23
|
+
ScrollArea: scroll
|
|
24
|
+
Tabs: tab-strip
|
|
25
|
+
Tab: tab
|
|
26
|
+
TabPanel: pane
|
|
27
|
+
Table: table
|
|
28
|
+
DataGrid: table
|
|
29
|
+
List: list
|
|
30
|
+
ListItem: list-item
|
|
31
|
+
Menu: list
|
|
32
|
+
Card: card
|
|
33
|
+
Panel: panel
|
|
34
|
+
Dialog: modal
|
|
35
|
+
Modal: modal
|
|
36
|
+
Popup: modal
|
|
37
|
+
Popover: modal
|
|
38
|
+
Drawer: sidebar
|
|
39
|
+
SidePanel: sidebar
|
|
40
|
+
Sidebar: sidebar
|
|
41
|
+
Nav: nav-bar
|
|
42
|
+
Navbar: nav-bar
|
|
43
|
+
AppBar: app-bar
|
|
44
|
+
Header: app-bar
|
|
45
|
+
Toolbar: app-bar
|
|
46
|
+
Alert: banner
|
|
47
|
+
Banner: banner
|
|
48
|
+
Toast: banner
|
|
49
|
+
Snackbar: banner
|
|
50
|
+
Sheet: sheet
|
|
51
|
+
BottomSheet: sheet
|
|
52
|
+
Btn: button
|
|
53
|
+
Button: button
|
|
54
|
+
IconButton: button
|
|
55
|
+
Fab: fab
|
|
56
|
+
ButtonGroup: button-group
|
|
57
|
+
SegmentedControl: segmented-control
|
|
58
|
+
Stat: text
|
|
59
|
+
StatCard: card
|
|
60
|
+
KpiBar: kpi-bar
|
|
61
|
+
Text: text
|
|
62
|
+
Typography: text
|
|
63
|
+
Label: text
|
|
64
|
+
Heading: text
|
|
65
|
+
Image: image
|
|
66
|
+
Img: image
|
|
67
|
+
Avatar: image
|
|
68
|
+
Icon: image
|
|
69
|
+
|
|
70
|
+
# Applied when no token matches. First matching suffix wins.
|
|
71
|
+
by_suffix_heuristic:
|
|
72
|
+
Button: button
|
|
73
|
+
Bar: app-bar
|
|
74
|
+
List: list
|
|
75
|
+
Grid: grid
|
|
76
|
+
Card: card
|
|
77
|
+
Panel: panel
|
|
78
|
+
Dialog: modal
|
|
79
|
+
Modal: modal
|
|
80
|
+
Layout: stack
|
|
81
|
+
Container: grid
|
|
82
|
+
Text: text
|
|
83
|
+
Image: image
|
|
84
|
+
Icon: image
|
|
85
|
+
|
|
86
|
+
default: panel
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# OpenUI Archetype Vocabulary — method-owned (ADR 0169 §4.1)
|
|
2
|
+
#
|
|
3
|
+
# A closed, platform-tagged superset of visual archetypes. An archetype assigns a
|
|
4
|
+
# component a VISUAL SHAPE + slots/orientation — never geometry or styling (stays inside
|
|
5
|
+
# the compose-only boundary, ADR 0166 §3). Default archetype maps
|
|
6
|
+
# (archetype-maps/*.yaml) resolve adopter component NAMES to entries here; the
|
|
7
|
+
# render-screen-wireframe skill lowers a resolved tree to SVG using `render`.
|
|
8
|
+
#
|
|
9
|
+
# `is_container` feeds ADR 0168 §4.1 isContainer when a resolved map is present.
|
|
10
|
+
|
|
11
|
+
vocabulary_version: "1.0.0"
|
|
12
|
+
|
|
13
|
+
archetypes:
|
|
14
|
+
|
|
15
|
+
# --- structural containers -------------------------------------------------
|
|
16
|
+
scaffold: { is_container: true, render: { slots: [header, main, footer], orientation: vertical } }
|
|
17
|
+
app-bar: { is_container: true, render: { shape: bar, position: top } }
|
|
18
|
+
nav-bar: { is_container: true, render: { shape: bar, position: top } }
|
|
19
|
+
tab-bar: { is_container: true, render: { shape: bar, position: bottom } }
|
|
20
|
+
sidebar: { is_container: true, render: { shape: rail, position: left } }
|
|
21
|
+
stack: { is_container: true, render: { orientation: vertical } }
|
|
22
|
+
row: { is_container: true, render: { orientation: horizontal } }
|
|
23
|
+
col: { is_container: true, render: { orientation: vertical } }
|
|
24
|
+
grid: { is_container: true, render: { shape: grid } }
|
|
25
|
+
scroll: { is_container: true, render: { orientation: vertical, scroll: true } }
|
|
26
|
+
list: { is_container: true, render: { orientation: vertical, repeat: true } }
|
|
27
|
+
table: { is_container: true, render: { shape: grid, header_row: true } }
|
|
28
|
+
card: { is_container: true, render: { shape: card } }
|
|
29
|
+
panel: { is_container: true, render: { shape: panel } } # opaque fallback
|
|
30
|
+
paged-panes: { is_container: true, render: { shape: paged, active_first: true } }
|
|
31
|
+
pane: { is_container: true, render: { shape: panel } }
|
|
32
|
+
tab-strip: { is_container: true, render: { orientation: horizontal, shape: chips } }
|
|
33
|
+
button-group: { is_container: true, render: { orientation: horizontal } }
|
|
34
|
+
segmented-control: { is_container: true, render: { orientation: horizontal, shape: chips } }
|
|
35
|
+
kpi-bar: { is_container: true, render: { orientation: horizontal, shape: chips } }
|
|
36
|
+
modal: { is_container: true, render: { shape: floating, overlay: true } }
|
|
37
|
+
sheet: { is_container: true, render: { shape: floating, position: bottom } }
|
|
38
|
+
banner: { is_container: true, render: { shape: bar } }
|
|
39
|
+
|
|
40
|
+
# --- leaves ----------------------------------------------------------------
|
|
41
|
+
tab: { is_container: false, render: { shape: chip } }
|
|
42
|
+
list-item: { is_container: false, render: { shape: row } }
|
|
43
|
+
button: { is_container: false, render: { shape: button } }
|
|
44
|
+
fab: { is_container: false, render: { shape: circle, position: bottom-right } }
|
|
45
|
+
text: { is_container: false, render: { shape: text } }
|
|
46
|
+
image: { is_container: false, render: { shape: image } }
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// OpenUI archetype resolver (per ADR 0169).
|
|
2
|
+
//
|
|
3
|
+
// Binds an adopter component NAME to a visual ARCHETYPE via a method-shipped
|
|
4
|
+
// platform default map plus an optional adopter overlay. The binding is what
|
|
5
|
+
// makes a shape-blind openui-lang block renderable as a wireframe, and it
|
|
6
|
+
// supplies the container/leaf classification the ADR 0168 `screen-root-is-leaf`
|
|
7
|
+
// check consumes (isContainerArchetype).
|
|
8
|
+
//
|
|
9
|
+
// Resolution order (ADR 0169 §4.3): adopter override -> default by_token (after
|
|
10
|
+
// stripping a vendor prefix) -> by_suffix_heuristic -> default (panel).
|
|
11
|
+
//
|
|
12
|
+
// The vocabulary + the four default maps are method-owned reference data,
|
|
13
|
+
// VENDORED from sdd-method (docs/method/methods/guides/openui-archetype-*.yaml /
|
|
14
|
+
// archetype-maps/) — the same vendoring relationship as canonical-schema.yaml.
|
|
15
|
+
// They use vendor-standard names only; never an adopter's bespoke vocabulary.
|
|
16
|
+
// copy-yaml-assets.mjs copies them next to the compiled JS at build time.
|
|
17
|
+
import { readFileSync } from "node:fs";
|
|
18
|
+
import { fileURLToPath } from "node:url";
|
|
19
|
+
import { dirname, join } from "node:path";
|
|
20
|
+
import { parse as parseYaml } from "yaml";
|
|
21
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
const PLATFORMS = new Set(["web-generic", "android", "swiftui", "uikit"]);
|
|
23
|
+
const FALLBACK_ARCHETYPE = "panel";
|
|
24
|
+
// Loaded once, cached. Reference data is immutable at runtime.
|
|
25
|
+
let vocabularyCache;
|
|
26
|
+
const mapCache = new Map();
|
|
27
|
+
function loadVocabulary() {
|
|
28
|
+
if (vocabularyCache === undefined) {
|
|
29
|
+
const raw = readFileSync(join(__dirname, "openui-archetype-vocabulary.yaml"), "utf-8");
|
|
30
|
+
const parsed = parseYaml(raw);
|
|
31
|
+
vocabularyCache = { archetypes: parsed?.archetypes ?? {} };
|
|
32
|
+
}
|
|
33
|
+
return vocabularyCache;
|
|
34
|
+
}
|
|
35
|
+
function loadMap(platform) {
|
|
36
|
+
let m = mapCache.get(platform);
|
|
37
|
+
if (m === undefined) {
|
|
38
|
+
const raw = readFileSync(join(__dirname, "maps", `${platform}.yaml`), "utf-8");
|
|
39
|
+
m = parseYaml(raw) ?? {};
|
|
40
|
+
mapCache.set(platform, m);
|
|
41
|
+
}
|
|
42
|
+
return m;
|
|
43
|
+
}
|
|
44
|
+
/** True when `platform` has a method-shipped default map. */
|
|
45
|
+
export function isKnownPlatform(platform) {
|
|
46
|
+
return platform !== undefined && PLATFORMS.has(platform);
|
|
47
|
+
}
|
|
48
|
+
function stripPrefix(name, prefixes) {
|
|
49
|
+
for (const p of prefixes ?? []) {
|
|
50
|
+
if (name.length > p.length && name.startsWith(p))
|
|
51
|
+
return name.slice(p.length);
|
|
52
|
+
}
|
|
53
|
+
return name;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Resolve a component name to a visual archetype for a platform, honouring an
|
|
57
|
+
* optional adopter overlay. Always resolves (panel fallback) — never throws.
|
|
58
|
+
*/
|
|
59
|
+
export function resolveArchetype(name, platform, overlay) {
|
|
60
|
+
const override = overlay?.overrides?.[name];
|
|
61
|
+
if (override !== undefined)
|
|
62
|
+
return { archetype: override, provenance: "override" };
|
|
63
|
+
const map = loadMap(platform);
|
|
64
|
+
const token = stripPrefix(name, map.strip_prefixes);
|
|
65
|
+
const byToken = map.by_token?.[token] ?? map.by_token?.[name];
|
|
66
|
+
if (byToken !== undefined)
|
|
67
|
+
return { archetype: byToken, provenance: "default" };
|
|
68
|
+
// First suffix the name ends with wins.
|
|
69
|
+
for (const [suffix, archetype] of Object.entries(map.by_suffix_heuristic ?? {})) {
|
|
70
|
+
if (name.endsWith(suffix))
|
|
71
|
+
return { archetype, provenance: "heuristic" };
|
|
72
|
+
}
|
|
73
|
+
return { archetype: map.default ?? FALLBACK_ARCHETYPE, provenance: "fallback" };
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Whether a component classifies as a container archetype for a platform.
|
|
77
|
+
* Used by ADR 0168 §4.1 isContainer to refine the appears-with-children
|
|
78
|
+
* heuristic. Returns undefined when the platform has no map (caller falls back
|
|
79
|
+
* to the heuristic) so an absent map never mis-classifies a known container.
|
|
80
|
+
*/
|
|
81
|
+
export function isContainerArchetype(name, platform, overlay) {
|
|
82
|
+
if (!isKnownPlatform(platform))
|
|
83
|
+
return undefined;
|
|
84
|
+
const { archetype } = resolveArchetype(name, platform, overlay);
|
|
85
|
+
return loadVocabulary().archetypes[archetype]?.is_container === true;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The vendor name prefixes the platform's default map strips (e.g. `UI`/`NS` for
|
|
89
|
+
* uikit, `Mui`/`Ch`/… for web-generic). The renderer combines these with the
|
|
90
|
+
* adopter overlay's `strip_prefixes` to humanise component names into labels.
|
|
91
|
+
* Returns `[]` for an unknown platform.
|
|
92
|
+
*/
|
|
93
|
+
export function mapStripPrefixes(platform) {
|
|
94
|
+
if (!isKnownPlatform(platform))
|
|
95
|
+
return [];
|
|
96
|
+
return loadMap(platform).strip_prefixes ?? [];
|
|
97
|
+
}
|
|
98
|
+
/** Vocabulary metadata for an archetype: container flag + render hints. */
|
|
99
|
+
export function archetypeInfo(archetype) {
|
|
100
|
+
const entry = loadVocabulary().archetypes[archetype];
|
|
101
|
+
return { isContainer: entry?.is_container === true, render: entry?.render ?? {} };
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=resolver.js.map
|
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
// Deterministic structural-wireframe renderer (ADR 0169 §4.4).
|
|
2
|
+
//
|
|
3
|
+
// Lowers a screen's openui-lang block to an SVG wireframe: parse the block into
|
|
4
|
+
// a composition tree, resolve each node to a visual archetype, and lay it out
|
|
5
|
+
// driven by the archetype's render hints — orientation, shape (bar / chips /
|
|
6
|
+
// grid / card / floating / paged / leaf shapes), slots, overlay, active_first,
|
|
7
|
+
// header_row. Each node is drawn as its archetype's visual SHAPE (an app-bar is
|
|
8
|
+
// a band, a tab-strip is a row of chips, a paged-panes shows the active pane and
|
|
9
|
+
// collapses the rest, a table has a header row, a modal floats), labelled with
|
|
10
|
+
// the component name + resolved archetype and shaded by resolution provenance so
|
|
11
|
+
// low-confidence inferences stay visible. A structural wireframe, not a pixel
|
|
12
|
+
// design; parameterised entirely by the archetype map (adopter-agnostic — no
|
|
13
|
+
// component vocabulary, no adopter knowledge).
|
|
14
|
+
//
|
|
15
|
+
// Deterministic companion to the LLM-driven render-screen-wireframe skill.
|
|
16
|
+
import { buildCompositionTree, stripBlockComments } from "../builders/screen-tree.js";
|
|
17
|
+
import { archetypeInfo, isKnownPlatform, mapStripPrefixes, resolveArchetype, } from "./resolver.js";
|
|
18
|
+
// --- geometry constants ----------------------------------------------------
|
|
19
|
+
const PAD = 8;
|
|
20
|
+
const GAP = 6;
|
|
21
|
+
const LABEL_H = 16; // container label strip
|
|
22
|
+
const TITLE_H = 26;
|
|
23
|
+
const TABLE_HDR_H = 20;
|
|
24
|
+
const COLLAPSE_H = 22; // collapsed (inactive) paged pane row
|
|
25
|
+
const SHADOW = 4; // floating/card drop-shadow offset
|
|
26
|
+
const GRID_COLS = 2;
|
|
27
|
+
/** Render an openui-lang block to a structural wireframe SVG string. */
|
|
28
|
+
export function renderWireframe(input) {
|
|
29
|
+
const tree = buildCompositionTree(stripBlockComments(input.block));
|
|
30
|
+
if (tree === null) {
|
|
31
|
+
return emptySvg(input.title, "empty composition — no statements");
|
|
32
|
+
}
|
|
33
|
+
const platform = isKnownPlatform(input.platform) ? input.platform : undefined;
|
|
34
|
+
const prefixes = [...(input.overlay?.strip_prefixes ?? []), ...mapStripPrefixes(platform)];
|
|
35
|
+
const root = measure(shape(tree, platform, input.overlay, prefixes));
|
|
36
|
+
const titleOffset = input.title ? TITLE_H : 0;
|
|
37
|
+
const width = root.w + PAD * 2;
|
|
38
|
+
const height = root.h + PAD * 2 + titleOffset;
|
|
39
|
+
const body = [];
|
|
40
|
+
if (input.title)
|
|
41
|
+
body.push(text(PAD, 17, escapeXml(input.title), "title"));
|
|
42
|
+
emit(root, PAD, PAD + titleOffset, body);
|
|
43
|
+
return svg(width, height, body.join("\n"));
|
|
44
|
+
}
|
|
45
|
+
// --- shape: resolve archetype + classify a render mode per node ------------
|
|
46
|
+
const LEAF_SHAPES = new Set(["button", "chip", "text", "image", "circle", "row"]);
|
|
47
|
+
function shape(node, platform, overlay, prefixes) {
|
|
48
|
+
const children = node.children.map((c) => shape(c, platform, overlay, prefixes));
|
|
49
|
+
const isContainer = children.length > 0;
|
|
50
|
+
linkPagedTabs(children, prefixes);
|
|
51
|
+
if (node.component === "" || platform === undefined) {
|
|
52
|
+
return base("", "none", isContainer ? "stack" : "leaf", "box");
|
|
53
|
+
}
|
|
54
|
+
const { archetype, provenance } = resolveArchetype(node.component, platform, overlay);
|
|
55
|
+
const r = archetypeInfo(archetype).render;
|
|
56
|
+
const sh = r.shape ?? "";
|
|
57
|
+
if (!isContainer) {
|
|
58
|
+
const leafShape = LEAF_SHAPES.has(sh) ? sh : "box";
|
|
59
|
+
return base(archetype, provenance, "leaf", leafShape);
|
|
60
|
+
}
|
|
61
|
+
let mode;
|
|
62
|
+
if (sh === "bar")
|
|
63
|
+
mode = "bar";
|
|
64
|
+
else if (sh === "chips")
|
|
65
|
+
mode = "chips";
|
|
66
|
+
else if (sh === "grid")
|
|
67
|
+
mode = r.header_row ? "table" : "grid";
|
|
68
|
+
else if (sh === "paged")
|
|
69
|
+
mode = "paged";
|
|
70
|
+
else if (sh === "card")
|
|
71
|
+
mode = "card";
|
|
72
|
+
else if (sh === "floating")
|
|
73
|
+
mode = "floating";
|
|
74
|
+
else
|
|
75
|
+
mode = r.orientation === "horizontal" ? "row" : "stack";
|
|
76
|
+
return base(archetype, provenance, mode, "box");
|
|
77
|
+
function base(archetype, provenance, mode, leafShape) {
|
|
78
|
+
return { node, archetype, provenance, mode, leafShape, children, stripPrefixes: prefixes, derivedLabel: "", w: 0, h: 0 };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Derive tab labels from windowed content (deterministic, spec-faithful): when a
|
|
83
|
+
* sibling group holds a tab-strip and a paged-panes with the SAME pane count, and
|
|
84
|
+
* the tabs carry no authored label, label each tab from its corresponding pane's
|
|
85
|
+
* content component name. Reads only names already in the spec — no fabrication.
|
|
86
|
+
*/
|
|
87
|
+
function linkPagedTabs(siblings, prefixes) {
|
|
88
|
+
const strip = siblings.find((s) => s.archetype === "tab-strip");
|
|
89
|
+
const paged = siblings.find((s) => s.archetype === "paged-panes");
|
|
90
|
+
if (!strip || !paged)
|
|
91
|
+
return;
|
|
92
|
+
if (strip.children.length === 0 || strip.children.length !== paged.children.length)
|
|
93
|
+
return;
|
|
94
|
+
strip.children.forEach((tab, i) => {
|
|
95
|
+
if (tab.node.args.length > 0 || tab.derivedLabel)
|
|
96
|
+
return; // authored label wins
|
|
97
|
+
const pane = paged.children[i];
|
|
98
|
+
const contentName = pane.children[0]?.node.component || pane.node.component;
|
|
99
|
+
if (contentName)
|
|
100
|
+
tab.derivedLabel = humaniseName(contentName, prefixes);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
// --- measure: size every node bottom-up ------------------------------------
|
|
104
|
+
function leafSize(shapeName, lbl) {
|
|
105
|
+
switch (shapeName) {
|
|
106
|
+
case "button": return { w: Math.max(84, lbl.length * 6 + 16), h: 26 };
|
|
107
|
+
case "chip": return { w: Math.max(56, lbl.length * 6 + 14), h: 22 };
|
|
108
|
+
case "text": return { w: Math.max(120, lbl.length * 6 + 12), h: 28 };
|
|
109
|
+
case "image": return { w: 72, h: 54 };
|
|
110
|
+
case "circle": return { w: 44, h: 44 };
|
|
111
|
+
case "row": return { w: Math.max(200, lbl.length * 6 + 24), h: 26 };
|
|
112
|
+
default: return { w: Math.max(120, lbl.length * 6.2 + 12), h: 40 };
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function measure(s) {
|
|
116
|
+
if (s.mode === "leaf") {
|
|
117
|
+
const { w, h } = leafSize(s.leafShape, labelPlain(s));
|
|
118
|
+
s.w = w;
|
|
119
|
+
s.h = h;
|
|
120
|
+
return s;
|
|
121
|
+
}
|
|
122
|
+
s.children.forEach(measure);
|
|
123
|
+
const cs = s.children;
|
|
124
|
+
let innerW;
|
|
125
|
+
let innerH;
|
|
126
|
+
switch (s.mode) {
|
|
127
|
+
case "row":
|
|
128
|
+
case "bar":
|
|
129
|
+
case "chips": {
|
|
130
|
+
innerW = cs.reduce((a, c) => a + c.w, 0) + GAP * (cs.length - 1);
|
|
131
|
+
innerH = Math.max(...cs.map((c) => c.h));
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
case "grid": {
|
|
135
|
+
const colW = Math.max(...cs.map((c) => c.w));
|
|
136
|
+
const rowH = Math.max(...cs.map((c) => c.h));
|
|
137
|
+
const rows = Math.ceil(cs.length / GRID_COLS);
|
|
138
|
+
const cols = Math.min(GRID_COLS, cs.length);
|
|
139
|
+
innerW = cols * colW + GAP * (cols - 1);
|
|
140
|
+
innerH = rows * rowH + GAP * (rows - 1);
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
case "table": {
|
|
144
|
+
innerW = Math.max(180, ...cs.map((c) => c.w));
|
|
145
|
+
innerH = TABLE_HDR_H + GAP + cs.reduce((a, c) => a + c.h, 0) + GAP * (cs.length - 1);
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
case "paged": {
|
|
149
|
+
const active = cs[0];
|
|
150
|
+
const restN = cs.length - 1;
|
|
151
|
+
innerW = Math.max(160, active.w);
|
|
152
|
+
innerH = active.h + (restN > 0 ? GAP + restN * COLLAPSE_H + GAP * (restN - 1) : 0);
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
default: {
|
|
156
|
+
// stack / card / floating
|
|
157
|
+
innerW = Math.max(...cs.map((c) => c.w));
|
|
158
|
+
innerH = cs.reduce((a, c) => a + c.h, 0) + GAP * (cs.length - 1);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
s.w = innerW + PAD * 2;
|
|
162
|
+
s.h = LABEL_H + innerH + PAD * 2;
|
|
163
|
+
return s;
|
|
164
|
+
}
|
|
165
|
+
// --- emit: place + draw top-down -------------------------------------------
|
|
166
|
+
function emit(s, x, y, out) {
|
|
167
|
+
if (s.mode === "leaf") {
|
|
168
|
+
drawLeaf(s, x, y, out);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
drawContainer(s, x, y, out);
|
|
172
|
+
const cx0 = x + PAD;
|
|
173
|
+
const cy0 = y + LABEL_H + PAD;
|
|
174
|
+
const cs = s.children;
|
|
175
|
+
switch (s.mode) {
|
|
176
|
+
case "row":
|
|
177
|
+
case "bar":
|
|
178
|
+
case "chips": {
|
|
179
|
+
const rowH = Math.max(...cs.map((c) => c.h));
|
|
180
|
+
let cx = cx0;
|
|
181
|
+
for (const c of cs) {
|
|
182
|
+
emit(c, cx, cy0 + (rowH - c.h) / 2, out);
|
|
183
|
+
cx += c.w + GAP;
|
|
184
|
+
}
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
case "grid": {
|
|
188
|
+
const colW = Math.max(...cs.map((c) => c.w));
|
|
189
|
+
const rowH = Math.max(...cs.map((c) => c.h));
|
|
190
|
+
cs.forEach((c, i) => {
|
|
191
|
+
const col = i % GRID_COLS;
|
|
192
|
+
const row = Math.floor(i / GRID_COLS);
|
|
193
|
+
emit(c, cx0 + col * (colW + GAP), cy0 + row * (rowH + GAP), out);
|
|
194
|
+
});
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
case "table": {
|
|
198
|
+
const innerW = s.w - PAD * 2;
|
|
199
|
+
out.push(rect(cx0, cy0, innerW, TABLE_HDR_H, "none", true, "#e2e8f0"));
|
|
200
|
+
for (let i = 1; i < 4; i++) {
|
|
201
|
+
const lx = cx0 + (innerW * i) / 4;
|
|
202
|
+
out.push(line(lx, cy0, lx, cy0 + TABLE_HDR_H));
|
|
203
|
+
}
|
|
204
|
+
let ry = cy0 + TABLE_HDR_H + GAP;
|
|
205
|
+
for (const c of cs) {
|
|
206
|
+
emit(c, cx0, ry, out);
|
|
207
|
+
ry += c.h + GAP;
|
|
208
|
+
}
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
case "paged": {
|
|
212
|
+
const active = cs[0];
|
|
213
|
+
emit(active, cx0, cy0, out);
|
|
214
|
+
let ry = cy0 + active.h + GAP;
|
|
215
|
+
for (const c of cs.slice(1)) {
|
|
216
|
+
out.push(rect(cx0, ry, s.w - PAD * 2, COLLAPSE_H, c.provenance, false, "#f1f5f9"));
|
|
217
|
+
out.push(text(cx0 + 6, ry + 15, "▸ " + collapsedPaneLabel(c), "dim"));
|
|
218
|
+
ry += COLLAPSE_H + GAP;
|
|
219
|
+
}
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
default: {
|
|
223
|
+
let cy = cy0;
|
|
224
|
+
for (const c of cs) {
|
|
225
|
+
emit(c, cx0, cy, out);
|
|
226
|
+
cy += c.h + GAP;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// --- drawing primitives ----------------------------------------------------
|
|
232
|
+
const DARK_BARS = new Set(["app-bar", "nav-bar"]);
|
|
233
|
+
function drawContainer(s, x, y, out) {
|
|
234
|
+
const dark = DARK_BARS.has(s.archetype);
|
|
235
|
+
let fill = "#f8fafc";
|
|
236
|
+
if (s.archetype === "banner")
|
|
237
|
+
fill = "#fef3c7";
|
|
238
|
+
else if (s.archetype === "tab-bar")
|
|
239
|
+
fill = "#eef2ff";
|
|
240
|
+
else if (s.mode === "card" || s.mode === "floating")
|
|
241
|
+
fill = "#ffffff";
|
|
242
|
+
else if (dark)
|
|
243
|
+
fill = "#1f2937";
|
|
244
|
+
if (s.mode === "floating" || s.mode === "card") {
|
|
245
|
+
out.push(`<rect x="${x + SHADOW}" y="${y + SHADOW}" width="${s.w}" height="${s.h}" rx="4" fill="#0f172a" opacity="0.12"/>`);
|
|
246
|
+
}
|
|
247
|
+
out.push(rect(x, y, s.w, s.h, s.provenance, true, fill));
|
|
248
|
+
out.push(text(x + PAD, y + 12, labelMarkup(s), dark ? "lblw" : "lbl"));
|
|
249
|
+
}
|
|
250
|
+
function drawLeaf(s, x, y, out) {
|
|
251
|
+
const lbl = labelMarkup(s);
|
|
252
|
+
switch (s.leafShape) {
|
|
253
|
+
case "button":
|
|
254
|
+
out.push(`<rect x="${x}" y="${y}" width="${s.w}" height="${s.h}" rx="13" fill="#dbeafe" stroke="#3b82f6"${dash(s.provenance)}/>`);
|
|
255
|
+
out.push(text(x + 10, y + s.h / 2 + 4, lbl, "btn"));
|
|
256
|
+
break;
|
|
257
|
+
case "chip":
|
|
258
|
+
out.push(`<rect x="${x}" y="${y}" width="${s.w}" height="${s.h}" rx="11" fill="#e2e8f0" stroke="#94a3b8"${dash(s.provenance)}/>`);
|
|
259
|
+
out.push(text(x + 9, y + s.h / 2 + 4, lbl, "lbl"));
|
|
260
|
+
break;
|
|
261
|
+
case "circle": {
|
|
262
|
+
const r = s.w / 2;
|
|
263
|
+
out.push(`<circle cx="${x + r}" cy="${y + r}" r="${r}" fill="#dbeafe" stroke="#3b82f6"${dash(s.provenance)}/>`);
|
|
264
|
+
out.push(text(x + 6, y + r + 4, lbl, "dim"));
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
case "image":
|
|
268
|
+
out.push(rect(x, y, s.w, s.h, s.provenance, false, "#f1f5f9"));
|
|
269
|
+
out.push(line(x, y, x + s.w, y + s.h));
|
|
270
|
+
out.push(line(x + s.w, y, x, y + s.h));
|
|
271
|
+
out.push(text(x + 4, y + s.h - 4, lbl, "dim"));
|
|
272
|
+
break;
|
|
273
|
+
case "text":
|
|
274
|
+
out.push(text(x, y + 12, lbl, "lbl"));
|
|
275
|
+
out.push(`<rect x="${x}" y="${y + 16}" width="${s.w}" height="5" rx="2" fill="#cbd5e1"/>`);
|
|
276
|
+
out.push(`<rect x="${x}" y="${y + 24}" width="${s.w * 0.66}" height="5" rx="2" fill="#e2e8f0"/>`);
|
|
277
|
+
break;
|
|
278
|
+
case "row":
|
|
279
|
+
out.push(rect(x, y, s.w, s.h, s.provenance, false, "#ffffff"));
|
|
280
|
+
out.push(`<circle cx="${x + 11}" cy="${y + s.h / 2}" r="5" fill="#cbd5e1"/>`);
|
|
281
|
+
out.push(text(x + 24, y + s.h / 2 + 4, lbl, "lbl"));
|
|
282
|
+
break;
|
|
283
|
+
default:
|
|
284
|
+
out.push(rect(x, y, s.w, s.h, s.provenance, false, "#fdfdfe"));
|
|
285
|
+
out.push(text(x + 6, y + s.h / 2 + 4, lbl, "lbl"));
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
// --- label derivation ------------------------------------------------------
|
|
289
|
+
/** The primary human label for a node, derived only from tokens in the spec. */
|
|
290
|
+
function primaryLabel(s) {
|
|
291
|
+
if (s.node.args.length > 0)
|
|
292
|
+
return s.node.args[0]; // authored positional label
|
|
293
|
+
if (s.mode === "leaf" && s.node.actions.length > 0 && (s.archetype === "button" || s.archetype === "fab"))
|
|
294
|
+
return humaniseAction(s.node.actions[0]); // CTA from action=
|
|
295
|
+
if (s.derivedLabel)
|
|
296
|
+
return s.derivedLabel; // windowed-pane tab label
|
|
297
|
+
const comp = s.node.component;
|
|
298
|
+
if (!comp)
|
|
299
|
+
return s.node.id;
|
|
300
|
+
// A `default` resolution is a recognised standard widget — its short vendor name
|
|
301
|
+
// reads fine as-is; bespoke names (override / heuristic / fallback) humanise better.
|
|
302
|
+
return s.provenance === "default" || s.provenance === "none" ? comp : humaniseName(comp, s.stripPrefixes);
|
|
303
|
+
}
|
|
304
|
+
function archMark(prov) {
|
|
305
|
+
return prov === "fallback" ? "?" : prov === "heuristic" ? "~" : "";
|
|
306
|
+
}
|
|
307
|
+
/** Plain text label (for width measurement): primary + archetype annotation. */
|
|
308
|
+
function labelPlain(s) {
|
|
309
|
+
const primary = primaryLabel(s);
|
|
310
|
+
if (s.archetype === "" || s.provenance === "none")
|
|
311
|
+
return primary;
|
|
312
|
+
return `${primary} :${s.archetype}${archMark(s.provenance)}`;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Collapsed paged-pane label: a wrapper pane often has a generic name (its
|
|
316
|
+
* content carries the identity), so prefer the content component's label when
|
|
317
|
+
* the pane itself has no authored label of its own. Spec-faithful — the content
|
|
318
|
+
* name is already in the block.
|
|
319
|
+
*/
|
|
320
|
+
function collapsedPaneLabel(s) {
|
|
321
|
+
const content = s.children[0];
|
|
322
|
+
if (s.node.args.length === 0 && !s.derivedLabel && content && content.node.component) {
|
|
323
|
+
const lbl = escapeXml(primaryLabel(content));
|
|
324
|
+
return s.archetype === "" ? lbl : `${lbl} <tspan class="arch">:${escapeXml(s.archetype)}</tspan>`;
|
|
325
|
+
}
|
|
326
|
+
return labelMarkup(s);
|
|
327
|
+
}
|
|
328
|
+
/** SVG markup label (for emission): primary text + a small dim `:archetype` tspan. */
|
|
329
|
+
function labelMarkup(s) {
|
|
330
|
+
const primary = escapeXml(primaryLabel(s));
|
|
331
|
+
if (s.archetype === "" || s.provenance === "none")
|
|
332
|
+
return primary;
|
|
333
|
+
return `${primary} <tspan class="arch">:${escapeXml(s.archetype)}${archMark(s.provenance)}</tspan>`;
|
|
334
|
+
}
|
|
335
|
+
// --- humanisation (deterministic, adopter-agnostic) ------------------------
|
|
336
|
+
const HUMANISE_SUFFIXES = [
|
|
337
|
+
"Component", "Container", "Controller", "Activity", "Fragment", "Section",
|
|
338
|
+
"Layout", "Panel", "Popup", "Dialog", "Modal", "Widget", "View", "Screen",
|
|
339
|
+
"Page", "Table", "List", "Card", "Item",
|
|
340
|
+
];
|
|
341
|
+
/** Strip a vendor prefix + a trailing archetype-ish suffix, split camelCase, Title Case. */
|
|
342
|
+
function humaniseName(name, prefixes) {
|
|
343
|
+
let s = name;
|
|
344
|
+
for (const p of prefixes) {
|
|
345
|
+
// Strip only at a PascalCase boundary (next char uppercase) so a short prefix
|
|
346
|
+
// like "St" peels "StLayout" → "Layout" but never "Status" → "atus".
|
|
347
|
+
if (s.length > p.length && s.startsWith(p) && /[A-Z]/.test(s.charAt(p.length))) {
|
|
348
|
+
s = s.slice(p.length);
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
for (const suf of HUMANISE_SUFFIXES) {
|
|
353
|
+
if (s.length > suf.length && s.endsWith(suf)) {
|
|
354
|
+
s = s.slice(0, -suf.length);
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
const words = splitWords(s);
|
|
359
|
+
if (words.length === 0)
|
|
360
|
+
return titleCaseWords(splitWords(name)); // prefix/suffix ate the whole name
|
|
361
|
+
return titleCaseWords(words);
|
|
362
|
+
}
|
|
363
|
+
/** Humanise an action token, prefixing create/add/new verbs with "+ ". */
|
|
364
|
+
function humaniseAction(action) {
|
|
365
|
+
const human = titleCaseWords(splitWords(action));
|
|
366
|
+
return /^(new|add|create)([^a-z]|$)/i.test(action) ? `+ ${human}` : human;
|
|
367
|
+
}
|
|
368
|
+
function splitWords(s) {
|
|
369
|
+
return s
|
|
370
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
371
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2")
|
|
372
|
+
.replace(/[_\-.]+/g, " ")
|
|
373
|
+
.trim()
|
|
374
|
+
.split(/\s+/)
|
|
375
|
+
.filter(Boolean);
|
|
376
|
+
}
|
|
377
|
+
function titleCaseWords(words) {
|
|
378
|
+
return words.map((w) => (w === w.toUpperCase() ? w : w.charAt(0).toUpperCase() + w.slice(1))).join(" ");
|
|
379
|
+
}
|
|
380
|
+
function dash(prov) {
|
|
381
|
+
return prov === "heuristic" ? ' stroke-dasharray="5 3"' : prov === "fallback" ? ' stroke-dasharray="1 4"' : "";
|
|
382
|
+
}
|
|
383
|
+
function rect(x, y, w, h, prov, container, fill) {
|
|
384
|
+
const f = fill ?? (container ? "#f4f6f8" : "#fdfdfe");
|
|
385
|
+
return `<rect x="${x}" y="${y}" width="${w}" height="${h}" rx="3" fill="${f}" stroke="#64748b" stroke-width="1"${dash(prov)}/>`;
|
|
386
|
+
}
|
|
387
|
+
function line(x1, y1, x2, y2) {
|
|
388
|
+
return `<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="#cbd5e1" stroke-width="1"/>`;
|
|
389
|
+
}
|
|
390
|
+
function text(x, y, content, cls) {
|
|
391
|
+
return `<text x="${x}" y="${y}" class="${cls}">${content}</text>`;
|
|
392
|
+
}
|
|
393
|
+
function svg(width, height, body) {
|
|
394
|
+
return [
|
|
395
|
+
`<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">`,
|
|
396
|
+
`<style>text{font-family:ui-monospace,monospace;font-size:11px;fill:#334155}.title{font-size:13px;font-weight:600;fill:#0f172a}.lbl{fill:#334155}.lblw{fill:#f8fafc}.dim{fill:#94a3b8}.btn{fill:#1e40af;font-weight:600}.arch{font-size:9px;fill:#a8b3c2}</style>`,
|
|
397
|
+
`<rect width="${width}" height="${height}" fill="#fff"/>`,
|
|
398
|
+
body,
|
|
399
|
+
`</svg>`,
|
|
400
|
+
].join("\n");
|
|
401
|
+
}
|
|
402
|
+
function emptySvg(title, message) {
|
|
403
|
+
const body = [
|
|
404
|
+
title ? text(PAD, 16, escapeXml(title), "title") : "",
|
|
405
|
+
text(PAD, title ? 36 : 16, escapeXml(message), "lbl"),
|
|
406
|
+
]
|
|
407
|
+
.filter(Boolean)
|
|
408
|
+
.join("\n");
|
|
409
|
+
return svg(240, title ? 48 : 28, body);
|
|
410
|
+
}
|
|
411
|
+
function escapeXml(s) {
|
|
412
|
+
return s
|
|
413
|
+
.replace(/&/g, "&")
|
|
414
|
+
.replace(/</g, "<")
|
|
415
|
+
.replace(/>/g, ">")
|
|
416
|
+
.replace(/"/g, """);
|
|
417
|
+
}
|
|
418
|
+
//# sourceMappingURL=wireframe.js.map
|