stylegallery 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/AGENTS.md +156 -0
- package/CATALOG.md +69 -0
- package/DOMAINS.md +65 -0
- package/GOVERNANCE.md +222 -0
- package/GUIDE.md +59 -0
- package/README.md +313 -0
- package/consumer-reference/adapters/style-dictionary.config.mjs +36 -0
- package/consumer-reference/agent-native/README.md +157 -0
- package/consumer-reference/agent-native/registry.json +640 -0
- package/consumer-reference/agent-native/schema/agent-native.schema.json +848 -0
- package/consumer-reference/agent-native/schema/epistemic.schema.json +14 -0
- package/consumer-reference/agent-native/schema/execution.schema.json +12 -0
- package/consumer-reference/agent-native/schema/identity.schema.json +10 -0
- package/consumer-reference/agent-native/schema/learning.schema.json +12 -0
- package/consumer-reference/agent-native/schema/manifest.schema.json +6 -0
- package/consumer-reference/agent-native/schema/operation.schema.json +12 -0
- package/consumer-reference/agent-native/schema/protocol-binding.schema.json +9 -0
- package/consumer-reference/agent-native/schema/retrieval.schema.json +10 -0
- package/consumer-reference/baselines/calibration.json +260 -0
- package/consumer-reference/baselines/manifest.json +21 -0
- package/consumer-reference/contract.md +83 -0
- package/consumer-reference/fixtures/consumer-conformance/e2e-fixture.mjs +282 -0
- package/consumer-reference/fixtures/consumer-conformance/manifest.json +102 -0
- package/consumer-reference/fixtures/consumer-conformance/runtime-proof.mjs +32 -0
- package/consumer-reference/fixtures/consumer-conformance/valid-migration-result.json +18 -0
- package/consumer-reference/fixtures/consumer-conformance/valid-migration.json +142 -0
- package/consumer-reference/fixtures/invalid-stable-ended-support.json +19 -0
- package/consumer-reference/fixtures/promotion/invalid-blocking-evidence.json +26 -0
- package/consumer-reference/fixtures/promotion/invalid-count-only.json +29 -0
- package/consumer-reference/fixtures/promotion/invalid-missing-owner-migration.json +25 -0
- package/consumer-reference/fixtures/promotion/invalid-normative-bypass-no-regression.json +25 -0
- package/consumer-reference/fixtures/promotion/invalid-related-counted-independent.json +45 -0
- package/consumer-reference/fixtures/promotion/invalid-stable-ended-support.json +26 -0
- package/consumer-reference/fixtures/promotion/manifest.json +15 -0
- package/consumer-reference/fixtures/promotion/valid-deferred-example.json +67 -0
- package/consumer-reference/fixtures/promotion/valid-normative-bypass.json +67 -0
- package/consumer-reference/fixtures/token-portability/invalid-cycle.json +7 -0
- package/consumer-reference/fixtures/token-portability/invalid-dangling-alias.json +6 -0
- package/consumer-reference/fixtures/token-portability/invalid-extends.json +4 -0
- package/consumer-reference/fixtures/token-portability/invalid-json-pointer.json +7 -0
- package/consumer-reference/fixtures/token-portability/invalid-resolver.json +9 -0
- package/consumer-reference/fixtures/token-portability/invalid-type-mismatch.json +10 -0
- package/consumer-reference/fixtures/token-portability/invalid-unknown-reserved.json +6 -0
- package/consumer-reference/fixtures/token-portability/invalid-untested-type.json +6 -0
- package/consumer-reference/fixtures/token-portability/valid-border.json +14 -0
- package/consumer-reference/fixtures/token-portability/valid-reference.json +32 -0
- package/consumer-reference/fixtures/token-portability/valid-whole-alias.json +7 -0
- package/consumer-reference/fixtures/valid-deprecated.json +21 -0
- package/consumer-reference/fixtures/valid-experimental.json +19 -0
- package/consumer-reference/generated/manifest.json +40 -0
- package/consumer-reference/generated/tokens.css +12 -0
- package/consumer-reference/index.md +32 -0
- package/consumer-reference/policies/shared-experimental.json +37 -0
- package/consumer-reference/schema/ax-evidence.schema.json +30 -0
- package/consumer-reference/schema/baseline-manifest.schema.json +55 -0
- package/consumer-reference/schema/calibration-record.schema.json +190 -0
- package/consumer-reference/schema/capture-session.schema.json +161 -0
- package/consumer-reference/schema/component-state.schema.json +150 -0
- package/consumer-reference/schema/consumer-conformance-record.schema.json +365 -0
- package/consumer-reference/schema/dom-evidence.schema.json +38 -0
- package/consumer-reference/schema/evidence-record.schema.json +81 -0
- package/consumer-reference/schema/fixture-manifest.schema.json +32 -0
- package/consumer-reference/schema/item.schema.json +188 -0
- package/consumer-reference/schema/page-evidence-manifest.schema.json +216 -0
- package/consumer-reference/schema/page-evidence-session.schema.json +119 -0
- package/consumer-reference/schema/portable-tokens.schema.json +104 -0
- package/consumer-reference/schema/promotion-rfc.schema.json +219 -0
- package/consumer-reference/schema/runtime-evidence-manifest.schema.json +33 -0
- package/consumer-reference/schema/visual-evidence.schema.json +31 -0
- package/design-engineering/consumer-migration-readiness.md +124 -0
- package/design-engineering/index.md +28 -0
- package/design-engineering/interface-craft.md +107 -0
- package/design-engineering/reference-profiles/external-adaptation/index.md +12 -0
- package/design-engineering/reference-profiles/governed-local/editorial/components/button.component.json +125 -0
- package/design-engineering/reference-profiles/governed-local/editorial/evidence/button.evidence.json +3504 -0
- package/design-engineering/reference-profiles/governed-local/editorial/fixtures/button.fixture.json +65 -0
- package/design-engineering/reference-profiles/governed-local/editorial/generated/evidence-coverage.md +23 -0
- package/design-engineering/reference-profiles/governed-local/editorial/generated/keyboard-matrix.md +19 -0
- package/design-engineering/reference-profiles/governed-local/editorial/generated/state-matrix.md +21 -0
- package/design-engineering/reference-profiles/governed-local/editorial/local-foundations.json +12 -0
- package/design-engineering/reference-profiles/governed-local/editorial/profile.json +43 -0
- package/design-engineering/reference-profiles/governed-local/editorial/states/button.states.json +195 -0
- package/design-engineering/reference-profiles/governed-local/editorial/tokens.dtcg.json +17 -0
- package/design-engineering/reference-profiles/governed-local/index.md +26 -0
- package/design-engineering/reference-profiles/governed-local/terminal/components/button.component.json +125 -0
- package/design-engineering/reference-profiles/governed-local/terminal/evidence/button.evidence.json +3504 -0
- package/design-engineering/reference-profiles/governed-local/terminal/fixtures/button.fixture.json +65 -0
- package/design-engineering/reference-profiles/governed-local/terminal/generated/evidence-coverage.md +23 -0
- package/design-engineering/reference-profiles/governed-local/terminal/generated/keyboard-matrix.md +19 -0
- package/design-engineering/reference-profiles/governed-local/terminal/generated/state-matrix.md +21 -0
- package/design-engineering/reference-profiles/governed-local/terminal/local-foundations.json +12 -0
- package/design-engineering/reference-profiles/governed-local/terminal/profile.json +43 -0
- package/design-engineering/reference-profiles/governed-local/terminal/states/button.states.json +195 -0
- package/design-engineering/reference-profiles/governed-local/terminal/tokens.dtcg.json +17 -0
- package/design-engineering/reference-profiles/index.md +19 -0
- package/game-ui/classification.md +82 -0
- package/game-ui/index.md +35 -0
- package/game-ui/reference-record.md +62 -0
- package/game-ui/screen-hierarchy.md +107 -0
- package/game-ui/unity/architecture.md +166 -0
- package/game-ui/unity/cli-loop.md +84 -0
- package/game-ui/unity/data/unity-technologies-public-repositories.json +26654 -0
- package/game-ui/unity/org-wiki.md +146 -0
- package/game-ui/unity/repository-map.md +102 -0
- package/game-ui/unity/ui-systems.md +85 -0
- package/guides/decision-tree.md +149 -0
- package/guides/documentation-mode-taxonomy.md +157 -0
- package/guides/layout-brief.md +220 -0
- package/guides/vocabulary.md +148 -0
- package/guides/webpage-generation-workflow.md +185 -0
- package/index.md +36 -0
- package/layout/index.md +26 -0
- package/motion/index.md +24 -0
- package/motion/practice-reference.md +145 -0
- package/motion/review-workflow.md +86 -0
- package/motion/vocabulary.md +82 -0
- package/package.json +78 -0
- package/patterns/centering/center.md +87 -0
- package/patterns/centering/index.md +6 -0
- package/patterns/centering/super-center.md +89 -0
- package/patterns/containment/box.md +87 -0
- package/patterns/containment/clamped-card.md +86 -0
- package/patterns/containment/content-limiter.md +87 -0
- package/patterns/containment/fluid-styles.md +86 -0
- package/patterns/containment/index.md +8 -0
- package/patterns/grid-repetition/card-grid.md +88 -0
- package/patterns/grid-repetition/columns.md +87 -0
- package/patterns/grid-repetition/dense-grid.md +88 -0
- package/patterns/grid-repetition/grid-wrapper.md +89 -0
- package/patterns/grid-repetition/index.md +12 -0
- package/patterns/grid-repetition/masonry-approx.md +87 -0
- package/patterns/grid-repetition/page-grid.md +90 -0
- package/patterns/grid-repetition/ram-grid.md +88 -0
- package/patterns/grid-repetition/twelve-span-grid.md +92 -0
- package/patterns/in-line-grouping/badge-list.md +94 -0
- package/patterns/in-line-grouping/breadcrumb.md +89 -0
- package/patterns/in-line-grouping/cluster.md +89 -0
- package/patterns/in-line-grouping/deconstructed-pancake.md +92 -0
- package/patterns/in-line-grouping/index.md +13 -0
- package/patterns/in-line-grouping/pagination.md +89 -0
- package/patterns/in-line-grouping/reel.md +90 -0
- package/patterns/in-line-grouping/split-nav.md +97 -0
- package/patterns/in-line-grouping/tab-strip.md +88 -0
- package/patterns/in-line-grouping/wrap-row.md +89 -0
- package/patterns/index.md +13 -0
- package/patterns/media-fit/frame.md +95 -0
- package/patterns/media-fit/icon-frame.md +87 -0
- package/patterns/media-fit/index.md +6 -0
- package/patterns/overlay-exception/imposter.md +95 -0
- package/patterns/overlay-exception/index.md +6 -0
- package/patterns/overlay-exception/overlay-stack.md +89 -0
- package/patterns/split-sidebar/index.md +12 -0
- package/patterns/split-sidebar/list-detail.md +95 -0
- package/patterns/split-sidebar/main-with-rail.md +95 -0
- package/patterns/split-sidebar/media-object.md +102 -0
- package/patterns/split-sidebar/sidebar.md +101 -0
- package/patterns/split-sidebar/split-screen.md +92 -0
- package/patterns/split-sidebar/sticky-aside.md +97 -0
- package/patterns/split-sidebar/supporting-pane.md +95 -0
- package/patterns/split-sidebar/switcher.md +93 -0
- package/patterns/stacking/feed.md +88 -0
- package/patterns/stacking/index.md +8 -0
- package/patterns/stacking/line-up.md +95 -0
- package/patterns/stacking/stack.md +87 -0
- package/patterns/stacking/step-nav.md +88 -0
- package/patterns/viewport-shell/cover.md +90 -0
- package/patterns/viewport-shell/fixed-sidenav-shell.md +97 -0
- package/patterns/viewport-shell/holy-grail.md +112 -0
- package/patterns/viewport-shell/index.md +11 -0
- package/patterns/viewport-shell/panel-layout.md +95 -0
- package/patterns/viewport-shell/scroll-body-shell.md +93 -0
- package/patterns/viewport-shell/sticky-footer.md +92 -0
- package/patterns/viewport-shell/sticky-header.md +86 -0
- package/platform-guides/apple-interaction.md +89 -0
- package/platform-guides/index.md +26 -0
- package/quality/claim-records/index.md +3 -0
- package/quality/claim-records/samples.md +92 -0
- package/quality/claim-records/stylegallery-multidomain-scope.md +42 -0
- package/quality/claims.md +77 -0
- package/quality/evidence/accessibility.md +58 -0
- package/quality/evidence/consumer-migration.md +42 -0
- package/quality/evidence/design-rationale.md +34 -0
- package/quality/evidence/executable-evidence.md +79 -0
- package/quality/evidence/families.md +56 -0
- package/quality/evidence/hci-evaluation.md +36 -0
- package/quality/evidence/index.md +16 -0
- package/quality/evidence/pattern-contract-audit.md +96 -0
- package/quality/evidence/search-metadata-polyhierarchy.md +86 -0
- package/quality/evidence/sources.md +43 -0
- package/quality/evidence/tokens.md +33 -0
- package/quality/evidence/visual-qa.md +41 -0
- package/quality/gates/accessibility-evidence.md +60 -0
- package/quality/gates/consumer-migration-evidence.md +53 -0
- package/quality/gates/design-claim.md +56 -0
- package/quality/gates/harmony-evaluation.md +78 -0
- package/quality/gates/index.md +13 -0
- package/quality/gates/layout.md +52 -0
- package/quality/gates/rationale.md +53 -0
- package/quality/gates/visual-evidence.md +47 -0
- package/quality/index.md +55 -0
- package/quality/principles.md +58 -0
- package/recipes/article-page.md +52 -0
- package/recipes/command-surface.md +55 -0
- package/recipes/dashboard.md +56 -0
- package/recipes/form-flow.md +55 -0
- package/recipes/homepage.md +89 -0
- package/recipes/index.md +13 -0
- package/recipes/list-detail.md +55 -0
- package/recipes/primitive-to-recipe-matrix.md +83 -0
- package/recipes/saas-settings.md +52 -0
- package/scripts/agent-native/a2a-projection.mjs +248 -0
- package/scripts/agent-native/agui-projection.mjs +178 -0
- package/scripts/agent-native/canonical-json.mjs +109 -0
- package/scripts/agent-native/cli-adapter.mjs +68 -0
- package/scripts/agent-native/events.mjs +113 -0
- package/scripts/agent-native/execution.mjs +227 -0
- package/scripts/agent-native/fixture.mjs +159 -0
- package/scripts/agent-native/identity.mjs +184 -0
- package/scripts/agent-native/immutable-store.mjs +102 -0
- package/scripts/agent-native/knowledge.mjs +168 -0
- package/scripts/agent-native/learning-records.mjs +244 -0
- package/scripts/agent-native/learning.mjs +247 -0
- package/scripts/agent-native/mcp-adapter.mjs +173 -0
- package/scripts/agent-native/queries.mjs +173 -0
- package/scripts/agent-native/registry.mjs +154 -0
- package/scripts/agent-native/retrieval.mjs +171 -0
- package/scripts/agent-native/self-description.mjs +160 -0
- package/scripts/artifact-metadata.mjs +20 -0
- package/scripts/audit-evidence-freshness.mjs +308 -0
- package/scripts/baseline-contract.mjs +189 -0
- package/scripts/baseline-schema-parity.mjs +40 -0
- package/scripts/build-reference-artifacts.mjs +127 -0
- package/scripts/calibration-raw-contract.mjs +172 -0
- package/scripts/capture-session-contract.mjs +187 -0
- package/scripts/component-state-artifact-fixture.mjs +44 -0
- package/scripts/component-state-contract.mjs +99 -0
- package/scripts/component-state-semantics.mjs +128 -0
- package/scripts/component-state-workflow-contract.mjs +67 -0
- package/scripts/component-state-workflow-negative-cases.mjs +64 -0
- package/scripts/consumer-conformance-contract.mjs +198 -0
- package/scripts/consumer-evidence-governance-contract.mjs +210 -0
- package/scripts/consumer-reference-case-runner.mjs +93 -0
- package/scripts/consumer-reference-schema.mjs +155 -0
- package/scripts/create-component-state-session.mjs +100 -0
- package/scripts/create-page-evidence-session.mjs +139 -0
- package/scripts/domain-document-boundaries.mjs +40 -0
- package/scripts/domain-validation-checks.mjs +141 -0
- package/scripts/evidence-artifact-contract.mjs +191 -0
- package/scripts/finalize-component-state-evidence.mjs +165 -0
- package/scripts/finalize-page-evidence.mjs +197 -0
- package/scripts/generate-consumer-reference-evidence.mjs +154 -0
- package/scripts/generate-patterns.mjs +218 -0
- package/scripts/governance-matrix-contract.mjs +73 -0
- package/scripts/governance-matrix-negative-cases.mjs +114 -0
- package/scripts/governance-policy-contract.mjs +49 -0
- package/scripts/governance-test-fixture.mjs +321 -0
- package/scripts/governance-workflow-negative-cases.mjs +241 -0
- package/scripts/governed-profile-registry.mjs +125 -0
- package/scripts/json-schema-formats.mjs +31 -0
- package/scripts/markdown-structure.mjs +45 -0
- package/scripts/page-artifact-metadata.mjs +124 -0
- package/scripts/page-evidence-contract.mjs +297 -0
- package/scripts/page-evidence-fixture.mjs +271 -0
- package/scripts/pattern-data.mjs +325 -0
- package/scripts/profile-record-contract.mjs +140 -0
- package/scripts/promotion-attestation-contract.mjs +91 -0
- package/scripts/promotion-boundary-test-contract.mjs +92 -0
- package/scripts/promotion-fixture-inventory.mjs +109 -0
- package/scripts/promotion-governance-contract.mjs +28 -0
- package/scripts/reference-artifact-path-contract.mjs +28 -0
- package/scripts/reference-profile-contract.mjs +114 -0
- package/scripts/reference-token-contract.mjs +156 -0
- package/scripts/run-consumer-page-evidence-ci.mjs +170 -0
- package/scripts/sg-mcp.mjs +16 -0
- package/scripts/sg.mjs +27 -0
- package/scripts/strict-json.mjs +72 -0
- package/scripts/summarize-sentinel-calibration.mjs +98 -0
- package/scripts/test-agent-adapter-conformance.mjs +250 -0
- package/scripts/test-agent-conformance-receipt.mjs +106 -0
- package/scripts/test-agent-events.mjs +81 -0
- package/scripts/test-agent-execution-effects.mjs +114 -0
- package/scripts/test-agent-execution-fixtures.mjs +140 -0
- package/scripts/test-agent-execution-kernel.mjs +39 -0
- package/scripts/test-agent-execution-learning.mjs +130 -0
- package/scripts/test-agent-execution-lifecycle.mjs +143 -0
- package/scripts/test-agent-execution-safety.mjs +158 -0
- package/scripts/test-agent-execution-views.mjs +117 -0
- package/scripts/test-agent-identity-integrity-cases.mjs +160 -0
- package/scripts/test-agent-knowledge-kernel.mjs +183 -0
- package/scripts/test-agent-learning.mjs +246 -0
- package/scripts/test-agent-mcp.mjs +246 -0
- package/scripts/test-agent-native-schemas.mjs +215 -0
- package/scripts/test-agent-projections.mjs +249 -0
- package/scripts/test-agent-registry-mcp-integrity.mjs +215 -0
- package/scripts/test-agent-registry-mutations.mjs +110 -0
- package/scripts/test-audit-evidence-freshness.mjs +177 -0
- package/scripts/test-component-state-sentinel.mjs +35 -0
- package/scripts/test-component-state-source-contract.mjs +83 -0
- package/scripts/test-consumer-conformance-sentinel.mjs +165 -0
- package/scripts/test-consumer-reference-sentinel.mjs +31 -0
- package/scripts/test-generate-consumer-reference-evidence.mjs +124 -0
- package/scripts/test-json-cli-output.mjs +151 -0
- package/scripts/test-json-schema-formats.mjs +40 -0
- package/scripts/test-reference-adapters.mjs +199 -0
- package/scripts/test-renderer-purity.mjs +34 -0
- package/scripts/test-sg-cli.mjs +129 -0
- package/scripts/test-summarize-sentinel-calibration.mjs +226 -0
- package/scripts/test-validate-baseline-manifest.mjs +234 -0
- package/scripts/test-validate-component-state-artifacts.mjs +243 -0
- package/scripts/test-validate-component-state.mjs +106 -0
- package/scripts/test-validate-consumer-conformance.mjs +427 -0
- package/scripts/test-validate-consumer-reference-cli.mjs +69 -0
- package/scripts/test-validate-consumer-reference.mjs +263 -0
- package/scripts/test-validate-domains.mjs +357 -0
- package/scripts/test-validate-governance.mjs +292 -0
- package/scripts/test-validate-ia.mjs +262 -0
- package/scripts/test-validate-okf.mjs +73 -0
- package/scripts/test-validate-page-evidence.mjs +302 -0
- package/scripts/test-validate-patterns.mjs +157 -0
- package/scripts/test-validate-promotion-rfc.mjs +250 -0
- package/scripts/test-validate-reference-profiles.mjs +170 -0
- package/scripts/test-validate-unity-org-wiki.mjs +57 -0
- package/scripts/test-validate-unity-source-contracts.mjs +67 -0
- package/scripts/test-validate-vocabulary.mjs +174 -0
- package/scripts/test-validate-webpage-workflow.mjs +293 -0
- package/scripts/validate-baseline-manifest.mjs +44 -0
- package/scripts/validate-catalog.mjs +65 -0
- package/scripts/validate-component-state.mjs +141 -0
- package/scripts/validate-consumer-conformance.mjs +477 -0
- package/scripts/validate-consumer-reference.mjs +288 -0
- package/scripts/validate-domains.mjs +237 -0
- package/scripts/validate-governance.mjs +248 -0
- package/scripts/validate-ia.mjs +136 -0
- package/scripts/validate-links.mjs +66 -0
- package/scripts/validate-okf.mjs +104 -0
- package/scripts/validate-page-evidence.mjs +191 -0
- package/scripts/validate-patterns.mjs +246 -0
- package/scripts/validate-promotion-rfc.mjs +224 -0
- package/scripts/validate-reference-artifacts.mjs +125 -0
- package/scripts/validate-renderer-purity.mjs +56 -0
- package/scripts/validate-unity-org-wiki.mjs +146 -0
- package/scripts/validate-unity-source-contracts.mjs +107 -0
- package/scripts/validate-vocabulary.mjs +115 -0
- package/scripts/validate-webpage-workflow.mjs +254 -0
- package/scripts/visual-expectation-contract.mjs +20 -0
- package/scripts/workflow-action-contract.mjs +163 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Planning Guide
|
|
3
|
+
title: Primitive To Recipe Matrix
|
|
4
|
+
description: Matrix that explains which layout primitives each screen recipe uses, why the slot exists, and what risk follows from substitution.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Primitive To Recipe Matrix
|
|
8
|
+
|
|
9
|
+
Use this matrix before selecting a recipe or generating a visual reference. It shows which primitives are part of each recipe, which slots are structural, and which slots can be replaced when the content model asks for a different spatial behavior.
|
|
10
|
+
|
|
11
|
+
Legend:
|
|
12
|
+
|
|
13
|
+
- `E` = essential: removing or replacing this primitive changes the recipe's spatial model.
|
|
14
|
+
- `H` = helper: supports rhythm, grouping, or local readability without defining the screen type by itself.
|
|
15
|
+
- `S` = substitutable: useful default, but another primitive may fit the same recipe slot when content constraints differ.
|
|
16
|
+
- `-` = not part of the recommended recipe stack.
|
|
17
|
+
|
|
18
|
+
## Recipe Pattern Matrix
|
|
19
|
+
|
|
20
|
+
| Recipe | `stack` | `box` | `center` | `cluster` | `content-limiter` | `super-center` | `icon-frame` | `frame` | `cover` | `sidebar` | `switcher` | `media-object` | `split-nav` | `holy-grail` | `sticky-footer` | `sticky-header` | `scroll-body-shell` | `fixed-sidenav-shell` | `sticky-aside` | `ram-grid` | `card-grid` | `twelve-span-grid` | `page-grid` | `grid-wrapper` | `columns` | `deconstructed-pancake` | `line-up` | `clamped-card` | `fluid-styles` | `split-screen` | `list-detail` | `supporting-pane` | `feed` | `breadcrumb` | `pagination` | `badge-list` | `step-nav` | `tab-strip` | `reel` | `imposter` | `panel-layout` | `overlay-stack` | `wrap-row` | `dense-grid` | `masonry-approx` | `main-with-rail` |
|
|
21
|
+
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
|
22
|
+
| [Homepage](homepage.md) | H | - | - | H | E | - | - | S | E | - | - | - | - | - | - | - | - | - | - | S | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
|
23
|
+
| [SaaS settings](saas-settings.md) | H | - | - | - | E | - | - | - | - | - | - | - | - | - | - | - | - | E | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
|
24
|
+
| [Dashboard](dashboard.md) | - | - | - | H | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | E | - | E | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
|
25
|
+
| [Article page](article-page.md) | H | - | - | - | E | - | - | - | - | - | - | - | - | - | - | - | - | - | S | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
|
26
|
+
| [List detail](list-detail.md) | H | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | H | - | - | - | - | - | - | - | - | - | - | - | - | - | E | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
|
27
|
+
| [Form flow](form-flow.md) | E | - | - | H | E | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
|
28
|
+
| [Command surface](command-surface.md) | - | - | - | - | - | - | - | - | - | - | - | - | H | - | - | - | E | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | E | - | - | - |
|
|
29
|
+
|
|
30
|
+
## Slot Classification Table
|
|
31
|
+
|
|
32
|
+
| Recipe | Pattern stack | Essential slots | Helper slots | Substitutable slots | Source lineage and semantic risk |
|
|
33
|
+
| --- | --- | --- | --- | --- | --- |
|
|
34
|
+
| [Homepage](homepage.md) | `cover`, `content-limiter`, `stack`, `cluster`, `ram-grid`, `frame` | `cover`, `content-limiter` | `stack`, `cluster` | `ram-grid`, `frame` | Local webpage synthesis from homepage section jobs, with Every Layout influence for cover/stack/cluster and MDN grid/media constraints. Risk: replacing `cover` or `content-limiter` weakens the first-viewport hook or readable explanation path; replacing `ram-grid`/`frame` is acceptable when proof or media content has a different shape. |
|
|
35
|
+
| [SaaS settings](saas-settings.md) | `fixed-sidenav-shell`, `content-limiter`, `stack` | `fixed-sidenav-shell`, `content-limiter` | `stack` | - | Local settings synthesis, with Material-style settings navigation and MDN sticky/overflow mechanics. Risk: replacing the shell changes scroll ownership and wayfinding; replacing `content-limiter` makes form-like settings harder to scan. |
|
|
36
|
+
| [Dashboard](dashboard.md) | `page-grid`, `card-grid`, `cluster` | `page-grid`, `card-grid` | `cluster` | - | Material dashboard layout and MDN grid lineage. Risk: replacing `page-grid` or `card-grid` removes comparable panel alignment; `cluster` can be adjusted only when action groups still wrap safely. |
|
|
37
|
+
| [Article page](article-page.md) | `content-limiter`, `sticky-aside`, `stack` | `content-limiter` | `stack` | `sticky-aside` | Every Layout sidebar/stack influence and MDN sticky positioning. Risk: replacing `content-limiter` harms readable measure; replacing `sticky-aside` is acceptable when supplemental content must remain in normal reading order. |
|
|
38
|
+
| [List detail](list-detail.md) | `list-detail`, `scroll-body-shell`, `stack` | `list-detail` | `scroll-body-shell`, `stack` | - | Local split-view synthesis, with Material master-detail patterns and MDN overflow mechanics. Risk: replacing `list-detail` changes the browse-and-inspect contract; shell behavior must preserve clear scroll ownership. |
|
|
39
|
+
| [Form flow](form-flow.md) | `content-limiter`, `stack`, `cluster` | `content-limiter`, `stack` | `cluster` | - | GOV.UK-style form sequencing with Every Layout stack/cluster influence. Risk: replacing `content-limiter` or `stack` breaks the single-column completion path; action grouping can vary only if labels and errors remain adjacent. |
|
|
40
|
+
| [Command surface](command-surface.md) | `scroll-body-shell`, `split-nav`, `wrap-row` | `scroll-body-shell`, `wrap-row` | `split-nav` | - | Local command-surface synthesis, with MDN overflow/flex wrapping mechanics. Risk: replacing `scroll-body-shell` destabilizes command/content regions; replacing `wrap-row` can create overflow or hidden controls. |
|
|
41
|
+
|
|
42
|
+
## Lineage And Risk Notes
|
|
43
|
+
|
|
44
|
+
### Homepage
|
|
45
|
+
|
|
46
|
+
The homepage recipe is local synthesis: it maps raw webpage content into hook, explanation, proof, comparison, conversion, and navigation jobs before visual styling. `cover` and `content-limiter` are essential because they protect first-viewport framing and readable copy. `ram-grid` and `frame` are substitutable when repeated proof or media should use `card-grid`, `columns`, or no media slot.
|
|
47
|
+
|
|
48
|
+
### SaaS settings
|
|
49
|
+
|
|
50
|
+
The SaaS settings recipe combines fixed navigation with a constrained settings column. `fixed-sidenav-shell` and `content-limiter` are essential because they define stable wayfinding and readable edit sections. The main semantic risk is accidental independent scrolling inside settings groups, which can separate labels, help text, errors, and controls.
|
|
51
|
+
|
|
52
|
+
### Dashboard
|
|
53
|
+
|
|
54
|
+
The dashboard recipe comes from page-level grid alignment and repeated panel comparison. `page-grid` and `card-grid` are essential because users scan across comparable cards and panels. `cluster` is a helper for filters and actions; it can be replaced with `wrap-row` when command density grows.
|
|
55
|
+
|
|
56
|
+
### Article page
|
|
57
|
+
|
|
58
|
+
The article recipe prioritizes reading measure before supplemental layout. `content-limiter` is essential; `sticky-aside` is substitutable because some supporting content should remain inline for source-order or narrow-screen reasons. The main risk is letting the aside become a second reading path that competes with the article.
|
|
59
|
+
|
|
60
|
+
### List detail
|
|
61
|
+
|
|
62
|
+
The list-detail recipe is a task model, not just a split screen. `list-detail` is essential because it defines peer list and detail regions with a selected item relationship. `scroll-body-shell` is a helper when the viewport owns a stable shell, but the recipe still has to name whether the list, detail, or page owns scrolling.
|
|
63
|
+
|
|
64
|
+
### Form flow
|
|
65
|
+
|
|
66
|
+
The form flow recipe is a sequential completion path. `content-limiter` and `stack` are essential because they keep groups readable and preserve vertical task progression. `cluster` is a helper for actions that need to wrap before overflow.
|
|
67
|
+
|
|
68
|
+
### Command surface
|
|
69
|
+
|
|
70
|
+
The command surface recipe is local synthesis for tool-heavy shells. `scroll-body-shell` is essential because command regions should remain stable while content changes. `wrap-row` is essential because controls must remain reachable under long labels and narrow widths; `split-nav` is a helper for separating command groups.
|
|
71
|
+
|
|
72
|
+
## Substitution Rules
|
|
73
|
+
|
|
74
|
+
- Substitute `ram-grid` with `card-grid`, `columns`, or `masonry-approx` only after naming the repeated content shape and scan behavior.
|
|
75
|
+
- Substitute `sticky-aside` with normal inline `stack` content when supporting material must be read in sequence.
|
|
76
|
+
- Substitute `cluster` with `wrap-row` when controls have longer labels or stronger overflow risk.
|
|
77
|
+
- Do not substitute `content-limiter` out of reading, form, or homepage explanation sections unless another primitive preserves readable measure.
|
|
78
|
+
- Do not substitute `scroll-body-shell`, `fixed-sidenav-shell`, or `list-detail` without renaming scroll ownership and focus-return behavior.
|
|
79
|
+
|
|
80
|
+
## IA Navigation
|
|
81
|
+
|
|
82
|
+
Parent: [Layout Recipes](index.md).
|
|
83
|
+
Next: [Quality Gates](../quality/index.md) for claim checks, or [Layout Pattern Catalog](../CATALOG.md) when replacing a primitive.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Layout Recipe
|
|
3
|
+
title: SaaS Settings
|
|
4
|
+
description: Compose a fixed side navigation with a constrained settings flow.
|
|
5
|
+
screen_type: SaaS settings
|
|
6
|
+
primary_user_task: Review and edit account or workspace settings.
|
|
7
|
+
spatial_model: Fixed navigation plus constrained primary content.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# SaaS Settings
|
|
11
|
+
|
|
12
|
+
## Recommended Pattern Stack
|
|
13
|
+
|
|
14
|
+
- [fixed-sidenav-shell](../patterns/viewport-shell/fixed-sidenav-shell.md)
|
|
15
|
+
- [content-limiter](../patterns/containment/content-limiter.md)
|
|
16
|
+
- [stack](../patterns/stacking/stack.md)
|
|
17
|
+
|
|
18
|
+
## DOM And Source Order
|
|
19
|
+
|
|
20
|
+
Place the navigation before the main settings region when it is the first meaningful wayfinding element. Keep each settings section as a semantic section with its own heading.
|
|
21
|
+
|
|
22
|
+
## Scroll Ownership
|
|
23
|
+
|
|
24
|
+
The main region owns vertical scrolling. The side navigation remains stable. Avoid adding independent scrolling to individual settings groups unless a group has a named overflow responsibility.
|
|
25
|
+
|
|
26
|
+
## Accessibility Expectations
|
|
27
|
+
|
|
28
|
+
- Focus expectation: Side-navigation links move predictably into the matching settings section, and focus remains visible when the main region scrolls.
|
|
29
|
+
- Scroll expectation: The stable navigation must not create a second hidden scroll path; each settings group should rely on the main region unless overflow is declared.
|
|
30
|
+
- Cognitive risk: Settings pages mix navigation and editing; use headings, save-state labels, and grouping so users do not lose the current section.
|
|
31
|
+
|
|
32
|
+
## Responsive Behavior
|
|
33
|
+
|
|
34
|
+
At narrow widths, navigation may become an inline region above the main content. Preserve source order and avoid visually reordering sections ahead of their headings.
|
|
35
|
+
|
|
36
|
+
## Constraints And Change Points
|
|
37
|
+
|
|
38
|
+
Use a content width token for the settings column and a gutter token for shell spacing. Keep fixed navigation width explicit so the main scroll region has a predictable inline size.
|
|
39
|
+
|
|
40
|
+
## When Not To Use
|
|
41
|
+
|
|
42
|
+
Do not use this recipe for a short one-page preference form where document flow and a simple [content-limiter](../patterns/containment/content-limiter.md) are enough.
|
|
43
|
+
|
|
44
|
+
## Related Patterns
|
|
45
|
+
|
|
46
|
+
- [scroll-body-shell](../patterns/viewport-shell/scroll-body-shell.md)
|
|
47
|
+
- [supporting-pane](../patterns/split-sidebar/supporting-pane.md)
|
|
48
|
+
|
|
49
|
+
## IA Navigation
|
|
50
|
+
|
|
51
|
+
Parent: [Layout Recipes](index.md).
|
|
52
|
+
Next: [Quality Gates](../quality/index.md) for claim checks, or [Layout Pattern Catalog](../CATALOG.md) when replacing a primitive.
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { cloneAndFreeze, hashCanonical } from "./canonical-json.mjs";
|
|
2
|
+
|
|
3
|
+
const STATE_MAP = Object.freeze({
|
|
4
|
+
PENDING: "submitted", CREATED: "submitted", QUEUED: "submitted", SUBMITTED: "submitted",
|
|
5
|
+
RUNNING: "working", STARTED: "working", IN_PROGRESS: "working", WORKING: "working",
|
|
6
|
+
COMPLETED: "completed", SUCCEEDED: "completed", SUCCESS: "completed", DONE: "completed",
|
|
7
|
+
FAILED: "failed", ERROR: "failed", ERRORED: "failed",
|
|
8
|
+
CANCELED: "canceled", CANCELLED: "canceled", ABORTED: "canceled",
|
|
9
|
+
REJECTED: "rejected", AUTH_REQUIRED: "auth-required", AUTHENTICATION_REQUIRED: "auth-required",
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const TERMINAL_STATES = new Set(["completed", "failed", "canceled", "rejected", "auth-required"]);
|
|
13
|
+
|
|
14
|
+
export class A2AProjectionError extends TypeError {
|
|
15
|
+
constructor(code, message, path = "") {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = "A2AProjectionError";
|
|
18
|
+
this.code = code;
|
|
19
|
+
if (path) this.path = path;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function fail(code, message, path) {
|
|
24
|
+
throw new A2AProjectionError(code, message, path);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function object(value) {
|
|
28
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function pick(value, ...keys) {
|
|
32
|
+
if (!object(value)) return undefined;
|
|
33
|
+
for (const key of keys) if (Object.hasOwn(value, key) && value[key] !== undefined) return value[key];
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function copy(value) {
|
|
38
|
+
return value === undefined ? undefined : structuredClone(value);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function freeze(value) {
|
|
42
|
+
return cloneAndFreeze(value);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function digest(value) {
|
|
46
|
+
return hashCanonical(value).slice("sha256:".length);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function idOf(value, ...keys) {
|
|
50
|
+
const id = pick(value, ...keys, "stableRef", "stable_ref", "id");
|
|
51
|
+
return typeof id === "string" && id.length > 0 ? id : undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function domainTaskId(source, task) {
|
|
55
|
+
const explicit = idOf(source, "domainTaskId", "domain_task_id");
|
|
56
|
+
const taskRef = idOf(task, "taskId", "task_id", "stableRef", "stable_ref");
|
|
57
|
+
const generic = !pick(source, "domainTask", "domain_task") ? idOf(source, "taskId", "task_id") : undefined;
|
|
58
|
+
return explicit
|
|
59
|
+
?? taskRef
|
|
60
|
+
?? generic
|
|
61
|
+
?? `sg:task/a2a-${digest({ task, message: pick(source, "message") }).slice(0, 20)}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function domainRunId(source, run, taskId) {
|
|
65
|
+
const explicit = idOf(source, "domainRunId", "domain_run_id");
|
|
66
|
+
const runRef = idOf(run, "runId", "run_id", "stableRef", "stable_ref");
|
|
67
|
+
const generic = !pick(source, "domainRun", "domain_run") ? idOf(source, "runId", "run_id") : undefined;
|
|
68
|
+
return explicit
|
|
69
|
+
?? runRef
|
|
70
|
+
?? generic
|
|
71
|
+
?? `sg:run/a2a-${digest({ run, taskId }).slice(0, 20)}`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function protocolIds(source, task, binding, taskId, runId) {
|
|
75
|
+
const protocolTaskId = pick(source, "protocolTaskId", "protocol_task_id", "a2aTaskId", "a2a_task_id")
|
|
76
|
+
?? pick(binding, "protocolTaskId", "protocol_task_id", "a2aTaskId", "a2a_task_id")
|
|
77
|
+
?? pick(task, "protocolTaskId", "protocol_task_id", "a2aTaskId", "a2a_task_id")
|
|
78
|
+
?? `a2a-task-${digest({ taskId, runId, message: pick(source, "message"), binding }).slice(0, 24)}`;
|
|
79
|
+
const protocolContextId = pick(source, "protocolContextId", "protocol_context_id", "a2aContextId", "a2a_context_id")
|
|
80
|
+
?? pick(binding, "protocolContextId", "protocol_context_id", "a2aContextId", "a2a_context_id")
|
|
81
|
+
?? pick(task, "contextId", "context_id")
|
|
82
|
+
?? `a2a-context-${digest({ taskId, runId }).slice(0, 24)}`;
|
|
83
|
+
if (protocolTaskId === taskId || protocolTaskId === runId) fail("protocol_task_id_conflict", "A2A task ID must not equal a domain Task or Run ID", "protocolTaskId");
|
|
84
|
+
if (protocolContextId === taskId || protocolContextId === runId || protocolContextId === protocolTaskId) fail("protocol_context_id_conflict", "A2A context ID must remain distinct from domain and task IDs", "protocolContextId");
|
|
85
|
+
return { protocolTaskId: String(protocolTaskId), protocolContextId: String(protocolContextId) };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function makeBinding(source, task, binding, taskId, runId, protocolTaskId, protocolContextId) {
|
|
89
|
+
const supplied = object(binding) ? copy(binding) : {};
|
|
90
|
+
const bindingId = pick(supplied, "bindingId", "binding_id", "protocolBindingId", "protocol_binding_id")
|
|
91
|
+
?? `sg:binding/a2a-${digest({ taskId, runId, protocolTaskId, protocolContextId }).slice(0, 24)}`;
|
|
92
|
+
if (typeof bindingId !== "string" || bindingId.length === 0) fail("protocol_binding_required", "ProtocolBinding requires a non-empty binding ID", "protocolBindingId");
|
|
93
|
+
if (bindingId === taskId || bindingId === runId || bindingId === protocolTaskId || bindingId === protocolContextId) fail("protocol_binding_id_conflict", "ProtocolBinding ID must remain distinct from every correlated ID", "protocolBindingId");
|
|
94
|
+
return freeze({
|
|
95
|
+
...supplied,
|
|
96
|
+
bindingId,
|
|
97
|
+
domainRunId: runId,
|
|
98
|
+
domainTaskId: taskId,
|
|
99
|
+
protocol: "a2a",
|
|
100
|
+
protocolBindingId: bindingId,
|
|
101
|
+
protocolContextId,
|
|
102
|
+
protocolTaskId,
|
|
103
|
+
runId,
|
|
104
|
+
taskId,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function taskFrom(input) {
|
|
109
|
+
const source = object(input) ? input : {};
|
|
110
|
+
const task = object(source.task) ? source.task : source;
|
|
111
|
+
return object(task.task) && !pick(task, "status", "id", "contextId") ? task.task : task;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function bindingFrom(input, task) {
|
|
115
|
+
return pick(input, "protocolBinding", "protocol_binding", "binding")
|
|
116
|
+
?? pick(task, "protocolBinding", "protocol_binding", "binding")
|
|
117
|
+
?? {};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function domainState(input, task) {
|
|
121
|
+
const source = object(input) ? input : {};
|
|
122
|
+
const domainTask = pick(source, "domainTask", "domain_task");
|
|
123
|
+
return pick(domainTask, "state", "status", "taskState", "task_state")
|
|
124
|
+
?? pick(task, "domainState", "domain_state")
|
|
125
|
+
?? pick(task, "state", "taskState", "task_state")
|
|
126
|
+
?? pick(pick(task, "status"), "state", "status")
|
|
127
|
+
?? "UNKNOWN";
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function taskStatus(task, state) {
|
|
131
|
+
const status = object(pick(task, "status")) ? copy(task.status) : {};
|
|
132
|
+
return { ...status, state };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function responseFor(task) {
|
|
136
|
+
const { protocolBinding: _binding, ...nested } = task;
|
|
137
|
+
return freeze({ ...task, task: nested });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function projectTask(input, state, mode = "project") {
|
|
141
|
+
const source = object(input) ? input : {};
|
|
142
|
+
const task = taskFrom(source);
|
|
143
|
+
const domainTask = object(pick(source, "domainTask", "domain_task")) ? pick(source, "domainTask", "domain_task") : task;
|
|
144
|
+
const run = pick(source, "run", "domainRun", "domain_run");
|
|
145
|
+
const taskId = domainTaskId(source, domainTask);
|
|
146
|
+
const runId = domainRunId(source, run, taskId);
|
|
147
|
+
const suppliedBinding = bindingFrom(source, task);
|
|
148
|
+
const { protocolTaskId, protocolContextId } = protocolIds(source, task, suppliedBinding, taskId, runId);
|
|
149
|
+
const binding = makeBinding(source, domainTask, suppliedBinding, taskId, runId, protocolTaskId, protocolContextId);
|
|
150
|
+
const requestedTaskId = pick(source, "taskId", "task_id");
|
|
151
|
+
const existingProtocolId = pick(task, "id", "protocolTaskId", "protocol_task_id")
|
|
152
|
+
?? (object(pick(task, "status")) ? pick(task, "taskId", "task_id") : undefined);
|
|
153
|
+
const currentProtocolId = mode === "send" ? protocolTaskId : existingProtocolId ?? protocolTaskId;
|
|
154
|
+
if (requestedTaskId !== undefined && requestedTaskId !== currentProtocolId && requestedTaskId !== protocolTaskId) fail("protocol_task_mismatch", "requested A2A task ID does not match the correlated task", "taskId");
|
|
155
|
+
const message = pick(source, "message");
|
|
156
|
+
const history = Array.isArray(pick(task, "history")) ? copy(task.history) : (message ? [copy(message)] : []);
|
|
157
|
+
const artifacts = Array.isArray(pick(task, "artifacts")) ? copy(task.artifacts) : [];
|
|
158
|
+
const projected = {
|
|
159
|
+
artifacts,
|
|
160
|
+
contextId: protocolContextId,
|
|
161
|
+
history,
|
|
162
|
+
id: currentProtocolId,
|
|
163
|
+
kind: "task",
|
|
164
|
+
metadata: {
|
|
165
|
+
...(object(pick(task, "metadata")) ? copy(task.metadata) : {}),
|
|
166
|
+
stylegallery: { domainRunId: runId, domainTaskId: taskId, protocolBindingId: binding.protocolBindingId },
|
|
167
|
+
},
|
|
168
|
+
protocolBinding: binding,
|
|
169
|
+
status: taskStatus(task, state),
|
|
170
|
+
};
|
|
171
|
+
if (mode === "send") projected.history = message ? [copy(message)] : history;
|
|
172
|
+
return freeze(projected);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Map every domain lifecycle state to an official A2A TaskState value. */
|
|
176
|
+
export function mapTaskState(value) {
|
|
177
|
+
const raw = typeof value === "string" ? value : pick(value, "state", "status", "taskState", "task_state");
|
|
178
|
+
const normalized = String(raw ?? "UNKNOWN").trim().toUpperCase().replaceAll("-", "_").replaceAll(" ", "_");
|
|
179
|
+
return STATE_MAP[normalized] ?? "unknown";
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Return the deterministic, v1-shaped Agent Card for StyleGallery. */
|
|
183
|
+
export function createAgentCard(input = {}) {
|
|
184
|
+
const source = object(input) ? input : {};
|
|
185
|
+
const skills = [
|
|
186
|
+
["discover", "Discover StyleGallery capabilities and immutable manifest."],
|
|
187
|
+
["resolve", "Resolve a StableRef or VersionID to its immutable object."],
|
|
188
|
+
["claims", "Inspect claims, evidence, validation, and governance disposition."],
|
|
189
|
+
["context", "Build a bounded retrieval context package with provenance."],
|
|
190
|
+
["ops", "List read-only operation specifications and schemas."],
|
|
191
|
+
["retrieve", "Retrieve ranked governed knowledge without mutation."],
|
|
192
|
+
].map(([id, description]) => ({ description, examples: [], id, inputModes: ["text", "data"], name: id, outputModes: ["text", "data"], tags: ["stylegallery", "knowledge"] }));
|
|
193
|
+
return freeze({
|
|
194
|
+
capabilities: { pushNotifications: false, stateTransitionHistory: true, streaming: false },
|
|
195
|
+
defaultInputModes: ["text", "data"],
|
|
196
|
+
defaultOutputModes: ["text", "data"],
|
|
197
|
+
description: typeof source.description === "string" ? source.description : "Evidence-preserving, agent-native StyleGallery knowledge operations.",
|
|
198
|
+
name: typeof source.name === "string" ? source.name : "StyleGallery Agent",
|
|
199
|
+
protocolVersion: "1.0",
|
|
200
|
+
skills,
|
|
201
|
+
supportsAuthenticatedExtendedCard: false,
|
|
202
|
+
url: typeof source.url === "string" ? source.url : "https://stylegallery.local/agent",
|
|
203
|
+
version: typeof source.version === "string" ? source.version : "1.0.0",
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Project an A2A SendMessage request into a new submitted protocol task. */
|
|
208
|
+
export function sendMessage(input) {
|
|
209
|
+
const source = object(input) ? input : fail("send_message_input_required", "sendMessage expects an object");
|
|
210
|
+
const message = pick(source, "message");
|
|
211
|
+
if (!object(message)) fail("message_required", "SendMessage requires a message object", "message");
|
|
212
|
+
const task = projectTask({ ...source, message }, "submitted", "send");
|
|
213
|
+
return responseFor(task);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Project current domain Task/Run state while preserving the A2A task ID. */
|
|
217
|
+
export function getTask(input) {
|
|
218
|
+
const source = object(input) ? input : fail("get_task_input_required", "getTask expects an object");
|
|
219
|
+
const task = taskFrom(source);
|
|
220
|
+
const taskId = pick(source, "taskId", "task_id");
|
|
221
|
+
if (taskId === undefined && !idOf(task, "id", "taskId", "task_id", "protocolTaskId", "protocol_task_id")) fail("protocol_task_required", "GetTask requires an A2A task ID", "taskId");
|
|
222
|
+
const state = mapTaskState(domainState(source, task));
|
|
223
|
+
const projected = projectTask(source, state);
|
|
224
|
+
return responseFor(projected);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** Request cancellation; terminal non-canceled states reject invalid transitions. */
|
|
228
|
+
export function cancelTask(input) {
|
|
229
|
+
const source = object(input) ? input : fail("cancel_task_input_required", "cancelTask expects an object");
|
|
230
|
+
const task = taskFrom(source);
|
|
231
|
+
const current = mapTaskState(domainState(source, task));
|
|
232
|
+
if (TERMINAL_STATES.has(current) && current !== "canceled") fail("cancellation_not_allowed", `cannot cancel a ${current} A2A task`, "task");
|
|
233
|
+
if (current === "unknown") fail("invalid_task_transition", "cannot cancel an unknown A2A task state", "task");
|
|
234
|
+
const projected = projectTask(source, "canceled");
|
|
235
|
+
return responseFor(projected);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export const buildAgentCard = createAgentCard;
|
|
239
|
+
export const projectAgentCard = createAgentCard;
|
|
240
|
+
export const agentCard = createAgentCard;
|
|
241
|
+
export const projectSendMessage = sendMessage;
|
|
242
|
+
export const handleSendMessage = sendMessage;
|
|
243
|
+
export const projectGetTask = getTask;
|
|
244
|
+
export const handleGetTask = getTask;
|
|
245
|
+
export const projectCancelTask = cancelTask;
|
|
246
|
+
export const handleCancelTask = cancelTask;
|
|
247
|
+
export const projectTaskState = mapTaskState;
|
|
248
|
+
export const toA2ATaskState = mapTaskState;
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { canonicalize, cloneAndFreeze, hashCanonical } from "./canonical-json.mjs";
|
|
2
|
+
|
|
3
|
+
const TERMINAL = new Set(["RUN_FINISHED", "RUN_ERROR"]);
|
|
4
|
+
const EVENT_TYPES = new Set([
|
|
5
|
+
"RUN_STARTED", "RUN_FINISHED", "RUN_ERROR",
|
|
6
|
+
"TEXT_MESSAGE_START", "TEXT_MESSAGE_CONTENT", "TEXT_MESSAGE_END",
|
|
7
|
+
"TOOL_CALL_START", "TOOL_CALL_ARGS", "TOOL_CALL_ARGUMENTS", "TOOL_CALL_END",
|
|
8
|
+
"STATE_SNAPSHOT", "STATE_DELTA",
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
export class AgUiProjectionError extends TypeError {
|
|
12
|
+
constructor(code, message, path = "") {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = "AgUiProjectionError";
|
|
15
|
+
this.code = code;
|
|
16
|
+
if (path) this.path = path;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function pick(value, ...keys) {
|
|
21
|
+
if (!value || typeof value !== "object") return undefined;
|
|
22
|
+
for (const key of keys) if (Object.hasOwn(value, key) && value[key] !== undefined) return value[key];
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function text(value, fallback) {
|
|
27
|
+
return typeof value === "string" && value.length > 0 ? value : fallback;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function domainId(value, ...keys) {
|
|
31
|
+
return text(pick(value, ...keys, "stable_ref", "stableRef", "id"), "unknown");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function bindingIds(binding, task, run) {
|
|
35
|
+
const bindingId = text(pick(binding, "binding_id", "bindingId", "protocol_binding_id", "protocolBindingId"), "sg:binding/agui");
|
|
36
|
+
const taskId = domainId(task, "task_id", "taskId");
|
|
37
|
+
const runId = domainId(run, "run_id", "runId");
|
|
38
|
+
const taskProtocolId = text(
|
|
39
|
+
pick(binding, "agui_task_id", "aguiTaskId", "protocol_task_id", "protocolTaskId", "protocolTask"),
|
|
40
|
+
`agui-task-${hashCanonical({ bindingId, taskId }).slice(7, 23)}`,
|
|
41
|
+
);
|
|
42
|
+
const runProtocolId = text(
|
|
43
|
+
pick(binding, "agui_run_id", "aguiRunId", "protocol_run_id", "protocolRunId", "protocolRun"),
|
|
44
|
+
`agui-run-${hashCanonical({ bindingId, runId }).slice(7, 23)}`,
|
|
45
|
+
);
|
|
46
|
+
const threadId = text(pick(binding, "thread_id", "threadId", "agui_thread_id", "aguiThreadId"), taskProtocolId);
|
|
47
|
+
return { bindingId, domainTaskId: taskId, domainRunId: runId, taskProtocolId, runProtocolId, threadId };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function eventType(event) {
|
|
51
|
+
return String(pick(event, "type", "eventType", "event_type", "name") ?? "")
|
|
52
|
+
.trim().toUpperCase().replaceAll("-", "_");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function asEvents(input) {
|
|
56
|
+
if (Array.isArray(input)) return input;
|
|
57
|
+
return pick(input, "events", "items") ?? null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function failure(code, message, path) {
|
|
61
|
+
return path ? { code, message, path } : { code, message };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Validate an AG-UI event sequence before it crosses the protocol boundary. */
|
|
65
|
+
export function validateAgUiTransitions(input) {
|
|
66
|
+
const events = asEvents(input);
|
|
67
|
+
const failures = [];
|
|
68
|
+
if (!Array.isArray(events)) return { ok: false, valid: false, failures: [failure("agui_events_required", "events must be an array", "events")] };
|
|
69
|
+
if (events.length === 0) failures.push(failure("agui_events_empty", "AG-UI event sequence cannot be empty", "events"));
|
|
70
|
+
let started = false;
|
|
71
|
+
let ended = false;
|
|
72
|
+
let openTextId = null;
|
|
73
|
+
let openToolId = null;
|
|
74
|
+
let runId = null;
|
|
75
|
+
let terminalCount = 0;
|
|
76
|
+
for (let index = 0; index < events.length; index += 1) {
|
|
77
|
+
const event = events[index];
|
|
78
|
+
const type = eventType(event);
|
|
79
|
+
const path = `/events/${index}`;
|
|
80
|
+
if (!event || typeof event !== "object" || Array.isArray(event)) {
|
|
81
|
+
failures.push(failure("agui_event_invalid", "event must be an object", path));
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (!EVENT_TYPES.has(type)) failures.push(failure("agui_event_type_invalid", `unsupported AG-UI event type ${type || "<empty>"}`, `${path}/type`));
|
|
85
|
+
const eventRunId = pick(event, "runId", "run_id");
|
|
86
|
+
if (eventRunId !== undefined && runId !== null && eventRunId !== runId) failures.push(failure("agui_run_id_mismatch", "all events must use one protocol run ID", `${path}/runId`));
|
|
87
|
+
if (eventRunId !== undefined) runId = eventRunId;
|
|
88
|
+
if (ended) failures.push(failure("agui_event_after_terminal", "no event may follow a terminal event", path));
|
|
89
|
+
if (type === "RUN_STARTED") {
|
|
90
|
+
if (started) failures.push(failure("agui_run_started_duplicate", "RUN_STARTED may occur only once", path));
|
|
91
|
+
if (index !== 0) failures.push(failure("agui_run_started_not_first", "RUN_STARTED must be the first event", path));
|
|
92
|
+
started = true;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (!started) failures.push(failure("agui_run_not_started", "event cannot precede RUN_STARTED", path));
|
|
96
|
+
if (TERMINAL.has(type)) {
|
|
97
|
+
terminalCount += 1;
|
|
98
|
+
ended = true;
|
|
99
|
+
if (openTextId !== null) failures.push(failure("agui_text_message_open", "terminal event closed an open text message", path));
|
|
100
|
+
if (openToolId !== null) failures.push(failure("agui_tool_call_open", "terminal event closed an open tool call", path));
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (type === "TEXT_MESSAGE_START") {
|
|
104
|
+
const current = pick(event, "messageId", "message_id");
|
|
105
|
+
if (typeof current !== "string" || current.length === 0) failures.push(failure("agui_message_id_required", "TEXT_MESSAGE_START requires messageId", `${path}/messageId`));
|
|
106
|
+
if (openTextId !== null) failures.push(failure("agui_text_message_nested", "text messages cannot be nested", path));
|
|
107
|
+
openTextId = current ?? null;
|
|
108
|
+
} else if (type === "TEXT_MESSAGE_CONTENT") {
|
|
109
|
+
const current = pick(event, "messageId", "message_id");
|
|
110
|
+
if (openTextId === null || current !== openTextId) failures.push(failure("agui_text_message_not_open", "TEXT_MESSAGE_CONTENT must target an open message", path));
|
|
111
|
+
} else if (type === "TEXT_MESSAGE_END") {
|
|
112
|
+
const current = pick(event, "messageId", "message_id");
|
|
113
|
+
if (openTextId === null || current !== openTextId) failures.push(failure("agui_text_message_not_open", "TEXT_MESSAGE_END must target an open message", path));
|
|
114
|
+
openTextId = null;
|
|
115
|
+
} else if (type === "TOOL_CALL_START") {
|
|
116
|
+
const current = pick(event, "toolCallId", "tool_call_id");
|
|
117
|
+
if (typeof current !== "string" || current.length === 0) failures.push(failure("agui_tool_call_id_required", "TOOL_CALL_START requires toolCallId", `${path}/toolCallId`));
|
|
118
|
+
if (openToolId !== null) failures.push(failure("agui_tool_call_nested", "tool calls cannot be nested", path));
|
|
119
|
+
openToolId = current ?? null;
|
|
120
|
+
} else if (type === "TOOL_CALL_ARGS" || type === "TOOL_CALL_ARGUMENTS") {
|
|
121
|
+
const current = pick(event, "toolCallId", "tool_call_id");
|
|
122
|
+
if (openToolId === null || current !== openToolId) failures.push(failure("agui_tool_call_not_open", "TOOL_CALL_ARGS must target an open tool call", path));
|
|
123
|
+
} else if (type === "TOOL_CALL_END") {
|
|
124
|
+
const current = pick(event, "toolCallId", "tool_call_id");
|
|
125
|
+
if (openToolId === null || current !== openToolId) failures.push(failure("agui_tool_call_not_open", "TOOL_CALL_END must target an open tool call", path));
|
|
126
|
+
openToolId = null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (!started) failures.push(failure("agui_run_started_required", "sequence must contain RUN_STARTED", "events"));
|
|
130
|
+
if (terminalCount !== 1) failures.push(failure("agui_terminal_count_invalid", "sequence must contain exactly one terminal event", "events"));
|
|
131
|
+
if (terminalCount === 1 && !TERMINAL.has(eventType(events.at(-1)))) failures.push(failure("agui_terminal_not_last", "terminal event must be last", `/events/${events.length - 1}`));
|
|
132
|
+
if (openTextId !== null) failures.push(failure("agui_text_message_unclosed", "text message did not receive TEXT_MESSAGE_END", "events"));
|
|
133
|
+
if (openToolId !== null) failures.push(failure("agui_tool_call_unclosed", "tool call did not receive TOOL_CALL_END", "events"));
|
|
134
|
+
return { ok: failures.length === 0, valid: failures.length === 0, failures };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function protocolEvent(type, ids, fields = {}) {
|
|
138
|
+
return { type, runId: ids.runProtocolId, ...fields };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Project a domain Task/Run outcome to one deterministic AG-UI 0.0.57 stream. */
|
|
142
|
+
export function projectAgUiEvents(input = {}) {
|
|
143
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) throw new AgUiProjectionError("agui_input_invalid", "projectAgUiEvents expects an object");
|
|
144
|
+
const ids = bindingIds(pick(input, "protocolBinding", "protocol_binding") ?? {}, input.task ?? {}, input.run ?? {});
|
|
145
|
+
const textValue = String(pick(input, "text", "message") ?? "");
|
|
146
|
+
const messageValue = pick(input, "messageId", "message_id") ?? `${ids.runProtocolId}:message`;
|
|
147
|
+
const tool = pick(input, "toolCall", "tool_call") ?? {};
|
|
148
|
+
const toolId = text(pick(tool, "id", "toolCallId", "tool_call_id"), `${ids.runProtocolId}:tool`);
|
|
149
|
+
const toolName = text(pick(tool, "name", "toolName", "tool_name"), "operation");
|
|
150
|
+
const toolArgs = pick(tool, "args", "arguments") ?? {};
|
|
151
|
+
const state = pick(input, "state", "snapshot") ?? {};
|
|
152
|
+
const outcome = String(pick(input, "outcome", "status") ?? pick(input.run, "state", "status") ?? pick(input.task, "state", "status") ?? "COMPLETED").toUpperCase();
|
|
153
|
+
const success = new Set(["COMPLETED", "SUCCEEDED", "SUCCESS", "OK", "PASS", "PASSED"]).has(outcome);
|
|
154
|
+
const correlation = { protocolBindingId: ids.bindingId, domainTaskId: ids.domainTaskId, domainRunId: ids.domainRunId };
|
|
155
|
+
const events = [
|
|
156
|
+
protocolEvent("RUN_STARTED", ids, { threadId: ids.threadId, input: pick(input.task, "intent", "request") ?? {}, metadata: correlation }),
|
|
157
|
+
protocolEvent("TEXT_MESSAGE_START", ids, { messageId: messageValue, role: "assistant" }),
|
|
158
|
+
protocolEvent("TEXT_MESSAGE_CONTENT", ids, { messageId: messageValue, delta: textValue }),
|
|
159
|
+
protocolEvent("TEXT_MESSAGE_END", ids, { messageId: messageValue }),
|
|
160
|
+
protocolEvent("TOOL_CALL_START", ids, { toolCallId: toolId, toolCallName: toolName }),
|
|
161
|
+
protocolEvent("TOOL_CALL_ARGS", ids, { toolCallId: toolId, delta: typeof toolArgs === "string" ? toolArgs : canonicalize(toolArgs) }),
|
|
162
|
+
protocolEvent("TOOL_CALL_END", ids, { toolCallId: toolId }),
|
|
163
|
+
protocolEvent("STATE_SNAPSHOT", ids, { snapshot: state }),
|
|
164
|
+
success
|
|
165
|
+
? protocolEvent("RUN_FINISHED", ids, { threadId: ids.threadId, result: pick(input, "result", "output") ?? { status: outcome } })
|
|
166
|
+
: protocolEvent("RUN_ERROR", ids, { threadId: ids.threadId, message: text(pick(input, "error", "errorMessage"), outcome) }),
|
|
167
|
+
];
|
|
168
|
+
const validation = validateAgUiTransitions(events);
|
|
169
|
+
if (!validation.ok) throw new AgUiProjectionError("agui_projection_invalid", "generated event sequence failed transition validation");
|
|
170
|
+
return cloneAndFreeze({ ok: true, events, protocolBinding: { ...correlation, protocol: "ag-ui" }, protocolIds: { threadId: ids.threadId, taskId: ids.taskProtocolId, runId: ids.runProtocolId } });
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export const projectAGUIEvents = projectAgUiEvents;
|
|
174
|
+
export const projectAguiEvents = projectAgUiEvents;
|
|
175
|
+
export const createAgUiEvents = projectAgUiEvents;
|
|
176
|
+
export const validateAGUITransitions = validateAgUiTransitions;
|
|
177
|
+
export const assertAgUiTransitions = validateAgUiTransitions;
|
|
178
|
+
export const validateEventSequence = validateAgUiTransitions;
|