@naeil/ui 0.12.0 → 0.13.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 (77) hide show
  1. package/CHANGELOG.md +31 -3
  2. package/DESIGN.md +10 -2
  3. package/README.md +13 -11
  4. package/THIRD_PARTY_NOTICES.md +4 -2
  5. package/dist/{chunk-XLISHUJZ.js → chunk-5BXZPDHZ.js} +68 -8
  6. package/dist/{chunk-DBE7R4MR.js → chunk-A2DCQVTP.js} +1 -1
  7. package/dist/{chunk-KPE4KKYM.js → chunk-Y5LW3ADW.js} +24 -15
  8. package/dist/components/index.d.ts +3 -3
  9. package/dist/components/index.js +4 -4
  10. package/dist/components/naeil-footer.d.ts +2 -2
  11. package/dist/components/naeil-footer.js +1 -1
  12. package/dist/components/naeil-header.d.ts +23 -8
  13. package/dist/components/naeil-header.js +7 -5
  14. package/dist/components/ui/index.d.ts +2 -2
  15. package/dist/components/ui/index.js +6 -6
  16. package/dist/lib/ui-release.d.ts +1 -1
  17. package/dist/lib/ui-release.js +1 -1
  18. package/dist/lib/utils.js +1 -7
  19. package/dist/naeil-locale/checker.js +1 -1
  20. package/dist/naeil-locale/chrome.d.ts +25 -8
  21. package/dist/naeil-locale/chrome.js +91 -22
  22. package/dist/naeil-locale/{chunk-4U64K4J3.js → chunk-24IL53QO.js} +1 -1
  23. package/dist/naeil-locale/{chunk-U65OQHZY.js → chunk-WT6QXR7O.js} +14 -8
  24. package/dist/naeil-locale/client.js +45 -15
  25. package/dist/naeil-locale/index.js +1 -1
  26. package/dist/naeil-locale/messages.js +2 -2
  27. package/dist/naeil-locale/server.js +6 -6
  28. package/dist/{navigation-D8N1tMTu.d.ts → navigation-CXZapKac.d.ts} +3 -1
  29. package/dist/{site-footer-s113FZgC.d.ts → site-footer-C4jgkGDU.d.ts} +1 -1
  30. package/dist/source/components/naeil-header-core.d.ts +23 -8
  31. package/dist/source/components/naeil-header-core.js +66 -6
  32. package/dist/source/components/naeil-header.d.ts +6 -4
  33. package/dist/source/components/naeil-header.js +4 -2
  34. package/dist/source/components/typography.d.ts +1 -1
  35. package/dist/source/components/typography.js +1 -1
  36. package/dist/source/components/ui/button.js +7 -7
  37. package/dist/source/components/ui/card.js +1 -1
  38. package/dist/source/components/ui/dialog.js +2 -2
  39. package/dist/source/components/ui/navigation.d.ts +3 -1
  40. package/dist/source/components/ui/navigation.js +17 -8
  41. package/dist/source/components/ui/sonner.js +2 -2
  42. package/dist/source/lib/ui-release.d.ts +1 -1
  43. package/dist/source/lib/ui-release.js +1 -1
  44. package/docs/components/README.md +3 -1
  45. package/docs/components/application-layout.md +23 -27
  46. package/docs/components/avatar.md +1 -1
  47. package/docs/components/badge.md +3 -1
  48. package/docs/components/button.md +7 -1
  49. package/docs/components/card.md +5 -1
  50. package/docs/components/checkbox.md +1 -1
  51. package/docs/components/common.md +16 -1
  52. package/docs/components/dialog.md +6 -2
  53. package/docs/components/dropdown-menu.md +1 -1
  54. package/docs/components/input.md +1 -1
  55. package/docs/components/naeil-footer.md +1 -1
  56. package/docs/components/naeil-header.md +41 -12
  57. package/docs/components/naeil-locale.md +36 -0
  58. package/docs/components/navigation.md +10 -4
  59. package/docs/components/select.md +1 -1
  60. package/docs/components/sheet.md +1 -1
  61. package/docs/components/sonner.md +7 -1
  62. package/docs/components/switch.md +1 -1
  63. package/docs/components/tabs.md +1 -1
  64. package/docs/components/textarea.md +1 -1
  65. package/docs/design/public-ui-support.md +3 -1
  66. package/docs/design/v2-migration.md +11 -6
  67. package/docs/package-release.md +21 -11
  68. package/package.json +7 -10
  69. package/src/styles/components.css +29 -0
  70. package/src/styles/globals.css +6 -2
  71. package/src/styles/theme.css +5 -0
  72. package/licenses/Nunito-Sans-OFL.txt +0 -93
  73. package/licenses/React-MIT.txt +0 -21
  74. package/licenses/Storybook-MIT.txt +0 -21
  75. package/licenses/popper-MIT.txt +0 -25
  76. package/licenses/store2-MIT.txt +0 -22
  77. package/licenses/toggle-selection-MIT.txt +0 -21
