@poodle64/ui 2026.9.11 → 2026.9.12

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.
@@ -84,6 +84,7 @@
84
84
  import Menu from '@lucide/svelte/icons/menu';
85
85
  import X from '@lucide/svelte/icons/x';
86
86
  import Search from '@lucide/svelte/icons/search';
87
+ import Settings from '@lucide/svelte/icons/settings';
87
88
  import Sun from '@lucide/svelte/icons/sun';
88
89
  import Moon from '@lucide/svelte/icons/moon';
89
90
  import PanelLeftClose from '@lucide/svelte/icons/panel-left-close';
@@ -107,6 +108,10 @@
107
108
  context,
108
109
  actions,
109
110
  identity,
111
+ settingsHref,
112
+ settingsLabel = 'Settings',
113
+ version,
114
+ versionHref,
110
115
  onSearch,
111
116
  searchLabel = 'Search…',
112
117
  searchShortcut = '⌘K',
@@ -162,8 +167,25 @@
162
167
  context?: Snippet;
163
168
  /** Trailing top-bar slot, before the theme toggle: app-level action buttons. */
164
169
  actions?: Snippet;
165
- /** The signed-in user surface. Rendered once, at the end of the top bar. */
170
+ /** The signed-in user surface. Rendered once, at the end of the top bar.
171
+ * It stays there at EVERY width rather than moving into the rail foot on
172
+ * desktop: the rail is an overlay drawer below `md`, so a rail-placed
173
+ * account is two taps away and who-am-I is hidden behind the menu; and
174
+ * the alternative, rendering it in both, is the duplication this file
175
+ * exists to avoid. */
166
176
  identity?: Snippet;
177
+ /** Where the Settings row at the rail's foot links. Absent renders no row.
178
+ * Settings sits at the foot because configuring the app is not navigating
179
+ * it, and the split reads without a label. */
180
+ settingsHref?: string;
181
+ settingsLabel?: string;
182
+ /** The build this person is looking at, e.g. `2026.9.3 · b032a3a`. Rendered
183
+ * as one muted line under the Settings row: no icon, no hover, not a nav
184
+ * row. It earns its place by answering "what am I running" the moment
185
+ * someone reports a bug, and it links into Settings where the full
186
+ * About section lives. */
187
+ version?: string;
188
+ versionHref?: string;
167
189
  /** Provide to render the search affordance. Usually opens a CommandPalette. */
168
190
  onSearch?: () => void;
169
191
  searchLabel?: string;
@@ -638,6 +660,41 @@
638
660
  label={navLabel}
639
661
  onNavigate={() => (mobileNavOpen = false)}
640
662
  />
