@iyulab/modern-app 0.18.19 → 0.19.1

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,47 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.19.1] - 2026-09-13
4
+
5
+ ### Added
6
+
7
+ - **Development-mode warning when the sidebar shell has no sized ancestor.** `u-sidebar-layout`
8
+ fills its parent with `height: 100%`; when a custom root has no height the shell sits at its own
9
+ chrome height (about 133px) and the route area is a few lines tall, with no error. It now says so
10
+ once (`[@iyulab/modern-app]`, development builds only) and points at the fix
11
+ (`#app { height: 100vh }` — `app.load()` already does this for `document.body`).
12
+
13
+ ### Changed
14
+
15
+ - **`@iyulab/router` range is now `^0.12.0`** (was `^0.11.0`, which a 0.x caret does not extend
16
+ to 0.12). Router 0.12.0 adds `RouteConfig.key`, so a route rendered through `app.load()` can now
17
+ keep its content across query-string-only navigations by declaring `key: ctx => ctx.pathname`.
18
+ Nothing in this package's own behaviour changes.
19
+
20
+ ## [0.19.0] - 2026-09-10
21
+
22
+ ### Changed
23
+
24
+ - **Fallback literals for `--u-txt-color-weak` follow `@iyulab/components` 1.40.0.** They are
25
+ only used when that stylesheet is not loaded; with it, nothing changes here.
26
+
27
+ ### Fixed
28
+
29
+ - **`u-master-detail-layout` ignored a change to `overlayBreakpoint` until the element
30
+ happened to resize.** The overlay decision has two inputs — the component's own width
31
+ and the breakpoint — but it was only ever evaluated inside the `ResizeObserver`
32
+ callback, so setting the prop at runtime left the `overlay` attribute on its previous
33
+ verdict. Setting it once before first render always worked, which is why the gap went
34
+ unnoticed. It is now re-evaluated whenever either input changes.
35
+
36
+ ### Changed
37
+
38
+ - **`@iyulab/components` moved from `dependencies` to `peerDependencies`.** It owns
39
+ process-global state — the custom element registry and module-level singletons — so a
40
+ duplicate copy in the install tree splits that state with no error: the notification layer
41
+ stops seeing overlays that a different copy registered, which is exactly the failure the
42
+ 1.39.0 toast fix addresses. Consumers already installing `@iyulab/components` are unaffected;
43
+ the declaration now says that this package uses the copy they installed.
44
+
3
45
  ## [0.18.19] - 2026-09-04
4
46
 
5
47
  ### Added
@@ -1,4 +1,4 @@
1
- //#region \0@oxc-project+runtime@0.148.0/helpers/esm/decorate.js
1
+ //#region \0@oxc-project+runtime@0.149.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.148.0/helpers/esm/decorateMetadata.js
1
+ //#region \0@oxc-project+runtime@0.149.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.148.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.149.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.148.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.149.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";
@@ -7,7 +7,7 @@ var t = e`
7
7
  align-items: center;
8
8
  text-align: center;
9
9
  padding: var(--u-space-4xl, 40px) var(--u-space-xl, 20px);
10
- color: var(--u-txt-color-weak, #757575);
10
+ color: var(--u-txt-color-weak, #616161);
11
11
  }
12
12
 
13
13
  .icon {
@@ -1,5 +1,5 @@
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";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.149.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.148.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.149.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";
@@ -10,7 +10,7 @@ var t = e`
10
10
  font-size: var(--u-text-caption-size, 12px);
11
11
  font-weight: var(--u-text-caption-weight, 400);
12
12
  line-height: var(--u-text-caption-leading, 1.5);
13
- color: var(--u-txt-color-weak, #757575);
13
+ color: var(--u-txt-color-weak, #616161);
14
14
  margin-bottom: var(--u-space-3xs, 2px);
15
15
  }
16
16
 
@@ -37,7 +37,7 @@ var t = e`
37
37
  ⚠더 흐리게 만들고 싶은 유혹이 있는데, 이 자리가 안 읽히면 사용자는 빈 칸인지
38
38
  로딩 중인지 구별하지 못한다. */
39
39
  .value.blank {
40
- color: var(--u-txt-color-weak, #757575);
40
+ color: var(--u-txt-color-weak, #616161);
41
41
  font-weight: var(--u-text-body-weight, 400);
42
42
  }
43
43
 
@@ -71,7 +71,7 @@ var t = e`
71
71
  }
