@boost33/ui 0.5.0 → 0.6.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 (50) hide show
  1. package/CHANGELOG.md +2894 -0
  2. package/README.md +11 -12
  3. package/dist/components/AccountMenu/AccountMenu.js +66 -0
  4. package/dist/components/AccountMenu/AccountMenu.js.map +1 -0
  5. package/dist/components/AccountMenu/AccountMenu.module.css.js +42 -0
  6. package/dist/components/AccountMenu/AccountMenu.module.css.js.map +1 -0
  7. package/dist/components/AccountMenu/icons.js +32 -0
  8. package/dist/components/AccountMenu/icons.js.map +1 -0
  9. package/dist/components/AccountMenu/labels.js +14 -0
  10. package/dist/components/AccountMenu/labels.js.map +1 -0
  11. package/dist/components/CommandPalette/CommandPalette.js +65 -0
  12. package/dist/components/CommandPalette/CommandPalette.js.map +1 -0
  13. package/dist/components/CommandPalette/CommandPalette.module.css.js +34 -0
  14. package/dist/components/CommandPalette/CommandPalette.module.css.js.map +1 -0
  15. package/dist/components/CommandPalette/icons.js +23 -0
  16. package/dist/components/CommandPalette/icons.js.map +1 -0
  17. package/dist/components/CommandPalette/labels.js +19 -0
  18. package/dist/components/CommandPalette/labels.js.map +1 -0
  19. package/dist/components/LaunchLoader/LaunchLoader.d.ts +8 -4
  20. package/dist/components/LaunchLoader/LaunchLoader.js +22 -35
  21. package/dist/components/LaunchLoader/LaunchLoader.js.map +1 -1
  22. package/dist/components/LaunchLoader/LaunchLoader.module.css.js +16 -18
  23. package/dist/components/LaunchLoader/LaunchLoader.module.css.js.map +1 -1
  24. package/dist/components/NotificationCenter/NotificationCenter.js +67 -0
  25. package/dist/components/NotificationCenter/NotificationCenter.js.map +1 -0
  26. package/dist/components/NotificationCenter/NotificationCenter.module.css.js +54 -0
  27. package/dist/components/NotificationCenter/NotificationCenter.module.css.js.map +1 -0
  28. package/dist/components/NotificationCenter/icons.js +45 -0
  29. package/dist/components/NotificationCenter/icons.js.map +1 -0
  30. package/dist/components/NotificationCenter/labels.js +26 -0
  31. package/dist/components/NotificationCenter/labels.js.map +1 -0
  32. package/dist/components/SettingsFrame/SettingsFrame.d.ts +91 -0
  33. package/dist/components/SettingsFrame/SettingsFrame.js +109 -0
  34. package/dist/components/SettingsFrame/SettingsFrame.js.map +1 -0
  35. package/dist/components/SettingsFrame/SettingsFrame.module.css.js +74 -0
  36. package/dist/components/SettingsFrame/SettingsFrame.module.css.js.map +1 -0
  37. package/dist/components/SettingsFrame/icons.d.ts +10 -0
  38. package/dist/components/SettingsFrame/icons.js +37 -0
  39. package/dist/components/SettingsFrame/icons.js.map +1 -0
  40. package/dist/components/SettingsFrame/labels.d.ts +36 -0
  41. package/dist/components/SettingsFrame/labels.js +18 -0
  42. package/dist/components/SettingsFrame/labels.js.map +1 -0
  43. package/dist/index.d.ts +18 -1
  44. package/dist/index.js +46 -32
  45. package/dist/index.js.map +1 -1
  46. package/dist/launch-gradient.d.ts +27 -103
  47. package/dist/launch-gradient.js +4 -56
  48. package/dist/launch-gradient.js.map +1 -1
  49. package/dist/style.css +1 -1
  50. package/package.json +3 -2
