@1agh/maude 0.19.0 → 0.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +8 -8
- package/plugins/design/dev-server/annotations-context-toolbar.tsx +14 -6
- package/plugins/design/dev-server/annotations-layer.tsx +144 -22
- package/plugins/design/dev-server/api.ts +70 -17
- package/plugins/design/dev-server/artboard-marquee.tsx +170 -0
- package/plugins/design/dev-server/canvas-lib.tsx +190 -94
- package/plugins/design/dev-server/canvas-shell.tsx +478 -34
- package/plugins/design/dev-server/client/app.jsx +173 -52
- package/plugins/design/dev-server/client/styles/1-tokens.css +15 -8
- package/plugins/design/dev-server/client/styles/4-components.css +32 -2
- package/plugins/design/dev-server/config.schema.json +31 -5
- package/plugins/design/dev-server/context-menu.tsx +3 -3
- package/plugins/design/dev-server/context.ts +37 -3
- package/plugins/design/dev-server/dist/client.bundle.js +212 -103
- package/plugins/design/dev-server/dist/styles.css +38 -2
- package/plugins/design/dev-server/export-dialog.tsx +3 -3
- package/plugins/design/dev-server/http.ts +14 -2
- package/plugins/design/dev-server/input-router.tsx +22 -8
- package/plugins/design/dev-server/server.mjs +84 -18
- package/plugins/design/dev-server/test/context-resolve-tokens.test.ts +97 -0
- package/plugins/design/dev-server/test/snap-distance-pill.test.ts +68 -0
- package/plugins/design/dev-server/test/system-endpoint.test.ts +144 -0
- package/plugins/design/dev-server/tool-palette.tsx +71 -15
- package/plugins/design/dev-server/use-annotation-resize.tsx +295 -0
- package/plugins/design/dev-server/use-snap-guides.tsx +25 -1
- package/plugins/design/dev-server/use-tool-mode.tsx +31 -2
|
@@ -1049,7 +1049,7 @@ function Wordmark({ project, port, version }) {
|
|
|
1049
1049
|
);
|
|
1050
1050
|
}
|
|
1051
1051
|
|
|
1052
|
-
function Viewport({ tabs, activePath, registerIframe, systemData, onOpenFromSystem, project, cfg }) {
|
|
1052
|
+
function Viewport({ tabs, activePath, registerIframe, systemData, onOpenFromSystem, onSelectDs, project, cfg }) {
|
|
1053
1053
|
return (
|
|
1054
1054
|
<div className="viewport">
|
|
1055
1055
|
{tabs.length === 0 && (
|
|
@@ -1073,7 +1073,7 @@ function Viewport({ tabs, activePath, registerIframe, systemData, onOpenFromSyst
|
|
|
1073
1073
|
if (t.path === SYSTEM_TAB) {
|
|
1074
1074
|
return (
|
|
1075
1075
|
<div key={t.path} className={'system-view' + (t.path === activePath ? ' active' : '')}>
|
|
1076
|
-
<SystemView data={systemData} onOpen={onOpenFromSystem} cfg={cfg} />
|
|
1076
|
+
<SystemView data={systemData} onOpen={onOpenFromSystem} cfg={cfg} onSelectDs={onSelectDs} />
|
|
1077
1077
|
</div>
|
|
1078
1078
|
);
|
|
1079
1079
|
}
|
|
@@ -1092,83 +1092,184 @@ function Viewport({ tabs, activePath, registerIframe, systemData, onOpenFromSyst
|
|
|
1092
1092
|
}
|
|
1093
1093
|
|
|
1094
1094
|
// ---------- SystemView ----------
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1095
|
+
//
|
|
1096
|
+
// DDR-048 — the System view renders the USER's design-system tokens. It does
|
|
1097
|
+
// NOT read from `document.documentElement` (that would surface the dev-server
|
|
1098
|
+
// shell's amber-rust chrome theme from styles/1-tokens.css, which is NOT a
|
|
1099
|
+
// user template) and it does NOT assume any canonical token-name contract.
|
|
1100
|
+
// Whatever the user's `colors_and_type.css` declared — names, values, theme
|
|
1101
|
+
// blocks — is what shows up here.
|
|
1102
|
+
|
|
1103
|
+
// Order kinds match the typical reading flow of a tokens file. Unknown kinds
|
|
1104
|
+
// fall through to `other` so a custom token group still renders, just last.
|
|
1105
|
+
const TOKEN_GROUP_ORDER = [
|
|
1106
|
+
'color', 'space', 'radius', 'shadow', 'leading', 'weight', 'motion', 'font', 'other',
|
|
1102
1107
|
];
|
|
1103
|
-
const
|
|
1108
|
+
const TOKEN_GROUP_LABELS = {
|
|
1109
|
+
color: 'colors',
|
|
1110
|
+
space: 'spacing',
|
|
1111
|
+
radius: 'radii',
|
|
1112
|
+
shadow: 'shadows',
|
|
1113
|
+
leading: 'leading',
|
|
1114
|
+
weight: 'weights',
|
|
1115
|
+
motion: 'motion',
|
|
1116
|
+
font: 'font stacks',
|
|
1117
|
+
other: 'other',
|
|
1118
|
+
};
|
|
1104
1119
|
|
|
1105
|
-
function
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1120
|
+
function isSwatchKind(kind) { return kind === 'color'; }
|
|
1121
|
+
|
|
1122
|
+
function TokenLadder({ tokens, tokenGroups, tokensPath }) {
|
|
1123
|
+
if (!tokens || tokens.length === 0) {
|
|
1124
|
+
return (
|
|
1125
|
+
<section className="sv-section sv-section-tokens">
|
|
1126
|
+
<h2>tokens<span className="sv-h-num">0</span></h2>
|
|
1127
|
+
<div className="sv-empty">
|
|
1128
|
+
<p>
|
|
1129
|
+
No tokens parsed from {tokensPath ? <code>{tokensPath}</code> : 'the configured tokens file'}.
|
|
1130
|
+
Does the file exist and contain CSS custom properties (<code>--name: value;</code>)?
|
|
1131
|
+
</p>
|
|
1132
|
+
</div>
|
|
1133
|
+
</section>
|
|
1134
|
+
);
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
const groups = tokenGroups || {};
|
|
1138
|
+
const kinds = Array.from(
|
|
1139
|
+
new Set([...TOKEN_GROUP_ORDER, ...Object.keys(groups)])
|
|
1140
|
+
).filter((k) => groups[k]?.length);
|
|
1110
1141
|
|
|
1111
|
-
function TokenLadder() {
|
|
1112
|
-
const [tokens, setTokens] = useState(() => readTokens(TOKEN_NAMES));
|
|
1113
|
-
useEffect(() => {
|
|
1114
|
-
setTokens(readTokens(TOKEN_NAMES));
|
|
1115
|
-
const obs = new MutationObserver(() => setTokens(readTokens(TOKEN_NAMES)));
|
|
1116
|
-
obs.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
|
|
1117
|
-
return () => obs.disconnect();
|
|
1118
|
-
}, []);
|
|
1119
1142
|
return (
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
<
|
|
1127
|
-
|
|
1128
|
-
<span className="sv-
|
|
1143
|
+
<>
|
|
1144
|
+
{kinds.map((kind) => {
|
|
1145
|
+
const list = groups[kind];
|
|
1146
|
+
const swatch = isSwatchKind(kind);
|
|
1147
|
+
return (
|
|
1148
|
+
<section className={'sv-section sv-section-tokens sv-section-' + kind} key={kind}>
|
|
1149
|
+
<h2>
|
|
1150
|
+
tokens · {TOKEN_GROUP_LABELS[kind] || kind}
|
|
1151
|
+
<span className="sv-h-num">{list.length}</span>
|
|
1152
|
+
</h2>
|
|
1153
|
+
<div className="sv-tokens-ladder">
|
|
1154
|
+
{list.map((t) => (
|
|
1155
|
+
<div className="sv-tok-cell" key={t.name + '|' + t.value}>
|
|
1156
|
+
{swatch ? (
|
|
1157
|
+
<div className="sv-tok-swatch" style={{ background: t.value }} />
|
|
1158
|
+
) : null}
|
|
1159
|
+
<div className="sv-tok-meta">
|
|
1160
|
+
<code className="sv-tok-name">{t.name}</code>
|
|
1161
|
+
<span className="sv-tok-value">{t.value || '—'}</span>
|
|
1162
|
+
</div>
|
|
1163
|
+
</div>
|
|
1164
|
+
))}
|
|
1129
1165
|
</div>
|
|
1130
|
-
</
|
|
1131
|
-
)
|
|
1132
|
-
|
|
1133
|
-
|
|
1166
|
+
</section>
|
|
1167
|
+
);
|
|
1168
|
+
})}
|
|
1169
|
+
</>
|
|
1134
1170
|
);
|
|
1135
1171
|
}
|
|
1136
1172
|
|
|
1137
|
-
|
|
1173
|
+
// Find a leading partner by name suffix: --fs-base → --lh-base, --type-xl → --lh-xl.
|
|
1174
|
+
// Returns null when no convention match exists; caller omits the lineHeight style.
|
|
1175
|
+
function findLeadingFor(typeToken, leadingTokens) {
|
|
1176
|
+
const m = typeToken.name.match(/^--(?:type|fs|text)-(.+)$/);
|
|
1177
|
+
if (!m) return null;
|
|
1178
|
+
const suffix = m[1];
|
|
1179
|
+
return (leadingTokens || []).find((t) =>
|
|
1180
|
+
/^--(?:lh|leading|line-height)-/.test(t.name) && t.name.endsWith('-' + suffix)
|
|
1181
|
+
)?.value ?? null;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
// Best-effort sample font: prefer body / sans / display tokens, fall back to
|
|
1185
|
+
// the first font-kind token, fall back to system-ui. Avoids the shell's
|
|
1186
|
+
// Berkeley Mono leaking into user-facing previews.
|
|
1187
|
+
function sampleFontFamily(fontTokens) {
|
|
1188
|
+
if (!fontTokens?.length) return undefined;
|
|
1189
|
+
const prefer = ['body', 'sans', 'display', 'text', 'family'];
|
|
1190
|
+
for (const tag of prefer) {
|
|
1191
|
+
const hit = fontTokens.find((t) => t.name.includes(tag));
|
|
1192
|
+
if (hit) return hit.value;
|
|
1193
|
+
}
|
|
1194
|
+
return fontTokens[0].value;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
function TypeLadder({ tokenGroups }) {
|
|
1198
|
+
const typeTokens = tokenGroups?.fontsize || [];
|
|
1199
|
+
if (typeTokens.length === 0) return null;
|
|
1200
|
+
const leadingTokens = tokenGroups?.leading || [];
|
|
1201
|
+
const sampleFont = sampleFontFamily(tokenGroups?.font);
|
|
1202
|
+
|
|
1138
1203
|
return (
|
|
1139
1204
|
<section className="sv-section sv-section-type">
|
|
1140
|
-
<h2>
|
|
1205
|
+
<h2>
|
|
1206
|
+
type · ladder<span className="sv-h-num">{typeTokens.length}</span>
|
|
1207
|
+
</h2>
|
|
1141
1208
|
<div className="sv-type-list">
|
|
1142
|
-
{
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1209
|
+
{typeTokens.map((t) => {
|
|
1210
|
+
const lh = findLeadingFor(t, leadingTokens);
|
|
1211
|
+
const style = { fontSize: t.value };
|
|
1212
|
+
if (lh) style.lineHeight = lh;
|
|
1213
|
+
if (sampleFont) style.fontFamily = sampleFont;
|
|
1214
|
+
return (
|
|
1215
|
+
<div className="sv-type-row" key={t.name}>
|
|
1216
|
+
<code className="sv-type-tok">{t.name}</code>
|
|
1217
|
+
<span className="sv-type-sample" style={style}>
|
|
1218
|
+
The catalog is the system.
|
|
1219
|
+
</span>
|
|
1220
|
+
</div>
|
|
1221
|
+
);
|
|
1222
|
+
})}
|
|
1150
1223
|
</div>
|
|
1151
1224
|
</section>
|
|
1152
1225
|
);
|
|
1153
1226
|
}
|
|
1154
1227
|
|
|
1155
|
-
function SystemView({ data, onOpen, cfg }) {
|
|
1228
|
+
function SystemView({ data, onOpen, cfg, onSelectDs }) {
|
|
1156
1229
|
if (!data) {
|
|
1157
1230
|
return <div className="sv-empty"><p>Loading design system…</p></div>;
|
|
1158
1231
|
}
|
|
1159
|
-
const {
|
|
1232
|
+
const {
|
|
1233
|
+
previewGallery,
|
|
1234
|
+
uiKitsGallery,
|
|
1235
|
+
systemDir,
|
|
1236
|
+
tokens,
|
|
1237
|
+
tokenGroups,
|
|
1238
|
+
tokensPath,
|
|
1239
|
+
ds,
|
|
1240
|
+
availableDesignSystems,
|
|
1241
|
+
} = data;
|
|
1160
1242
|
const empty = (!previewGallery || !previewGallery.length) && (!uiKitsGallery || !uiKitsGallery.length);
|
|
1243
|
+
const hasPicker = Array.isArray(availableDesignSystems) && availableDesignSystems.length > 1;
|
|
1244
|
+
const selectedName = ds?.name ?? availableDesignSystems?.[0]?.name ?? '';
|
|
1161
1245
|
|
|
1162
1246
|
return (
|
|
1163
1247
|
<div className="sv">
|
|
1164
1248
|
<header className="sv-header">
|
|
1165
1249
|
<span className="sv-sku">MDCC-DSN/01</span>
|
|
1166
1250
|
<span className="sv-title">design system view</span>
|
|
1251
|
+
{hasPicker ? (
|
|
1252
|
+
<label className="sv-ds-picker">
|
|
1253
|
+
<span className="sv-ds-picker-label">DS</span>
|
|
1254
|
+
<select
|
|
1255
|
+
value={selectedName}
|
|
1256
|
+
onChange={(e) => onSelectDs && onSelectDs(e.target.value)}
|
|
1257
|
+
>
|
|
1258
|
+
{availableDesignSystems.map((d) => (
|
|
1259
|
+
<option key={d.name} value={d.name}>{d.name}</option>
|
|
1260
|
+
))}
|
|
1261
|
+
</select>
|
|
1262
|
+
</label>
|
|
1263
|
+
) : null}
|
|
1167
1264
|
<span className="sv-loc"><code>{systemDir}</code></span>
|
|
1168
1265
|
</header>
|
|
1169
1266
|
|
|
1170
|
-
|
|
1171
|
-
|
|
1267
|
+
{ds?.description ? (
|
|
1268
|
+
<p className="sv-ds-description">{ds.description}</p>
|
|
1269
|
+
) : null}
|
|
1270
|
+
|
|
1271
|
+
<TokenLadder tokens={tokens} tokenGroups={tokenGroups} tokensPath={tokensPath} />
|
|
1272
|
+
<TypeLadder tokenGroups={tokenGroups} />
|
|
1172
1273
|
|
|
1173
1274
|
{empty ? (
|
|
1174
1275
|
<div className="sv-empty">
|
|
@@ -1494,10 +1595,29 @@ function App() {
|
|
|
1494
1595
|
useEffect(() => { loadTree(); }, [loadTree]);
|
|
1495
1596
|
|
|
1496
1597
|
// ----- System data (lazy) -----
|
|
1497
|
-
|
|
1598
|
+
// `dsName` scopes to a single design-system entry (DDR-048). The initial
|
|
1599
|
+
// call is unscoped — server returns `availableDesignSystems[]` + a default
|
|
1600
|
+
// — so the picker can render without a probe round-trip. Subsequent calls
|
|
1601
|
+
// (e.g. picker change) pass the chosen DS name and we replace systemData
|
|
1602
|
+
// wholesale (tokens + previews + ds metadata all shift together).
|
|
1603
|
+
const loadSystemData = useCallback(async (dsName) => {
|
|
1498
1604
|
try {
|
|
1499
|
-
const
|
|
1605
|
+
const url = dsName ? `/_system-data?ds=${encodeURIComponent(dsName)}` : '/_system-data';
|
|
1606
|
+
const r = await fetch(url);
|
|
1607
|
+
if (!r.ok) {
|
|
1608
|
+
console.error('failed to load system-data', r.status);
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1500
1611
|
const data = await r.json();
|
|
1612
|
+
// If the initial unscoped fetch has a defaultDesignSystem but no `ds`
|
|
1613
|
+
// attached (multi-DS project), kick off a scoped fetch so the visible
|
|
1614
|
+
// tokens + previews match the default DS, not the union root scan.
|
|
1615
|
+
if (!dsName && data?.defaultDesignSystem && !data.ds) {
|
|
1616
|
+
setSystemData(data);
|
|
1617
|
+
const r2 = await fetch(`/_system-data?ds=${encodeURIComponent(data.defaultDesignSystem)}`);
|
|
1618
|
+
if (r2.ok) setSystemData(await r2.json());
|
|
1619
|
+
return;
|
|
1620
|
+
}
|
|
1501
1621
|
setSystemData(data);
|
|
1502
1622
|
} catch (e) {
|
|
1503
1623
|
console.error('failed to load system-data', e);
|
|
@@ -1918,6 +2038,7 @@ function App() {
|
|
|
1918
2038
|
registerIframe={registerIframe}
|
|
1919
2039
|
systemData={systemData}
|
|
1920
2040
|
onOpenFromSystem={openTab}
|
|
2041
|
+
onSelectDs={loadSystemData}
|
|
1921
2042
|
project={project}
|
|
1922
2043
|
cfg={cfg}
|
|
1923
2044
|
/>
|
|
@@ -1,13 +1,20 @@
|
|
|
1
|
-
/* @layer tokens —
|
|
1
|
+
/* @layer tokens — SHELL theme for the dev-server UI itself.
|
|
2
2
|
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* manually and keep the values verbatim.
|
|
3
|
+
* ⚠ This is NOT a template for user design systems and NOT a fallback for
|
|
4
|
+
* the System view's token ladder. It themes the dev-server chrome — sidebar,
|
|
5
|
+
* status bar, header, menubar, comments panel, System tab outer shell. The
|
|
6
|
+
* amber-rust "catalog stamp" aesthetic below belongs to Maude as a tool, not
|
|
7
|
+
* to any project that runs against it.
|
|
9
8
|
*
|
|
10
|
-
*
|
|
9
|
+
* Per DDR-048, the System view (MDCC-DSN/01) reads user tokens from the
|
|
10
|
+
* project's own `colors_and_type.css` (parsed by the server, returned via
|
|
11
|
+
* /_system-data) and renders them by raw value — never by `var(--name)`
|
|
12
|
+
* resolving against this document. That isolation is load-bearing: prior
|
|
13
|
+
* to DDR-048 the System view leaked these shell tokens into the user's
|
|
14
|
+
* "design system overview" because both happened to share the canonical
|
|
15
|
+
* `--bg-0..4 / --fg-0..3 / --accent*` naming.
|
|
16
|
+
*
|
|
17
|
+
* Two shell themes — both first-class, both equal:
|
|
11
18
|
* :root + [data-theme="light"] — paper-light (cream + ink)
|
|
12
19
|
* [data-theme="dark"] — phosphor-dark (default for dev-server)
|
|
13
20
|
*
|
|
@@ -36,14 +36,44 @@
|
|
|
36
36
|
|
|
37
37
|
/* CV-09 header — SKU framing, hairline divider underneath. */
|
|
38
38
|
.sv-header {
|
|
39
|
-
display:
|
|
40
|
-
|
|
39
|
+
display: flex;
|
|
40
|
+
flex-wrap: wrap;
|
|
41
41
|
align-items: baseline;
|
|
42
42
|
gap: var(--space-5);
|
|
43
43
|
border-bottom: 2px solid var(--u-fg-0);
|
|
44
44
|
padding-bottom: var(--u-s-3);
|
|
45
45
|
margin-bottom: var(--u-s-5);
|
|
46
46
|
}
|
|
47
|
+
.sv-header .sv-title { flex: 1 1 auto; }
|
|
48
|
+
.sv-ds-picker {
|
|
49
|
+
display: inline-flex;
|
|
50
|
+
align-items: baseline;
|
|
51
|
+
gap: var(--u-s-2);
|
|
52
|
+
font-family: var(--u-font-mono);
|
|
53
|
+
font-size: 10px;
|
|
54
|
+
letter-spacing: var(--tracking-sku);
|
|
55
|
+
text-transform: uppercase;
|
|
56
|
+
color: var(--u-fg-2);
|
|
57
|
+
}
|
|
58
|
+
.sv-ds-picker select {
|
|
59
|
+
font-family: var(--u-font-mono);
|
|
60
|
+
font-size: 11px;
|
|
61
|
+
color: var(--u-fg-0);
|
|
62
|
+
background: var(--u-bg-1);
|
|
63
|
+
border: 1px solid var(--u-border);
|
|
64
|
+
border-radius: var(--u-r-sm);
|
|
65
|
+
padding: 2px 6px;
|
|
66
|
+
text-transform: none;
|
|
67
|
+
letter-spacing: 0;
|
|
68
|
+
}
|
|
69
|
+
.sv-ds-description {
|
|
70
|
+
font-family: var(--u-font-body);
|
|
71
|
+
font-size: var(--type-sm);
|
|
72
|
+
line-height: 1.5;
|
|
73
|
+
color: var(--u-fg-1);
|
|
74
|
+
margin: 0 0 var(--u-s-5);
|
|
75
|
+
max-width: 72ch;
|
|
76
|
+
}
|
|
47
77
|
.sv-sku {
|
|
48
78
|
font-family: var(--u-font-mono);
|
|
49
79
|
font-size: var(--type-lg);
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
},
|
|
53
53
|
"tokensCssRel": {
|
|
54
54
|
"type": "string",
|
|
55
|
-
"description": "
|
|
55
|
+
"description": "Project-wide tokens CSS path, relative to designRoot. Used as the fallback when 'designSystems' is unset OR when an entry omits its own 'tokensCssRel'. New canvases <link> to this file by default. Per DDR-048, per-DS entries auto-resolve to '<designSystems[i].path>/colors_and_type.css' when their own field is missing.",
|
|
56
56
|
"default": "system/colors_and_type.css"
|
|
57
57
|
},
|
|
58
58
|
"teamAccentDefault": {
|
|
@@ -130,7 +130,7 @@
|
|
|
130
130
|
},
|
|
131
131
|
"designSystems": {
|
|
132
132
|
"type": "array",
|
|
133
|
-
"description": "Design systems available in this project. Single-DS projects have one entry; multi-DS projects (marketing vs. admin vs. mobile) list each here. Each canvas's .meta.json declares which DS it uses via the 'designSystem' field.",
|
|
133
|
+
"description": "Design systems available in this project. Single-DS projects have one entry; multi-DS projects (marketing vs. admin vs. mobile) list each here. Each canvas's .meta.json declares which DS it uses via the 'designSystem' field. The System view's DS picker iterates this array.",
|
|
134
134
|
"items": {
|
|
135
135
|
"type": "object",
|
|
136
136
|
"required": ["name", "path"],
|
|
@@ -138,13 +138,39 @@
|
|
|
138
138
|
"name": {
|
|
139
139
|
"type": "string",
|
|
140
140
|
"pattern": "^[a-z][a-z0-9-]*$",
|
|
141
|
-
"description": "Kebab-case slug. Used to match canvas .meta.json.designSystem references."
|
|
141
|
+
"description": "Kebab-case slug. Used to match canvas .meta.json.designSystem references and as the ?ds=<name> query param on /_system-data."
|
|
142
142
|
},
|
|
143
143
|
"path": {
|
|
144
144
|
"type": "string",
|
|
145
|
-
"description": "Folder under designRoot (e.g. 'system/marketing')."
|
|
145
|
+
"description": "Folder under designRoot (e.g. 'system/marketing'). The DS-scoped System view reads tokens, previews, and ui_kits from inside this folder."
|
|
146
146
|
},
|
|
147
|
-
"description": { "type": "string" }
|
|
147
|
+
"description": { "type": "string" },
|
|
148
|
+
"tokensCssRel": {
|
|
149
|
+
"type": "string",
|
|
150
|
+
"description": "Per-DS tokens CSS path, relative to designRoot. When omitted, auto-resolves to '<path>/colors_and_type.css' (DDR-048). Set explicitly when the DS's tokens live under a non-standard filename."
|
|
151
|
+
},
|
|
152
|
+
"rootClass": {
|
|
153
|
+
"type": "string",
|
|
154
|
+
"description": "Optional root class applied to canvases in this DS (overrides the project-wide rootClass for this DS's previews + new canvases)."
|
|
155
|
+
},
|
|
156
|
+
"themeDefault": {
|
|
157
|
+
"type": "string",
|
|
158
|
+
"enum": ["dark", "light"],
|
|
159
|
+
"description": "Per-DS theme default. Overrides the project-wide themeDefault when rendering this DS's specimens."
|
|
160
|
+
},
|
|
161
|
+
"themes": {
|
|
162
|
+
"type": "array",
|
|
163
|
+
"items": { "type": "string" },
|
|
164
|
+
"description": "Optional list of theme names this DS supports (used by the System view's theme toggle when more than one theme is declared)."
|
|
165
|
+
},
|
|
166
|
+
"newCanvasDir": {
|
|
167
|
+
"type": "string",
|
|
168
|
+
"description": "Override of the project-wide newCanvasDir for canvases scaffolded inside this DS."
|
|
169
|
+
},
|
|
170
|
+
"newComponentDir": {
|
|
171
|
+
"type": "string",
|
|
172
|
+
"description": "Override of the project-wide newComponentDir for components scaffolded inside this DS."
|
|
173
|
+
}
|
|
148
174
|
},
|
|
149
175
|
"additionalProperties": false
|
|
150
176
|
},
|
|
@@ -158,10 +158,10 @@ const MENU_CSS = `
|
|
|
158
158
|
.dc-context-menu {
|
|
159
159
|
position: fixed;
|
|
160
160
|
z-index: 7;
|
|
161
|
-
background: var(--u-bg-
|
|
161
|
+
background: var(--u-bg-0, var(--bg-0, #fff));
|
|
162
162
|
border: 1px solid var(--u-fg-0, #1c1917);
|
|
163
|
-
border-radius:
|
|
164
|
-
box-shadow:
|
|
163
|
+
border-radius: 8px;
|
|
164
|
+
box-shadow: 0 6px 24px color-mix(in oklab, var(--u-fg-0, #1c1917) 10%, transparent);
|
|
165
165
|
padding: 4px;
|
|
166
166
|
min-width: 220px;
|
|
167
167
|
font-family: var(--u-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
|
|
@@ -31,6 +31,7 @@ export interface DevServerConfig {
|
|
|
31
31
|
designRoot: string;
|
|
32
32
|
canvasGroups: CanvasGroup[];
|
|
33
33
|
designSystems?: DesignSystemEntry[];
|
|
34
|
+
defaultDesignSystem?: string;
|
|
34
35
|
rootClass: string;
|
|
35
36
|
themeDefault: 'dark' | 'light';
|
|
36
37
|
tokensCssRel: string;
|
|
@@ -124,18 +125,51 @@ function loadConfig(repoRoot: string): DevServerConfig {
|
|
|
124
125
|
try {
|
|
125
126
|
raw = readFileSync(configPath, 'utf8');
|
|
126
127
|
} catch {
|
|
127
|
-
return { ...DEFAULT_CONFIG, _source: 'defaults' };
|
|
128
|
+
return normalizeConfig({ ...DEFAULT_CONFIG, _source: 'defaults' });
|
|
128
129
|
}
|
|
129
130
|
try {
|
|
130
131
|
const parsed = JSON.parse(raw);
|
|
131
|
-
return { ...DEFAULT_CONFIG, ...parsed, _source: '.design/config.json' };
|
|
132
|
+
return normalizeConfig({ ...DEFAULT_CONFIG, ...parsed, _source: '.design/config.json' });
|
|
132
133
|
} catch (e) {
|
|
133
134
|
const msg = e instanceof Error ? e.message : String(e);
|
|
134
135
|
console.error(` warn: ${configPath} is not valid JSON: ${msg}. Using defaults.`);
|
|
135
|
-
return { ...DEFAULT_CONFIG, _source: 'defaults (config invalid)' };
|
|
136
|
+
return normalizeConfig({ ...DEFAULT_CONFIG, _source: 'defaults (config invalid)' });
|
|
136
137
|
}
|
|
137
138
|
}
|
|
138
139
|
|
|
140
|
+
/**
|
|
141
|
+
* Fill in per-DS `tokensCssRel` defaults so the system view can read each DS's
|
|
142
|
+
* tokens without forcing every config author to spell out the path. When an
|
|
143
|
+
* entry omits `tokensCssRel`, derive it from `<entry.path>/colors_and_type.css`
|
|
144
|
+
* — the scaffold layout `/design:setup-ds` produces. Also strips leading /
|
|
145
|
+
* trailing slashes from `entry.path` so downstream `path.posix.join` calls
|
|
146
|
+
* don't produce double-slash artifacts.
|
|
147
|
+
*
|
|
148
|
+
* The top-level `cfg.tokensCssRel` is preserved untouched as the
|
|
149
|
+
* project-wide fallback for legacy single-DS configs that don't declare
|
|
150
|
+
* `designSystems[]` at all.
|
|
151
|
+
*
|
|
152
|
+
* DDR-048: the system view renders user tokens only; this normalization is the
|
|
153
|
+
* load-bearing step that makes per-DS rendering possible.
|
|
154
|
+
*/
|
|
155
|
+
export function normalizeDesignSystems<T extends DevServerConfig>(cfg: T): T {
|
|
156
|
+
if (!cfg.designSystems?.length) return cfg;
|
|
157
|
+
const designSystems = cfg.designSystems.map((entry) => {
|
|
158
|
+
const p = entry.path.replace(/^\/+|\/+$/g, '');
|
|
159
|
+
return {
|
|
160
|
+
...entry,
|
|
161
|
+
path: p,
|
|
162
|
+
tokensCssRel:
|
|
163
|
+
entry.tokensCssRel?.replace(/^\/+/, '') ?? path.posix.join(p, 'colors_and_type.css'),
|
|
164
|
+
};
|
|
165
|
+
});
|
|
166
|
+
return { ...cfg, designSystems };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function normalizeConfig(cfg: DevServerConfig): DevServerConfig {
|
|
170
|
+
return normalizeDesignSystems(cfg);
|
|
171
|
+
}
|
|
172
|
+
|
|
139
173
|
export function createContext(): Context {
|
|
140
174
|
const repoRoot = resolveRepoRoot();
|
|
141
175
|
|