@adia-ai/web-components 0.0.23 → 0.0.25

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 (38) hide show
  1. package/components/app-shell/app-shell.a2ui.json +136 -0
  2. package/components/app-shell/app-shell.css +16 -0
  3. package/components/app-shell/app-shell.js +202 -0
  4. package/components/app-shell/app-shell.yaml +183 -0
  5. package/components/aside/aside.a2ui.json +84 -0
  6. package/components/aside/aside.yaml +100 -0
  7. package/components/button/button.css +7 -5
  8. package/components/check/check.css +24 -27
  9. package/components/drawer/drawer.css +356 -349
  10. package/components/drawer/drawer.js +44 -11
  11. package/components/footer/footer.a2ui.json +1 -1
  12. package/components/footer/footer.yaml +1 -1
  13. package/components/header/header.a2ui.json +2 -2
  14. package/components/header/header.yaml +2 -2
  15. package/components/index.js +2 -0
  16. package/components/input/input.css +13 -11
  17. package/components/kbd/kbd.css +1 -1
  18. package/components/modal/modal.js +12 -11
  19. package/components/option-card/option-card.css +28 -36
  20. package/components/page/page.a2ui.json +107 -0
  21. package/components/page/page.css +68 -0
  22. package/components/page/page.js +88 -0
  23. package/components/page/page.yaml +148 -0
  24. package/components/radio/radio.css +13 -14
  25. package/components/range/range.css +8 -4
  26. package/components/section/section.a2ui.json +1 -1
  27. package/components/section/section.yaml +1 -1
  28. package/components/segment/segment.css +7 -7
  29. package/components/switch/switch.css +13 -11
  30. package/components/textarea/textarea.css +10 -5
  31. package/components/toast/toast.css +27 -26
  32. package/components/toggle-group/toggle-group.css +4 -3
  33. package/components/tree/tree.css +21 -9
  34. package/package.json +1 -1
  35. package/patterns/app-nav-item/app-nav-item.css +24 -24
  36. package/patterns/app-shell/app-shell.css +12 -0
  37. package/patterns/section-nav-item/section-nav-item.css +23 -24
  38. package/styles/components.css +2 -0
