@poodle64/ui 2026.9.3 → 2026.9.4

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.
@@ -23,6 +23,13 @@
23
23
  * baked in here: `onSignOut` and `onAccountSettings` are callbacks. Theme
24
24
  * follows AppShell's own `onToggleTheme` pattern — override it, or accept
25
25
  * mode-watcher's `toggleMode` by default.
26
+ *
27
+ * `onManageMembers` exists for the caller a workspace switcher has nothing
28
+ * to show: `WorkspaceMenu` (the factory template) renders nothing in the
29
+ * bar for a single-member workspace, and this is where Members lands
30
+ * instead (operator ruling, 07/09/2026, master-project#291's org-of-one
31
+ * case). Wire it whenever the app has a members route, whether or not it
32
+ * also renders a switcher.
26
33
  */
27
34
  import { toggleMode } from 'mode-watcher';
28
35
  import * as DropdownMenu from '../dropdown-menu/index.js';
@@ -35,6 +42,7 @@
35
42
  entitlements = [],
36
43
  onSignOut,
37
44
  onAccountSettings,
45
+ onManageMembers,
38
46
  onSwitchTheme
39
47
  }: {
40
48
  user: { username: string; display_name: string | null; email: string | null };
@@ -47,6 +55,19 @@
47
55
  onSignOut: () => void;
48
56
  /** Optional until the app has an account-settings destination to send it to. */
49
57
  onAccountSettings?: () => void;
58
+ /**
59
+ * Reach the workspace's members surface from here. Ignored while
60
+ * `workspace` is unset.
61
+ *
62
+ * A workspace holding exactly one member has nothing to switch, so the
63
+ * app's own workspace switcher renders nothing for it (operator ruling,
64
+ * 07/09/2026) — and Members was that switcher's one non-switching act,
65
+ * the route an org-of-one needs to make its first grant. This is where
66
+ * that act lives once the switcher is gone; a switcher still showing for
67
+ * several workspaces keeps its own Members entry too, so the two never
68
+ * disagree about where it is.
69
+ */
70
+ onManageMembers?: () => void;
50
71
  /** Override the theme action. Defaults to mode-watcher's toggleMode, matching AppShell. */
51
72
  onSwitchTheme?: () => void;
52
73
  } = $props();
@@ -63,7 +84,31 @@
63
84
  if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
64
85
  return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
65
86
  }
66
- const initials = $derived(initialsOf(shown));
87
+
88
+ /**
89
+ * Initials for a bare HANDLE — no display name to fall back on.
90
+ *
91
+ * A handle has no word boundaries a name has: `initialsOf` slicing the
92
+ * first two characters of one unbroken token produces a pair that reads
93
+ * as real initials while meaning nothing (operator ruling, 07/09/2026).
94
+ * Where the handle itself encodes two parts — `firstname.lastname`,
95
+ * `firstname-lastname` — one letter from each reads the same as a real
96
+ * name's initials; where it does not, only the first letter is honest.
97
+ */
98
+ function handleInitials(handle: string): string {
99
+ const parts = handle.trim().split(/[.-]+/).filter(Boolean);
100
+ if (parts.length === 0) return '';
101
+ if (parts.length === 1) return parts[0].slice(0, 1).toUpperCase();
102
+ return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
103
+ }
104
+
105
+ // Never text in the bar (operator ruling, 07/09/2026): with no display
106
+ // name, the trigger below still renders only this avatar, initialled off
107
+ // the handle rather than off `shown`, which for this branch just IS the
108
+ // handle.
109
+ const initials = $derived(
110
+ user.display_name ? initialsOf(user.display_name) : handleInitials(user.username)
111
+ );
67
112
 
68
113
  function switchTheme() {
69
114
  if (onSwitchTheme) onSwitchTheme();
@@ -128,6 +173,9 @@
128
173
  {/each}
129
174
  </div>
130
175
  {/if}
176
+ {#if onManageMembers}
177
+ <DropdownMenu.Item onSelect={onManageMembers}>Members</DropdownMenu.Item>
178
+ {/if}
131
179
  {/if}
132
180
  <DropdownMenu.Separator />
133
181
  <DropdownMenu.Item onSelect={switchTheme}>Switch theme</DropdownMenu.Item>
@@ -13,6 +13,19 @@ type $$ComponentProps = {
13
13
  onSignOut: () => void;
14
14
  /** Optional until the app has an account-settings destination to send it to. */
15
15
  onAccountSettings?: () => void;
16
+ /**
17
+ * Reach the workspace's members surface from here. Ignored while
18
+ * `workspace` is unset.
19
+ *
20
+ * A workspace holding exactly one member has nothing to switch, so the
21
+ * app's own workspace switcher renders nothing for it (operator ruling,
22
+ * 07/09/2026) — and Members was that switcher's one non-switching act,
23
+ * the route an org-of-one needs to make its first grant. This is where
24
+ * that act lives once the switcher is gone; a switcher still showing for
25
+ * several workspaces keeps its own Members entry too, so the two never
26
+ * disagree about where it is.
27
+ */
28
+ onManageMembers?: () => void;
16
29
  /** Override the theme action. Defaults to mode-watcher's toggleMode, matching AppShell. */
17
30
  onSwitchTheme?: () => void;
18
31
  };
package/package.json CHANGED
@@ -1,10 +1,13 @@
1
1
  {
2
2
  "name": "@poodle64/ui",
3
- "version": "2026.9.3",
3
+ "version": "2026.9.4",
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",
7
- "repository": "github:radar-hooves/design-system",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/radar-hooves/design-system.git"
10
+ },
8
11
  "publishConfig": {
9
12
  "registry": "https://registry.npmjs.org",
10
13
  "access": "public",
@@ -32,7 +35,7 @@
32
35
  }
33
36
  },
34
37
  "bin": {
35
- "ds-check-colour-surface": "./bin/check-colour-surface.mjs"
38
+ "ds-check-colour-surface": "bin/check-colour-surface.mjs"
36
39
  },
37
40
  "peerDependencies": {
38
41
  "bits-ui": "^2.18.1",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "meta": {
3
3
  "package": "@poodle64/ui",
4
- "version": "2026.9.3",
4
+ "version": "2026.9.4",
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": 56,
@@ -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.3`. 56 components, 15 situations.
4
+ Generated from `@poodle64/ui@2026.9.4`. 56 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