The canonical starting point for a new design-system spoke of the ESA Ecology
hub. This directory is an inert template: its manifest is stored as
package.json.tmpl (not package.json) so the monorepo's packages/* workspace
glob ignores it and npm install never tries to build it. Files contain
__PLACEHOLDER__ tokens that get substituted when the template is instantiated.
The /spoke-init command (from the spoke-kit plugin) orchestrates
instantiation, and the deterministic copy/rename/substitute work is a script —
scripts/create-spoke.mjs at the hub root. Always scaffold via the script;
hand-copying once silently dropped the .claude/ dot-directory and the spoke
ran with no component-first enforcement. The manual steps below document what
the script does (and remain the fallback of last resort).
A spoke is a child of the @esa/ecology hub — its own Astro repo that:
- depends on the hub packages,
- ships a thin
theme-<slug>.cssof semantic/component token overrides (the brand layer — primitives never move), and - dogfoods the inherited
esa-*components + builds project-specific prototypes on top of them.
The hub owns the design standard and the reference implementation; a spoke re-skins it and adds its own catalog + prototypes. Patterns that prove broadly useful get promoted back up to the hub.
A spoke depends on four packages from the sibling ../ecology checkout:
| Package | Dep type | Role |
|---|---|---|
@esa/tokens |
dependency | Primitives + default semantic tokens (tokens.css, component-tokens.css). |
@esa/ecology |
dependency | The esa-* components (.astro + Lit web components). |
@esa/docs |
dependency | The shared DocsShell + token-driven foundation components (ColorFoundation, TypeFoundation, …). |
@esa/handoff |
devDependency | Dev-mode handoff export (rendered prototype → de-scoped HTML/CSS bundle). |
Plus astro (dependency) and gh-pages (devDependency, for the GitHub Pages
deploy). All four @esa/* deps use file:../ecology/packages/* links, so the
spoke must live as a sibling of the ecology checkout.
Substitute every occurrence of each token across every file (including the
theme-firma2.css filename itself):
| Placeholder | Meaning | Example |
|---|---|---|
ProjectFirma 2.0 |
Display name (titles, brand chrome, prose) | Beacon |
firma2 |
Lowercase id (data-theme, theme filename, DocsShell theme prop) |
beacon |
firma2-design |
Repo/dir name + GitHub Pages base | beacon-design |
firma2 |
npm scope; package name becomes @firma2/design; raw token prefix --firma2-* |
beacon |
PF |
Short text mark for the sidebar | B |
<!-- TODO(spoke-init): add the brand's Google Fonts <link> tags --> |
Where the brand's Google Fonts <link> tags go |
<link href="…DM+Sans…" rel="stylesheet" /> |
Conservation project-tracking platform — design-system spoke for the 2.0 rebuild |
One-line description for the landing hero | The brand language, patterns, and prototypes for the Beacon platform |
The five Foundations pages (color, typography, spacing, radius,
iconography) are generic and ship ready to go — they render live from the
active theme via @esa/docs components. The only per-spoke edit is in
color.astro: pass this spoke's own primitive ramp(s) via the ramps prop (a
TODO(spoke-init) comment marks the spot).
src/data/ds-nav.ts ships a scaffold componentGroups array with one example
entry (Button → esa-button), and src/pages/design-system/components/ ships
exactly one page (esa-button.astro) demonstrating the ComponentDoc pattern.
Populate the rest per spoke:
- If the source app has a catalog (e.g. an Angular
ui-catalog), mirror its sections and entries, mapping eachui-*component to its@esa/ecologyesa-*equivalent. - Otherwise, curate: list the
esa-*components this spoke actually uses, grouped into sensible sections.
For each catalog entry, add a NavItem in ds-nav.ts and a sibling page
under src/pages/design-system/components/<slug>.astro (copy esa-button.astro
as the template).
Preferred (deterministic — run from the hub root):
node scripts/create-spoke.mjs --name "Beacon" --slug beacon --dir beacon-design \
--scope beacon --mark B --tagline "..."Then continue with steps 5–9 below (the judgment work /spoke-init walks).
By hand (fallback only):
- Copy this directory to a new sibling of
../ecology, namedfirma2-design(e.g.../beacon-design). Include dotfiles —.claude/settings.jsondeclares the spoke-kit plugin (skills + enforcement hook); without it the spoke runs with no intelligence layer. - Rename
package.json.tmpl→package.json. - Rename
src/styles/theme-firma2.css→theme-<slug>.css. - Find-and-replace every placeholder from the legend above across all files.
- Insert the brand's Google Fonts
<link>tags at<!-- TODO(spoke-init): add the brand's Google Fonts <link> tags -->insrc/layouts/BaseLayout.astroandsrc/layouts/DocsLayout.astro. - Fill in
src/styles/theme-<slug>.css— work through sections (1)–(7), replacing each/* __FILL__ */marker and placeholder value. Reference../beacon-design/src/styles/theme-beacon.cssas a worked example. - Populate
src/data/ds-nav.tscomponentGroups+ add the matchingsrc/pages/design-system/components/*.astropages (see "catalog" above). - Pass the spoke's primitive ramp(s) into
color.astroviaramps. npm install && npm run devto verify, then build prototypes.
create-spoke.mjs performs steps 1–4 deterministically; /spoke-init runs the
script and then walks 5–9 (drafting 6–8 from the source app's tokens/catalog
where one exists, with human review). It finishes with the definition-of-done
checklist — including the intelligence-layer checks.
firma2-design/
├─ package.json (from package.json.tmpl)
├─ astro.config.mjs base = /firma2-design/ in prod
├─ tsconfig.json
├─ .gitignore
├─ .nojekyll so GitHub Pages serves _astro/
├─ .claude/
│ └─ settings.json declares the ecology marketplace + enables spoke-kit
│ (skills, /spoke-init, and the component-first hook
│ ship from the PLUGIN — nothing is copied here)
├─ README.md
└─ src/
├─ lib/base.ts withBase() — base-aware path helper
├─ layouts/
│ ├─ BaseLayout.astro data-theme + fonts + global CSS
│ ├─ DocsLayout.astro DocsShell wrapper (sidebar/topbar/chrome)
│ └─ ComponentDoc.astro per-component doc page wrapper
├─ data/
│ ├─ ds-nav.ts sidebar nav (foundations + scaffold catalog)
│ └─ prototypes.ts prototype registry (starts empty)
├─ styles/
│ └─ theme-firma2.css the brand theme skeleton
└─ pages/
├─ index.astro landing (layers + prototype list)
├─ patterns/index.astro pattern library (coming soon)
└─ design-system/
├─ index.astro design-system home
├─ foundations/ color, typography, spacing, radius, iconography
└─ components/
└─ esa-button.astro the one example component page