Skip to content

Commit 1e9787d

Browse files
mpstatonclaude
andcommitted
feat(shell, docs-portal): mount the design system under the shell header, outside the sign-in wall
The Developers menu opened the portal in a new tab. It now mounts inside the shell as a federated remote, and it renders whether or not you are signed in. Brand guidelines and a token contract are not client data. Gating them behind a session bought nothing and cost a developer the one reference they need while debugging a themed surface — including the case where the thing being debugged is why they cannot get past the wall. docs-portal becomes a federation remote exposing ./mount, while remaining runnable standalone on :3020. The two entries differ deliberately: the federated mount loads only the token floor, because under the shell the shell is the canonical injector and supplies all three mode blocks; the standalone entry still loads the full theme.css, because on :3020 there is no shell to inherit from and a swatch page with one mode would be pointless. Verified both ways — all three modes resolve correctly inside the shell. It is registered as DESIGN_SYSTEM_REMOTE and deliberately kept OUT of REMOTES: it is not a step in any flow and must never appear in a rotation. It is also still absent from DESIGN.md's member registry, since it documents the system rather than consuming it as a product surface, so the per-member contract does not apply to it. The surface gets a slim header of its own rather than the full one. The full header carries workspace switching plus the chat and queue rails, all of which assume a session; rendering it pre-auth would mean guarding every one of them for a surface that needs none. Brand, mode toggle and a way back is the whole requirement, and the back button reads "Back to sign in" or "Back to app" depending on where you came from. A matching entry point sits on the wall itself, so the portal is reachable with no session at all. Also adds the legend the ratios needed. A bare "5.69" means nothing without its thresholds, so the page now states the 1-21 scale, marks 3 as the floor for lines and boundaries and 4.5 for text, and records the reason 4.5 applies everywhere here: every type size in augment-it is under 18.66px, so there is no large-text allowance. Note for anyone with a dev server already running: adding the @augment-it/federation dependency to docs-portal will not reach a live rsbuild process, which holds its module-resolution graph from startup. It reports "Can't resolve '@augment-it/federation'" until restarted. A clean build is green. Verified in the browser: the menu item mounts the remote under the slim header, 150 swatch cells and 153 ratios render, dark/light/vibrant resolve to #0f1115 / #faf9f6 / #0c0814 inside the shell, the shell's ModeToggle and the portal's own buttons stay in sync via onModeChange, and no mount error. 20 packages build, shell and portal typecheck clean. Files changed: - shell/src/App.svelte - shell/src/DevelopersMenu.svelte - shell/src/remotes.ts - shell/rsbuild.config.ts - apps/docs-portal/src/mount.ts (new) - apps/docs-portal/src/App.svelte - apps/docs-portal/src/app.css - apps/docs-portal/rsbuild.config.ts - apps/docs-portal/package.json Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019a8tSPbFdvF1pKtADnWyDg
1 parent 8f6df46 commit 1e9787d

10 files changed

Lines changed: 196 additions & 13 deletions

File tree

