Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e10b03dff4 | ||
|
|
733d6f2ece |
No files matched your search
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"schema_version": 2,
|
||||
"diagram_type": "workflow",
|
||||
"meta": {
|
||||
"title": "Stage 1: Delivery first",
|
||||
"output": "static/diagrams/strategy-roadmap-adoption-mobile/index.html",
|
||||
"quality_profile": "showcase",
|
||||
"animation": "none",
|
||||
"legend": {
|
||||
"mode": "hidden"
|
||||
}
|
||||
},
|
||||
"lanes": [
|
||||
{
|
||||
"id": "adoption",
|
||||
"label": "Stage 1: Delivery first"
|
||||
}
|
||||
],
|
||||
"mainPath": [
|
||||
"delivery",
|
||||
"remaining"
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"id": "delivery",
|
||||
"lane": "adoption",
|
||||
"col": 0,
|
||||
"yOffset": -140.0,
|
||||
"type": "backend",
|
||||
"label": "Delivery first",
|
||||
"sublabel": "Human + AI Agent; keep improving.",
|
||||
"width": 340,
|
||||
"height": 240
|
||||
},
|
||||
{
|
||||
"id": "remaining",
|
||||
"lane": "adoption",
|
||||
"col": 0,
|
||||
"yOffset": 140.0,
|
||||
"type": "backend",
|
||||
"label": "Other SDLC phases",
|
||||
"sublabel": "Expand adoption; order not yet decided.",
|
||||
"width": 340,
|
||||
"height": 240
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "flow-0",
|
||||
"from": "delivery",
|
||||
"to": "remaining",
|
||||
"role": "main",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "top"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"schema_version": 2,
|
||||
"diagram_type": "workflow",
|
||||
"meta": {
|
||||
"title": "Stage 1: Delivery first",
|
||||
"output": "static/diagrams/strategy-roadmap-adoption/index.html",
|
||||
"quality_profile": "showcase",
|
||||
"animation": "none",
|
||||
"legend": {
|
||||
"mode": "hidden"
|
||||
}
|
||||
},
|
||||
"lanes": [
|
||||
{
|
||||
"id": "adoption",
|
||||
"label": "Stage 1: Delivery first"
|
||||
}
|
||||
],
|
||||
"mainPath": [
|
||||
"delivery",
|
||||
"remaining"
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"id": "delivery",
|
||||
"lane": "adoption",
|
||||
"col": 0,
|
||||
"yOffset": 0,
|
||||
"type": "backend",
|
||||
"label": "Delivery first",
|
||||
"sublabel": "Human + AI Agent; keep improving.",
|
||||
"width": 340,
|
||||
"height": 240
|
||||
},
|
||||
{
|
||||
"id": "remaining",
|
||||
"lane": "adoption",
|
||||
"col": 1,
|
||||
"yOffset": 0,
|
||||
"type": "backend",
|
||||
"label": "Other SDLC phases",
|
||||
"sublabel": "Expand adoption; order not yet decided.",
|
||||
"width": 340,
|
||||
"height": 240
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "flow-0",
|
||||
"from": "delivery",
|
||||
"to": "remaining",
|
||||
"role": "main",
|
||||
"fromSide": "right",
|
||||
"toSide": "left"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
{
|
||||
"schema_version": 2,
|
||||
"diagram_type": "workflow",
|
||||
"meta": {
|
||||
"title": "Improve within each phase",
|
||||
"output": "static/diagrams/strategy-roadmap-loop-mobile/index.html",
|
||||
"quality_profile": "showcase",
|
||||
"animation": "none",
|
||||
"legend": {
|
||||
"mode": "hidden"
|
||||
}
|
||||
},
|
||||
"lanes": [
|
||||
{
|
||||
"id": "adoption",
|
||||
"label": "Improve within each phase"
|
||||
}
|
||||
],
|
||||
"mainPath": [
|
||||
"understand",
|
||||
"baseline",
|
||||
"topology"
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"id": "understand",
|
||||
"lane": "adoption",
|
||||
"col": 0,
|
||||
"yOffset": -700.0,
|
||||
"type": "backend",
|
||||
"label": "Understand today",
|
||||
"sublabel": "Actual practices, tools and problems.",
|
||||
"width": 340,
|
||||
"height": 240
|
||||
},
|
||||
{
|
||||
"id": "baseline",
|
||||
"lane": "adoption",
|
||||
"col": 0,
|
||||
"yOffset": -420.0,
|
||||
"type": "backend",
|
||||
"label": "Set metrics / KPIs",
|
||||
"sublabel": "Establish a baseline before changes.",
|
||||
"width": 340,
|
||||
"height": 240
|
||||
},
|
||||
{
|
||||
"id": "topology",
|
||||
"lane": "adoption",
|
||||
"col": 0,
|
||||
"yOffset": -140.0,
|
||||
"type": "backend",
|
||||
"label": "Engineer topology",
|
||||
"sublabel": "Connect skills, triggers and handoffs.",
|
||||
"width": 340,
|
||||
"height": 240
|
||||
},
|
||||
{
|
||||
"id": "use",
|
||||
"lane": "adoption",
|
||||
"col": 0,
|
||||
"yOffset": 140.0,
|
||||
"type": "backend",
|
||||
"label": "Use skills",
|
||||
"sublabel": "Real work with a human + AI Agent.",
|
||||
"width": 340,
|
||||
"height": 240
|
||||
},
|
||||
{
|
||||
"id": "collect",
|
||||
"lane": "adoption",
|
||||
"col": 0,
|
||||
"yOffset": 420.0,
|
||||
"type": "backend",
|
||||
"label": "Collect results",
|
||||
"sublabel": "Metrics, friction and human feedback.",
|
||||
"width": 340,
|
||||
"height": 240
|
||||
},
|
||||
{
|
||||
"id": "reassess",
|
||||
"lane": "adoption",
|
||||
"col": 0,
|
||||
"yOffset": 700.0,
|
||||
"type": "backend",
|
||||
"label": "Understand anew",
|
||||
"sublabel": "Compare with baseline; repeat the loop.",
|
||||
"width": 340,
|
||||
"height": 240
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "flow-0",
|
||||
"from": "understand",
|
||||
"to": "baseline",
|
||||
"role": "main",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "top"
|
||||
},
|
||||
{
|
||||
"id": "flow-1",
|
||||
"from": "baseline",
|
||||
"to": "topology",
|
||||
"role": "main",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "top"
|
||||
},
|
||||
{
|
||||
"id": "flow-2",
|
||||
"from": "topology",
|
||||
"to": "use",
|
||||
"role": "main",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "top"
|
||||
},
|
||||
{
|
||||
"id": "flow-3",
|
||||
"from": "use",
|
||||
"to": "collect",
|
||||
"role": "main",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "top"
|
||||
},
|
||||
{
|
||||
"id": "flow-4",
|
||||
"from": "collect",
|
||||
"to": "reassess",
|
||||
"role": "main",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "top"
|
||||
},
|
||||
{
|
||||
"id": "repeat",
|
||||
"from": "reassess",
|
||||
"to": "understand",
|
||||
"role": "return",
|
||||
"fromSide": "left",
|
||||
"toSide": "left",
|
||||
"route": "return-left"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
{
|
||||
"schema_version": 2,
|
||||
"diagram_type": "workflow",
|
||||
"meta": {
|
||||
"title": "Improve within each phase",
|
||||
"output": "static/diagrams/strategy-roadmap-loop/index.html",
|
||||
"quality_profile": "showcase",
|
||||
"animation": "none",
|
||||
"legend": {
|
||||
"mode": "hidden"
|
||||
}
|
||||
},
|
||||
"lanes": [
|
||||
{
|
||||
"id": "adoption",
|
||||
"label": "Improve within each phase"
|
||||
}
|
||||
],
|
||||
"mainPath": [
|
||||
"understand",
|
||||
"baseline",
|
||||
"topology"
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"id": "understand",
|
||||
"lane": "adoption",
|
||||
"col": 0,
|
||||
"yOffset": -160,
|
||||
"type": "backend",
|
||||
"label": "Understand today",
|
||||
"sublabel": "Actual practices, tools and problems.",
|
||||
"width": 340,
|
||||
"height": 240
|
||||
},
|
||||
{
|
||||
"id": "baseline",
|
||||
"lane": "adoption",
|
||||
"col": 1,
|
||||
"yOffset": -160,
|
||||
"type": "backend",
|
||||
"label": "Set metrics / KPIs",
|
||||
"sublabel": "Establish a baseline before changes.",
|
||||
"width": 340,
|
||||
"height": 240
|
||||
},
|
||||
{
|
||||
"id": "topology",
|
||||
"lane": "adoption",
|
||||
"col": 2,
|
||||
"yOffset": -160,
|
||||
"type": "backend",
|
||||
"label": "Engineer topology",
|
||||
"sublabel": "Connect skills, triggers and handoffs.",
|
||||
"width": 340,
|
||||
"height": 240
|
||||
},
|
||||
{
|
||||
"id": "use",
|
||||
"lane": "adoption",
|
||||
"col": 2,
|
||||
"yOffset": 160,
|
||||
"type": "backend",
|
||||
"label": "Use skills",
|
||||
"sublabel": "Real work with a human + AI Agent.",
|
||||
"width": 340,
|
||||
"height": 240
|
||||
},
|
||||
{
|
||||
"id": "collect",
|
||||
"lane": "adoption",
|
||||
"col": 1,
|
||||
"yOffset": 160,
|
||||
"type": "backend",
|
||||
"label": "Collect results",
|
||||
"sublabel": "Metrics, friction and human feedback.",
|
||||
"width": 340,
|
||||
"height": 240
|
||||
},
|
||||
{
|
||||
"id": "reassess",
|
||||
"lane": "adoption",
|
||||
"col": 0,
|
||||
"yOffset": 160,
|
||||
"type": "backend",
|
||||
"label": "Understand anew",
|
||||
"sublabel": "Compare with baseline; repeat the loop.",
|
||||
"width": 340,
|
||||
"height": 240
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "flow-0",
|
||||
"from": "understand",
|
||||
"to": "baseline",
|
||||
"role": "main",
|
||||
"fromSide": "right",
|
||||
"toSide": "left"
|
||||
},
|
||||
{
|
||||
"id": "flow-1",
|
||||
"from": "baseline",
|
||||
"to": "topology",
|
||||
"role": "main",
|
||||
"fromSide": "right",
|
||||
"toSide": "left"
|
||||
},
|
||||
{
|
||||
"id": "flow-2",
|
||||
"from": "topology",
|
||||
"to": "use",
|
||||
"role": "main",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "top"
|
||||
},
|
||||
{
|
||||
"id": "flow-3",
|
||||
"from": "use",
|
||||
"to": "collect",
|
||||
"role": "main",
|
||||
"fromSide": "left",
|
||||
"toSide": "right"
|
||||
},
|
||||
{
|
||||
"id": "flow-4",
|
||||
"from": "collect",
|
||||
"to": "reassess",
|
||||
"role": "main",
|
||||
"fromSide": "left",
|
||||
"toSide": "right"
|
||||
},
|
||||
{
|
||||
"id": "repeat",
|
||||
"from": "reassess",
|
||||
"to": "understand",
|
||||
"role": "return",
|
||||
"fromSide": "top",
|
||||
"toSide": "bottom",
|
||||
"route": "straight"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
"schema_version": 2,
|
||||
"diagram_type": "workflow",
|
||||
"meta": {
|
||||
"title": "Recommended adoption roadmap",
|
||||
"title": "Two stages toward Target",
|
||||
"output": "static/diagrams/strategy-roadmap-mobile/index.html",
|
||||
"quality_profile": "showcase",
|
||||
"animation": "none",
|
||||
@@ -13,102 +13,42 @@
|
||||
"lanes": [
|
||||
{
|
||||
"id": "adoption",
|
||||
"label": "Recommended sequence"
|
||||
"label": "Two stages toward Target"
|
||||
}
|
||||
],
|
||||
"mainPath": [
|
||||
"assessment",
|
||||
"readiness",
|
||||
"pilot",
|
||||
"expansion",
|
||||
"sustain"
|
||||
"skills",
|
||||
"autonomy"
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"id": "assessment",
|
||||
"id": "skills",
|
||||
"lane": "adoption",
|
||||
"col": 0,
|
||||
"yOffset": -140.0,
|
||||
"type": "backend",
|
||||
"label": "Understand today",
|
||||
"sublabel": "Code quality and delivery processes understood.",
|
||||
"label": "Adopt skills",
|
||||
"sublabel": "Human + AI Agent in each SDLC phase.",
|
||||
"width": 340,
|
||||
"height": 240,
|
||||
"yOffset": -560
|
||||
"height": 240
|
||||
},
|
||||
{
|
||||
"id": "readiness",
|
||||
"id": "autonomy",
|
||||
"lane": "adoption",
|
||||
"col": 0,
|
||||
"yOffset": 140.0,
|
||||
"type": "backend",
|
||||
"label": "Prepare foundations",
|
||||
"sublabel": "Critical pilot gaps closed.",
|
||||
"label": "Autonomous Agent",
|
||||
"sublabel": "Future: use proven skills and practices.",
|
||||
"width": 340,
|
||||
"height": 240,
|
||||
"yOffset": -280
|
||||
},
|
||||
{
|
||||
"id": "pilot",
|
||||
"lane": "adoption",
|
||||
"col": 0,
|
||||
"type": "backend",
|
||||
"label": "Pilot",
|
||||
"sublabel": "AI skills tested against the baseline.",
|
||||
"width": 340,
|
||||
"height": 240,
|
||||
"yOffset": 0
|
||||
},
|
||||
{
|
||||
"id": "expansion",
|
||||
"lane": "adoption",
|
||||
"col": 0,
|
||||
"type": "backend",
|
||||
"label": "Expand",
|
||||
"sublabel": "Next workflow justified by evidence.",
|
||||
"width": 340,
|
||||
"height": 240,
|
||||
"yOffset": 280
|
||||
},
|
||||
{
|
||||
"id": "sustain",
|
||||
"lane": "adoption",
|
||||
"col": 0,
|
||||
"type": "backend",
|
||||
"label": "Sustain",
|
||||
"sublabel": "Skills reviewed and improved.",
|
||||
"width": 340,
|
||||
"height": 240,
|
||||
"yOffset": 560
|
||||
"height": 240
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "flow-0",
|
||||
"from": "assessment",
|
||||
"to": "readiness",
|
||||
"role": "main",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "top"
|
||||
},
|
||||
{
|
||||
"id": "flow-1",
|
||||
"from": "readiness",
|
||||
"to": "pilot",
|
||||
"role": "main",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "top"
|
||||
},
|
||||
{
|
||||
"id": "flow-2",
|
||||
"from": "pilot",
|
||||
"to": "expansion",
|
||||
"role": "main",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "top"
|
||||
},
|
||||
{
|
||||
"id": "flow-3",
|
||||
"from": "expansion",
|
||||
"to": "sustain",
|
||||
"from": "skills",
|
||||
"to": "autonomy",
|
||||
"role": "main",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "top"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"schema_version": 2,
|
||||
"diagram_type": "workflow",
|
||||
"meta": {
|
||||
"title": "Recommended adoption roadmap",
|
||||
"title": "Two stages toward Target",
|
||||
"output": "static/diagrams/strategy-roadmap/index.html",
|
||||
"quality_profile": "showcase",
|
||||
"animation": "none",
|
||||
@@ -13,64 +13,33 @@
|
||||
"lanes": [
|
||||
{
|
||||
"id": "adoption",
|
||||
"label": "Recommended sequence"
|
||||
"label": "Two stages toward Target"
|
||||
}
|
||||
],
|
||||
"mainPath": [
|
||||
"assessment",
|
||||
"readiness",
|
||||
"pilot",
|
||||
"expansion",
|
||||
"sustain"
|
||||
"skills",
|
||||
"autonomy"
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"id": "assessment",
|
||||
"id": "skills",
|
||||
"lane": "adoption",
|
||||
"col": 0,
|
||||
"yOffset": 0,
|
||||
"type": "backend",
|
||||
"label": "Understand today",
|
||||
"sublabel": "Code quality and delivery processes understood.",
|
||||
"label": "Adopt skills",
|
||||
"sublabel": "Human + AI Agent in each SDLC phase.",
|
||||
"width": 340,
|
||||
"height": 240
|
||||
},
|
||||
{
|
||||
"id": "readiness",
|
||||
"id": "autonomy",
|
||||
"lane": "adoption",
|
||||
"col": 1,
|
||||
"yOffset": 0,
|
||||
"type": "backend",
|
||||
"label": "Prepare foundations",
|
||||
"sublabel": "Critical pilot gaps closed.",
|
||||
"width": 340,
|
||||
"height": 240
|
||||
},
|
||||
{
|
||||
"id": "pilot",
|
||||
"lane": "adoption",
|
||||
"col": 2,
|
||||
"type": "backend",
|
||||
"label": "Pilot",
|
||||
"sublabel": "AI skills tested against the baseline.",
|
||||
"width": 340,
|
||||
"height": 240
|
||||
},
|
||||
{
|
||||
"id": "expansion",
|
||||
"lane": "adoption",
|
||||
"col": 3,
|
||||
"type": "backend",
|
||||
"label": "Expand",
|
||||
"sublabel": "Next workflow justified by evidence.",
|
||||
"width": 340,
|
||||
"height": 240
|
||||
},
|
||||
{
|
||||
"id": "sustain",
|
||||
"lane": "adoption",
|
||||
"col": 4,
|
||||
"type": "backend",
|
||||
"label": "Sustain",
|
||||
"sublabel": "Skills reviewed and improved.",
|
||||
"label": "Autonomous Agent",
|
||||
"sublabel": "Future: use proven skills and practices.",
|
||||
"width": 340,
|
||||
"height": 240
|
||||
}
|
||||
@@ -78,32 +47,8 @@
|
||||
"edges": [
|
||||
{
|
||||
"id": "flow-0",
|
||||
"from": "assessment",
|
||||
"to": "readiness",
|
||||
"role": "main",
|
||||
"fromSide": "right",
|
||||
"toSide": "left"
|
||||
},
|
||||
{
|
||||
"id": "flow-1",
|
||||
"from": "readiness",
|
||||
"to": "pilot",
|
||||
"role": "main",
|
||||
"fromSide": "right",
|
||||
"toSide": "left"
|
||||
},
|
||||
{
|
||||
"id": "flow-2",
|
||||
"from": "pilot",
|
||||
"to": "expansion",
|
||||
"role": "main",
|
||||
"fromSide": "right",
|
||||
"toSide": "left"
|
||||
},
|
||||
{
|
||||
"id": "flow-3",
|
||||
"from": "expansion",
|
||||
"to": "sustain",
|
||||
"from": "skills",
|
||||
"to": "autonomy",
|
||||
"role": "main",
|
||||
"fromSide": "right",
|
||||
"toSide": "left"
|
||||
|
||||
+25
-31
@@ -1,7 +1,7 @@
|
||||
---
|
||||
id: roadmap
|
||||
title: Roadmap
|
||||
description: Assess current code quality and delivery processes, close critical gaps, then pilot, expand and sustain AI adoption.
|
||||
description: Build proven skills in human and AI Agent work, starting with Delivery, as the foundation for the future autonomous SDLC.
|
||||
hide_table_of_contents: true
|
||||
---
|
||||
|
||||
@@ -9,68 +9,62 @@ import ArchifyDiagram from '@site/src/components/ArchifyDiagram';
|
||||
|
||||
<div className="strategy-content">
|
||||
|
||||
Understand today’s code and how work really reaches production before introducing AI into the workflow. Fix what would make a pilot unsafe or impossible to evaluate—not every item of technical debt.
|
||||
## Two stages toward Target
|
||||
|
||||
<ArchifyDiagram name="strategy-roadmap" title="Understand today → Prepare foundations → Pilot → Expand → Sustain" story />
|
||||
<ArchifyDiagram name="strategy-roadmap" title="Adopt skills → Future Autonomous Agent" story />
|
||||
|
||||
**Recommended sequence · Target operating model, not an approved schedule or deployed governance.**
|
||||
**Humans direct, guide and review throughout.** Stage 1 standardizes skills in human + AI Agent work across the SDLC. Proven skills, measurements and working practices become the foundation for Stage 2: the future Autonomous Agent in [Target](./target-operating-model.mdx).
|
||||
|
||||
## 1. Understand code quality and delivery
|
||||
Target operating model—not deployed autonomy, an agreed schedule or approved KPI thresholds.
|
||||
|
||||
Inspect representative repositories across important services and change types: tests and CI reliability, maintainability, technical debt and security findings. Trace recent changes through the actual SDLC with the people doing the work—from request to production and feedback. Check ownership, review and release practices, handoffs, rework and bottlenecks; do not assume the documented process is the real one.
|
||||
## Stage 1: Delivery first
|
||||
|
||||
**Output:** baseline findings, a process map and prioritized gaps. Use these to identify a low-risk, repeatable workflow with observable acceptance—not simply the easiest repository.
|
||||
<ArchifyDiagram name="strategy-roadmap-adoption" title="Delivery first → Other SDLC phases, order undecided" story />
|
||||
|
||||
## 2. Fix critical readiness gaps
|
||||
Start with Delivery, then extend skills adoption to the remaining SDLC phases. Their order is not yet decided. **Delivery keeps improving as adoption expands.**
|
||||
|
||||
Resolve gaps that threaten safe execution or a trustworthy comparison: for example, exposed secrets, unclear approval authority or unreliable checks needed to accept the pilot’s changes. Establish accountable people, data boundaries and recovery for the selected scope. Broader refactoring and non-critical debt can run alongside the pilot with a separate improvement backlog.
|
||||
## Improve within each phase
|
||||
|
||||
**Output:** a readiness decision and bounded pilot charter. Proceed only when the critical gaps for that workflow are closed; otherwise narrow the scope or hold.
|
||||
<ArchifyDiagram name="strategy-roadmap-loop" title="Each phase: understand → baseline → topology → use → collect → reassess → repeat" story />
|
||||
|
||||
## 3. Run a bounded pilot
|
||||
Repeat this loop within every phase—not as six global roadmap stages. Skills topology defines **what skills exist, how they relate, when they are used and how they work together**, including handoffs—not just a catalogue.
|
||||
|
||||
Try a small set of reviewed skills in the selected workflow, with human review and no new privileged autonomous actions. Compare equivalent work with the baseline: accepted outcomes, escaped defects, full cost and verification effort. [Skills Measurement](./measurement.mdx) explains what to collect, calculate and report; agree the comparison rules before starting.
|
||||
## Delivery in practice
|
||||
|
||||
**Output:** a pilot report and a continue, adjust or stop decision. Missing evidence is a reason to investigate, not proof of improvement.
|
||||
**Understand the engineering path first:** codebase structure and existing principles → system-logic source → tests → builds and packaging → deployments. Inspect actual practices, tooling, controls and problems with the people doing the work, rather than assuming the documented process is the real one.
|
||||
|
||||
## 4. Expand only on evidence
|
||||
**Establish the comparison, then engineer skills.** Define metrics/KPIs and a baseline for comparable work before changing it. [Skills Measurement](./measurement.mdx) explains collection and calculation. Connect reviewed skills to the engineering path, with clear triggers, dependencies and acceptance checks.
|
||||
|
||||
Extend to another workflow or team only when quality, usefulness, cost and support capacity justify it. Prove reliable skill distribution, revocation and recovery before expanding authority. Introduce autonomous SDLC execution progressively under human direction; new departmental workflows need their own data boundaries and acceptance evidence.
|
||||
|
||||
**Output:** an evidenced expansion decision with verified controls for the next scope—not organization-wide rollout by default.
|
||||
|
||||
## 5. Sustain and improve
|
||||
|
||||
Review outcomes, drift, incidents and maintenance effort. Retire ineffective skills, revisit process bottlenecks and repeat assessment when repositories, clients, data or authority change. Work toward [Target](./target-operating-model.mdx): an autonomous agent executes and governs the repeating SDLC while humans direct, guide and review.
|
||||
|
||||
**Output:** maintained skills, a refreshed improvement backlog and evidence for the next cycle. Keep human takeover available; narrow or stop workflows when evidence no longer supports them.
|
||||
**Use, collect, compare, repeat.** A human works with an AI Agent on real changes. Collect results, friction and feedback alongside quality, cost and verification effort. Compare with the baseline to understand the new current situation; revise the skills and working practices, then run the loop again.
|
||||
|
||||
<details>
|
||||
<summary>Supporting gate evidence and hold conditions</summary>
|
||||
|
||||
These are recommended implementation gates, not assigned human owners, approved thresholds or installed integrations. Agree acceptance criteria for each selected workflow before evaluating it.
|
||||
|
||||
### Assessment → readiness
|
||||
### Understand the current situation
|
||||
|
||||
Retain the sampled repository revisions, test/CI results, debt and security findings, and traced changes behind the process map. Record coverage and unknowns. Distinguish critical pilot blockers from parallel improvements; hold selection if the sample cannot explain how the candidate workflow is delivered and accepted.
|
||||
Sample representative repositories and changes. Retain repository revisions, tests and CI reliability, maintainability, technical debt and security findings. Trace ownership, review and release practices, handoffs, rework and bottlenecks. Keep baseline findings, a process map and prioritized gaps; hold selection if the sample cannot explain how work is delivered and accepted.
|
||||
|
||||
### Readiness → pilot
|
||||
### Prepare safe, measurable human + AI work
|
||||
|
||||
Inventory actual client hosts/builds, provider/data terms, authority and event availability. Confirm accountable people and independent reviewers, forbidden actions, data/retention decisions, recovery and a reproducible baseline. Hold if accountability, lawful collection or critical acceptance checks remain unresolved.
|
||||
|
||||
### Pilot → expansion
|
||||
Close critical gaps such as exposed secrets, unclear approval authority or unreliable acceptance checks. Broader refactoring and non-critical debt can run alongside the selected work. Stage 1 introduces no new privileged autonomous actions.
|
||||
|
||||
### Engineer and evaluate connected skills
|
||||
|
||||
Use a small registry, immutable skill contracts, qualified client adapters, evaluations and coaching. Verify the measurement calculations with synthetic fixtures before interpreting real outcomes. Require mandatory safety checks, independent task acceptance, reconciled source coverage and user feedback. Hold or narrow scope if quality or coverage is inadequate; usage alone does not justify expansion.
|
||||
|
||||
### Expansion → wider authority
|
||||
### Extend adoption without stopping improvement
|
||||
|
||||
Prove canary updates, effective-version readback, version locking, exception expiry, stale/offline handling, rollback and revocation. Hold if effective versions or controls cannot be verified or the catalog cannot be supported.
|
||||
|
||||
For autonomous execution, begin with observation and evidence assembly, then proposing and routing work, before granting scoped execution authority. Recommended controls include exact-artifact approvals, idempotency, duplicate/outage recovery, separation of duties, audit and kill-switch drills. Prove human takeover; never infer permission from missing evidence. The agent ultimately executes and governs the cycle, not only routes tasks.
|
||||
Use evidence to choose the next SDLC phase; no second or third phase is assigned here. Continue the improvement loop in Delivery and every phase already using skills.
|
||||
|
||||
### Sustained operation
|
||||
### Prepare for the future Autonomous Agent
|
||||
|
||||
Operations, Marketing, Finance & Accounting and HR & Administration workflows each need a workflow owner, outcome, data boundary, capacity and domain-specific review; Product and Technology continue quality improvement. Keep sensitive decisions human. Use accepted outcomes, guardrails, total cost and experience to continue, narrow, stop or roll back. Changed client, provider, data or authority boundaries reopen the relevant gates.
|
||||
Stage 2 builds on proven skills, measurements and working practices—not permission inferred from adoption alone. Before granting scoped execution authority, prove observation and evidence assembly, then proposing and routing work. Recommended controls include exact-artifact approvals, idempotency, duplicate/outage recovery, separation of duties, audit and kill-switch drills. Prove human takeover; never infer permission from missing evidence. The agent ultimately executes and governs the cycle, not only routes tasks, while humans retain direction and review. Keep sensitive decisions human. Changed client, provider, data or authority boundaries reopen the relevant checks.
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
@@ -1,129 +1,136 @@
|
||||
const {chromium} = require('playwright');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const ids = ['assessment','readiness','pilot','expansion','sustain'];
|
||||
const views = {
|
||||
'strategy-roadmap': ['skills','autonomy'],
|
||||
'strategy-roadmap-adoption': ['delivery','remaining'],
|
||||
'strategy-roadmap-loop': ['understand','baseline','topology','use','collect','reassess'],
|
||||
};
|
||||
(async () => {
|
||||
const base = process.argv[2] || 'http://127.0.0.1:4173';
|
||||
const output = `.cache/roadmap-timeline-${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 = [];
|
||||
const inspect = async (frame, vertical) => {
|
||||
const inspect = async (frame, name, vertical) => {
|
||||
await frame.locator('svg[role="img"]').waitFor();
|
||||
const geometry = await frame.locator('svg[role="img"]').evaluate(svg => ({
|
||||
width:innerWidth, height:innerHeight,
|
||||
edges:[...svg.querySelectorAll('path[data-edge-id]')].map(p => ({id:p.dataset.edgeId,box:p.getBoundingClientRect().toJSON()})),
|
||||
cards:[...svg.querySelectorAll('[data-node-id]')].map(node => ({
|
||||
id:node.dataset.nodeId, box:node.querySelector('rect').getBoundingClientRect().toJSON(),
|
||||
number:node.querySelector('[data-stage-number]').textContent,
|
||||
numberColor:getComputedStyle(node.querySelector('[data-stage-number]')).fill,
|
||||
titleColor:getComputedStyle(node.querySelector('[data-node-label]')).fill,
|
||||
text:[...node.querySelectorAll('text')].map(text => ({value:text.textContent, box:text.getBoundingClientRect().toJSON(), size:parseFloat(getComputedStyle(text).fontSize)*text.getScreenCTM().a}))
|
||||
}))
|
||||
}));
|
||||
geometry.cards.sort((a,b) => a.box.y-b.box.y || a.box.x-b.box.x);
|
||||
const ids=views[name];
|
||||
geometry.cards.sort((a,b)=>ids.indexOf(a.id)-ids.indexOf(b.id));
|
||||
assert.deepEqual(geometry.cards.map(n => n.id), ids);
|
||||
assert.deepEqual(geometry.cards.map(n => n.number), ['1','2','3','4','5']);
|
||||
assert.deepEqual(geometry.cards.map(n => n.number), ids.length === 6 ? ids.map((_,i)=>`Step ${i+1}`) : name === 'strategy-roadmap' ? ['Stage 1','Stage 2'] : ['First','Then']);
|
||||
assert.equal(geometry.edges.length,ids.length===6?6:1);
|
||||
if(ids.length===6) assert(geometry.edges.some(e=>e.id==='repeat'));
|
||||
for(const edge of geometry.edges) assert(edge.box.left>=0 && edge.box.right<=geometry.width+1 && edge.box.top>=0 && edge.box.bottom<=geometry.height+1, `clipped edge ${name} ${edge.id}`);
|
||||
for (const [index, card] of geometry.cards.entries()) {
|
||||
assert.equal(card.text.length, 3, 'number, title and one outcome only');
|
||||
assert.equal(card.numberColor,card.titleColor,'stage number must inherit readable theme color');
|
||||
assert(card.box.left >= 0 && card.box.top >= 0 && card.box.right <= geometry.width + 1 && card.box.bottom <= geometry.height + 1, `outside iframe: ${card.id}`);
|
||||
for (const text of card.text) {
|
||||
assert(text.size >= 13, `small text: ${card.id} ${text.size}`);
|
||||
assert(!/OUTPUT|Exit:/.test(text.value));
|
||||
assert(text.box.left >= card.box.left && text.box.right <= card.box.right + 1 && text.box.top >= card.box.top && text.box.bottom <= card.box.bottom + 1, `clipped: ${card.id}`);
|
||||
}
|
||||
for (let t=1; t<card.text.length; t++) assert(card.text[t-1].box.bottom < card.text[t].box.top, `overlapping text: ${card.id}`);
|
||||
if (index) {
|
||||
const previous = geometry.cards[index-1].box;
|
||||
if (vertical) {assert.equal(previous.x,card.box.x); assert(previous.bottom < card.box.top);}
|
||||
else {assert.equal(previous.y,card.box.y); assert(previous.right < card.box.left);}
|
||||
assert.equal(card.text.length,3);
|
||||
assert(card.box.left >= 0 && card.box.top >= 0 && card.box.right <= geometry.width+1 && card.box.bottom <= geometry.height+1, `outside iframe ${name} ${card.id}`);
|
||||
for(const text of card.text) {
|
||||
assert(text.size>=13, `small text ${name} ${card.id}: ${text.size}`);
|
||||
assert(text.box.left>=card.box.left && text.box.right<=card.box.right+1 && text.box.top>=card.box.top && text.box.bottom<=card.box.bottom+1, `clipped text ${name} ${card.id}`);
|
||||
}
|
||||
for(let t=1;t<card.text.length;t++) assert(card.text[t-1].box.bottom<card.text[t].box.top,`text overlap ${card.id}`);
|
||||
if(index && vertical) {assert.equal(geometry.cards[index-1].box.x,card.box.x);assert(geometry.cards[index-1].box.bottom<card.box.top);}
|
||||
}
|
||||
return geometry;
|
||||
};
|
||||
try {
|
||||
for (const width of [1440,1024,390,320]) {
|
||||
const context = await browser.newContext({viewport:{width,height:1000}, colorScheme:'light'});
|
||||
for(const width of [1440,1024,390,320]) {
|
||||
const context=await browser.newContext({viewport:{width,height:1000},colorScheme:'light'});
|
||||
try {
|
||||
const page = await context.newPage();
|
||||
const errors = [];
|
||||
page.on('pageerror', error => errors.push(error.message));
|
||||
assert.equal((await page.goto(`${base}/eldorado-v1/documentation/strategy/roadmap/`, {waitUntil:'networkidle'})).status(),200);
|
||||
const panel = page.locator('[data-diagram="strategy-roadmap"]');
|
||||
const iframe = panel.locator(width < 1360 ? '.story-mobile' : '.story-desktop');
|
||||
const frame = await (await iframe.elementHandle()).contentFrame();
|
||||
await frame.locator('svg[role="img"]').waitFor();
|
||||
const opener = panel.getByRole('button', {name:/^Expand /});
|
||||
for (const theme of ['light','dark']) {
|
||||
if (await frame.locator('html').getAttribute('data-theme') !== theme) await frame.locator('#btn-theme').click();
|
||||
await page.waitForFunction(t => document.documentElement.dataset.theme === t, theme);
|
||||
const inline = await inspect(frame,width < 1360);
|
||||
await panel.screenshot({path:`${output}/${width}-${theme}-inline.png`});
|
||||
await opener.focus(); await page.keyboard.press('Enter');
|
||||
const close = panel.getByRole('button', {name:'Close full-screen diagram', exact:true});
|
||||
await close.waitFor({state:'visible'});
|
||||
const fullscreen = await (await panel.locator('dialog iframe').elementHandle()).contentFrame();
|
||||
await fullscreen.waitForFunction(t => document.documentElement.dataset.theme === t, theme);
|
||||
const expanded = await inspect(fullscreen,width < 1000);
|
||||
await page.screenshot({path:`${output}/${width}-${theme}-fullscreen.png`});
|
||||
await page.keyboard.press('Escape');
|
||||
await page.waitForFunction(() => !document.querySelector('[data-diagram="strategy-roadmap"] dialog').open);
|
||||
assert(await opener.evaluate(n => n === document.activeElement));
|
||||
assert(!await page.evaluate(() => document.documentElement.scrollWidth > innerWidth));
|
||||
results.push({width,theme,inline,expanded});
|
||||
fs.writeFileSync(`${output}/results.json`,JSON.stringify(results,null,2));
|
||||
const page=await context.newPage(); const errors=[]; page.on('pageerror',e=>errors.push(e.message));
|
||||
assert.equal((await page.goto(`${base}/eldorado-v1/documentation/strategy/roadmap/`,{waitUntil:'networkidle'})).status(),200);
|
||||
await page.addStyleTag({content:'html {scroll-behavior:auto !important;}'});
|
||||
assert.equal(await page.locator('.archify-panel--story').count(),3);
|
||||
for(const name of Object.keys(views)) {
|
||||
const panel=page.locator(`[data-diagram="${name}"]`);
|
||||
const iframe=panel.locator(width<1100?'.story-mobile':'.story-desktop');
|
||||
await iframe.scrollIntoViewIfNeeded();
|
||||
const frame=await (await iframe.elementHandle()).contentFrame();
|
||||
await frame.locator('svg[role="img"]').waitFor();
|
||||
const opener=panel.getByRole('button',{name:/^Expand /});
|
||||
for(const theme of ['light','dark']) {
|
||||
const frameTop=await iframe.evaluate(n=>n.getBoundingClientRect().top+scrollY);
|
||||
await page.evaluate(y=>scrollTo({top:y-120,behavior:'instant'}),frameTop);
|
||||
if(await frame.locator('html').getAttribute('data-theme')!==theme) await frame.locator('#btn-theme').click();
|
||||
await page.waitForFunction(t=>document.documentElement.dataset.theme===t,theme);
|
||||
const inline=await inspect(frame,name,width<1100);
|
||||
await frame.locator('#btn-preset').focus();await page.keyboard.press('Enter');
|
||||
await frame.locator('[data-preset-value="blueprint"]').focus();await page.keyboard.press('Enter');
|
||||
await frame.waitForFunction(()=>document.documentElement.dataset.preset==='blueprint');
|
||||
await inspect(frame,name,width<1100);
|
||||
await frame.locator('#btn-preset').focus();await page.keyboard.press('Enter');
|
||||
await frame.locator('[data-preset-value="classic"]').focus();await page.keyboard.press('Enter');
|
||||
await frame.waitForFunction(()=>document.documentElement.dataset.preset==='classic');
|
||||
await frame.locator('#btn-export').focus();await page.keyboard.press('Enter');
|
||||
await frame.locator('[data-format="svg"]').focus();
|
||||
const [download]=await Promise.all([page.waitForEvent('download'),page.keyboard.press('Enter')]);
|
||||
assert.equal(await download.failure(),null);
|
||||
const exported=`${output}/${width}-${theme}-${name}.svg`;
|
||||
await download.saveAs(exported);
|
||||
const svg=fs.readFileSync(exported,'utf8');
|
||||
for(const id of views[name]) assert(svg.includes(`data-node-id="${id}"`));
|
||||
assert(svg.includes('eldorado-roadmap-typography'));
|
||||
await panel.screenshot({path:`${output}/${width}-${theme}-${name}-inline.png`});
|
||||
// Scroll each card into the real viewport: tall iframe captures alone
|
||||
// may contain compositor blanks even when DOM geometry is correct.
|
||||
for(const card of inline.cards) {
|
||||
const top=await iframe.evaluate(n=>n.getBoundingClientRect().top+scrollY);
|
||||
await page.evaluate(y=>scrollTo({top:y,behavior:'instant'}),top+card.box.top-100);
|
||||
await page.screenshot({path:`${output}/${width}-${theme}-${name}-${card.id}.png`});
|
||||
}
|
||||
await opener.focus();await page.keyboard.press('Enter');
|
||||
const close=panel.getByRole('button',{name:'Close full-screen diagram',exact:true});await close.waitFor({state:'visible'});
|
||||
const fullIframe=panel.locator('dialog iframe');
|
||||
const fullscreen=await (await fullIframe.elementHandle()).contentFrame();
|
||||
await fullscreen.waitForFunction(t=>document.documentElement.dataset.theme===t,theme);
|
||||
const expanded=await inspect(fullscreen,name,width<1000);
|
||||
await page.screenshot({path:`${output}/${width}-${theme}-${name}-fullscreen.png`});
|
||||
await close.focus();await page.keyboard.press('Escape');
|
||||
await fullIframe.waitFor({state:'detached'});assert(await opener.evaluate(n=>n===document.activeElement));
|
||||
results.push({width,theme,name,inline,expanded});fs.writeFileSync(`${output}/results.json`,JSON.stringify(results,null,2));
|
||||
}
|
||||
}
|
||||
assert.deepEqual(errors,[]);
|
||||
assert(!await page.evaluate(()=>document.documentElement.scrollWidth>innerWidth));assert.deepEqual(errors,[]);
|
||||
} finally {await context.close();}
|
||||
}
|
||||
assert.equal(results.length,8);
|
||||
for (const viewport of [{width:390,height:667},{width:320,height:568}]) {
|
||||
const context = await browser.newContext({viewport});
|
||||
assert.equal(results.length,24);
|
||||
for(const viewport of [{width:390,height:667},{width:320,height:568}]) {
|
||||
const context=await browser.newContext({viewport});
|
||||
try {
|
||||
const page = await context.newPage();
|
||||
await page.goto(`${base}/eldorado-v1/documentation/strategy/roadmap/`, {waitUntil:'networkidle'});
|
||||
const panel = page.locator('[data-diagram="strategy-roadmap"]');
|
||||
const opener = panel.getByRole('button', {name:/^Expand /});
|
||||
await opener.focus(); await page.keyboard.press('Enter');
|
||||
const modal = panel.locator('dialog');
|
||||
const close = modal.getByRole('button', {name:'Close full-screen diagram', exact:true});
|
||||
await close.waitFor({state:'visible'});
|
||||
const iframe = modal.locator('iframe');
|
||||
const frame = await (await iframe.elementHandle()).contentFrame();
|
||||
const geometry = await inspect(frame,true);
|
||||
assert(await modal.evaluate(n => n.scrollHeight > n.clientHeight), 'short fullscreen must scroll');
|
||||
for (const card of geometry.cards) {
|
||||
await modal.evaluate((n, y) => {n.scrollTop = y;}, card.box.top);
|
||||
const frameBox = await iframe.boundingBox();
|
||||
const toolbarBox = await modal.locator('.archify-modal-toolbar').boundingBox();
|
||||
const closeBox = await close.boundingBox();
|
||||
assert(closeBox.y >= 0 && closeBox.y + closeBox.height <= viewport.height, 'close remains visible');
|
||||
assert(frameBox.y + card.box.top >= toolbarBox.y + toolbarBox.height - 1 && frameBox.y + card.box.bottom <= viewport.height + 1, `stage unreachable: ${card.id}`);
|
||||
const page=await context.newPage();await page.goto(`${base}/eldorado-v1/documentation/strategy/roadmap/`,{waitUntil:'networkidle'});
|
||||
await page.addStyleTag({content:'html {scroll-behavior:auto !important;}'});
|
||||
for(const name of Object.keys(views)) {
|
||||
const panel=page.locator(`[data-diagram="${name}"]`), opener=panel.getByRole('button',{name:/^Expand /});
|
||||
await opener.focus();await page.keyboard.press('Enter');const modal=panel.locator('dialog'), close=modal.getByRole('button',{name:'Close full-screen diagram',exact:true});await close.waitFor({state:'visible'});
|
||||
const iframe=modal.locator('iframe'), frame=await (await iframe.elementHandle()).contentFrame();
|
||||
const geometry=await inspect(frame,name,true);
|
||||
assert(await modal.evaluate(n=>n.scrollHeight>n.clientHeight),'short fullscreen must scroll');
|
||||
for(const card of geometry.cards) {
|
||||
await modal.evaluate((n,y)=>{n.scrollTop=y;},card.box.top);
|
||||
const b=await iframe.boundingBox(), t=await modal.locator('.archify-modal-toolbar').boundingBox(), c=await close.boundingBox();
|
||||
assert(c.y>=0 && c.y+c.height<=viewport.height);
|
||||
assert(b.y+card.box.top>=t.y+t.height-1 && b.y+card.box.bottom<=viewport.height+1,`unreachable ${name} ${card.id}`);
|
||||
}
|
||||
await modal.evaluate(n=>{n.scrollTop=0;});
|
||||
for(const selector of ['#btn-theme','#btn-preset','#btn-export']) {
|
||||
const control=frame.locator(selector), b=await control.boundingBox();assert(b.x>=0 && b.x+b.width<=viewport.width && b.y>=0 && b.y+b.height<=viewport.height);await control.click();if(selector!=='#btn-theme')await control.click();
|
||||
}
|
||||
await close.focus();await page.keyboard.press('Escape');await iframe.waitFor({state:'detached'});assert(await opener.evaluate(n=>n===document.activeElement));
|
||||
await page.keyboard.press('Enter');await close.waitFor({state:'visible'});await close.click();await iframe.waitFor({state:'detached'});assert(await opener.evaluate(n=>n===document.activeElement));
|
||||
results.push({viewport,name,scrollable:geometry});fs.writeFileSync(`${output}/results.json`,JSON.stringify(results,null,2));
|
||||
}
|
||||
// Controls stay reachable at the beginning of the scrollable viewer.
|
||||
await modal.evaluate(n => {n.scrollTop = 0;});
|
||||
for (const selector of ['#btn-theme','#btn-preset','#btn-export']) {
|
||||
const control = frame.locator(selector);
|
||||
const box = await control.boundingBox();
|
||||
assert(box.x >= 0 && box.x + box.width <= viewport.width && box.y >= 0 && box.y + box.height <= viewport.height, `control outside viewport: ${selector}`);
|
||||
await control.click();
|
||||
if (selector !== '#btn-theme') await control.click();
|
||||
}
|
||||
await close.focus(); await page.keyboard.press('Escape');
|
||||
await page.waitForFunction(() => !document.querySelector('[data-diagram="strategy-roadmap"] dialog').open);
|
||||
await iframe.waitFor({state:'detached'});
|
||||
assert(await opener.evaluate(n => n === document.activeElement), 'Escape restores opener focus');
|
||||
await page.keyboard.press('Enter');
|
||||
await close.waitFor({state:'visible'});
|
||||
await close.focus(); await page.keyboard.press('Enter');
|
||||
await page.waitForFunction(() => !document.querySelector('[data-diagram="strategy-roadmap"] dialog').open);
|
||||
assert(await opener.evaluate(n => n === document.activeElement), 'close button restores opener focus');
|
||||
results.push({viewport,scrollable:geometry});
|
||||
fs.writeFileSync(`${output}/results.json`,JSON.stringify(results,null,2));
|
||||
} finally {await context.close();}
|
||||
}
|
||||
assert.equal(results.length,10);
|
||||
console.log('PASS 16 timeline contexts: 4 widths × 2 themes × inline/fullscreen; ordered readable text, no clipping, keyboard exit.');
|
||||
console.log('PASS short fullscreen 390×667 and 320×568: readable stages reachable by scroll, sticky close, accessible controls, keyboard close and focus restoration.');
|
||||
assert.equal(results.length,30);
|
||||
console.log('PASS 48 diagram contexts: three views × four widths × light/dark × inline/fullscreen; plus six short-phone scrolling/keyboard contexts.');
|
||||
} finally {await browser.close();}
|
||||
})().catch(error => {console.error(error); process.exitCode=1;});
|
||||
})().catch(error=>{console.error(error);process.exitCode=1;});
|
||||
+10
-10
@@ -8,8 +8,8 @@ const fs = require('node:fs');
|
||||
const browser = await chromium.launch({headless:true, executablePath:process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE, args:['--no-sandbox']});
|
||||
const results = [];
|
||||
try {
|
||||
// Intermediate widths must use the readable vertical Roadmap, not shrink five cards.
|
||||
for (const width of [768, 1024, 1280]) {
|
||||
// Intermediate widths must use the readable vertical Roadmap, not shrink the loop.
|
||||
for (const width of [768, 1024]) {
|
||||
const context = await browser.newContext({viewport:{width,height:1000}, colorScheme:'light'});
|
||||
try {
|
||||
const page = await context.newPage();
|
||||
@@ -31,7 +31,7 @@ const fs = require('node:fs');
|
||||
text:[...node.querySelectorAll('text')].map(t => ({text:t.textContent, box:t.getBoundingClientRect().toJSON(), size:parseFloat(getComputedStyle(t).fontSize)*t.getScreenCTM().a}))
|
||||
})).sort((a,b) => a.box.y-b.box.y || a.box.x-b.box.x)
|
||||
}));
|
||||
assert.deepEqual(geometry.nodes.map(n => n.id), ['assessment','readiness','pilot','expansion','sustain']);
|
||||
assert.deepEqual(geometry.nodes.map(n => n.id), ['skills','autonomy']);
|
||||
for (const [index, card] of geometry.nodes.entries()) {
|
||||
assert(card.text.length > 0, `${width}: missing text in ${card.id}`);
|
||||
assert(card.box.left >= 0 && card.box.top >= 0 && card.box.right <= geometry.width + 1 && card.box.bottom <= geometry.height + 1, `${width}: card outside iframe: ${card.id}`);
|
||||
@@ -61,16 +61,16 @@ const fs = require('node:fs');
|
||||
assert.equal(response.status(), 200);
|
||||
assert.equal(await page.locator('h1').innerText(), 'Roadmap');
|
||||
const headings = page.locator('.strategy-content > h2');
|
||||
assert.deepEqual((await headings.allTextContents()).map(s => s.replace(/\u200b/g, '')), ['1. Understand code quality and delivery','2. Fix critical readiness gaps','3. Run a bounded pilot','4. Expand only on evidence','5. Sustain and improve']);
|
||||
assert.deepEqual((await headings.allTextContents()).map(s => s.replace(/\u200b/g, '')), ['Two stages toward Target','Stage 1: Delivery first','Improve within each phase','Delivery in practice']);
|
||||
const text = await page.locator('.strategy-content').innerText();
|
||||
for (const marker of ['baseline findings', 'process map', 'prioritized gaps', 'alongside the pilot', 'Recommended sequence']) assert(text.includes(marker), marker);
|
||||
for (const marker of ['Humans direct, guide and review throughout', 'Delivery keeps improving', 'system-logic source', 'baseline', 'Target operating model']) assert(text.includes(marker), marker);
|
||||
assert(!text.includes('Supporting gate evidence and hold conditions\n\nThese are'));
|
||||
if (width < 700) await page.getByRole('button', {name:'Toggle navigation bar'}).click();
|
||||
const menu = page.locator('.theme-doc-sidebar-menu .menu__link').filter({visible:true});
|
||||
await menu.first().waitFor({state:'visible'});
|
||||
assert.deepEqual((await menu.allTextContents()).slice(0,2), ['Target','Roadmap']);
|
||||
if (width < 700) await page.getByRole('button', {name:'Close navigation bar'}).click();
|
||||
const panel = page.locator('.archify-panel--story');
|
||||
const panel = page.locator('.archify-panel--story').first();
|
||||
const iframe = panel.locator(width > 700 ? '.story-desktop' : '.story-mobile');
|
||||
await iframe.scrollIntoViewIfNeeded();
|
||||
const frame = await (await iframe.elementHandle()).contentFrame();
|
||||
@@ -92,7 +92,7 @@ const fs = require('node:fs');
|
||||
width:innerWidth, height:innerHeight,
|
||||
minLabel:Math.min(...[...svg.querySelectorAll('text[data-node-label]')].map(n => parseFloat(getComputedStyle(n).fontSize)*n.getScreenCTM().a))
|
||||
}));
|
||||
assert.deepEqual(geometry.nodes.map(n => n.id), ['assessment','readiness','pilot','expansion','sustain']);
|
||||
assert.deepEqual(geometry.nodes.map(n => n.id), ['skills','autonomy']);
|
||||
for (const n of geometry.nodes) assert(n.x >= 0 && n.y >= 0 && n.right <= geometry.width + 1 && n.bottom <= geometry.height + 1);
|
||||
assert(geometry.minLabel >= 10, `readable node labels: ${geometry.minLabel}`);
|
||||
for (let i=1; i<geometry.nodes.length; i++) {
|
||||
@@ -125,9 +125,9 @@ const fs = require('node:fs');
|
||||
const [download] = await Promise.all([page.waitForEvent('download'), frame.locator('[data-format="svg"]').click()]);
|
||||
assert.equal(await download.failure(), null);
|
||||
await download.saveAs(`${output}/${width}.svg`);
|
||||
assert(fs.readFileSync(`${output}/${width}.svg`, 'utf8').includes('Understand today'));
|
||||
assert(fs.readFileSync(`${output}/${width}.svg`, 'utf8').includes('Adopt skills'));
|
||||
assert(fs.readFileSync(`${output}/${width}.svg`, 'utf8').includes('eldorado-roadmap-typography'));
|
||||
for (const id of ['assessment','readiness','pilot','expansion','sustain']) {
|
||||
for (const id of ['skills','autonomy']) {
|
||||
await frame.locator(`svg[role="img"] [data-node-id="${id}"]`).screenshot({path:`${output}/${width}-${id}-card.png`});
|
||||
}
|
||||
const opener = panel.getByRole('button', {name:/^Expand /});
|
||||
@@ -159,6 +159,6 @@ const fs = require('node:fs');
|
||||
fs.writeFileSync(`${output}/results.json`, JSON.stringify(results,null,2));
|
||||
await context.close();
|
||||
}
|
||||
console.log(`PASS ${results.length} Roadmap viewports: visible stages/outputs, sidebar, diagram geometry, controls, export and links`);
|
||||
console.log(`PASS ${results.length} Roadmap viewports: three-level story, sidebar, diagram geometry, controls, export and links`);
|
||||
} finally {await browser.close();}
|
||||
})().catch(error => {console.error(error); process.exitCode = 1;});
|
||||
@@ -12,14 +12,14 @@ const fs=require('node:fs');
|
||||
const page=await context.newPage();
|
||||
for(const id of ['index','target-operating-model','skills-governance','skills-quality','skills-adoption','measurement','skills-maintenance','roadmap']){
|
||||
const response=await page.goto(`${base}/eldorado-v1/documentation/strategy/${id==='index'?'':id+'/'}`,{waitUntil:'networkidle'});assert.equal(response.status(),200);
|
||||
const panel=page.locator('.archify-panel--story');
|
||||
const panel=page.locator('.archify-panel--story').first();
|
||||
const iframe=panel.locator(width===390?'.story-mobile':'.story-desktop');await iframe.scrollIntoViewIfNeeded();
|
||||
const frame=await (await iframe.elementHandle()).contentFrame();await frame.locator('svg[role="img"]').waitFor();
|
||||
const geometry=await frame.locator('svg[role="img"]').evaluate(svg=>{
|
||||
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<=(['index','target-operating-model'].includes(id)?18:7),id);
|
||||
assert(geometry.nodes.length>=(id==='roadmap'?2: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<=(id==='measurement'?1100:id==='roadmap'?600:200),id);assert(!data.overflow,id);assert.equal(data.openDetails,0,id);
|
||||
|
||||
@@ -26,7 +26,7 @@ for (const file of readdirSync('diagrams').sort()) {
|
||||
mkdirSync(`static/diagrams/${name}`, {recursive: true});
|
||||
if (spec.diagram_type !== type || spec.meta.output !== output) throw new Error(`Diagram type/output mismatch: ${file}`);
|
||||
const run = args => execFileSync(process.execPath,[cli,...args],{stdio:'inherit',env:{...process.env,ARCHIFY_UPDATE_CHECK_DISABLED:'1'}});
|
||||
const roadmap = ['strategy-roadmap', 'strategy-roadmap-mobile'].includes(name);
|
||||
const roadmap = /^strategy-roadmap(?:-(?:adoption|loop))?(?:-mobile)?$/.test(name);
|
||||
if (roadmap) {
|
||||
// Native renderer validates schema and compiles layout. Check readability
|
||||
// after multiline typography, not upstream's single-line preview.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Native Workflow v2 owns node geometry, mainPath and routing. The portal adds
|
||||
// multiline typography: upstream's three single-line labels are too small for
|
||||
// a five-stage reading view. Do not change the pinned vendor or graph semantics.
|
||||
// a Roadmap reading view. Do not change the pinned vendor or graph semantics.
|
||||
const escape = text => text.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"');
|
||||
const wrap = (text, limit = 21) => {
|
||||
const lines = [];
|
||||
@@ -11,7 +11,7 @@ const wrap = (text, limit = 21) => {
|
||||
return lines;
|
||||
};
|
||||
export function roadmapViewer(html, spec) {
|
||||
if (spec.diagram_type !== 'workflow' || spec.schema_version !== 2 || spec.nodes.length !== 5) throw new Error('Roadmap workflow contract drift');
|
||||
if (spec.diagram_type !== 'workflow' || spec.schema_version !== 2 || ![2, 6].includes(spec.nodes.length)) throw new Error('Roadmap workflow contract drift');
|
||||
const boxes = [];
|
||||
for (const [index, node] of spec.nodes.entries()) {
|
||||
const start = html.indexOf(`<g id="node-${node.id}"`);
|
||||
@@ -30,7 +30,8 @@ export function roadmapViewer(html, spec) {
|
||||
return `<text x="${x+24}" y="${y+top}" font-size="${size}" class="${cls}" ${attrs}>${lines.map((line,i) => `<tspan x="${x+24}" dy="${i ? 34 : 0}">${escape(line)}</tspan>`).join('')}</text>`;
|
||||
};
|
||||
if (!node.sublabel || node.tag || /Output:|Exit:|\n/.test(node.sublabel)) throw new Error(`Roadmap content contract drift: ${node.id}`);
|
||||
const content = text(String(index+1), 40, 26, 't-primary', 'data-stage-number="" font-weight="700"')
|
||||
const number = spec.nodes.length === 6 ? `Step ${index+1}` : spec.nodes[0].id === 'skills' ? `Stage ${index+1}` : index === 0 ? 'First' : 'Then';
|
||||
const content = text(number, 40, 26, 't-primary', 'data-stage-number="" font-weight="700"')
|
||||
+ text(node.label, 88, 30, 't-primary', 'data-node-label="" font-weight="700"')
|
||||
+ text(node.sublabel, 160, 26, 't-muted');
|
||||
// Preserve native node title/metadata and rectangles; replace sigil/text only.
|
||||
@@ -41,7 +42,8 @@ export function roadmapViewer(html, spec) {
|
||||
const replacement = original.slice(0,prefixEnd).replaceAll('rx="6"', 'rx="0"') + content + '</g>';
|
||||
html = html.slice(0,start) + replacement + html.slice(end);
|
||||
}
|
||||
const left = Math.min(...boxes.map(b => b.x)) - 12;
|
||||
// The native mobile return channel sits outside the card column.
|
||||
const left = Math.min(...boxes.map(b => b.x)) - (spec.edges.some(e => e.route === 'return-left') ? 80 : 12);
|
||||
const top = Math.min(...boxes.map(b => b.y)) - 12;
|
||||
const width = Math.max(...boxes.map(b => b.x+b.w)) - left + 12;
|
||||
const height = Math.max(...boxes.map(b => b.y+b.h)) - top + 12;
|
||||
@@ -51,6 +53,7 @@ export function roadmapViewer(html, spec) {
|
||||
if (roots.length !== 1) throw new Error('Roadmap SVG contract drift');
|
||||
const root = roots[0][0].replace(/viewBox="[^"]*"/, `viewBox="${left} ${top} ${width} ${height}"`);
|
||||
const styles = `<style id="eldorado-roadmap-typography">
|
||||
html[data-embed="true"] .diagram-container > svg {max-height:calc(100vh - 84px);}
|
||||
[data-node-id] text {font-family: Arial, sans-serif;}
|
||||
svg[data-preset="classic"] [data-node-id] > rect.c-backend,
|
||||
svg[data-preset="editorial"] [data-node-id] > rect.c-backend {fill:var(--panel); stroke:var(--border);}
|
||||
|
||||
@@ -4,18 +4,18 @@ import {readFileSync, writeFileSync, mkdtempSync, rmSync, mkdirSync} from 'node:
|
||||
import {execFileSync} from 'node:child_process';
|
||||
import {roadmapViewer} from './roadmap-viewer.mjs';
|
||||
|
||||
for (const suffix of ['', '-mobile']) test(`native Workflow${suffix} retains graph and passes final composition`, () => {
|
||||
for (const name of ['strategy-roadmap', 'strategy-roadmap-adoption', 'strategy-roadmap-loop']) for (const suffix of ['', '-mobile']) test(`${name}${suffix} retains graph and passes final composition`, () => {
|
||||
mkdirSync('.cache', {recursive:true});
|
||||
const dir = mkdtempSync('.cache/roadmap-test-');
|
||||
try {
|
||||
const source = `diagrams/strategy-roadmap${suffix}.workflow.json`;
|
||||
const source = `diagrams/${name}${suffix}.workflow.json`;
|
||||
const spec = JSON.parse(readFileSync(source, 'utf8'));
|
||||
const output = `${dir}/index.html`;
|
||||
execFileSync(process.execPath, ['vendor/archify/renderers/workflow/render-workflow.mjs', source, output], {env:{...process.env, ARCHIFY_UPDATE_CHECK_DISABLED:'1'}});
|
||||
const native = readFileSync(output, 'utf8');
|
||||
const styled = roadmapViewer(native, spec);
|
||||
const graph = html => [...html.matchAll(/<path\b[^>]*data-edge-id="[^"]+"[^>]*>/g)].map(m => m[0]);
|
||||
assert.equal(graph(native).length, 4);
|
||||
assert.equal(graph(native).length, spec.edges.length);
|
||||
assert.deepEqual(graph(styled), graph(native), 'native paths must not be rewritten');
|
||||
for (const node of spec.nodes) assert(styled.includes(`data-node-id="${node.id}"`));
|
||||
assert.equal((styled.match(/<text data-node-label=/g) || []).length, 0, 'single-line preview replaced');
|
||||
|
||||
+24
-25
@@ -3,33 +3,32 @@ import assert from 'node:assert/strict';
|
||||
import {readFileSync} from 'node:fs';
|
||||
const read = path => readFileSync(path, 'utf8');
|
||||
|
||||
test('Roadmap visibly assesses code and real delivery before adoption', () => {
|
||||
export const views = {
|
||||
'strategy-roadmap': ['skills', 'autonomy'],
|
||||
'strategy-roadmap-adoption': ['delivery', 'remaining'],
|
||||
'strategy-roadmap-loop': ['understand', 'baseline', 'topology', 'use', 'collect', 'reassess'],
|
||||
};
|
||||
test('Roadmap has three visual levels and preserves human direction and Delivery discovery', () => {
|
||||
const page = read('docs/strategy/roadmap.mdx');
|
||||
const visible = page.split('<details>')[0];
|
||||
const headings = [...visible.matchAll(/^## (.+)$/gm)].map(match => match[1]);
|
||||
assert.deepEqual(headings, ['1. Understand code quality and delivery', '2. Fix critical readiness gaps', '3. Run a bounded pilot', '4. Expand only on evidence', '5. Sustain and improve']);
|
||||
assert(visible.indexOf(' story />') < visible.indexOf('## 1.'));
|
||||
for (const marker of ['representative repositories', 'tests and CI', 'maintainability', 'technical debt', 'security findings', 'actual SDLC', 'ownership', 'review and release', 'bottlenecks', 'baseline findings', 'process map', 'prioritized gaps', 'alongside the pilot', './measurement.mdx', './target-operating-model.mdx']) assert(visible.includes(marker), marker);
|
||||
assert.equal((visible.match(/\*\*Output:/g) || []).length, 5);
|
||||
assert.match(visible, /Recommended sequence/);
|
||||
assert(!page.includes('### Foundation'));
|
||||
for (const marker of ['revocation', 'exact-artifact approvals', 'idempotency', 'human takeover', 'sensitive decisions']) assert(page.includes(marker), marker);
|
||||
assert.deepEqual([...visible.matchAll(/<ArchifyDiagram name="([^"]+)"/g)].map(m => m[1]), Object.keys(views));
|
||||
assert.deepEqual([...visible.matchAll(/^## (.+)$/gm)].map(m => m[1]), ['Two stages toward Target', 'Stage 1: Delivery first', 'Improve within each phase', 'Delivery in practice']);
|
||||
for (const marker of ['Humans direct, guide and review throughout', 'standardizes skills', 'foundation for Stage 2', 'future Autonomous Agent', 'not deployed autonomy', 'order is not yet decided', 'Delivery keeps improving', 'not as six global roadmap stages', 'what skills exist, how they relate, when they are used and how they work together', 'codebase structure', 'existing principles', 'system-logic source', 'tests', 'builds and packaging', 'deployments', 'practices, tooling, controls and problems', 'metrics/KPIs and a baseline', 'friction and feedback', './measurement.mdx', './target-operating-model.mdx']) assert(visible.includes(marker), marker);
|
||||
for (const marker of ['revocation','exact-artifact approvals','idempotency','human takeover','sensitive decisions','security findings','reproducible baseline']) assert(page.includes(marker), marker);
|
||||
assert(!/## [1-5]\.|Prepare foundations|Sustain and improve/.test(page));
|
||||
});
|
||||
|
||||
test('both roadmap diagrams make assessment and readiness prerequisites to the pilot', () => {
|
||||
const ids = ['assessment', 'readiness', 'pilot', 'expansion', 'sustain'];
|
||||
for (const suffix of ['', '-mobile']) {
|
||||
const diagram = JSON.parse(read(`diagrams/strategy-roadmap${suffix}.workflow.json`));
|
||||
assert.equal(diagram.schema_version, 2);
|
||||
assert.equal(diagram.diagram_type, 'workflow');
|
||||
assert.deepEqual(diagram.mainPath, ids);
|
||||
assert.deepEqual(diagram.nodes.map(n => n.id), ids);
|
||||
assert.deepEqual(diagram.nodes.map(n => n.label), ['Understand today', 'Prepare foundations', 'Pilot', 'Expand', 'Sustain']);
|
||||
assert.deepEqual(diagram.edges.map(e => [e.from,e.to]), ids.slice(1).map((id, i) => [ids[i],id]));
|
||||
assert.equal(diagram.nodes[0].sublabel, 'Code quality and delivery processes understood.');
|
||||
assert.equal(diagram.nodes[1].sublabel, 'Critical pilot gaps closed.');
|
||||
assert(diagram.nodes.every(n => !n.sublabel.includes('Output:') && !n.tag && n.height === 240));
|
||||
assert.deepEqual(diagram.nodes.map(n => n.col), suffix ? [0,0,0,0,0] : [0,1,2,3,4]);
|
||||
assert.equal(diagram.lanes.length, 1);
|
||||
for (const [name, ids] of Object.entries(views)) test(`${name}: native graph, mobile parity and loop semantics`, () => {
|
||||
const variants = ['', '-mobile'].map(suffix => JSON.parse(read(`diagrams/${name}${suffix}.workflow.json`)));
|
||||
for (const [index, d] of variants.entries()) {
|
||||
assert.equal(d.schema_version, 2);
|
||||
assert.equal(d.diagram_type, 'workflow');
|
||||
assert.deepEqual(d.mainPath, ids.length === 6 ? ids.slice(0,3) : ids);
|
||||
assert.deepEqual(d.nodes.map(n => n.id), ids);
|
||||
assert.deepEqual(d.edges.filter(e => e.role === 'main').map(e => [e.from,e.to]), ids.slice(1).map((id,i) => [ids[i],id]));
|
||||
assert.deepEqual(d.edges.filter(e => e.role === 'return').map(e => [e.from,e.to]), ids.length === 6 ? [['reassess','understand']] : []);
|
||||
if (index) assert(d.nodes.every(n => n.col === 0));
|
||||
assert(d.nodes.every(n => n.sublabel && !n.tag && n.height === 240));
|
||||
}
|
||||
const semantics = d => ({nodes:d.nodes.map(({col,yOffset,...n}) => n), edges:d.edges.map(({fromSide,toSide,route,...e}) => e)});
|
||||
assert.deepEqual(semantics(variants[0]),semantics(variants[1]));
|
||||
});
|
||||
@@ -8,12 +8,12 @@ test('every strategy page leads with a compact diagram story and closes its refe
|
||||
for (const {id} of model.pages) {
|
||||
const page = read(`docs/strategy/${id}.mdx`);
|
||||
const narrative = page.split('<div className="strategy-content">')[1].replace(/<details>[\s\S]*?<\/details>/g, '');
|
||||
assert.equal((narrative.match(/<ArchifyDiagram /g) || []).length, 1, id);
|
||||
assert.equal((narrative.match(/<ArchifyDiagram /g) || []).length, id === 'roadmap' ? 3 : 1, id);
|
||||
assert(narrative.includes(' story />'), id);
|
||||
assert(!page.includes('<details>') || page.indexOf(' story />') < page.indexOf('<details>'), id);
|
||||
assert(!/<details\s+open/.test(page), id);
|
||||
assert.equal((page.match(/<details>/g) || []).length, (page.match(/<\/details>/g) || []).length, id);
|
||||
assert.equal((narrative.match(/^## /gm) || []).length, id === 'measurement' ? 4 : id === 'roadmap' ? 5 : 1, id);
|
||||
assert.equal((narrative.match(/^## /gm) || []).length, id === 'measurement' ? 4 : id === 'roadmap' ? 4 : 1, id);
|
||||
assert((narrative.match(/^- /gm) || []).length <= 3, id);
|
||||
assert(narrative.includes('**Next:**'), id);
|
||||
assert(!narrative.includes('| ---'), id);
|
||||
|
||||
@@ -13,7 +13,7 @@ export default function ArchifyDiagram({name, title, compact = false, story = fa
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const [narrowRoadmap, setNarrowRoadmap] = useState(false);
|
||||
useEffect(() => {
|
||||
if (name !== 'strategy-roadmap') return;
|
||||
if (!name.startsWith('strategy-roadmap')) return;
|
||||
const media = window.matchMedia('(max-width: 999px)');
|
||||
const update = () => setNarrowRoadmap(media.matches);
|
||||
update();
|
||||
@@ -74,7 +74,7 @@ export default function ArchifyDiagram({name, title, compact = false, story = fa
|
||||
</> : <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 ref={fullscreen} onLoad={() => syncTheme(fullscreen.current)} src={`${narrowRoadmap ? mobileSource : source}?${name === 'strategy-roadmap' ? 'embed=1&' : ''}theme=${initialTheme.current}`} title={`${title} — expanded`} sandbox="allow-scripts allow-downloads" referrerPolicy="no-referrer" />}
|
||||
{expanded && <iframe ref={fullscreen} onLoad={() => syncTheme(fullscreen.current)} src={`${narrowRoadmap ? mobileSource : source}?${name.startsWith('strategy-roadmap') ? 'embed=1&' : ''}theme=${initialTheme.current}`} title={`${title} — expanded`} sandbox="allow-scripts allow-downloads" referrerPolicy="no-referrer" />}
|
||||
</dialog>
|
||||
</section>;
|
||||
}
|
||||
+13
-11
@@ -111,8 +111,10 @@ a:focus-visible, button:focus-visible {outline: 3px solid #b88719; outline-offse
|
||||
.archify-panel--story > iframe {height: auto; aspect-ratio: 480 / 408;}
|
||||
.archify-panel--story > .story-mobile {display: none;}
|
||||
.theme-doc-markdown:has([data-diagram="strategy-roadmap"]) {max-width: none;}
|
||||
.archify-panel--story[data-diagram="strategy-roadmap"] {max-width: none;}
|
||||
.archify-panel--story[data-diagram="strategy-roadmap"] > .story-desktop {aspect-ratio: auto; height: 240px;}
|
||||
.archify-panel--story[data-diagram^="strategy-roadmap"] {max-width: none; --roadmap-height: 640px;}
|
||||
.archify-panel--story[data-diagram^="strategy-roadmap"] > .story-desktop {aspect-ratio: auto; height: 300px;}
|
||||
.archify-panel--story[data-diagram="strategy-roadmap-loop"] {--roadmap-height: 1840px;}
|
||||
.archify-panel--story[data-diagram="strategy-roadmap-loop"] > .story-desktop {height: 460px;}
|
||||
.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;}
|
||||
@@ -124,15 +126,15 @@ a:focus-visible, button:focus-visible {outline: 3px solid #b88719; outline-offse
|
||||
@media (max-width: 700px) {
|
||||
.archify-panel--story > .story-desktop {display: none;}
|
||||
.archify-panel--story > .story-mobile {display: block; height: auto; aspect-ratio: 240 / 748;}
|
||||
.archify-panel--story[data-diagram="strategy-roadmap"] > .story-mobile {aspect-ratio: 364 / 1480;}
|
||||
|
||||
.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: 680 / 2340;}
|
||||
}
|
||||
/* Five Roadmap cards need desktop space, including room for the sidebar. */
|
||||
@media (width < 1360px) {
|
||||
.archify-panel--story[data-diagram="strategy-roadmap"] > .story-desktop {display: none;}
|
||||
.archify-panel--story[data-diagram="strategy-roadmap"] > .story-mobile {display: block; height: auto; width: 100%; max-width: 364px; margin-inline: auto; aspect-ratio: 364 / 1480;}
|
||||
/* Keep the three-card loop readable beside the documentation sidebar. */
|
||||
@media (width < 1100px) {
|
||||
.archify-panel--story[data-diagram^="strategy-roadmap"] > .story-desktop {display: none;}
|
||||
.archify-panel--story[data-diagram^="strategy-roadmap"] > .story-mobile {display: block; height: var(--roadmap-height); width: 100%; max-width: 432px; margin-inline: auto; aspect-ratio: auto;}
|
||||
}
|
||||
.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);}
|
||||
@@ -142,11 +144,11 @@ a:focus-visible, button:focus-visible {outline: 3px solid #b88719; outline-offse
|
||||
.archify-modal-toolbar {display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 12px 20px; border-bottom: 1px solid var(--line);}
|
||||
.archify-modal-toolbar button {display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; width: 44px; height: 44px; padding: 0; background: var(--gold); color: #14131a; font: inherit; font-size: 24px; font-weight: 700; border: 0; cursor: pointer;}
|
||||
.archify-panel .archify-modal iframe {flex: 1; min-height: 0; height: 100%; width: 100%;}
|
||||
/* Read the five-stage narrow Roadmap by scrolling, never by fitting its
|
||||
/* Read narrow Roadmap views by scrolling, never by fitting their
|
||||
entire height into a short phone screen. Other diagram viewers are unchanged. */
|
||||
@media (max-width: 999px) {
|
||||
[data-diagram="strategy-roadmap"] .archify-modal {overflow-y: auto; overscroll-behavior: contain;}
|
||||
[data-diagram="strategy-roadmap"] .archify-modal-toolbar {position: sticky; top: 0; z-index: 1; flex-shrink: 0; background: var(--surface);}
|
||||
[data-diagram="strategy-roadmap"] .archify-modal iframe {flex: none; height: 1480px; min-height: 1480px;}
|
||||
[data-diagram^="strategy-roadmap"] .archify-modal {overflow-y: auto; overscroll-behavior: contain;}
|
||||
[data-diagram^="strategy-roadmap"] .archify-modal-toolbar {position: sticky; top: 0; z-index: 1; flex-shrink: 0; background: var(--surface);}
|
||||
[data-diagram^="strategy-roadmap"] .archify-modal iframe {flex: none; height: var(--roadmap-height); min-height: var(--roadmap-height);}
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {.start-card {transition: none;} .start-card:hover {transform: none;}}
|
||||
Reference in new issue
Block a user