72
72
 
73
73
  .trend.tone-neutral {
74
- color: var(--u-txt-color-weak, #757575);
74
+ color: var(--u-txt-color-weak, #616161);
75
75
  }
76
76
  `;
77
77
  //#endregion
@@ -1,5 +1,5 @@
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";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.149.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";
@@ -52,6 +52,20 @@ export declare class MasterDetailLayout extends StyledElement<ElementParts> {
52
52
  private hasDetail;
53
53
  private resizeObserver?;
54
54
  connectedCallback(): void;
55
+ /**
56
+ * 오버레이 판정 한 곳. **두 입력이 있고 둘 다 바뀔 수 있다** — 자기 폭과
57
+ * `overlayBreakpoint`.
58
+ *
59
+ * 🔴**종전에는 이 판정이 ResizeObserver 콜백 «안에만» 있었다.** 그래서 `overlayBreakpoint`
60
+ * 를 런타임에 바꿔도 **폭이 함께 바뀌지 않으면 아무 일도 일어나지 않았다** — prop 은
61
+ * 새 값을 갖고 있는데 `overlay` 속성은 옛 판정 그대로다. 값에 반응하는 prop 으로
62
+ * 선언·문서화해 놓고 실제로는 리사이즈에만 반응한 셈이라, 이 리포가 「선언 ≠ 동작」이라
63
+ * 부르는 부류다.
64
+ *
65
+ * @param width 0 이면 아직 레이아웃되지 않은 과도 상태일 수 있다(예: 조상이 `display:none`)
66
+ * — 그 순간의 값으로 오버레이를 강제하지 않는다.
67
+ */
68
+ private applyOverlay;
55
69
  disconnectedCallback(): void;
56
70
  protected updated(changed: PropertyValues): void;
57
71
  /**
@@ -1,5 +1,5 @@
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";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.149.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";
@@ -22,15 +22,17 @@ var u = class extends n {
22
22
  }
23
23
  connectedCallback() {
24
24
  super.connectedCallback(), this.resizeObserver = new ResizeObserver((e) => {
25
- let t = e[0]?.contentRect.width ?? 0;
26
- t !== 0 && this.toggleAttribute("overlay", t < this.overlayBreakpoint);
25
+ this.applyOverlay(e[0]?.contentRect.width ?? 0);
27
26
  }), this.resizeObserver.observe(this);
28
27
  }
28
+ applyOverlay(e) {
29
+ e !== 0 && this.toggleAttribute("overlay", e < this.overlayBreakpoint);
30
+ }
29
31
  disconnectedCallback() {
30
32
  super.disconnectedCallback(), this.resizeObserver?.disconnect();
31
33
  }
32
34
  updated(e) {
33
- super.updated(e), e.has("masterSize") && this.style.setProperty("--_master-size", this.masterSize);
35
+ super.updated(e), e.has("masterSize") && this.style.setProperty("--_master-size", this.masterSize), e.has("overlayBreakpoint") && this.applyOverlay(this.getBoundingClientRect().width);
34
36
  }
35
37
  render() {
36
38
  return o`
@@ -1,5 +1,5 @@
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";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.149.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";
@@ -37,7 +37,7 @@ var t = e`
37
37
  font-size: var(--u-text-caption-size, 12px);
38
38
  font-weight: var(--u-text-caption-weight, 400);
39
39
  line-height: var(--u-text-caption-leading, 1.5);
40
- color: var(--u-txt-color-weak, #757575);
40
+ color: var(--u-txt-color-weak, #616161);
41
41
  }
42
42
 
43
43
  .back {
@@ -1,5 +1,5 @@
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";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.149.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.148.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.149.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.148.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.149.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.148.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.149.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.148.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.149.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";
@@ -40,7 +40,7 @@ var t = e`
40
40
  border: none;
41
41
  padding: var(--u-space-2xs, 4px) 0;
42
42
  font: inherit;
43
- color: var(--u-txt-color-weak, #757575);
43
+ color: var(--u-txt-color-weak, #616161);
44
44
  cursor: pointer;
45
45
  text-align: left;
46
46
  }
@@ -1,5 +1,5 @@
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";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.149.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
@@ -24,6 +24,14 @@ export declare class SidebarLayout extends StyledElement<SidebarParts> {
24
24
  connectedCallback(): void;
25
25
  disconnectedCallback(): void;
26
26
  protected willUpdate(changedProperties: PropertyValues): void;
27
+ protected firstUpdated(changedProperties: PropertyValues): void;
28
+ /**
29
+ * 개발 모드 사용 안내(HD-61 ⒝): 이 셸은 `:host { height: 100% }` 로 부모를 채우는데, 부모(커스텀
30
+ * root)에 높이가 없으면 걸릴 곳이 없어 자기 크롬 높이(실측 약 133px)로 앉는다 — 오류도 경고도 없이
31
+ * 라우트 콘텐츠 영역이 몇 줄짜리 띠가 된다. 첫 배치 뒤 한 번 재서 알린다.
32
+ * 임계값은 규칙이라 손으로 쓴다 — 앱 셸이 200px 보다 낮은 것이 의도인 경우는 없다.
33
+ */
34
+ private warnIfUnsized;
27
35
  render(): typeof nothing | import('lit-html').TemplateResult<1>;
28
36
  /** 사이드바 아이템 렌더링 */
29
37
  private renderItem;
@@ -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.148.0/helpers/esm/decorateMetadata.js";
4
- import r from "../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
3
+ import n from "../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
4
+ import r from "../_virtual/_@oxc-project_runtime@0.149.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";
@@ -96,6 +96,10 @@ var v = class extends i {
96
96
  willUpdate(e) {
97
97
  super.willUpdate(e), e.has("config") && (this.styles = this.config?.styles);
98
98
  }
99
+ firstUpdated(e) {
100
+ super.firstUpdated(e), this.warnIfUnsized();
101
+ }
102
+ warnIfUnsized() {}
99
103
  render() {
100
104
  return this.config ? s`
101
105
  <!-- Mobile Header -->
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.19",
4
+ "version": "0.19.1",
5
5
  "keywords": [
6
6
  "iyulab",
7
7
  "web-framework",
@@ -60,12 +60,12 @@
60
60
  "typecheck": "tsc --noEmit"
61
61
  },
62
62
  "dependencies": {
63
- "@iyulab/components": "^1.27.0",
64
- "@iyulab/router": "^0.11.0",
63
+ "@iyulab/router": "^0.12.0",
65
64
  "i18next": "^26.3.6",
66
65
  "lit": "^3.3.3"
67
66
  },
68
67
  "peerDependencies": {
68
+ "@iyulab/components": ">=1.27.0",
69
69
  "@lit/react": ">=1.0.8",
70
70
  "react": ">=18.0.0"
71
71
  },
@@ -86,7 +86,7 @@
86
86
  "happy-dom": "^20.10.6",
87
87
  "react": "^19.2.7",
88
88
  "react-dom": "^19.2.7",
89
- "typescript": "^5.9.3",
89
+ "typescript": "^6.0.2",
90
90
  "vite": "^8.1.4",
91
91
  "vite-plugin-dts": "^5.0.3",
92
92
  "vitest": "^4.1.10"
@@ -269,5 +269,5 @@ interface AuthGateContext {
269
269
  | `router` | `Router \| undefined` | Underlying `@iyulab/router` instance |
270
270
  | `screen` | `ScreenSize \| undefined` | Current responsive screen size |
271
271
  | `user` | `unknown` | Authenticated user when the `auth` boot gate is used; `undefined` if unauthenticated or unused |
272
- | `theme` | `Theme` (static) | Theme utility (`get`, `set`, `isInitialized`) |
272
+ | `theme` | `Theme` (static) | Theme utility `get()`, `set()`, `resolved()`, `accent()`, `isInitialized`. Branch on `resolved()` (always `'light'` or `'dark'`), not `get()`, which can be `'system'` |
273
273
  | `i18n` | `i18next` | Raw i18next instance |
@@ -35,6 +35,22 @@ each pane. CSS container-query conditions cannot read a runtime custom property,
35
35
  configurable breakpoint means measuring the host's own width in JavaScript and reflecting an
36
36
  `overlay` attribute — same self-width philosophy, different mechanism, for a fixed reason.
37
37
 
38
+ ## Sizing
39
+
40
+ This is a shell, not a sized box: `:host` is `height: 100%`, so **the parent decides its height**.
41
+ Inside a page shell that is already constrained (an `app.load()` layout, a grid or flex row with a
42
+ height) there is nothing to do.
43
+
44
+ Put it in a container with no height of its own and `height: 100%` has nothing to resolve against,
45
+ so the panes fall back to their content's height rather than the available space. That renders
46
+ fine — it simply is not the full-height split pane you asked for, and the independent per-pane
47
+ scrolling (`overflow: auto` on `master` and `detail`) only means something once the height is
48
+ constrained.
49
+
50
+ ```css
51
+ .page { height: 100%; } /* or any constrained height on the parent */
52
+ ```
53
+
38
54
  ## Slots
39
55
 
40
56
  | Name | Description |
@@ -228,13 +228,19 @@ for `for="#id"` anchoring to resolve (see the `id` note above).
228
228
  }
229
229
  ```
230
230
 
231
- ⚠**Pick `placement` based on `state`, not a fixed value.** On `mobile`/`mobile-open`, the sidebar
232
- itself widens to occupy nearly the full screen a sideways placement (`right-start`, the natural
233
- choice for a desktop flyout) then has no room on either side, and `flip()` correctly declines to
234
- flip when the opposite side has none either. The popover renders off-screen and is invisible. A
235
- vertical placement (`bottom-start`) has room regardless of sidebar width and works at every state.
236
- Confirmed empirically in `tests/browser/sidebar-popover-submenu.browser.test.ts` — this is not a
237
- `strategy="absolute"` vs `"fixed"` distinction, switching strategy does not change the outcome.
231
+ **A fixed `placement` is safe as of `@iyulab/components@1.37.1`.** On `mobile`/`mobile-open`
232
+ the sidebar widens to occupy nearly the full screen, so a sideways placement (`right-start`,
233
+ the natural desktop flyout) has room on neither side. `flip()` now falls back **across the**
234
+ **axis** in exactly that case, landing the popover vertically instead of off-screen.
235
+
236
+ Against an older `components` it did render off-screen and invisible `flip()` only ever
237
+ considered the opposite side on the same axis, found no room there either, and gave up. If you
238
+ pin below `1.37.1`, keep choosing `placement` from `state` as the snippet above does.
239
+
240
+ Choosing from `state` is still reasonable when you want to *decide* the direction rather than
241
+ let `flip()` pick it. Either way this is not a `strategy="absolute"` vs `"fixed"` distinction —
242
+ switching strategy changes nothing. Both behaviours are pinned in
243
+ `tests/browser/sidebar-popover-submenu.browser.test.ts`.
238
244
 
239
245
  ---
240
246
 
@@ -255,6 +261,21 @@ Parts available for `styles` overrides on the root layout:
255
261
 
256
262
  ---
257
263
 
264
+ ## Sizing
265
+
266
+ `<u-sidebar-layout>` is a shell: `:host` is `height: 100%` with `overflow: hidden`, so **its
267
+ height comes from the parent** — it never sizes itself. `app.load()` covers the default case: when
268
+ `root` is `document.body` it sets `margin: 0; width: 100vw; height: 100vh` on the body for you.
269
+
270
+ ⚠ **A custom `root` receives no styling.** Hand it a container with no height of its own and
271
+ `height: 100%` has nothing to resolve against: the shell renders at whatever its own chrome
272
+ resolves to (measured: about 133px) instead of filling the screen — with no error and nothing in
273
+ the console. Give that container a height:
274
+
275
+ ```css
276
+ #app { height: 100vh; } /* or 100%, inside an already-constrained ancestor */
277
+ ```
278
+
258
279
  ## Responsive behaviour
259
280
 
260
281
  | Screen width | Sidebar state |