@krapwoo/ds-viewer 0.14.2 → 0.16.0

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.
Files changed (37) hide show
  1. package/README.md +249 -3
  2. package/config/index.ts +3 -0
  3. package/dist/cli/config.js +9 -1
  4. package/dist/cli/doctor.js +452 -19
  5. package/dist/cli/explain.js +26 -0
  6. package/dist/cli/staticPage.js +99 -10
  7. package/dist/cli/tokenDefinition.js +125 -0
  8. package/dist/native/catalog/catalogNavigation.js +27 -11
  9. package/dist/native/catalog/pageApi.js +557 -26
  10. package/dist/native/catalog/tokenDefinition.js +205 -0
  11. package/native/catalog/CatalogSearchInput.tsx +18 -3
  12. package/native/catalog/CatalogShell.tsx +318 -46
  13. package/native/catalog/CatalogSidebar.tsx +37 -1
  14. package/native/catalog/FamilyPage.tsx +567 -137
  15. package/native/catalog/GroupPage.tsx +230 -0
  16. package/native/catalog/JumpRow.tsx +81 -0
  17. package/native/catalog/ReferenceDetails.tsx +144 -16
  18. package/native/catalog/SectionBlock.tsx +49 -5
  19. package/native/catalog/SourceReferencePicker.tsx +180 -0
  20. package/native/catalog/StructuredTokenPage.tsx +592 -0
  21. package/native/catalog/Swatch.tsx +29 -2
  22. package/native/catalog/TokenLayouts.tsx +30 -9
  23. package/native/catalog/catalogNavigation.ts +30 -10
  24. package/native/catalog/catalogReferences.ts +212 -0
  25. package/native/catalog/familyModel.ts +10 -4
  26. package/native/catalog/index.ts +22 -0
  27. package/native/catalog/pageApi.ts +598 -23
  28. package/native/catalog/tokenDefinition.ts +430 -0
  29. package/native/catalog/types.ts +77 -5
  30. package/package.json +1 -1
  31. package/starter-kit/components/AnimatedChevron/AnimatedChevron.catalog.tsx +1 -0
  32. package/starter-kit/components/Avatar/Avatar.catalog.tsx +1 -0
  33. package/starter-kit/components/BottomSheet/BottomSheet.catalog.tsx +1 -0
  34. package/starter-kit/components/ButtonGroup/ButtonGroup.catalog.tsx +1 -0
  35. package/starter-kit/components/Divider/Divider.catalog.tsx +1 -0
  36. package/starter-kit/components/Pill/Pill.catalog.tsx +1 -0
  37. package/starter-kit/components/TopNav/TopNav.catalog.tsx +1 -0
@@ -1,5 +1,6 @@
1
1
  import * as ts from 'typescript';
2
2
  import { readFileSync } from 'node:fs';
