6bfae2033e
Extended the typography `--step-*` scale to cover the ad-hoc pixel values used across components (10px to 48px). Added overlay tokens `--white-14`, `--white-23`, `--white-25`, `--white-31`. Canonicalized one-off legacy color hex values in `ReviewDetail`, `ChangeLens`, `PreviewPane`, `RulesInteractive`, and `SkillPackageExplorer` to map to the core semantic palette (`--deep`, `--ink`, `--line`, `--paper`, `--muted`). Replaced ad-hoc max-width media query boundaries (520px, 530px, 600px, 620px) with the closest approved named tokens (`560px`, `800px`, `1100px`).
218 lines
5.4 KiB
TypeScript
218 lines
5.4 KiB
TypeScript
---
|
|
// 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.
|
|
|
|
type Localized = { en: string; pt: string };
|
|
|
|
interface Branch {
|
|
/** The `data-tree` hook, e.g. "ui", "tests", "docs". */
|
|
id: string;
|
|
/** Uppercase label, e.g. "UI AGENT". */
|
|
label: string | Localized;
|
|
/** Strong line, e.g. "agent/ui". */
|
|
strong: string;
|
|
/** Status small, e.g. "3 files · working". */
|
|
small: string | Localized;
|
|
/** CSS modifier so each branch picks up its tone. */
|
|
tone: 'ui' | 'tests' | 'docs' | string;
|
|
}
|
|
|
|
interface Props {
|
|
branches: Branch[];
|
|
initial?: string;
|
|
rootLabel?: string | Localized;
|
|
rootSmall?: string | Localized;
|
|
}
|
|
|
|
const { branches, initial = 'main', rootLabel = 'ROOT', rootSmall = '● clean' } = 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>
|
|
{
|
|
typeof rootLabel === 'string' ? (
|
|
rootLabel
|
|
) : (
|
|
<>
|
|
<span data-language-content="en">{rootLabel.en}</span>
|
|
<span data-language-content="pt" hidden>
|
|
{rootLabel.pt}
|
|
</span>
|
|
</>
|
|
)
|
|
}
|
|
</span>
|
|
<strong>main</strong>
|
|
<small>
|
|
{
|
|
typeof rootSmall === 'string' ? (
|
|
rootSmall
|
|
) : (
|
|
<>
|
|
<span data-language-content="en">{rootSmall.en}</span>
|
|
<span data-language-content="pt" hidden>
|
|
{rootSmall.pt}
|
|
</span>
|
|
</>
|
|
)
|
|
}
|
|
</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>
|
|
{typeof branch.label === 'string' ? (
|
|
branch.label
|
|
) : (
|
|
<>
|
|
<span data-language-content="en">{branch.label.en}</span>
|
|
<span data-language-content="pt" hidden>
|
|
{branch.label.pt}
|
|
</span>
|
|
</>
|
|
)}
|
|
</span>
|
|
<strong>{branch.strong}</strong>
|
|
<small>
|
|
{typeof branch.small === 'string' ? (
|
|
branch.small
|
|
) : (
|
|
<>
|
|
<span data-language-content="en">{branch.small.en}</span>
|
|
<span data-language-content="pt" hidden>
|
|
{branch.small.pt}
|
|
</span>
|
|
</>
|
|
)}
|
|
</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: 1px solid var(--muted);
|
|
text-align: left;
|
|
cursor: pointer;
|
|
}
|
|
.tree-node.root {
|
|
background: var(--gold);
|
|
color: var(--ink);
|
|
}
|
|
.tree-node span {
|
|
color: var(--accent);
|
|
/* token-gap: source uses 9px; no --step-* covers 9px; owner design-system-keeper */
|
|
font:
|
|
500 9px 'DM Mono',
|
|
monospace;
|
|
letter-spacing: 0.1em;
|
|
text-transform: uppercase;
|
|
}
|
|
.tree-node strong {
|
|
/* token-gap: source uses 14px with default weight; no --step-* covers 14px; owner design-system-keeper */
|
|
font:
|
|
14px '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>
|