@iyulab/modern-app 0.18.11 → 0.18.17

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,93 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.18.17] - 2026-09-03
4
+
5
+ ### Fixed
6
+
7
+ - **`AppConfig.auth` (the boot-time auth gate) and its `AuthGateConfig`/`AuthGateContext`
8
+ types were entirely undocumented in both reference docs** — the feature has a full worked
9
+ example in the README but no type reference anywhere a consumer would look one up. Also
10
+ documented `app.user` (the authenticated-user getter), and added `enter`/`initialLoad`/
11
+ `useIntercept` — three more real `AppConfig` fields missing specifically from
12
+ `skills/modern-app/references/api.md`.
13
+ - **`FallbackRouteConfig.title` was dropped by `0.18.13`**, which only touched the `render`
14
+ callback's context type and didn't notice it removed the field. Restored in both docs.
15
+ Found by a new internal tool (`type-doc-check.js`) that diffs hand-copied TS interface doc
16
+ snippets against source.
17
+
18
+ ## [0.18.16] - 2026-09-03
19
+
20
+ ### Fixed
21
+
22
+ - **`ThemeInitOptions.store`'s type documented a `'sessionStorage'` value that has
23
+ never existed.** The field's real type is re-exported unchanged from
24
+ `@iyulab/components`' `BrowserStorageOptions`, which only supports
25
+ `'localStorage' | 'cookie'` — confirmed against `BrowserStorage.ts` and its test
26
+ suite, which uses `'sessionStorage'` specifically as the invalid-type negative
27
+ case. All three docs (`docs/theme.md`, `docs/configuration.md`,
28
+ `skills/modern-app/references/api.md`) listed it as valid.
29
+
30
+ ## [0.18.15] - 2026-09-02
31
+
32
+ ### Fixed
33
+
34
+ - **`NotificationOptions.position` documented only 4 of the 9 valid values in
35
+ four places** (`docs/notifications.md`, `docs/configuration.md`,
36
+ `skills/modern-app/references/api.md`, `skills/modern-app/SKILL.md`).
37
+ The field's actual type is `ToastPosition` (`@iyulab/components`, re-exported
38
+ unchanged) and `App.ts` passes `options` straight through to `Toast.*()` with
39
+ no restriction — `app.success(msg, { position: 'middle-center' })` genuinely
40
+ works today. Docs narrowed it to `'top-right' | 'top-left' | 'bottom-right'
41
+ | 'bottom-left'`, silently hiding `top-center`, `middle-left`,
42
+ `middle-center`, `middle-right`, and `bottom-center`.
43
+
44
+ ## [0.18.14] - 2026-09-02
45
+
46
+ ### Fixed
47
+
48
+ - **`SidebarLayoutConfig`'s `mainAriaLabel` and `hasPermission` fields were
49
+ undocumented in all three reference docs** (`docs/layout.md`,
50
+ `docs/configuration.md`, `skills/modern-app/references/layout.md`). Both
51
+ are real, shipped features — `mainAriaLabel` sets the main nav landmark's
52
+ `aria-label`, `hasPermission` filters sidebar items by permission — and
53
+ the README already has a working usage example for `hasPermission`, but
54
+ a consumer reading only the type reference had no way to discover either
55
+ field. Added to all three snippets.
56
+
57
+ ## [0.18.13] - 2026-09-02
58
+
59
+ ### Fixed
60
+
61
+ - **`RouteContext`'s documented field list had drifted independently of
62
+ `RouteConfig`'s.** `0.18.12` fixed `RouteConfig`'s snippet in two of three
63
+ docs but left `RouteContext` untouched everywhere, and `configuration.md`'s
64
+ `RouteConfig` snippet was missed entirely. `docs/configuration.md` and
65
+ `docs/routing.md` were both missing `origin`, `path`, and `hash`
66
+ (`routing.md` also missing `metadata`, despite `RouteConfig.metadata`'s own
67
+ doc comment on the line above pointing to it); `skills/modern-app/references/api.md`
68
+ was missing `origin`, `path`, `query`, `hash`, and `metadata` — a consumer
69
+ reading only that doc had no way to discover `ctx.query` exists at all.
70
+ `configuration.md`'s `RouteConfig` snippet gained `id`, `ignoreCase`, and
71
+ `children`, matching the other two docs. `FallbackRouteConfig`'s `render`
72
+ callback is now typed `RouteContext & { error: RouteError }` — what
73
+ `Router.ts` actually passes — instead of plain `RouteContext` or an
74
+ untyped prose note.
75
+
76
+ ## [0.18.12] - 2026-09-01
77
+
78
+ ### Fixed
79
+
80
+ - **Routing docs never mentioned nested routes (`RouteConfig.children`).**
81
+ `@iyulab/router`'s `RouteConfig` supports it and its own README documents
82
+ it, but `modern-app`'s `docs/routing.md` and `skills/modern-app/references/api.md`
83
+ only reproduced a partial `RouteConfig` snippet that omitted `children`,
84
+ `id`, `ignoreCase`, and `enter`, and narrowed `path` to `string` only. A
85
+ consumer reading only `modern-app`'s docs had no way to discover the
86
+ feature and would flatten routes that share a layout into a single list.
87
+ Both docs now cover the full shape and link to `@iyulab/router`'s README
88
+ as the source of truth, and `docs/routing.md` gained a "Nested routes"
89
+ section with an example.
90
+
3
91
  ## [0.18.11] - 2026-09-01
