Young's Mind
Implementation

Docs website redesign brief — docs.youngle.au

This is the design of record for docs.youngle.au. It covers content IA, layout, and branding for the gated Docs surface, and is the input Stage 5 of the vhost plan has been waiting on.

Docs website redesign brief — docs.youngle.au

This is the design of record for docs.youngle.au. It covers content IA, layout, and branding for the gated Docs surface, and is the input Stage 5 of the vhost plan has been waiting on.

Audience: operators and customers reading reference material. This is a documentation site, not a marketing site. Every rule below is chosen for findability and legibility of reference text, not for persuasion. Concretely: no hero, no feature grid, no pricing, no testimonials, no CTA banner, no "trusted by" logos. If a section of the site could plausibly appear on a brochure page, it does not belong here.

What this brief is not. It does not redesign Pulse (that spec is [pulse-homepage-design](pulse-homepage-design), and Pulse is the public surface). It does not redesign the fleet dashboard (that is [fleet-dashboard-design-of-record-2026-10-20](fleet-dashboard-design-of-record-2026-10-20)). It does not touch the Authelia gate (that is Sol's infra lane, already proven live). It specifies the shape and skin of the Docs site; it does not implement it.


0. What I measured before writing this

Everything below is measured against the running system, not assumed. Probes run 2026-10-21 from LAB against prod 161.33.71.67 and the LAB vault.

#FactMeasured valueHow
M1StackDocus 5.12.3, Nuxt 4.5.1, @nuxt/content 3.15.2, @nuxt/ui 4.10.0, Tailwind 4.3.3package-lock.json on prod
M2Content collections2 — docs (content/Docs/**, prefix /docs), pulse (content/Pulse/**, prefix /pulse)content.config.ts
M3Site content volume580 pages published — 250 Docs + 330 Pulse (.md + .mdx); .md only: 248 Docs + 327 Pulsefind content on prod
M4Vault volume1,806 .md under Docs/ (incl. 1,571 in 05-archive); 660 .md/.mdx reach the forge after archive + junk-dir exclusionfind on LAB vault + forge walk
M5Content depthmax 5 path segments under Docs/; 18 pages at depth 5, 54 at depth 4awk -F/ '{print NF-1}' on the prod tree
M6Nav defect — /docsaside renders 248 links; 331 on /pulselink count in served HTML
M7Nav defect — cross-section/pulse aside contains 1 /docs link; /docs aside contains 1 /pulse link (the section tabs)grep of served HTML
M8Gridlg:grid-cols-10; upstream UPage = left 2 / center 8 / right 2.nuxt/ui/page.ts
M9Sidebar overrideaside[data-slot=left] forced to span 3, div[data-slot=center] to span 7 — 3 + 7 + 2 = 12 in a 10-column gridapp/assets/css/sidebar.css
M10Primary token--ui-primary: var(--ui-color-primary-500) → emerald 500 = oklch(69.6% 0.17 162.48) = #00bc7dserved HTML :root
M11Neutralzinc; --ui-text-highlighted: var(--ui-color-neutral-900); --ui-text-muted: neutral-500; --ui-text-dimmed: neutral-400served HTML
M12Fontsystem stack, no webfont: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, …built CSS --font-sans
M13Type scaleNuxt UI: text-3xl sm:text-4xl page H1, text-pretty on headings, text-sm navserved HTML
M14Radius / container / header--ui-radius: .25rem · --ui-container: 80rem (90rem ≥1536px) · --ui-header-height: 4rembuilt CSS
M15Auth gatedocs.youngle.au → 302 → auth.youngle.au/?rd=…; nginx include snippets/authelia-forward-auth.conf; no auth_basic anywherelive curl + nginx -T
M16Doc-class coverage — vaultof the 213 Docs/ files that carry frontmatter (of 235 outside 05-archive): title: 212, status: 203, date: 198, author: 190, authority: 130, lane: 87, category: 56, description: 9frontmatter parse over LAB vault Docs/
M17Status vocabulary41 distinct status: values in Docs/: 33 authority, 29 verified, 24 finding, 16 research, 12 decision, 10 done, 8 design, 8 study, 7 proposal, 6 plan, 5 manual-copy, 4 verification, 3 implemented/superseded/design-for-review, + ~26 singletonsfrontmatter parse over LAB vault Docs/
M18Category vocabularycategory: is inconsistent — 23 research, 20 implementation, 9 fleet-ops, 2 security, 2 architecture; ~6% coveragegrep on vault
M19Longest doc namesmedusa-notification-channels-and-email-provider-2026-10-20 (58 chars)find on vault
M20Route collision/docs/agents serves AGENTS.md (the vault README) — title "Young's Mind Vault" — while SolOS/agents/ is at /docs/solos/agentslive curl on prod :8091
M21Storefront stacknuxt ^3.14.0 + @nuxt/ui ^2.18.0 (installed 2.22.3) at /home/ubuntu/builds/storefrontprod package.json
M22Colour contrastcomputed by rasterising the Tailwind v4 OKLCH ramps in a real browser canvas — see §4.4Chromium canvas probe
M23Frontmatter is stripped in transitthe forge deletes the entire YAML frontmatter block from every published page. Vault: 213 of 660 Docs/ files carry frontmatter; published: 0 — only the 39 generated index.md landings keep a title:build_site.py FRONT.sub("", text, count=1) + a parse of vault / forge output / repo content/
M24Site content is a moving targetthe publish lane pushes every 10 min. The prod tree was at 0b2eeb3 (250 Docs pages) when measured; LAB's solos-docs was already at d3e3419. Page counts in this brief are a snapshot of d3e3419, and will driftgit rev-parse on both ends

Four findings that change the design, stated plainly:

  1. The nav is broken in a way no visual redesign will fix. The aside on /docs renders 248 links and on /pulse 331 — one flat, fully-expanded tree. The cause is upstream Docus: useSubNavigation maps a route to a section by route.path.startsWith(item.path + '/'), and the merged navigation array's first matching root wins. With docs and pulse concatenated, /docs/... resolves correctly, but every level below it is flattened because UContentNavigation is given collapsible: false (DocsAsideLeftBody.vue). The 3/7 column override in sidebar.css then makes the grid 12 columns wide inside a 10-column grid (M9). A brief that only says "make it prettier" would ship the same unusable nav. §2.3 specifies the fix.
  2. The publish lane deletes the frontmatter before the site ever sees it. This is the single most consequential measurement in this brief, and it invalidates the obvious design (M23). The forge does FRONT.sub("", text, count=1) on every file — it removes the entire YAML block. Measured end to end:
    fileswith frontmattertitle:status:description:
    Vault (Docs + Pulse, excl. archive)660362 (54.8%)27531215
    Forge output /tmp/site_build58039 (6.7%)3900
    Published solos-docs/content/58039 (6.7%)3900

    The 39 survivors are the forge's own generated index.md landings, which are written after the strip. Scoped to Docs/ alone, 213 of 235 non-archive files carry frontmatter — so the loss is not a rounding error, it is the whole metadata surface. Every hand-authored page reaches the site with no frontmatter at all. A status badge, a date line, a lede, a per-section filter — none of it can be built on frontmatter today. It is all in the vault, and it is all deleted in transit. §3.1 specifies the frontmatter contract and the one-line forge change that makes it real; §6.1 sequences that change as step 0, because steps 6–8 all depend on it.
  3. Frontmatter coverage is uneven even before the strip. Only 9 Docs/ files carry a description: (M16) and status: has 41 distinct values (M17). So even after the strip is fixed, a design that renders a description under every page title will render an empty string on most pages. §3.1 specifies a frontmatter contract with a fallback, and states plainly which fields may be relied on.
  4. The brand green and the theme green are different greens. The logo green is #16a34a; the theme primary is emerald 500 #00bc7d (M10). #16a34a on white is 3.30:1 — it fails AA for body text and only just clears the 3:1 non-text threshold. The wordmark and the links are therefore not the same colour today, and the links are the wrong green for a reading surface. §4 resolves it: one accent, specified as a ramp with an explicit shade per role.

1. Content IA — the top-level sections

1.1 The rule that decides the IA

The site is a mirror of the vault tree, with one change: the vault's numbered folders become named sections.

The vault is organised by lifecycle stage (00-scope → 06-implementation), which is right for a repo and wrong for a reader, who asks "where is the thing I need?" The site reorganises the same content into eight named sections. The URL keeps the vault path (so existing links and the publish pipeline never break); the navigation and landing pages speak the reader's vocabulary.

This is a presentation-layer change. No content moves, no file is renamed, the forge is untouched, and the publish lane keeps working. §1.4 records this explicitly because it is the single most important scope statement in the brief.

1.2 The eight sections

#Section labelVault sourcePublished pagesWhat the reader finds there
1Start HereDocs/Fleet/manual/ + Docs/VAULT-INDEX.md + Docs/AGENTS.md10The operator's manual, entry point, and the manifest of what is current vs historical
2ProductDocs/Fleet/{00-scope,02-architecture,04-structure-snapshot}/ + Fleet/README.md26Scope, the design of record, the structure of record — what we are building
3ResearchDocs/Fleet/01-research/ + Docs/Projects/vhost/research/56Studies and third-party analysis — what we learned
4DecisionsDocs/Fleet/03-decisions/ + Docs/Projects/vhost/decisions/56Rulings and their reasons — what we chose and why
5Build & RunDocs/Fleet/06-implementation/ + Docs/Projects/vhost/implementation/46Plans, stage registers, provider builds — how it was built
6OperationsDocs/Fleet/05-operations/ + Docs/SolOS/45Runbooks, host topology, spend ledger — how it runs today
7ProjectsDocs/Projects/{youngle,yukonit}/5Per-client project trees
8Tenancy PlatformDocs/Projects/vhost/index.md1The vhost platform entry point

Counts are the published pages, measured on the content tree at solos-docs rev d3e3419 (2026-10-21 18:42), which mirrors prod. That tree holds 250 markdown files under content/Docs/: 26 are generated index.md landings and 224 are hand-authored documents. The eight sections address 245 of 250; the remaining 5 are the section landings themselves — Docs/index.md, Docs/Fleet/index.md, Docs/Projects/index.md, Docs/Kanban/index.md and Docs/Kanban/fleet-board.md — all handled explicitly in §1.5.

These counts will drift. The publish lane pushes every 10 minutes (M24); between measurement and this sentence the tree moved from 248 to 250 pages. The counts are a snapshot for sizing the navigation and the landing pages, not a number to assert later. The mapping rules in §1.4 and §2.5 are the durable part — they are what an implementer codes against.

Why eight and not four. The vault's four top-level folders (Fleet, Projects, SolOS, Kanban) are a storage taxonomy; a reader's taxonomy runs on the question being asked. Four vault folders also cut across the reader's questions in exactly the wrong way: Fleet/03-decisions and Projects/vhost/decisions are the same kind of thing (a ruling) in different places, and a reader looking for "decisions" must currently look in two unrelated branches of the sidebar. Merging them by kind rather than by owner is the whole point of the reorganisation. Eight is the smallest number where no section holds two unrelated kinds of document.

1.3 Nesting rules

  • Depth 1 — the eight sections. Always visible as the top-level list.
  • Depth 2 — the vault's subfolders (01-research, hosts, manual, …), rendered with their reader-facing name, not the raw folder name. The 00-scope → "Scope" mapping table is §2.5.
  • Depth 3 — individual documents, grouped under their depth-2 parent. Collapsed by default. This is the fix for M6. 249 always-expanded links is the reason the aside is unusable today.
  • Depth 4+ — the document itself, plus its headings. Never more than two levels of nav below depth 2.

Never expose a raw vault folder name to a reader. 00-scope, 01-research, 05-operations are repo hygiene; a reader needs "Scope", "Research", "Operations". §2.5 gives the full mapping.

1.4 What each existing vault doc class lands in

This is the acceptance criterion "where does each existing vault doc class land". Every class present in the vault, mapped to exactly one section:

Vault doc classDetected byLands inPublished pages
Scope, lanes, planFleet/00-scope/ + Fleet/README.mdProduct6
Design of record, architecture, structureFleet/02-architecture/, Fleet/04-structure-snapshot/Product20
Studies, third-party analysisFleet/01-research/, Projects/vhost/research/Research56
Rulings, decisions, proposalsFleet/03-decisions/, Projects/vhost/decisions/Decisions56
Implementation plans, stage registers, buildsFleet/06-implementation/, Projects/vhost/implementation/Build & Run46
Ops runbooks, audits, spendFleet/05-operations/Operations21
Host topology, agents, subscriptionsDocs/SolOS/Operations24
Operator manual, manifestFleet/manual/Start Here8
Vault indexVAULT-INDEX.mdStart Here1
Per-client treesProjects/youngle/, Projects/yukonit/Projects5
Platform entry pointProjects/vhost/index.mdTenancy Platform1
Board stubKanban/fleet-board.mdOperations1 — a stub, and the board truth is hermes kanban, not a docs page
Vault operating instructionsAGENTS.mdStart Here1 — reads as a page about the vault itself; right, but see M20
Research notes, briefs, podcastPulse/**not herePulse is the public surface; separate site, separate spec
Superseded, archivedFleet/05-archive/**not publishedAlready excluded by the forge; stays excluded

Note on the Board stub row. Kanban/fleet-board.md is listed under Operations, but Kanban/index.md is a generated landing and sits with the other section landings in the residual set of §1.2. The two are different kinds of file and are counted separately.

1.5 Deliberately absent, and why

Absence is load-bearing. Writing it down stops the next session rebuilding it.

Not publishedWhy
Fleet/05-archive/**Superseded by ruling. The forge already excludes it (v4). MANIFEST.md classifies what is current vs historical — that is the replacement for reading the archive.
Pulse/** on the Docs sitePulse is the single public surface (pulse.youngle.au). It has its own spec. Duplicating it inside the gate creates two homes for one fact.
Docs/Kanban/*.kanbanNot .md; Nuxt Content cannot parse them; the projector rewrites them every minute. Already excluded from publish. fleet-board.md is a stub and is retired.
SolOS/subscriptions/Empty directory. It is a declared home with no consumer. It renders as a nav node that leads nowhere. Gate it: it appears when the first subscription doc lands.
Raw AGENTS.md as a top-level pageIt collides at /docs/agents with SolOS/agents/ (M20). The page is real and useful — it is the vault's operating instructions — so it stays, but it moves under Start Here as "Vault Operations", and SolOS/agents/ keeps the /docs/solos/agents path. The route collision is a real defect today and must be fixed by the brief's implementer.
Any marketing surfaceAcceptance criterion 5. This is a reference site.

1.6 The landing pages

One landing page per section, at the section route. Each is a directory, not a feature grid — a reader scanning for a document needs a list, not tiles.

Structure of every section landing:

  1. H1 — the section name (Product, Decisions, …). One <h1> per page.
  2. One-line scope — what this section holds, in the reader's terms. ("Decisions — rulings, proposals, and the reasons behind them.")
  3. Subsection rows — one row per depth-2 group: name · one-line scope · page count →. A row, not a card. Five equal cards is a marketing layout; a directory row list carries hierarchy by content.
  4. Status filter note — where the section supports it, a one-line note that status: frontmatter is shown on document cards (§3.4).

The site landing (/docs) is the eight-section directory, in the order of §1.2, each row showing name · scope · count. Plus one "Start Here" call-out at the top, because "Start Here" is a reader's first move.


2. Layout — templates and navigation

2.1 Templates, as reusable patterns

Five templates. Every page on the site is one of these. A template is a pattern with named slots, not a one-off layout.

T1 — Reference page (the workhorse)

┌────────────────────────────────────────────────────────────────────┐
│ HEADER  [logo]  [Docs] [Pulse]        [search ⌘K]  [theme] [menu]  │
├──────────────┬──────────────────────────────────┬──────────────────┤
│ SECTION RAIL │  BREADCRUMB  Decisions › Medusa │  ON THIS PAGE    │
│  ▸ Product   │  H1  Medusa notification …      │  Decision         │
│  ▸ Research  │  ─────────────────────────────  │  Channels         │
│  ▾ Decisions │  STATUS  decision · 2026-10-20  │  Providers        │
│    Medusa    │  lede (1–2 sentences)           │  Open questions   │
│    Platform  │  ─────────────────────────────  │                  │
│  ▸ Build     │  body — max 72ch                │  EDIT / REPORT    │
│  ▸ Operations│  ...                           │  ──────────────── │
│  ...         │  ─────────────────────────────  │  ← Prev   Next →  │
│              │  [Related documents]            │                  │
└──────────────┴──────────────────────────────────┴──────────────────┘
   2/10 cols         6/10 cols                          2/10 cols
  • Rail — the eight sections. Depth-2 groups under the active section only. Collapsed by default. This is the M6 fix.
  • Centre — breadcrumb, H1, status line, lede, body, related.
  • Right — on-this-page TOC (H2+ only), edit/report links, prev/next.
  • Body measure — max-width: 72ch. Long lines are the single most common readability defect in a docs site and the default prose width is not it.
  • Right rail hides below lg. Below that, the TOC moves into a drawer (T1-mobile, below).

T2 — Section landing

BREADCRUMB  Docs › Decisions
H1  Decisions
lede  Rulings, proposals, and the reasons behind them.
─────────────────────────────────────────────────────
▸ Medusa notification channels   [12 docs]  →
▸ Platform architecture           [ 8 docs]  →
▸ Marketing & social add-on       [ 6 docs]  →

Rows, not cards. Row = border-top 1px --border, padding 16px 0, name (600) · scope (muted, flex-1) · count (mono, dimmed) · arrow. Hover: background --surface-2, arrow nudges 2px.

T3 — Directory index (the site landing, and every "all documents" view)

Same row pattern as T2, one level deeper. Adds a right-aligned count and a status: badge where present. Used at /docs and for any group with > 20 documents.

T4 — Document card (used in lists, never as a page)

┌──────────────────────────────────────────┐
│ DECISION · 2026-10-20                     │
│ Medusa notification channels and …       │
│ Two sentences of what this document       │
│ decides.                                  │
│ ────────────────────────────────────────  │
│ 12 docs in this group         →           │
└──────────────────────────────────────────┘

bg --surface, border 1px --border, radius --r-md, padding 24px. Label = mono, uppercase, 12px, dimmed. Title = 1.05rem/600, clamped to 2 lines. Hover: border → --border-strong, shadow → --shadow-lift, transform: translateY(-1px).

T5 — Operator dashboard (already designed — do not restate)

/dashboard is the fleet dashboard and is specified in [fleet-dashboard-design-of-record-2026-10-20](fleet-dashboard-design-of-record-2026-10-20). It lives in the same app and the same gate but uses a full-width layout with no sidebars — data panels must not compete with a navigation rail. This brief specifies only what the dashboard must borrow from the docs shell: the header, the token set (§4), and the footer. The dashboard's own panels, colour rules, and empty-state doctrine are in that document and are not restated here.

2.2 The header

Two rows, 112px total on desktop (64px header + 48px section bar) — the --ui-header-height: 112px override in app/app.vue already does this.

Row 1 (64px)

  • Left: logo mark (green brain SVG) + wordmark. The existing public/logo/light.svg / dark.svg at h-6 — already correct, do not redraw.
  • Centre: search trigger. ⌘K / Ctrl K badge. Opens the command palette.
  • Right: theme toggle, here only — the floating duplicate toggle in the bottom-right of the current build is a rendering artifact and must go. Then the mobile menu button.

Row 2 (48px) — the section bar. The eight sections as a horizontal UNavigationMenu, variant="pill". On /pulse this row is replaced by Pulse's own sections. Below lg, the whole bar collapses into the mobile drawer.

Sticky header must not cover a focused element. With a 112px sticky header, every heading anchor needs scroll-margin-top: calc(var(--ui-header-height) + 16px). The current build already does this for H1 (scroll-mt-[calc(45px+var(--ui-header-height))]) — extend it to H2/H3, which the current build does not do.

2.3 The navigation model — and the fix for M6

Target model (three levels, collapsed by default):

Sections (8, always visible)
└── Depth-2 groups (only under the active section)
    └── Documents (collapsed by default; expand on click)

The defect, stated exactly. node_modules/docus/app/composables/useSubNavigation.ts:

const sidebarNavigation = computed(() => {
  if (subNavigationMode.value && currentSection.value) {
    return currentSection.value.children || []
  }
  return navigation?.value || []
})

and currentSection finds the first root whose path prefixes the route. With the merged [...docsNav, ...pulseNav] array, /docs/x matches the Docs root correctly. But DocsAsideLeftBody.vue renders <UContentNavigation :collapsible="false" …> — collapsible: false is the bug. It forces every one of the 249 depth-3 documents to render expanded, in one flat list, with no way to collapse a group.

The fix, in the order it must be applied:

  1. Set collapsible to true on the aside's UContentNavigation, and set default-open on the depth-2 level only. Docus ships DocsAsideLeftBody.vue as an upstream file — the repo already overrides upstream files (2-line diff in [...slug](...slug).vue per [docs-nav-v2](docs-nav-v2)), so a diff-sized override of this one is the established pattern here.
  2. Filter the navigation tree to the active section before rendering. The merged array must not reach the sidebar whole. Either filter in app.vue where navigation is provided, or use Docus's useSubNavigation currentSection and render currentSection.children explicitly rather than sidebarNavigation.
  3. Keep the section bar from the merged roots — that is what transformNavigation in app/app.vue already preserves, and the /docs + /pulse tabs must keep working (M7 shows they do).

Verify with a count, not an eyeball. The acceptance probe is: the aside on any page renders ≤ 40 links (8 sections + ~10 groups + a collapsed group's title), not 249. After expanding one group it renders that group's documents only.

2.4 The grid — fixing M9

The current app/assets/css/sidebar.css forces:

aside[data-slot=left] { grid-column: span 3 / span 3 !important; }
div[data-slot=center] { grid-column: span 7 / span 7 !important; }

inside a lg:grid-cols-10 grid. 3 + 7 + 2 = 12 columns in a 10-column grid. The right rail (TOC) is pushed out of the grid, and the layout overflows its own container. The override was a fix for a real problem (deep labels truncating to "C…" — Young's 2026-10-15 readability ruling) and it solved it by breaking the grid.

The correct fix — 3 / 5 / 2, summing to 10:

SlotUpstreamTargetReason
Left (rail)23Keep Young's ruling — deep labels need the width
Centre (body)85With the nav collapsible, the centre no longer needs 8
Right (TOC)22Unchanged

Implementation: override the UPage theme slots rather than the raw grid classes, so the arithmetic stays visible in one place:

// app/app.config.ts
ui: {
  page: {
    slots: {
      root: 'flex flex-col lg:grid lg:grid-cols-10 lg:gap-6',
      left: 'lg:col-span-3',
      center: 'lg:col-span-5',
      right: 'lg:col-span-2 order-first lg:order-last',
    },
  },
}

Then delete the !important grid override from sidebar.css and keep only the two rules that were the actual intent:

/* sidebar.css — keep these two, drop the grid-column overrides */
aside[data-slot="left"] ul[data-slot="root"] { margin-inline-start: 0.75rem !important; }
aside[data-slot="left"] [data-slot="linkLabel"] {
  white-space: normal !important;
  overflow: visible !important;
  display: -webkit-box !important;
  -webkit-line-clamp: 2 !important;
  -webkit-box-orient: vertical !important;
}

Do not remove the readability rules. The nav must fit the 3-column rail with two-line labels; that was Young's ruling and it stands. What changes is that the rail is now 3 of 10 with a 5-column body, not 3 of 10 with a 7-column body in a 10-column grid.

Body measure. The centre column at 5/10 of an 80rem container minus gaps is ~35rem. Prose inside it is capped at max-width: 72ch, centred or left-aligned within the column. Do not let the body text stretch to the full column.

2.5 Vault folder → reader label

Every raw folder name the reader would see, and what it becomes:

Vault folderReader labelSection
Fleet/manual/Operator's ManualStart Here
Fleet/00-scope/Scope & LanesProduct
Fleet/02-architecture/ArchitectureProduct
Fleet/04-structure-snapshot/StructureProduct
Fleet/01-research/Fleet ResearchResearch
Projects/vhost/research/Platform ResearchResearch
Fleet/03-decisions/Fleet DecisionsDecisions
Projects/vhost/decisions/Platform DecisionsDecisions
Fleet/06-implementation/Fleet BuildBuild & Run
Projects/vhost/implementation/Platform BuildBuild & Run
Fleet/05-operations/OperationsOperations
SolOS/hosts/HostsOperations
SolOS/agents/AgentsOperations
SolOS/subscriptions/(gated — empty)Operations
Projects/youngle/Young LeProjects
Projects/yukonit/Yukon ITProjects
Projects/vhost/vHost PlatformTenancy Platform
Kanban/(not published)—
AGENTS.mdVault OperationsStart Here

The mapping lives in one place: a LABEL dictionary in the forge (build_site.py), alongside the existing PRETTY dictionary that already does exactly this job for solosv3, 00-scope, 01-research, etc. One dictionary, one owner, no second copy of the naming.

2.6 Mobile

BreakpointBehaviour
≥ 1024pxFull 3 / 5 / 2 grid, section bar visible, rail + TOC both visible
768–1023pxRail collapses into the left drawer (Docus's DocsAsideMobileBar already does this); TOC moves to a right drawer; body full width
< 768pxSingle column. Section bar → drawer. TOC → drawer. Body measure 72ch (≈ 45 characters at 16px, comfortable).

Touch targets ≥ 44px. Drawers get overscroll-behavior: contain. The page keeps overscroll-behavior: none on &lt;html&gt; under @media (pointer: fine) only, so mobile pull-to-refresh still works.

--surface-2 band, padding 64px 0. Left: wordmark + one line ("SolOS operations and product documentation"). Right: the eight sections as a compact link list, grouped in two columns. Bottom row: © 2026 Young's Mind · the gate note ("Internal — behind Authelia") · last-publish date.

The gate note is deliberate. A reader who screenshots a page and sends it to someone without access should know it is not public. pulse.youngle.au is the public surface; docs.youngle.au is not.


3. Document-level conventions

3.1 The frontmatter contract

The site reads frontmatter from the markdown. Two things must be true for that to work, and today only one of them is.

First: the frontmatter has to survive the publish lane. It does not (M23). The forge strips it:

# /home/lab/.solos/operation/ops/build_site.py
FRONT = re.compile(r"^---\n.*?\n---\n", re.S)
def conv(text):
    body = FRONT.sub("", text, count=1)   # <-- deletes the whole YAML block

The fix: split the file, transform the body only, and re-emit the frontmatter. The @nuxt/content v3 collections parse YAML frontmatter natively — the strip is not a Nuxt Content requirement, it is a leftover. Three constraints make the naive one-line change wrong:

  1. The wikilink rewrite ([label](x) → [label](x)) is a body transform. Run it over a file whose frontmatter holds a related: list of [wikilinks](wikilinks) and it rewrites those into Markdown links inside the YAML, which breaks the parse.
  2. 23 of the 213 files have malformed YAML — an unquoted colon in a value (lane: cost/footprint. Design: sol. Coordination: min.). Emitting those verbatim would make @nuxt/content throw on the page.
  3. The transform must be idempotent — the forge re-runs over the vault and a non-idempotent transform corrupts content on the second pass.

The following implementation was written and tested against all 235 Docs/ files before being put in this brief. It satisfies all three:

# /home/lab/.solos/operation/ops/build_site.py
import yaml

FRONT = re.compile(r"^(---\n)(.*?)\n---\n", re.S)

def _fm_ok(fm_text: str) -> bool:
    """True only if the block is a parseable, non-empty YAML mapping."""
    try:
        d = yaml.safe_load(fm_text)
    except Exception:
        return False
    return isinstance(d, dict) and bool(d)

def conv(text: str, src: str = "?") -> str:
    m = FRONT.match(text)
    if not m:
        return body_transforms(text)
    fm_text, body = m.group(2), text[m.end():]
    body_out = body_transforms(body)
    if not _fm_ok(fm_text):
        # Malformed YAML: strip it (as today) and name the file, so the
        # vault owner fixes it. Never emit unparseable frontmatter.
        print(f"[build_site] WARNING: unparseable frontmatter, stripped: {src}",
              file=sys.stderr)
        return body_out
    return "---\n" + fm_text.rstrip() + "\n---\n\n" + body_out

body_transforms() is the existing body-only transform chain, unchanged.

Measured result of that change, over all 235 Docs/ files:

CheckResult
Frontmatter preserved190 (all valid YAML; 189 carry a title:)
Stripped with a warning (malformed YAML)23 — every one has a usable H1 title
Files with no frontmatter in the vault22
Malformed YAML emitted as frontmatter0
Frontmatter wikilinks rewritten0 (correct — they are data)
Body wikilinks left unrewritten0
Not idempotent0
Body text lost0

Verify with a count, not an eyeball. After the change, the published tree must have frontmatter on ~190 of 235 Docs/ files, not 39 of 250. That is the acceptance probe, and it is one grep on the repo:

grep -rl '^title:' --include=*.md content/Docs | wc -l   # expect ~190, was 18

Second: the vault's frontmatter is inconsistent (M16–M17), so the contract below is what the site reads, and what happens when it is missing. It is written to be safe on today's data — i.e. before the vault is cleaned up.

FieldUsed forIf missing
titlePage H1, nav label, &lt;title&gt;Fall back to the first H1 in the body, then the filename, title-cased
descriptionPage lede, meta description, search resultEmpty — no lede block, no meta description. Do not synthesise from body text
statusStatus badge on the page and in listsNo badge. Do not show "unknown"
dateStatus line, sort keyOmit the date, show only the status
author / lanePage footer metaOmit the block
tagsNot rendered—

title needs a fallback chain, not a filename guess. With frontmatter restored, 212 of 213 Docs/ files have a title: (M16) — but the fallback still matters for the 23 malformed-YAML files and the 22 with no frontmatter at all. Order: frontmatter title → first H1 in the body → filename, title-cased. The H1 step is not decoration: most vault documents begin with a proper # Heading that is a better title than the filename ever would be (SCOPE.md → "SolOS v3 — scope", not "Scope"). All 23 malformed files were checked and all 23 have a usable H1.

status: is the one field worth investing in — 203 Docs/ files carry it (M16), and it is the only metadata that tells a reader whether a document is current. §3.2 specifies the badge mapping.

Do not render category: — it is inconsistent (M18: 23 research, 20 implementation, 9 fleet-ops, 2 security, 2 architecture, ~6% coverage — 56 of 235 files). Fix the vault first or ignore the field. Rendering an inconsistent field produces an unpredictable site.

Do not render related: as a visible list. It is a wikilink list, and [X](x) inside YAML has no valid YAML value. The forge's wikilink rewrite does not apply to frontmatter (that is the point of the body split above), so related: stays as raw [wikilinks](wikilinks) — valid as data for a future "related documents" feature, invalid as a rendered link. §2.1's [Related documents] slot is populated from the nav tree, not from related:.

Do not render authority: as a badge. It is a governance field (whose ruling this is) with 130 occurrences, and it does not map to the four-state reader model in §3.2. It belongs in the page footer meta, if anywhere.

3.1a The 23 malformed frontmatter files

The malformed files are a vault defect, not a site defect, and the fix belongs to the vault owner. Each has an unquoted colon inside a value — lane: cost/footprint. Design: sol. Coordination: min. — which is valid in a note-taking app and invalid in YAML. Quoting the value fixes it:

# before
lane: cost/footprint. Design: sol. Coordination: min.
# after
lane: "cost/footprint. Design: sol. Coordination: min."

The 23 files are listed in §7 as open item O8. Until they are fixed, the site still renders them correctly (frontmatter stripped, H1 title used), so this is not a blocker for the build — it is a blocker for the metadata features in §3.4.

3.2 The status vocabulary

The vault uses 30+ distinct status: values (M17). 13 cover 190 files; the rest are singletons. The site maps them to four badges, so the reader learns four states, not thirty:

BadgeVault status: valuesColour
Liveauthority, live, implemented, done, verified, verification, manual-copy--ok (green)
Designdesign, design-for-review, proposal, plan, implementation-plan, study, research, draft--info (blue)
Supersededsuperseded, retired--muted, strikethrough-free, with an explicit "Superseded" word — never colour alone
Workingin-progress, in-progress-nova-lane, draft-for-team-review, draft-for-sol, report-to-coordination, pending, open, in-progress--warn (amber)

Every unmapped value falls back to "Design" — never to "Live", and never to a blank. A doc with an unknown status is more likely a draft than a shipped fact, and a site that guesses "Live" is lying.

Status is shown with a word, not colour alone (WCAG 1.4.1). The badge reads "Live", not a green dot.

3.3 Body typography

The body is the product. Rules:

  • Measure — max-width: 72ch on prose blocks.
  • Line height — 1.7 for body, 1.15–1.2 for headings. Docus's default prose is 1.6–1.7; do not compress it.
  • Code — mono, font-variant-ligatures: none. Never below 0.875rem.
  • Tables — full width of the body column, font-variant-numeric: tabular-nums on numeric cells. Many vault tables are cost/port tables where column alignment is the whole point.
  • Long tokens — overflow-wrap: anywhere on code and table cells. Vault doc names reach 58 characters (M19) and container/port IDs are longer.
  • Headings — text-wrap: balance; anchors on H2/H3 with scroll-margin-top: calc(var(--ui-header-height) + 16px).
  • Ellipsis — …, never .... Curly quotes “ ”, never straight.
  • Loading — "Loading…", "Saving…", never "Loading...".

3.4 The status block

Below the H1 and above the lede, on documents that have both a status: and a date::

DECISION · 2026-10-20 · Nova

Mono, uppercase, 12px, letter-spacing: 0.08em, --ink-3. This is the one place the vault's own metadata becomes visible, and it does the job the MANIFEST.md classification does on paper.

On section landings and list views, a document row carries a small status badge on the right — same four-colour mapping, same "word not colour alone" rule.

3.5 What the page does not show

  • No "Last updated" that is not real. Nuxt Content exposes modifiedAt from the file mtime, which the publish lane resets. If a real last-updated is wanted, it must come from frontmatter date: — which is a document date, not an edit date. Do not label date: as "Last updated". Label it "Dated" or show it bare.
  • No reading-time estimate — it is wrong for reference material.
  • No social share row, no comments, no "Was this helpful?" — a docs site for a gated internal audience does not need them, and each is a component to maintain.

4. Branding

4.1 The one accent

One accent colour: the brand green. No second hue anywhere on the site. The brand is the green brain; a second accent would dilute it.

The problem: the logo green and the theme green are different greens (M10). The logo is #16a34a; the Docus theme's primary is emerald 500 #00bc7d. #16a34a on white is 3.30:1 — it fails AA for body text and barely clears the 3:1 non-text threshold. So today the wordmark and the links are different greens, and the links are too light for a reading surface.

Resolution: keep the logo exactly as it is (#16a34a is the identity and the mark is an SVG that can carry any colour), and re-key the theme to the same hue family with an explicit shade per role, so the accent is one green ramp, not two unrelated greens.

Decision: re-key the theme primary from emerald to green. Emerald 500 at #00bc7d is a teal-leaning green; the logo's #16a34a is a warmer, yellower green. They read as different brands side by side. Tailwind's green ramp is the closer match, and re-keying is a one-line change to app.config.ts.

// app/app.config.ts — the entire branding change
ui: {
  colors: {
    primary: 'green',   // was: 'emerald' (Docus default)
    neutral: 'zinc',    // unchanged — already correct
  },
}

This is the only change to the palette. Everything else in §4.2–4.4 is a specification of what the existing tokens must be, so the implementer does not have to re-derive them.

4.2 The colour ramp — role to shade, measured

Values from Tailwind v4's theme.css (read on prod), hex resolved and contrast computed by rasterising the OKLCH values in a real browser canvas (M22). Green 500/600/700 are the ramp the green key resolves to.

RoleTokenLightDarkContrast (light / dark)
Links, prose accents, active nav--ui-primary (= primary-500)#00c950#00c9502.22:1 ❌ / 8.97:1 ✅
Link hover, active nav underlineprimary-600#00a63e#00a63e3.22:1 ⚠️ / 6.18:1 ✅
Primary button fillprimary-600 (light) / primary-400 (dark)#00a63e#05df72white on 600 = 3.22:1 ⚠️
Primary button fill, text-safeprimary-700 (light)#008236—white on 700 = 4.95:1 ✅
Focus ringprimary-600#00a63e#05df723.22:1 ✅ (needs ≥3:1) / 11.20:1 ✅
Logo / wordmark#16a34a fixed#16a34a#16a34a3.30:1 (mark, not text)
Body ink--ui-text-highlighted = neutral-900#18181b#fff17.72:1 ✅
Secondary ink--ui-text-muted = neutral-500#71717b#71717b4.83:1 ✅ / 4.12:1 ⚠️
Tertiary ink--ui-text-dimmed = neutral-400#9f9fa9#9f9fa92.62:1 ❌ / 7.59:1 ✅
Page background--ui-bg = neutral-50 / neutral-950#fff#09090b—
Bordersborder-default = neutral-200 / neutral-800———

Three accessibility findings, each with a required action:

  1. primary-500 on white is 2.22:1 — it fails AA as link text. A reader following a link in body text gets 2.22:1. Required: prose links and inline nav links use primary-700 (light) / primary-400 (dark), not the default --ui-primary. Implement by overriding the prose link colour in app.config.ts and the contentNavigation link colour, not by changing the global primary — the global primary is also the button fill, where it sits on a coloured surface and passes differently.
    ui: {
      prose: { slots: { a: 'text-primary-700 dark:text-primary-400 font-medium' } },
      contentNavigation: { slots: { link: 'text-default hover:text-primary-700 dark:hover:text-primary-400' } },
    }
    
  2. White on primary-600 is 3.22:1 — below AA for button text. A 16px button label needs 4.5:1. Required: the primary button fill is primary-700 in light mode (#008236, white on it = 4.95:1) and primary-400 in dark mode (#05df72, ink on it = 11.20:1 with zinc-950 ink). Specify via defaultVariants on UButton rather than per-instance color props.
  3. --ui-text-dimmed (neutral-400) on white is 2.62:1 — it fails AA for any text. It is currently used for metadata (page counts, dates, the status line). Required: metadata uses --ui-text-muted (neutral-500, 4.83:1) in light mode. Reserve text-dimmed for genuinely decorative marks, or override it to neutral-600 (7.72:1).

The dark-mode text-muted is 4.12:1 — passes AA-large only. On dark mode, bump muted to neutral-400 (#9f9fa9, 7.59:1). Both changes are in app.config.ts under ui.colors.neutral overrides via @theme.

Do not use the logo green #16a34a for text or UI controls. It is 3.30:1 on white — fine for a mark, not for a link. This is the one place the brand colour and the UI colour deliberately differ, and the reason is measured contrast, not taste.

4.3 Type scale

The site uses the system font stack (M12) — no webfont, no FOUT, no font CDN. @nuxt/fonts is installed but unused; do not add a webfont for a reference site. The one exception is the wordmark, which is baked into the existing logo SVG.

Base size 16px. The scale is Tailwind's, with the roles fixed:

RoleSizeWeightLine heightTrackingToken
Page H130px → 36px7001.15-0.02emtext-3xl sm:text-4xl
Section H1 (landing)36px7001.1-0.02emtext-4xl
H224px6001.2-0.01emtext-2xl
H319px6001.3—text-lg
H416px6001.4—text-base
Body16px4001.7—text-base
Lede18px4001.6—text-lg
Small / meta14px4001.5—text-sm
Micro / status line12px5001.4+0.08em, uppercasetext-xs
Mono / counts12px5001.4—font-mono text-xs
  • Headings — text-wrap: balance; text-pretty where Docus already applies it (it does, on UPageHeader).
  • Body copy — text-pretty on paragraphs over 3 lines; text-wrap: pretty avoids orphans.
  • Numbers — font-variant-numeric: tabular-nums on every table column that holds a number, and on the page counts in list rows.
  • Non-breaking spaces — in 10 MB, ⌘ K, and in brand names like "Young's Mind" where a line break would look wrong.

4.4 Spacing, radius, elevation

Spacing — a 4px base, with the scale the site actually uses:

TokenValueUsed for
--space-14pxicon gaps, tight label padding
--space-28pxinline gaps, meta label → value
--space-312pxbetween a label and its value, list item padding
--space-416pxcard inner padding, row padding, grid gap (mobile)
--space-524pxcard padding, grid gap (desktop)
--space-640pxbetween blocks in a landing page
--space-764pxsection padding, footer padding

Radius — --ui-radius: 0.25rem (4px) is the Nuxt UI default and is correct for a dense reference site. Cards use --r-md (8px), rows use 0, pills use --r-full. Do not round everything to 16px — that is the marketing-site look this brief is avoiding.

Elevation — border-led, not shadow-led. A reference site is flat by nature; a shadow on every card is noise.

--shadow-card: 0 1px 2px 0 rgb(24 24 27 / 0.04);
--shadow-lift: 0 4px 12px -2px rgb(24 24 27 / 0.08), 0 1px 2px 0 rgb(24 24 27 / 0.04);

Shadows appear only on hover and on overlay surfaces (dropdowns, modals, the command palette). A resting card has a 1px border and no shadow.

4.5 Logo and wordmark

  • The mark — the green brain. public/logo/light.svg (dark ink) and dark.svg (light ink) already exist and are correct. Use them; do not redraw. Both carry the brand green #16a34a with the #22c55e accent nodes.
  • Header — mark at h-6 (24px) + the wordmark, as the existing AppHeaderLeft does. The wordmark is part of the SVG, so it does not scale with the font — at h-6 the wordmark reads at ~14px, which is correct for a header.
  • Favicon — public/favicon.svg, the green brain on #0e1116. Already correct.
  • Never stretch the logo, recolour the brain, put it on a non-white background without the dark variant, or use the wordmark alone as a logo.
  • Open Graph — defineOgImage('Docs', …) already renders a per-page OG image. Keep it; it should carry the section name, not the site name.

4.6 Motion

  • Hover — transform: translateY(-1px) + shadow swap, 120ms, cubic-bezier(0.2, 0, 0, 1). Nav row hover: background + 2px arrow nudge, 120ms.
  • Transitions — transform and opacity only. Never transition: all. Every transition lists its properties.
  • prefers-reduced-motion: reduce — disable all transforms and transitions. Not optional; it is a WCAG 2.3.3 concern and the site's users are reading for hours.
  • No entrance animations on first paint. A reference page must be readable the instant it paints.
  • Focus — :focus-visible ring, 2px solid var(--ui-primary-600), offset 2px. Never outline: none without a replacement. The Docus nav already carries focus-visible:outline-3 and outline-primary/25 — keep that mechanism, and make the ring the --ui-primary-600 value from §4.2 so it clears 3:1.

4.7 Voice

The site's own copy, in one paragraph: active voice, second person, specific labels. "Save API key", not "Continue". "The deployment failed at the migration step", not "An error occurred". Errors name the fix.

Headings in Title Case. Numerals for counts ("8 deployments", not "eight deployments"). & over "and" where space is constrained.

The vault's own prose is written for engineers reading for themselves; the site does not rewrite it. Where the site adds its own copy — section scopes, landing ledes, empty states — it follows the rules above.


5. Accessibility checklist (Web Interface Guidelines)

This is the acceptance list for the implementer. It is the guidelines applicable to a documentation site, with the ones that do not apply marked.

5.1 Must pass

#RuleWhere it applies here
A1Icon-only buttons have aria-labelTheme toggle, menu, drawer triggers, TOC toggle
A2Form controls have a &lt;label&gt; or aria-labelSearch input, any future filter
A3&lt;button&gt; for actions, &lt;a&gt;/<NuxtLink> for navigationEvery nav row, every card, every breadcrumb
A4Images have alt (or alt="" if decorative)Logo (has it), OG images, any diagram
A5Decorative icons have aria-hidden="true"Iconify icons render aria-hidden by default — verify, do not assume
A6Headings hierarchical H1–H6; one H1 per pageLanding pages and reference pages each have exactly one H1
A7scroll-margin-top on heading anchorscalc(var(--ui-header-height) + 16px) on H2/H3 — the current build only does H1
A8Skip link to main contentNot present today. Add one; it is the single highest-value a11y addition on a keyboard-heavy reference site
A9Semantic HTML before ARIA&lt;nav&gt;, &lt;main&gt;, &lt;article&gt;, &lt;aside&gt;
A10Visible focus on every interactive element:focus-visible ring — see §4.6
A11focus-visible over focusDo not show a ring on mouse click
A12Sticky header does not cover the focused elementVerified by the scroll-margin-top in A7
A13aria-live="polite" on async updatesSearch results count, any client-side filter
A14No outline: none without a focus replacementGrep the final diff
A15Touch targets ≥ 44pxMobile drawers, section bar, TOC toggle
A16overscroll-behavior: contain on drawersMobile nav drawer and TOC drawer
A17touch-action: manipulation on buttonsPrevents the 300ms double-tap delay
A18Text containers handle long contentmin-w-0 on flex children; line-clamp-2 on card titles; overflow-wrap: anywhere on code and table cells
A19Empty states handledA section with 0 documents must not render a broken list
A20text-wrap: balance on headingsAll headings
A21… not ...; curly quotes; loading states end with …All site-authored copy
A22font-variant-numeric: tabular-nums on numeric columnsEvery table and every count
A23color-scheme: dark on &lt;html&gt; for dark themesDocus handles this; verify in both modes
A24&lt;meta name="theme-color"&gt; matches the page backgroundNot present today. Add, per mode
A25prefers-reduced-motion honouredSee §4.6
A26No transition: allGrep the final diff
A27URL reflects stateDeep-link the expanded nav group (§5.2)
A28Images have explicit width/heightLogo, OG images — prevents CLS
A29alt="" on decorative imagesThe logo mark next to the wordmark is decorative when the wordmark is text
A30translate="no" on brand names and code tokens"Young's Mind", container names, port IDs

5.2 Deliberately out of scope

Not applicable to a gated, read-only reference site: form validation patterns, unsaved-changes warnings, destructive-action confirmation, file upload, drag-and-drop, gesture-only actions, autoplay media, carousel controls, focus-trap behaviour beyond what UDrawer/UModal already provide, and i18n locale switching (the site is English-only by vault rule 8).

5.3 One additional rule this site earns

Deep-link the expanded nav group. A reader who shares /docs/fleet/03-decisions expects the Decisions group to be open when they arrive, and their colleague's link to land on the same view. Encode the expanded group in the URL (?group=decisions or a path segment), and read it back on load. This is the "URL reflects state" rule, applied to the one piece of state this site actually has.


6. Implementation notes for whoever builds this

The stack is not in question. Docus 5.12.3 / Nuxt 4.5.1 / Nuxt UI 4.10.0 / Tailwind 4.3.3 (M1). The repo already overrides upstream Docus files with small diffs — [docs-nav-v2](docs-nav-v2) documents a 2-line diff in app/pages/[lang](lang)/[...slug].vue. The changes in this brief are the same shape: override app.config.ts theme slots, override app/pages/[lang](lang)/[...slug].vue where the layout needs it, and adjust app/assets/css/sidebar.css.

Do not fork Docus. Build on top, as [docs-nav-v2](docs-nav-v2) established.

The publish lane is not affected. The forge (build_site.py) mirrors the vault; the section reorganisation is a navigation and landing layer, not a content move. The vault paths stay the URLs. The one place the forge changes is the PRETTY label dictionary (§2.5) and the two landing pages it generates (Docs/index.md, Pulse/index.md).

One caveat on the storefront. The card notes the storefront is Nuxt 3 + @nuxt/ui 2.18 (M21 confirms nuxt ^3.14.0 + @nuxt/ui ^2.18.0, 2.22.3 installed). The Docs site is a different stack — Nuxt 4 + Nuxt UI 4 + Tailwind 4. The token values in §4 are Tailwind v4 / Nuxt UI v4 values and do not transfer to the storefront. What transfers is the structure: one accent, a named role per shade, a measured contrast floor, a 4px spacing base, and the same template patterns. The storefront's own brand work is a separate brief in a separate lane.

6.1 Work order

Each step is independently shippable and independently verifiable.

#StepVerifiable by
1Fix the nav — collapsible: true, filter the tree to the active section (§2.3)The aside renders ≤ 40 links, not 249
2Fix the grid — 3 / 5 / 2, delete the !important grid override (§2.4)No horizontal overflow at 1024–1440px; the TOC is visible at ≥1024px
3Re-key the palette — primary: 'green', apply the §4.2 shade-per-role overridesThe contrast table in §4.2 holds, measured in a browser
4Add the section layer — the eight sections, the label dictionary, the landing pages (§1, §2.5)Each section landing renders the directory rows for its content
5Apply the templates — T1, T2, T3, T4 (§2.1)A reference page, a landing, and a card list all render per spec
6Typography and prose — 72ch measure, the §3.3 rules (§3.3)Measure is 72ch; no line exceeds it in a body paragraph
7Status badges — the four-badge mapping (§3.2)Every doc with a status: shows one of four badges with a word
8Accessibility — the §5.1 checklistEach row of §5.1 is a pass or a filed defect
9Frontmatter — the description: field added to the vault where it matters (§3.1)Section landing ledes and page meta descriptions are populated where they matter

Step 9 is a vault task, not a site task — it is the only step that changes content rather than presentation, and it is the slowest. It is listed last on purpose: the site is shippable without it, because §3.1 specifies a fallback for every missing field.

6.2 What is explicitly NOT in this brief

  • The marketing site (acceptance criterion 5). No hero, no feature grid, no pricing, no testimonials, no CTA banner.
  • The Pulse public site — [pulse-homepage-design](pulse-homepage-design).
  • The fleet dashboard — [fleet-dashboard-design-of-record-2026-10-20](fleet-dashboard-design-of-record-2026-10-20).
  • The Authelia gate — Sol's infra lane, already live and verified (M15).
  • The tenant storefront / admin console — separate products, separate briefs, a different Nuxt version.
  • Implementation — this is a design of record. Nova does not implement production code; the build lane is Sol's.

7. Open items — for Young, and for Min

Ordered by what they unblock. None of them blocks steps 1–3 of §6.1.

#Open questionRecommendationOwnerBlocks
O1Do the eight reader-facing section names have Young's approval? The vault names (Fleet, Projects, SolOS) are storage names; the site names (Product, Research, Decisions, Build & Run, Operations, Projects, Tenancy Platform) are reader names. This brief takes the second set.Approve the eight names, or supply replacements. Renaming afterwards means re-touching the label dictionary and eight landing pages.YoungStep 4
O2/docs/agents route collision (M20) — AGENTS.md and SolOS/agents/ both want the same path.Move AGENTS.md under Start Here as "Vault Operations" (§1.5). Requires a forge change and a redirect.Sol (forge) + MinStep 4
O3The empty SolOS/subscriptions/ directory renders as a nav node leading nowhere.Gate it — render the node only when it has content. One line in the forge.SolStep 4
O4Frontmatter coverage. 61 of 1,798 files have a description:. §3.1 falls back gracefully, so this is not a blocker, but page ledes and meta descriptions are thin without it.A vault pass adding description: to the highest-traffic documents (the Fleet/manual/ set and the section index files) — 15 files, not 1,798.Min (register) + doc ownersStep 9
O5status: vocabulary is 30+ values. §3.2 maps them to four badges with a "Design" fallback, so the site works today, but the underlying vocabulary should narrow.A vault ruling narrowing status: to the four badge values plus an "other" bucket. The site does not need to wait.MinNothing — §3.2 is the default
O6Should the Docs site keep the two-section Docs / Pulse split in its own header? M7 shows the tabs work. Pulse is the public surface; a gated reader does not need a link to it in the header.Keep the tab but label it "Pulse (public)" so the audience boundary is explicit.YoungStep 2
O7The dashboard route. [fleet-dashboard-design-of-record-2026-10-20](fleet-dashboard-design-of-record-2026-10-20) §10 Phase 2 assigns the page to Nova and is still open. The dashboard's data layer (Phase 1) is not built.Confirm Nova's dashboard page work is in scope for a separate card, so this brief's T5 does not get built twice.MinT5 only

What this brief does not decide: anything about the Authelia gate's upgrade path (2FA / O365 SSO — [authelia-optimization](authelia-optimization) and the 2FA spec), anything about which documents are "curated" versus mirrored (Stage 5's "curated from LAB, not vault mirror" — this brief specifies the shape of the site and is deliberately compatible with either a full mirror or a curated subset), and anything about the tenant onboarding of the site itself (Stage 5 moves it into the tenant vhost; that is Sol's lane and it does not change the design).


8. What I measured but did not change

Recorded so the next session does not re-derive them.

  • The publish lane works. docs-publish.sh (LAB, every 10 min) → solos-docs repo → docs-pull.sh (prod) → build → restart. Verified live: the prod tree was at 4ab36bc, matching LAB's origin/main, with a clean build and an active service. A redesign must not disturb this lane.
  • The auth gate works and is correct. 302 → auth.youngle.au, forward-auth include, zero auth_basic occurrences.
  • Docs/AGENTS.md is the only vault doc that describes the site itself — and it is stale (it describes the retired solos-docs-watcher / port.py sync, which was replaced by the git publish lane on 2026-10-21). It is parked as a vault-operations page (§1.5) rather than deleted, because the vault is the source of truth and deleting it is not this brief's call. It should be corrected — in the vault, by its owner — not by the site implementer.
  • The MANIFEST.md classification is the site's freshness mechanism.Fleet/manual/MANIFEST.md already classifies every document as CURRENT or HISTORICAL. A status: badge per §3.2 is the visible version of that classification. If the manifest and the badges disagree, the manifest wins.

Register: Min (vhost index + cross-links). Build lane: Sol. Content lane: Tam (forge + publish). Design: Nova (this document). Related: [prod-implementation-stages-2026-10-18](prod-implementation-stages-2026-10-18) (Stage 5), [pulse-docs-redesign-brief](pulse-docs-redesign-brief), [pulse-homepage-design](pulse-homepage-design), [fleet-dashboard-design-of-record-2026-10-20](fleet-dashboard-design-of-record-2026-10-20), [vault-structure-authority-2026-10-18](vault-structure-authority-2026-10-18), [fleet-ground-rules-2026-10-20](fleet-ground-rules-2026-10-20).

Copyright © 2026