663
+
664
+ <!--
665
+ The rail's foot. `mt-auto` pins it under the nav however short the
666
+ section list is, so it sits on the rail's bottom edge rather than
667
+ trailing the last section. Two things live here and nothing else:
668
+ where you configure the app, and which build you are looking at.
669
+
670
+ Settings is a nav row like any other and takes the same active and
671
+ hover treatment, because it IS a destination — it is last rather
672
+ than separate because configuring the app is not navigating it.
673
+ The version is not a row: no icon, no tile, no hover, one muted
674
+ line, so it reads as metadata and never competes for a click.
675
+ -->
676
+ {#if settingsHref || version}
677
+ <div class="ds-shell-rail-foot mt-auto" class:sr-only={railCollapsed && !mobileNavOpen}>
678
+ {#if settingsHref}
679
+ <a
680
+ href={settingsHref}
681
+ class="ds-nav-item"
682
+ aria-current={currentPath?.startsWith(settingsHref) ? 'page' : undefined}
683
+ onclick={() => (mobileNavOpen = false)}
684
+ >
685
+ <Settings class="ds-nav-icon size-[1.125rem] flex-none" aria-hidden="true" />
686
+ <span class="truncate">{settingsLabel}</span>
687
+ </a>
688
+ {/if}
689
+ {#if version}
690
+ {#if versionHref}
691
+ <a href={versionHref} class="ds-shell-version">{version}</a>
692
+ {:else}
693
+ <p class="ds-shell-version">{version}</p>
694
+ {/if}
695
+ {/if}
696
+ </div>
697
+ {/if}
641
698
  {#if collapsible && !railCollapsed && !mobileNavOpen}
642
699
  <!-- The rail's right edge: drag to resize, drag past the snap to
643
700
  collapse, arrows to nudge, Home to reset, double-click to reset.
@@ -124,8 +124,25 @@ type $$ComponentProps = {
124
124
  context?: Snippet;
125
125
  /** Trailing top-bar slot, before the theme toggle: app-level action buttons. */
126
126
  actions?: Snippet;
127
- /** The signed-in user surface. Rendered once, at the end of the top bar. */
127
+ /** The signed-in user surface. Rendered once, at the end of the top bar.
128
+ * It stays there at EVERY width rather than moving into the rail foot on
129
+ * desktop: the rail is an overlay drawer below `md`, so a rail-placed
130
+ * account is two taps away and who-am-I is hidden behind the menu; and
131
+ * the alternative, rendering it in both, is the duplication this file
132
+ * exists to avoid. */
128
133
  identity?: Snippet;
134
+ /** Where the Settings row at the rail's foot links. Absent renders no row.
135
+ * Settings sits at the foot because configuring the app is not navigating
136
+ * it, and the split reads without a label. */
137
+ settingsHref?: string;
138
+ settingsLabel?: string;
139
+ /** The build this person is looking at, e.g. `2026.9.3 · b032a3a`. Rendered
140
+ * as one muted line under the Settings row: no icon, no hover, not a nav
141
+ * row. It earns its place by answering "what am I running" the moment
142
+ * someone reports a bug, and it links into Settings where the full
143
+ * About section lives. */
144
+ version?: string;
145
+ versionHref?: string;
129
146
  /** Provide to render the search affordance. Usually opens a CommandPalette. */
130
147
  onSearch?: () => void;
131
148
  searchLabel?: string;
package/dist/styles.css CHANGED
@@ -793,6 +793,33 @@
793
793
  border-left: 1px solid color-mix(in oklch, var(--ds-nav-ink) 50%, transparent);
794
794
  }
795
795
 
796
+ /* The rail's foot: where you configure the app, and which build you are on.
797
+ Separated from the scrolling section list by a rule in the chrome's OWN
798
+ ink rather than `--border`, for the reason the group guide takes the same
799
+ treatment — a page-surface token cannot follow chrome an app has branded. */
800
+ .ds-shell-rail-foot {
801
+ display: flex;
802
+ flex-direction: column;
803
+ padding-top: 0.375rem;
804
+ border-top: 1px solid color-mix(in oklch, var(--ds-nav-ink) 22%, transparent);
805
+ }
806
+
807
+ /* Metadata, not a row. No icon, no tile, no hover: it answers "what am I
808
+ running" when someone reports a bug and otherwise stays out of the way.
809
+ `tabular-nums` so a version does not re-flow as the digits change. */
810
+ .ds-shell-version {
811
+ margin: 0;
812
+ padding: 0.25rem 0.75rem 0.375rem;
813
+ color: var(--ds-nav-ink-muted);
814
+ font-size: var(--ds-text-2xs);
815
+ font-variant-numeric: tabular-nums;
816
+ text-decoration: none;
817
+ }
818
+
819
+ a.ds-shell-version:hover {
820
+ color: var(--ds-nav-ink);
821
+ }
822
+
796
823
  /* `hidden` is an attribute the UA stylesheet honours at the weakest possible
797
824
  specificity, so the `display: flex` above beats it and a closed group would
798
825
  render wide open. This is not belt-and-braces; without it the attribute is
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poodle64/ui",
3
- "version": "2026.9.11",
3
+ "version": "2026.9.12",
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,7 +1,7 @@
1
1
  {
2
2
  "meta": {
3
3
  "package": "@poodle64/ui",
4
- "version": "2026.9.11",
4
+ "version": "2026.9.12",
5
5
  "generatedBy": "scripts/generate-registry.mjs",
6
6
  "source": "scripts/situations.json + package source (DO NOT EDIT the outputs by hand)",
7
7
  "componentCount": 57,
@@ -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.11`. 57 components, 15 situations.
4
+ Generated from `@poodle64/ui@2026.9.12`. 57 components, 15 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