@@ -1 +1 @@
1
- {"version":3,"file":"LaunchLoader.js","sources":["../../../src/components/LaunchLoader/LaunchLoader.tsx"],"sourcesContent":["import { useId, type CSSProperties, type ReactNode } from 'react'\nimport { DotMatrixLoader } from '../DotMatrixLoader/DotMatrixLoader'\nimport {\n LAUNCH_MESH,\n launchGround,\n launchRamp,\n launchShapeStyle,\n launchSolid,\n type LaunchGradientStops,\n} from '../../launch-gradient'\nimport styles from './LaunchLoader.module.css'\n\nexport interface LaunchLoaderProps {\n /**\n * What is happening, in words -- \"Opening Boost Score\". It may be one line for\n * the whole launch, which is what the first consumers pass, or rewritten as\n * real milestones land, in which case each rewrite is announced. It is the\n * only text on the screen and the progress bar's name.\n *\n * Taken whole rather than assembled from a destination name, because where a\n * product's name sits in the sentence is the language's to decide, not this\n * package's -- the same reason WalletChip's labels take whole templates.\n */\n caption: string\n /** The destination's four gradient stops, darkest first. See LaunchGradientStops. */\n stops: LaunchGradientStops\n /**\n * 0-100, or `null`/omitted while the page has no honest number. Indeterminate\n * omits `aria-valuenow` entirely: passing 0 to mean \"unknown\" announces 0%.\n * Values outside 0-100 are clamped.\n */\n progress?: number | null\n /**\n * The colour the dots and the bar are drawn in, before blending. Defaults to\n * white, the caption's colour (decided 14 Oct 2026). Both are drawn with\n * `soft-light` at 75% opacity, which a colour passes through too.\n */\n dotColor?: string\n /**\n * Whether the content behind this screen is still loading. Sets `aria-busy`\n * on the root; set it to false the moment the real content lands.\n */\n busy?: boolean\n /**\n * An optional layer painted over the mesh and under the content --\n * where an animated backdrop goes. Never required: the screen is complete\n * without it, and dropped entirely under `prefers-contrast`.\n */\n backdrop?: ReactNode\n className?: string\n}\n\nfunction clampProgress(value: number): number {\n return Math.min(100, Math.max(0, value))\n}\n\n/**\n * The launch screen every product shows while another product opens (B33-1389).\n * Presentational: it knows no product, fetches nothing and owns no timing. The\n * page drives the caption and the progress; this draws them.\n *\n * No \"use client\" and no client-only hook: useId is in React's server build, so\n * this renders -- and its dots animate -- straight from server HTML.\n */\nexport function LaunchLoader({\n caption,\n stops,\n progress = null,\n dotColor,\n busy = true,\n backdrop,\n className,\n}: LaunchLoaderProps) {\n const barId = useId()\n const captionId = useId()\n const determinate = typeof progress === 'number' && Number.isFinite(progress)\n const value = determinate ? Math.round(clampProgress(progress)) : null\n\n const ramp = launchRamp(stops)\n const rootStyle = {\n '--launch-ground': launchGround(stops),\n '--launch-solid': launchSolid(stops),\n ...(dotColor ? { '--launch-accent-input': dotColor } : {}),\n } as CSSProperties\n\n return (\n <div\n className={className ? `${styles.root} ${className}` : styles.root}\n style={rootStyle}\n aria-busy={busy}\n aria-describedby={barId}\n data-launch-loader=\"\"\n >\n {/* The mesh: solid shapes melted by one blur on their container. Every\n shape is styled inline, so the composition paints from the HTML\n alone, in the same chunk as the rest of the component -- on both\n sides of a hop, from the same props. It drifts once and settles back\n on this exact frame (see the module's .mesh rule). */}\n <div className={styles.mesh} aria-hidden=\"true\">\n {LAUNCH_MESH.map((shape, i) => (\n <div\n key={i}\n className={styles.shape}\n style={launchShapeStyle(shape, ramp)}\n />\n ))}\n </div>\n\n {backdrop ? <div className={styles.backdrop}>{backdrop}</div> : null}\n\n <div className={styles.content}>\n {/* Decorative: the caption already says everything the dots do. */}\n {/* Coloured by the .dots class, not DotMatrixLoader's `color` prop: an\n inline colour would outrank the prefers-contrast branch. */}\n <DotMatrixLoader label={null} className={styles.dots} />\n\n {/* Outside the live region on purpose. The caption's region is atomic,\n so a bar inside it would re-announce everything on every tick. */}\n <div\n id={barId}\n className={styles.bar}\n role=\"progressbar\"\n aria-labelledby={captionId}\n aria-valuemin={0}\n aria-valuemax={100}\n aria-valuenow={value ?? undefined}\n data-indeterminate={value === null}\n style={value === null ? undefined : ({ '--launch-progress': value } as CSSProperties)}\n >\n {/* Separate layers on purpose: the track blends into the mesh, the\n fill does not. A fill inside the track's blend group would blend\n with it and lose the 3:1 it needs against it. */}\n <div className={styles.track} />\n <div className={styles.fill} />\n </div>\n\n {/* The single source of the words: the bar's name and the live region\n in one node, so nothing is read twice.\n\n aria-live stays even though the first consumers pass one static\n line (\"Opening Boost Score\", decided 14 Oct 2026 -- a prerendered\n arrival gives no honest signal for a middle phase). It is inert\n while the text does not change, and it is what makes a changing\n caption announce if a consumer passes one: `caption` is a prop, and\n phased captions are the case this markup was designed for. */}\n <p id={captionId} className={styles.caption} aria-live=\"polite\">\n {caption}\n </p>\n </div>\n </div>\n )\n}\n"],"names":["clampProgress","value","LaunchLoader","caption","stops","progress","dotColor","busy","backdrop","className","barId","useId","captionId","ramp","launchRamp","rootStyle","launchGround","launchSolid","jsxs","styles","jsx","LAUNCH_MESH","shape","i","launchShapeStyle","DotMatrixLoader"],"mappings":";;;;;AAoDA,SAASA,EAAcC,GAAuB;AAC5C,SAAO,KAAK,IAAI,KAAK,KAAK,IAAI,GAAGA,CAAK,CAAC;AACzC;AAUO,SAASC,EAAa;AAAA,EAC3B,SAAAC;AAAA,EACA,OAAAC;AAAA,EACA,UAAAC,IAAW;AAAA,EACX,UAAAC;AAAA,EACA,MAAAC,IAAO;AAAA,EACP,UAAAC;AAAA,EACA,WAAAC;AACF,GAAsB;AACpB,QAAMC,IAAQC,EAAA,GACRC,IAAYD,EAAA,GAEZV,IADc,OAAOI,KAAa,YAAY,OAAO,SAASA,CAAQ,IAChD,KAAK,MAAML,EAAcK,CAAQ,CAAC,IAAI,MAE5DQ,IAAOC,EAAWV,CAAK,GACvBW,IAAY;AAAA,IAChB,mBAAmBC,EAAaZ,CAAK;AAAA,IACrC,kBAAkBa,EAAYb,CAAK;AAAA,IACnC,GAAIE,IAAW,EAAE,yBAAyBA,MAAa,CAAA;AAAA,EAAC;AAG1D,SACE,gBAAAY;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAWT,IAAY,GAAGU,EAAO,IAAI,IAAIV,CAAS,KAAKU,EAAO;AAAA,MAC9D,OAAOJ;AAAA,MACP,aAAWR;AAAA,MACX,oBAAkBG;AAAA,MAClB,sBAAmB;AAAA,MAOnB,UAAA;AAAA,QAAA,gBAAAU,EAAC,OAAA,EAAI,WAAWD,EAAO,MAAM,eAAY,QACtC,UAAAE,EAAY,IAAI,CAACC,GAAOC,MACvB,gBAAAH;AAAA,UAAC;AAAA,UAAA;AAAA,YAEC,WAAWD,EAAO;AAAA,YAClB,OAAOK,EAAiBF,GAAOT,CAAI;AAAA,UAAA;AAAA,UAF9BU;AAAA,QAAA,CAIR,GACH;AAAA,QAECf,IAAW,gBAAAY,EAAC,OAAA,EAAI,WAAWD,EAAO,UAAW,aAAS,IAAS;AAAA,QAEhE,gBAAAD,EAAC,OAAA,EAAI,WAAWC,EAAO,SAIrB,UAAA;AAAA,UAAA,gBAAAC,EAACK,GAAA,EAAgB,OAAO,MAAM,WAAWN,EAAO,MAAM;AAAA,UAItD,gBAAAD;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,IAAIR;AAAA,cACJ,WAAWS,EAAO;AAAA,cAClB,MAAK;AAAA,cACL,mBAAiBP;AAAA,cACjB,iBAAe;AAAA,cACf,iBAAe;AAAA,cACf,iBAAeX,KAAS;AAAA,cACxB,sBAAoBA,MAAU;AAAA,cAC9B,OAAOA,MAAU,OAAO,SAAa,EAAE,qBAAqBA,EAAA;AAAA,cAK5D,UAAA;AAAA,gBAAA,gBAAAmB,EAAC,OAAA,EAAI,WAAWD,EAAO,MAAA,CAAO;AAAA,gBAC9B,gBAAAC,EAAC,OAAA,EAAI,WAAWD,EAAO,KAAA,CAAM;AAAA,cAAA;AAAA,YAAA;AAAA,UAAA;AAAA,UAY/B,gBAAAC,EAAC,OAAE,IAAIR,GAAW,WAAWO,EAAO,SAAS,aAAU,UACpD,UAAAhB,EAAA,CACH;AAAA,QAAA,EAAA,CACF;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGN;"}
1
+ {"version":3,"file":"LaunchLoader.js","sources":["../../../src/components/LaunchLoader/LaunchLoader.tsx"],"sourcesContent":["import { useId, type CSSProperties, type ReactNode } from 'react'\nimport { DotMatrixLoader } from '../DotMatrixLoader/DotMatrixLoader'\nimport type { LaunchGradientStops } from '../../launch-gradient'\nimport styles from './LaunchLoader.module.css'\n\nexport interface LaunchLoaderProps {\n /**\n * What is happening, in words -- \"Opening Boost Score\". It may be one line for\n * the whole launch, which is what the first consumers pass, or rewritten as\n * real milestones land, in which case each rewrite is announced. It is the\n * only text on the screen and the progress bar's name.\n *\n * Taken whole rather than assembled from a destination name, because where a\n * product's name sits in the sentence is the language's to decide, not this\n * package's -- the same reason WalletChip's labels take whole templates.\n */\n caption: string\n /**\n * @deprecated Ignored since 0.5.1 (B33-1401). Every product's launch screen\n * is the same Boost33 background, built in. Optional and still typed only so\n * a consumer on `^0.5.0` that passes it keeps compiling; stop passing it.\n */\n stops?: LaunchGradientStops\n /**\n * 0-100, or `null`/omitted while the page has no honest number. Indeterminate\n * omits `aria-valuenow` entirely: passing 0 to mean \"unknown\" announces 0%.\n * Values outside 0-100 are clamped.\n */\n progress?: number | null\n /**\n * The colour the dots and the bar are drawn in, before blending. Defaults to\n * white, the caption's colour (decided 14 Oct 2026). Both are drawn with\n * `soft-light` at 75% opacity, which a colour passes through too.\n */\n dotColor?: string\n /**\n * Whether the content behind this screen is still loading. Sets `aria-busy`\n * on the root; set it to false the moment the real content lands.\n */\n busy?: boolean\n /**\n * An optional layer painted over the glow and under the content --\n * where an animated backdrop goes. Never required: the screen is complete\n * without it, and dropped entirely under `prefers-contrast`.\n */\n backdrop?: ReactNode\n className?: string\n}\n\nfunction clampProgress(value: number): number {\n return Math.min(100, Math.max(0, value))\n}\n\n/**\n * The launch screen every product shows while another product opens (B33-1389).\n * Presentational: it knows no product, fetches nothing and owns no timing. The\n * page drives the caption and the progress; this draws them.\n *\n * No \"use client\" and no client-only hook: useId is in React's server build, so\n * this renders -- and its dots animate -- straight from server HTML.\n */\nexport function LaunchLoader({\n caption,\n progress = null,\n dotColor,\n busy = true,\n backdrop,\n className,\n}: LaunchLoaderProps) {\n const barId = useId()\n const captionId = useId()\n const determinate = typeof progress === 'number' && Number.isFinite(progress)\n const value = determinate ? Math.round(clampProgress(progress)) : null\n\n // `stops` is not read: the background is the same for every product\n // (B33-1401). It is left out of the destructuring above rather than taken\n // and discarded, so nothing here can start depending on it again.\n const rootStyle = dotColor ? ({ '--launch-accent-input': dotColor } as CSSProperties) : undefined\n\n return (\n <div\n className={className ? `${styles.root} ${className}` : styles.root}\n style={rootStyle}\n aria-busy={busy}\n aria-describedby={barId}\n data-launch-loader=\"\"\n >\n {/* The glow: one blurred ellipse of brand orange rising from below the\n middle of the screen, on the root's black. Drawn entirely by the\n stylesheet and taking no prop, so both sides of a hop paint the same\n screen. The old mesh was styled inline only because its colours came\n from props; nothing here does. */}\n <div className={styles.glow} aria-hidden=\"true\" />\n\n {backdrop ? <div className={styles.backdrop}>{backdrop}</div> : null}\n\n <div className={styles.content}>\n {/* Decorative: the caption already says everything the dots do. */}\n {/* Coloured by the .dots class, not DotMatrixLoader's `color` prop: an\n inline colour would outrank the prefers-contrast branch. */}\n <DotMatrixLoader label={null} className={styles.dots} />\n\n {/* Outside the live region on purpose. The caption's region is atomic,\n so a bar inside it would re-announce everything on every tick. */}\n <div\n id={barId}\n className={styles.bar}\n role=\"progressbar\"\n aria-labelledby={captionId}\n aria-valuemin={0}\n aria-valuemax={100}\n aria-valuenow={value ?? undefined}\n data-indeterminate={value === null}\n style={value === null ? undefined : ({ '--launch-progress': value } as CSSProperties)}\n >\n {/* Separate layers on purpose: the track blends into the glow, the\n fill does not. A fill inside the track's blend group would blend\n with it and lose the 3:1 it needs against it. */}\n <div className={styles.track} />\n <div className={styles.fill} />\n </div>\n\n {/* The single source of the words: the bar's name and the live region\n in one node, so nothing is read twice.\n\n aria-live stays even though the first consumers pass one static\n line (\"Opening Boost Score\", decided 14 Oct 2026 -- a prerendered\n arrival gives no honest signal for a middle phase). It is inert\n while the text does not change, and it is what makes a changing\n caption announce if a consumer passes one: `caption` is a prop, and\n phased captions are the case this markup was designed for. */}\n <p id={captionId} className={styles.caption} aria-live=\"polite\">\n {caption}\n </p>\n </div>\n </div>\n )\n}\n"],"names":["clampProgress","value","LaunchLoader","caption","progress","dotColor","busy","backdrop","className","barId","useId","captionId","rootStyle","jsxs","styles","jsx","DotMatrixLoader"],"mappings":";;;;AAiDA,SAASA,EAAcC,GAAuB;AAC5C,SAAO,KAAK,IAAI,KAAK,KAAK,IAAI,GAAGA,CAAK,CAAC;AACzC;AAUO,SAASC,EAAa;AAAA,EAC3B,SAAAC;AAAA,EACA,UAAAC,IAAW;AAAA,EACX,UAAAC;AAAA,EACA,MAAAC,IAAO;AAAA,EACP,UAAAC;AAAA,EACA,WAAAC;AACF,GAAsB;AACpB,QAAMC,IAAQC,EAAA,GACRC,IAAYD,EAAA,GAEZT,IADc,OAAOG,KAAa,YAAY,OAAO,SAASA,CAAQ,IAChD,KAAK,MAAMJ,EAAcI,CAAQ,CAAC,IAAI,MAK5DQ,IAAYP,IAAY,EAAE,yBAAyBA,MAA+B;AAExF,SACE,gBAAAQ;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAWL,IAAY,GAAGM,EAAO,IAAI,IAAIN,CAAS,KAAKM,EAAO;AAAA,MAC9D,OAAOF;AAAA,MACP,aAAWN;AAAA,MACX,oBAAkBG;AAAA,MAClB,sBAAmB;AAAA,MAOnB,UAAA;AAAA,QAAA,gBAAAM,EAAC,OAAA,EAAI,WAAWD,EAAO,MAAM,eAAY,QAAO;AAAA,QAE/CP,IAAW,gBAAAQ,EAAC,OAAA,EAAI,WAAWD,EAAO,UAAW,aAAS,IAAS;AAAA,QAEhE,gBAAAD,EAAC,OAAA,EAAI,WAAWC,EAAO,SAIrB,UAAA;AAAA,UAAA,gBAAAC,EAACC,GAAA,EAAgB,OAAO,MAAM,WAAWF,EAAO,MAAM;AAAA,UAItD,gBAAAD;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,IAAIJ;AAAA,cACJ,WAAWK,EAAO;AAAA,cAClB,MAAK;AAAA,cACL,mBAAiBH;AAAA,cACjB,iBAAe;AAAA,cACf,iBAAe;AAAA,cACf,iBAAeV,KAAS;AAAA,cACxB,sBAAoBA,MAAU;AAAA,cAC9B,OAAOA,MAAU,OAAO,SAAa,EAAE,qBAAqBA,EAAA;AAAA,cAK5D,UAAA;AAAA,gBAAA,gBAAAc,EAAC,OAAA,EAAI,WAAWD,EAAO,MAAA,CAAO;AAAA,gBAC9B,gBAAAC,EAAC,OAAA,EAAI,WAAWD,EAAO,KAAA,CAAM;AAAA,cAAA;AAAA,YAAA;AAAA,UAAA;AAAA,UAY/B,gBAAAC,EAAC,OAAE,IAAIJ,GAAW,WAAWG,EAAO,SAAS,aAAU,UACpD,UAAAX,EAAA,CACH;AAAA,QAAA,EAAA,CACF;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGN;"}
@@ -1,26 +1,24 @@
1
- const t = "_root_1xhf4_6", o = "_mesh_1xhf4_8", _ = "_shape_1xhf4_81", c = "_backdrop_1xhf4_90", s = "_content_1xhf4_13", n = "_dots_1xhf4_127", a = "_bar_1xhf4_160", h = "_track_1xhf4_168", f = "_fill_1xhf4_176", r = "_caption_1xhf4_202", e = {
2
- root: t,
3
- mesh: o,
4
- shape: _,
5
- backdrop: c,
6
- content: s,
1
+ const o = "_root_1pg11_6", t = "_glow_1pg11_9", _ = "_backdrop_1pg11_103", c = "_content_1pg11_16", n = "_dots_1pg11_139", p = "_bar_1pg11_178", s = "_track_1pg11_186", a = "_fill_1pg11_194", r = "_caption_1pg11_220", g = {
2
+ root: o,
3
+ glow: t,
4
+ backdrop: _,
5
+ content: c,
7
6
  dots: n,
8
- bar: a,
9
- track: h,
10
- fill: f,
7
+ bar: p,
8
+ track: s,
9
+ fill: a,
11
10
  caption: r
12
11
  };
13
12
  export {
14
- c as backdrop,
15
- a as bar,
13
+ _ as backdrop,
14
+ p as bar,
16
15
  r as caption,
17
- s as content,
18
- e as default,
16
+ c as content,
17
+ g as default,
19
18
  n as dots,
20
- f as fill,
21
- o as mesh,
22
- t as root,
23
- _ as shape,
24
- h as track
19
+ a as fill,
20
+ t as glow,
21
+ o as root,
22
+ s as track
25
23
  };
26
24
  //# sourceMappingURL=LaunchLoader.module.css.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"LaunchLoader.module.css.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;"}
1
+ {"version":3,"file":"LaunchLoader.module.css.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;"}
@@ -0,0 +1,67 @@
1
+ import { jsxs as r, jsx as a, Fragment as u } from "react/jsx-runtime";
2
+ import { useId as f } from "react";
3
+ import { ProductMark as b } from "../ProductMark/ProductMark.js";
4
+ import { EmptyBell as v, SourceBellMark as T } from "./icons.js";
5
+ import { resolveNotificationCenterLabels as g } from "./labels.js";
6
+ import e from "./NotificationCenter.module.css.js";
7
+ const h = 22;
8
+ function C({
9
+ items: n,
10
+ viewAllHref: t,
11
+ labels: p,
12
+ linkComponent: N
13
+ }) {
14
+ const s = g(p), c = N ?? "a", d = f(), i = n.filter((l) => l.unread === !0).length, o = n.length === 0;
15
+ return (
16
+ // Named by the heading it already draws rather than by a second copy of the
17
+ // same word in an aria-label (ADR-0010; see `title` in labels.ts).
18
+ /* @__PURE__ */ r("div", { className: e.panel, "aria-labelledby": d, children: [
19
+ /* @__PURE__ */ r("div", { className: e.header, children: [
20
+ /* @__PURE__ */ a("h2", { className: e.title, id: d, children: s.title }),
21
+ !o && /* @__PURE__ */ a("button", { type: "button", className: e.markAll, children: s.markAllAsRead })
22
+ ] }),
23
+ o ? /* @__PURE__ */ r("div", { className: e.empty, children: [
24
+ /* @__PURE__ */ a(v, {}),
25
+ /* @__PURE__ */ r("div", { className: e.emptyText, children: [
26
+ /* @__PURE__ */ a("p", { className: e.emptyTitle, children: s.emptyTitle }),
27
+ /* @__PURE__ */ a("p", { className: e.emptyBody, children: s.emptyBody })
28
+ ] })
29
+ ] }) : /* @__PURE__ */ r(u, { children: [
30
+ /* @__PURE__ */ r("div", { className: e.tabs, children: [
31
+ /* @__PURE__ */ a("button", { type: "button", className: e.tab, "aria-pressed": "true", children: s.all }),
32
+ /* @__PURE__ */ r("button", { type: "button", className: e.tab, "aria-pressed": "false", children: [
33
+ s.unread,
34
+ i > 0 && /* @__PURE__ */ a("span", { className: e.count, children: i })
35
+ ] })
36
+ ] }),
37
+ /* @__PURE__ */ a("ul", { className: e.list, children: n.map((l) => {
38
+ const m = l.unread === !0, y = l.href != null ? c : "div";
39
+ return /* @__PURE__ */ a("li", { children: /* @__PURE__ */ r(
40
+ y,
41
+ {
42
+ className: e.row,
43
+ href: l.href,
44
+ "data-unread": m ? "true" : void 0,
45
+ children: [
46
+ l.slug != null ? /* @__PURE__ */ a(b, { slug: l.slug, size: h, className: e.sourceMark }) : /* @__PURE__ */ a(T, { size: h }),
47
+ /* @__PURE__ */ r("span", { className: e.rowText, children: [
48
+ /* @__PURE__ */ a("span", { className: e.rowTitle, children: l.title }),
49
+ /* @__PURE__ */ a("span", { className: e.rowMeta, children: l.dateTime != null ? /* @__PURE__ */ a("time", { dateTime: l.dateTime, children: s.meta(l.source, l.time) }) : s.meta(l.source, l.time) })
50
+ ] }),
51
+ /* @__PURE__ */ a("span", { className: e.unreadSlot, children: m && /* @__PURE__ */ r(u, { children: [
52
+ /* @__PURE__ */ a("span", { className: e.dot }),
53
+ /* @__PURE__ */ a("span", { className: e.srOnly, children: s.unreadItem })
54
+ ] }) })
55
+ ]
56
+ }
57
+ ) }, l.id);
58
+ }) }),
59
+ t != null && /* @__PURE__ */ a("div", { className: e.footer, children: /* @__PURE__ */ a(c, { className: e.viewAll, href: t, children: s.viewAll }) })
60
+ ] })
61
+ ] })
62
+ );
63
+ }
64
+ export {
65
+ C as NotificationCenter
66
+ };
67
+ //# sourceMappingURL=NotificationCenter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NotificationCenter.js","sources":["../../../src/components/NotificationCenter/NotificationCenter.tsx"],"sourcesContent":["// Not a client module, and that is a statement about this stage rather than\n// about the component. Stage A holds no state, wires no handler and reads no\n// clock, so there is nothing here a Server Component cannot carry. Stage B adds\n// `useAnchoredOverlay` and the directive with it -- see the note on the panel\n// below, and tests/guards.test.ts, which pins the exact set of built modules\n// carrying \"use client\" so the directive cannot appear here unnoticed.\n\nimport { useId, type ElementType } from 'react'\nimport { ProductMark } from '../ProductMark/ProductMark'\nimport { EmptyBell, SourceBellMark } from './icons'\nimport { resolveNotificationCenterLabels, type NotificationCenterLabels } from './labels'\nimport styles from './NotificationCenter.module.css'\n\n/**\n * The size the source mark renders at, in px.\n *\n * 22, which is `--spacing-5_5` written as a number because `ProductMark.size` is\n * a React prop and cannot read a custom property. ProductMark's own default is\n * 20 and the AppSwitcher's rows deliberately take it -- that is the Wallet's\n * size at the Wallet's call sites, settled against three approved baselines, and\n * its comment warns against passing a size at all. This is the other case the\n * comment contemplates and not a contradiction of it: there is no Wallet\n * rendering of this panel and no approved baseline to move, so 22 is the drawing\n * on page 004 reproduced rather than a settled measurement overruled. The person\n * at the fidelity gate settles it (CLAUDE.md, \"The three components the Wallet\n * never rendered\"), and until then nothing here is authoritative.\n */\nconst SOURCE_MARK_PX = 22\n\n/**\n * One notification, as the host supplies it. **This package invents no backend\n * and fetches nothing** -- the array is the host's, and so is every value in a\n * row (B33-494's data decision, 2026-10-06; CLAUDE.md, \"it renders what it is\n * given\").\n *\n * The shape is deliberately generic. Nothing in it names a lead, a campaign or a\n * prospect, and nothing in it should: a field that carried product meaning is\n * how a shared component turns back into four private ones (CLAUDE.md). A\n * notification here is a title, where it came from, when, whether it has been\n * read, and optionally somewhere to go.\n */\nexport interface NotificationCenterItem {\n /** Stable identity for the row, the host's. Used as the React key, never rendered. */\n id: string\n /** What happened, in the host's words. The row's first line. */\n title: string\n /**\n * Where it came from, in the host's words -- a product's display name, or\n * something that is not a product at all (\"Account\"). The first half of the\n * row's second line.\n *\n * A display string and not an identifier: `slug` below is what selects the\n * artwork, and the two are separate for the same reason `AppSwitcherProduct`\n * keeps `slug` and `name` apart. Identity owns names, and this package does\n * not translate them.\n */\n source: string\n /**\n * When it happened, **already formatted by the host** -- \"12 minutes ago\",\n * \"Yesterday\", \"3 Oct\". The second half of the row's second line.\n *\n * **This package does not format it, and the choice is deliberate.** The chip\n * formats its figures from a `locale` prop (B33-876), so the obvious move is\n * to do the same here and take a `Date`. Three things separate the two.\n *\n * A number is a pure function of its value and a locale; a *relative* time is\n * not. It needs a `now`, so the component would have to read a clock, and then\n * re-read it on a timer or go stale on screen -- which is state, in a\n * component whose whole contract is that it has none.\n *\n * It would also make this component's own fidelity gate unreproducible. The\n * baseline for an unbuilt component is captured from the specimen route by the\n * person approving it (CLAUDE.md, tests/baselines/README.md \"Designed\n * baselines\"), and a panel that renders \"12 minutes ago\" from `Date.now()`\n * renders a different string an hour later, so the diff would fail for reasons\n * no change caused. `4,600` is `4,600` forever; \"12 minutes ago\" is not.\n *\n * And the hard part is editorial rather than linguistic. Where a product cuts\n * over from \"12 minutes ago\" to \"Yesterday\" to a date is a policy each product\n * is entitled to differ on, and `Intl.RelativeTimeFormat` does not decide it\n * -- a caller has to pick the value and the unit before it can be called. The\n * host already holds the raw timestamp under B33-494's decision; it is the\n * side that can answer.\n *\n * `dateTime` below is how the machine-readable value still reaches the page.\n */\n time: string\n /**\n * The machine-readable timestamp for the `<time>` element's `datetime`\n * attribute -- an ISO 8601 string. Optional: supply it and the row's time is a\n * `<time dateTime>`; omit it and the same text renders in a plain `<span>`.\n *\n * This is what `time` being preformatted costs and how the cost is paid back.\n * A relative phrase on its own tells assistive technology and anything else\n * reading the DOM nothing about *when*; the attribute carries the instant\n * without this package deciding how to say it.\n */\n dateTime?: string\n /**\n * Identity's product slug, selecting the ProductMark artwork for the row's\n * tile. Optional, because a notification need not come from a product -- an\n * account or security event does not, and the drawing's own fifth row is one.\n * Omit it and the row draws the panel's bell instead (see `SourceBellMark`).\n *\n * A slug this package has no artwork for degrades to the bare tile rather than\n * throwing, exactly as it does in the switcher (ADR-0003).\n */\n slug?: string\n /**\n * Where selecting this notification goes. Optional: a row with no `href` is\n * not a link and is drawn as plain content, which is the honest rendering for\n * something there is nothing to open for.\n *\n * A row that does navigate renders through `linkComponent` (ADR-0011).\n */\n href?: string\n /**\n * Whether this notification is still unread. Defaults to `false`.\n *\n * **The flag is spelled `unread` and defaults to not-lit on purpose**, which\n * is the same decision the TopBar's bell already carries and for the same\n * reason B33-494 records: a permanently lit marker on a panel nobody has\n * cleared trains people to ignore it, and that attention does not come back.\n * A host that does not track read state passes nothing and gets a quiet panel;\n * a host that does says so row by row.\n */\n unread?: boolean\n}\n\nexport interface NotificationCenterProps {\n /**\n * The notifications, in the order the host wants them read. Rendered whole --\n * this component does not sort, filter, page or truncate, because every one of\n * those is a decision about data it does not own.\n *\n * An empty array is not an error state. It is the empty rendering, and it is\n * the state most consumers see first: a host with no feed wired yet has\n * nothing to pass (B33-494).\n */\n items: NotificationCenterItem[]\n /**\n * Where the footer's \"View all notifications\" link goes. Omit it and the\n * footer is not rendered -- a link to a page the host does not have is worse\n * than no link.\n *\n * The empty rendering never draws the footer, whether this is supplied or not:\n * there is nothing to see more of.\n */\n viewAllHref?: string\n /**\n * Every user-facing string that is not row data. Each field defaults to\n * English, and a field left out -- or supplied as `undefined`, which is what a\n * missed next-intl key looks like -- keeps its default (B33-873).\n */\n labels?: Partial<NotificationCenterLabels>\n /**\n * The element a row with an `href` renders through, and the footer link with\n * it. Defaults to a plain `a`; a router-owning consumer passes its own so the\n * rows route client-side with the locale prefix intact (ADR-0011, B33-847).\n */\n linkComponent?: ElementType\n}\n\n/**\n * The panel behind the top bar's bell: everything the four products did, in one\n * list, with the unread ones marked (B33-494).\n *\n * **It is presentational and it owns no feed.** The notifications arrive as a\n * prop and this package neither fetches, stores nor subscribes to anything --\n * there is no cross-product event source in any repo, and a component that\n * implied one would make the shared shell the place four services integrate,\n * which is the opposite of the dependency direction every other component here\n * has. That was decided on the ticket on 2026-10-06; the feed is separate work,\n * outside 1.0.\n *\n * **This is Stage A: static.** The panel renders, the tabs and the header action\n * draw, and nothing responds to anything (CLAUDE.md, \"Presentational-first\").\n * There is no open state here because there is no trigger here either: the bell\n * is the TopBar's, and connecting the two -- `useAnchoredOverlay` for the shared\n * dismissal contract (ADR-0009), the bell's `aria-haspopup`/`aria-expanded`, the\n * filter the tabs select, the mark-all handler -- is Stage B, after a human has\n * approved what this draws.\n *\n * **A disclosure, not a menu** (ADR-0010): the panel carries no widget role, the\n * tabs are not an ARIA `tablist`, the rows are the real links they already are,\n * and Tab is the document's own order, so the header button, both filters, every\n * row and the footer link are each their own tab stop. An ARIA tab set or a\n * `menu` would promise a composite widget with one tab stop and arrow-key\n * traversal, which is the promise ADR-0010 took the switcher out of.\n */\nexport function NotificationCenter({\n items,\n viewAllHref,\n labels,\n linkComponent,\n}: NotificationCenterProps) {\n const t = resolveNotificationCenterLabels(labels)\n const Link: ElementType = linkComponent ?? 'a'\n const headingId = useId()\n const unreadCount = items.filter((item) => item.unread === true).length\n const isEmpty = items.length === 0\n\n return (\n // Named by the heading it already draws rather than by a second copy of the\n // same word in an aria-label (ADR-0010; see `title` in labels.ts).\n <div className={styles.panel} aria-labelledby={headingId}>\n <div className={styles.header}>\n <h2 className={styles.title} id={headingId}>\n {t.title}\n </h2>\n {/* Nothing to mark, and nothing to mark it on: the empty rendering drops\n the action rather than offering a control that would do nothing even\n once it is wired. */}\n {!isEmpty && (\n <button type=\"button\" className={styles.markAll}>\n {t.markAllAsRead}\n </button>\n )}\n </div>\n\n {isEmpty ? (\n <div className={styles.empty}>\n <EmptyBell />\n <div className={styles.emptyText}>\n <p className={styles.emptyTitle}>{t.emptyTitle}</p>\n <p className={styles.emptyBody}>{t.emptyBody}</p>\n </div>\n </div>\n ) : (\n <>\n {/* Two toggle buttons, not a `role=\"tablist\"`. The ARIA tabs pattern\n requires one tab stop for the set and arrow keys to move between\n them, which contradicts ADR-0010's \"Tab is the document's tab\n order, unchanged and unmanaged\" -- and the APG's own banner, \"No\n ARIA is better than Bad ARIA\". `aria-pressed` says which filter is\n on without claiming a composite widget.\n\n Both are pressed-state-only in Stage A: \"All\" is on because the\n list under it is every row. Which filter is selected becomes state\n when the filtering is wired, and the markup does not change when it\n does. */}\n <div className={styles.tabs}>\n <button type=\"button\" className={styles.tab} aria-pressed=\"true\">\n {t.all}\n </button>\n <button type=\"button\" className={styles.tab} aria-pressed=\"false\">\n {t.unread}\n {/* Counted off `items`, never passed in, so it cannot disagree\n with the list beneath it. Drawn only when there is something to\n count -- a \"0\" pill is a decoration that reads as a state. */}\n {unreadCount > 0 && <span className={styles.count}>{unreadCount}</span>}\n </button>\n </div>\n\n <ul className={styles.list}>\n {items.map((item) => {\n const unread = item.unread === true\n // A row that navigates is a link; a row with nowhere to go is not\n // dressed as one. `linkComponent` is only consulted for the linked\n // form, exactly as ADR-0011 has NavItem consult it.\n const Row: ElementType = item.href != null ? Link : 'div'\n\n return (\n <li key={item.id}>\n <Row\n className={styles.row}\n href={item.href}\n data-unread={unread ? 'true' : undefined}\n >\n {item.slug != null ? (\n <ProductMark slug={item.slug} size={SOURCE_MARK_PX} className={styles.sourceMark} />\n ) : (\n <SourceBellMark size={SOURCE_MARK_PX} />\n )}\n <span className={styles.rowText}>\n <span className={styles.rowTitle}>{item.title}</span>\n <span className={styles.rowMeta}>\n {/* The whole second line through one label, so a locale\n owns both the word order and the separator. The\n machine-readable instant rides on the <time> element\n when the host supplies one; the text is the host's\n either way. */}\n {item.dateTime != null ? (\n <time dateTime={item.dateTime}>{t.meta(item.source, item.time)}</time>\n ) : (\n t.meta(item.source, item.time)\n )}\n </span>\n </span>\n {/* A fixed lane, drawn on every row whether or not it holds\n a dot, so the dots line up in one column and a read row\n does not reflow its text wider than an unread one. */}\n <span className={styles.unreadSlot}>\n {unread && (\n <>\n <span className={styles.dot} />\n {/* The dot is a colour and nothing else, which reaches\n nobody on a screen reader. This is the same state\n in words (CLAUDE.md, \"Accessibility is part of\n done\"). */}\n <span className={styles.srOnly}>{t.unreadItem}</span>\n </>\n )}\n </span>\n </Row>\n </li>\n )\n })}\n </ul>\n\n {viewAllHref != null && (\n <div className={styles.footer}>\n <Link className={styles.viewAll} href={viewAllHref}>\n {t.viewAll}\n </Link>\n </div>\n )}\n </>\n )}\n </div>\n )\n}\n"],"names":["SOURCE_MARK_PX","NotificationCenter","items","viewAllHref","labels","linkComponent","t","resolveNotificationCenterLabels","Link","headingId","useId","unreadCount","item","isEmpty","styles","jsxs","jsx","EmptyBell","Fragment","unread","Row","ProductMark","SourceBellMark"],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ const t = "_panel_sl837_36", _ = "_header_sl837_54", s = "_title_sl837_62", o = "_markAll_sl837_73", l = "_tab_sl837_86", e = "_viewAll_sl837_87", r = "_tabs_sl837_96", n = "_count_sl837_131", c = "_list_sl837_141", a = "_row_sl837_156", y = "_sourceMark_sl837_187", p = "_sourceMarkGround_sl837_196", m = "_rowText_sl837_201", d = "_rowTitle_sl837_212", u = "_rowMeta_sl837_223", i = "_unreadSlot_sl837_234", w = "_dot_sl837_243", T = "_empty_sl837_258", k = "_emptyGlyph_sl837_267", M = "_emptyText_sl837_275", x = "_emptyTitle_sl837_282", b = "_emptyBody_sl837_290", h = "_footer_sl837_299", A = "_srOnly_sl837_321", G = {
2
+ panel: t,
3
+ header: _,
4
+ title: s,
5
+ markAll: o,
6
+ tab: l,
7
+ viewAll: e,
8
+ tabs: r,
9
+ count: n,
10
+ list: c,
11
+ row: a,
12
+ sourceMark: y,
13
+ sourceMarkGround: p,
14
+ rowText: m,
15
+ rowTitle: d,
16
+ rowMeta: u,
17
+ unreadSlot: i,
18
+ dot: w,
19
+ empty: T,
20
+ emptyGlyph: k,
21
+ emptyText: M,
22
+ emptyTitle: x,
23
+ emptyBody: b,
24
+ footer: h,
25
+ srOnly: A
26
+ };
27
+ export {
28
+ n as count,
29
+ G as default,
30
+ w as dot,
31
+ T as empty,
32
+ b as emptyBody,
33
+ k as emptyGlyph,
34
+ M as emptyText,
35
+ x as emptyTitle,
36
+ h as footer,
37
+ _ as header,
38
+ c as list,
39
+ o as markAll,
40
+ t as panel,
41
+ a as row,
42
+ u as rowMeta,
43
+ m as rowText,
44
+ d as rowTitle,
45
+ y as sourceMark,
46
+ p as sourceMarkGround,
47
+ A as srOnly,
48
+ l as tab,
49
+ r as tabs,
50
+ s as title,
51
+ i as unreadSlot,
52
+ e as viewAll
53
+ };
54
+ //# sourceMappingURL=NotificationCenter.module.css.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NotificationCenter.module.css.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,45 @@
1
+ import { jsxs as e, jsx as r } from "react/jsx-runtime";
2
+ import t from "./NotificationCenter.module.css.js";
3
+ function i() {
4
+ return /* @__PURE__ */ e(
5
+ "svg",
6
+ {
7
+ className: t.emptyGlyph,
8
+ viewBox: "0 0 16 16",
9
+ fill: "none",
10
+ stroke: "currentColor",
11
+ strokeWidth: "1.2",
12
+ strokeLinejoin: "round",
13
+ "aria-hidden": "true",
14
+ children: [
15
+ /* @__PURE__ */ r("path", { d: "M8 2.5C6 2.5 4.6 4 4.6 6V8.3C4.6 8.9 4.4 9.4 4 9.8L3.3 10.6C3 10.9 3.2 11.5 3.7 11.5H12.3C12.8 11.5 13 10.9 12.7 10.6L12 9.8C11.6 9.4 11.4 8.9 11.4 8.3V6C11.4 4 10 2.5 8 2.5Z" }),
16
+ /* @__PURE__ */ r("path", { d: "M6.5 13C6.8 13.5 7.4 13.8 8 13.8C8.6 13.8 9.2 13.5 9.5 13", strokeLinecap: "round" })
17
+ ]
18
+ }
19
+ );
20
+ }
21
+ function a({ size: o }) {
22
+ return /* @__PURE__ */ e(
23
+ "svg",
24
+ {
25
+ className: t.sourceMark,
26
+ width: o,
27
+ height: o,
28
+ viewBox: "0 0 40 40",
29
+ xmlns: "http://www.w3.org/2000/svg",
30
+ "aria-hidden": "true",
31
+ children: [
32
+ /* @__PURE__ */ r("rect", { className: t.sourceMarkGround, width: "40", height: "40" }),
33
+ /* @__PURE__ */ e("g", { transform: "translate(10 10) scale(1.25)", fill: "none", stroke: "currentColor", strokeWidth: "1.3", strokeLinejoin: "round", children: [
34
+ /* @__PURE__ */ r("path", { d: "M8 2.5C6 2.5 4.6 4 4.6 6V8.3C4.6 8.9 4.4 9.4 4 9.8L3.3 10.6C3 10.9 3.2 11.5 3.7 11.5H12.3C12.8 11.5 13 10.9 12.7 10.6L12 9.8C11.6 9.4 11.4 8.9 11.4 8.3V6C11.4 4 10 2.5 8 2.5Z" }),
35
+ /* @__PURE__ */ r("path", { d: "M6.5 13C6.8 13.5 7.4 13.8 8 13.8C8.6 13.8 9.2 13.5 9.5 13", strokeLinecap: "round" })
36
+ ] })
37
+ ]
38
+ }
39
+ );
40
+ }
41
+ export {
42
+ i as EmptyBell,
43
+ a as SourceBellMark
44
+ };
45
+ //# sourceMappingURL=icons.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"icons.js","sources":["../../../src/components/NotificationCenter/icons.tsx"],"sourcesContent":["// The notification centre's own glyphs. Both are bells, and they are two\n// drawings rather than one because they are drawn at two sizes on two grounds:\n// one is white ink inside a 22px dark tile, the other is a 28px outline standing\n// alone on the panel's surface. Everything else the panel draws is either\n// ProductMark's or plain text.\n\nimport styles from './NotificationCenter.module.css'\n\n/**\n * The bell on the empty state, standing alone above \"You're all caught up\".\n *\n * Decorative: the two lines beneath it say the same thing in words, so a screen\n * reader that announced it as well would say the state twice. It is sized in CSS\n * (`--spacing-7`) rather than by width/height attributes, which is why it takes\n * no size prop.\n */\nexport function EmptyBell() {\n return (\n <svg\n className={styles.emptyGlyph}\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.2\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M8 2.5C6 2.5 4.6 4 4.6 6V8.3C4.6 8.9 4.4 9.4 4 9.8L3.3 10.6C3 10.9 3.2 11.5 3.7 11.5H12.3C12.8 11.5 13 10.9 12.7 10.6L12 9.8C11.6 9.4 11.4 8.9 11.4 8.3V6C11.4 4 10 2.5 8 2.5Z\" />\n <path d=\"M6.5 13C6.8 13.5 7.4 13.8 8 13.8C8.6 13.8 9.2 13.5 9.5 13\" strokeLinecap=\"round\" />\n </svg>\n )\n}\n\n/**\n * The tile a row draws when its notification names no product -- an account or\n * security event, which is what the drawing's fifth row is.\n *\n * **Why this is not a `ProductMark` with an unknown slug.** It would draw the\n * right thing by accident: ADR-0003's safe degrade gives an unrecognised slug\n * the bare tile with no glyph, which is a product whose artwork has not landed.\n * A notification with no product is not that. It is a row that correctly has no\n * product, and drawing it as a gap says the mark failed to load. So the fallback\n * is a bell -- the panel's own symbol, which is what the drawing puts there.\n *\n * It is still drawn ProductMark's way rather than as a styled `div`: a `rect` on\n * a 40-unit grid, its corner taken from `--radius-product-tile`, so the two\n * kinds of tile in the same column share one corner at every size instead of\n * agreeing to a px value that only matches at 22px. The glyph is on the same\n * 16-unit grid as `EmptyBell` above and the group transform insets it into the\n * middle 20 of the 40, which is the inset ProductMark gives its four -- so the\n * bell's ink lands at the weight the product glyphs do (1.3 on the 16 grid,\n * scaled 1.25, is 0.89px of stroke at 22px, which is what Paper draws).\n *\n * Decorative like ProductMark is here: the row's text names the source.\n */\nexport function SourceBellMark({ size }: { size: number }) {\n return (\n <svg\n className={styles.sourceMark}\n width={size}\n height={size}\n viewBox=\"0 0 40 40\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <rect className={styles.sourceMarkGround} width=\"40\" height=\"40\" />\n <g transform=\"translate(10 10) scale(1.25)\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.3\" strokeLinejoin=\"round\">\n <path d=\"M8 2.5C6 2.5 4.6 4 4.6 6V8.3C4.6 8.9 4.4 9.4 4 9.8L3.3 10.6C3 10.9 3.2 11.5 3.7 11.5H12.3C12.8 11.5 13 10.9 12.7 10.6L12 9.8C11.6 9.4 11.4 8.9 11.4 8.3V6C11.4 4 10 2.5 8 2.5Z\" />\n <path d=\"M6.5 13C6.8 13.5 7.4 13.8 8 13.8C8.6 13.8 9.2 13.5 9.5 13\" strokeLinecap=\"round\" />\n </g>\n </svg>\n )\n}\n"],"names":["EmptyBell","jsxs","styles","jsx","SourceBellMark","size"],"mappings":";;AAgBO,SAASA,IAAY;AAC1B,SACE,gBAAAC;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAWC,EAAO;AAAA,MAClB,SAAQ;AAAA,MACR,MAAK;AAAA,MACL,QAAO;AAAA,MACP,aAAY;AAAA,MACZ,gBAAe;AAAA,MACf,eAAY;AAAA,MAEZ,UAAA;AAAA,QAAA,gBAAAC,EAAC,QAAA,EAAK,GAAE,iLAAA,CAAiL;AAAA,QACzL,gBAAAA,EAAC,QAAA,EAAK,GAAE,6DAA4D,eAAc,QAAA,CAAQ;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGhG;AAwBO,SAASC,EAAe,EAAE,MAAAC,KAA0B;AACzD,SACE,gBAAAJ;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAWC,EAAO;AAAA,MAClB,OAAOG;AAAA,MACP,QAAQA;AAAA,MACR,SAAQ;AAAA,MACR,OAAM;AAAA,MACN,eAAY;AAAA,MAEZ,UAAA;AAAA,QAAA,gBAAAF,EAAC,UAAK,WAAWD,EAAO,kBAAkB,OAAM,MAAK,QAAO,MAAK;AAAA,QACjE,gBAAAD,EAAC,KAAA,EAAE,WAAU,gCAA+B,MAAK,QAAO,QAAO,gBAAe,aAAY,OAAM,gBAAe,SAC7G,UAAA;AAAA,UAAA,gBAAAE,EAAC,QAAA,EAAK,GAAE,iLAAA,CAAiL;AAAA,UACzL,gBAAAA,EAAC,QAAA,EAAK,GAAE,6DAA4D,eAAc,QAAA,CAAQ;AAAA,QAAA,EAAA,CAC5F;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGN;"}
@@ -0,0 +1,26 @@
1
+ import { fillTemplate as r } from "../../fill-template.js";
2
+ const l = Object.freeze({
3
+ title: "Notifications",
4
+ markAllAsRead: "Mark all as read",
5
+ all: "All",
6
+ unread: "Unread",
7
+ unreadItem: "Unread",
8
+ meta: "%source% · %time%",
9
+ viewAll: "View all notifications",
10
+ emptyTitle: "You're all caught up",
11
+ emptyBody: "Nothing from any product needs your attention."
12
+ });
13
+ function s(a) {
14
+ const i = Object.fromEntries(
15
+ Object.entries(a ?? {}).filter(([, e]) => e !== void 0)
16
+ ), t = { ...l, ...i };
17
+ return {
18
+ ...t,
19
+ meta: (e, o) => r(t.meta, { source: e, time: o })
20
+ };
21
+ }
22
+ export {
23
+ l as defaultNotificationCenterLabels,
24
+ s as resolveNotificationCenterLabels
25
+ };
26
+ //# sourceMappingURL=labels.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"labels.js","sources":["../../../src/components/NotificationCenter/labels.ts"],"sourcesContent":["// The notification centre's own user-facing words, each a field with an English\n// default. Every string a person can read or a screen reader can announce that\n// is not row data lives here, so the first two consumers -- both French-default\n// -- translate the panel without this package taking an i18n dependency\n// (CLAUDE.md; B33-849). The notifications themselves are not here: their title,\n// their source and their time are the host's data, supplied per row on `items`,\n// because the host is the only side that knows what happened (B33-494's data\n// decision, 2026-10-06).\n//\n// ── Which label contract this follows, and why it is not a fourth one ────────\n//\n// This is `WalletChipLabels`'s contract, in all four of its parts, and the\n// choice is deliberate. B33-1007 found `AppSwitcherLabels` claiming parity with\n// the chip while diverging from it three ways; two were closed by B33-919 and\n// the third -- the freeze -- is still open. Picking the chip means the third\n// labels module in this package lands on the shape the other two are converging\n// *to*, so B33-1007 closes by making the switcher match this file rather than by\n// this file becoming a fourth thing to reconcile. The four parts:\n//\n// 1. `labels` is a `Partial`, so a consumer translates the strings it wants\n// and inherits the rest -- and so adding a field here later is not a\n// compile error in four apps on a patch bump.\n// 2. A `resolve*` step filters explicitly-`undefined` fields before merging,\n// because a next-intl `t('...')` returns `undefined` on a missed key and a\n// plain spread copies that `undefined` across (B33-873).\n// 3. The defaults are `Object.freeze({ … } satisfies …)` with NO type\n// annotation. That is the spelling B33-916 settled for the chip: the\n// annotation widens `Readonly<T>` straight back to the mutable interface,\n// so a consumer writing to the frozen export gets no compile error and an\n// uncaught TypeError in production instead.\n// 4. The one string that carries values is a `%placeholder%` template -- never\n// fragments concatenated in the render, because word order is not a property\n// of English. The delimiter is `%name%` and not `{name}` because a brace is\n// ICU argument syntax and a consumer keeps these strings in an ICU catalogue\n// (B33-1277); the reasoning is in `src/fill-template.ts`.\n//\n// A template, and *only* a template. `meta` took a function of the same values\n// until B33-1278, which is the shape B33-880 had already recorded as unpassable:\n// React's Flight serializer throws on any function it finds in a Client\n// Component's props, so a labels object holding one cannot be passed from a\n// Server Component page -- which is what a host page is by default. The union was\n// type-legal and fatal in production, and nothing in the type said so. A meta line\n// needing logic a template cannot express is computed in the host's own catalogue\n// and passed here finished.\n\nimport { fillTemplate } from '../../fill-template'\n\n/** Every user-facing string the notification centre renders that is not row data. */\nexport interface NotificationCenterLabels {\n /**\n * The panel's heading, and through it the panel's accessible name. Default\n * \"Notifications\".\n *\n * It is drawn as a real heading and the panel is pointed at it with\n * `aria-labelledby`, rather than carrying a second copy of the same word in an\n * `aria-label`. ADR-0010 says a disclosure panel carries no widget role and\n * takes a name without one; it does not say the name has to be invisible, and\n * a name that is already on the screen is one fewer string to translate and\n * one fewer place for the two to disagree.\n */\n title: string\n /**\n * The header's action, which clears every unread marker. Default\n * \"Mark all as read\".\n *\n * Stage A renders the control and wires nothing to it -- the handler is Stage\n * B's, with the rest of the interaction (CLAUDE.md, \"Presentational-first\").\n */\n markAllAsRead: string\n /** The first filter tab, showing every notification. Default \"All\". */\n all: string\n /**\n * The second filter tab, showing only the unread ones. Default \"Unread\".\n *\n * The count beside it is not a label: it is `items` counted, so it needs no\n * translation and cannot disagree with the list under it.\n */\n unread: string\n /**\n * The marker on an unread row, announced rather than drawn. Default \"Unread\".\n *\n * The drawing carries unread as a 6px orange dot and nothing else, which is a\n * colour-only distinction and reaches nobody using a screen reader. This is\n * the same state in words, in the row, visually hidden (CLAUDE.md,\n * \"Accessibility is part of done\"). It repeats `unread` by default because it\n * is the same English word, and it is a separate field because a locale is\n * entitled to tag a row differently from how it names a filter.\n */\n unreadItem: string\n /**\n * The second line of a row, around the host's source and time. Supplied whole\n * so the consumer owns word order and the separator.\n *\n * Placeholders: `%source%`, `%time%`. Default (en) `%source% · %time%`.\n *\n * Both values are the host's, preformatted -- see `NotificationCenterItem`\n * for why the time arrives as a string this package never computes.\n */\n meta: string\n /** The footer link out to the full list. Default \"View all notifications\". */\n viewAll: string\n /** The empty state's headline. Default \"You're all caught up\". */\n emptyTitle: string\n /**\n * The line under the empty state's headline. Default\n * \"Nothing from any product needs your attention.\"\n *\n * **The empty state is not an edge case.** A host that has not wired a feed\n * yet renders it, which makes it the state most consumers see first (B33-494's\n * data decision). A locale that leaves it untranslated is a blank panel.\n */\n emptyBody: string\n}\n\n/**\n * The English defaults. A consumer that passes nothing renders the panel in\n * English, which is what the specimen route shows and what a human will be\n * looking at when they approve this component at its fidelity gate.\n *\n * Frozen, and frozen without a type annotation. It is a public export reaching\n * four products on a version bump: `const` protects the binding and not the\n * contents, so a consumer aliasing it -- `const mine = defaultNotificationCenterLabels;\n * mine.title = 'Notifications'` -- would silently rename every panel in the\n * process (B33-877), and an annotation here would widen the `Readonly` back off\n * the emitted `.d.ts` and turn that into a runtime TypeError rather than a\n * compile error (B33-916).\n *\n * The freeze is shallow. No field here is an object and none should become one\n * without re-reading B33-916's note: a nested label defeats the guard silently.\n */\nexport const defaultNotificationCenterLabels = Object.freeze({\n title: 'Notifications',\n markAllAsRead: 'Mark all as read',\n all: 'All',\n unread: 'Unread',\n unreadItem: 'Unread',\n meta: '%source% · %time%',\n viewAll: 'View all notifications',\n emptyTitle: \"You're all caught up\",\n emptyBody: 'Nothing from any product needs your attention.',\n} satisfies NotificationCenterLabels)\n\n/**\n * The labels as the render consumes them: `meta` presented as a function of its\n * two values, so the component writes `t.meta(…)` and never handles a template\n * itself. Internal, and the interpolation has to happen somewhere.\n */\nexport interface ResolvedNotificationCenterLabels extends Omit<NotificationCenterLabels, 'meta'> {\n meta: (source: string, time: string) => string\n}\n\n/**\n * Merge a consumer's partial `labels` onto the English defaults, treating an\n * explicitly-`undefined` field as unsupplied, and present `meta` as a function of\n * its two values.\n *\n * `{ title: undefined }` is what one missing next-intl key looks like at the\n * call site. It must fall back to English rather than blank the heading -- which\n * here would also unname the panel, since the heading is what names it\n * (B33-873).\n */\nexport function resolveNotificationCenterLabels(\n labels?: Partial<NotificationCenterLabels>,\n): ResolvedNotificationCenterLabels {\n const supplied = Object.fromEntries(\n Object.entries(labels ?? {}).filter(([, value]) => value !== undefined),\n ) as Partial<NotificationCenterLabels>\n const merged = { ...defaultNotificationCenterLabels, ...supplied }\n return {\n ...merged,\n meta: (source, time) => fillTemplate(merged.meta, { source, time }),\n }\n}\n"],"names":["defaultNotificationCenterLabels","resolveNotificationCenterLabels","labels","supplied","value","merged","source","time","fillTemplate"],"mappings":";AAkIO,MAAMA,IAAkC,OAAO,OAAO;AAAA,EAC3D,OAAO;AAAA,EACP,eAAe;AAAA,EACf,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,MAAM;AAAA,EACN,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,WAAW;AACb,CAAoC;AAqB7B,SAASC,EACdC,GACkC;AAClC,QAAMC,IAAW,OAAO;AAAA,IACtB,OAAO,QAAQD,KAAU,EAAE,EAAE,OAAO,CAAC,CAAA,EAAGE,CAAK,MAAMA,MAAU,MAAS;AAAA,EAAA,GAElEC,IAAS,EAAE,GAAGL,GAAiC,GAAGG,EAAA;AACxD,SAAO;AAAA,IACL,GAAGE;AAAA,IACH,MAAM,CAACC,GAAQC,MAASC,EAAaH,EAAO,MAAM,EAAE,QAAAC,GAAQ,MAAAC,EAAA,CAAM;AAAA,EAAA;AAEtE;"}
@@ -0,0 +1,91 @@
1
+ import { ElementType, ReactNode } from 'react';
2
+ import { SettingsFrameLabels } from './labels';
3
+ /** One entry in a sidebar group: a settings page, here or on another origin. */
4
+ export interface SettingsFrameItem {
5
+ /** A stable key for the row, unique within its group. */
6
+ id: string;
7
+ /** The row's visible name. */
8
+ label: string;
9
+ /**
10
+ * Where the row goes. A path renders through `linkComponent`; an absolute URL
11
+ * renders as a plain `<a>` and loads the page in full -- which is what a
12
+ * product's page on its own origin needs.
13
+ */
14
+ href: string;
15
+ /**
16
+ * The row's glyph: a 16px square slot, inked in `currentColor`. The frame
17
+ * sets the colour (muted at rest, foreground when active), so draw the glyph
18
+ * with `stroke="currentColor"` or `fill="currentColor"` and it follows.
19
+ * Decorative -- give it `aria-hidden`; the label is the row's name.
20
+ */
21
+ icon?: ReactNode;
22
+ /** The page being shown. Marks the row `aria-current="page"`. */
23
+ active?: boolean;
24
+ }
25
+ /** A titled run of items: "You", the account's name, or a product's name. */
26
+ export interface SettingsFrameGroup {
27
+ /** A stable key for the group. */
28
+ id: string;
29
+ /** The small uppercase heading over the group. Hidden below 768px. */
30
+ label: string;
31
+ items: SettingsFrameItem[];
32
+ }
33
+ /**
34
+ * An account other than the one being acted in. Switching is Identity's job,
35
+ * so `href` is normally Identity's account chooser on Identity's origin.
36
+ */
37
+ export interface SettingsFrameAccount {
38
+ id: string;
39
+ label: string;
40
+ href: string;
41
+ }
42
+ /** A plain labelled link: the back link, or a help link in the header. */
43
+ export interface SettingsFrameLink {
44
+ label: string;
45
+ href: string;
46
+ }
47
+ /** One step of the breadcrumb. The last one is the page being shown. */
48
+ export interface SettingsFrameCrumb {
49
+ label: string;
50
+ /** Optional. A crumb with no href is text, which is how the hub draws both. */
51
+ href?: string;
52
+ }
53
+ /** The page's heading block, across the top of the content column. */
54
+ export interface SettingsFrameHeading {
55
+ title: string;
56
+ description?: string;
57
+ /** The page's primary action, level with the title rather than below it. */
58
+ action?: ReactNode;
59
+ }
60
+ export interface SettingsFrameProps {
61
+ /** The link out of settings, at the head of the sidebar. */
62
+ back: SettingsFrameLink;
63
+ /** The sidebar's groups, in order. The frame renders exactly these. */
64
+ groups: SettingsFrameGroup[];
65
+ /** Accounts other than the acting one. The list is omitted when empty. */
66
+ otherAccounts?: SettingsFrameAccount[];
67
+ /**
68
+ * Where "Sign out" goes. Sign-out belongs to Identity, which owns the
69
+ * session; a host that dropped its own cookie would have revoked nothing. So
70
+ * this is a link to Identity, never a handler.
71
+ */
72
+ signOutHref: string;
73
+ /** The header's help links (the hub's "Feedback" and "Docs"). Hidden below 768px. */
74
+ helpLinks?: SettingsFrameLink[];
75
+ /** The header's trail, ending at the page being shown. */
76
+ breadcrumb: SettingsFrameCrumb[];
77
+ /** The page heading, drawn above `children`. Omit it and the content starts at the top. */
78
+ heading?: SettingsFrameHeading;
79
+ /** The header's trailing slot: the host's account menu. */
80
+ accountMenu?: ReactNode;
81
+ /** The page itself. */
82
+ children?: ReactNode;
83
+ /** The frame's own words. Partial; missing fields fall back to English. */
84
+ labels?: Partial<SettingsFrameLabels>;
85
+ /**
86
+ * The element a same-origin link renders as (ADR-0011). Defaults to `'a'`.
87
+ * Only consulted for paths: an absolute URL is always a plain `<a>`.
88
+ */
89
+ linkComponent?: ElementType;
90
+ }
91
+ export declare function SettingsFrame({ back, groups, otherAccounts, signOutHref, helpLinks, breadcrumb, heading, accountMenu, children, labels, linkComponent, }: SettingsFrameProps): import("react").JSX.Element;
@@ -0,0 +1,109 @@
1
+ import { jsx as a, jsxs as s } from "react/jsx-runtime";
2
+ import e from "./SettingsFrame.module.css.js";
3
+ import { ArrowLeftGlyph as L, SignOutGlyph as y, PanelGlyph as x } from "./icons.js";
4
+ import { resolveSettingsFrameLabels as S } from "./labels.js";
5
+ const T = /^(?:[a-z][a-z\d+.-]*:|\/\/)/i;
6
+ function i({
7
+ href: n,
8
+ linkComponent: N,
9
+ className: t,
10
+ current: o,
11
+ children: m
12
+ }) {
13
+ const p = T.test(n) ? "a" : N;
14
+ return /* @__PURE__ */ a(p, { href: n, className: t, "aria-current": o ? "page" : void 0, children: m });
15
+ }
16
+ function H({
17
+ back: n,
18
+ groups: N,
19
+ otherAccounts: t = [],
20
+ signOutHref: o,
21
+ helpLinks: m = [],
22
+ breadcrumb: p,
23
+ heading: d,
24
+ accountMenu: v,
25
+ children: u,
26
+ labels: g,
27
+ linkComponent: c = "a"
28
+ }) {
29
+ const h = S(g);
30
+ return /* @__PURE__ */ a("div", { className: e.root, children: /* @__PURE__ */ s("div", { className: e.frame, children: [
31
+ /* @__PURE__ */ s("nav", { "aria-label": h.navigation, className: e.sidebar, children: [
32
+ /* @__PURE__ */ s(i, { href: n.href, linkComponent: c, className: e.back, children: [
33
+ /* @__PURE__ */ a(L, { className: e.glyph }),
34
+ /* @__PURE__ */ a("span", { className: e.backLabel, children: n.label })
35
+ ] }),
36
+ /* @__PURE__ */ a("div", { className: e.title, children: /* @__PURE__ */ a("span", { className: e.titleText, children: h.title }) }),
37
+ N.map((l) => /* @__PURE__ */ s("div", { className: e.group, children: [
38
+ /* @__PURE__ */ a("div", { className: e.groupLabel, children: /* @__PURE__ */ a("span", { className: e.groupLabelText, children: l.label }) }),
39
+ l.items.map((r) => /* @__PURE__ */ s(
40
+ i,
41
+ {
42
+ href: r.href,
43
+ linkComponent: c,
44
+ className: e.item,
45
+ current: r.active,
46
+ children: [
47
+ /* @__PURE__ */ a("span", { className: e.icon, children: r.icon }),
48
+ /* @__PURE__ */ a("span", { className: e.label, children: r.label })
49
+ ]
50
+ },
51
+ r.id
52
+ ))
53
+ ] }, l.id)),
54
+ t.length > 0 && /* @__PURE__ */ s("div", { className: e.group, children: [
55
+ /* @__PURE__ */ a("div", { className: e.groupLabel, children: /* @__PURE__ */ a("span", { className: e.groupLabelText, children: h.otherAccounts }) }),
56
+ t.map((l) => /* @__PURE__ */ s(i, { href: l.href, linkComponent: c, className: e.item, children: [
57
+ /* @__PURE__ */ a("span", { className: e.swatch, "aria-hidden": "true" }),
58
+ /* @__PURE__ */ a("span", { className: e.label, children: l.label })
59
+ ] }, l.id))
60
+ ] }),
61
+ /* @__PURE__ */ a("div", { className: e.spacer }),
62
+ /* @__PURE__ */ s(i, { href: o, linkComponent: c, className: `${e.item} ${e.signOut}`, children: [
63
+ /* @__PURE__ */ a(y, { className: e.glyph }),
64
+ /* @__PURE__ */ a("span", { className: e.label, children: h.signOut })
65
+ ] })
66
+ ] }),
67
+ /* @__PURE__ */ s("div", { className: e.column, children: [
68
+ /* @__PURE__ */ s("header", { className: e.header, children: [
69
+ /* @__PURE__ */ s("div", { className: e.headerStart, children: [
70
+ /* @__PURE__ */ a("span", { className: e.panelMark, children: /* @__PURE__ */ a(x, { className: e.glyph }) }),
71
+ /* @__PURE__ */ a("nav", { "aria-label": h.breadcrumb, children: /* @__PURE__ */ a("ol", { className: e.crumbs, children: p.map((l, r) => {
72
+ const b = r === p.length - 1, f = b ? e.crumbCurrent : e.crumb;
73
+ return /* @__PURE__ */ s("li", { className: e.crumbStep, children: [
74
+ r > 0 && /* @__PURE__ */ a("span", { className: e.crumbSeparator, "aria-hidden": "true", children: "›" }),
75
+ l.href ? /* @__PURE__ */ a(
76
+ i,
77
+ {
78
+ href: l.href,
79
+ linkComponent: c,
80
+ className: f,
81
+ current: b,
82
+ children: l.label
83
+ }
84
+ ) : /* @__PURE__ */ a("span", { className: f, "aria-current": b ? "page" : void 0, children: l.label })
85
+ ] }, `${r}-${l.label}`);
86
+ }) }) })
87
+ ] }),
88
+ /* @__PURE__ */ s("div", { className: e.headerEnd, children: [
89
+ m.length > 0 && /* @__PURE__ */ a("span", { className: e.helpLinks, children: m.map((l) => /* @__PURE__ */ a(i, { href: l.href, linkComponent: c, className: e.helpLink, children: l.label }, l.href)) }),
90
+ v
91
+ ] })
92
+ ] }),
93
+ /* @__PURE__ */ s("main", { className: e.content, children: [
94
+ d && /* @__PURE__ */ s("div", { className: e.pageHead, children: [
95
+ /* @__PURE__ */ s("div", { className: e.pageHeadText, children: [
96
+ /* @__PURE__ */ a("h1", { className: e.pageTitle, children: d.title }),
97
+ d.description && /* @__PURE__ */ a("p", { className: e.pageDescription, children: d.description })
98
+ ] }),
99
+ d.action
100
+ ] }),
101
+ u
102
+ ] })
103
+ ] })
104
+ ] }) });
105
+ }
106
+ export {
107
+ H as SettingsFrame
108
+ };
109
+ //# sourceMappingURL=SettingsFrame.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SettingsFrame.js","sources":["../../../src/components/SettingsFrame/SettingsFrame.tsx"],"sourcesContent":["import type { ElementType, ReactNode } from 'react'\nimport styles from './SettingsFrame.module.css'\nimport { ArrowLeftGlyph, PanelGlyph, SignOutGlyph } from './icons'\nimport { resolveSettingsFrameLabels, type SettingsFrameLabels } from './labels'\n\n/*\n * The settings frame (B33-1529): the full-page takeover the hub and every\n * product draw their settings pages in, so somebody moving from the hub's\n * settings to a product's sees one settings area rather than two apps.\n *\n * Estate decision, 22 Oct 2026: settings live in the hub's menu and each product\n * hosts its own settings pages (boost33-voice ADR-0008). The hub's sidebar\n * carries a group per product the account has; that group's entries are pages\n * the product serves on its own origin, drawn in this same frame. So the frame\n * is rendered by several apps on several origins, and two things follow.\n *\n * ── It takes its groups as data and knows no product ────────────────────────\n *\n * Which groups exist, what they are called and where their entries go is the\n * host's decision, made from what Identity says the account has. Nothing here\n * names a product, a section or a role, and no item is active because of\n * anything the frame worked out: the host says `active`.\n *\n * ── A link to another origin is a plain full-page link ──────────────────────\n *\n * The rule is read off the href, not off a flag: an href with a scheme\n * (`https:`, `mailto:`) or protocol-relative (`//`) is rendered as a plain\n * `<a>`, and a path is rendered through `linkComponent` (ADR-0011). A product's\n * pages are not on the hub's origin, so the hub passes them as absolute URLs and\n * a router's client-side `Link` never sees them; a host's own pages are paths\n * and keep client navigation. A host that writes one of its own pages as an\n * absolute URL gets a full page load, which is slower and still correct --\n * the safe direction to be wrong in.\n *\n * ── The referent is the hub's settings chrome, not Paper ────────────────────\n *\n * The ticket's last criterion is that the hub's settings render through this\n * frame with no visible change. So the measurements are the hub's, copied from\n * boost33-hub `src/app/settings/chrome.tsx`, `ui.tsx` (`PageHead`) and the\n * `.settings-*` rules in its `globals.css`, and the comments in\n * SettingsFrame.module.css name each one. Paper page 104 is where the hub got\n * them; where the two might disagree, the hub's rendering is what this frame\n * reproduces, and the frame's fidelity gate is where a person decides otherwise.\n *\n * APPROVED at its fidelity gate on 26 Oct 2026: Jamal judged it on the\n * specimen route, captured its three designed baselines (settingsframe-hub,\n * -voice, -phone) and approved its measurements into the spec (\"SettingsFrame,\n * recorded at its gate\"). Those now bind. Static markup with no state and no\n * handlers, and no Stage B is planned: a frame of links has nothing to wire.\n */\n\n/** One entry in a sidebar group: a settings page, here or on another origin. */\nexport interface SettingsFrameItem {\n /** A stable key for the row, unique within its group. */\n id: string\n /** The row's visible name. */\n label: string\n /**\n * Where the row goes. A path renders through `linkComponent`; an absolute URL\n * renders as a plain `<a>` and loads the page in full -- which is what a\n * product's page on its own origin needs.\n */\n href: string\n /**\n * The row's glyph: a 16px square slot, inked in `currentColor`. The frame\n * sets the colour (muted at rest, foreground when active), so draw the glyph\n * with `stroke=\"currentColor\"` or `fill=\"currentColor\"` and it follows.\n * Decorative -- give it `aria-hidden`; the label is the row's name.\n */\n icon?: ReactNode\n /** The page being shown. Marks the row `aria-current=\"page\"`. */\n active?: boolean\n}\n\n/** A titled run of items: \"You\", the account's name, or a product's name. */\nexport interface SettingsFrameGroup {\n /** A stable key for the group. */\n id: string\n /** The small uppercase heading over the group. Hidden below 768px. */\n label: string\n items: SettingsFrameItem[]\n}\n\n/**\n * An account other than the one being acted in. Switching is Identity's job,\n * so `href` is normally Identity's account chooser on Identity's origin.\n */\nexport interface SettingsFrameAccount {\n id: string\n label: string\n href: string\n}\n\n/** A plain labelled link: the back link, or a help link in the header. */\nexport interface SettingsFrameLink {\n label: string\n href: string\n}\n\n/** One step of the breadcrumb. The last one is the page being shown. */\nexport interface SettingsFrameCrumb {\n label: string\n /** Optional. A crumb with no href is text, which is how the hub draws both. */\n href?: string\n}\n\n/** The page's heading block, across the top of the content column. */\nexport interface SettingsFrameHeading {\n title: string\n description?: string\n /** The page's primary action, level with the title rather than below it. */\n action?: ReactNode\n}\n\nexport interface SettingsFrameProps {\n /** The link out of settings, at the head of the sidebar. */\n back: SettingsFrameLink\n /** The sidebar's groups, in order. The frame renders exactly these. */\n groups: SettingsFrameGroup[]\n /** Accounts other than the acting one. The list is omitted when empty. */\n otherAccounts?: SettingsFrameAccount[]\n /**\n * Where \"Sign out\" goes. Sign-out belongs to Identity, which owns the\n * session; a host that dropped its own cookie would have revoked nothing. So\n * this is a link to Identity, never a handler.\n */\n signOutHref: string\n /** The header's help links (the hub's \"Feedback\" and \"Docs\"). Hidden below 768px. */\n helpLinks?: SettingsFrameLink[]\n /** The header's trail, ending at the page being shown. */\n breadcrumb: SettingsFrameCrumb[]\n /** The page heading, drawn above `children`. Omit it and the content starts at the top. */\n heading?: SettingsFrameHeading\n /** The header's trailing slot: the host's account menu. */\n accountMenu?: ReactNode\n /** The page itself. */\n children?: ReactNode\n /** The frame's own words. Partial; missing fields fall back to English. */\n labels?: Partial<SettingsFrameLabels>\n /**\n * The element a same-origin link renders as (ADR-0011). Defaults to `'a'`.\n * Only consulted for paths: an absolute URL is always a plain `<a>`.\n */\n linkComponent?: ElementType\n}\n\n// A scheme (`https:`, `mailto:`) or protocol-relative `//`. Anything else is a\n// path on the host's own origin.\nconst OTHER_ORIGIN = /^(?:[a-z][a-z\\d+.-]*:|\\/\\/)/i\n\nfunction FrameLink({\n href,\n linkComponent,\n className,\n current,\n children,\n}: {\n href: string\n linkComponent: ElementType\n className?: string\n current?: boolean\n children: ReactNode\n}) {\n const Link: ElementType = OTHER_ORIGIN.test(href) ? 'a' : linkComponent\n return (\n <Link href={href} className={className} aria-current={current ? 'page' : undefined}>\n {children}\n </Link>\n )\n}\n\nexport function SettingsFrame({\n back,\n groups,\n otherAccounts = [],\n signOutHref,\n helpLinks = [],\n breadcrumb,\n heading,\n accountMenu,\n children,\n labels,\n linkComponent = 'a',\n}: SettingsFrameProps) {\n const text = resolveSettingsFrameLabels(labels)\n\n return (\n <div className={styles.root}>\n <div className={styles.frame}>\n <nav aria-label={text.navigation} className={styles.sidebar}>\n <FrameLink href={back.href} linkComponent={linkComponent} className={styles.back}>\n <ArrowLeftGlyph className={styles.glyph} />\n <span className={styles.backLabel}>{back.label}</span>\n </FrameLink>\n\n <div className={styles.title}>\n <span className={styles.titleText}>{text.title}</span>\n </div>\n\n {groups.map((group) => (\n <div key={group.id} className={styles.group}>\n <div className={styles.groupLabel}>\n <span className={styles.groupLabelText}>{group.label}</span>\n </div>\n {group.items.map((item) => (\n <FrameLink\n key={item.id}\n href={item.href}\n linkComponent={linkComponent}\n className={styles.item}\n current={item.active}\n >\n <span className={styles.icon}>{item.icon}</span>\n <span className={styles.label}>{item.label}</span>\n </FrameLink>\n ))}\n </div>\n ))}\n\n {otherAccounts.length > 0 && (\n <div className={styles.group}>\n <div className={styles.groupLabel}>\n <span className={styles.groupLabelText}>{text.otherAccounts}</span>\n </div>\n {otherAccounts.map((account) => (\n <FrameLink key={account.id} href={account.href} linkComponent={linkComponent} className={styles.item}>\n <span className={styles.swatch} aria-hidden=\"true\" />\n <span className={styles.label}>{account.label}</span>\n </FrameLink>\n ))}\n </div>\n )}\n\n <div className={styles.spacer} />\n\n <FrameLink href={signOutHref} linkComponent={linkComponent} className={`${styles.item} ${styles.signOut}`}>\n <SignOutGlyph className={styles.glyph} />\n <span className={styles.label}>{text.signOut}</span>\n </FrameLink>\n </nav>\n\n <div className={styles.column}>\n <header className={styles.header}>\n <div className={styles.headerStart}>\n <span className={styles.panelMark}>\n <PanelGlyph className={styles.glyph} />\n </span>\n <nav aria-label={text.breadcrumb}>\n <ol className={styles.crumbs}>\n {breadcrumb.map((crumb, index) => {\n const last = index === breadcrumb.length - 1\n const className = last ? styles.crumbCurrent : styles.crumb\n return (\n <li key={`${index}-${crumb.label}`} className={styles.crumbStep}>\n {index > 0 && (\n <span className={styles.crumbSeparator} aria-hidden=\"true\">\n ›\n </span>\n )}\n {crumb.href ? (\n <FrameLink\n href={crumb.href}\n linkComponent={linkComponent}\n className={className}\n current={last}\n >\n {crumb.label}\n </FrameLink>\n ) : (\n <span className={className} aria-current={last ? 'page' : undefined}>\n {crumb.label}\n </span>\n )}\n </li>\n )\n })}\n </ol>\n </nav>\n </div>\n\n <div className={styles.headerEnd}>\n {helpLinks.length > 0 && (\n <span className={styles.helpLinks}>\n {helpLinks.map((link) => (\n <FrameLink key={link.href} href={link.href} linkComponent={linkComponent} className={styles.helpLink}>\n {link.label}\n </FrameLink>\n ))}\n </span>\n )}\n {accountMenu}\n </div>\n </header>\n\n <main className={styles.content}>\n {heading && (\n <div className={styles.pageHead}>\n <div className={styles.pageHeadText}>\n <h1 className={styles.pageTitle}>{heading.title}</h1>\n {heading.description && <p className={styles.pageDescription}>{heading.description}</p>}\n </div>\n {heading.action}\n </div>\n )}\n {children}\n </main>\n </div>\n </div>\n </div>\n )\n}\n"],"names":["OTHER_ORIGIN","FrameLink","href","linkComponent","className","current","children","Link","jsx","SettingsFrame","back","groups","otherAccounts","signOutHref","helpLinks","breadcrumb","heading","accountMenu","labels","text","resolveSettingsFrameLabels","styles","jsxs","ArrowLeftGlyph","group","item","account","SignOutGlyph","PanelGlyph","crumb","index","last","link"],"mappings":"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"}