3
+ import { readStaticTokenDefinition } from "./tokenDefinition.js";
3
4
  const TRANSPILE_OPTIONS = {
4
5
  compilerOptions: { jsx: ts.JsxEmit.ReactJSX, module: ts.ModuleKind.ESNext, target: ts.ScriptTarget.ES2022 },
5
6
  reportDiagnostics: true,
@@ -58,21 +59,26 @@ function readStaticPage(file) {
58
59
  return { file, checkable: false, viewerImports };
59
60
  const page = { file, checkable: true, viewerImports };
60
61
  const idProp = findProp(pageObject, 'id');
62
+ const titleProp = findProp(pageObject, 'title');
61
63
  const componentProp = findProp(pageObject, 'component');
62
64
  const groupProp = findProp(pageObject, 'group');
63
65
  const specimenSizeProp = findProp(pageObject, 'specimenSize');
64
66
  const tokenGalleryProp = findProp(pageObject, 'tokenGallery');
65
67
  if (idProp)
66
68
  page.id = stringLiteral(resolve(idProp.initializer, consts, new Set()));
69
+ if (titleProp)
70
+ page.title = stringLiteral(resolve(titleProp.initializer, consts, new Set()));
67
71
  if (componentProp)
68
72
  page.component = stringLiteral(resolve(componentProp.initializer, consts, new Set()));
69
73
  if (groupProp)
70
74
  page.group = stringLiteral(resolve(groupProp.initializer, consts, new Set()));
71
75
  if (specimenSizeProp)
72
76
  page.specimenSize = stringLiteral(resolve(specimenSizeProp.initializer, consts, new Set()));
77
+ page.hasExplicitTokenGallery = tokenGalleryProp !== undefined;
73
78
  if (tokenGalleryProp)
74
79
  page.tokenGallery = resolve(tokenGalleryProp.initializer, consts, new Set()).kind === ts.SyntaxKind.TrueKeyword;
75
80
  const tokenSectionsProp = findProp(pageObject, 'tokenSections');
81
+ page.hasExplicitTokenSections = tokenSectionsProp !== undefined;
76
82
  if (tokenSectionsProp) {
77
83
  const list = resolve(tokenSectionsProp.initializer, consts, new Set());
78
84
  const items = ts.isArrayLiteralExpression(list) ? list.elements.map((element) => resolve(element, consts, new Set())) : [list];
@@ -201,6 +207,7 @@ function readStaticPage(file) {
201
207
  }
202
208
  }
203
209
  const membersProp = findProp(pageObject, 'members');
210
+ page.hasMembers = membersProp !== undefined;
204
211
  if (membersProp) {
205
212
  const members = readFamilyMembers(membersProp.initializer, consts, gridNames, sourceFile);
206
213
  if (!members)
@@ -209,6 +216,7 @@ function readStaticPage(file) {
209
216
  page.members = members;
210
217
  }
211
218
  const wholeProp = findProp(pageObject, 'whole');
219
+ page.hasWhole = wholeProp !== undefined;
212
220
  if (wholeProp) {
213
221
  const whole = stringLiteral(resolve(wholeProp.initializer, consts, new Set()));
214
222
  if (whole === undefined)
@@ -217,6 +225,7 @@ function readStaticPage(file) {
217
225
  page.whole = whole;
218
226
  }
219
227
  const sectionsProp = findProp(pageObject, 'sections');
228
+ page.hasSections = sectionsProp !== undefined;
220
229
  if (sectionsProp) {
221
230
  const sectionsRead = readSections(sectionsProp.initializer, consts);
222
231
  if (!sectionsRead)
@@ -235,11 +244,46 @@ function readStaticPage(file) {
235
244
  const relatedPagesProp = findProp(pageObject, 'relatedPages');
236
245
  if (relatedPagesProp)
237
246
  page.relatedPages = readRelatedPages(relatedPagesProp.initializer, consts);
247
+ page.hasAnatomy = findProp(pageObject, 'anatomy') !== undefined;
248
+ const navigationProp = findProp(pageObject, 'navigation');
249
+ if (navigationProp) {
250
+ const resolved = resolve(navigationProp.initializer, consts, new Set());
251
+ if (ts.isStringLiteral(resolved) && (resolved.text === 'browse' || resolved.text === 'reference')) {
252
+ page.navigation = resolved.text;
253
+ }
254
+ else {
255
+ page.checkable = false;
256
+ }
257
+ }
258
+ const standaloneProp = findProp(pageObject, 'standalone');
259
+ if (standaloneProp)
260
+ page.standalone = stringLiteral(resolve(standaloneProp.initializer, consts, new Set()));
261
+ const tokenDefinitionProp = findProp(pageObject, 'tokenDefinition');
262
+ page.hasTokenDefinition = tokenDefinitionProp !== undefined;
263
+ if (tokenDefinitionProp) {
264
+ const tokenDefinition = readStaticTokenDefinition(tokenDefinitionProp.initializer, consts);
265
+ if (!tokenDefinition)
266
+ page.checkable = false;
267
+ else {
268
+ page.tokenDefinition = tokenDefinition;
269
+ // #61 geometry parity (design R4): the same titles/wide flags `buildCatalogSections` derives
270
+ // at runtime from `tokenDefinition.sections` — only when no separate `tokenSections` already
271
+ // set them (an explicit conflict is reported on its own terms by `cli/doctor.ts`; whichever
272
+ // geometry already won here doesn't change that).
273
+ if (page.tokenSectionTitles === undefined) {
274
+ page.tokenSectionTitles = tokenDefinition.sections.map((section) => section.title);
275
+ page.tokenSectionWide = tokenDefinition.sections.map((section) => section.wide ?? false);
276
+ page.tokenGallery = true;
277
+ }
278
+ }
279
+ }
238
280
  return page;
239
281
  }
240
282
  /** A literal array of string literals (or same-file `const` references to one) — undefined for
241
- * anything else, including an array containing even one non-literal element. */
242
- function readStringLiteralArray(node, consts) {
283
+ * anything else, including an array containing even one non-literal element. Exported for
284
+ * `cli/tokenDefinition.ts`'s own static read of a tokenDefinition section's `aliases` (#64 B5),
285
+ * the same leniency every other string-literal-array field here already gets. */
286
+ export function readStringLiteralArray(node, consts) {
243
287
  const resolved = resolve(node, consts, new Set());
244
288
  if (!ts.isArrayLiteralExpression(resolved))
245
289
  return undefined;
@@ -295,15 +339,19 @@ function readFamilyMembers(node, consts, gridNames, sourceFile) {
295
339
  if (!comparison)
296
340
  return undefined;
297
341
  }
342
+ const anatomyProp = findProp(resolvedElement, 'anatomy');
343
+ const memberIds = anatomyProp ? readAnatomyMemberIds(anatomyProp.initializer, consts) : undefined;
298
344
  members.push({
299
345
  id,
300
346
  component,
301
347
  parent,
302
348
  aliases,
303
349
  composedOf,
350
+ hasComposedOf: composedOfProp !== undefined,
304
351
  hasWhenToUse: findProp(resolvedElement, 'whenToUse') !== undefined,
305
352
  hasA11y: findProp(resolvedElement, 'a11y') !== undefined,
306
353
  hasExamples: Boolean(variants.items?.length) || Boolean(states.items?.length) || Boolean(comparison),
354
+ anatomy: memberIds ? { memberIds } : undefined,
307
355
  });
308
356
  }
309
357
  return members;
@@ -339,9 +387,9 @@ function readSections(node, consts) {
339
387
  }
340
388
  return sections;
341
389
  }
342
- /** A literal array of `ComposedOfEntry`-shaped object literals, read down to just `component`
343
- * (design §4: role/relationship are authored prose `doctor` never needs for its missing-
344
- * composition comparison). Undefined — not an empty array — for anything that isn't itself a
390
+ /** A literal array of `ComposedOfEntry`-shaped object literals, read down to `component` and a
391
+ * literal `relationship` (a non-literal one is left off the entry; `role` is authored prose
392
+ * `doctor` never needs). Undefined — not an empty array — for anything that isn't itself a
345
393
  * literal array, or whose element isn't an object literal with a literal `component`: `doctor`
346
394
  * must never mistake "couldn't read this" for "author declared no composition here". */
347
395
  function readComposedOf(node, consts) {
@@ -356,10 +404,51 @@ function readComposedOf(node, consts) {
356
404
  const component = propertyStringLiteral(resolvedElement, 'component', consts);
357
405
  if (component === undefined)
358
406
  return undefined;
359
- entries.push({ component });
407
+ const relationship = propertyStringLiteral(resolvedElement, 'relationship', consts);
408
+ entries.push(relationship === undefined ? { component } : { component, relationship });
360
409
  }
361
410
  return entries;
362
411
  }
412
+ /** #63 A4/A10: `CatalogMember.anatomy`/`CatalogPageInput.anatomy`'s own `groups[].items[]`, read
413
+ * down to just the literal `member` ids a `{ member: string }` item names — an `{ other: string }`
414
+ * item is skipped (it never names a descendant), and `description`/`picture` are never read (design
415
+ * parity with `readComposedOf`/`readRelatedPages`: `doctor`'s own rule never needs them). Undefined
416
+ * — never a partial read — for anything that isn't itself a literal `{ groups: [...] }` object with
417
+ * a literal `groups` array of literal `{ items: [...] }` objects whose own `items` array is itself
418
+ * literal; an `other`/`description`-shaped item still reads fine even though its own `member` (if
419
+ * any) is simply absent from the result. */
420
+ function readAnatomyMemberIds(node, consts) {
421
+ const resolved = resolve(node, consts, new Set());
422
+ if (!ts.isObjectLiteralExpression(resolved))
423
+ return undefined;
424
+ const groupsProp = findProp(resolved, 'groups');
425
+ if (!groupsProp)
426
+ return undefined;
427
+ const groupsResolved = resolve(groupsProp.initializer, consts, new Set());
428
+ if (!ts.isArrayLiteralExpression(groupsResolved))
429
+ return undefined;
430
+ const memberIds = [];
431
+ for (const groupElement of groupsResolved.elements) {
432
+ const group = resolve(groupElement, consts, new Set());
433
+ if (!ts.isObjectLiteralExpression(group))
434
+ return undefined;
435
+ const itemsProp = findProp(group, 'items');
436
+ if (!itemsProp)
437
+ return undefined;
438
+ const itemsResolved = resolve(itemsProp.initializer, consts, new Set());
439
+ if (!ts.isArrayLiteralExpression(itemsResolved))
440
+ return undefined;
441
+ for (const itemElement of itemsResolved.elements) {
442
+ const item = resolve(itemElement, consts, new Set());
443
+ if (!ts.isObjectLiteralExpression(item))
444
+ return undefined;
445
+ const member = propertyStringLiteral(item, 'member', consts);
446
+ if (member !== undefined)
447
+ memberIds.push(member);
448
+ }
449
+ }
450
+ return memberIds;
451
+ }
363
452
  /** A literal array of `RelatedPage`-shaped object literals, read down to just `page` (design
364
453
  * parity with `readComposedOf`: `note` is authored prose `doctor` never needs). Undefined — not
365
454
  * an empty array — for anything that isn't itself a literal array, or whose element isn't an
@@ -433,7 +522,7 @@ function topLevelConsts(sourceFile) {
433
522
  * reference to its own initializer (same pattern as `cli/tokens.ts`'s `readExpression`) — returns
434
523
  * the resolved expression node itself, not a value, so callers can pattern-match on its kind
435
524
  * (array/object literal, call expression, …). `seen` guards a reference cycle. */
436
- function resolve(node, consts, seen) {
525
+ export function resolve(node, consts, seen) {
437
526
  if (ts.isAsExpression(node) || ts.isParenthesizedExpression(node) || ts.isSatisfiesExpression(node)) {
438
527
  return resolve(node.expression, consts, seen);
439
528
  }
@@ -444,7 +533,7 @@ function resolve(node, consts, seen) {
444
533
  }
445
534
  return node;
446
535
  }
447
- function stringLiteral(node) {
536
+ export function stringLiteral(node) {
448
537
  return ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node) ? node.text : undefined;
449
538
  }
450
539
  /** `true`/`false` only from a known `true`/`false` literal; any other expression (a variable, a
@@ -464,11 +553,11 @@ function literalBooleanOrUnknown(node, consts) {
464
553
  function propertyName(name) {
465
554
  return ts.isIdentifier(name) || ts.isStringLiteral(name) ? name.text : name.getText();
466
555
  }
467
- function findProp(obj, name) {
556
+ export function findProp(obj, name) {
468
557
  const prop = obj.properties.find((p) => ts.isPropertyAssignment(p) && propertyName(p.name) === name);
469
558
  return prop;
470
559
  }
471
- function propertyStringLiteral(obj, name, consts) {
560
+ export function propertyStringLiteral(obj, name, consts) {
472
561
  const prop = findProp(obj, name);
473
562
  return prop ? stringLiteral(resolve(prop.initializer, consts, new Set())) : undefined;
474
563
  }
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Static, literal-only reader for `CatalogPageInput.tokenDefinition` — the same posture
3
+ * `cli/staticPage.ts`'s own `readStaticPage` already uses for every other field: no module
4
+ * resolution, no execution, a same-file `const` chase only (`resolve`, reused from
5
+ * `cli/staticPage.ts`). Reads structural metadata only (`kind`, section `id`/`title`/`desc`/`wide`,
6
+ * and each entry's own literal `id`, for duplicate detection) — never an entry's real value, which
7
+ * stays an opaque runtime import (design R4).
8
+ */
9
+ import * as ts from 'typescript';
10
+ import { findProp, propertyStringLiteral, readStringLiteralArray, resolve } from "./staticPage.js";
11
+ import { canonicalTokenTitle, isTokenKind } from "../native/catalog/tokenDefinition.js";
12
+ /** Reads `node` (a `tokenDefinition:` property initializer) down to `kind` and each section's
13
+ * `id`/`title`/`desc`/`wide`/entry-ids. Returns `undefined` — never a partial read — when `kind`
14
+ * isn't a literal, `sections` isn't a literal array, or any one section isn't a literal object
15
+ * with a literal `id` and `title`: the same "never a partial read" contract every other array
16
+ * reader in `cli/staticPage.ts` already follows (`readComposedOf`, `readSections`, …). An entry
17
+ * whose own `id` isn't literal is simply omitted from `entryIds` — informational leniency, since
18
+ * only the *structural* shape (kind/sections/ids) needs to be checkable, never an entry's real
19
+ * value (design R4: "no blanket uncheckability of otherwise literal page geometry"). */
20
+ export function readStaticTokenDefinition(node, consts) {
21
+ const resolved = resolve(node, consts, new Set());
22
+ if (!ts.isObjectLiteralExpression(resolved))
23
+ return undefined;
24
+ const kind = propertyStringLiteral(resolved, 'kind', consts);
25
+ if (kind === undefined)
26
+ return undefined;
27
+ const sectionsProp = findProp(resolved, 'sections');
28
+ if (!sectionsProp)
29
+ return undefined;
30
+ const sectionsResolved = resolve(sectionsProp.initializer, consts, new Set());
31
+ if (!ts.isArrayLiteralExpression(sectionsResolved))
32
+ return undefined;
33
+ const sections = [];
34
+ for (const element of sectionsResolved.elements) {
35
+ const sectionResolved = resolve(element, consts, new Set());
36
+ if (!ts.isObjectLiteralExpression(sectionResolved))
37
+ return undefined;
38
+ const id = propertyStringLiteral(sectionResolved, 'id', consts);
39
+ const title = propertyStringLiteral(sectionResolved, 'title', consts);
40
+ if (id === undefined || title === undefined)
41
+ return undefined;
42
+ const desc = propertyStringLiteral(sectionResolved, 'desc', consts);
43
+ const wideProp = findProp(sectionResolved, 'wide');
44
+ const wide = wideProp ? resolve(wideProp.initializer, consts, new Set()).kind === ts.SyntaxKind.TrueKeyword : false;
45
+ const entryIds = [];
46
+ let entriesOpaque = false;
47
+ const entriesProp = findProp(sectionResolved, 'entries');
48
+ if (entriesProp) {
49
+ const entriesResolved = resolve(entriesProp.initializer, consts, new Set());
50
+ if (ts.isArrayLiteralExpression(entriesResolved)) {
51
+ for (const entryElement of entriesResolved.elements) {
52
+ const entryResolved = resolve(entryElement, consts, new Set());
53
+ if (!ts.isObjectLiteralExpression(entryResolved)) {
54
+ entriesOpaque = true;
55
+ continue;
56
+ }
57
+ const entryId = propertyStringLiteral(entryResolved, 'id', consts);
58
+ if (entryId !== undefined)
59
+ entryIds.push(entryId);
60
+ else
61
+ entriesOpaque = true;
62
+ }
63
+ }
64
+ else {
65
+ entriesOpaque = true;
66
+ }
67
+ }
68
+ // #64 B1: this section's own sub-groups, read the same lenient way as its own entries above.
69
+ const groups = [];
70
+ let groupsOpaque = false;
71
+ const groupsProp = findProp(sectionResolved, 'groups');
72
+ if (groupsProp) {
73
+ const groupsResolved = resolve(groupsProp.initializer, consts, new Set());
74
+ if (ts.isArrayLiteralExpression(groupsResolved)) {
75
+ for (const groupElement of groupsResolved.elements) {
76
+ const groupResolved = resolve(groupElement, consts, new Set());
77
+ if (!ts.isObjectLiteralExpression(groupResolved)) {
78
+ groupsOpaque = true;
79
+ continue;
80
+ }
81
+ const groupId = propertyStringLiteral(groupResolved, 'id', consts);
82
+ const groupTitle = propertyStringLiteral(groupResolved, 'title', consts);
83
+ if (groupId === undefined || groupTitle === undefined) {
84
+ groupsOpaque = true;
85
+ continue;
86
+ }
87
+ const groupEntryIds = [];
88
+ let groupEntriesOpaque = false;
89
+ const groupEntriesProp = findProp(groupResolved, 'entries');
90
+ if (groupEntriesProp) {
91
+ const groupEntriesResolved = resolve(groupEntriesProp.initializer, consts, new Set());
92
+ if (ts.isArrayLiteralExpression(groupEntriesResolved)) {
93
+ for (const entryElement of groupEntriesResolved.elements) {
94
+ const entryResolved = resolve(entryElement, consts, new Set());
95
+ if (!ts.isObjectLiteralExpression(entryResolved)) {
96
+ groupEntriesOpaque = true;
97
+ continue;
98
+ }
99
+ const entryId = propertyStringLiteral(entryResolved, 'id', consts);
100
+ if (entryId !== undefined)
101
+ groupEntryIds.push(entryId);
102
+ else
103
+ groupEntriesOpaque = true;
104
+ }
105
+ }
106
+ else {
107
+ groupEntriesOpaque = true;
108
+ }
109
+ }
110
+ groups.push({ id: groupId, title: groupTitle, entryIds: groupEntryIds, entriesOpaque: groupEntriesOpaque });
111
+ }
112
+ }
113
+ else {
114
+ groupsOpaque = true;
115
+ }
116
+ }
117
+ // #64 B5: this section's own literal `aliases`, when every element resolves as a literal
118
+ // string — omitted (never a penalty) otherwise.
119
+ const aliasesProp = findProp(sectionResolved, 'aliases');
120
+ const aliases = aliasesProp ? readStringLiteralArray(aliasesProp.initializer, consts) : undefined;
121
+ sections.push({ id, title, desc, wide, entryIds, entriesOpaque, groups, groupsOpaque, ...(aliases !== undefined ? { aliases } : null) });
122
+ }
123
+ return { kind, sections };
124
+ }
125
+ export { canonicalTokenTitle, isTokenKind };
@@ -132,23 +132,37 @@ export function resolveActiveFromHash(hash, order) {
132
132
  return idFromHash(hash, order);
133
133
  }
134
134
  /** Every member id other than the whole, every member alias, and every family page's own
135
- * (page-level) alias, mapped to where it resolves (spec §5.3 "Old addresses"). Pages without a
136
- * `family` contribute nothing. */
135
+ * (page-level) alias, mapped to where it resolves (spec §5.3 "Old addresses"). #64 B5: also, a
136
+ * tokenDefinition page's own page-level aliases (`tokenDefinitionAliases`) and each of its
137
+ * sections' own `aliases` — the same routing a family page's aliases already get, extended to
138
+ * tokenDefinition pages. A page with neither contributes nothing. */
137
139
  export function aliasTargets(sections) {
138
140
  const targets = new Map();
139
141
  for (const section of sections) {
140
142
  const family = section.family;
141
- if (!family)
142
- continue;
143
- for (const member of family.members) {
144
- if (member.id === family.whole)
145
- continue;
146
- targets.set(member.id, { pageId: section.id, memberId: member.id });
147
- for (const alias of member.aliases ?? [])
148
- targets.set(alias, { pageId: section.id, memberId: member.id });
143
+ if (family) {
144
+ for (const member of family.members) {
145
+ if (member.id === family.whole) {
146
+ // C3: the whole's own id already resolves via `order` (it equals the page's own id), but
147
+ // its own aliases were never registered anywhere — routing one to the page, with no member
148
+ // focus (unlike a non-whole member's alias, which focuses that member).
149
+ for (const alias of member.aliases ?? [])
150
+ targets.set(alias, { pageId: section.id });
151
+ continue;
152
+ }
153
+ targets.set(member.id, { pageId: section.id, memberId: member.id });
154
+ for (const alias of member.aliases ?? [])
155
+ targets.set(alias, { pageId: section.id, memberId: member.id });
156
+ }
157
+ for (const alias of family.aliases ?? [])
158
+ targets.set(alias, { pageId: section.id });
149
159
  }
150
- for (const alias of family.aliases ?? [])
160
+ for (const alias of section.tokenDefinitionAliases ?? [])
151
161
  targets.set(alias, { pageId: section.id });
162
+ for (const tdSection of section.tokenDefinition?.sections ?? []) {
163
+ for (const alias of tdSection.aliases ?? [])
164
+ targets.set(alias, { pageId: section.id, sectionId: tdSection.id });
165
+ }
152
166
  }
153
167
  return targets;
154
168
  }
@@ -171,6 +185,8 @@ export function resolveRoute(hash, order, aliases) {
171
185
  if (target) {
172
186
  if (target.memberId !== undefined)
173
187
  return { kind: 'member', pageId: target.pageId, memberId: target.memberId };
188
+ if (target.sectionId !== undefined)
189
+ return { kind: 'page', id: target.pageId, sectionId: target.sectionId };
174
190
  return { kind: 'page', id: target.pageId };
175
191
  }
176
192
  return { kind: 'notFound', raw: decoded };