@poodle64/ui 2026.9.16 → 2026.9.18

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.
@@ -0,0 +1,111 @@
1
+ <script lang="ts">
2
+ /**
3
+ * The settings destination: a grouped section list beside the section you are
4
+ * reading, inside AppShell's content area.
5
+ *
6
+ * Two PANES, not a panel and not a drawer. The distinction is load-bearing:
7
+ * a panel overlays and closes, and this package carries a standing directive
8
+ * against side panels for work you are actually doing. These panes divide and
9
+ * persist, each scrolling on its own, so a long settings page never pushes the
10
+ * section list out of reach.
11
+ *
12
+ * The list renders through AppNav rather than a row renderer of its own, and
13
+ * that is the whole reason this component is small. AppNav already owns the
14
+ * group eyebrow, the active row's leading edge and tint and weight, the
15
+ * `aria-current="page"`, the segment-safe prefix match, truncation, and the
16
+ * density ramp — and it is exported in its own right precisely so a navigation
17
+ * list that belongs in a PAGE is built from the same affordance rather than
18
+ * hand-copied. The estate has already paid for the alternative once: one app's
19
+ * inner nav had reimplemented the active indicator independently of its own
20
+ * top bar. A second row renderer here would re-commit that drift one
21
+ * destination down, on the one surface every app is about to adopt at once.
22
+ *
23
+ * Grouping is by WHOSE setting it is — the person's, then the workspace's —
24
+ * because that is the only split a reader can predict: "yours" changes what
25
+ * you see, "this company" changes what everyone sees. A group whose `items`
26
+ * is empty renders nothing (`toGroups` drops it), so an app with no personal
27
+ * sections yet can leave the group in its list rather than stubbing a row that
28
+ * leads nowhere.
29
+ *
30
+ * One label per row, nothing beneath it. A description long enough to be worth
31
+ * reading does not fit a 240px column: every one of them truncated to an
32
+ * ellipsis and told the reader less than the label already did (measured
33
+ * 16/09/2026 in a consumer, removed there the same day). The page introduces
34
+ * its own sections — that is what Panel's `description` is for.
35
+ *
36
+ * No "Settings" heading. The shell's bar names the section it is on as of
37
+ * 2026.9.16, so a heading here is the same word twice, one above the other.
38
+ *
39
+ * BELOW md the list STACKS above the content and the page scrolls as one.
40
+ * A horizontally scrollable strip of the same rows was the obvious
41
+ * alternative and loses on the requirement it was meant to serve — every
42
+ * section reachable in one tap. A strip fits about two and a half rows at
43
+ * 375px, so the rest are behind a sideways swipe with no scrollbar to
44
+ * advertise them: a swipe THEN a tap, where a stack is a scroll the reader is
45
+ * already doing. It also has to drop the group labels for width, which throws
46
+ * away the one split the reader could predict, and it needs a second row
47
+ * renderer in a horizontal mode — the drift this component exists to avoid.
48
+ *
49
+ * USE IT WITH `padded={false}` on AppShell. The list's border and tint have to
50
+ * reach the content area's edges or the two panes read as a floating card, and
51
+ * this component pads its own content pane to the shell's own rhythm
52
+ * (`CONTENT_PADDING`) so the settings destination is not the one route whose
53
+ * text sits somewhere else.
54
+ */
55
+ import type { Snippet } from 'svelte';
56
+ import type { HTMLAttributes } from 'svelte/elements';
57
+ import AppNav from '../app-shell/app-nav.svelte';
58
+ import { CONTENT_PADDING } from '../app-shell/padding.js';
59
+ import type { NavSource } from '../app-shell/types.js';
60
+ import { cn, type WithElementRef } from '../../../utils.js';
61
+
62
+ let {
63
+ sections,
64
+ currentPath,
65
+ listLabel = 'Settings sections',
66
+ children,
67
+ ref = $bindable(null),
68
+ class: className,
69
+ ...restProps
70
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
71
+ /**
72
+ * The sections, as the package's own navigation vocabulary: groups of
73
+ * `NavItem` (`{ heading, items }`), bare items, or a mix. The same type the
74
+ * rail takes, so a row is the same row everywhere in the household and an
75
+ * app types its settings list against something it already has.
76
+ *
77
+ * A row at the settings ROOT (`/settings`) needs `exact: true`, or its
78
+ * prefix match keeps it lit on every section beneath it alongside the
79
+ * section's own row.
80
+ */
81
+ sections: NavSource;
82
+ /** The active path. Apps pass `page.url.pathname`, as they do to AppShell. */
83
+ currentPath?: string;
84
+ /** The list's accessible name. */
85
+ listLabel?: string;
86
+ children: Snippet;
87
+ } = $props();
88
+ </script>
89
+
90
+ <div bind:this={ref} class={cn('ds-settings', className)} {...restProps}>
91
+ <!--
92
+ Width, tint and rule as utilities rather than in styles.css, because AppNav
93
+ declares `flex-1` on this same element: a base-layer `flex: none` loses to
94
+ a utility by layer, whatever order it is written in, while a utility passed
95
+ through `cn()` is resolved by tailwind-merge — `flex-none` and `flex-1` are
96
+ one group, so the last one wins and it is this one.
97
+
98
+ 15rem (240px) is measured, not picked: it is the width at which a
99
+ two-word section label ("Overhead suppliers", "Delegation limits") sits on
100
+ one line beside its icon at comfortable density.
101
+ -->
102
+ <AppNav
103
+ nav={sections}
104
+ {currentPath}
105
+ label={listLabel}
106
+ class="border-border bg-muted/30 flex-none border-b md:w-60 md:border-b-0 md:border-e"
107
+ />
108
+ <div class={cn('ds-settings-pane', CONTENT_PADDING)}>
109
+ {@render children()}
110
+ </div>
111
+ </div>
@@ -0,0 +1,78 @@
1
+ /**
2
+ * The settings destination: a grouped section list beside the section you are
3
+ * reading, inside AppShell's content area.
4
+ *
5
+ * Two PANES, not a panel and not a drawer. The distinction is load-bearing:
6
+ * a panel overlays and closes, and this package carries a standing directive
7
+ * against side panels for work you are actually doing. These panes divide and
8
+ * persist, each scrolling on its own, so a long settings page never pushes the
9
+ * section list out of reach.
10
+ *
11
+ * The list renders through AppNav rather than a row renderer of its own, and
12
+ * that is the whole reason this component is small. AppNav already owns the
13
+ * group eyebrow, the active row's leading edge and tint and weight, the
14
+ * `aria-current="page"`, the segment-safe prefix match, truncation, and the
15
+ * density ramp — and it is exported in its own right precisely so a navigation
16
+ * list that belongs in a PAGE is built from the same affordance rather than
17
+ * hand-copied. The estate has already paid for the alternative once: one app's
18
+ * inner nav had reimplemented the active indicator independently of its own
19
+ * top bar. A second row renderer here would re-commit that drift one
20
+ * destination down, on the one surface every app is about to adopt at once.
21
+ *
22
+ * Grouping is by WHOSE setting it is — the person's, then the workspace's —
23
+ * because that is the only split a reader can predict: "yours" changes what
24
+ * you see, "this company" changes what everyone sees. A group whose `items`
25
+ * is empty renders nothing (`toGroups` drops it), so an app with no personal
26
+ * sections yet can leave the group in its list rather than stubbing a row that
27
+ * leads nowhere.
28
+ *
29
+ * One label per row, nothing beneath it. A description long enough to be worth
30
+ * reading does not fit a 240px column: every one of them truncated to an
31
+ * ellipsis and told the reader less than the label already did (measured
32
+ * 16/09/2026 in a consumer, removed there the same day). The page introduces
33
+ * its own sections — that is what Panel's `description` is for.
34
+ *
35
+ * No "Settings" heading. The shell's bar names the section it is on as of
36
+ * 2026.9.16, so a heading here is the same word twice, one above the other.
37
+ *
38
+ * BELOW md the list STACKS above the content and the page scrolls as one.
39
+ * A horizontally scrollable strip of the same rows was the obvious
40
+ * alternative and loses on the requirement it was meant to serve — every
41
+ * section reachable in one tap. A strip fits about two and a half rows at
42
+ * 375px, so the rest are behind a sideways swipe with no scrollbar to
43
+ * advertise them: a swipe THEN a tap, where a stack is a scroll the reader is
44
+ * already doing. It also has to drop the group labels for width, which throws
45
+ * away the one split the reader could predict, and it needs a second row
46
+ * renderer in a horizontal mode — the drift this component exists to avoid.
47
+ *
48
+ * USE IT WITH `padded={false}` on AppShell. The list's border and tint have to
49
+ * reach the content area's edges or the two panes read as a floating card, and
50
+ * this component pads its own content pane to the shell's own rhythm
51
+ * (`CONTENT_PADDING`) so the settings destination is not the one route whose
52
+ * text sits somewhere else.
53
+ */
54
+ import type { Snippet } from 'svelte';
55
+ import type { HTMLAttributes } from 'svelte/elements';
56
+ import type { NavSource } from '../app-shell/types.js';
57
+ import { type WithElementRef } from '../../../utils.js';
58
+ type $$ComponentProps = WithElementRef<HTMLAttributes<HTMLDivElement>> & {
59
+ /**
60
+ * The sections, as the package's own navigation vocabulary: groups of
61
+ * `NavItem` (`{ heading, items }`), bare items, or a mix. The same type the
62
+ * rail takes, so a row is the same row everywhere in the household and an
63
+ * app types its settings list against something it already has.
64
+ *
65
+ * A row at the settings ROOT (`/settings`) needs `exact: true`, or its
66
+ * prefix match keeps it lit on every section beneath it alongside the
67
+ * section's own row.
68
+ */
69
+ sections: NavSource;
70
+ /** The active path. Apps pass `page.url.pathname`, as they do to AppShell. */
71
+ currentPath?: string;
72
+ /** The list's accessible name. */
73
+ listLabel?: string;
74
+ children: Snippet;
75
+ };
76
+ declare const SettingsShell: import("svelte").Component<$$ComponentProps, {}, "ref">;
77
+ type SettingsShell = ReturnType<typeof SettingsShell>;
78
+ export default SettingsShell;
package/dist/styles.css CHANGED
@@ -770,27 +770,24 @@
770
770
  color: var(--ds-nav-ink);