@@ -0,0 +1,148 @@
1
+ # Edit this file; run `npm run build:components` to regenerate a2ui.json.
2
+ $schema: ../../../../scripts/schemas/component.yaml.schema.json
3
+ name: AdiaPage
4
+ tag: page-ui
5
+ component: Page
6
+ category: container
7
+ version: 1
8
+ description: |
9
+ Page container. Holds page-level chrome — header / content / footer —
10
+ and manages max-width clamps, padding scale, optional scroll-container,
11
+ and an optional sticky-header sentinel. Compose with the slot
12
+ primitives (`<header-ui>`, `<section-ui>`, `<footer-ui>`); the page's
13
+ @scope rules style them. Drop in directly, or nest inside an
14
+ `<app-shell-ui>`'s main column.
15
+ props:
16
+ scroll:
17
+ description: |
18
+ Sets the page as a scroll container. `overflow-y: auto`, full
19
+ height, contained overscroll. Use when the page IS the scroll
20
+ surface (standalone pages); leave off when nested inside a parent
21
+ that already manages scroll (e.g. inside an `<app-shell-ui>`'s
22
+ main `<section>`).
23
+ type: boolean
24
+ default: false
25
+ reflect: true
26
+ maxWidth:
27
+ description: |
28
+ Token-bound max-width clamp. `prose` (65ch) for reading pages,
29
+ `narrow` (80ch) for tight forms, `wide` (1080px) for data-rich
30
+ pages, `full` for unconstrained. Empty defers to parent / 100%.
31
+ Centered horizontally via `margin-inline: auto`.
32
+ type: string
33
+ default: ""
34
+ enum:
35
+ - ""
36
+ - prose
37
+ - narrow
38
+ - wide
39
+ - full
40
+ attribute: max-width
41
+ reflect: true
42
+ padding:
43
+ description: |
44
+ Page-padding scale from the spacing system. Accepts `0`–`8`
45
+ (mapped to `--a-space-N`). Empty (no value) applies the
46
+ `--page-padding-default` token; `0` removes padding.
47
+ type: string
48
+ default: ""
49
+ reflect: true
50
+ stickyHeader:
51
+ description: |
52
+ Installs an IntersectionObserver sentinel before the first
53
+ `<header>` / `<header-ui>` child. When the sentinel scrolls out
54
+ of view the page gains `[data-header-stuck]`, which the CSS
55
+ uses to add a border + shadow to the header. No-op when no
56
+ header is present.
57
+ type: boolean
58
+ default: false
59
+ attribute: sticky-header
60
+ reflect: true
61
+ events: {}
62
+ slots:
63
+ default:
64
+ description: |
65
+ Composes from the slot primitives — `<header-ui>` (page header),
66
+ `<section-ui>` (main content), optional `<footer-ui>`. Native
67
+ `<header>` / `<section>` / `<footer>` also work; the @scope rules
68
+ target both via `:where(header, header-ui)`.
69
+ states:
70
+ - name: idle
71
+ description: Default, ready for interaction.
72
+ - name: header-stuck
73
+ description: Header has scrolled past the sentinel; visual cue applied.
74
+ traits: []
75
+ tokens: {}
76
+ a2ui:
77
+ rules: []
78
+ anti_patterns: []
79
+ examples:
80
+ - name: prose-page
81
+ description: Reading page with sticky header, 65ch column, padding scale 6.
82
+ a2ui: >-
83
+ [
84
+ {
85
+ "id": "root",
86
+ "component": "Page",
87
+ "stickyHeader": true,
88
+ "maxWidth": "prose",
89
+ "padding": "6",
90
+ "children": ["hdr", "body"]
91
+ },
92
+ {
93
+ "id": "hdr",
94
+ "component": "Header",
95
+ "children": ["title"]
96
+ },
97
+ {
98
+ "id": "title",
99
+ "component": "Text",
100
+ "variant": "display",
101
+ "textContent": "Reading Page"
102
+ },
103
+ {
104
+ "id": "body",
105
+ "component": "Section",
106
+ "children": []
107
+ }
108
+ ]
109
+ - name: dashboard-page
110
+ description: Wide-clamp dashboard page acting as a scroll container.
111
+ a2ui: >-
112
+ [
113
+ {
114
+ "id": "root",
115
+ "component": "Page",
116
+ "scroll": true,
117
+ "maxWidth": "wide",
118
+ "padding": "4",
119
+ "children": ["hdr", "body"]
120
+ },
121
+ {
122
+ "id": "hdr",
123
+ "component": "Header",
124
+ "children": []
125
+ },
126
+ {
127
+ "id": "body",
128
+ "component": "Section",
129
+ "children": []
130
+ }
131
+ ]
132
+ keywords:
133
+ - page
134
+ - layout
135
+ - container
136
+ - scroll
137
+ - sticky-header
138
+ - max-width
139
+ - padding
140
+ - prose-page
141
+ - dashboard-page
142
+ synonyms: {}
143
+ related:
144
+ - app-shell
145
+ - card
146
+ - section
147
+ - header
148
+ - footer
@@ -1,6 +1,7 @@
1
- /* Safari 17.x bug: `:scope*:hover [descendant]` inside `@scope` doesn't
2
- match the scope root. The entire selector (including the descendant)
3
- moves out together. See docs/BROWSER-COMPAT.md §3a. */
1
+ /* Safari 17.x bug: `:scope*:hover [descendant]` (Flavor A) and
2
+ `:scope[checked] [descendant]` (Flavor B attribute-removal restyle)
3
+ both fail inside `@scope`. Selectors moved out. See
4
+ docs/BROWSER-COMPAT.md §3a. */
4
5
  radio-ui:not([disabled]):hover [slot="dot"] {
5
6
  border-color: var(--radio-border-hover);
6
7
  background: var(--radio-bg-hover);
@@ -9,6 +10,14 @@ radio-ui[checked]:not([disabled]):hover [slot="dot"] {
9
10
  background: var(--radio-bg-checked-hover);
10
11
  border-color: var(--radio-bg-checked-hover);
11
12
  }
13
+ radio-ui[checked] [slot="dot"] {
14
+ background: var(--radio-bg-checked);
15
+ border-color: var(--radio-border-checked);
16
+ }
17
+ radio-ui[checked] [slot="dot"]::after {
18
+ width: calc(var(--radio-size) * 0.6);
19
+ height: calc(var(--radio-size) * 0.6);
20
+ }
12
21
 
13
22
  @scope (radio-ui) {
14
23
  :where(:scope) {
@@ -84,17 +93,7 @@ radio-ui[checked]:not([disabled]):hover [slot="dot"] {
84
93
  height var(--radio-duration) var(--radio-easing);
85
94
  }
86
95
 
87
- :scope[checked] [slot="dot"]::after {
88
- width: calc(var(--radio-size) * 0.6);
89
- height: calc(var(--radio-size) * 0.6);
90
- }
91
-
92
- /* hover rules moved outside @scope — see Safari 17.x bug note at top. */
93
-
94
- :scope[checked] [slot="dot"] {
95
- background: var(--radio-bg-checked);
96
- border-color: var(--radio-border-checked);
97
- }
96
+ /* hover + [checked] rules moved outside @scope — see Safari 17.x bug note at top. */
98
97
 
99
98
  /* Label */
100
99
  :scope[label]::after { content: attr(label); }
@@ -1,3 +1,10 @@
1
+ /* Safari 17.x bug: `:scope:not(...) [descendant]:hover` inside `@scope`
2
+ doesn't match the scope root. Plain selector outside works. See
3
+ docs/BROWSER-COMPAT.md §3a. */
4
+ range-ui:not([disabled]) [slot="field"]:hover [data-layer="fill"] {
5
+ background: var(--range-fill-bg-hover);
6
+ }
7
+
1
8
  @scope (range-ui) {
2
9
  :where(:scope) {
3
10
  /* ── Tokens (wired to --a-ui-*) ── */
@@ -127,10 +134,7 @@
127
134
  color: var(--range-label-fg-hover);
128
135
  }
129
136
 
130
- /* Hover: brighten the fill */
131
- :scope:not([disabled]) [slot="field"]:hover [data-layer="fill"] {
132
- background: var(--range-fill-bg-hover);
133
- }
137
+ /* Hover-fill brighten moved outside @scope — see Safari 17.x bug note at top. */
134
138
 
135
139
  /* Dragging: deepest fill, sharper border, instant (no transition lag on the clip) */
136
140
  :scope[data-dragging] [slot="field"] {
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://adiaui.dev/a2ui/v0_9/components/Section.json",
4
4
  "title": "Section",
5
- "description": "Content section inside a Card. Groups related content between Header and Footer.",
5
+ "description": "Content section styled by closest container parent (Card / Drawer / Modal / Page / AppShell). Groups related content between Header and Footer. `[scroll]` only kicks in inside Card / Drawer / Modal — Page and AppShell own their own scroll containers.",
6
6
  "type": "object",
7
7
  "allOf": [
8
8
  {
@@ -6,7 +6,7 @@ tag: section-ui
6
6
  component: Section
7
7
  category: container
8
8
  version: 1
9
- description: Content section inside a Card. Groups related content between Header and Footer.
9
+ description: Content section styled by closest container parent (Card / Drawer / Modal / Page / AppShell). Groups related content between Header and Footer. `[scroll]` only kicks in inside Card / Drawer / Modal — Page and AppShell own their own scroll containers.
10
10
  props:
11
11
  scroll:
12
12
  description: Enable overflow scrolling
@@ -1,9 +1,12 @@
1
- /* Safari 17.x bug: `:scope:not(...):hover` inside `@scope` doesn't match
2
- the scope root. Plain selector outside works. See
3
- docs/BROWSER-COMPAT.md §3a. */
1
+ /* Safari 17.x bug: `:scope:not(...):hover` (Flavor A) and `:scope[selected]`
2
+ (Flavor B attribute-removal restyle) both fail inside `@scope`.
3
+ Selectors moved out. See docs/BROWSER-COMPAT.md §3a. */
4
4
  segment-ui:not([disabled]):not([selected]):hover {
5
5
  color: var(--segment-fg-hover);
6
6
  }
7
+ segment-ui[selected] {
8
+ color: var(--segment-fg-selected);
9
+ }
7
10
 
8
11
  @scope (segment-ui) {
9
12
  :where(:scope) {
@@ -80,10 +83,7 @@ segment-ui:not([disabled]):not([selected]):hover {
80
83
  white-space: nowrap;
81
84
  }
82
85
 
83
- /* States — hover rule moved outside @scope; see Safari 17.x bug note at top. */
84
- :scope[selected] {
85
- color: var(--segment-fg-selected);
86
- }
86
+ /* States — hover + [selected] rules moved outside @scope; see Safari 17.x bug note at top. */
87
87
 
88
88
  :scope:focus-visible {
89
89
  outline: none;
@@ -1,5 +1,6 @@
1
- /* Safari 17.x bug: `:scope[attr]:hover` inside `@scope` doesn't match the
2
- scope root. Plain selectors outside work. See docs/BROWSER-COMPAT.md §3a. */
1
+ /* Safari 17.x bug: `:scope[attr]:hover` (Flavor A) and `:scope[checked]`
2
+ token-block (Flavor B attribute-removal restyle) both fail inside
3
+ `@scope`. Selectors moved out. See docs/BROWSER-COMPAT.md §3a. */
3
4
  switch-ui:not([disabled]):hover {
4
5
  --switch-track-bg: var(--switch-track-bg-hover);
5
6
  --switch-track-border: var(--switch-track-border-hover);
@@ -8,6 +9,14 @@ switch-ui[checked]:not([disabled]):hover {
8
9
  --switch-track-bg: var(--switch-track-bg-checked-hover);
9
10
  --switch-track-border: var(--switch-track-border-checked-hover);
10
11
  }
12
+ switch-ui[checked] {
13
+ --switch-track-bg: var(--switch-track-bg-checked);
14
+ --switch-track-border: var(--switch-track-border-checked);
15
+ --switch-thumb-bg: var(--switch-thumb-bg-checked);
16
+ }
17
+ switch-ui[checked] [slot="thumb"] {
18
+ transform: translateX(var(--switch-thumb-travel));
19
+ }
11
20
 
12
21
  @scope (switch-ui) {
13
22
  :where(:scope) {
@@ -77,12 +86,7 @@ switch-ui[checked]:not([disabled]):hover {
77
86
  background var(--switch-duration) var(--switch-easing),
78
87
  border-color var(--switch-duration) var(--switch-easing);
79
88
  }
80
- /* hover rules moved outside @scope — see Safari 17.x bug note at top. */
81
- :scope[checked] {
82
- --switch-track-bg: var(--switch-track-bg-checked);
83
- --switch-track-border: var(--switch-track-border-checked);
84
- --switch-thumb-bg: var(--switch-thumb-bg-checked);
85
- }
89
+ /* hover + [checked] rules moved outside @scope — see Safari 17.x bug note at top. */
86
90
 
87
91
  [slot="thumb"] {
88
92
  position: absolute;
@@ -97,9 +101,7 @@ switch-ui[checked]:not([disabled]):hover {
97
101
  transform var(--switch-duration) var(--switch-easing),
98
102
  background var(--switch-duration) var(--switch-easing);
99
103
  }
100
- :scope[checked] [slot="thumb"] {
101
- transform: translateX(var(--switch-thumb-travel));
102
- }
104
+ /* :scope[checked] [slot="thumb"] moved outside @scope — see Safari 17.x bug note at top. */
103
105
 
104
106
  [slot="label"] { font-size: var(--switch-font-size); }
105
107
 
@@ -1,3 +1,12 @@
1
+ /* Safari 17.x bug: `:scope:not(...) [descendant]:hover` inside `@scope`
2
+ doesn't match the scope root. Plain selector outside works. See
3
+ docs/BROWSER-COMPAT.md §3a. */
4
+ textarea-ui:not([disabled]) [slot="text"]:hover {
5
+ background: var(--textarea-bg-hover);
6
+ border-color: var(--textarea-border-hover);
7
+ color: var(--textarea-fg-hover);
8
+ }
9
+
1
10
  @scope (textarea-ui) {
2
11
  :where(:scope) {
3
12
  /* ── Tokens (wired to --a-ui-*) ── */
@@ -66,11 +75,7 @@
66
75
  outline: none;
67
76
  transition: border-color var(--textarea-duration) var(--textarea-easing);
68
77
  }
69
- :scope:not([disabled]) [slot="text"]:hover {
70
- background: var(--textarea-bg-hover);
71
- border-color: var(--textarea-border-hover);
72
- color: var(--textarea-fg-hover);
73
- }
78
+ /* hover rule moved outside @scope — see Safari 17.x bug note at top. */
74
79
  :scope:not([disabled]) [slot="text"]:focus {
75
80
  /* Canonical ring via L3 token (see semantics.css FOCUS block).
76
81
  `:focus` (not :focus-visible) is deliberate — the caret lives
@@ -2,6 +2,31 @@
2
2
  TOAST-N — Notification popup with auto-dismiss.
3
3
  ═══════════════════════════════════════════════════════════════ */
4
4
 
5
+ /* Safari 17.x bug: `:scope[data-open]` and `:scope[data-closing]`
6
+ (Flavor B — attribute-removal restyle) don't reliably restyle on
7
+ attribute toggling inside `@scope`. Selectors moved out as plain
8
+ `toast-ui[data-…]` rules. See docs/BROWSER-COMPAT.md §3a. */
9
+ toast-ui[data-open] {
10
+ transition: transform var(--toast-duration) var(--toast-easing),
11
+ opacity var(--toast-duration) var(--toast-easing);
12
+ transform: translateY(0);
13
+ opacity: 1;
14
+ }
15
+ toast-ui[data-closing] {
16
+ transition: transform var(--toast-duration) var(--toast-easing),
17
+ opacity var(--toast-duration) var(--toast-easing);
18
+ opacity: 0;
19
+ }
20
+ toast-ui[data-closing],
21
+ toast-ui[data-closing][position="bottom-right"],
22
+ toast-ui[data-closing][position="bottom-left"] {
23
+ transform: translateY(1rem);
24
+ }
25
+ toast-ui[data-closing][position="top-right"],
26
+ toast-ui[data-closing][position="top-left"] {
27
+ transform: translateY(-1rem);
28
+ }
29
+
5
30
  @scope (toast-ui) {
6
31
  :where(:scope) {
7
32
  --toast-bg: var(--a-bg-subtle);
@@ -78,32 +103,8 @@
78
103
  transform: translateY(-1rem);
79
104
  }
80
105
 
81
- /* ── Enter animation ── */
82
-
83
- :scope[data-open] {
84
- transition: transform var(--toast-duration) var(--toast-easing),
85
- opacity var(--toast-duration) var(--toast-easing);
86
- transform: translateY(0);
87
- opacity: 1;
88
- }
89
-
90
- /* ── Exit animation ── */
91
-
92
- :scope[data-closing] {
93
- transition: transform var(--toast-duration) var(--toast-easing),
94
- opacity var(--toast-duration) var(--toast-easing);
95
- opacity: 0;
96
- }
97
-
98
- :where(:scope[data-closing]),
99
- :where(:scope[data-closing][position="bottom-right"]),
100
- :where(:scope[data-closing][position="bottom-left"]) {
101
- transform: translateY(1rem);
102
- }
103
- :where(:scope[data-closing][position="top-right"]),
104
- :where(:scope[data-closing][position="top-left"]) {
105
- transform: translateY(-1rem);
106
- }
106
+ /* Enter / exit animation rules ([data-open] / [data-closing]) moved
107
+ outside @scope — see Safari 17.x bug note at top of file. */
107
108
 
108
109
  /* ── Variant: info (default) ── */
109
110
 
@@ -1,7 +1,8 @@
1
1
  /* Safari 17.x bug: `:scope:not(...):hover` inside `@scope` doesn't match
2
- the scope root. Plain selector outside works. See
3
- docs/BROWSER-COMPAT.md §3a. */
4
- toggle-group-ui:not([disabled]):hover {
2
+ the scope root. Plain selector outside works. The @scope is
3
+ `(toggle-option-ui)` NOT `(toggle-group-ui)` — so the moved-out
4
+ selector targets `toggle-option-ui:hover`. See docs/BROWSER-COMPAT.md §3a. */
5
+ toggle-option-ui:not([disabled]):hover {
5
6
  --toggle-option-bg: var(--toggle-option-bg-hover);
6
7
  --toggle-option-fg: var(--toggle-option-fg-hover);
7
8
  }
@@ -90,10 +90,7 @@
90
90
  box-shadow: var(--tree-focus-ring);
91
91
  }
92
92
 
93
- :scope[selected] > [slot="row"] {
94
- background: var(--tree-bg-selected);
95
- color: var(--tree-fg);
96
- }
93
+ /* :scope[selected] rules moved outside @scope — see tree-ui Safari note at end of file. */
97
94
 
98
95
  /* ── Chevron ── */
99
96
  [slot="chevron"] {
@@ -120,9 +117,7 @@
120
117
  flex-shrink: 0;
121
118
  }
122
119
 
123
- :scope[selected] > [slot="row"] [slot="icon"] {
124
- color: var(--tree-fg);
125
- }
120
+ /* :scope[selected] icon override moved outside @scope — see Safari note at end of file. */
126
121
 
127
122
  /* ── Text ── */
128
123
  [slot="text"] {
@@ -142,13 +137,30 @@
142
137
  transition: opacity var(--tree-duration) var(--tree-easing);
143
138
  }
144
139
 
145
- [slot="row"]:hover [slot="actions"],
146
- :scope[selected] > [slot="row"] [slot="actions"] {
140
+ [slot="row"]:hover [slot="actions"] {
147
141
  opacity: 1;
148
142
  }
143
+ /* :scope[selected] > [slot="row"] [slot="actions"] moved outside @scope. */
149
144
 
150
145
  /* ── Children — collapsed state ── */
151
146
  :scope:not([open]) > tree-item-ui {
152
147
  display: none;
153
148
  }
154
149
  }
150
+
151
+ /* Safari 17.x bug: `:scope[selected]` (Flavor B — attribute-removal restyle)
152
+ doesn't restyle on attribute removal inside `@scope`. Selectors moved
153
+ out as plain `tree-ui[selected] …` rules (the @scope is `(tree-ui)`, so
154
+ `:scope` resolved to `tree-ui`). Custom-property tokens from
155
+ `:where(:scope)` still resolve via inheritance. See
156
+ docs/BROWSER-COMPAT.md §3a. */
157
+ tree-ui[selected] > [slot="row"] {
158
+ background: var(--tree-bg-selected);
159
+ color: var(--tree-fg);
160
+ }
161
+ tree-ui[selected] > [slot="row"] [slot="icon"] {
162
+ color: var(--tree-fg);
163
+ }
164
+ tree-ui[selected] > [slot="row"] [slot="actions"] {
165
+ opacity: 1;
166
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adia-ai/web-components",
3
- "version": "0.0.23",
3
+ "version": "0.0.25",
4
4
  "description": "AdiaUI web components — vanilla custom elements. A2UI runtime (renderer, registry, streams, wiring) lives in @adia-ai/a2ui-utils.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,10 +1,10 @@
1
- /* Safari 17.x bug: `:scope:hover` inside `@scope` doesn't match the scope
2
- root. Plain selector outside the @scope works custom props from the
3
- `:where(:scope)` block are still set on the element via inheritance, so
4
- `var(--token)` lookups resolve correctly. Specificity (0,1,1) is
5
- preserved. Safari < 18 is below the §1 floor (ADR-0007); this fix
6
- protects opt-in consumers extending the floor downward and is
7
- harmlessly redundant on engines without the bug. */
1
+ /* Safari 17.x bug: `:scope:hover` (Flavor A) and `:scope[selected]`
2
+ (Flavor B attribute-removal restyle) both fail inside `@scope`.
3
+ Selectors moved out — custom props from `:where(:scope)` are still set
4
+ on the element via inheritance. Specificity (0,1,1 / 0,2,1) preserved.
5
+ Safari < 18 is below the §1 floor (ADR-0007); fix protects opt-in
6
+ consumers extending below the floor and is harmlessly redundant on
7
+ engines without the bug. */
8
8
  app-nav-item-ui:hover {
9
9
  background: var(--nav-item-bg-hover);
10
10
  color: var(--nav-item-fg-hover);
@@ -12,6 +12,21 @@ app-nav-item-ui:hover {
12
12
  app-nav-item-ui:hover [slot="icon"] {
13
13
  color: var(--nav-item-fg-hover);
14
14
  }
15
+ app-nav-item-ui[selected] {
16
+ background: var(--nav-item-bg-selected);
17
+ color: var(--nav-item-fg-selected);
18
+ font-weight: var(--nav-item-selected-weight);
19
+ }
20
+ app-nav-item-ui[selected] [slot="icon"] {
21
+ color: var(--nav-item-icon-fg-selected);
22
+ }
23
+ app-nav-item-ui[selected] [slot="icon"]:empty::before {
24
+ content: '';
25
+ width: 2px;
26
+ height: 1em;
27
+ border-radius: 1px;
28
+ background: var(--nav-item-accent);
29
+ }
15
30
 
16
31
  @scope (app-nav-item-ui) {
17
32
  :where(:scope) {
@@ -61,15 +76,7 @@ app-nav-item-ui:hover [slot="icon"] {
61
76
  outline: none;
62
77
  }
63
78
 
64
- :scope[selected] {
65
- background: var(--nav-item-bg-selected);
66
- color: var(--nav-item-fg-selected);
67
- font-weight: var(--nav-item-selected-weight);
68
- }
69
-
70
- :scope[selected] [slot="icon"] {
71
- color: var(--nav-item-icon-fg-selected);
72
- }
79
+ /* :scope[selected] rules moved outside @scope — see Safari 17.x bug note at top. */
73
80
 
74
81
  /* Icon slot — always present, reserves space even when empty */
75
82
  [slot="icon"] {
@@ -87,14 +94,7 @@ app-nav-item-ui:hover [slot="icon"] {
87
94
  --a-icon-size: calc(var(--nav-item-icon-font-size) + 2px);
88
95
  }
89
96
 
90
- /* Selected accent line — shown on empty icon slot */
91
- :scope[selected] [slot="icon"]:empty::before {
92
- content: '';
93
- width: 2px;
94
- height: 1em;
95
- border-radius: 1px;
96
- background: var(--nav-item-accent);
97
- }
97
+ /* Selected accent line on empty icon slot moved outside @scope — see Safari 17.x bug note at top. */
98
98
 
99
99
  [slot="text"] {
100
100
  flex: 1;
@@ -1,6 +1,18 @@
1
1
  /* ═══════════════════════════════════════════════════════════════
2
2
  App Shell — <app-shell-ui> pattern component CSS
3
3
 
4
+ ⚠ DEPRECATED PATH (2026-04-29): preserved as an alias for one
5
+ release while consumers migrate. The CSS now lives behind the
6
+ <app-shell-ui> custom element at
7
+ packages/web-components/components/app-shell/ and ships via
8
+ packages/web-components/styles/components.css.
9
+
10
+ Migration: drop the explicit <link rel="stylesheet"> to this file —
11
+ styles/components.css now bundles the same content via the new
12
+ component.
13
+
14
+ Companion ADR: ../../../../.brain/adrs/0009-promote-app-shell-and-page-to-components.md.
15
+
4
16
  Split into constituent parts for maintainability.
5
17
  Import order matters: tokens first, then structure, then overrides.
6
18
  ═══════════════════════════════════════════════════════════════ */
@@ -1,8 +1,8 @@
1
- /* Safari 17.x bug: `:scope:hover` inside `@scope` doesn't match the scope
2
- root. Plain selector outside the @scope works. `:focus-visible` matches
3
- correctly inside @scope, so the combined `:hover, :focus-visible` rule
4
- is split `:hover` moves out, `:focus-visible` stays. See
5
- docs/BROWSER-COMPAT.md §3a. */
1
+ /* Safari 17.x bug: `:scope:hover` (Flavor A) and `:scope[selected]`
2
+ (Flavor B attribute-removal restyle) both fail inside `@scope`.
3
+ Selectors moved out. `:focus-visible` matches correctly inside `@scope`,
4
+ so the combined `:hover, :focus-visible` rule was split — `:hover`
5
+ moves out, `:focus-visible` stays. See docs/BROWSER-COMPAT.md §3a. */
6
6
  section-nav-item-ui:hover:not([disabled]) {
7
7
  background: var(--section-nav-item-bg-hover);
8
8
  color: var(--section-nav-item-fg-hover);
@@ -10,6 +10,23 @@ section-nav-item-ui:hover:not([disabled]) {
10
10
  section-nav-item-ui:hover:not([disabled]) > icon-ui {
11
11
  color: var(--section-nav-item-fg-hover);
12
12
  }
13
+ section-nav-item-ui[selected] {
14
+ background: var(--section-nav-item-bg-selected);
15
+ color: var(--section-nav-item-fg-selected);
16
+ font-weight: var(--section-nav-item-selected-weight);
17
+ }
18
+ section-nav-item-ui[selected] > icon-ui {
19
+ color: var(--section-nav-item-icon-fg-selected);
20
+ }
21
+ section-nav-item-ui[selected]:not(:has(> icon-ui))::before {
22
+ content: '';
23
+ position: absolute;
24
+ inset-inline-start: calc(var(--section-nav-item-row-px) - var(--a-space-1));
25
+ inset-block: 25%;
26
+ width: 2px;
27
+ border-radius: 1px;
28
+ background: var(--section-nav-item-accent);
29
+ }
13
30
 
14
31
  @scope (section-nav-item-ui) {
15
32
  :where(:scope) {
@@ -79,25 +96,7 @@ section-nav-item-ui:hover:not([disabled]) > icon-ui {
79
96
  color: var(--section-nav-item-fg-hover);
80
97
  }
81
98
 
82
- :scope[selected] {
83
- background: var(--section-nav-item-bg-selected);
84
- color: var(--section-nav-item-fg-selected);
85
- font-weight: var(--section-nav-item-selected-weight);
86
- }
87
- :scope[selected] > icon-ui {
88
- color: var(--section-nav-item-icon-fg-selected);
89
- }
90
-
91
- /* Selected accent bar — shown only when no leading icon */
92
- :scope[selected]:not(:has(> icon-ui))::before {
93
- content: '';
94
- position: absolute;
95
- inset-inline-start: calc(var(--section-nav-item-row-px) - var(--a-space-1));
96
- inset-block: 25%;
97
- width: 2px;
98
- border-radius: 1px;
99
- background: var(--section-nav-item-accent);
100
- }
99
+ /* :scope[selected] rules moved outside @scope — see Safari 17.x bug note at top. */
101
100
 
102
101
  :scope[disabled] {
103
102
  color: var(--section-nav-item-fg-disabled);
@@ -26,6 +26,8 @@
26
26
  @import "../components/range/range.css";
27
27
  @import "../components/tree/tree.css";
28
28
  @import "../components/pane/pane.css";
29
+ @import "../components/app-shell/app-shell.css";
30
+ @import "../components/page/page.css";
29
31
  @import "../components/chat/chat-input.css";
30
32
  @import "../components/chat/chat.css";
31
33
  @import "../components/drawer/drawer.css";