apps/docs-portal/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@
1010
"check": "svelte-check --tsconfig ./tsconfig.json"
1111
},
1212
"dependencies": {
13+
"@augment-it/federation": "workspace:*",
1314
"@augment-it/theme": "workspace:*",
1415
"svelte": "^5.56.4"
1516
},
1617
"devDependencies": {
18+
"@module-federation/enhanced": "^2.6.0",
19+
"@module-federation/rsbuild-plugin": "^2.6.0",
1720
"@rsbuild/core": "^2.1.2",
1821
"@rsbuild/plugin-svelte": "^2.0.0",
1922
"svelte-check": "^4.7.1",

apps/docs-portal/rsbuild.config.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,30 @@
11
import { defineConfig } from '@rsbuild/core';
22
import { pluginSvelte } from '@rsbuild/plugin-svelte';
3+
import { pluginModuleFederation } from '@module-federation/rsbuild-plugin';
34

4-
// The design-system portal. Deliberately NOT a federation remote — it is not in
5-
// DESIGN.md's member registry, it is a development surface for looking at the
6-
// token system, and it must load the FULL theme.css (all three mode blocks) so
7-
// the mode toggle has something to switch between.
5+
// The design-system portal. Exposed as a federation remote so the shell can
6+
// mount it under its own header, AND runnable standalone on :3020.
7+
//
8+
// It is still not in DESIGN.md's member registry — it documents the system
9+
// rather than consuming it as a product surface, so the per-member contract
10+
// (prefix, root_class, tiering, its own DESIGN.md) does not apply.
811
export default defineConfig({
9-
plugins: [pluginSvelte()],
12+
plugins: [
13+
pluginSvelte(),
14+
pluginModuleFederation({
15+
name: 'designSystem',
16+
filename: 'remoteEntry.js',
17+
exposes: { './mount': './src/mount.ts' },
18+
dts: false,
19+
}),
20+
],
1021
source: { entry: { index: './src/index.ts' } },
1122
output: {
1223
target: 'web',
1324
overrideBrowserslist: ['last 2 Chrome versions', 'last 2 Firefox versions', 'last 2 Safari versions'],
1425
},
1526
tools: { swc: { jsc: { target: 'es2022' } } },
1627
html: { title: 'augment-it · design system' },
17-
server: { port: 3020 },
28+
server: { port: 3020, cors: { origin: ['http://localhost:3100'] } },
1829
dev: { assetPrefix: 'http://localhost:3020' },
1930
});

apps/docs-portal/src/App.svelte

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,9 +175,22 @@
175175
<p class="note">
176176
Each cell paints <code>color: var(--token)</code> over <code>background: var(--surface)</code>.
177177
A cell that vanishes is a token that does not resolve on that surface — the P2/P3 failure this
178-
page exists to catch. The number is the measured contrast ratio.
178+
page exists to catch.
179179
</p>
180180

181+
<div class="legend">
182+
<span class="legend-title">The number is a WCAG contrast ratio, 1–21:</span>
183+
<span class="legend-item"><span class="ratio fail">1</span> identical — invisible</span>
184+
<span class="legend-item"><span class="ratio large">3</span> floor for lines &amp; boundaries</span>
185+
<span class="legend-item"><span class="ratio pass">4.5</span> floor for text (AA)</span>
186+
<span class="legend-item"><span class="ratio pass">7</span> enhanced (AAA)</span>
187+
<span class="legend-note">
188+
Every type size in augment-it is under 18.66px, so there is no large-text allowance —
189+
<strong>4.5 is the bar for all text</strong>. Structural tokens are measured but not graded:
190+
they are meant to be seen, not read through.
191+
</span>
192+
</div>
193+
181194
<div class="grid" style="--cols: {SURFACES.length}">
182195
<div class="cell head-cell">token</div>
183196
{#each SURFACES as surface}

apps/docs-portal/src/app.css

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,3 +131,21 @@ code { font-family: var(--font-mono); font-size: 11px; }
131131
:root.measuring, :root.measuring * { transition: none !important; }
132132

133133
.accent-cell { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
134+
135+
/* Legend — the ratios are meaningless without the thresholds next to them. */
136+
.legend {
137+
display: flex;
138+
flex-wrap: wrap;
139+
align-items: center;
140+
gap: 8px 14px;
141+
background: var(--color-surface);
142+
border: 1px solid var(--color-border);
143+
border-radius: 3px;
144+
padding: 10px 12px;
145+
margin-bottom: 14px;
146+
font-size: 11px;
147+
}
148+
.legend-title { color: var(--color-text); }
149+
.legend-item { display: inline-flex; align-items: center; gap: 6px; color: var(--color-text-muted); }
150+
.legend-note { flex-basis: 100%; color: var(--color-text-muted); line-height: 1.55; }
151+
.legend-note strong { color: var(--color-text); }

apps/docs-portal/src/mount.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Federation-exposed mount. The shared body lives in @augment-it/federation,
2+
// which also carries the token floor — see that module for why the ordering
3+
// below (federation first, ./app.css second) is load-bearing.
4+
//
5+
// Note what this does NOT import: theme.css. Under the shell, the shell is the
6+
// canonical injector and supplies all three mode blocks, so the portal's mode
7+
// toggle switches against the real theme. The standalone entry (./index.ts)
8+
// still loads the full theme, because on :3020 there is no shell to inherit
9+
// from and a swatch page with one mode would be pointless.
10+
11+
import { makeMount } from '@augment-it/federation';
12+
import './app.css';
13+
import type { Component } from 'svelte';
14+
import App from './App.svelte';
15+
16+
export type { MountResult } from '@augment-it/federation';
17+
18+
export const mountDesignSystem = makeMount(App as Component);

pnpm-lock.yaml

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

shell/rsbuild.config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ const STRATEGY_CURATOR_REMOTE = process.env.PUBLIC_STRATEGY_CURATOR_REMOTE || 'h
2727
const CHAT_REMOTE = process.env.PUBLIC_CHAT_REMOTE || 'http://localhost:3006/remoteEntry.js';
2828
// Augment-from-DB remotes — deployed for the reach-edu opening (#69);
2929
// localhost fallbacks keep local dev unchanged.
30+
// The design system portal. Public by design — it documents the brand and the
31+
// token contract, neither of which is client data, so the shell mounts it
32+
// outside the sign-in wall.
33+
const DESIGN_SYSTEM_REMOTE = process.env.PUBLIC_DESIGN_SYSTEM_REMOTE || 'http://localhost:3020/remoteEntry.js';
3034
const ORG_WORKBENCH_REMOTE = process.env.PUBLIC_ORG_WORKBENCH_REMOTE || 'http://localhost:3014/remoteEntry.js';
3135
const SEARCH_AND_ADD_REMOTE = process.env.PUBLIC_SEARCH_AND_ADD_REMOTE || 'http://localhost:3016/remoteEntry.js';
3236
const SEARCH_RESULTS_REMOTE = process.env.PUBLIC_SEARCH_RESULTS_REMOTE || 'http://localhost:3018/remoteEntry.js';
@@ -56,6 +60,7 @@ export default defineConfig({
5660
// 3018 — the spec said 3017, but strategy-curator had already
5761
// claimed it by build time.
5862
searchResults: `searchResults@${SEARCH_RESULTS_REMOTE}`,
63+
designSystem: `designSystem@${DESIGN_SYSTEM_REMOTE}`,
5964
},
6065
// No `shared` block — sharing Svelte 5's reactive runtime and a
6166
// .svelte.ts singleton across federation has known issues with the

shell/src/App.svelte

Lines changed: 88 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
PAIRINGS,
1515
CHAT_REMOTE,
1616
SEARCH_RESULTS_REMOTE,
17+
DESIGN_SYSTEM_REMOTE,
1718
remoteById,
1819
slotById,
1920
type RemoteEntry,
@@ -114,6 +115,28 @@
114115
return () => window.removeEventListener('augment-it:search-submitted', onSearchSubmitted);
115116
});
116117
118+
// ---- design-system surface --------------------------------------------
119+
// The portal mounts as its own full-bleed surface rather than a step in a
120+
// flow: it documents the system, it is not part of any pipeline, and it must
121+
// be reachable OUTSIDE the sign-in wall. Brand guidelines and a token
122+
// contract are not client data, so gating them behind a session buys nothing
123+
// and costs a developer the one reference they need while debugging a
124+
// themed surface.
125+
//
126+
// It gets a slim header of its own rather than the full one. The full header
127+
// carries workspace switching, chat and queue rails, all of which assume a
128+
// session — rendering it pre-auth would mean guarding every one of them for
129+
// a surface that needs none. Brand, mode toggle and a way back is the whole
130+
// requirement.
131+
let designSystemOpen = $state(false);
132+
133+
function openDesignSystem(): void {
134+
designSystemOpen = true;
135+
}
136+
function closeDesignSystem(): void {
137+
designSystemOpen = false;
138+
}
139+
117140
// ---- pre-auth wall (Build-Order Step 7) --------------------------------
118141
// A single-tenant deploy sets DIDI_AUTH=required; the session frame
119142
// carries that posture (workspace.didi_auth_mode) so the shell can
@@ -519,8 +542,27 @@
519542
}
520543
</script>
521544

522-
{#if showWall}
545+
{#if designSystemOpen}
546+
<header class="ds-header">
547+
<div class="brand">
548+
<strong>augment-it</strong>
549+
<span class="muted">· design system</span>
550+
</div>
551+
<div class="ds-header-right">
552+
<ModeToggle />
553+
<button class="ds-back" onclick={closeDesignSystem}>
554+
{showWall ? 'Back to sign in' : 'Back to app'}
555+
</button>
556+
</div>
557+
</header>
558+
<div class="ds-surface">
559+
<MountHost remote={DESIGN_SYSTEM_REMOTE} />
560+
</div>
561+
{:else if showWall}
523562
<SignInWall />
563+
<div class="wall-dev">
564+
<button class="ds-back" onclick={openDesignSystem}>⚙ Design system</button>
565+
</div>
524566
{:else}
525567
<header>
526568
<div class="header-left">
@@ -594,7 +636,7 @@
594636
>
595637
🔎 queue{#if queueDoneCount > 0}<span class="queue-badge">{queueDoneCount}</span>{/if}
596638
</button>
597-
<DevelopersMenu wsHttpBase={WS_HTTP_BASE} />
639+
<DevelopersMenu wsHttpBase={WS_HTTP_BASE} onOpenDesignSystem={openDesignSystem} />
598640
<DidiBadge />
599641
<ModeToggle />
600642
{#if !workspace.pinned}
@@ -962,4 +1004,47 @@
9621004
margin: auto;
9631005
color: var(--color-text-muted);
9641006
}
965-
</style>
1007+
/* ---- design-system surface -------------------------------------------
1008+
A slim header of its own rather than the full one: the full header
1009+
carries workspace switching plus the chat and queue rails, all of which
1010+
assume a session, and this surface must render pre-auth. */
1011+
.ds-header {
1012+
display: flex;
1013+
align-items: center;
1014+
justify-content: space-between;
1015+
gap: 16px;
1016+
padding: 10px 16px;
1017+
border-bottom: 1px solid var(--color-border);
1018+
background: var(--color-surface-raised);
1019+
}
1020+
.ds-header-right {
1021+
display: flex;
1022+
align-items: center;
1023+
gap: 10px;
1024+
}
1025+
.ds-back {
1026+
font-family: var(--font-mono);
1027+
font-size: 11px;
1028+
padding: 5px 11px;
1029+
background: var(--color-field);
1030+
color: var(--color-text);
1031+
border: 1px solid var(--color-border);
1032+
border-radius: 3px;
1033+
cursor: pointer;
1034+
}
1035+
.ds-back:hover { background: var(--color-field-focus); }
1036+
.ds-back:focus-visible { outline: var(--focus-ring, 2px solid var(--color-accent)); outline-offset: 2px; }
1037+
1038+
.ds-surface {
1039+
height: calc(100vh - 45px);
1040+
overflow: auto;
1041+
}
1042+
1043+
/* The one developer affordance that survives the sign-in wall. */
1044+
.wall-dev {
1045+
position: fixed;
1046+
right: 16px;
1047+
bottom: 16px;
1048+
z-index: 10;
1049+
}
1050+
</style>

shell/src/DevelopersMenu.svelte

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,14 @@
1414
import JumboPopdown, { type PopdownItem } from './JumboPopdown.svelte';
1515
import { workspace } from '@augment-it/workspace';
1616
17-
let { wsHttpBase }: { wsHttpBase: string } = $props();
17+
let {
18+
wsHttpBase,
19+
onOpenDesignSystem,
20+
}: {
21+
wsHttpBase: string;
22+
/** Mounts the portal inside the shell rather than opening a tab. */
23+
onOpenDesignSystem: () => void;
24+
} = $props();
1825
1926
// Same env convention as the federated remotes and DidiBadge: a PUBLIC_-
2027
// prefixed var inlined at build time, with a localhost fallback so local dev
@@ -34,7 +41,7 @@
3441
{
3542
id: 'design-system',
3643
title: 'Design system',
37-
description: 'Brand guidelines, design tokens, the three-mode contract — every token on every surface with live contrast.',
44+
description: 'Brand guidelines, design tokens, the three-mode contract — every token on every surface with live contrast. Opens in the shell.',
3845
},
3946
{
4047
id: 'workspace-service',
@@ -81,7 +88,10 @@
8188
async function onSelect(id: string): Promise<void> {
8289
switch (id) {
8390
case 'design-system':
84-
open(DESIGN_PORTAL);
91+
// Mounts under the shell header as a federated remote. The standalone
92+
// page on DESIGN_PORTAL still exists for anyone who wants it in its own
93+
// tab, but the default is to stay in the app.
94+
onOpenDesignSystem();
8595
break;
8696
case 'workspace-service':
8797
open(`${wsHttpBase}/config`);

shell/src/remotes.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,17 @@ export const AFFILIATION_RATING_ROTATION: string[] = ['recordCollector', 'affili
100100
*/
101101
export const AUGMENT_FROM_DB_ROTATION: string[] = ['orgWorkbench'];
102102

103+
// The design system portal. Deliberately NOT in REMOTES — it is not a step in
104+
// any flow and must never appear in a rotation. The shell mounts it as its own
105+
// full-bleed surface from the Developers menu, outside the sign-in wall.
106+
export const DESIGN_SYSTEM_REMOTE: RemoteEntry = {
107+
id: 'designSystem',
108+
label: 'Design system',
109+
description: 'Brand guidelines, design tokens, the three-mode contract',
110+
// @ts-expect-error — federation remote, type comes from the MF runtime
111+
importMount: () => import('designSystem/mount'),
112+
};
113+
103114
export const REMOTES: RemoteEntry[] = [
104115
{
105116
id: 'recordCollector',

0 commit comments

Comments
 (0)