771
771
  }
772
772
 
773
- /* The disclosed rows. The indent is measured against the parent's own
774
- geometry rather than picked: .ds-nav-item pads 0.625rem and the icon is
775
- 1.125rem, so the icon's centre line sits at 1.1875rem — where the guide
776
- border goes — and 0.625rem of padding here plus the child's own 0.625rem
777
- lands its label at 2.5rem, exactly under the parent's. The border is a real
778
- affordance at fifteen rows, not decoration: it is what makes the run
779
- scannable as one section. */
780
773
  /* The disclosed rows carry no guide rule. Indentation and the children's own
781
774
  icons already read as one group, and a line down the side is a second
782
- signal for a grouping that was never ambiguous. The indent is still
783
- measured against the parent's geometry rather than picked: `.ds-nav-item`
784
- pads 0.625rem and the icon is 1.125rem, so 1.1875rem plus 0.625rem here
785
- plus the child's own 0.625rem lands its label at 2.5rem, exactly under the
786
- parent's. */
775
+ signal for a grouping that was never ambiguous. 8f39b02 removed the rule
776
+ but not the 1px it was contributing to the indent (design-system#38): the
777
+ border sat between this margin and this padding, so the child label
778
+ landed 1px further right than the padding alone now gives it. Restored
779
+ here instead, so the geometry — not a rule nobody can see — still carries
780
+ it: `.ds-nav-item` pads 0.625rem and the icon is 1.125rem, so 1.1875rem
781
+ (margin) + 0.625rem plus the former border's 1px (this padding) +
782
+ 0.625rem (the child's own padding) lands its label at 2.5rem, exactly
783
+ under the parent's. */
787
784
  .ds-nav-children {
788
785
  display: flex;
789
786
  flex-direction: column;
790
787
  gap: 0.125rem;
791
788
  margin-top: 0.125rem;
792
789
  margin-left: 1.1875rem;
793
- padding-left: 0.625rem;
790
+ padding-left: calc(0.625rem + 1px);
794
791
  }
795
792
 
796
793
  /* The rail's foot: where you configure the app, and which build you are on.
@@ -881,6 +878,64 @@
881
878
  padding-inline: 0;
882
879
  }
883
880
 
881
+ /* The settings destination's two panes (SettingsShell).
882
+
883
+ Only the SHAPE is here; the list's width, tint and rule are utilities on the
884
+ AppNav element itself, because AppNav declares `flex-1` there and a
885
+ base-layer `flex: none` would lose to it by layer order rather than by
886
+ position — the cascade trap recorded against `.ds-nav`'s ink. What is left is
887
+ the axis swap and the scroll bounding, which nothing else declares.
888
+
889
+ 48rem is Tailwind's own --breakpoint-md, written literally for the reason
890
+ `.ds-shell-rail` writes it literally: a media query cannot read a custom
891
+ property, so an app that retunes md retunes this with it. */
892
+ .ds-settings {
893
+ display: flex;
894
+ flex-direction: column;
895
+ flex: 1 1 auto;
896
+ }
897
+
898
+ /* `min-height: 0` is what buys the two independent scrollers, and it is
899
+ deliberately NOT set below md. A flex item is floored at its content height
900
+ unless it is allowed to shrink past it, so without this neither pane ever
901
+ scrolls and the whole content area does instead. Below md that floor is
902
+ exactly what is wanted: the list stacks above the content and the page
903
+ scrolls as one, which is the only sane arrangement on a phone — two nested
904
+ scrollers at 375px is the sideways-scroll hazard this package already carries
905
+ a gate for. */
906
+ @media (min-width: 48rem) {
907
+ .ds-settings {
908
+ flex-direction: row;
909
+ min-height: 0;
910
+ }
911
+ }
912
+
913
+ .ds-settings-pane {
914
+ flex: 1 1 auto;
915
+ min-width: 0;
916
+ }
917
+
918
+ /* The pane stacks its sections itself, so a settings page writes a run of
919
+ Panels and no wrapper — the framing markup this surface exists to stop a
920
+ consumer writing. 1.25rem is the package's existing section rhythm, the same
921
+ gap `.ds-dialog-section` puts between adjacent sections.
922
+
923
+ A margin rule rather than `display: flex; gap`, because at md and up this box
924
+ is a height-bounded scroller: as a flex column its children carry
925
+ `flex-shrink: 1` and get squashed toward min-content before the scrollbar is
926
+ ever reached, so a tall section would compress instead of scrolling. Block
927
+ flow overflows, which is what a scroller wants. */
928
+ .ds-settings-pane > * + * {
929
+ margin-top: 1.25rem;
930
+ }
931
+
932
+ @media (min-width: 48rem) {
933
+ .ds-settings-pane {
934
+ min-height: 0;
935
+ overflow-y: auto;
936
+ }
937
+ }
938
+
884
939
  /* A hard ceiling on the whole shell, in the one unit that names the actual
885
940
  drawn surface rather than a percentage of an ancestor. `.ds-shell` is
886
941
  already `flex` with `overflow-hidden` in the component markup, which
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poodle64/ui",
3
- "version": "2026.9.16",
3
+ "version": "2026.9.18",
4
4
  "description": "Household shared component layer: shadcn-svelte primitives (bits-ui) plus the composed page chrome every app builds its routes from, restyled by each app's @poodle64/design-tokens alias layer. One fix reaches every app.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "meta": {
3
3
  "package": "@poodle64/ui",
4
- "version": "2026.9.16",
4
+ "version": "2026.9.18",
5
5
  "generatedBy": "scripts/generate-registry.mjs",
6
6
  "source": "scripts/situations.json + package source (DO NOT EDIT the outputs by hand)",
7
- "componentCount": 57,
8
- "situationCount": 15
7
+ "componentCount": 60,
8
+ "situationCount": 17
9
9
  },
10
10
  "situations": [
11
11
  {
@@ -33,6 +33,13 @@
33
33
  "import": "@poodle64/ui/stat-list",
34
34
  "props": "items, title?, info?",
35
35
  "insteadOf": "a hand-built <dl> or a stack of label/value <div>s"
36
+ },
37
+ {
38
+ "name": "FactGrid",
39
+ "dir": "fact-grid",
40
+ "import": "@poodle64/ui/fact-grid",
41
+ "props": "facts",
42
+ "insteadOf": "the same figures inside a Panel or DetailPanel that already supplies the card — StatList's own border would nest a box in a box; FactGrid is the borderless, wrapping-grid answer for that spot"
36
43
  }
37
44
  ]
38
45
  },
@@ -150,8 +157,8 @@
150
157
  "name": "Panel",
151
158
  "dir": "panel",
152
159
  "import": "@poodle64/ui/panel",
153
- "props": "children, title?, subtitle?, icon?, action?, pad?",
154
- "insteadOf": "raw Card + CardHeader + CardTitle assembled by hand on every route"
160
+ "props": "children, title?, subtitle?, description?, icon?, action?, footer?, tone?, pad?",
161
+ "insteadOf": "raw Card + CardHeader + CardTitle assembled by hand on every route; also the settings section — there is no separate SettingsSection, `description` wraps, `footer` carries Save/Cancel, `tone=\"destructive\"` is the danger zone"
155
162
  }
156
163
  ]
157
164
  },
@@ -274,6 +281,20 @@
274
281
  }
275
282
  ]
276
283
  },
284
+ {
285
+ "key": "document-evidence",
286
+ "title": "A document a machine read, with evidence outlined",
287
+ "description": "One page at a time, fitted to its pane both ways so an A4 statement and a tall receipt alike fill it, with what a reading points at outlined on the page — a hovered or clicked region with its label, a dashed 'where it looked' set, and a focus that zooms in and centres. The app supplies the page render (image, PDF canvas, anything) and the region coordinates as page fractions; this owns the fit/zoom/pan and the overlay only.",
288
+ "components": [
289
+ {
290
+ "name": "PageCanvas",
291
+ "dir": "page-canvas",
292
+ "import": "@poodle64/ui/page-canvas",
293
+ "props": "pageCount, page, currentPage?, regions?, lookedRegions?, activeRegionId?, focusedRegionId?, zoom?",
294
+ "insteadOf": "a hand-built page viewer per app (godswood's DocumentViewer, pebblestone's PdfViewer) each re-deriving fit/zoom/pan, now with no way to outline what was read (#839)"
295
+ }
296
+ ]
297
+ },
277
298
  {
278
299
  "key": "page-frame",
279
300
  "title": "The frame every route shares",
@@ -323,6 +344,20 @@
323
344
  }
324
345
  ]
325
346
  },
347
+ {
348
+ "key": "settings-destination",
349
+ "title": "The settings destination",
350
+ "description": "The one settings surface: a grouped section list beside the section you are reading, inside AppShell’s content area (give AppShell padded={false}). Sections are grouped by whose setting it is — the person’s, then the workspace’s. The sections themselves are Panels.",
351
+ "components": [
352
+ {
353
+ "name": "SettingsShell",
354
+ "dir": "settings-shell",
355
+ "import": "@poodle64/ui/settings-shell",
356
+ "props": "sections, children, currentPath?, listLabel?",
357
+ "insteadOf": "a hand-built two-pane settings layout, or a card of tab links in a grid, per app"
358
+ }
359
+ ]
360
+ },
326
361
  {
327
362
  "key": "dialog-and-overlay",
328
363
  "title": "A dialog or overlay",
@@ -1,7 +1,7 @@
1
1
  # @poodle64/ui — situation → component map
2
2
 
3
3
  <!-- GENERATED by scripts/generate-registry.mjs from scripts/situations.json + package source. DO NOT EDIT. -->
4
- Generated from `@poodle64/ui@2026.9.16`. 57 components, 15 situations.
4
+ Generated from `@poodle64/ui@2026.9.18`. 60 components, 17 situations.
5
5
 
6
6
  **Read this before writing a `<div>`.** Find the SITUATION you are in below, then compose the component named for it — do not hand-build it from raw `Card` or utility classes. Import is `import { Name } from '<import path>'`. Props marked `?` are optional. This map is the retrieval step the [`frontend-design` skill] makes mandatory; the [CHI 2026 study] measured composing-from-a-registry at 95% design-system compliance against 71% for writing the CSS from a prose style guide.
7
7
 
@@ -23,6 +23,7 @@ _A block of plain figures read at a glance — an 'at a glance' strip — where
23
23
  | Component | Import | Key props | Reach for it instead of |
24
24
  | --- | --- | --- | --- |
25
25
  | `StatList` | `@poodle64/ui/stat-list` | `items, title?, info?` | a hand-built <dl> or a stack of label/value <div>s |
26
+ | `FactGrid` | `@poodle64/ui/fact-grid` | `facts` | the same figures inside a Panel or DetailPanel that already supplies the card — StatList's own border would nest a box in a box; FactGrid is the borderless, wrapping-grid answer for that spot |
26
27
 
27
28
  ## Comparing magnitudes across rows
28
29
 
@@ -81,7 +82,7 @@ _The generic titled card every route reaches for — an optional header with ico
81
82
 
82
83
  | Component | Import | Key props | Reach for it instead of |
83
84
  | --- | --- | --- | --- |
84
- | `Panel` | `@poodle64/ui/panel` | `children, title?, subtitle?, icon?, action?, pad?` | raw Card + CardHeader + CardTitle assembled by hand on every route |
85
+ | `Panel` | `@poodle64/ui/panel` | `children, title?, subtitle?, description?, icon?, action?, footer?, tone?, pad?` | raw Card + CardHeader + CardTitle assembled by hand on every route; also the settings section — there is no separate SettingsSection, `description` wraps, `footer` carries Save/Cancel, `tone="destructive"` is the danger zone |
85
86
 
86
87
  ## A form the app itself describes
87
88
 
@@ -118,6 +119,14 @@ _The estate's shared document-library surfaces — a faceted catalogue, one coll
118
119
  | `DocumentDetail` | `@poodle64/ui/document-detail` | `document, loading?, error?, collectionHref?, onOpenCollection?, actions?, children?` | a bespoke document page: identity dl, locations, tags and memberships hand-laid-out per app |
119
120
  | `SearchResults` | `@poodle64/ui/search-results` | `results, query?, total?, loading?, error?, resultHref?, onOpen?, onRetry?` | a hand-built ranked hit list minting its own highlight and relevance styling |
120
121
 
122
+ ## A document a machine read, with evidence outlined
123
+
124
+ _One page at a time, fitted to its pane both ways so an A4 statement and a tall receipt alike fill it, with what a reading points at outlined on the page — a hovered or clicked region with its label, a dashed 'where it looked' set, and a focus that zooms in and centres. The app supplies the page render (image, PDF canvas, anything) and the region coordinates as page fractions; this owns the fit/zoom/pan and the overlay only._
125
+
126
+ | Component | Import | Key props | Reach for it instead of |
127
+ | --- | --- | --- | --- |
128
+ | `PageCanvas` | `@poodle64/ui/page-canvas` | `pageCount, page, currentPage?, regions?, lookedRegions?, activeRegionId?, focusedRegionId?, zoom?` | a hand-built page viewer per app (godswood's DocumentViewer, pebblestone's PdfViewer) each re-deriving fit/zoom/pan, now with no way to outline what was read (#839) |
129
+
121
130
  ## The frame every route shares
122
131
 
123
132
  _The standing chrome — the app shell and its nav, the one page-title treatment, the persistent right-hand context column._
@@ -131,6 +140,14 @@ _The standing chrome — the app shell and its nav, the one page-title treatment
131
140
  | `ContextColumn` | `@poodle64/ui/context-column` | `stats, statsTitle?, statsInfo?, detail?, ariaLabel?` | a hand-built right-hand <aside> with a stat card and a detail pane |
132
141
  | `AppIdentity` | `@poodle64/ui/app-identity` | `user, workspace?, role?, entitlements?, onSignOut, onAccountSettings?, onSwitchTheme?` | a hand-rolled account/avatar dropdown wired to raw DropdownMenu.* + Avatar.* |
133
142
 
143
+ ## The settings destination
144
+
145
+ _The one settings surface: a grouped section list beside the section you are reading, inside AppShell’s content area (give AppShell padded={false}). Sections are grouped by whose setting it is — the person’s, then the workspace’s. The sections themselves are Panels._
146
+
147
+ | Component | Import | Key props | Reach for it instead of |
148
+ | --- | --- | --- | --- |
149
+ | `SettingsShell` | `@poodle64/ui/settings-shell` | `sections, children, currentPath?, listLabel?` | a hand-built two-pane settings layout, or a card of tab links in a grid, per app |
150
+
134
151
  ## A dialog or overlay
135
152
 
136
153
  _A modal task and the pieces around it — the dialog frame, a section within its body, a ⌘K palette, an inline (i) hint._