@@ -169,6 +169,7 @@ function Navigation(_a) {
169
169
  toolbar,
170
170
  mobileToolbar,
171
171
  mobileFooter,
172
+ omitEmptyNavigation = false,
172
173
  mobileOpen: controlledOpen,
173
174
  onMobileOpenChange,
174
175
  containerClassName,
@@ -183,6 +184,7 @@ function Navigation(_a) {
183
184
  "toolbar",
184
185
  "mobileToolbar",
185
186
  "mobileFooter",
187
+ "omitEmptyNavigation",
186
188
  "mobileOpen",
187
189
  "onMobileOpenChange",
188
190
  "containerClassName",
@@ -253,7 +255,8 @@ function Navigation(_a) {
253
255
  setOpenGroup(null);
254
256
  }, [currentHref, setMobileOpen]);
255
257
  React.useEffect(() => {
256
- const element = desktop.current;
258
+ var _a2, _b2;
259
+ const element = omitEmptyNavigation && !items.length ? (_b2 = (_a2 = desktop.current) == null ? void 0 : _a2.parentElement) == null ? void 0 : _b2.querySelector(".ui-navigation-toolbar") : desktop.current;
257
260
  if (!element || typeof ResizeObserver === "undefined") return;
258
261
  const observer = new ResizeObserver(() => {
259
262
  if (element.getBoundingClientRect().width > 0) setMobileOpen(false);
@@ -261,7 +264,8 @@ function Navigation(_a) {
261
264
  });
262
265
  observer.observe(element);
263
266
  return () => observer.disconnect();
264
- }, [setMobileOpen]);
267
+ }, [setMobileOpen, omitEmptyNavigation, items.length]);
268
+ const DesktopElement = omitEmptyNavigation && !items.length ? "div" : "nav";
265
269
  const active = (item) => {
266
270
  var _a2;
267
271
  return (_a2 = item.active) != null ? _a2 : !item.external && item.href === currentHref;
@@ -310,10 +314,12 @@ function Navigation(_a) {
310
314
  children: /* @__PURE__ */ jsx("div", { className: cn("ui-navigation-frame", containerClassName), children: /* @__PURE__ */ jsxs("div", { className: "ui-navigation-row", children: [
311
315
  /* @__PURE__ */ jsx("div", { className: "ui-navigation-brand", children: brand }),
312
316
  /* @__PURE__ */ jsx(
313
- "nav",
317
+ DesktopElement,
314
318
  {
315
- ref: desktop,
316
- "aria-label": labels.navigation,
319
+ ref: (element) => {
320
+ desktop.current = element;
321
+ },
322
+ "aria-label": omitEmptyNavigation && !items.length ? void 0 : labels.navigation,
317
323
  className: "ui-navigation-desktop",
318
324
  children: items.map(
319
325
  (item) => "items" in item ? /* @__PURE__ */ jsx(
@@ -365,7 +371,7 @@ function Navigation(_a) {
365
371
  className: "ui-navigation-sheet ui-popup",
366
372
  "aria-describedby": void 0,
367
373
  onCloseAutoFocus: (event) => {
368
- var _a2, _b2;
374
+ var _a2, _b2, _c, _d;
369
375
  if (controlledOpen !== void 0 && (!mounted.current || state.current.open)) {
370
376
  event.preventDefault();
371
377
  return;
@@ -379,7 +385,10 @@ function Navigation(_a) {
379
385
  }
380
386
  if (trigger.current && trigger.current.getBoundingClientRect().width === 0) {
381
387
  event.preventDefault();
382
- (_b2 = (_a2 = desktop.current) == null ? void 0 : _a2.querySelector("button, a")) == null ? void 0 : _b2.focus();
388
+ const target = (_a2 = desktop.current) == null ? void 0 : _a2.querySelector("button, a");
389
+ const toolbar2 = omitEmptyNavigation && !items.length ? (_c = (_b2 = desktop.current) == null ? void 0 : _b2.parentElement) == null ? void 0 : _c.querySelector(".ui-navigation-toolbar") : void 0;
390
+ const toolbarTarget = !target && toolbar2 ? Array.from(toolbar2.querySelectorAll("button, a[href], input, select, textarea, [tabindex]")).find((element) => element.tabIndex >= 0 && !element.matches(":disabled") && element.getBoundingClientRect().width > 0 && element.getBoundingClientRect().height > 0) : void 0;
391
+ (_d = target != null ? target : toolbarTarget) == null ? void 0 : _d.focus();
383
392
  }
384
393
  },
385
394
  children: [
@@ -394,7 +403,7 @@ function Navigation(_a) {
394
403
  }
395
404
  )
396
405
  ] }),
397
- /* @__PURE__ */ jsx(
406
+ (!omitEmptyNavigation || items.length > 0) && /* @__PURE__ */ jsx(
398
407
  "nav",
399
408
  {
400
409
  "aria-label": labels.navigation,
@@ -39,11 +39,11 @@ import { useTheme } from "next-themes";
39
39
  import { Toaster as Sonner } from "sonner";
40
40
  const Toaster = (_a) => {
41
41
  var props = __objRest(_a, []);
42
- const { theme = "system" } = useTheme();
42
+ const { theme = "system", forcedTheme } = useTheme();
43
43
  return /* @__PURE__ */ jsx(
44
44
  Sonner,
45
45
  __spreadValues({
46
- theme,
46
+ theme: forcedTheme != null ? forcedTheme : theme,
47
47
  className: "toaster group font-sans",
48
48
  position: "bottom-right",
49
49
  duration: 4e3,
@@ -1,5 +1,5 @@
1
1
  declare const uiRelease: {
2
- readonly version: "0.12.0";
2
+ readonly version: "0.13.0";
3
3
  readonly contractVersion: "1";
4
4
  };
5
5
 
@@ -1,4 +1,4 @@
1
- const uiRelease = { version: "0.12.0", contractVersion: "1" };
1
+ const uiRelease = { version: "0.13.0", contractVersion: "1" };
2
2
  export {
3
3
  uiRelease
4
4
  };
@@ -1,6 +1,6 @@
1
1
  # Component usage guides
2
2
 
3
- These maintained guides cover published exact **@naeil/ui 0.8.0** and the unpublished **0.9.0 frame candidate**. On 2026-10-29 registry tags were latest=0.7.2 and next=0.8.0; see [release policy and identities](../package-release.md#publication-and-deployment). [Setup, themes, fonts and shared responsibilities](common.md). Run `pnpm storybook` for the current 18 Docs pages (16 UI and 2 Brand) and choose Docs or Usage. The [hosted Storybook](https://naeil-dev.github.io/naeil-ui/) has recorded [0.5.0 verification](https://github.com/naeil-dev/naeil-ui/releases/tag/v0.5.0); each subsequent deployment requires its own receipt. Select chooses a value; DropdownMenu chooses an action.
3
+ These maintained guides describe the repository APIs. See [release policy and identities](../package-release.md#publication-and-deployment) for the current published release. [Setup, themes, fonts and shared responsibilities](common.md). Run `pnpm storybook` for the current 18 Docs pages (16 UI and 2 Brand) and choose Docs or Usage. The [hosted Storybook](https://naeil-dev.github.io/naeil-ui/) has recorded [0.5.0 verification](https://github.com/naeil-dev/naeil-ui/releases/tag/v0.5.0); each subsequent deployment requires its own receipt. Select chooses a value; DropdownMenu chooses an action.
4
4
 
5
5
  Start application layouts with the [shared navigation/footer selection and frame guide](application-layout.md). It includes a starter, the 1600px frame contract and instructions to place in consumer repositories so agents reuse the shared implementation.
6
6
 
@@ -25,6 +25,8 @@ Start application layouts with the [shared navigation/footer selection and frame
25
25
  | NaeilFooter | [NaeilFooter](./naeil-footer.md) | Brand / NaeilHeader / ReferenceFull, ReferenceCompact |
26
26
  | NaeilHeader and account control | [NaeilHeader](./naeil-header.md) | Brand / NaeilHeader / Usage, JapaneseApplication, AccountStates |
27
27
 
28
+ [Owned-service language runtime](naeil-locale.md) documents `/naeil-locale`, `/client`, `/server`, `/messages`, `/checker` and `/chrome`. Owned Next services under the shared provider import header/account/footer from `/naeil-locale/chrome`; the standalone React adapters remain available.
29
+
28
30
  [Field, native radio, Select and action composition](./composition.md) explains consumer state and semantic choices. [Demand decision](../design/component-demand.md) records why these two families were added.
29
31
 
30
32
  Use opt-in `@naeil/ui/naeil-header` for the actual naeil.dev branded header and its `NaeilHeaderAccountControl` in `authSlot`, paired with `@naeil/ui/naeil-footer` on owned services. Consumers supply routing/save guards, theme providers and authentication state/actions; official artwork remains URL-referenced. Canonical footer and account examples are part of existing stories, without additional Docs families. Use generic `Navigation` and `SiteFooter` from `/ui` for different products. [Navigation / SiteFooter](navigation.md) also documents the compatibility adapters.
@@ -1,6 +1,6 @@
1
1
  # Application frame and shared navigation/footer
2
2
 
3
- Exact **@naeil/ui 0.8.0 is published** (`next` tag; `latest` remains 0.7.2 at the 2026-10-29 verification). The **0.9.0 candidate** adds shared root reservation. Install exact `@naeil/ui@0.8.0` for the published Home APIs; hosted Docs and service activation have separate receipts. See [release identities and status](../package-release.md#publication-and-deployment) and [consumer setup](../design/v2-migration.md) for CSS, Tailwind sources and fonts.
3
+ Product navigation and controlled mobile APIs are available since 0.8.0; shared root reservation is available since 0.9.0. See [release identities and publication status](../package-release.md#publication-and-deployment) for the current published release; hosted Docs and service activation have separate receipts. See [consumer setup](../design/v2-migration.md) for CSS, Tailwind sources and fonts.
4
4
 
5
5
  ## Choose the shared composition first
6
6
 
@@ -8,7 +8,7 @@ Exact **@naeil/ui 0.8.0 is published** (`next` tag; `latest` remains 0.7.2 at th
8
8
  | --- | --- |
9
9
  | The actual naeil.dev brand, project catalog and controls | `NaeilHeader` from `@naeil/ui/naeil-header` |
10
10
  | Owned-service login/account/logout presentation | `NaeilHeaderAccountControl` from `@naeil/ui/naeil-header` in `authSlot` |
11
- | Owned naeil service with product menu data (0.8.0) | Complete `NaeilHeader`, with `appNavigation`, catalog/blog hidden, optional `brandSuffix` and `statusSlot` |
11
+ | Owned naeil service with product menu data (0.8.0) | Complete `NaeilHeader` with `service` and its own `appNavigation` |
12
12
  | A different product menu without canonical naeil chrome | `Navigation` from `@naeil/ui/ui` |
13
13
  | Owned naeil service footer | `NaeilFooter` from `@naeil/ui/naeil-footer` |
14
14
  | Different product footer or explicit content override | `SiteFooter` from `@naeil/ui/ui` |
@@ -30,7 +30,7 @@ Choose one header per application frame. Supply menu data, links, route guards,
30
30
 
31
31
  Use `.ui-app-frame` on main. Shared header/footer already apply the frame internally; **do not wrap them in another padded frame**. Put purpose-specific sections inside main. At 1920px the default frame has 1520px available after its 40px gutters. Small screens shrink the frame and reduce gutters to at least 16px. A narrow form must not constrain or move navigation.
32
32
 
33
- The **0.9.0 candidate** supplies `html { scrollbar-gutter: stable }` once in `components.css`, reached by `globals.css` or `theme.css + components.css`. Classic mode reserves one browser-sized gutter on short and long pages; overlay mode reserves none. The shared lock correction removes duplicate Radix body right-margin/gap compensation and retains its scroll prevention, focus and nested cleanup. Keep header/main/footer and their ordinary content horizontally stable. Compare matching root policies and measure before any fullPage capture. See the [adopted acceptance contract](https://github.com/naeil-dev/naeil-ui/blob/434b7d07235316097ca3a55265ee02c1422b0cfd/docs/superpowers/specs/2026-10-29-page-root-scrollbar-gutter.md).
33
+ Since 0.9.0, the package supplies `html { scrollbar-gutter: stable }` once in `components.css`, reached by `globals.css` or `theme.css + components.css`. Classic mode reserves one browser-sized gutter on short and long pages; overlay mode reserves none. The shared lock correction removes duplicate Radix body right-margin/gap compensation and retains its scroll prevention, focus and nested cleanup. Keep header/main/footer and their ordinary content horizontally stable. Compare matching root policies and measure before any fullPage capture. See the [adopted acceptance contract](https://github.com/naeil-dev/naeil-ui/blob/434b7d07235316097ca3a55265ee02c1422b0cfd/docs/superpowers/specs/2026-10-29-page-root-scrollbar-gutter.md).
34
34
 
35
35
  For a different document policy, declare this on the root **before first paint**:
36
36
 
@@ -57,28 +57,33 @@ Frame and control tokens stay unchanged. At 1920px with a 15px classic gutter th
57
57
 
58
58
  ## Canonical naeil application starter
59
59
 
60
+ Owned Next services under `NaeilLocaleProvider` import header/account/footer chrome from `@naeil/ui/naeil-locale/chrome`; `/naeil-header` and `/naeil-footer` remain standalone React adapters. See [the owned-service language runtime](naeil-locale.md) for `/naeil-locale`, `/client`, `/server`, `/messages`, `/checker` and `/chrome` setup.
61
+
62
+ For the standalone React adapter:
63
+
60
64
  Owned services use NaeilFooter's shared `full` default as-is; omit `variant`. `compact` is a deliberate, recorded exception (for example, a focused task screen), not the starter default.
61
65
 
62
66
  ```tsx
63
67
  import type { ReactNode } from "react";
64
68
  import { NaeilHeader, NaeilHeaderAccountControl,
65
- type NaeilHeaderAccount, type NaeilHeaderProps } from "@naeil/ui/naeil-header";
69
+ type NaeilHeaderAccount, type NaeilHeaderProps, type NaeilService } from "@naeil/ui/naeil-header";
66
70
  import { NaeilFooter } from "@naeil/ui/naeil-footer";
67
71
 
68
72
  // Render inside the existing next-themes ThemeProvider.
69
73
  // The app owns session state, its guarded logout and pending request lock.
70
74
  type ApplicationLayoutProps = {
71
75
  children: ReactNode;
76
+ service: NaeilService;
72
77
  locale: NaeilHeaderProps["locale"];
73
78
  Link: NaeilHeaderProps["Link"];
74
79
  currentHref: string;
75
80
  account: NaeilHeaderAccount;
76
81
  };
77
82
 
78
- export function ApplicationLayout({ children, locale, Link, currentHref,
83
+ export function ApplicationLayout({ children, service, locale, Link, currentHref,
79
84
  account }: ApplicationLayoutProps) {
80
85
  return <>
81
- <NaeilHeader locale={locale} Link={Link} currentHref={currentHref}
86
+ <NaeilHeader service={service} locale={locale} Link={Link} currentHref={currentHref}
82
87
  authSlot={<NaeilHeaderAccountControl locale={locale} Link={Link}
83
88
  account={account} />} />
84
89
  <main className="ui-app-frame"
@@ -89,23 +94,23 @@ export function ApplicationLayout({ children, locale, Link, currentHref,
89
94
  </>;
90
95
  }
91
96
 
92
- <ApplicationLayout locale="en" Link={GuardedLink} currentHref={pathname}
97
+ <ApplicationLayout service={service} locale="en" Link={GuardedLink} currentHref={pathname}
93
98
  account={account}>
94
99
  <section data-ui-layout="settings">{/* Settings form */}</section>
95
100
  </ApplicationLayout>
96
101
  ```
97
102
 
98
- `GuardedLink`, `pathname` and `account` come from the consumer. Use `{ status: 'signed-out', loginHref }` or `{ status: 'signed-in', onLogout, busy, displayName, email }` for `account`; the app handles async cancellation, failure and retry. Keep pending state and the request lock above `NaeilHeader`, since desktop/mobile slot contents mount separately. See the [account contract](naeil-header.md#account-presentation).
103
+ `service`, `GuardedLink`, `pathname` and `account` come from the consumer. Use `{ status: 'signed-out', loginHref }` or `{ status: 'signed-in', onLogout, busy, displayName, email }` for `account`; the app handles async cancellation, failure and retry. Keep pending state and the request lock above `NaeilHeader`, since desktop/mobile slot contents mount separately. See the [account contract](naeil-header.md#account-presentation).
99
104
 
100
- `NaeilHeader` is fixed; reserve its 65px height and account for it in sticky areas and focus/anchor scroll margins. Default destinations are official naeil.dev URLs. Preserve routing/unsaved-change guards using the [header contract](naeil-header.md) and [footer interception contract](naeil-footer.md); a Link adapter must handle absolute official and GitHub destinations as well as local routes. Only pass `onLocaleChange` when the application supports its complete language set.
105
+ `NaeilHeader` is fixed; reserve its 65px height and account for it in sticky areas and focus/anchor scroll margins. The service brand defaults to `/`; footer destinations remain official naeil.dev URLs. Preserve routing/unsaved-change guards using the [header contract](naeil-header.md) and [footer interception contract](naeil-footer.md); a Link adapter must handle absolute official and GitHub destinations as well as local routes. Only pass `onLocaleChange` when the application supports its complete language set.
101
106
 
102
- For owned naeil product navigation, keep the whole header and pass menu data through `appNavigation`; see the [Home profile and host conditions](naeil-header.md#host-typography-and-mobile-handoff-080). `mobileOpen`/`onMobileOpenChange` can coordinate the sheet with a consumer confirmation, while busy state and request locking remain above the header. For a different product menu without canonical chrome, use the [generic Navigation composition](navigation.md) with the same main/footer structure. Generic Navigation is in document flow by default and needs no fixed-header clearance. The [standalone React example](https://github.com/naeil-dev/naeil-ui/tree/main/examples/react) exercises the installed header, narrow form and SiteFooter together.
107
+ For owned naeil product navigation, set the matching `service` id and pass only product menu data through `appNavigation`; see the [Home profile and host conditions](naeil-header.md#host-typography-and-mobile-handoff-080). `mobileOpen`/`onMobileOpenChange` can coordinate the sheet with a consumer confirmation, while busy state and request locking remain above the header. For a different product menu without canonical chrome, use the [generic Navigation composition](navigation.md) with the same main/footer structure. Generic Navigation is in document flow by default and needs no fixed-header clearance. The [standalone React example](https://github.com/naeil-dev/naeil-ui/tree/main/examples/react) exercises the installed header/account control, narrow form and NaeilFooter together.
103
108
 
104
- An empty desktop menu preserves B1's limitation: resizing alone leaves the sheet open; Escape/close/overlay/route change dismiss it. Controlled-mobile account callbacks wait for both closing layers and their focus handoff; desktop and uncontrolled-mobile account callbacks retain B1's selection-time behavior. A consumer confirmation owns cancellation focus, including a visible desktop account fallback after resize.
109
+ Without `service`, an empty desktop menu preserves B1's limitation: resizing alone leaves the sheet open; Escape/close/overlay/route change dismiss it. Service profiles dismiss on desktop crossing even with an empty center; the mobile sheet omits empty navigation. Controlled-mobile account callbacks wait for both closing layers and their focus handoff; desktop and uncontrolled-mobile account callbacks retain B1's selection-time behavior. A consumer confirmation owns cancellation focus, including a visible desktop account fallback after resize.
105
110
 
106
111
  ## Deliberate overrides
107
112
 
108
- Apply overrides on a common ancestor or document root so header, main and footer inherit them together. Explicit `containerClassName` overrides still work. Do not give each route its own header cap or gutter.
113
+ Apply overrides on a common ancestor or document root so header, main and footer inherit them together. Legacy (no `service`) `containerClassName` overrides still work; the owned-service profile rejects header class/style overrides. Do not give each route its own header cap or gutter.
109
114
 
110
115
  ```css
111
116
  /* Product exception: record why this cap is needed. */
@@ -128,8 +133,12 @@ Put this in the **consumer application's** `AGENTS.md` (or equivalent repository
128
133
  - Read the installed @naeil/ui/DESIGN.md and docs/components/application-layout.md before UI layout work.
129
134
  - Use @naeil/ui/naeil-header for the canonical naeil header; use Navigation from @naeil/ui/ui for a different product menu. Use NaeilFooter from @naeil/ui/naeil-footer for owned service footers; SiteFooter remains the generic primitive. Keep existing Next Nav/Footer compatibility adapters when applicable.
130
135
  - Put NaeilHeaderAccountControl from @naeil/ui/naeil-header in authSlot for owned apps. Supply real session state, the busy flag and guarded logout callback. Keep pending state and the request lock above the header so desktop/mobile copies share them; do not copy account/avatar/menu markup.
131
- - Local wrappers supply menu data, routing/guards, locale, auth and slots. Do not copy the menu, mobile sheet, controls or footer implementation. Extend the shared component for reusable missing behavior.
132
- - The 0.9.0 candidate CSS reserves a stable root gutter through globals or theme+components; opt out on html with data-naeil-scrollbar-gutter="auto" before first paint to disable both root reservation and lock correction. Compare classic/overlay and short/long body content through menus/dialogs; measure before fullPage screenshots.
136
+ - For owned Next services under NaeilLocaleProvider, import header/account/footer from @naeil/ui/naeil-locale/chrome. Read docs/components/naeil-locale.md for the root controller, provider, server routing, message loading and checker. /naeil-header and /naeil-footer remain standalone React adapters.
137
+ - Owned services must set service="abstorm" / "home" / "estimate" / "jev". The DS renders logo → .naeil → lowercase id; no statusSlot, status dot or extra indicator beside the brand.
138
+ - Supply only the service's own appNavigation tabs; never copy naeil.dev Projects/Blog. Single-screen services leave appNavigation empty; the mobile menu still exposes theme/locale/account without empty navigation.
139
+ - No header escape hatches: brandSuffix/brandSlot/toolbarSlot/subNav/brandLinkComponent, catalog/translations overrides, brandLinkProps/mobileFooter, className/containerClassName/style or data-slot/data-naeil-* marker overrides. Keep the shared tab and toolbar styles/states.
140
+ - Local wrappers supply menu data, routing/guards, locale and shared auth state. Do not copy the menu, mobile sheet, controls or footer implementation. Extend the shared component for reusable missing behavior.
141
+ - Since 0.9.0, shared CSS reserves a stable root gutter through globals or theme+components; opt out on html with data-naeil-scrollbar-gutter="auto" before first paint to disable both root reservation and lock correction. Compare classic/overlay and short/long body content through menus/dialogs; measure before fullPage screenshots.
133
142
  - Use ui-app-frame on main; navigation/footer own their internal frames. Keep reading/settings/short-form sections inside that frame. Apply frame overrides consistently across routes.
134
143
  - Check the installed package version and CSS before creating a replacement for an apparently missing component.
135
144
  - Verify actual shared navigation/footer rendering and stable anchors across routes, themes and supported sizes, including signed-out, signed-in, menu, busy, cancellation/failure and retry states. An import or anonymous-only check is not adoption evidence. Record intentional route exceptions.
@@ -144,16 +153,3 @@ Owned-service checks also verify canonical header/footer destinations, rendered
144
153
  A blanket ban on `<header>`, `<nav>` or `<footer>` would also reject valid section landmarks and adapters. Review the root composition and its rendered output instead. This repository checks packed React/Next examples; external applications still need their own upgrades, adapter migrations and CI. Publication and deployment are separate work.
145
154
 
146
155
  See [canonical footer content and save interception](naeil-footer.md). Version 0.7.2 includes package/contract identity; registry publication, hosted Docs and per-service operational rollout have separate receipts.
147
-
148
- ## Shared naeil.dev login (services on `*.naeil.dev`)
149
-
150
- Live since 2026-10-22 for naeil.dev, abestimate.naeil.dev and jev.naeil.dev. A new service joins the same login instead of running its own session, relay or password. Home Console (`:2455` on the tailnet) is outside the cookie scope.
151
-
152
- - **Session:** read the default `@supabase/ssr` 0.9.0 cookie `sb-dathsxdsaxooifnxjket-auth-token` (and its `.0`, `.1` chunks) with `Domain=.naeil.dev; Path=/; Secure; SameSite=Lax`. Write refreshed chunks with the same options; never set a host-only copy.
153
- - **Identity and access:** on the server call `supabase.auth.getUser()`, then `rpc('access_decision', { p_service_id: '<service>' })` with that session's access token. `allowed` → continue; `not_granted` / `member_inactive` / `service_inactive` → 403 page; terminal session errors → treat as signed out; other failures → 503.
154
- - **Signed out:** redirect (307) to `https://naeil.dev/login?next=<absolute URL>`. Add the service origin to the site's `canonicalRedirect` allowlist (`src/lib/auth/redirect.ts`).
155
- - **Logout:** `signOut({ scope: 'local' })`, then expire every shared cookie chunk with the same Domain/Path. All services see the user as signed out on the next request.
156
- - **Header:** `NaeilHeaderAccountControl` in `authSlot`, linked to the login URL and the logout callback.
157
- - **Onboarding:** register the service id in the central catalog, mark it verified, and grant users from Home Console (`console_set_access`).
158
-
159
- Reference code: Estimate `src/server/naeil-shared-session.ts`; site `src/lib/auth/site-session.ts` and `cookie-domain.ts`. Commit `c076391` handles a stale pre-2026-10-17 host-only apex cookie that otherwise breaks the OAuth callback.
@@ -10,7 +10,7 @@ Avatar forwards Radix Root span props and defaults **size="default"**; sizes `sm
10
10
 
11
11
  Inherited [Avatar 1.1.10 declaration](https://unpkg.com/@radix-ui/react-avatar@1.1.10/dist/index.d.ts).
12
12
 
13
- The [shared usage contract](common.md) covers setup, inherited/native prop typing, themes, density, font delivery and overrides. [Wrapper source](https://github.com/naeil-dev/naeil-ui/blob/81fd395031dc5b20074c4d915a8585360ce2d28a/src/components/ui/avatar.tsx) is the API authority.
13
+ The [shared usage contract](common.md) covers setup, inherited/native prop typing, themes, density, font delivery and overrides. [Wrapper source](https://github.com/naeil-dev/naeil-ui/blob/main/src/components/ui/avatar.tsx) is the API authority.
14
14
 
15
15
  ## Composition
16
16
 
@@ -6,7 +6,9 @@ Use Badge for concise status/category text. Prefer readable body/helper text for
6
6
 
7
7
  Exports: `Badge`, `badgeVariants`. Native span props plus `variant` and `asChild`. Defaults: **variant="default", asChild=false**. Variants: `default`, `secondary`, `outline`, `destructive`, `success`, `warning`, `error`, `info`. asChild uses Slot.Root; no disabled/loading or announcement API. Default text is intentionally small and single-line.
8
8
 
9
- The [shared usage contract](common.md) covers setup, inherited/native prop typing, themes, density, font delivery and overrides. [Wrapper source](https://github.com/naeil-dev/naeil-ui/blob/81fd395031dc5b20074c4d915a8585360ce2d28a/src/components/ui/badge.tsx) is the API authority.
9
+ **Server Components:** To call `badgeVariants` on the server, import it from `@naeil/ui/components/ui/badge`. The `@naeil/ui/ui` component barrel remains a client boundary. Import `cn` from `@naeil/ui/utils` or the RSC-safe deep path `@naeil/ui/lib/utils`.
10
+
11
+ The [shared usage contract](common.md) covers setup, inherited/native prop typing, themes, density, font delivery and overrides. [Wrapper source](https://github.com/naeil-dev/naeil-ui/blob/main/src/components/ui/badge.tsx) is the API authority.
10
12
 
11
13
  ## Composition
12
14
 
@@ -6,6 +6,8 @@ Use Button for an action, a native anchor for navigation, and Select for a value
6
6
 
7
7
  Exports: `Button`, `buttonVariants`.
8
8
 
9
+ **Server Components:** To call `buttonVariants` on the server, import it from `@naeil/ui/components/ui/button`. The `@naeil/ui/ui` component barrel remains a client boundary. Import `cn` from `@naeil/ui/utils` or the RSC-safe deep path `@naeil/ui/lib/utils`.
10
+
9
11
  | Prop | Contract / wrapper default |
10
12
  | --- | --- |
11
13
  | `variant` | `default` (default), `destructive`, `outline`, `secondary`, `ghost`, `link` |
@@ -15,7 +17,7 @@ Exports: `Button`, `buttonVariants`.
15
17
 
16
18
  `size` names are preserved; shared CSS controls actual height (40/36px; lg adds 4px; mobile minimum 44px). `xs` does not imply an extra-small touch target. `loading` is not an API.
17
19
 
18
- The [shared usage contract](common.md) covers setup, inherited/native prop typing, themes, density, font delivery and overrides. [Wrapper source](https://github.com/naeil-dev/naeil-ui/blob/81fd395031dc5b20074c4d915a8585360ce2d28a/src/components/ui/button.tsx) is the API authority.
20
+ The [shared usage contract](common.md) covers setup, inherited/native prop typing, themes, density, font delivery and overrides. [Wrapper source](https://github.com/naeil-dev/naeil-ui/blob/main/src/components/ui/button.tsx) is the API authority.
19
21
 
20
22
  ## Composition
21
23
 
@@ -39,3 +41,7 @@ Use `variant="destructive"` for the filled dangerous-action pair and `text-error
39
41
  Demonstrate normal, disabled, saving and recovery; no-data/error belong to the operation. Do not nest Button in a link or treat variant="link" as navigation. With asChild anchors, own href/activation/disabled policy. For long translated actions use `className="h-auto min-h-11 whitespace-normal py-2"` and check both densities.
40
42
 
41
43
  Check this example in light/dark, comfortable/compact and mobile, including long Korean, English and Japanese text. Consumer overrides remain supported; follow the [shared override and acceptance guidance](common.md#styling-themes-and-overrides). Actual engine results and manual limits are in the [support matrix](../design/public-ui-support.md).
44
+
45
+ ## Pressed state and compatibility (2026-11-12)
46
+
47
+ Use `aria-pressed={selected}` for toggle actions. Every variant now shows a persistent 2px inset indicator; default uses its hover surface, and outline/secondary/ghost/link use `surface-selected` with foreground text. The destructive variant retains its dangerous-action fill. Filled variants inset the indicator 2px from the edge so it is surrounded by the contrasting fill. Indicators meet 3:1 against their adjacent fill and text meets 4.5:1 in both themes, including destructive hover. This changes the appearance of existing toggle buttons. Consumer `aria-pressed:*` classes still merge last; override the indicator and its contrast deliberately. `aria-pressed={false}` and ordinary actions retain their existing appearance.
@@ -6,7 +6,7 @@ Use Card to group related information or a form. Use plain sections/headings for
6
6
 
7
7
  Exports: `Card`, `CardElevated`, `CardHeader`, `CardFooter`, `CardTitle`, `CardAction`, `CardDescription`, `CardContent`. All accept `React.ComponentProps<"div">`; no variant/size/asChild/loading API. Card and CardElevated use `data-slot="card"`, default panel spacing and consumer className; CardElevated adds shadow/elevated dark surface. CardTitle is a **div**, not a semantic heading. CardAction occupies the second header column.
8
8
 
9
- The [shared usage contract](common.md) covers setup, inherited/native prop typing, themes, density, font delivery and overrides. [Wrapper source](https://github.com/naeil-dev/naeil-ui/blob/81fd395031dc5b20074c4d915a8585360ce2d28a/src/components/ui/card.tsx) is the API authority.
9
+ The [shared usage contract](common.md) covers setup, inherited/native prop typing, themes, density, font delivery and overrides. [Wrapper source](https://github.com/naeil-dev/naeil-ui/blob/main/src/components/ui/card.tsx) is the API authority.
10
10
 
11
11
  ## Composition
12
12
 
@@ -30,3 +30,7 @@ Static cards are not tab stops. Put real buttons/links inside and a semantic hea
30
30
  Loading/empty/error are consumer content with status/retry actions, not Card props; disabled is inapplicable to the static surface. Keep CardAction concise, wrap long localized headings and use `w-full max-w-*` instead of fixed widths. Avoid nested cards and implicit clickable-card behavior; an entire link card must contain no nested controls.
31
31
 
32
32
  Check this example in light/dark, comfortable/compact and mobile, including long Korean, English and Japanese text. Consumer overrides remain supported; follow the [shared override and acceptance guidance](common.md#styling-themes-and-overrides). Actual engine results and manual limits are in the [support matrix](../design/public-ui-support.md).
33
+
34
+ ## Typography and theme compatibility (2026-11-12)
35
+
36
+ CardTitle now uses the component-title role (16/25px), replacing `leading-none`; CardDescription uses supporting text (14/22px). Wrapped titles and descriptions can make cards taller. Explicit `className` typography/leading overrides still win. CardElevated now also uses its elevated dark surface under OS dark with no root theme class when importing globals.css.
@@ -8,7 +8,7 @@ Export: `Checkbox`. Forwards Radix Root props: `checked`/`defaultChecked`/`onChe
8
8
 
9
9
  Inherited [Checkbox 1.3.3 declaration](https://unpkg.com/@radix-ui/react-checkbox@1.3.3/dist/index.d.ts).
10
10
 
11
- The [shared usage contract](common.md) covers setup, inherited/native prop typing, themes, density, font delivery and overrides. [Wrapper source](https://github.com/naeil-dev/naeil-ui/blob/81fd395031dc5b20074c4d915a8585360ce2d28a/src/components/ui/checkbox.tsx) is the API authority.
11
+ The [shared usage contract](common.md) covers setup, inherited/native prop typing, themes, density, font delivery and overrides. [Wrapper source](https://github.com/naeil-dev/naeil-ui/blob/main/src/components/ui/checkbox.tsx) is the API authority.
12
12
 
13
13
  ## Composition
14
14
 
@@ -1,6 +1,6 @@
1
1
  # Shared usage contract
2
2
 
3
- These maintained guides cover published exact **@naeil/ui 0.8.0** and the unpublished **0.9.0 frame candidate**; see [release identities and status](../package-release.md#publication-and-deployment). The [hosted Docs](https://naeil-dev.github.io/naeil-ui/) were last verified for [0.5.0 on 2026-10-15](https://github.com/naeil-dev/naeil-ui/releases/tag/v0.5.0), while `pnpm storybook` shows this checkout. Start with the [consumer setup and migration](../design/v2-migration.md). Primitives and generic Navigation/SiteFooter are exported from `@naeil/ui/ui`; the actual branded whole header uses the opt-in [NaeilHeader](naeil-header.md) entry `@naeil/ui/naeil-header`. Both are React-only. The legacy root needs the optional Next/next-intl peers. Examples import real wrappers; product state, validation and network requests belong to consumers.
3
+ These maintained guides describe the repository APIs; for the current published release, see [release identities and status](../package-release.md#publication-and-deployment). The [hosted Docs](https://naeil-dev.github.io/naeil-ui/) were last verified for [0.5.0 on 2026-10-15](https://github.com/naeil-dev/naeil-ui/releases/tag/v0.5.0), while `pnpm storybook` shows this checkout. Start with the [consumer setup and migration](../design/v2-migration.md). Primitives and generic Navigation/SiteFooter are exported from `@naeil/ui/ui`; the actual branded whole header uses the opt-in [NaeilHeader](naeil-header.md) entry `@naeil/ui/naeil-header`. Both are React-only. The legacy root needs the optional Next/next-intl peers. Examples import real wrappers; product state, validation and network requests belong to consumers.
4
4
 
5
5
  ## Styling, themes and overrides
6
6
 
@@ -35,3 +35,18 @@ Run each affected composition under matching light/dark, comfortable/compact, mo
35
35
  ## Exact inherited contracts
36
36
 
37
37
  Native wrappers use React 19 element props: inspect your installed `@types/react/index.d.ts`, or infer `React.ComponentProps<typeof Component>`. Radix wrappers use `React.ComponentProps<typeof Primitive.Part>`; inspect the installed declarations reached through `radix-ui/dist/index.d.ts`. Per-family guides link pinned declarations inspected locally during this work. Those links describe the inherited contract, not a promise that all primitive configurations have been tested. Wrapper defaults below are separate from primitive defaults; omitted primitive defaults are not invented here. Consumer examples and browser checks demonstrate only their recorded configuration.
38
+
39
+ ## Theme and typography compatibility (2026-11-12)
40
+
41
+ The `dark:` variant in globals.css now follows the same conditions as generated dark tokens: `.dark` on an element or ancestor, plus OS dark under `:root:not(.light)` (including the root itself). Root `.light` opts out of OS dark; a deliberately nested `.dark` still opts into dark. Use only one root theme class. `.light` is a root OS opt-out, not a nested light-theme reset. Consumers composing theme.css + components.css with their own Tailwind entry must copy this variant into that entry to match the token contract:
42
+
43
+ ```css
44
+ @custom-variant dark {
45
+ &:where(.dark, .dark *) { @slot; }
46
+ @media (prefers-color-scheme: dark) {
47
+ &:where(:root:not(.light), :root:not(.light) *) { @slot; }
48
+ }
49
+ }
50
+ ```
51
+
52
+ Generated font-size utilities now include the approved line heights: `text-3xl` 32/42px, `text-xl` 20/29px, `text-base` 16/25px (UI body, component title and controls), `text-sm` 14/22px and `text-xs` 13/20px at a 16px root. Reading body uses `text-base leading-relaxed` (16/30px). PageTitle, DialogTitle and CardTitle no longer override their role line heights. This changes wrapping and vertical rhythm; recheck constrained layouts. Explicit consumer `leading-*` utilities remain supported. Numeric values live in typography.json; regenerate with `pnpm build:tokens`.
@@ -10,7 +10,7 @@ Root forwards Radix `open`/`defaultOpen`/`onOpenChange`/`modal`; wrapper adds no
10
10
 
11
11
  Inherited [Dialog 1.1.15 declaration](https://unpkg.com/@radix-ui/react-dialog@1.1.15/dist/index.d.ts).
12
12
 
13
- The [shared usage contract](common.md) covers setup, inherited/native prop typing, themes, density, font delivery and overrides. [Wrapper source](https://github.com/naeil-dev/naeil-ui/blob/81fd395031dc5b20074c4d915a8585360ce2d28a/src/components/ui/dialog.tsx) is the API authority.
13
+ The [shared usage contract](common.md) covers setup, inherited/native prop typing, themes, density, font delivery and overrides. [Wrapper source](https://github.com/naeil-dev/naeil-ui/blob/main/src/components/ui/dialog.tsx) is the API authority.
14
14
 
15
15
  ## Compatibility decision (2026-11-09)
16
16
 
@@ -42,6 +42,10 @@ For the modal example, opening moves focus inside, Tab/Shift+Tab stay inside, Es
42
42
 
43
43
  ## States, resilience and mistakes
44
44
 
45
- Disabled applies to triggers/actions; loading/error/empty are content state. Keep failed input inside the dialog and enable retry. Long/mobile content should remain scrollable within the viewport (e.g. max-h-[calc(100%-2rem)] overflow-y-auto); do not hide all close actions. Consumer outside-dismissal overrides need keyboard/focus review. Do not nest modal roots casually or prevent Escape without an accessible exit.
45
+ Disabled applies to triggers/actions; loading/error/empty are content state. Keep failed input inside the dialog and enable retry. Long/mobile content is scrollable within the dynamic viewport by default; preserve that behavior and do not hide all close actions. Consumer outside-dismissal overrides need keyboard/focus review. Do not nest modal roots casually or prevent Escape without an accessible exit.
46
46
 
47
47
  Check this example in light/dark, comfortable/compact and mobile, including long Korean, English and Japanese text. Consumer overrides remain supported; follow the [shared override and acceptance guidance](common.md#styling-themes-and-overrides). Actual engine results and manual limits are in the [support matrix](../design/public-ui-support.md).
48
+
49
+ ## Scroll default compatibility (2026-11-12)
50
+
51
+ Content now defaults to `max-h-[min(calc(100%-2rem),calc(100dvh-2rem))] overflow-y-auto`, keeping tall content and footer actions reachable inside the dynamic viewport. The percentage cap also respects CSS zoom, where viewport units alone can exceed the visible area. Remove equivalent consumer classes. To own a different scroll layout, pass a replacement max-height/overflow class; `className="max-h-[none] overflow-y-visible"` opts out (the consumer must keep all actions reachable). Title now follows the section-title 20/29px typography role.
@@ -10,7 +10,7 @@ Root: primitive `open`/`defaultOpen`/`onOpenChange`, `modal`, `dir`; no wrapper
10
10
 
11
11
  Inherited [DropdownMenu 2.1.16 declaration](https://unpkg.com/@radix-ui/react-dropdown-menu@2.1.16/dist/index.d.ts), including its inherited Menu types.
12
12
 
13
- The [shared usage contract](common.md) covers setup, inherited/native prop typing, themes, density, font delivery and overrides. [Wrapper source](https://github.com/naeil-dev/naeil-ui/blob/81fd395031dc5b20074c4d915a8585360ce2d28a/src/components/ui/dropdown-menu.tsx) is the API authority.
13
+ The [shared usage contract](common.md) covers setup, inherited/native prop typing, themes, density, font delivery and overrides. [Wrapper source](https://github.com/naeil-dev/naeil-ui/blob/main/src/components/ui/dropdown-menu.tsx) is the API authority.
14
14
 
15
15
  ## Composition
16
16
 
@@ -6,7 +6,7 @@ Use Input for native single-line text/value entry. Use [Textarea](./textarea.md)
6
6
 
7
7
  Export: `Input`. Accepts `React.ComponentProps<"input">`; no wrapper default for `type`, value, disabled or validation. Native omitted type resolves to text. Important props: `value` + `onChange` or `defaultValue`, `name`, `type`, `required`, `readOnly`, `disabled`, `autoComplete`, `inputMode`, `min`/`max`, `minLength`/`maxLength`, `pattern`, form and ARIA props. No built-in label, error text or async validation API.
8
8
 
9
- The [shared usage contract](common.md) covers setup, inherited/native prop typing, themes, density, font delivery and overrides. [Wrapper source](https://github.com/naeil-dev/naeil-ui/blob/81fd395031dc5b20074c4d915a8585360ce2d28a/src/components/ui/input.tsx) is the API authority.
9
+ The [shared usage contract](common.md) covers setup, inherited/native prop typing, themes, density, font delivery and overrides. [Wrapper source](https://github.com/naeil-dev/naeil-ui/blob/main/src/components/ui/input.tsx) is the API authority.
10
10
 
11
11
  ## Composition
12
12
 
@@ -1,6 +1,6 @@
1
1
  # NaeilFooter — canonical service footer
2
2
 
3
- Included in **published and registry-verified 0.7.1**, from the unpublished 0.7.0 candidate. Install exact `@naeil/ui@0.7.1` from npm; hosted Docs and service activation have separate receipts. See [release identities and publication status](../package-release.md#publication-and-deployment). React-only opt-in entry:
3
+ Available since **0.7.1**, the first published release containing this entry. See [release identities and publication status](../package-release.md#publication-and-deployment) for the current published release; hosted Docs and service activation have separate receipts. React-only opt-in entry:
4
4
 
5
5
  ```tsx
6
6
  import { NaeilFooter } from '@naeil/ui/naeil-footer';
@@ -1,6 +1,6 @@
1
1
  # NaeilHeader — branded whole-header composition
2
2
 
3
- Exact **@naeil/ui 0.8.0 is published** (registry `next`; `latest=0.7.2` verified 2026-10-29). These Home APIs are available in that exact version. The **0.9.0 candidate** adds shared root reservation and remains unpublished. Hosted Docs and service activation have separate receipts. See [release identities](../package-release.md#publication-and-deployment).
3
+ Product navigation and controlled mobile APIs are available since 0.8.0; shared root reservation is available since 0.9.0. See [release identities and publication status](../package-release.md#publication-and-deployment) for the current published release; hosted Docs and service activation have separate receipts.
4
4
 
5
5
  ```tsx
6
6
  import { NaeilHeader, NaeilHeaderAccountControl } from '@naeil/ui/naeil-header';
@@ -26,6 +26,7 @@ Only provide `onLocaleChange` when the application's interface supports all thre
26
26
 
27
27
  | Prop | Contract |
28
28
  | --- | --- |
29
+ | `service` | Opt-in `NaeilService`: `abstorm`, `home`, `estimate`, `jev`; exported readonly `naeilServices` lists these ids. Owns lowercase suffix/name, local `/` brand default and product-only tabs. |
29
30
  | `locale` | Required `en`, `ko` or `ja`; selects header-only copy. |
30
31
  | `resolveHref` | Resolves canonical unlocalized paths; defaults to the official localized site. |
31
32
  | `Link`, `currentHref`, `onNavigate` | Routing adapter, active destination and navigation interception inherited from Navigation. |
@@ -33,16 +34,45 @@ Only provide `onLocaleChange` when the application's interface supports all thre
33
34
  | `authSlot` | Canonical `NaeilHeaderAccountControl`, with consumer-owned auth state/actions. Legacy arbitrary markup remains supported. |
34
35
  | `brandHref`, `brandSlot`, `brandLinkProps`, `brandLinkComponent` | Explicit brand destination/content/anchor overrides; default accessible brand name is `.naeil`. |
35
36
  | `projects`, `projectsExternal`, `showProjects`, `showBlog` | Legacy catalog/visibility/native-link overrides; defaults use the complete canonical catalog. |
36
- | `appNavigation` (0.8.0) | `NavigationItem[]` passed unchanged after catalog/blog and before flat `subNav`. Owned product menus use this with `showProjects={false} showBlog={false}`. |
37
+ | `appNavigation` (0.8.0) | `NavigationItem[]` passed unchanged after catalog/blog and before flat `subNav`. Owned services set `service` and supply only their own tabs. |
37
38
  | `brandSuffix` (0.8.0) | Product qualifier inside the brand link; the package owns its 14px type, 4px gap and optical alignment. Ignored when `brandSlot` replaces the complete content. Set `brandLinkProps["aria-label"]` for a product name. |
38
39
  | `statusSlot` (0.8.0) | Exactly once, outside and 8px after the brand link at every width. The consumer owns the content and accessible status. No wrapper is added when omitted. |
39
- | `mobileOpen`, `onMobileOpenChange` (0.8.0) | Controlled/uncontrolled sheet state, also on generic Navigation. Omit `mobileOpen` for internal state; a callback alone observes it. Accepted transitions notify once. In controlled mode callbacks request a change against the committed prop, so another close request is reported if the parent rejects or delays the previous one; an external prop update emits no request. Route changes, ordinary links, close/overlay/Escape dismiss. Desktop crossing dismisses when the desktop navigation gains width; an empty menu retains B1's open sheet until Escape/close/overlay/route change. Modified/middle clicks preserve it; nested menus consume the first Escape. |
40
+ | `mobileOpen`, `onMobileOpenChange` (0.8.0) | Controlled/uncontrolled sheet state, also on generic Navigation. Omit `mobileOpen` for internal state; a callback alone observes it. Accepted transitions notify once. In controlled mode callbacks request a change against the committed prop, so another close request is reported if the parent rejects or delays the previous one; an external prop update emits no request. Route changes, ordinary links, close/overlay/Escape dismiss. Desktop crossing dismisses when the desktop navigation gains width; without `service`, an empty menu retains B1's open sheet until Escape/close/overlay/route change. Modified/middle clicks preserve it; nested menus consume the first Escape. |
40
41
  | `subNav`, `currentProjectKey`, `currentProjectHref` | Legacy extra links and current-project destination. |
41
42
  | `translations`, `labels` | Legacy header/project lookup overrides and navigation/menu/close accessible labels. |
42
43
  | `toolbarSlot` | Replaces the complete default theme/locale/auth toolbar. |
43
44
  | `mobileFooter`, `className`, `containerClassName` | Inherited mobile slot and style overrides; recheck frame/behavior after changes. |
44
45
 
45
- Standard header attributes and the remaining inherited [Navigation props](navigation.md) remain available. Compatibility overrides preserve existing consumers; new applications normally use the canonical composition.
46
+ Standard header attributes and appropriate inherited [Navigation props](navigation.md) remain available. Omitting `service` preserves all legacy overrides and output.
47
+
48
+ ## Owned-service profiles
49
+
50
+ | Profile | Left brand | Center | Brand default |
51
+ | --- | --- | --- | --- |
52
+ | naeil.dev (no `service`) | Logo + `.naeil` | Projects / Blog | Official localized site |
53
+ | `service="abstorm"` | Logo + `.naeil` + `abstorm` | Service's own tabs | `/` |
54
+ | `service="home"` | Logo + `.naeil` + `home` | Service's own tabs | `/` |
55
+ | `service="estimate"` | Logo + `.naeil` + `estimate` | Service's own flat/grouped tabs | `/` |
56
+ | `service="jev"` | Logo + `.naeil` + `jev` | Empty for this single-screen service | `/` |
57
+
58
+ The accessible brand name is `.naeil <id>`. `brandHref` can override the local destination. No status dot or extra brand indicator is allowed. The DS fixes tab shape, position, size, spacing and all interaction states. Theme / locale / account retain the canonical presentation; the app supplies routing/guards, locale and shared account state.
59
+
60
+ `NaeilHeaderProps` remains an extensible legacy interface with `service?: undefined`; `NaeilServiceHeaderProps` is the strict service type. Both are exported from the standalone and provider chrome entries. The component overloads accept the strict service type first and the legacy interface last, so `ComponentProps<typeof NaeilHeader>` remains `NaeilHeaderProps` for existing wrappers. Type service props explicitly as `NaeilServiceHeaderProps`.
61
+
62
+ The `service` discriminated branch rejects `brandSuffix`, `statusSlot`, `brandSlot`, `toolbarSlot`, `subNav`, `brandLinkComponent`, `projects`, `projectsExternal`, `showProjects`, `showBlog`, `translations`, `className` and `containerClassName`. It also rejects inline `style`, `brandLinkProps`, `mobileFooter` and the inherited empty-navigation policy override. JavaScript/cast attempts are ignored, with one warning per prop name in development and none in production. Root `data-slot`, `data-global-header` and `data-naeil-*` markers cannot be replaced. Root attributes such as `id`, `aria-*` and `dir`, plus the consumer `Link` and `authSlot` markup, remain consumer-owned and must not be used to restyle shared chrome. Labels, currentHref/onNavigate, brandHref, appNavigation, locale callbacks and controlled mobile state remain available. Unknown, empty or null runtime service values fall back to the legacy profile without a `service` attribute or stripped legacy props, with one development-only warning.
63
+
64
+ `statusSlot`, `brandSuffix`, `brandSlot`, `toolbarSlot`, `subNav` and `brandLinkComponent` are deprecated for owned services and retained for legacy compatibility. They remain functional without `service`.
65
+
66
+ With no `appNavigation`, the desktop center stays empty and the toolbar remains right-aligned. The mobile menu button stays available; its sheet contains the toolbar without an empty navigation landmark/list. Desktop crossing restores focus to a visible toolbar control when the center has no links/buttons. Resizing to desktop dismisses a service sheet by observing the desktop toolbar, including an empty-center profile. Legacy empty-menu headers retain the B1 resize exception described below.
67
+
68
+ ## Standalone theme and locale controls
69
+
70
+ `NaeilHeaderThemeControl` and `NaeilHeaderLocaleControl` are public exports from `@naeil/ui/naeil-header` and `@naeil/ui/naeil-locale/chrome`. The latter uses the shared provider’s selected messages; the standalone entry supplies its own common dictionaries. Prefer the complete header’s default toolbar.
71
+
72
+ - `NaeilHeaderThemeControl({ locale })` accepts `en | ko | ja`, reads the consumer’s next-themes provider and toggles its resolved light/dark theme. The localized 44px icon button is disabled until mount.
73
+ - `NaeilHeaderLocaleControl({ locale, onLocaleChange })` requires the same locale union and a `(locale) => void` callback. It renders the shared three-language Select; routing, persistence and supported application translations belong to the consumer.
74
+
75
+ See [the locale runtime](naeil-locale.md) before using provider-backed chrome in an owned Next service.
46
76
 
47
77
  ## Account presentation
48
78
 
@@ -67,7 +97,7 @@ The fixed header is 65px: the current 64px navigation token plus a 1px border. C
67
97
 
68
98
  The 0.7.2 default catalog contains naeil-ui and references only `https://naeil.dev/images/whale.png`. Rendering that decorative image requires network access to the official origin. Its bytes are not redistributed in npm; [third-party notices](../../THIRD_PARTY_NOTICES.md) retain unresolved artwork provenance and redistribution limits. Brand marks are not relicensed by the repository MIT license. Captions and links remain usable when images fail.
69
99
 
70
- This entry requires no Next.js, next-intl, site session/auth backend, full application message catalog, hero or site public folder. Generic `/ui` imports do not import its catalog. Storybook provides **Brand / NaeilHeader / Docs, Usage, JapaneseApplication, AccountStates, ReferenceFull, ReferenceCompact**; the Japanese example omits unsupported locale switching and supplies application state to the shared account control. Local examples do not establish hosted deployment.
100
+ This entry requires no Next.js, next-intl, site session/auth backend, full application message catalog, hero or site public folder. Generic `/ui` imports do not import its catalog. Storybook provides **Brand / NaeilHeader / Docs, Usage, JapaneseApplication, AccountStates, Abstorm, Home, Estimate, Jev, ReferenceFull, ReferenceCompact**; the Japanese example omits unsupported locale switching and supplies application state to the shared account control. Local examples do not establish hosted deployment.
71
101
 
72
102
  Verify actual packed consumption, Storybook behavior and rendered comparisons at matching viewport/language/theme. Source imports alone do not prove visual parity. Native Safari, devices, assistive technology, full OAuth and unresolved accessibility observations retain the [documented support limits](../design/public-ui-support.md).
73
103
 
@@ -82,20 +112,19 @@ Marker-and-language-scoped final typography uses a package-local font stack and
82
112
 
83
113
  A controlled mobile sheet defers its shared account action until the account menu and sheet finish closing and restore a surviving header control. The consumer still owns dirty state, confirmation, logout, busy state and the request lock; `onLogout` must tolerate this asynchronous handoff. When a confirmation saves its opener, its restoration policy must also handle a viewport change hiding that opener. Verify normal and reduced motion, cancellation after a desktop transition, pending requests, reopen and retry in the actual application; package verification does not replace actual product acceptance.
84
114
 
85
- The empty-menu desktop exception is intentional B1 compatibility: a zero-width desktop navigation cannot signal the crossing, so the sheet stays open. Escape, its close button, the overlay or a `currentHref` change still close it. Home owns the confirmation's desktop cancel fallback to the visible row account trigger; the package supplies a visible, focusable control and does not implement the confirmation's restoration policy.
115
+ Without `service`, the empty-menu desktop exception is intentional B1 compatibility: a zero-width desktop navigation cannot signal the crossing, so the sheet stays open. Escape, its close button, the overlay or a `currentHref` change still close it. Home owns the confirmation's desktop cancel fallback to the visible row account trigger; the package supplies a visible, focusable control and does not implement the confirmation's restoration policy.
86
116
 
87
117
  ```tsx
88
- <NaeilHeader locale={locale} currentHref={pathname} Link={GuardedLink}
89
- showProjects={false} showBlog={false} appNavigation={navigationItems}
90
- brandHref="/" brandSuffix="Home" brandLinkProps={{ 'aria-label': 'Naeil Home' }}
91
- statusSlot={readinessStatus} mobileOpen={mobileOpen} onMobileOpenChange={setMobileOpen}
118
+ <NaeilHeader service="home" locale={locale} currentHref={pathname} Link={GuardedLink}
119
+ appNavigation={navigationItems}
120
+ mobileOpen={mobileOpen} onMobileOpenChange={setMobileOpen}
92
121
  onLocaleChange={changeLanguage}
93
122
  authSlot={<NaeilHeaderAccountControl locale={locale} account={account} />} />
94
123
  ```
95
124
 
96
- Keep the default theme/locale/separator/account toolbar for this profile; `toolbarSlot` is a compatibility override, not the Home migration path. Omitting all new props preserves the 0.7.2 canonical profile, apart from markers, language correction, release identity and Radix-generated IDs. SVG/Logo IDs and consumer-supplied IDs are not normalized away. See the `docs/superpowers/specs/2026-10-28-canonical-home-chrome.md` acceptance contract for gates and the four allowed Home differences.
125
+ Keep the shared theme/locale/separator/account toolbar; the service profile rejects toolbar replacement. Omitting all new props preserves the 0.7.2 canonical profile, apart from markers, language correction, release identity and Radix-generated IDs. SVG/Logo IDs and consumer-supplied IDs are not normalized away. See the `docs/superpowers/specs/2026-10-28-canonical-home-chrome.md` acceptance contract for gates and the four allowed Home differences.
97
126
 
98
- ## Page-root frame policy (0.9.0 candidate)
127
+ ## Page-root frame policy (since 0.9.0)
99
128
 
100
129
  Both supported CSS paths obtain the stable root rule and scoped Radix duplicate-compensation correction from `components.css`. Theme-only and JS-only imports do not set document scrolling. Declare `<html data-naeil-scrollbar-gutter="auto">` before first paint to disable both pieces for a custom policy. See [application layout](application-layout.md) for custom-body/scroller/helper-class limits, fallback and the [adopted contract](https://github.com/naeil-dev/naeil-ui/blob/434b7d07235316097ca3a55265ee02c1422b0cfd/docs/superpowers/specs/2026-10-29-page-root-scrollbar-gutter.md) for required probes and capture ordering.
101
130