4
92
 
5
93
  ### Changed
@@ -1,4 +1,4 @@
1
- //#region \0@oxc-project+runtime@0.147.0/helpers/esm/decorate.js
1
+ //#region \0@oxc-project+runtime@0.148.0/helpers/esm/decorate.js
2
2
  function e(e, t, n, r) {
3
3
  var i = arguments.length, a = i < 3 ? t : r === null ? r = Object.getOwnPropertyDescriptor(t, n) : r, o;
4
4
  if (typeof Reflect == "object" && typeof Reflect.decorate == "function") a = Reflect.decorate(e, t, n, r);
@@ -1,4 +1,4 @@
1
- //#region \0@oxc-project+runtime@0.147.0/helpers/esm/decorateMetadata.js
1
+ //#region \0@oxc-project+runtime@0.148.0/helpers/esm/decorateMetadata.js
2
2
  function e(e, t) {
3
3
  if (typeof Reflect == "object" && typeof Reflect.metadata == "function") return Reflect.metadata(e, t);
4
4
  }
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.147.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.147.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { slotHasContent as r } from "../internals/slotted.js";
5
5
  import { styles as i } from "./ActionBar.styles.js";
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.147.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.147.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { getLocaleStrings as r } from "../internals/locale.js";
5
5
  import { slotHasContent as i } from "../internals/slotted.js";
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.147.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.147.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { slotHasContent as r } from "../internals/slotted.js";
5
5
  import { styles as i } from "./GroupBox.styles.js";
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.147.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.147.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { styles as r } from "./InfoField.styles.js";
5
5
  import { html as i } from "lit";
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.147.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.147.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { styles as r } from "./InfoSection.styles.js";
5
5
  import { html as i } from "lit";
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.147.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.147.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { getLocaleStrings as r } from "../internals/locale.js";
5
5
  import { slotHasContent as i } from "../internals/slotted.js";
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.147.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.147.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { getLocaleStrings as r } from "../internals/locale.js";
5
5
  import { slotHasContent as i } from "../internals/slotted.js";
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.147.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.147.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { DEFAULT_NAV_ICON as r } from "../internals/nav-icon.js";
5
5
  import { styles as i } from "./SidebarButton.styles.js";
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.147.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.147.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { DEFAULT_NAV_ICON as r } from "../internals/nav-icon.js";
5
5
  import { SidebarLink as i } from "./SidebarLink.js";
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.147.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.147.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { DEFAULT_NAV_ICON as r } from "../internals/nav-icon.js";
5
5
  import { styles as i } from "./SidebarLink.styles.js";
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.147.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.147.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { styles as r } from "./SidebarSection.styles.js";
5
5
  import { html as i } from "lit";
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.147.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.147.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { getLocaleStrings as r } from "../internals/locale.js";
5
5
  import { styles as i } from "./Wizard.styles.js";
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.147.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.147.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
3
3
  import { property as n } from "lit/decorators.js";
4
4
  import { UElement as r } from "@iyulab/components/dist/components/UElement.js";
5
5
  //#region src/internals/StyledElement.ts
@@ -1,7 +1,7 @@
1
1
  import { app as e } from "../App.js";
2
2
  import { filterSidebarItems as t } from "./filterSidebarItems.js";
3
- import n from "../_virtual/_@oxc-project_runtime@0.147.0/helpers/esm/decorateMetadata.js";
4
- import r from "../_virtual/_@oxc-project_runtime@0.147.0/helpers/esm/decorate.js";
3
+ import n from "../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
4
+ import r from "../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
5
5
  import { StyledElement as i } from "../internals/StyledElement.js";
6
6
  import { getLocaleStrings as a } from "../internals/locale.js";
7
7
  import "../components/SidebarSection.js";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@iyulab/modern-app",
3
3
  "description": "web-framework by iyulab based on lit-element",
4
- "version": "0.18.11",
4
+ "version": "0.18.17",
5
5
  "keywords": [
6
6
  "iyulab",
7
7
  "web-framework",
@@ -105,7 +105,7 @@ await app.warning('Check your input');
105
105
  await app.error('Something went wrong');
106
106
  ```
107
107
 
108
- `position` values: `'top-right'` | `'top-left'` | `'bottom-right'` | `'bottom-left'`
108
+ `position` values: `'top-left'` | `'top-center'` | `'top-right'` | `'middle-left'` | `'middle-center'` | `'middle-right'` | `'bottom-left'` | `'bottom-center'` | `'bottom-right'`
109
109
  `duration` default: `3000` ms
110
110
 
111
111
  ---
@@ -19,6 +19,18 @@ interface AppConfig {
19
19
  /** Fallback rendered on 404 or unhandled errors. */
20
20
  fallback?: FallbackRouteConfig;
21
21
 
22
+ /**
23
+ * Global auth/authorization guard, called before every navigation.
24
+ * `string` = redirect, `false` = cancel (403), `true`/undefined = proceed.
25
+ */
26
+ enter?: (ctx: RouteContext) => Promise<string | boolean> | string | boolean;
27
+
28
+ /** Auto-navigate to the current URL on load. Default: true */
29
+ initialLoad?: boolean;
30
+
31
+ /** Intercept `<a>` tag clicks for client-side routing. Default: true */
32
+ useIntercept?: boolean;
33
+
22
34
  /** Layout configuration. Currently only 'sidebar' is supported. */
23
35
  layout: LayoutConfig;
24
36
 
@@ -27,6 +39,12 @@ interface AppConfig {
27
39
 
28
40
  /** i18next options plus optional plugins array. Omit to skip i18n. */
29
41
  i18n?: I18nInitOptions;
42
+
43
+ /**
44
+ * Boot-time auth gate. When set, resolves the session via `me()` before the app shell
45
+ * is built. Omit for full backward compatibility (no gate). See `AuthGateConfig` below.
46
+ */
47
+ auth?: AuthGateConfig;
30
48
  }
31
49
  ```
32
50
 
@@ -57,11 +75,10 @@ interface ThemeInitOptions {
57
75
  * Persist the user's preference.
58
76
  * Set to `false` to disable persistence.
59
77
  * Default: localStorage with no prefix.
78
+ * `type: 'cookie'` also accepts path/domain/expires/sameSite/partitioned — see
79
+ * `@iyulab/components`'s `BrowserStorage` reference. No `'sessionStorage'` backend exists.
60
80
  */
61
- store?: false | {
62
- type: 'cookie' | 'localStorage' | 'sessionStorage';
63
- prefix?: string;
64
- };
81
+ store?: false | { type: 'localStorage'; prefix?: string } | { type: 'cookie'; prefix?: string };
65
82
 
66
83
  /** Apply built-in CSS custom properties. Default: true */
67
84
  useBuiltIn?: boolean;
@@ -72,13 +89,18 @@ interface ThemeInitOptions {
72
89
 
73
90
  ## `RouteConfig`
74
91
 
92
+ `RouteConfig` is re-exported unchanged from `@iyulab/router` — see its README for the full
93
+ reference. Supports `children: RouteConfig[]` for nested routes (a matched parent renders
94
+ its own `<u-outlet>` to host the matched child) — see `docs/routing.md`'s "Nested routes"
95
+ section for an example.
96
+
75
97
  ```typescript
76
98
  interface RouteConfig {
77
99
  /** Matches the root path (equivalent to `path: ''`). */
78
100
  index?: boolean;
79
101
 
80
- /** Path string. Supports `:param` segments. */
81
- path?: string;
102
+ /** Path string or `URLPattern`. Supports `:param` segments. */
103
+ path?: string | URLPattern;
82
104
 
83
105
  /** Sets `document.title` when the route activates. */
84
106
  title?: string;
@@ -86,6 +108,26 @@ interface RouteConfig {
86
108
  /** Force a re-render even if the path did not change. */
87
109
  force?: boolean;
88
110
 
111
+ /** Case-insensitive path matching. Defaults to `false`. */
112
+ ignoreCase?: boolean;
113
+
114
+ /** Identifier used internally by the router (auto-generated if omitted). */
115
+ id?: string;
116
+
117
+ /** Metadata merged parent → child, exposed on `RouteContext.metadata`. */
118
+ metadata?: Record<string, unknown>;
119
+
120
+ /**
121
+ * Guard called before this route activates.
122
+ * - Return a `string` to redirect.
123
+ * - Return `false` to cancel navigation.
124
+ * - Return `true` (or nothing) to proceed.
125
+ */
126
+ enter?: (context: RouteContext) => Promise<string | boolean> | string | boolean;
127
+
128
+ /** Child routes, matched relative to this route's path. */
129
+ children?: RouteConfig[];
130
+
89
131
  /** Render function. May be async. */
90
132
  render: (context: RouteContext) => RenderResult | Promise<RenderResult>;
91
133
  }
@@ -100,14 +142,29 @@ interface RouteContext {
100
142
  /** Full URL string. */
101
143
  href: string;
102
144
 
103
- /** Pathname portion of the URL. */
104
- pathname: string;
145
+ /** Domain name portion of the URL (e.g. `https://example.com`). */
146
+ origin: string;
105
147
 
106
148
  /** Configured basepath. */
107
149
  basepath: string;
108
150
 
151
+ /** Full path including query string and hash. */
152
+ path: string;
153
+
154
+ /** Pathname portion of the URL. */
155
+ pathname: string;
156
+
109
157
  /** Named URL parameters extracted from the path pattern. */
110
- params: Record<string, string>;
158
+ params: Record<string, string | undefined>;
159
+
160
+ /** Parsed query string. `query.get('key')` returns `string | null`. */
161
+ query: URLSearchParams;
162
+
163
+ /** Hash portion of the URL, if present. */
164
+ hash?: string;
165
+
166
+ /** Metadata merged parent → child from the matched route chain. */
167
+ metadata: Record<string, unknown>;
111
168
 
112
169
  /**
113
170
  * Report loading progress (0–100).
@@ -123,12 +180,15 @@ interface RouteContext {
123
180
 
124
181
  ```typescript
125
182
  interface FallbackRouteConfig {
126
- render: (context: RouteContext) => RenderResult | Promise<RenderResult>;
183
+ /** Sets `document.title` when the fallback renders. */
184
+ title?: string;
185
+
186
+ render: (context: RouteContext & { error: RouteError }) => RenderResult | Promise<RenderResult>;
127
187
  }
128
188
  ```
129
189
 
130
- The fallback `RouteContext` will include an `error` property when triggered by
131
- a routing error.
190
+ `RouteError` is importable from `@iyulab/router` (`code`/`original`/`timestamp` alongside the
191
+ inherited `message`) — always populated when the fallback fires.
132
192
 
133
193
  ---
134
194
 
@@ -149,7 +209,40 @@ type I18nInitOptions = i18next.InitOptions & {
149
209
  interface NotificationOptions {
150
210
  title?: string;
151
211
  duration?: number; // milliseconds, default 3000
152
- position?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left';
212
+ position?: 'top-left' | 'top-center' | 'top-right'
213
+ | 'middle-left' | 'middle-center' | 'middle-right'
214
+ | 'bottom-left' | 'bottom-center' | 'bottom-right';
215
+ }
216
+ ```
217
+
218
+ ---
219
+
220
+ ## `AuthGateConfig` / `AuthGateContext`
221
+
222
+ The framework owns only the orchestration (check → branch → reload) — session lookup/login itself
223
+ (HTTP), the user/permission shape, and mid-session 401 handling belong to the app (or
224
+ `@iyulab/enterprise`'s `createAuthClient`/`createODataService`).
225
+
226
+ ```typescript
227
+ interface AuthGateConfig {
228
+ /** Resolve the current session. Return a value for authenticated, `null`/`undefined` for not. */
229
+ me: () => Promise<unknown | null | undefined> | unknown | null | undefined;
230
+
231
+ /**
232
+ * Renders login UI into `context.root` when unauthenticated. Call `context.onSuccess()` on
233
+ * success. Return a cleanup function to have it called on app load/`unload`.
234
+ */
235
+ renderLogin: (context: AuthGateContext) => (() => void) | void;
236
+
237
+ /** Called once authenticated, right before the app shell is built. */
238
+ onAuthenticated?: (user: unknown) => void | Promise<void>;
239
+ }
240
+
241
+ interface AuthGateContext {
242
+ /** Root element to render the login UI into (same as `AppConfig.root`, default `document.body`). */
243
+ root: Element;
244
+ /** Call on successful login — the app (re)loads and the shell appears. */
245
+ onSuccess: () => void;
153
246
  }
154
247
  ```
155
248
 
@@ -175,5 +268,6 @@ interface NotificationOptions {
175
268
  | `config` | `AppConfig \| undefined` | Current config passed to `load()` |
176
269
  | `router` | `Router \| undefined` | Underlying `@iyulab/router` instance |
177
270
  | `screen` | `ScreenSize \| undefined` | Current responsive screen size |
271
+ | `user` | `unknown` | Authenticated user when the `auth` boot gate is used; `undefined` if unauthenticated or unused |
178
272
  | `theme` | `Theme` (static) | Theme utility (`get`, `set`, `isInitialized`) |
179
273
  | `i18n` | `i18next` | Raw i18next instance |
@@ -18,6 +18,12 @@ interface SidebarLayoutConfig {
18
18
  /** Footer (bottom-pinned) items. */
19
19
  footer?: SidebarItem[];
20
20
 
21
+ /** Accessible name for the main nav landmark (`<nav class="sidebar-main">`), reflected as `aria-label`. Unset by default. */
22
+ mainAriaLabel?: string;
23
+
24
+ /** Permission filter — hides items (and emptied section/groups) whose requirement fails. Unset shows everything. See "권한 기반 메뉴 필터" below. */
25
+ hasPermission?: (code: string) => boolean;
26
+
21
27
  /** Per-part style overrides (CSS custom properties / inline styles). */
22
28
  styles?: StyleMap<SidebarParts>;
23
29
  }