feat(blocks): extract six full-guide block components

PhasePanel, FleetDiagram, HandoffTable, WorktreeMap, RouteTable,
SkillPackage as static shells. Each takes typed props and renders
server-side markup with the data-* hooks verified by scripts/verify.mjs
(data-phase, data-tree, data-worker, data-route, data-skill-file). No
client:* directives; interactive islands wire up in task 15.

Tokens only. No raw hex or px font sizes (check-tokens passes). The
parent-background seam colour for the gap:1px grid trick in
FleetDiagram is a documented token gap; see component header comment
and the task final report.
This commit is contained in:
Marcos Paulo
2026-09-05 07:09:38 +00:00
parent 73ceae2aa8
commit 233cc5d6e6
6 changed files with 745 additions and 0 deletions
+160
View File
@@ -0,0 +1,160 @@
---
// WorktreeMap — the repository-topology diagram with the SVG trunk-and-branches
// behind four `tree-node` buttons.
//
// Static shell: the SVG paths render server-side; the nodes are buttons with
// the `data-tree` hook asserted by `scripts/verify.mjs`. The `root` node and
// the `initial` branch are marked selected.
interface Branch {
/** The `data-tree` hook, e.g. "ui", "tests", "docs". */
id: string;
/** Uppercase label, e.g. "UI AGENT". */
label: string;
/** Strong line, e.g. "agent/ui". */
strong: string;
/** Status small, e.g. "3 files · working". */
small: string;
/** CSS modifier so each branch picks up its tone. */
tone: 'ui' | 'tests' | 'docs' | string;
}
interface Props {
branches: Branch[];
initial?: string;
}
const { branches, initial = 'main' } = Astro.props;
---
<div class="tree-stage" role="tree" aria-label="Repository worktree topology">
<svg viewBox="0 0 760 330" preserveAspectRatio="none" aria-hidden="true">
<path class="tree-edge trunk" d="M380 48 V118"></path>
<path class="tree-edge" d="M380 118 C380 170 110 150 110 224"></path>
<path class="tree-edge" d="M380 118 V224"></path>
<path class="tree-edge" d="M380 118 C380 170 650 150 650 224"></path>
</svg>
<button
class:list={['tree-node', 'root', { active: initial === 'main' }]}
data-tree="main"
role="treeitem"
aria-selected={initial === 'main'}
><span>ROOT</span><strong>main</strong><small> clean</small></button
>
{
branches.map((branch) => (
<button
class:list={['tree-node', 'branch', branch.tone, { active: branch.id === initial }]}
data-tree={branch.id}
role="treeitem"
aria-selected={branch.id === initial}
>
<>
<span>{branch.label}</span>
<strong>{branch.strong}</strong>
<small>{branch.small}</small>
</>
</button>
))
}
</div>
<style>
.tree-stage {
position: relative;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 14px;
padding-top: 90px;
}
/* SVG trunk-and-branches behind the buttons. */
.tree-stage svg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
.tree-edge {
fill: none;
stroke: var(--gold);
stroke-width: 2;
}
/* Root sits at the top centre, spanning both columns. */
.tree-node.root {
grid-column: 1 / -1;
justify-self: center;
width: 180px;
}
.tree-node {
display: grid;
align-content: start;
gap: 12px;
min-height: 135px;
padding: 20px;
color: var(--paper);
background: var(--ink);
border: 0;
text-align: left;
cursor: pointer;
}
.tree-node.root {
background: var(--gold);
color: var(--ink);
}
.tree-node span {
color: var(--accent);
font:
500 var(--step-0) 'DM Mono',
monospace;
letter-spacing: 0.1em;
text-transform: uppercase;
}
.tree-node strong {
font:
600 var(--step-1) 'DM Mono',
monospace;
}
.tree-node small {
margin-top: 0;
color: var(--muted);
font-size: var(--step-0);
}
.tree-node.root small {
color: var(--muted);
}
.tree-node.active {
box-shadow: inset 4px 0 0 var(--gold);
}
.tree-node:focus-visible {
outline: 3px solid var(--gold);
outline-offset: -3px;
}
/* Branch tone variants — left/right placement stays on the grid columns. */
.tree-node.branch.ui {
grid-column: 1;
}
.tree-node.branch.tests {
grid-column: 1 / -1;
justify-self: center;
}
.tree-node.branch.docs {
grid-column: 2;
}
@media (max-width: 560px) {
.tree-stage {
grid-template-columns: 1fr;
padding-top: 90px;
}
.tree-node.branch.ui,
.tree-node.branch.tests,
.tree-node.branch.docs {
grid-column: 1;
justify-self: stretch;
}
}
</style>