Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1057433c2a | ||
|
|
504d9f8845 | ||
|
|
29beebb379 | ||
|
|
e95b85afee | ||
|
|
d7e1ad1c0c | ||
|
|
714090a83d | ||
|
|
9347de298f | ||
|
|
62d2afc152 |
No files matched your search
@@ -31,6 +31,10 @@ Top-level sections are Organization, Strategy, and Contribution, each with an in
|
||||
|
||||
Archify 3.0.1 is vendored under `vendor/archify/` from commit `d5a1333d7447c866a765adac7d4d062f2f02e4d2`. MIT and third-party notices are preserved. `diagrams/` holds editable JSON; `pnpm diagrams:build` validates and generates local standalone viewers before every site build. The iframe is sandboxed without same-origin permission. See `docs/contribution/diagrams.md` for capability analysis, authoring, boundaries, and upgrade procedure.
|
||||
|
||||
The build checks upstream provenance before applying the portal integration: `scripts/inline-viewer.css` restores compact embed controls, two guarded preset restrictions are removed from generated viewers only, and `scripts/viewer-theme-bridge.js` synchronizes theme with the portal through source-validated messages. Vendor files remain unchanged. Delivery receipts describe the upstream artifact before this integration, not the final served bytes. Theme changes do not reload viewers or discard visual presets. PNG, JPEG, WebP and SVG downloads remain available; browser-dependent exports stay disabled when unsupported.
|
||||
|
||||
For interaction acceptance, serve the production build on port 4173 and run `pnpm test:browser` with an installed Playwright Chromium (or `PLAYWRIGHT_CHROMIUM_EXECUTABLE`). This checks story layouts, portal/diagram theme synchronization, all four presets, actual exported artifact decoding, route changes, and fullscreen click/Escape/focus behavior at desktop and mobile widths. Screenshots, downloads and results are saved under ignored `.cache/` directories.
|
||||
|
||||
## Design
|
||||
|
||||
Inspired by https://www.eldorado.gg/: Lato, near-black navigation, charcoal, gold `#ffc950`, light panels, bold headings. Original documentation artwork and square panels; no official marketplace assets or business claims. See `docs/core/reference.md`.
|
||||
@@ -1,156 +1,52 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"diagram_type": "architecture",
|
||||
"meta": {
|
||||
"title": "TO-BE: coordination without authority transfer",
|
||||
"output": "static/diagrams/strategy-target-mobile/index.html",
|
||||
"quality_profile": "showcase",
|
||||
"legend": {
|
||||
"mode": "hidden"
|
||||
}
|
||||
},
|
||||
"meta": {"title": "TO-BE: autonomous SDLC, human direction", "output": "static/diagrams/strategy-target-mobile/index.html", "quality_profile": "showcase", "legend": {"mode": "hidden"}},
|
||||
"components": [
|
||||
{
|
||||
"id": "people",
|
||||
"type": "backend",
|
||||
"label": "People authorize",
|
||||
"sublabel": "Retain accountability",
|
||||
"pos": [
|
||||
40,
|
||||
20
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "coordinator",
|
||||
"type": "backend",
|
||||
"label": "SDLC coordinator",
|
||||
"sublabel": "Routes approved work",
|
||||
"pos": [
|
||||
40,
|
||||
260
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "clients",
|
||||
"type": "backend",
|
||||
"label": "Local coding agents",
|
||||
"sublabel": "Propose changes",
|
||||
"pos": [
|
||||
40,
|
||||
380
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gates",
|
||||
"type": "security",
|
||||
"label": "Repository + CI",
|
||||
"sublabel": "Verify exact revision",
|
||||
"pos": [
|
||||
40,
|
||||
500
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "registry",
|
||||
"type": "backend",
|
||||
"label": "Governed skills",
|
||||
"sublabel": "Pinned + qualified",
|
||||
"pos": [
|
||||
40,
|
||||
140
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "approval",
|
||||
"type": "security",
|
||||
"label": "Human approval",
|
||||
"sublabel": "Exact artifact + scope",
|
||||
"pos": [
|
||||
40,
|
||||
620
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "release",
|
||||
"type": "security",
|
||||
"label": "Release service",
|
||||
"sublabel": "Production credentials",
|
||||
"pos": [
|
||||
40,
|
||||
740
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
}
|
||||
{"id":"humans","type":"backend","label":"Humans","sublabel":"Direct, guide and review","pos":[140,20],"size":[360,100]},
|
||||
{"id":"agent","type":"security","label":"Autonomous agent","sublabel":"Executes / governs SDLC","pos":[140,200],"size":[360,120]},
|
||||
{"id":"scope","type":"backend","label":"Scope","sublabel":"01","pos":[100,440],"size":[240,100]},
|
||||
{"id":"architecture","type":"backend","label":"Architecture","sublabel":"02","pos":[100,660],"size":[240,100]},
|
||||
{"id":"ui-ux","type":"backend","label":"UI/UX","sublabel":"03","pos":[100,880],"size":[240,100]},
|
||||
{"id":"planning","type":"backend","label":"Planning","sublabel":"04","pos":[100,1100],"size":[240,100]},
|
||||
{"id":"delivery","type":"backend","label":"Delivery","sublabel":"05","pos":[100,1320],"size":[240,100]},
|
||||
{"id":"uat","type":"backend","label":"UAT","sublabel":"06","pos":[100,1540],"size":[240,100]},
|
||||
{"id":"security","type":"backend","label":"Security","sublabel":"07","pos":[100,1760],"size":[240,100]},
|
||||
{"id":"releases","type":"backend","label":"Releases","sublabel":"08","pos":[100,1980],"size":[240,100]},
|
||||
{"id":"code-scope","type":"frontend","label":"Code agent","sublabel":"Human-controlled","pos":[400,440],"size":[240,100]},
|
||||
{"id":"code-architecture","type":"frontend","label":"Code agent","sublabel":"Human-controlled","pos":[400,660],"size":[240,100]},
|
||||
{"id":"code-ui-ux","type":"frontend","label":"Code agent","sublabel":"Human-controlled","pos":[400,880],"size":[240,100]},
|
||||
{"id":"code-planning","type":"frontend","label":"Code agent","sublabel":"Human-controlled","pos":[400,1100],"size":[240,100]},
|
||||
{"id":"code-delivery","type":"frontend","label":"Code agent","sublabel":"Human-controlled","pos":[400,1320],"size":[240,100]},
|
||||
{"id":"code-uat","type":"frontend","label":"Code agent","sublabel":"Human-controlled","pos":[400,1540],"size":[240,100]},
|
||||
{"id":"code-security","type":"frontend","label":"Code agent","sublabel":"Human-controlled","pos":[400,1760],"size":[240,100]},
|
||||
{"id":"code-releases","type":"frontend","label":"Code agent","sublabel":"Human-controlled","pos":[400,1980],"size":[240,100]}
|
||||
],
|
||||
"connections": [
|
||||
{
|
||||
"id": "flow-0",
|
||||
"from": "people",
|
||||
"to": "coordinator",
|
||||
"label": "Authorize scope"
|
||||
},
|
||||
{
|
||||
"id": "flow-1",
|
||||
"from": "coordinator",
|
||||
"to": "clients",
|
||||
"label": "Route tasks"
|
||||
},
|
||||
{
|
||||
"id": "flow-2",
|
||||
"from": "registry",
|
||||
"to": "coordinator",
|
||||
"label": "Guide"
|
||||
},
|
||||
{
|
||||
"id": "flow-3",
|
||||
"from": "registry",
|
||||
"to": "clients",
|
||||
"label": "Guide"
|
||||
},
|
||||
{
|
||||
"id": "flow-4",
|
||||
"from": "clients",
|
||||
"to": "gates",
|
||||
"label": "Propose"
|
||||
},
|
||||
{
|
||||
"id": "flow-5",
|
||||
"from": "gates",
|
||||
"to": "approval",
|
||||
"label": "Evidence"
|
||||
},
|
||||
{
|
||||
"id": "flow-6",
|
||||
"from": "approval",
|
||||
"to": "release",
|
||||
"label": "Authorize"
|
||||
}
|
||||
{"id":"human-direction","from":"humans","to":"agent","label":"Guide","fromSide":"bottom","toSide":"top"},
|
||||
{"id":"govern-scope","from":"agent","to":"scope","label":"","variant":"security","fromSide":"left","toSide":"left","via":[[60,260],[60,490]]},
|
||||
{"id":"govern-architecture","from":"agent","to":"architecture","label":"","variant":"security","fromSide":"left","toSide":"left","via":[[60,260],[60,710]]},
|
||||
{"id":"govern-ui-ux","from":"agent","to":"ui-ux","label":"","variant":"security","fromSide":"left","toSide":"left","via":[[60,260],[60,930]]},
|
||||
{"id":"govern-planning","from":"agent","to":"planning","label":"","variant":"security","fromSide":"left","toSide":"left","via":[[60,260],[60,1150]]},
|
||||
{"id":"govern-delivery","from":"agent","to":"delivery","label":"","variant":"security","fromSide":"left","toSide":"left","via":[[60,260],[60,1370]]},
|
||||
{"id":"govern-uat","from":"agent","to":"uat","label":"","variant":"security","fromSide":"left","toSide":"left","via":[[60,260],[60,1590]]},
|
||||
{"id":"govern-security","from":"agent","to":"security","label":"","variant":"security","fromSide":"left","toSide":"left","via":[[60,260],[60,1810]]},
|
||||
{"id":"govern-releases","from":"agent","to":"releases","label":"","variant":"security","fromSide":"left","toSide":"left","via":[[60,260],[60,2030]]},
|
||||
{"id":"stage-1","from":"scope","to":"architecture","label":"Then","variant":"emphasis","fromSide":"bottom","toSide":"top"},
|
||||
{"id":"stage-2","from":"architecture","to":"ui-ux","label":"Then","variant":"emphasis","fromSide":"bottom","toSide":"top"},
|
||||
{"id":"stage-3","from":"ui-ux","to":"planning","label":"Then","variant":"emphasis","fromSide":"bottom","toSide":"top"},
|
||||
{"id":"stage-4","from":"planning","to":"delivery","label":"Then","variant":"emphasis","fromSide":"bottom","toSide":"top"},
|
||||
{"id":"stage-5","from":"delivery","to":"uat","label":"Then","variant":"emphasis","fromSide":"bottom","toSide":"top"},
|
||||
{"id":"stage-6","from":"uat","to":"security","label":"Then","variant":"emphasis","fromSide":"bottom","toSide":"top"},
|
||||
{"id":"stage-7","from":"security","to":"releases","label":"Then","variant":"emphasis","fromSide":"bottom","toSide":"top"},
|
||||
{"id":"stage-8","from":"releases","to":"scope","label":"Repeat indefinitely","variant":"emphasis","fromSide":"bottom","toSide":"top","via":[[220,2140],[680,2140],[680,380],[220,380]],"labelAt":[440,2180]},
|
||||
{"id":"contribute-scope","from":"code-scope","to":"scope","label":"Contribute","variant":"dashed","fromSide":"left","toSide":"right"},
|
||||
{"id":"contribute-architecture","from":"code-architecture","to":"architecture","label":"Contribute","variant":"dashed","fromSide":"left","toSide":"right"},
|
||||
{"id":"contribute-ui-ux","from":"code-ui-ux","to":"ui-ux","label":"Contribute","variant":"dashed","fromSide":"left","toSide":"right"},
|
||||
{"id":"contribute-planning","from":"code-planning","to":"planning","label":"Contribute","variant":"dashed","fromSide":"left","toSide":"right"},
|
||||
{"id":"contribute-delivery","from":"code-delivery","to":"delivery","label":"Contribute","variant":"dashed","fromSide":"left","toSide":"right"},
|
||||
{"id":"contribute-uat","from":"code-uat","to":"uat","label":"Contribute","variant":"dashed","fromSide":"left","toSide":"right"},
|
||||
{"id":"contribute-security","from":"code-security","to":"security","label":"Contribute","variant":"dashed","fromSide":"left","toSide":"right"},
|
||||
{"id":"contribute-releases","from":"code-releases","to":"releases","label":"Contribute","variant":"dashed","fromSide":"left","toSide":"right"}
|
||||
]
|
||||
}
|
||||
@@ -1,156 +1,52 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"diagram_type": "architecture",
|
||||
"meta": {
|
||||
"title": "TO-BE: coordination without authority transfer",
|
||||
"output": "static/diagrams/strategy-target/index.html",
|
||||
"quality_profile": "showcase",
|
||||
"legend": {
|
||||
"mode": "hidden"
|
||||
}
|
||||
},
|
||||
"meta": {"title": "TO-BE: autonomous SDLC, human direction", "output": "static/diagrams/strategy-target/index.html", "quality_profile": "showcase", "legend": {"mode": "hidden"}},
|
||||
"components": [
|
||||
{
|
||||
"id": "people",
|
||||
"type": "backend",
|
||||
"label": "People authorize",
|
||||
"sublabel": "Retain accountability",
|
||||
"pos": [
|
||||
20,
|
||||
20
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "coordinator",
|
||||
"type": "backend",
|
||||
"label": "SDLC coordinator",
|
||||
"sublabel": "Routes approved work",
|
||||
"pos": [
|
||||
20,
|
||||
140
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "clients",
|
||||
"type": "backend",
|
||||
"label": "Local coding agents",
|
||||
"sublabel": "Propose changes",
|
||||
"pos": [
|
||||
20,
|
||||
260
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gates",
|
||||
"type": "security",
|
||||
"label": "Repository + CI",
|
||||
"sublabel": "Verify exact revision",
|
||||
"pos": [
|
||||
20,
|
||||
380
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "registry",
|
||||
"type": "backend",
|
||||
"label": "Governed skills",
|
||||
"sublabel": "Pinned + qualified",
|
||||
"pos": [
|
||||
260,
|
||||
140
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "approval",
|
||||
"type": "security",
|
||||
"label": "Human approval",
|
||||
"sublabel": "Exact artifact + scope",
|
||||
"pos": [
|
||||
260,
|
||||
380
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "release",
|
||||
"type": "security",
|
||||
"label": "Release service",
|
||||
"sublabel": "Production credentials",
|
||||
"pos": [
|
||||
260,
|
||||
500
|
||||
],
|
||||
"size": [
|
||||
180,
|
||||
60
|
||||
]
|
||||
}
|
||||
{"id":"humans","type":"backend","label":"Humans","sublabel":"Direct, guide and review","pos":[465,322.5],"size":[270,75]},
|
||||
{"id":"agent","type":"security","label":"Autonomous agent","sublabel":"Executes / governs SDLC","pos":[465,450],"size":[270,90]},
|
||||
{"id":"scope","type":"backend","label":"Scope","sublabel":"01","pos":[510,187.5],"size":[180,75]},
|
||||
{"id":"architecture","type":"backend","label":"Architecture","sublabel":"02","pos":[780,187.5],"size":[180,75]},
|
||||
{"id":"ui-ux","type":"backend","label":"UI/UX","sublabel":"03","pos":[780,457.5],"size":[180,75]},
|
||||
{"id":"planning","type":"backend","label":"Planning","sublabel":"04","pos":[780,727.5],"size":[180,75]},
|
||||
{"id":"delivery","type":"backend","label":"Delivery","sublabel":"05","pos":[510,727.5],"size":[180,75]},
|
||||
{"id":"uat","type":"backend","label":"UAT","sublabel":"06","pos":[240,727.5],"size":[180,75]},
|
||||
{"id":"security","type":"backend","label":"Security","sublabel":"07","pos":[240,457.5],"size":[180,75]},
|
||||
{"id":"releases","type":"backend","label":"Releases","sublabel":"08","pos":[240,187.5],"size":[180,75]},
|
||||
{"id":"code-scope","type":"frontend","label":"Code agent","sublabel":"Human-controlled","pos":[510,37.5],"size":[180,75]},
|
||||
{"id":"code-architecture","type":"frontend","label":"Code agent","sublabel":"Human-controlled","pos":[780,37.5],"size":[180,75]},
|
||||
{"id":"code-ui-ux","type":"frontend","label":"Code agent","sublabel":"Human-controlled","pos":[1005,457.5],"size":[180,75]},
|
||||
{"id":"code-planning","type":"frontend","label":"Code agent","sublabel":"Human-controlled","pos":[780,877.5],"size":[180,75]},
|
||||
{"id":"code-delivery","type":"frontend","label":"Code agent","sublabel":"Human-controlled","pos":[510,877.5],"size":[180,75]},
|
||||
{"id":"code-uat","type":"frontend","label":"Code agent","sublabel":"Human-controlled","pos":[240,877.5],"size":[180,75]},
|
||||
{"id":"code-security","type":"frontend","label":"Code agent","sublabel":"Human-controlled","pos":[15,457.5],"size":[180,75]},
|
||||
{"id":"code-releases","type":"frontend","label":"Code agent","sublabel":"Human-controlled","pos":[240,37.5],"size":[180,75]}
|
||||
],
|
||||
"connections": [
|
||||
{
|
||||
"id": "flow-0",
|
||||
"from": "people",
|
||||
"to": "coordinator",
|
||||
"label": "Authorize scope"
|
||||
},
|
||||
{
|
||||
"id": "flow-1",
|
||||
"from": "coordinator",
|
||||
"to": "clients",
|
||||
"label": "Route tasks"
|
||||
},
|
||||
{
|
||||
"id": "flow-2",
|
||||
"from": "registry",
|
||||
"to": "coordinator",
|
||||
"label": "Guide"
|
||||
},
|
||||
{
|
||||
"id": "flow-3",
|
||||
"from": "registry",
|
||||
"to": "clients",
|
||||
"label": "Guide"
|
||||
},
|
||||
{
|
||||
"id": "flow-4",
|
||||
"from": "clients",
|
||||
"to": "gates",
|
||||
"label": "Propose"
|
||||
},
|
||||
{
|
||||
"id": "flow-5",
|
||||
"from": "gates",
|
||||
"to": "approval",
|
||||
"label": "Evidence"
|
||||
},
|
||||
{
|
||||
"id": "flow-6",
|
||||
"from": "approval",
|
||||
"to": "release",
|
||||
"label": "Authorize"
|
||||
}
|
||||
{"id":"human-direction","from":"humans","to":"agent","label":"Guide","fromSide":"bottom","toSide":"top"},
|
||||
{"id":"govern-scope","from":"agent","to":"scope","label":"","variant":"security","fromSide":"right","toSide":"bottom","via":[[750,495],[750,292.5],[600,292.5]]},
|
||||
{"id":"govern-architecture","from":"agent","to":"architecture","label":"","variant":"security","fromSide":"right","toSide":"bottom","via":[[765,495],[765,345],[870,345]]},
|
||||
{"id":"govern-ui-ux","from":"agent","to":"ui-ux","label":"","variant":"security","fromSide":"right","toSide":"left"},
|
||||
{"id":"govern-planning","from":"agent","to":"planning","label":"","variant":"security","fromSide":"right","toSide":"top","via":[[765,495],[765,690],[870,690]]},
|
||||
{"id":"govern-delivery","from":"agent","to":"delivery","label":"","variant":"security","fromSide":"bottom","toSide":"top"},
|
||||
{"id":"govern-uat","from":"agent","to":"uat","label":"","variant":"security","fromSide":"left","toSide":"top","via":[[435,495],[435,690],[330,690]]},
|
||||
{"id":"govern-security","from":"agent","to":"security","label":"","variant":"security","fromSide":"left","toSide":"right"},
|
||||
{"id":"govern-releases","from":"agent","to":"releases","label":"","variant":"security","fromSide":"left","toSide":"bottom","via":[[435,495],[435,345],[330,345]]},
|
||||
{"id":"stage-1","from":"scope","to":"architecture","label":"Then","variant":"emphasis","fromSide":"right","toSide":"left"},
|
||||
{"id":"stage-2","from":"architecture","to":"ui-ux","label":"Then","variant":"emphasis","fromSide":"bottom","toSide":"top"},
|
||||
{"id":"stage-3","from":"ui-ux","to":"planning","label":"Then","variant":"emphasis","fromSide":"bottom","toSide":"top"},
|
||||
{"id":"stage-4","from":"planning","to":"delivery","label":"Then","variant":"emphasis","fromSide":"left","toSide":"right"},
|
||||
{"id":"stage-5","from":"delivery","to":"uat","label":"Then","variant":"emphasis","fromSide":"left","toSide":"right"},
|
||||
{"id":"stage-6","from":"uat","to":"security","label":"Then","variant":"emphasis","fromSide":"top","toSide":"bottom"},
|
||||
{"id":"stage-7","from":"security","to":"releases","label":"Then","variant":"emphasis","fromSide":"top","toSide":"bottom"},
|
||||
{"id":"stage-8","from":"releases","to":"scope","label":"Repeat indefinitely","variant":"emphasis","fromSide":"right","toSide":"left","labelAt":[465,170]},
|
||||
{"id":"contribute-scope","from":"code-scope","to":"scope","label":"Contribute","variant":"dashed","fromSide":"bottom","toSide":"top"},
|
||||
{"id":"contribute-architecture","from":"code-architecture","to":"architecture","label":"Contribute","variant":"dashed","fromSide":"bottom","toSide":"top"},
|
||||
{"id":"contribute-ui-ux","from":"code-ui-ux","to":"ui-ux","label":"Contribute","variant":"dashed","fromSide":"left","toSide":"right","labelAt":[990,570]},
|
||||
{"id":"contribute-planning","from":"code-planning","to":"planning","label":"Contribute","variant":"dashed","fromSide":"top","toSide":"bottom"},
|
||||
{"id":"contribute-delivery","from":"code-delivery","to":"delivery","label":"Contribute","variant":"dashed","fromSide":"top","toSide":"bottom"},
|
||||
{"id":"contribute-uat","from":"code-uat","to":"uat","label":"Contribute","variant":"dashed","fromSide":"top","toSide":"bottom"},
|
||||
{"id":"contribute-security","from":"code-security","to":"security","label":"Contribute","variant":"dashed","fromSide":"right","toSide":"left","labelAt":[220,570]},
|
||||
{"id":"contribute-releases","from":"code-releases","to":"releases","label":"Contribute","variant":"dashed","fromSide":"bottom","toSide":"top"}
|
||||
]
|
||||
}
|
||||
+12
-10
@@ -1,7 +1,7 @@
|
||||
---
|
||||
id: index
|
||||
title: AI adoption strategy
|
||||
description: Govern skills first; outcomes before activity; explicit target status.
|
||||
description: An autonomous SDLC cycle directed, guided and reviewed by humans, supported by governed skills.
|
||||
slug: /
|
||||
hide_table_of_contents: true
|
||||
---
|
||||
@@ -12,15 +12,17 @@ import ArchifyDiagram from '@site/src/components/ArchifyDiagram';
|
||||
|
||||
**TO-BE · Target operating model — not installed controls or company results.**
|
||||
|
||||
## How do we reach dependable AI-assisted work?
|
||||
## Autonomous SDLC, human direction
|
||||
|
||||
Start with governed skills, then widen coordination only when evidence supports it. The destination is one logical SDLC coordinator alongside people using qualified local coding agents.
|
||||
<ArchifyDiagram name="strategy-target" title="Autonomous agent executes and governs the SDLC; humans direct, guide and review" story />
|
||||
|
||||
<ArchifyDiagram name="strategy-journey" title="Skills and quality evidence precede wider coordination" story />
|
||||
An autonomous agent executes and governs every phase. Humans direct, guide and review it; a separate human-controlled code agent contributes to each phase from outside the cycle.
|
||||
|
||||
- **Skills first:** reusable working knowledge, not permission to act.
|
||||
- **People stay accountable:** the six departments and eleven teams keep their decisions.
|
||||
- **Outcomes before activity:** quality, risk and total cost determine whether to continue.
|
||||
**Solid arrows:** lifecycle · **Dotted spokes:** execution and governance · **Dashed arrows:** contributions.
|
||||
|
||||
**Scope → Architecture → UI/UX → Planning → Delivery → UAT → Security → Releases → Scope**
|
||||
|
||||
Releases returns to Scope and the cycle repeats indefinitely. Governed skills and measured outcomes support the path toward this target; they are not a substitute for it.
|
||||
|
||||
<details>
|
||||
<summary>Strategy rationale and reading map</summary>
|
||||
@@ -31,7 +33,7 @@ Start with governed skills, then widen coordination only when evidence supports
|
||||
|
||||
Eldorado's target is to make AI-assisted work dependable across the organization: useful guidance, clear human ownership, controlled execution and measurable outcomes. The first investment is a governed skills system, not an autonomous replacement for the organization. A skill packages reusable working knowledge, prerequisites and verification steps. It helps an agent perform a task; it does not grant permission or guarantee that the work is correct.
|
||||
|
||||
The target combines one logical SDLC coordinating agent with developer choice on local laptops. Copilot, Claude Code and Cline remain client options within a qualified support matrix. They receive the same reviewed portable skill content through client-specific adapters. Repository, CI and release services remain the enforcement points, independently of what any agent says or which local tool produced a change.
|
||||
The target combines an autonomous agent executing and governing the SDLC with humans directing, guiding and reviewing it. Developer choice on local laptops remains a supporting option: Copilot, Claude Code and Cline can receive reviewed portable skill content through qualified adapters. Repository, CI and release controls support execution; detailed approval policies remain implementation decisions, not new roles established by this strategy.
|
||||
|
||||
The strategy describes the intended operating model and the evidence required to introduce it. It does not establish that these controls are installed, that every client is supported, or that AI has improved Eldorado's performance. The current reference is the documented six-department organization. Rollout begins by validating actual clients, responsibilities, data boundaries and available evidence.
|
||||
|
||||
@@ -39,7 +41,7 @@ Success means accepted outcomes with maintained quality, controlled risk and und
|
||||
|
||||
## Navigate the strategy
|
||||
|
||||
- [Target operating model](./target-operating-model.mdx) — One logical coordinator; local client choice; independent authority; all departments.
|
||||
- [Target operating model](./target-operating-model.mdx) — The autonomous SDLC cycle, with human direction, guidance and review.
|
||||
- [Skills governance](./skills-governance.mdx) — Contract, lifecycle, decision rights, adapters, locking, exceptions.
|
||||
- [Skills quality](./skills-quality.mdx) — Risk tiers, independent evaluation, repeated trials, adversarial conformance.
|
||||
- [Skills adoption](./skills-adoption.mdx) — Workflow-led coaching, client choice, feedback, manual path.
|
||||
@@ -49,6 +51,6 @@ Success means accepted outcomes with maintained quality, controlled risk and und
|
||||
|
||||
</details>
|
||||
|
||||
**Next:** [See how people, skills and delivery controls connect →](./target-operating-model.mdx)
|
||||
**Next:** [Explore the autonomous SDLC cycle →](./target-operating-model.mdx)
|
||||
|
||||
</div>
|
||||
@@ -1,7 +1,8 @@
|
||||
---
|
||||
id: target-operating-model
|
||||
title: Target operating model
|
||||
description: One logical coordinator; local client choice; independent authority; all departments.
|
||||
sidebar_label: Target
|
||||
description: An autonomous agent executes and governs the SDLC; humans direct, guide and review the repeating cycle.
|
||||
hide_table_of_contents: true
|
||||
---
|
||||
|
||||
@@ -9,42 +10,44 @@ import ArchifyDiagram from '@site/src/components/ArchifyDiagram';
|
||||
|
||||
<div className="strategy-content">
|
||||
|
||||
**TO-BE · Target operating model — not installed controls or company results.**
|
||||
**TO-BE · Target operating model — the intended strategy, not a deployed system.**
|
||||
|
||||
## Who coordinates, and who can authorize delivery?
|
||||
## Autonomous SDLC, human direction
|
||||
|
||||
One logical coordinator routes work; people retain authority. Local agents propose changes, while independent CI and human approval govern what the release service can execute.
|
||||
<ArchifyDiagram name="strategy-target" title="Autonomous agent executes and governs the SDLC; humans direct, guide and review" story />
|
||||
|
||||
<ArchifyDiagram name="strategy-target" title="People authorize; skills guide; independent gates protect release" story />
|
||||
An autonomous agent executes and governs every phase. Humans direct, guide and review it; a separate human-controlled code agent contributes to each phase from outside the cycle.
|
||||
|
||||
- **Local choice:** Copilot, Claude Code and Cline use qualified adapters and pinned skills.
|
||||
- **Central controls:** neither laptops nor the coordinator hold production credentials.
|
||||
- **Existing organization:** six departments, eleven teams; Data & AI owns coordination, not their decisions.
|
||||
**Solid arrows:** lifecycle · **Dotted spokes:** execution and governance · **Dashed arrows:** contributions.
|
||||
|
||||
**Scope → Architecture → UI/UX → Planning → Delivery → UAT → Security → Releases → Scope**
|
||||
|
||||
Each stage follows the previous one. Releases returns to Scope; the cycle repeats indefinitely, with human direction informing the next iteration.
|
||||
|
||||
<details>
|
||||
<summary>Trust boundaries, recovery and detailed delivery path</summary>
|
||||
<summary>Supporting implementation options: trust boundaries and recovery</summary>
|
||||
|
||||
## Coordination without transferred authority
|
||||
## Controls that support autonomous execution
|
||||
|
||||
The diagram is an overview; the equivalent delivery path below remains readable on narrow screens. Expand the diagram to inspect its trust boundaries and use its zoom controls for individual relationships.
|
||||
The lifecycle above is the strategic direction. The earlier control design below is a supporting implementation option, not an agreed assignment of approval roles or a claim of installed controls. Detailed approval policy and responsibilities remain to be defined with humans.
|
||||
|
||||
1. Accountable people authorize the work scope; one logical coordinator routes tasks.
|
||||
1. Humans direct, guide and review; the autonomous agent executes and governs work across the lifecycle.
|
||||
2. The reviewed registry supplies pinned skills and qualified adapters to coordinator and local clients.
|
||||
3. Laptop contributors propose changes; independent repository and CI checks verify the exact revision.
|
||||
4. A human approves that artifact; only the release service holds production credentials.
|
||||
5. Restricted audit and measurement evidence returns to people and informs the next authorized task.
|
||||
3. Laptop contributors can participate alongside the agent; independent repository and CI checks verify the exact revision.
|
||||
4. A separate release service and artifact-scoped approval are possible controls, subject to the policy humans define.
|
||||
5. Restricted audit and measurement evidence supports human review and the next Scope iteration.
|
||||
|
||||
<ArchifyDiagram name="ai-adoption-target" title="TO-BE AI adoption: human accountability, no direct production authority for laptops or coordinator" />
|
||||
<ArchifyDiagram name="ai-adoption-target" title="Supporting control option, not agreed approval policy: isolated release authority" />
|
||||
|
||||
**TO-BE · Target operating model.** Introduction depends on the [roadmap gates](./roadmap.mdx); these pages do not describe installed controls or measured company results.
|
||||
|
||||
### One coordination function, distributed contributions
|
||||
|
||||
Data & AI owns the coordinating function, with the existing AI Architect function connecting it to Technology leadership. The logical coordinator maintains approved work records, dependencies, evidence requests, bounded retries and escalation. It can summarize, propose work and route decisions. It cannot declare its own work approved, change protected checks, authorize production release or replace the responsible human.
|
||||
The autonomous agent is more than a task router: it executes and governs the repeating SDLC under human direction, guidance and review. A coordinating function maintains work records, dependencies, evidence requests, bounded retries and escalation. Data & AI and the existing AI Architect function are possible implementation contributors, not newly assigned approval authorities. Autonomous execution does not remove human involvement or allow the agent to redefine its own controls.
|
||||
|
||||
One logical coordinator does not mean one fragile process. A durable task ledger, explicit state transitions, leases and idempotency keys allow recovery or multiple replicas without duplicate delivery. Inference outages stop new agent decisions, not the organization's ability to take over. People can resume work through the normal deterministic workflow. Evidence and authorization services fail closed for privileged actions; missing evidence never becomes permission.
|
||||
|
||||
The delivery path is: an approved problem and acceptance criteria; an authorized task; local or isolated worker proposals; independently reviewed repository changes; exact-revision CI evidence; a human release decision; execution by the release service; then operational feedback. Product Management accepts the outcome hypothesis and business result. Product Design reviews experience and accessibility. Product Engineering owns the code and maintainability. Quality Engineering assesses quality independently. Architecture & Security assesses risk. Platform & IT operates the delivery boundary. No arrow from a laptop or coordinator goes directly to production authority.
|
||||
These controls support the sequence Scope → Architecture → UI/UX → Planning → Delivery → UAT → Security → Releases → Scope; they do not replace it with a separate lifecycle. Product, engineering, design, quality, security and platform expertise inform human direction and review. Specific approval assignments and release permissions are implementation decisions still to be defined.
|
||||
|
||||
### Trust and authority boundaries
|
||||
|
||||
@@ -57,10 +60,12 @@ Central controls verify protected-branch review, actual artifact identity, exact
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Responsibilities and first workflows for all eleven teams</summary>
|
||||
<summary>Candidate contributions and first workflows for all eleven teams</summary>
|
||||
|
||||
### Department contributions
|
||||
|
||||
The existing team structure provides a starting point. The contributions below are implementation proposals, not settled approval roles or lifecycle gates.
|
||||
|
||||
### Product / Product Management
|
||||
|
||||
**Contribution and retained human decision** — Own pilot priorities, acceptance criteria and outcome hypotheses; decide product value.
|
||||
|
||||
+2
-1
@@ -15,12 +15,13 @@
|
||||
"verify:build": "node scripts/verify.mjs --build",
|
||||
"diagrams:build": "node scripts/build-diagrams.mjs",
|
||||
"test": "node --test scripts/*.test.mjs",
|
||||
"test:browser": "node scripts/browser/strategy-story.cjs",
|
||||
"test:browser": "node scripts/browser/strategy-story.cjs && node scripts/browser/inline-controls.cjs && node scripts/browser/sdlc-cycle.cjs",
|
||||
"kpi:example": "node scripts/ai-kpi.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.10.2",
|
||||
"@docusaurus/preset-classic": "3.10.2",
|
||||
"@docusaurus/theme-common": "3.10.2",
|
||||
"@easyops-cn/docusaurus-search-local": "0.55.3",
|
||||
"@fontsource/lato": "5.2.6",
|
||||
"@mdx-js/react": "3.1.1",
|
||||
|
||||
Generated
+3
@@ -17,6 +17,9 @@ importers:
|
||||
'@docusaurus/preset-classic':
|
||||
specifier: 3.10.2
|
||||
version: 3.10.2(@algolia/client-search@5.59.0)(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(postcss@8.5.28)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(search-insights@2.17.3)(typescript@5.9.3)
|
||||
'@docusaurus/theme-common':
|
||||
specifier: 3.10.2
|
||||
version: 3.10.2(@docusaurus/plugin-content-docs@3.10.2(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@19.1.1))(postcss@8.5.28)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.9.3))(postcss@8.5.28)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||
'@easyops-cn/docusaurus-search-local':
|
||||
specifier: 0.55.3
|
||||
version: 0.55.3(@docusaurus/theme-common@3.10.2(@docusaurus/plugin-content-docs@3.10.2(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@19.1.1))(postcss@8.5.28)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.9.3))(postcss@8.5.28)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@19.1.1))(postcss@8.5.28)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.9.3)
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
const {chromium} = require('playwright');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
(async () => {
|
||||
const base = process.argv[2] || 'http://127.0.0.1:4173';
|
||||
const output = `.cache/inline-${process.argv[3] || 'local'}`;
|
||||
fs.mkdirSync(output, {recursive: true});
|
||||
const browser = await chromium.launch({headless: true, executablePath: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE, args: ['--no-sandbox']});
|
||||
const results = [];
|
||||
try {
|
||||
for (const width of [1440, 390, 320]) {
|
||||
const context = await browser.newContext({viewport: {width, height: 1000}, colorScheme: 'light'});
|
||||
const page = await context.newPage();
|
||||
await page.goto(`${base}/eldorado-v1/documentation/strategy/`, {waitUntil: 'networkidle'});
|
||||
const panel = page.locator('.archify-panel--story');
|
||||
const iframe = panel.locator(width > 700 ? '.story-desktop' : '.story-mobile');
|
||||
await iframe.scrollIntoViewIfNeeded();
|
||||
const frame = await (await iframe.elementHandle()).contentFrame();
|
||||
const theme = async value => {
|
||||
await page.waitForFunction(t => document.documentElement.dataset.theme === t, value);
|
||||
await frame.waitForFunction(t => document.documentElement.dataset.theme === t, value);
|
||||
};
|
||||
await theme('light');
|
||||
for (const id of ['btn-theme', 'btn-preset', 'btn-export']) {
|
||||
const box = await frame.locator(`#${id}`).boundingBox();
|
||||
assert(box && box.width >= 40 && box.height >= 44, id);
|
||||
}
|
||||
const toolbar = await frame.locator('.toolbar').boundingBox();
|
||||
const exportButton = await frame.locator('#btn-export').boundingBox();
|
||||
const diagram = await frame.locator('.diagram-container > svg').boundingBox();
|
||||
const rightGap = toolbar.x + toolbar.width - exportButton.x - exportButton.width;
|
||||
assert(Math.abs(rightGap - 8) < 1, `toolbar right padding at ${width}: ${rightGap}`);
|
||||
assert(diagram.y >= toolbar.y + toolbar.height, 'toolbar must stay above nodes');
|
||||
const checkMenu = async selector => {
|
||||
await frame.locator(selector).evaluate(n => Promise.all(n.getAnimations().map(a => a.finished)));
|
||||
const menu = await frame.locator(selector).boundingBox();
|
||||
const viewport = await iframe.boundingBox();
|
||||
const currentToolbar = await frame.locator('.toolbar').boundingBox();
|
||||
assert(menu && menu.x >= viewport.x - 1 && menu.y >= currentToolbar.y + currentToolbar.height - 1, selector);
|
||||
assert(menu.x + menu.width <= viewport.x + viewport.width + 1, `${selector} clips horizontally`);
|
||||
assert(menu.y + menu.height <= viewport.y + viewport.height + 1, `${selector} clips vertically`);
|
||||
};
|
||||
const light = await frame.locator('body').evaluate(n => getComputedStyle(n).backgroundColor);
|
||||
await frame.locator('#btn-theme').click(); await theme('dark');
|
||||
assert.notEqual(await frame.locator('body').evaluate(n => getComputedStyle(n).backgroundColor), light);
|
||||
await frame.locator('#btn-theme').click(); await theme('light');
|
||||
// Mobile portal theme control lives in the navigation drawer.
|
||||
const portalTheme = async target => {
|
||||
if (width <= 700) await page.getByRole('button', {name: 'Toggle navigation bar'}).click();
|
||||
for (let attempt = 0; attempt < 3; attempt++) {
|
||||
await page.getByRole('button', {name: /Switch between dark and light mode/}).filter({visible: true}).click();
|
||||
if (await page.locator('html').getAttribute('data-theme') === target) break;
|
||||
}
|
||||
if (width <= 700) await page.getByRole('button', {name: 'Close navigation bar'}).click();
|
||||
};
|
||||
await portalTheme('dark'); await theme('dark');
|
||||
await portalTheme('light'); await theme('light');
|
||||
for (const preset of ['signal-flow', 'blueprint', 'editorial', 'classic']) {
|
||||
await frame.locator('#btn-preset').click();
|
||||
await checkMenu('.preset-menu');
|
||||
await frame.locator(`[data-preset-value="${preset}"]`).click();
|
||||
assert.equal(await frame.locator('html').getAttribute('data-preset'), preset);
|
||||
}
|
||||
// A theme change must not reload the iframe or lose the selected visual preset.
|
||||
await frame.locator('#btn-preset').click();
|
||||
await frame.locator('[data-preset-value="blueprint"]').click();
|
||||
await portalTheme('dark'); await theme('dark');
|
||||
assert.equal(await frame.locator('html').getAttribute('data-preset'), 'blueprint');
|
||||
const exports = [];
|
||||
for (const format of ['png', 'jpeg', 'webp', 'svg', 'svg-light', 'svg-dark', 'webm']) {
|
||||
await frame.locator('#btn-export').click();
|
||||
await checkMenu('.export-menu');
|
||||
if (format === 'webm' && await frame.locator('[data-format="webm"]').isDisabled()) {
|
||||
exports.push({format, unsupported: true});
|
||||
await frame.locator('#btn-export').click();
|
||||
continue;
|
||||
}
|
||||
console.log(`Export ${width} ${format}`);
|
||||
const [download] = await Promise.all([
|
||||
page.waitForEvent('download', {timeout: 60000}),
|
||||
frame.locator(`[data-format="${format}"]`).click(),
|
||||
]);
|
||||
assert.equal(await download.failure(), null);
|
||||
const path = `${output}/${width}-${format}-${download.suggestedFilename()}`;
|
||||
await download.saveAs(path);
|
||||
const bytes = fs.readFileSync(path);
|
||||
assert(bytes.length > 1000);
|
||||
if (format.startsWith('svg')) {
|
||||
const valid = await page.evaluate(xml => {
|
||||
const doc = new DOMParser().parseFromString(xml, 'image/svg+xml');
|
||||
return !doc.querySelector('parsererror') && doc.documentElement.localName === 'svg' && doc.querySelectorAll('text').length > 0 && doc.querySelector('#eldorado-cycle-export')?.textContent.includes('stroke-dasharray: 3 9');
|
||||
}, bytes.toString());
|
||||
assert(valid, format);
|
||||
} else {
|
||||
const mime = {png:'image/png', jpeg:'image/jpeg', webp:'image/webp', webm:'video/webm'}[format];
|
||||
const dimensions = await page.evaluate(async ({data, mime}) => {
|
||||
const media = document.createElement(mime.startsWith('video') ? 'video' : 'img');
|
||||
const loaded = new Promise((resolve, reject) => {media[mime.startsWith('video') ? 'onloadedmetadata' : 'onload'] = resolve; media.onerror = reject;});
|
||||
media.src = `data:${mime};base64,${data}`; await loaded;
|
||||
return [media.videoWidth || media.naturalWidth, media.videoHeight || media.naturalHeight];
|
||||
}, {data: bytes.toString('base64'), mime});
|
||||
assert(dimensions.every(n => n > 0), format);
|
||||
}
|
||||
exports.push({format, path, bytes:bytes.length});
|
||||
}
|
||||
const opener = panel.getByRole('button', {name:/^Expand /});
|
||||
await opener.click();
|
||||
let full = await (await panel.locator('dialog iframe').elementHandle()).contentFrame();
|
||||
await full.waitForFunction(() => document.documentElement.dataset.theme === 'dark');
|
||||
await full.locator('#btn-theme').click(); await theme('light');
|
||||
const close = panel.getByRole('button', {name:'Close full-screen diagram'});
|
||||
assert.equal(await close.innerText(), '×');
|
||||
await close.click(); assert(await opener.evaluate(n => n === document.activeElement));
|
||||
await opener.click();
|
||||
full = await (await panel.locator('dialog iframe').elementHandle()).contentFrame();
|
||||
await full.waitForFunction(() => document.documentElement.dataset.theme === 'light');
|
||||
await close.focus(); await page.keyboard.press('Escape');
|
||||
assert(await opener.evaluate(n => n === document.activeElement));
|
||||
await iframe.scrollIntoViewIfNeeded();
|
||||
await page.evaluate(() => scrollTo({top: 0, behavior: 'instant'}));
|
||||
await page.evaluate(() => new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve))));
|
||||
await page.screenshot({path:`${output}/${width}.png`, fullPage:true});
|
||||
await frame.locator('#btn-theme').click(); await theme('dark');
|
||||
// Exercise keyboard navigation without racing the portal's smooth scroll.
|
||||
await page.locator('.strategy-content a[href$="/strategy/target-operating-model/"]').filter({visible:true}).focus();
|
||||
await page.keyboard.press('Enter');
|
||||
await page.waitForURL('**/strategy/target-operating-model/');
|
||||
await page.getByRole('heading', {name:'Target operating model', exact:true, level:1}).waitFor();
|
||||
const next = page.locator(`.archify-panel--story[data-diagram="strategy-target"] > .story-${width > 700 ? 'desktop' : 'mobile'}`);
|
||||
await next.scrollIntoViewIfNeeded();
|
||||
await (await (await next.elementHandle()).contentFrame()).waitForFunction(() => document.documentElement.dataset.theme === 'dark');
|
||||
assert(!await page.evaluate(() => document.documentElement.scrollWidth > innerWidth));
|
||||
results.push({width, toolbar, rightGap, dropdownsUnclipped:true, themeSync:true, presets:4, exports, fullscreen:true, routeTheme:true});
|
||||
fs.writeFileSync(`${output}/results.json`, JSON.stringify(results,null,2));
|
||||
await context.close();
|
||||
}
|
||||
console.log(`PASS ${results.length} viewports: bidirectional themes, 4 presets, all supported exports decoded, fullscreen and route persistence`);
|
||||
} finally {await browser.close();}
|
||||
})().catch(error => {console.error(error); process.exitCode = 1;});
|
||||
@@ -0,0 +1,74 @@
|
||||
const {chromium} = require('playwright');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
(async () => {
|
||||
const base = process.argv[2] || 'http://127.0.0.1:4173';
|
||||
const output = `.cache/cycle-${process.argv[3] || 'local'}`;
|
||||
fs.mkdirSync(output, {recursive:true});
|
||||
const browser = await chromium.launch({headless:true, executablePath:process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE, args:['--no-sandbox']});
|
||||
const results = [];
|
||||
try {
|
||||
for (const width of [1440, 390, 320]) {
|
||||
const context = await browser.newContext({viewport:{width,height:1000}, colorScheme:'light'});
|
||||
const page = await context.newPage();
|
||||
await page.goto(`${base}/eldorado-v1/documentation/`, {waitUntil:'networkidle'});
|
||||
if (width < 700) await page.getByRole('button', {name:'Toggle navigation bar'}).click();
|
||||
const nav = page.getByRole('link', {name:'Strategy', exact:true}).filter({visible:true});
|
||||
assert((await nav.getAttribute('href')).endsWith('/strategy/target-operating-model/'));
|
||||
await nav.click();
|
||||
await page.waitForURL('**/strategy/target-operating-model/');
|
||||
for (const route of ['target-operating-model/', '']) {
|
||||
await page.goto(`${base}/eldorado-v1/documentation/strategy/${route}`, {waitUntil:'networkidle'});
|
||||
if (width === 1440) assert.equal(await page.locator('.theme-doc-sidebar-menu .menu__link').first().innerText(), 'Target');
|
||||
const iframe = page.locator(`.archify-panel--story > .story-${width > 700 ? 'desktop' : 'mobile'}`);
|
||||
await iframe.scrollIntoViewIfNeeded();
|
||||
const frame = await (await iframe.elementHandle()).contentFrame();
|
||||
await frame.locator('svg[role="img"]').waitFor();
|
||||
const metrics = await frame.evaluate(() => {
|
||||
const svg = document.querySelector('svg[role="img"]');
|
||||
const labels = [...svg.querySelectorAll('text[data-node-label]')].map(n => ({text:n.textContent,px:parseFloat(getComputedStyle(n).fontSize)*n.getScreenCTM().a}));
|
||||
const edges = [...svg.querySelectorAll('path[data-edge-id^="stage-"]')].map(n => [n.dataset.edgeFrom,n.dataset.edgeTo,n.getAttribute('marker-end')]);
|
||||
const overflow = [...svg.querySelectorAll('[data-node-id]')].flatMap(n => {
|
||||
const box = n.querySelector('rect').getBoundingClientRect();
|
||||
return [...n.querySelectorAll('text')].filter(t => {const b=t.getBoundingClientRect();return b.left<box.left||b.right>box.right||b.top<box.top||b.bottom>box.bottom;}).map(t=>t.textContent);
|
||||
});
|
||||
const relationships = [...svg.querySelectorAll('path[data-edge-id]')].map(n => ({id:n.dataset.edgeId,from:n.dataset.edgeFrom,to:n.dataset.edgeTo,dash:getComputedStyle(n).strokeDasharray,arrow:n.getAttribute('marker-end')}));
|
||||
const nodes = [...svg.querySelectorAll('[data-node-id]')].map(n => {const r=n.querySelector('rect').getBoundingClientRect();return {id:n.dataset.nodeId,x:r.x+r.width/2,y:r.y+r.height/2};});
|
||||
return {labels,edges,overflow,relationships,nodes};
|
||||
});
|
||||
const ids = ['scope','architecture','ui-ux','planning','delivery','uat','security','releases'];
|
||||
assert.deepEqual(metrics.edges.map(e=>e.slice(0,2)), ids.map((id,i)=>[id,ids[(i+1)%ids.length]]));
|
||||
assert(metrics.edges.every(e=>e[2]?.startsWith('url(')), 'all stages must have arrowheads');
|
||||
assert.equal(metrics.nodes.length, 18);
|
||||
const governs = metrics.relationships.filter(e=>e.id.startsWith('govern-'));
|
||||
const contributions = metrics.relationships.filter(e=>e.id.startsWith('contribute-'));
|
||||
assert.deepEqual(governs.map(e=>[e.from,e.to]), ids.map(id=>['agent',id]));
|
||||
assert.deepEqual(contributions.map(e=>[e.from,e.to]), ids.map(id=>[`code-${id}`,id]));
|
||||
assert(governs.every(e=>e.dash==='3px, 9px' && e.arrow?.startsWith('url(')));
|
||||
assert(contributions.every(e=>e.dash==='12px, 8px' && e.arrow?.startsWith('url(')));
|
||||
assert(metrics.relationships.some(e=>e.from==='humans' && e.to==='agent'));
|
||||
if (width > 700) {
|
||||
const agent = metrics.nodes.find(n=>n.id==='agent');
|
||||
const phases = metrics.nodes.filter(n=>ids.includes(n.id));
|
||||
for (const axis of ['x','y']) assert(Math.abs(agent[axis]-(Math.min(...phases.map(n=>n[axis]))+Math.max(...phases.map(n=>n[axis])))/2)<1);
|
||||
for (const id of ids) {
|
||||
const phase=phases.find(n=>n.id===id), code=metrics.nodes.find(n=>n.id===`code-${id}`);
|
||||
assert(Math.hypot(code.x-agent.x,code.y-agent.y)>Math.hypot(phase.x-agent.x,phase.y-agent.y));
|
||||
}
|
||||
}
|
||||
assert(metrics.labels.every(n=>n.px>=14), JSON.stringify(metrics.labels));
|
||||
assert.deepEqual(metrics.overflow, []);
|
||||
assert(!await page.evaluate(()=>document.documentElement.scrollWidth>innerWidth));
|
||||
await page.evaluate(()=>scrollTo({top:0,behavior:'instant'}));
|
||||
await page.waitForFunction(()=>scrollY===0);
|
||||
await page.evaluate(()=>new Promise(resolve=>requestAnimationFrame(()=>requestAnimationFrame(resolve))));
|
||||
await page.screenshot({path:`${output}/${width}-${route ? 'target' : 'landing'}.png`,fullPage:true});
|
||||
results.push({width,route,...metrics});
|
||||
fs.writeFileSync(`${output}/results.json`,JSON.stringify(results,null,2));
|
||||
}
|
||||
await context.close();
|
||||
}
|
||||
assert.equal(results.length,6);
|
||||
console.log('PASS 6 cycle routes/viewports: navigation, sidebar order, exact directed loop, arrowheads, >=14px labels, text bounds and no overflow');
|
||||
} finally {await browser.close();}
|
||||
})().catch(error=>{console.error(error);process.exitCode=1;});
|
||||
@@ -19,18 +19,21 @@ const fs=require('node:fs');
|
||||
const nodes=[...svg.querySelectorAll('[data-node-id]')].map(n=>{const r=n.getBoundingClientRect();return {id:n.id,x:r.x,y:r.y,right:r.right,bottom:r.bottom};});
|
||||
return {width:innerWidth,height:innerHeight,nodes,svg:svg.getBoundingClientRect().toJSON(),minLabel:Math.min(...[...svg.querySelectorAll('text[data-node-label]')].map(n=>parseFloat(getComputedStyle(n).fontSize)*n.getScreenCTM().a))};
|
||||
});
|
||||
assert(geometry.nodes.length>=5 && geometry.nodes.length<=7,id);
|
||||
assert(geometry.nodes.length>=5 && geometry.nodes.length<=(['index','target-operating-model'].includes(id)?18:7),id);
|
||||
for(const n of geometry.nodes) assert(n.x>=0 && n.y>=0 && n.right<=geometry.width+1 && n.bottom<=geometry.height+1,`${id} ${width} clipped ${JSON.stringify(n)} / ${geometry.height}`);
|
||||
const data=await page.locator('.strategy-content').evaluate(el=>({words:el.innerText.trim().split(/\s+/).length,overflow:document.documentElement.scrollWidth>innerWidth,openDetails:el.querySelectorAll('details[open]').length}));
|
||||
assert(data.words<=200,id);assert(!data.overflow,id);assert.equal(data.openDetails,0,id);
|
||||
await page.evaluate(()=>scrollTo(0,0));await page.screenshot({path:`.cache/story-${label}/${width}-${id}.png`,fullPage:true});
|
||||
await page.evaluate(()=>scrollTo({top:0,behavior:'instant'}));await page.screenshot({path:`.cache/story-${label}/${width}-${id}.png`,fullPage:true});
|
||||
const opener=panel.getByRole('button',{name:/^Expand /});await opener.click();
|
||||
const close=panel.getByRole('button',{name:'Close full-screen diagram',exact:true});await close.waitFor({state:'visible'});
|
||||
assert.equal(await close.innerText(),'×');assert(await close.evaluate(n=>n===document.activeElement));
|
||||
const box=await close.boundingBox();assert.equal(box.width,44);assert.equal(box.height,44);
|
||||
await close.click();assert(await opener.evaluate(n=>n===document.activeElement));
|
||||
await opener.click();await close.waitFor({state:'visible'});await page.keyboard.press('Escape');await page.waitForFunction(()=>!document.querySelector('.archify-panel--story dialog').open);assert(await opener.evaluate(n=>n===document.activeElement));
|
||||
const summary=page.locator('.strategy-content > details > summary').first();await summary.click();assert(await summary.evaluate(n=>n.parentElement.open));await summary.click();
|
||||
const summary=page.locator('.strategy-content > details > summary').first();
|
||||
await summary.focus();await page.keyboard.press('Enter');
|
||||
await page.waitForFunction(()=>document.querySelector('.strategy-content > details').open);
|
||||
await page.keyboard.press('Enter');
|
||||
results.push({id,width,...data,geometry,modalClick:true,modalEscape:true,focusRestored:true,details:true});fs.writeFileSync(`.cache/story-${label}/results.json`,JSON.stringify(results,null,2));
|
||||
}
|
||||
await page.goto(`${base}/eldorado-v1/documentation/strategy/measurement/#validated-task-success`,{waitUntil:'networkidle'});
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import {readdirSync, readFileSync, mkdirSync, copyFileSync, renameSync, rmSync} from 'node:fs';
|
||||
import {readdirSync, readFileSync, writeFileSync, mkdirSync, copyFileSync, renameSync, rmSync} from 'node:fs';
|
||||
import {join} from 'node:path';
|
||||
import {execFileSync} from 'node:child_process';
|
||||
const cli = 'vendor/archify/bin/archify.mjs';
|
||||
const types = new Set(['architecture','workflow','sequence','dataflow','lifecycle']);
|
||||
const inlineStyles = readFileSync(new URL('./inline-viewer.css', import.meta.url), 'utf8');
|
||||
const cycleStyles = readFileSync(new URL('./cycle-viewer.css', import.meta.url), 'utf8');
|
||||
const themeBridge = readFileSync(new URL('./viewer-theme-bridge.js', import.meta.url), 'utf8');
|
||||
// This directory is generated-only and ignored by Git. Remove stale artifacts.
|
||||
rmSync('static/diagrams', {recursive: true, force: true});
|
||||
mkdirSync('static/diagrams', {recursive: true});
|
||||
@@ -23,6 +26,28 @@ for (const file of readdirSync('diagrams').sort()) {
|
||||
run(['validate',type,input,'--quality','showcase','--json']);
|
||||
run(['deliver',type,input,output,'--quality','showcase','--json']);
|
||||
run(['check',output,'--json','--require-provenance']);
|
||||
// Verify the upstream artifact first, then add the scoped portal integration.
|
||||
let viewer = readFileSync(output, 'utf8');
|
||||
// Upstream intentionally disables preset selection in gallery embeds. Enable
|
||||
// only those two guards; fail closed if a future vendor update changes them.
|
||||
const start = viewer.indexOf('Archify.preset = (function () {');
|
||||
const end = viewer.indexOf('Archify.theme = (function () {', start);
|
||||
const preset = viewer.slice(start, end);
|
||||
const guard = "if (html.getAttribute('data-embed') === 'true') return false;";
|
||||
if (start < 0 || end < start || preset.split(guard).length !== 3) throw new Error('Archify preset integration drift');
|
||||
viewer = viewer.slice(0, start) + preset.replaceAll(guard, '') + viewer.slice(end);
|
||||
if (!viewer.includes('</head>') || !viewer.includes('</body>')) throw new Error(`Invalid viewer: ${output}`);
|
||||
const storyStyles = ['strategy-target', 'strategy-target-mobile'].includes(name)
|
||||
? `${name === 'strategy-target' ? ':root {--cycle-label: 27px; --cycle-detail: 21px;}' : ':root {--cycle-label: 38px; --cycle-detail: 28px;}'}${cycleStyles}` : '';
|
||||
if (storyStyles) {
|
||||
// Keep typography and relationship styles inside exported standalone SVGs.
|
||||
const diagramRoot = /<svg\b[^>]*role="img"[^>]*>/g;
|
||||
if ([...viewer.matchAll(diagramRoot)].length !== 1) throw new Error('Cycle SVG integration drift');
|
||||
const exportStyles = storyStyles.replaceAll(':root', 'svg[role="img"]').replaceAll('.diagram-container > svg', 'svg[role="img"]');
|
||||
viewer = viewer.replace(diagramRoot, root => `${root}<style id="eldorado-cycle-export">${exportStyles}</style>`);
|
||||
}
|
||||
writeFileSync(output, viewer.replace('</head>', `<style id="eldorado-inline-controls">${inlineStyles}</style><style id="eldorado-cycle-labels">${storyStyles}</style></head>`)
|
||||
.replace('</body>', `<script>${themeBridge}</script></body>`));
|
||||
renameSync(`static/diagrams/${name}/index.delivery.json`, `.cache/archify/${name}.delivery.json`);
|
||||
}
|
||||
copyFileSync('vendor/archify/LICENSE','static/diagrams/ARCHIFY-LICENSE.txt');
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/* Readable lifecycle story at inline scale, isolated from other diagrams. */
|
||||
.diagram-container > svg text {
|
||||
font-family: Arial, sans-serif;
|
||||
letter-spacing: 0;
|
||||
font-size: 30px;
|
||||
}
|
||||
.diagram-container > svg text[data-node-label] {
|
||||
font-size: var(--cycle-label);
|
||||
transform: translateY(-6px);
|
||||
}
|
||||
.diagram-container > svg text[data-detail="context"] {
|
||||
font-size: var(--cycle-detail);
|
||||
transform: translateY(16px);
|
||||
}
|
||||
/* Edge semantics stay in SVG metadata; one page legend avoids repeated labels. */
|
||||
.diagram-container > svg g[data-edge-id] {display: none;}
|
||||
.diagram-container > svg path[data-edge-id^="stage-"] {stroke-width: 2.5;}
|
||||
.diagram-container > svg path[data-edge-id^="govern-"] {stroke-dasharray: 3 9; stroke-width: 3;}
|
||||
.diagram-container > svg path[data-edge-id^="contribute-"] {stroke-dasharray: 12 8; stroke-width: 3;}
|
||||
@@ -0,0 +1,33 @@
|
||||
/* Portal integration only; keep the upstream renderer and export code pinned. */
|
||||
html[data-embed="true"] .toolbar {
|
||||
display: flex !important;
|
||||
position: relative;
|
||||
inset: auto;
|
||||
justify-content: flex-end;
|
||||
height: 52px;
|
||||
padding: 4px 8px;
|
||||
box-sizing: border-box;
|
||||
background: var(--bg);
|
||||
}
|
||||
html[data-embed="true"] #btn-present,
|
||||
html[data-embed="true"] #btn-motion { display: none !important; }
|
||||
html[data-embed="true"] .diagram-container > svg {
|
||||
max-height: calc(100vh - 68px);
|
||||
}
|
||||
html[data-embed="true"] .toolbar .preset-menu,
|
||||
html[data-embed="true"] .toolbar .export-menu {
|
||||
position: fixed;
|
||||
top: 52px;
|
||||
left: auto;
|
||||
right: 0;
|
||||
width: min(320px, 100vw);
|
||||
max-width: 100vw;
|
||||
max-height: calc(100vh - 52px);
|
||||
overflow-y: auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
/* Keep the preset menu in the iframe viewport without moving its capsule. */
|
||||
html[data-embed="true"] .toolbar .export-wrap { position: static; }
|
||||
html[data-embed="true"] .toolbar .export-menu { position: absolute; top: 100%; }
|
||||
html[data-embed="true"] .toolbar .preset-menu button,
|
||||
html[data-embed="true"] .toolbar .export-menu button { min-height: 44px; }
|
||||
@@ -0,0 +1,51 @@
|
||||
import {test} from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {readFileSync} from 'node:fs';
|
||||
import {runInNewContext} from 'node:vm';
|
||||
const read = path => readFileSync(path, 'utf8');
|
||||
|
||||
test('both story layouts permit downloads without weakening origin isolation', () => {
|
||||
const wrapper = read('src/components/ArchifyDiagram/index.tsx');
|
||||
for (const layout of ['desktop', 'mobile']) {
|
||||
const iframe = wrapper.split(`<iframe className="story-${layout}"`)[1].split(' />')[0];
|
||||
assert(iframe.includes('sandbox="allow-scripts allow-downloads"'));
|
||||
assert(iframe.includes('?embed=1&theme=${initialTheme.current}'));
|
||||
}
|
||||
assert(!wrapper.includes('allow-same-origin'));
|
||||
});
|
||||
|
||||
test('build adds maintained embed-only controls without changing pinned vendor', () => {
|
||||
const build = read('scripts/build-diagrams.mjs');
|
||||
assert(build.includes("new URL('./inline-viewer.css', import.meta.url)"));
|
||||
assert(build.includes('eldorado-inline-controls'));
|
||||
const css = read('scripts/inline-viewer.css');
|
||||
assert(css.includes('html[data-embed="true"] .toolbar'));
|
||||
assert(css.includes('display: flex !important'));
|
||||
assert(css.includes('position: relative'));
|
||||
assert(css.includes('max-height: calc(100vh - 52px)'));
|
||||
});
|
||||
|
||||
test('theme bridge rejects unrelated senders/values and never echoes parent updates', () => {
|
||||
const root = {dataset:{theme:'light'}};
|
||||
let receive, observerCallback, mutations = 0;
|
||||
const messages = [];
|
||||
const parent = {postMessage: message => messages.push(message)};
|
||||
const window = {parent, addEventListener: (type, callback) => {receive = callback;}};
|
||||
const button = {click: () => {root.dataset.theme = root.dataset.theme === 'light' ? 'dark' : 'light'; mutations++;}};
|
||||
class MutationObserver {
|
||||
constructor(callback) {observerCallback = callback;}
|
||||
observe() {}
|
||||
takeRecords() {mutations = 0;}
|
||||
}
|
||||
runInNewContext(read('scripts/viewer-theme-bridge.js'), {window, document:{documentElement:root, getElementById: () => button}, MutationObserver});
|
||||
const send = (source, theme) => receive({source, data:{type:'eldorado:set-theme', theme}});
|
||||
send({}, 'dark'); send(parent, 'invalid');
|
||||
assert.equal(root.dataset.theme, 'light');
|
||||
send(parent, 'dark');
|
||||
assert.equal(root.dataset.theme, 'dark');
|
||||
assert.equal(mutations, 0);
|
||||
assert.equal(messages.length, 0);
|
||||
button.click(); observerCallback();
|
||||
assert.equal(messages.length, 1);
|
||||
assert.equal(messages[0].theme, 'light');
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
import {test} from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {readFileSync} from 'node:fs';
|
||||
const read = p => readFileSync(p, 'utf8');
|
||||
const stages = ['Scope', 'Architecture', 'UI/UX', 'Planning', 'Delivery', 'UAT', 'Security', 'Releases'];
|
||||
|
||||
test('Strategy opens at the autonomous lifecycle, with a matching direct landing story', () => {
|
||||
assert.match(read('sidebars/strategy.ts'), /strategySidebar: \['target-operating-model', 'index'/);
|
||||
assert.match(read('docs/strategy/target-operating-model.mdx'), /^sidebar_label: Target$/m);
|
||||
for (const id of ['index', 'target-operating-model']) {
|
||||
const page = read(`docs/strategy/${id}.mdx`);
|
||||
const primary = page.split('<details>')[0];
|
||||
assert(primary.includes('name="strategy-target"'));
|
||||
assert(primary.includes('autonomous agent'));
|
||||
assert(primary.includes('direct, guide and review'));
|
||||
assert(primary.includes('Scope → Architecture → UI/UX → Planning → Delivery → UAT → Security → Releases → Scope'));
|
||||
assert(primary.includes('human-controlled code agent'));
|
||||
assert(primary.includes('TO-BE'));
|
||||
assert(!/only routes|People authorize|independent gates protect release/.test(primary));
|
||||
}
|
||||
});
|
||||
|
||||
test('both target diagrams have eight stages, eight governed phases and eight human-controlled contributors', () => {
|
||||
for (const suffix of ['', '-mobile']) {
|
||||
const d = JSON.parse(read(`diagrams/strategy-target${suffix}.architecture.json`));
|
||||
const ids = stages.map(label => d.components.find(n => n.label === label)?.id);
|
||||
assert(ids.every(Boolean));
|
||||
assert.equal(new Set(ids).size, 8);
|
||||
const flow = d.connections.filter(e => ids.includes(e.from) && ids.includes(e.to));
|
||||
assert.deepEqual(flow.map(e => [e.from, e.to]), ids.map((id, i) => [id, ids[(i + 1) % ids.length]]));
|
||||
assert.match(flow.at(-1).label, /Repeat/);
|
||||
|
||||
assert(d.components.some(n => n.label === 'Autonomous agent' && /Executes.*governs/.test(n.sublabel)));
|
||||
assert(d.components.some(n => n.label === 'Humans' && n.sublabel === 'Direct, guide and review'));
|
||||
assert(d.connections.some(e => e.from === 'humans' && e.to === 'agent'));
|
||||
assert.deepEqual(d.connections.filter(e => e.from === 'agent').map(e => e.to), ids);
|
||||
const contributors = d.components.filter(n => n.id.startsWith('code-'));
|
||||
assert.equal(contributors.length, 8);
|
||||
assert(contributors.every(n => n.label === 'Code agent' && n.sublabel === 'Human-controlled'));
|
||||
for (const id of ids) {
|
||||
assert(d.connections.some(e => e.from === `code-${id}` && e.to === id && e.variant === 'dashed'));
|
||||
assert(d.connections.some(e => e.from === 'agent' && e.to === id && e.variant === 'security'));
|
||||
}
|
||||
assert.equal(d.components.length, 18);
|
||||
if (!suffix) {
|
||||
const center = n => n.pos.map((v, i) => v + n.size[i] / 2);
|
||||
const agent = center(d.components.find(n => n.id === 'agent'));
|
||||
const phaseCenters = ids.map(id => center(d.components.find(n => n.id === id)));
|
||||
for (const axis of [0, 1]) {
|
||||
const values = phaseCenters.map(c => c[axis]);
|
||||
assert.equal(agent[axis], (Math.min(...values) + Math.max(...values)) / 2);
|
||||
}
|
||||
for (const code of contributors) {
|
||||
const phase = d.components.find(n => n.id === code.id.slice(5));
|
||||
const distance = n => Math.hypot(...center(n).map((v, i) => v - agent[i]));
|
||||
assert(distance(code) > distance(phase), `${code.id} is exterior`);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -28,10 +28,10 @@ test('mobile story diagrams preserve the same nodes and directed relationships',
|
||||
const name = /<ArchifyDiagram name="([^"]+)"[^>]+ story/.exec(page)[1];
|
||||
const desktop = JSON.parse(read(`diagrams/${name}.architecture.json`));
|
||||
const mobile = JSON.parse(read(`diagrams/${name}-mobile.architecture.json`));
|
||||
assert(desktop.components.length >= 5 && desktop.components.length <= 7, name);
|
||||
const semantic = d => ({nodes:d.components.map(({pos,size,...n})=>n),edges:d.connections.map(({fromSide,toSide,...e})=>e)});
|
||||
assert(desktop.components.length >= 5 && desktop.components.length <= (name === 'strategy-target' ? 18 : 7), name);
|
||||
const semantic = d => ({nodes:d.components.map(({pos,size,...n})=>n),edges:d.connections.map(({fromSide,toSide,via,labelAt,...e})=>e)});
|
||||
assert.deepEqual(semantic(desktop), semantic(mobile), name);
|
||||
assert(desktop.connections.every(e => e.label && e.from !== e.to), name);
|
||||
assert(desktop.connections.every(e => (e.label || e.id.startsWith('govern-')) && e.from !== e.to), name);
|
||||
assert.equal(desktop.meta.legend.mode, 'hidden');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// Sandboxed viewers have opaque origins: authenticate the sender by WindowProxy,
|
||||
// not origin. Only exchange a validated theme, never HTML, URLs or commands.
|
||||
(() => {
|
||||
if (window.parent === window) return;
|
||||
let connected = false;
|
||||
window.addEventListener('message', event => {
|
||||
if (event.source !== window.parent || event.data?.type !== 'eldorado:set-theme') return;
|
||||
const theme = event.data.theme;
|
||||
if (theme !== 'light' && theme !== 'dark') return;
|
||||
connected = true;
|
||||
if (document.documentElement.dataset.theme !== theme) {
|
||||
document.getElementById('btn-theme').click();
|
||||
// Parent updates are not new user choices; do not echo stale acknowledgements.
|
||||
observer.takeRecords();
|
||||
}
|
||||
});
|
||||
const observer = new MutationObserver(() => {
|
||||
if (connected) window.parent.postMessage({type: 'eldorado:theme', theme: document.documentElement.dataset.theme}, '*');
|
||||
});
|
||||
observer.observe(document.documentElement, {attributes: true, attributeFilter: ['data-theme']});
|
||||
})();
|
||||
@@ -1,2 +1,2 @@
|
||||
import type {SidebarsConfig} from '@docusaurus/plugin-content-docs';
|
||||
export default {strategySidebar: ['index', 'target-operating-model', 'skills-governance', 'skills-quality', 'skills-adoption', 'measurement', 'skills-maintenance', 'roadmap']} satisfies SidebarsConfig;
|
||||
export default {strategySidebar: ['target-operating-model', 'index', 'skills-governance', 'skills-quality', 'skills-adoption', 'measurement', 'skills-maintenance', 'roadmap']} satisfies SidebarsConfig;
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, {useEffect, useId, useRef, useState} from 'react';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
import {useLocation} from '@docusaurus/router';
|
||||
import {useColorMode} from '@docusaurus/theme-common';
|
||||
|
||||
export default function ArchifyDiagram({name, title, compact = false, story = false}: {name: string; title: string; compact?: boolean; story?: boolean}): React.ReactElement {
|
||||
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name)) throw new Error('Invalid diagram name');
|
||||
@@ -10,6 +11,22 @@ export default function ArchifyDiagram({name, title, compact = false, story = fa
|
||||
const opener = useRef<HTMLButtonElement>(null);
|
||||
const headingId = useId();
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const {colorMode, setColorMode} = useColorMode();
|
||||
const initialTheme = useRef(colorMode);
|
||||
const desktop = useRef<HTMLIFrameElement>(null);
|
||||
const mobile = useRef<HTMLIFrameElement>(null);
|
||||
const fullscreen = useRef<HTMLIFrameElement>(null);
|
||||
const syncTheme = (frame: HTMLIFrameElement | null) => frame?.contentWindow?.postMessage({type: 'eldorado:set-theme', theme: colorMode}, '*');
|
||||
useEffect(() => {
|
||||
for (const frame of [desktop, mobile, fullscreen]) syncTheme(frame.current);
|
||||
const receiveTheme = (event: MessageEvent) => {
|
||||
if (!event.source || ![desktop, mobile, fullscreen].some(frame => frame.current?.contentWindow === event.source)) return;
|
||||
if (event.data?.type !== 'eldorado:theme' || !['light', 'dark'].includes(event.data.theme)) return;
|
||||
if (event.data.theme !== colorMode) setColorMode(event.data.theme);
|
||||
};
|
||||
window.addEventListener('message', receiveTheme);
|
||||
return () => window.removeEventListener('message', receiveTheme);
|
||||
}, [colorMode, setColorMode, expanded]);
|
||||
const {pathname, hash} = useLocation();
|
||||
useEffect(() => {
|
||||
if (!story || !hash) return;
|
||||
@@ -43,12 +60,12 @@ export default function ArchifyDiagram({name, title, compact = false, story = fa
|
||||
return <section className={`archify-panel${compact ? ' archify-panel--compact' : ''}${story ? ' archify-panel--story' : ''}`} data-diagram={name} aria-label={title}>
|
||||
<div className="archify-toolbar"><button ref={opener} type="button" className="archify-expand" aria-label={`Expand ${title}`} title="Expand diagram" onClick={() => setExpanded(true)}><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true"><path d="M8 3H3v5M16 3h5v5M21 16v5h-5M8 21H3v-5" /></svg></button></div>
|
||||
{story ? <>
|
||||
<iframe className="story-desktop" src={`${source}?embed=1&theme=light`} title={title} sandbox="allow-scripts" loading="lazy" referrerPolicy="no-referrer" />
|
||||
<iframe className="story-mobile" src={`${mobileSource}?embed=1&theme=light`} title={`${title} — narrow layout`} sandbox="allow-scripts" loading="lazy" referrerPolicy="no-referrer" />
|
||||
</> : <iframe src={source} title={title} sandbox="allow-scripts allow-downloads" loading="lazy" referrerPolicy="no-referrer" />}
|
||||
<iframe className="story-desktop" ref={desktop} onLoad={() => syncTheme(desktop.current)} src={`${source}?embed=1&theme=${initialTheme.current}`} title={title} sandbox="allow-scripts allow-downloads" loading="lazy" referrerPolicy="no-referrer" />
|
||||
<iframe className="story-mobile" ref={mobile} onLoad={() => syncTheme(mobile.current)} src={`${mobileSource}?embed=1&theme=${initialTheme.current}`} title={`${title} — narrow layout`} sandbox="allow-scripts allow-downloads" loading="lazy" referrerPolicy="no-referrer" />
|
||||
</> : <iframe ref={desktop} onLoad={() => syncTheme(desktop.current)} src={`${source}?theme=${initialTheme.current}`} title={title} sandbox="allow-scripts allow-downloads" loading="lazy" referrerPolicy="no-referrer" />}
|
||||
<dialog ref={dialog} className="archify-modal" aria-labelledby={headingId} onClose={() => {setExpanded(false); opener.current?.focus();}}>
|
||||
<div className="archify-modal-toolbar"><strong id={headingId}>{title}</strong><button type="button" onClick={close} autoFocus aria-label="Close full-screen diagram"><span aria-hidden="true">×</span></button></div>
|
||||
{expanded && <iframe src={source} title={`${title} — expanded`} sandbox="allow-scripts allow-downloads" referrerPolicy="no-referrer" />}
|
||||
{expanded && <iframe ref={fullscreen} onLoad={() => syncTheme(fullscreen.current)} src={`${source}?theme=${initialTheme.current}`} title={`${title} — expanded`} sandbox="allow-scripts allow-downloads" referrerPolicy="no-referrer" />}
|
||||
</dialog>
|
||||
</section>;
|
||||
}
|
||||
+2
-2
@@ -110,7 +110,7 @@ a:focus-visible, button:focus-visible {outline: 3px solid #b88719; outline-offse
|
||||
.archify-panel--story {max-width: 740px;}
|
||||
.archify-panel--story > iframe {height: auto; aspect-ratio: 480 / 408;}
|
||||
.archify-panel--story > .story-mobile {display: none;}
|
||||
.archify-panel--story[data-diagram="strategy-target"] > iframe {aspect-ratio: 480 / 628;}
|
||||
.archify-panel--story[data-diagram="strategy-target"] > iframe {aspect-ratio: 1620 / 1430;}
|
||||
.archify-panel--story[data-diagram="strategy-adoption"] > iframe {aspect-ratio: 545 / 408;}
|
||||
.archify-panel--story[data-diagram="strategy-maintenance"] > iframe {aspect-ratio: 524 / 408;}
|
||||
.strategy-content > details {margin: .65rem 0; border: 1px solid var(--line); padding: .8rem 1rem; background: var(--surface);}
|
||||
@@ -124,7 +124,7 @@ a:focus-visible, button:focus-visible {outline: 3px solid #b88719; outline-offse
|
||||
.archify-panel--story[data-diagram="strategy-roadmap"] > .story-mobile {aspect-ratio: 240 / 628;}
|
||||
.archify-panel--story[data-diagram="strategy-adoption"] > .story-mobile {aspect-ratio: 305 / 748;}
|
||||
.archify-panel--story[data-diagram="strategy-maintenance"] > .story-mobile {aspect-ratio: 284 / 748;}
|
||||
.archify-panel--story[data-diagram="strategy-target"] > .story-mobile {aspect-ratio: 301 / 868;}
|
||||
.archify-panel--story[data-diagram="strategy-target"] > .story-mobile {aspect-ratio: 680 / 2340;}
|
||||
}
|
||||
.archify-expand {display: grid; place-items: center; width: 44px; height: 44px; border: 0; padding: 0; background: transparent; color: var(--ifm-link-color); cursor: pointer;}
|
||||
.archify-expand:hover {background: var(--ifm-hover-overlay);}
|
||||
|
||||
Reference in new issue
Block a user