- Python 23.5%
- Cython 23.1%
- PHP 15.2%
- JavaScript 13.6%
- HTML 9.6%
- Other 15%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
Some checks failed
Security Scans / Gitleaks secret scan (push) Successful in 36s
Security Scans / Trivy filesystem scan (push) Successful in 46s
Copy coverage / Design copy reaching rendered HTML (push) Failing after 40s
UDG dist drift gate / dist/ matches dtcg + udg sources (push) Successful in 33s
Reviewed-on: #135 |
||
| .claude | ||
| .design-sync | ||
| .forgejo | ||
| .spel | ||
| .vale/styles/Rituals | ||
| Brand | ||
| components | ||
| dist | ||
| docs | ||
| generator | ||
| Media | ||
| out | ||
| templates | ||
| tools | ||
| udg | ||
| .gitattributes | ||
| .gitignore | ||
| .mcp.json | ||
| .pre-commit-config.yaml | ||
| .vale.ini | ||
| .yamllint | ||
| CLAUDE.md | ||
| CONTRIBUTING.md | ||
| justfile | ||
| pyproject.toml | ||
| README.md | ||
| toilville_design.code-workspace | ||
Toilville Design System
This repo is the canonical source of truth for Toilville's design tokens, brand assets, and generated design artifacts. Every Toilville frontend — Rituals Web, ToilMail, SPELWork sites — consumes outputs from here. It does not contain application code; it is a design governance repo.
All tokens are authored as DTCG JSON under Brand/UDG/ — color in color.json, and the six
non-color tiers (type, space, radius, border, shadow, motion) each in their own *.json file
(Brand/UDG/dtcg/ baseline + Brand/UDG/products/<p>/dtcg/ overrides). udg/generators/extract.py
fuses them and writes dist/<product>/. Downstream apps consume only the generated artifacts —
never re-author values in consumer repos.
The flat-YAML tier files under
udg/baseline/tokens/*.ymlare not the CSS/native source of truth —extract.pyreads the DTCG JSON above. Today only the WordPress-theme generator (udg/generators/wp_theme.py) reads that yml, and onlyspace.yml+type.yml;border/radius/shadow/motion.ymlare unread legacy pending a delete decision.
See Brand/UDG/README.md for color governance policy and DTCG format detail.
Pipeline at a glance
Brand/UDG/dtcg/color.json ← AUTHORED (baseline color, all themes, ARGB)
Brand/UDG/products/<p>/dtcg/color.json ← AUTHORED (product color, multi-theme)
Brand/UDG/dtcg/{typography,space,radius,border,shadow,motion}.json ← AUTHORED (6 non-color tiers, DTCG)
Brand/UDG/products/<p>/dtcg/<tier>.json ← AUTHORED (per-product non-color overrides, optional)
udg/products/<p>/meta.yml ← AUTHORED (product config)
udg/products/<p>/ux.yml ← AUTHORED (adaptive shell + feature placements, optional)
udg/config/surface_profiles.yml ← AUTHORED (shared adaptive shells / regions)
↓ udg/generators/extract.py (just build / just check)
dist/<p>/skin.css ← GENERATED — never hand-edit
dist/<p>/tokens.dtcg.json ← GENERATED — never hand-edit
dist/<p>/artifact.html ← GENERATED — interactive design spec
dist/<p>/artifact.bbs ← GENERATED — 80-col ANSI terminal spec
dist/<p>/artifact.sms ← GENERATED — terse text spec
↓
App consumers (Toilville_Rituals, rituals-web, etc.)
just check is the drift gate — it fails if dist/ is stale. Run it before any commit that
touches token sources.
Part 1: Creating a new UDG product
1.1 Scaffold the product
just new <product>
Creates udg/products/<product>/meta.yml with draft defaults. Edit the four key fields:
| Field | Values | Guidance |
|---|---|---|
css_var_style |
short / canonical |
canonical for any app consumer (--udg-color-* names). short only for internal brand components (--primary, --background). |
color_inherits_baseline |
true / false |
false for standalone apps with their own palette (Rituals Web). true only for brand extensions of the Toilville baseline. |
status |
draft / candidate / confirmed |
draft stays local. confirmed means published in sync-manifest.yml. |
version |
semver | Bump in the same PR as any token change — the registry rejects duplicate versions. |
If the product has adaptive UX rules, add:
ux_manifest: ux.ymldefault_surface_profile: <shared-profile-id>nominal_shell: <shell-kind>
and create udg/products/<product>/ux.yml to bind product features into the
shared regions declared in udg/config/surface_profiles.yml.
1.2 Author color tokens
If the product needs colors distinct from the baseline, create:
Brand/UDG/products/<product>/dtcg/color.json
DTCG format — every token carries:
{
"color": {
"brand": {
"primary": {
"$type": "color",
"$value": {
"light": "#FFCC5C37",
"dark": "#FFCC5C37",
"oldschool": "#FFB84B2A"
},
"udg.css-var": "--udg-color-brand-primary",
"udg.dart-key": "brandPrimary"
}
}
}
}
Key rules:
- Alpha is the first two hex digits in
#AARRGGBBformat.FF= fully opaque. Use< FFonly for overlay tokens (e.g. scrim). lightis always required. Adddark,oldschool, or any named themes as needed.- Set
color_inherits_baseline: falseinmeta.ymlwhen the product is self-contained (no merge with the baseline grammar). - The baseline taxonomy is
brand.*,bg.*,text.*,border.*,accent.*,safety.*,status.*. Mirror this hierarchy for new products that extend the baseline.
If color_inherits_baseline: true, the generator merges baseline ⊕ product dtcg. Product tokens
win on conflict. Only add what diverges from baseline.
1.3 Override non-color tiers (optional)
For tier overrides, create Brand/UDG/products/<product>/dtcg/<tier>.json (DTCG format) with only
the keys that differ from Brand/UDG/dtcg/<tier>.json. extract.py merges baseline → product.
(The udg/baseline/tokens/*.yml files are not read here — see the note at the top of this README.)
Example — a product that uses rounder corners:
// Brand/UDG/products/rituals/dtcg/radius.json
{
"radius": {
"lg": { "$type": "dimension", "$value": "24px", "udg.css-var": "--radius-lg" },
"full": { "$type": "dimension", "$value": "9999px", "udg.css-var": "--radius-full" }
}
}
All other radius keys (sm, md) inherit from baseline.
1.4 Build and verify
just build-one <product> # writes dist/<product>/skin.css + tokens.dtcg.json
just check # drift gate — exit 1 if dist/ is stale
just artifact <product> # writes dist/<product>/artifact.html (interactive spec)
just bbs <product> # writes dist/<product>/artifact.bbs (80-col ANSI)
just sms <product> # writes dist/<product>/artifact.sms (terse text)
just serve # http://localhost:8080 to browse dist/
Open dist/<product>/artifact.html in a browser to verify the token values, theme switcher,
and design board before declaring the product confirmed.
1.5 Register in sync-manifest
Add to udg/config/sync-manifest.yml:
products:
<product>:
version: 0.1.0
skin: dist/<product>/skin.css
consumers:
- repo: spel/apps/<repo>
mode: package-bump # bumps the udg Dart package version
package_name: udg
pubspec_path: pubspec.yaml
- repo: Toilville_Rituals
mode: source-inject # writes a generated file into the consumer repo
kind: json-tokens-macos
output: apps/rituals-macos/Rituals/Resources/udg_tokens.json
Delivery modes:
package-bump— consumer uses a hostedudgDart package. This bumps the version constraint inpubspec.yaml. Used byrituals-web.source-inject— this repo writes a generated JSON file directly into the consumer repo. Used by macOS (Swift) targets that cannot import a Dart package.
Version gate: bump version in the same PR as any token change. The registry rejects
duplicate versions — a forgotten bump fails publish and is the intended gate.
1.6 Document theme divergence
Every named theme beyond light and dark needs a rationale. Until RATIONALE.yml is
formalized, document divergence in two places:
- The
$descriptionfield indtcg/color.json - The PR description — why the default themes don't work, who approved, what the contrast target is for the new theme
Part 2: Forge pipeline — actor & tool schema
This section covers how to register a new system actor so its tools are discoverable via
ritual query "...". Every registered actor contributes ritual blocks to the forge registry
and appears in the design artifact for its UDG product.
2.1 The four required tables
core.mcp_servers the process: command, transport (stdio), consent zone
core.mcp_tools the tools: name, params schema, consent zone
core.actors the system actor: name, type, mcp_server reference
core.intents natural language → tool invocation mapping
Optional: ux.frames + ux.tool_modalities for modality-aware UX presentation (Part 3.4).
2.2 Step 1: MCP server
.mcp.json in the project root (Claude Code auto-discovers this):
{
"mcpServers": {
"<server_name>": {
"command": "python3",
"args": ["path/to/mcp_server.py"],
"cwd": "/absolute/path/to/project"
}
}
}
mcp_server.py using FastMCP:
from mcp.server.fastmcp import FastMCP
from typing import Any, Optional
mcp = FastMCP("<server_name>", "<one-line description>")
@mcp.tool()
def my_tool(param: str, optional: Optional[str] = None) -> dict[str, Any]:
"""Tool description — this becomes the forge registry description."""
return {"result": ..., "param": param}
if __name__ == "__main__":
mcp.run()
Conventions:
- Docstring → forge
description. Type hints →params_schema. Returndict[str, Any]. - Tier the tools by mutation risk (UDG convention; apply to any server):
- Tier 0 Discovery: list, meta, manifest — no side effects
- Tier 1 Inspection: read tokens, themes, CSS vars
- Tier 2 Validation: check drift, validate names
- Tier 3 Build: writes to
dist/ - Tier 4 UX output: generates artifact HTML from forge DB + tokens
2.3 Step 2: Four migrations
Create numbered migration files under ~/.spel/migrations/. Each must insert a record into
public.schema_migrations and optionally core.schema_migrations.
Migration 1 — core.mcp_servers:
INSERT INTO core.mcp_servers (server_name, display_name, transport, endpoint, consent_zone, active)
VALUES ('<name>', '<display>', 'stdio', 'python3 /abs/path/to/mcp_server.py', 'Z1', true)
ON CONFLICT (server_name) DO NOTHING;
Migration 2 — core.mcp_tools (one row per @mcp.tool()):
INSERT INTO core.mcp_tools (tool_name, server_name, display_name, description, params_schema, consent_zone)
VALUES (
'my_tool', '<server_name>', 'My Tool', 'What it does',
'{"type":"object","properties":{"param":{"type":"string"}},"required":["param"]}'::jsonb,
'Z1'
)
ON CONFLICT (tool_name, server_name) DO NOTHING;
Migration 3 — core.actors:
INSERT INTO core.actors (actor_name, actor_type, attributes, active)
VALUES (
'<actor-name>', 'system',
'{"description":"...","zone":"Z1","category":"tool","mcp_server":"<server_name>"}'::jsonb,
true
)
ON CONFLICT (actor_name) DO NOTHING;
Migration 4 — core.intents (one row per tool, plus aggregate aliases):
INSERT INTO core.intents (
intent_name, intent_type, executor, execution_config,
zone, description, aliases, priority, status
)
VALUES (
'<prefix>_my_tool',
'query', -- 'query' for reads, 'mutation' for writes, 'action' for external effects
'ritual_executor',
'{"invocation":"invoke_mcp_tool","server_ref":"<server_name>","tool_name":"my_tool","params_template":{"param":"","optional":null}}'::jsonb,
'Z1',
'What this intent does in one sentence',
'[{"alias":"natural language phrase","weight":1.0},{"alias":"shorthand","weight":0.85}]'::jsonb,
50,
'candidate'
)
ON CONFLICT (intent_name, scope_type) WHERE scope_id IS NULL DO NOTHING;
execution_config shape — the only shape the ritual executor understands:
{
"invocation": "invoke_mcp_tool",
"server_ref": "<server_name>",
"tool_name": "<tool_name>",
"params_template": {
"required_param": "",
"optional_param": null
}
}
Empty string = required parameter, filled by the ritual matcher. null = optional, passed
only if provided.
ON CONFLICT DO NOTHING pitfall: if a prior migration created the row empty, the config
won't be applied. Use ON CONFLICT DO UPDATE SET execution_config = EXCLUDED.execution_config
instead, or write a follow-up UPDATE migration. This burned us in migration 0101 — see 0105
for the backfill pattern.
Alias weight guidelines:
1.0— exact canonical phrase (udg list products)0.9— close synonym (list udg products,show products)0.85— shorthand or informal (udg products)
2.4 Step 3: Verify
ritual query "list <server_name> tools"
ritual query "<natural language alias>"
If routing fails, check in order:
- Intent
status=candidateoractive(notdraft) execution_config->>'server_ref'matchescore.mcp_servers.server_nameexactlytool_namematchescore.mcp_tools.tool_nameexactly (case-sensitive)aliasesJSON is valid (parse it withpsql -c "SELECT aliases::jsonb FROM core.intents WHERE intent_name='...'")
2.5 Optional: UX presentation layer
Register modality frames so the artifact generator can scope tools per platform:
-- Declare the canvas type (run once per modality)
INSERT INTO ux.modalities (modality_id, display_name, wf_atom, description)
VALUES ('flutter', 'Flutter', 'phone', 'Flutter/iOS phone canvas')
ON CONFLICT (modality_id) DO NOTHING;
-- Link actor + modality + udg_product
-- WHERE EXISTS guard is required: actor must be registered first
INSERT INTO ux.frames (frame_id, actor_name, modality_id, display_name, udg_product, active)
SELECT '<actor>-flutter', '<actor>', 'flutter', '<Product> Flutter', '<udg_product>', true
WHERE EXISTS (SELECT 1 FROM core.actors WHERE actor_name = '<actor>');
The artifact generator walks ux.frames to resolve the udg_product for token loading,
then queries ux.tools for tools in that frame.
Part 3: Ritual blocks
3.1 What a ritual block is
A ritual block is a core.intents row with execution_config->>'server_ref' pointing at
an MCP server. In the design artifact, each ritual block becomes a card on the Design tab —
showing intent name, type badge, description, execution config shape, and natural language aliases.
Ritual blocks are the forge's UI primitive: they represent what a system can do expressed in natural language. The artifact surfaces them as design reference — not interactive runtime buttons.
3.2 Intent types → visual representation
intent_type |
Badge color | Meaning |
|---|---|---|
query |
blue | Read-only, no side effects — safe to invoke any time |
mutation |
amber | Writes to dist/, DB, or filesystem — requires confirmation |
action |
red | External side effects (file a wish, push a notification) |
form |
purple | Collects structured input before invoking |
ritual |
teal | Multi-step SPELWork ceremony (Wish → Ruse → Ritual → Rubric) |
Rule: every intent that writes must have intent_type = 'mutation'. Never mark a write
as query — the artifact uses the type to render the appropriate confirmation affordance.
3.3 Authoring a new ritual block
To add a new capability to the forge:
- Add
@mcp.tool()tomcp_server.py— this is the implementation. - Add a
core.mcp_toolsrow in a migration. - Add a
core.intentsrow with the correctintent_type, readablealiases, andexecution_configpointing to the tool. - Run
just artifact <product>to regenerate the design artifact with the new ritual block.
3.4 Surfacing in artifact.html
The Design tab is built at artifact generation time. The generator queries:
SELECT intent_name, intent_type, description, execution_config, aliases
FROM core.intents
WHERE execution_config->>'server_ref' = '<server_ref>'
ORDER BY intent_type, intent_name
Results are embedded statically — the artifact does not query the DB at runtime. To pick up
new or updated intents, re-run just artifact <product> or ritual query "udg build artifact <product>".
The bbs and sms channel artifacts are similarly re-generated on each just bbs/just sms run.
Part 4: Accessibility & ARIA
No formal accessibility documentation existed in this repo before this document. These guidelines apply to every artifact in the Toilville pipeline: HTML/web, Flutter, Swift/SwiftUI, WordPress, CLI.
4.1 Color and contrast
WCAG AA minimums (required for all products with status: confirmed):
| Element | Minimum ratio |
|---|---|
| Normal text (< 18px / < 14px bold) | 4.5:1 against background |
| Large text (≥ 18px / ≥ 14px bold) | 3:1 |
| UI components (buttons, inputs) | 3:1 |
Focus ring (--focus-ring) |
3:1 against any surface it appears on |
Token policy:
--focus-ring: #005FC8— do not suppress or override for aesthetic reasons. It must achieve 3:1 contrast against every surface (dark cocoa, warm peach, white).- Safety tokens (
--safety-allow,--safety-review,--safety-block) are supplementary. Always pair with text or an icon — never communicate tier status by color alone. - Consent tier accent colors (Observatory/Workshop/Pilgrimage/Sanctum/Federation) follow the same rule: color + text label, never color alone.
Per-theme contrast audit:
Every named theme in color.json must be independently contrast-checked. The accessiblealt
theme in Rituals is the WCAG AA reference baseline. When adding a new theme, verify these four
pairings at minimum:
text.primaryonbg.surfacetext.on-accentonbrand.primarytext.on-accentonbrand.secondary- Focus ring (
--focus-ring) onbg.surface
Use tokens.dtcg.json ($value.<theme> per token) for the hex values, then verify with
browser DevTools Accessibility panel or any WCAG contrast checker.
4.2 Typography
- Minimum body: 16px (
--font-size-body). Never go below 14px (--font-size-small) for non-decorative text. - Line height:
--leading-normal: 1.55for body — do not tighten below 1.5. - Weight as the only differentiator is forbidden. Weight extremes (200 vs 800) are brand style, not accessibility tools — always pair with size or color changes for visual hierarchy.
- Negative tracking on body text is forbidden.
--tracking-displayand--tracking-headingare for headings only. - Touch targets: minimum 44px (
size.touch.target.min) for all interactive elements. Text links inside paragraphs are exempt. Buttons, tabs, chips, and form controls are not.
4.3 Motion
The motion tier defines durations and easings. Under prefers-reduced-motion: reduce, all
durations must collapse to near-zero:
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
This block must appear in every production stylesheet that consumes skin.css. The artifact
generator does not yet emit it — add it in your app's global CSS reset.
Staggered entrance animations are the brand's primary motion pattern and the highest-risk affordance for users with vestibular disorders. Apply them only to non-critical decorative elements; always test with reduced-motion enabled in OS settings.
4.4 ARIA patterns by component
| Component | Required ARIA |
|---|---|
| Icon-only button | aria-label="<action>" |
| Toggle / switch | role="switch" · `aria-checked="true |
| Tab list | role="tablist" on container · role="tab" + aria-selected per tab · role="tabpanel" + aria-labelledby per panel |
| Segmented control | Same as tab list |
| Theme switcher button | `aria-pressed="true |
| Modal / dialog | role="dialog" · aria-modal="true" · aria-labelledby → dialog title · focus trap on open, restore on close |
| Navigation | role="navigation" · aria-label="<section>" · aria-current="page" on active item |
| Form field | <label for="id"> (not aria-label as substitute) · aria-describedby → error/hint text |
| Inline error | role="alert" or aria-live="polite" — never color-only |
| Loading state | role="status" · aria-live="polite" · aria-busy="true" during load |
| Ritual block card | aria-label="<intent_name> — <intent_type>" on the card container |
Consent tier UI (Rituals-specific):
- Tier gate screen:
aria-describedbypointing to the explanation text for why the tier is required before the user sees the gated content. - Mode badges (Observatory / Workshop / etc.): the mode name must be in text content, not only expressed as an accent color swatch.
4.5 Per-platform guidelines
Flutter / Dart
- Wrap non-semantic widgets in
Semantics(label: '...', child: ...). excludeFromSemantics: truefor purely decorative elements (icon beside a labeled button, background art, shading dividers).- Never override
TextScaleFactor— respect the system setting. Verify layouts don't break at 200% text scale before any release cut. - Test with TalkBack (Android) and VoiceOver (iOS/macOS) before Rituals release.
- Token bindings in
packages/udgmapudg.dart-keykeys to token values. Use the key, notColor(0x...)— hardcoded values bypass the accessibility theme system. - The
accessiblealttheme is the WCAG AA palette for Rituals. Surface it in Settings under "High contrast" or equivalent.
Swift / SwiftUI
.accessibilityLabel("...")for all custom drawing and icon-only controls..accessibilityHint("...")for non-obvious affordances (swipe gestures, long press)..accessibilityElement(children: .ignore)to flatten compound decorative views..accessibilityAddTraits(.isButton)for tappable non-Buttonviews.- Token values arrive via
source-injectasudg_tokens.json— use theudg.dart-keyfield as the Swift dictionary key (matches the JSON structure).
WordPress / web
- Semantic HTML first:
<nav>,<main>,<section aria-label>,<header>,<footer>. ARIA supplements; it does not replace semantic elements. - Every
<img>:alt=""for decorative, descriptivealtfor informative. - Provide a skip-navigation link as the first focusable element on pages with navigation before main content.
skin.cssCSS custom properties work in all modern browsers without a polyfill.
CLI (SPELWork / forge tools)
- Error output → stderr. stdout must be clean for piping and screen reader pipelines.
- Never use ANSI color as the only differentiator — pair with prefixes:
[ERROR],[OK],[WARN]. Screen readers announce text, not color. - Support
NO_COLOR=1(the standard env var) to suppress all ANSI. The BBS artifact generator respects this via--no-color. ritual querysupports--jsonfor structured output — enables programmatic consumption by assistive technology pipelines.
4.6 Pre-release checklist
Before moving a product from status: candidate → status: confirmed:
- Contrast ratio verified for default (
light) theme — text, UI components, focus ring - Contrast ratio verified for every additional named theme
accessiblealt(or equivalent high-contrast) theme available- All interactive components keyboard-navigable (Tab, Enter/Space, Escape, Arrow keys)
- No interactive element communicates state by color alone
- Safety / consent tier UI pairs color with text label
prefers-reduced-motion: reducetested — no layout shift, content immediately visible- Focus ring visible at 3:1 against all surfaces in the product's themes
- Touch targets ≥ 44px for Flutter / mobile targets
- Screen reader tested (TalkBack + VoiceOver for Flutter; NVDA or VoiceOver for web)
Part 5: Text-channel modalities — BBS and SMS
The design artifact has two plain-text siblings for contexts where HTML isn't the right
channel. Both render the same ritual blocks as artifact.html but in different fidelity levels.
| Channel | Context | Width | Color | Fidelity |
|---|---|---|---|---|
bbs |
ritual-cli and ANSI-capable terminals |
80 cols fixed | ANSI 256-color | Full: box-drawing, grouped menus, product header |
sms |
SMS, webhook payloads, notification APIs | ≤ 320 chars | None | Minimal: numbered key:label list |
5.1 BBS format
Classic BBS aesthetic: 80-column fixed width, Unicode heavy box-drawing, ANSI 256-color
mapped from UDG tokens. Intents are grouped by intent_type into labeled menu sections.
Queries get numeric keys (1–9), mutations get letter keys (A–Z).
Reference output for Rituals:
╔══════════════════════════════════════════════════════════════════════════════╗
║ RITUALS v1.1.0 · Universal Design Grammar · toilville forge ║
╠══════════════════════════════════════════════════════════════════════════════╣
║ QUERY ║
╟──────────────────────────────────────────────────────────────────────────────╢
║ [1] list products List all products with metadata ║
║ [2] get product meta Full metadata for a product ║
║ [3] list themes Available themes for a product ║
╠══════════════════════════════════════════════════════════════════════════════╣
║ MUTATION ║
╟──────────────────────────────────────────────────────────────────────────────╢
║ [B] build product Build CSS + DTCG tokens for a product ║
║ [A] build artifact Generate interactive UX artifact (HTML) ║
╠══════════════════════════════════════════════════════════════════════════════╣
║ [Q] Quit · forge 15.835.0 · zone Z1 ║
╚══════════════════════════════════════════════════════════════════════════════╝
Canonical ANSI 256 color mapping from UDG tokens:
| UDG token | Hex | ANSI 256 |
|---|---|---|
brand.primary (terracotta) |
#C05734 |
166 |
brand.secondary (mustard) |
#E1B659 |
178 |
brand.background (cocoa) |
#471F12 |
52 |
text.primary |
#FFFFFF (dark mode) |
255 |
safety.allow |
#155724 |
28 |
safety.review |
#78350F |
130 |
safety.block |
#7F1D1D |
88 |
NO_COLOR=1 or --no-color disables all ANSI escapes — the output is plain UTF-8 box-drawing
with no color, safe for logging and accessibility pipelines.
5.2 SMS format
≤ 320-char plain ASCII (2 SMS messages). No box-drawing, no ANSI:
RITUALS v1.1.0 [14 tools]
1:list 2:meta 3:themes 4:tokens 5:css-skin
6:drift-check 7:build 8:preview 9:build-all
B:artifact
Reply # to invoke. Z1 zone req.
Format rules:
- Line 1:
<PRODUCT_ABBREV> v<version> [<tool_count> tools] - Body lines:
<key>:<shortest-alias>pairs, ≤ 8 per line, alias truncated at 10 chars - Queries → numeric keys (1–9 cycling), mutations → letter keys (A–Z cycling)
- Final line: reply instruction + consent zone abbreviation
5.3 Per-product house style
Each product can define udg/products/<product>/bbs-style.yml:
# bbs-style.yml — text-channel house style
bbs:
header: |
╔══════════════════════════════════════════════════════════════════════════════╗
║ RITUALS v{version} · Universal Design Grammar · toilville forge ║
╚══════════════════════════════════════════════════════════════════════════════╝
color_primary: 166 # ANSI 256 — brand.primary
color_secondary: 178 # ANSI 256 — brand.secondary
color_surface: 52 # ANSI 256 — brand.background
border_style: heavy # 'heavy' (╔╗║╚╝═) or 'light' (┌┐│└┘─)
shade_char: "░" # decorative fill character: ░ ▒ ▓
sms:
product_abbrev: RITUALS # ≤ 10 chars, all-caps
key_style: numeric # 'numeric' (default) or 'alpha'
{version} in the header interpolates from meta.yml. If no bbs-style.yml exists, the
generator uses a neutral default: light borders, no ANSI color.
Toilville house convention:
- Product headers → heavy box-drawing (
╔═╗) - Section separators within a product → light box-drawing (
╟──╢) - Decorative fill in headers →
░for Rituals (warm grain),▓for dark/terminal products
5.4 Generating text artifacts
just bbs <product> # dist/<product>/artifact.bbs
just sms <product> # dist/<product>/artifact.sms
ritual query "udg bbs artifact <product>"
ritual query "udg sms artifact <product>"
Verification:
just bbs rituals | cat # should render ≤ 80-col, readable box-drawing
just sms rituals | wc -c # should be ≤ 320 characters
NO_COLOR=1 just bbs rituals # should emit clean UTF-8 with no escape sequences
Quick reference: commands
just build # regenerate all products
just build-one <product> # regenerate one product
just check # drift gate — CI equivalent
just artifact <product> [tab] # interactive HTML spec (flutter/swift/cli/wordpress/design)
just bbs <product> # 80-col ANSI terminal spec
just sms <product> # terse SMS/notification spec
just serve # http://localhost:8080
ritual query "udg list products"
ritual query "udg get tokens <product>"
ritual query "udg build artifact <product>"
ritual query "udg bbs artifact <product>"
ritual query "udg check drift"
ritual query "udg validate token <path>"
Quick reference: token tiers
| Tier | Authored in | CSS var pattern | Example |
|---|---|---|---|
| color | Brand/UDG/dtcg/color.json + Brand/UDG/products/<p>/dtcg/ |
--udg-color-* (canonical) / --primary (short) |
--udg-color-brand-primary |
| type | Brand/UDG/dtcg/typography.json |
--font-*, --font-size-*, --weight-*, --leading-*, --tracking-* |
--font-size-body: 16px |
| space | Brand/UDG/dtcg/space.json |
--space-1 … --space-8 |
--space-3: 16px |
| radius | Brand/UDG/dtcg/radius.json |
--radius-sm/md/lg/full |
--radius-md: 9px |
| border | Brand/UDG/dtcg/border.json |
--border-width, --border-width-emphasis |
--border-width: 1px |
| shadow | Brand/UDG/dtcg/shadow.json |
--shadow-sm/md/lg |
--shadow-md: 0 4px 12px ... |
| motion | Brand/UDG/dtcg/motion.json |
--duration-*, --ease-* |
--ease-standard: cubic-bezier(...) |
Space scale (px): 1→4 · 2→8 · 3→16 · 4→24 · 5→32 · 6→40 · 7→56 · 8→64
Further reading
Brand/UDG/README.md— color token format, DTCG spec, divergence policyudg/config/sync-manifest.yml— product versions and consumer routing.forgejo/workflows/udg-dist-check.yml— CI drift gate.pre-commit-config.yaml— local pre-commit hooksjustfile— all available tasks