@ponchia/ui 0.8.0 → 0.9.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.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,131 @@
5
5
  |> `^0` / `*` wildcard does **not** protect you. See README → Versioning, and
6
6
  |> the deprecation policy in CONTRIBUTING.md.
7
7
 
8
+ ## 0.9.0 — 2026-08-11
9
+
10
+ Two additions and one correction, all from the same source: migrating a
11
+ downstream workbench onto 0.8.1 and watching where adoption stalled.
12
+
13
+ ### Added
14
+
15
+ - **`.ui-row` — the dense selectable row, in the default bundle.** Every
16
+ workbench grows a list of these: a search result, a file in an explorer, an
17
+ outline entry, a backlink, a commit. Bronto already had the shape and it was
18
+ imprisoned — `.ui-menu__item` was exactly it, minus the persistence — so a
19
+ consumer building an explorer either claimed to be a menu or wrote the row
20
+ again. The one that migrated had **63 families of it, about 1,200 lines**,
21
+ each re-deciding the selected state, the hover, the ellipsis and the coarse
22
+ floor.
23
+
24
+ `__title` is the part that truncates and `__meta` is the part that does not;
25
+ that asymmetry is the contract. Selection reads `aria-selected` /
26
+ `aria-current` — the attributes a host already sets for assistive tech — so
27
+ the visual state cannot disagree with the announced one. `--stacked` for a
28
+ row with a snippet, `--ruled` for a list that reads as one object.
29
+
30
+ Selection: use **`aria-current`** for a row that is merely the current one —
31
+ that is the common case and valid anywhere. `aria-selected` is only legal on a
32
+ role that accepts it (`option` in a `listbox`, or `row`/`tab`/`gridcell`/
33
+ `treeitem`); on a bare `<button>` it is invalid ARIA and axe rates it
34
+ *critical*. This release's own demo shipped that mistake, three a11y specs
35
+ went red, and a new unit gate now catches the same class of error in
36
+ milliseconds rather than twenty minutes of browser matrix.
37
+
38
+ `min-inline-size: 0` is on the row itself and is not optional: a row is
39
+ usually a flex or grid item, whose automatic minimum is its min-content width,
40
+ and `__title` is `white-space: nowrap` — so without it a long title stops the
41
+ row shrinking and pushes its container past the viewport instead of
42
+ truncating. The demo proved that at 320/360/390px before release.
43
+
44
+ It is in **core**, unusually for a new surface, because `.ui-menu__item` now
45
+ composes it and a core component cannot depend on an opt-in leaf. It also
46
+ earns the roadmap's stronger argument for a core addition on two counts:
47
+ universal application chrome, and it *reduces duplicated core markup*. A unit
48
+ proof asserts the menu item does not restate what the row says, so the two
49
+ cannot drift back apart.
50
+
51
+ ### Changed
52
+
53
+ - **`.ui-menu` no longer welds placement into the surface.** It used to declare
54
+ `position: absolute` plus a trigger-relative offset, so a menu opened at a
55
+ pointer — a canvas context menu, a long-press — could not use it at all, and
56
+ consumers re-declared the panel, border, radius and shadow to get a surface.
57
+ The dropdown placement is now `--dropdown` (unchanged behaviour, opted into)
58
+ and `--at-pointer` takes a menu out of flow for a host that computes its own
59
+ position.
60
+
61
+ **Migration:** add `ui-menu--dropdown` to an existing `ui-menu` inside a
62
+ `ui-menu-host`. Without it the surface renders in flow.
63
+
64
+ ### Fixed
65
+
66
+ - **`data-density` claimed more than it does, and the guard could not tell.**
67
+ `docs/theming.md` said the preset applies "on any element". Measured: it
68
+ re-points the `--space-*` scale, so it reaches the ~40 components whose
69
+ padding is expressed in that scale and **none** of the rest — `ui-alert` and
70
+ `ui-menu__item` among them. The remaining paddings are tuned pairs like
71
+ `0.5rem 0.55rem` that a seven-step scale cannot express, so tokenising them
72
+ would change how they look at the default density; exactly one declaration in
73
+ the whole catalog mapped cleanly and was converted.
74
+
75
+ The docs now say which components respond and why the others cannot, and a
76
+ test pins the responding set. The previous guard checked that the preset
77
+ changed the token *family*, which is true and useless — it would have passed
78
+ with every component hardcoded. That is the third contract in three releases
79
+ whose gate validated the mechanism instead of the effect, after the 43.5px tap
80
+ floor and the shipped-docs list.
81
+
82
+ ### Notes
83
+
84
+ - Bundle budget raised to 95,600 B / 16,400 B for `.ui-row` (+1,234 B raw /
85
+ +83 B gzip). The gzip cost is small because the new rules compress against the
86
+ menu rules they replaced.
87
+
88
+ ## 0.8.1 — 2026-08-11
89
+
90
+ A packaging fix, plus dependency hygiene. **No published CSS, token, class, or
91
+ type artifact changed** — `dist/`, `css/`, `tokens/` and `classes/` are byte-for-byte
92
+ identical to 0.8.0. If you are on 0.8.0 and do not read the migration guide from
93
+ the package, there is nothing here for you.
94
+
95
+ ### Fixed
96
+
97
+ - **`docs/migrations/0.7-to-0.8.md` was missing from the published package.**
98
+ 0.8.0's one breaking change is the colorway canvas re-point, `CHANGELOG.md`
99
+ and `MIGRATIONS.json` both point at that guide, and it was not in the tarball —
100
+ so a consumer following the pointer from an offline install got nothing. The
101
+ guide was written, linked, and gated by `check:doc-links`; it was simply never
102
+ added to `package.json`'s hand-maintained `files` array.
103
+ - **The gate that should have caught it was circular.** `check-pack.mjs` derived
104
+ its set of shipped docs *from* `files`, so it proved every listed doc ships and
105
+ could never prove a doc that exists is listed. It now asserts the other
106
+ direction for the two directories the published package itself references —
107
+ `docs/migrations/` (from `MIGRATIONS.json`) and `docs/adr/` (from
108
+ `docs/architecture.md`) — where a missing file costs a reader a dead pointer.
109
+ Other `docs/` pages stay opt-in: the package ships a curated subset by design.
110
+
111
+ ### Changed
112
+
113
+ - **Dependency sweep** — ten Dependabot PRs landed as one reviewed change. Every
114
+ advisory was **devDependency-scoped**; the package declares no runtime
115
+ dependencies and `npm audit --omit=dev` was already clean, so none of them
116
+ ever reached a consumer. `npm audit` now reports 0 across the whole tree
117
+ (`fast-uri`, `js-yaml`, `nanoid`, `pdfjs-dist`, `postcss`, `shell-quote`,
118
+ `tar`, `undici`), the toolchain moved (`jsdom` 30, `knip` 6.32, `prettier`
119
+ 3.9.6, `publint` 0.3.23, `react`/`react-dom` 19.2.8, `solid-js` 1.9.14,
120
+ `stylelint` 17.14.1, `vega` 6.3.1, `@arethetypeswrong/cli` 0.18.5), the pinned
121
+ action SHAs moved, and the Astro example moved to Astro 7 (verified building
122
+ against the packed 0.8.0 tarball).
123
+
124
+ ### Not taken
125
+
126
+ - **TypeScript 7.** Its default export is now `{ version, versionMajorMinor }` —
127
+ the compiler-API namespace moved in the native port — so `ts.ScriptTarget` is
128
+ `undefined` and `scripts/lib/import-policy.mjs` throws at `check:exports`.
129
+ Adapting this repository's AST tooling to that API is real work with its own
130
+ review, and there is no security driver: TypeScript is not in any advisory
131
+ here. Pinned to `^6.0.3`; Dependabot's dev-group PR stays open for it.
132
+
8
133
  ## 0.8.0 — 2026-08-11
9
134
 
10
135
  A single-consumer release. A full-surface audit of the largest downstream
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [![npm](https://img.shields.io/npm/v/@ponchia/ui?logo=npm)](https://www.npmjs.com/package/@ponchia/ui)
4
4
  [![npm provenance](https://img.shields.io/badge/npm-provenance-blue?logo=npm)](https://www.npmjs.com/package/@ponchia/ui#provenance)
5
5
  [![runtime deps](https://img.shields.io/badge/runtime%20deps-0-brightgreen)](https://github.com/Ponchia/bronto-ui/blob/main/package.json)
6
- [![default CSS](https://img.shields.io/badge/default%20CSS-~92kB%20%2F%20~16kB%20gzip-informational)](https://github.com/Ponchia/bronto-ui/blob/main/scripts/check-dist.mjs)
6
+ [![default CSS](https://img.shields.io/badge/default%20CSS-~93kB%20%2F%20~16kB%20gzip-informational)](https://github.com/Ponchia/bronto-ui/blob/main/scripts/check-dist.mjs)
7
7
  [![CI](https://github.com/Ponchia/bronto-ui/actions/workflows/ci.yml/badge.svg)](https://github.com/Ponchia/bronto-ui/actions/workflows/ci.yml)
8
8
  [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/Ponchia/bronto-ui/badge)](https://scorecard.dev/viewer/?uri=github.com/Ponchia/bronto-ui)
9
9
  [![license: MIT](https://img.shields.io/badge/license-MIT-blue)](https://github.com/Ponchia/bronto-ui/blob/main/LICENSE)
@@ -81,12 +81,12 @@ Or drop it in with no build step, straight from a CDN (replace the version only
81
81
  when deliberately upgrading across a breaking pre-1.0 minor):
82
82
 
83
83
  ```html
84
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.8.0/dist/bronto.css">
84
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.9.0/dist/bronto.css">
85
85
  ```
86
86
 
87
87
  ## Quick start
88
88
 
89
- **1. Load the CSS.** One flattened, minified default CSS bundle — the standard component set, one request (~92 kB raw / ~16 kB gzip) — that is `dist/bronto.css`, not the whole package tarball:
89
+ **1. Load the CSS.** One flattened, minified default CSS bundle — the standard component set, one request (~93 kB raw / ~16 kB gzip) — that is `dist/bronto.css`, not the whole package tarball:
90
90
 
91
91
  ```css
92
92
  @import '@ponchia/ui'; /* via a bundler */
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$comment": "@ponchia/ui class vocabulary as language-neutral data — validate emitted markup without executing the ESM cls map or parsing the .d.ts. Generated from classes/index.js — do not edit by hand; run `npm run classes:json:build`. Drift-checked in CI. `groups[].base` is null for a parts-only namespace (no standalone base class — do NOT emit it). A modifier whose name contains `__` (e.g. `ui-spark__bar--pos`) attaches to THAT part, not the base host. `states` is the author-applied `is-*` hooks (runtime/behavior-managed hooks are excluded); `behaviorAttributes` are the `data-bronto-*` wiring hooks the optional behaviors delegate on; `requiredAria` is the role/aria a generator must emit per component. `states` + `customProperties` are documented in docs/reference.md and ship outside `cls` by design.",
3
3
  "counts": {
4
- "classes": 669,
5
- "groups": 183
4
+ "classes": 677,
5
+ "groups": 184
6
6
  },
7
7
  "groups": {
8
8
  "ui-accordion": {
@@ -830,7 +830,10 @@
830
830
  },
831
831
  "ui-menu": {
832
832
  "base": "ui-menu",
833
- "modifiers": [],
833
+ "modifiers": [
834
+ "ui-menu--at-pointer",
835
+ "ui-menu--dropdown"
836
+ ],
834
837
  "parts": [
835
838
  "ui-menu__item",
836
839
  "ui-menu__label",
@@ -1074,6 +1077,18 @@
1074
1077
  "modifiers": [],
1075
1078
  "parts": []
1076
1079
  },
1080
+ "ui-row": {
1081
+ "base": "ui-row",
1082
+ "modifiers": [
1083
+ "ui-row--ruled",
1084
+ "ui-row--stacked"
1085
+ ],
1086
+ "parts": [
1087
+ "ui-row__mark",
1088
+ "ui-row__meta",
1089
+ "ui-row__title"
1090
+ ]
1091
+ },
1077
1092
  "ui-screen-only": {
1078
1093
  "base": "ui-screen-only",
1079
1094
  "modifiers": [],
@@ -1924,6 +1939,8 @@
1924
1939
  "ui-mark--warning",
1925
1940
  "ui-matrix",
1926
1941
  "ui-menu",
1942
+ "ui-menu--at-pointer",
1943
+ "ui-menu--dropdown",
1927
1944
  "ui-menu-host",
1928
1945
  "ui-menu__item",
1929
1946
  "ui-menu__label",
@@ -2039,6 +2056,12 @@
2039
2056
  "ui-report__title",
2040
2057
  "ui-report__toc",
2041
2058
  "ui-reveal",
2059
+ "ui-row",
2060
+ "ui-row--ruled",
2061
+ "ui-row--stacked",
2062
+ "ui-row__mark",
2063
+ "ui-row__meta",
2064
+ "ui-row__title",
2042
2065
  "ui-screen-only",
2043
2066
  "ui-scroll-progress",
2044
2067
  "ui-scroll-reveal",
@@ -55,6 +55,12 @@ export declare const cls: {
55
55
  readonly linkArrow: 'ui-link--arrow';
56
56
  readonly linkCta: 'ui-link--cta';
57
57
  readonly keyValue: 'ui-key-value';
58
+ readonly row: 'ui-row';
59
+ readonly rowTitle: 'ui-row__title';
60
+ readonly rowMeta: 'ui-row__meta';
61
+ readonly rowMark: 'ui-row__mark';
62
+ readonly rowStacked: 'ui-row--stacked';
63
+ readonly rowRuled: 'ui-row--ruled';
58
64
  readonly emptyState: 'ui-empty-state';
59
65
  readonly emptyStateInvite: 'ui-empty-state--invite';
60
66
  readonly emptyStateGlyph: 'ui-empty-state__glyph';
@@ -164,6 +170,8 @@ export declare const cls: {
164
170
  readonly modalDrawer: 'ui-modal--drawer';
165
171
  readonly menuHost: 'ui-menu-host';
166
172
  readonly menu: 'ui-menu';
173
+ readonly menuDropdown: 'ui-menu--dropdown';
174
+ readonly menuAtPointer: 'ui-menu--at-pointer';
167
175
  readonly menuLabel: 'ui-menu__label';
168
176
  readonly menuItem: 'ui-menu__item';
169
177
  readonly menuSep: 'ui-menu__sep';
package/classes/index.js CHANGED
@@ -59,6 +59,12 @@ export const cls = Object.freeze({
59
59
  linkArrow: 'ui-link--arrow',
60
60
  linkCta: 'ui-link--cta',
61
61
  keyValue: 'ui-key-value',
62
+ row: 'ui-row',
63
+ rowTitle: 'ui-row__title',
64
+ rowMeta: 'ui-row__meta',
65
+ rowMark: 'ui-row__mark',
66
+ rowStacked: 'ui-row--stacked',
67
+ rowRuled: 'ui-row--ruled',
62
68
  emptyState: 'ui-empty-state',
63
69
  emptyStateInvite: 'ui-empty-state--invite',
64
70
  emptyStateGlyph: 'ui-empty-state__glyph',
@@ -173,6 +179,8 @@ export const cls = Object.freeze({
173
179
  modalDrawer: 'ui-modal--drawer',
174
180
  menuHost: 'ui-menu-host',
175
181
  menu: 'ui-menu',
182
+ menuDropdown: 'ui-menu--dropdown',
183
+ menuAtPointer: 'ui-menu--at-pointer',
176
184
  menuLabel: 'ui-menu__label',
177
185
  menuItem: 'ui-menu__item',
178
186
  menuSep: 'ui-menu__sep',
package/css/core.css CHANGED
@@ -18,6 +18,7 @@
18
18
  @import url('./site.css') layer(bronto);
19
19
  @import url('./content.css') layer(bronto);
20
20
  @import url('./primitives.css') layer(bronto);
21
+ @import url('./row.css') layer(bronto);
21
22
  @import url('./forms.css') layer(bronto);
22
23
  @import url('./feedback.css') layer(bronto);
23
24
  @import url('./overlay.css') layer(bronto);
package/css/overlay.css CHANGED
@@ -247,6 +247,12 @@ html:has(.ui-modal.is-open) {
247
247
  position: relative;
248
248
  }
249
249
 
250
+ /* The SURFACE only. Placement is a separate decision, because a menu is not
251
+ always a dropdown: a canvas context menu opens at the pointer, a mobile one
252
+ arrives as a sheet. This rule used to weld `position: absolute` plus a
253
+ trigger-relative offset into the surface itself, so a consumer with a
254
+ pointer-positioned menu could not use it at all and re-declared the panel,
255
+ the border, the radius and the shadow to get one. */
250
256
  .ui-menu {
251
257
  background: var(--panel-strong);
252
258
  border: 1px solid var(--line-strong);
@@ -256,12 +262,25 @@ html:has(.ui-modal.is-open) {
256
262
  gap: 1px;
257
263
  min-inline-size: 11rem;
258
264
  padding: 0.3rem;
265
+ }
266
+
267
+ /* The dropdown placement this class always had: anchored under its trigger
268
+ inside a `.ui-menu-host`. Unchanged behaviour, now opted into. */
269
+ .ui-menu--dropdown {
259
270
  position: absolute;
260
271
  inset-inline-end: 0;
261
272
  inset-block-start: calc(100% + 0.4rem);
262
273
  z-index: var(--z-overlay);
263
274
  }
264
275
 
276
+ /* Opened at a point the host computes — a right-click, a long-press. The host
277
+ sets `left`/`top` (or `inset-inline-start`/`inset-block-start`); Bronto only
278
+ takes it out of flow and gives it the layer. */
279
+ .ui-menu--at-pointer {
280
+ position: fixed;
281
+ z-index: var(--z-popover);
282
+ }
283
+
265
284
  .ui-menu__label {
266
285
  color: var(--text-dim);
267
286
  font-family: var(--mono);
@@ -271,20 +290,15 @@ html:has(.ui-modal.is-open) {
271
290
  text-transform: uppercase;
272
291
  }
273
292
 
293
+ /* Composes `.ui-row` (css/row.css) — a menu item IS a dense selectable row,
294
+ and keeping two copies of that shape is how they drift. What is menu-specific
295
+ stays here: the mono face, the slightly roomier padding a pointer target
296
+ wants, and the leading bullet below. `row.css` is in the core bundle, which
297
+ is what lets a core component compose it. */
274
298
  .ui-menu__item {
275
- align-items: center;
276
- background: transparent;
277
- border: 0;
278
- border-radius: var(--radius-sm);
279
- color: var(--text-soft);
280
- cursor: pointer;
281
- display: flex;
282
299
  font-family: var(--mono);
283
300
  font-size: var(--text-sm);
284
- gap: 0.5rem;
285
301
  padding: 0.5rem 0.55rem;
286
- text-align: start;
287
- inline-size: 100%;
288
302
  }
289
303
 
290
304
  .ui-menu__item::before {
package/css/row.css ADDED
@@ -0,0 +1,154 @@
1
+ /* ==========================================================================
2
+ row — the dense selectable row. IN THE DEFAULT BUNDLE.
3
+
4
+ Every workbench grows a list of these: a search result, a file in an
5
+ explorer, an outline entry, a backlink, an inbox item, a commit. They are not
6
+ table rows — there is no column contract and no header — and they are not
7
+ menu items, because they persist rather than dismissing. But they are all the
8
+ same object: a full-width clickable line with a leading mark, a title that
9
+ gives up space first, and trailing metadata that does not.
10
+
11
+ Bronto already had this shape and it was imprisoned. `.ui-menu__item` is
12
+ exactly it, minus the persistence — so a consumer building an explorer either
13
+ claimed to be a menu or wrote the row again. One downstream workbench had
14
+ SIXTY-THREE families of it, about 1,200 lines, each re-deciding the selected
15
+ state, the hover, the ellipsis and the coarse-pointer floor.
16
+
17
+ `.ui-menu__item` now composes this, so the two cannot drift.
18
+
19
+ BOUNDARY. The host owns what a row means, what selects it, and what its
20
+ columns are. Bronto owns the line: its rhythm, its states, and the fact that
21
+ the title is the part that truncates. Rows that carry a severity should reach
22
+ for `.ui-severity-row` in `state.css` instead — that one adds the tone gutter.
23
+
24
+ In core rather than opt-in, unusually for a new surface, because
25
+ `.ui-menu__item` composes it and a core component cannot depend on a leaf.
26
+ ========================================================================== */
27
+
28
+ .ui-row {
29
+ align-items: center;
30
+ background: transparent;
31
+ border: 0;
32
+ border-radius: var(--radius-sm);
33
+ color: var(--text-soft);
34
+ cursor: pointer;
35
+ display: flex;
36
+ font: inherit;
37
+ gap: 0.5rem;
38
+
39
+ /* The companion to `__title`'s ellipsis, and the demo proved it is not
40
+ optional: a row is usually a flex or grid ITEM, whose automatic minimum
41
+ size is its min-content width — and `__title` is `white-space: nowrap`, so
42
+ that min-content is the WHOLE title. Without this a long title stops the
43
+ row shrinking and pushes its container past the viewport instead of
44
+ truncating. Caught at 320/360/390px by the overflow specs. */
45
+ min-inline-size: 0;
46
+ padding: 0.4rem 0.5rem;
47
+ text-align: start;
48
+ inline-size: 100%;
49
+ }
50
+
51
+ /* A row is usually a <button> or an <a>; when it is neither, it still needs to
52
+ look inert rather than pressable. */
53
+ .ui-row:not(button, a, [role='button'], [role='option'], [tabindex]) {
54
+ cursor: default;
55
+ }
56
+
57
+ /* The title gives up space FIRST — it is the only part of a row that can be
58
+ truncated without losing a fact. Everything else keeps its size. */
59
+ .ui-row__title {
60
+ flex: 1 1 auto;
61
+ min-inline-size: 0;
62
+ overflow: hidden;
63
+ text-overflow: ellipsis;
64
+ white-space: nowrap;
65
+ }
66
+
67
+ /* Trailing facts: a count, an age, a path fragment. Never the thing that
68
+ shrinks, because a half-rendered number is worse than no number. */
69
+ .ui-row__meta {
70
+ color: var(--text-dim);
71
+ flex: none;
72
+ font-family: var(--mono);
73
+ font-size: var(--text-2xs);
74
+ }
75
+
76
+ /* A leading mark — an icon, a glyph, a status dot. Sized by the host. */
77
+ .ui-row__mark {
78
+ display: inline-flex;
79
+ flex: none;
80
+ }
81
+
82
+ /* A row that wraps to a second line: a result with a snippet under its title.
83
+ The parts stack rather than sharing the baseline. */
84
+ .ui-row--stacked {
85
+ align-items: stretch;
86
+ flex-direction: column;
87
+ gap: 0.15rem;
88
+ }
89
+
90
+ .ui-row--stacked .ui-row__title {
91
+ flex: none;
92
+ }
93
+
94
+ /* Rows in a list read as one object when they share a rule; rows in a menu
95
+ float. `--ruled` is the list form. */
96
+ .ui-row--ruled {
97
+ border-block-end: 1px solid var(--line);
98
+ border-radius: 0;
99
+ }
100
+
101
+ /* --- States, and WHICH attribute is yours to use.
102
+
103
+ `aria-selected` is only valid on a row that has a role which accepts it —
104
+ `option` inside a `listbox`, or `row` / `tab` / `gridcell` / `treeitem`. On a
105
+ bare `<button>` it is invalid ARIA, and axe rates that CRITICAL. This project
106
+ shipped exactly that mistake in its own demo and the a11y gate caught it.
107
+
108
+ So: `aria-current` for a row that is merely the current one (the common
109
+ case), `aria-selected` only when the row genuinely carries an option/row
110
+ role, and `.is-selected` when neither applies. All three paint the same. --- */
111
+
112
+ .ui-row[aria-selected='true'],
113
+ .ui-row[aria-current]:not([aria-current='false']),
114
+ .ui-row.is-selected {
115
+ background: var(--panel-soft);
116
+ color: var(--text);
117
+ }
118
+
119
+ @media (hover: hover) {
120
+ .ui-row:is(button, a, [role='button'], [role='option'], [tabindex]):hover {
121
+ background: var(--bg-accent);
122
+ color: var(--text);
123
+ }
124
+ }
125
+
126
+ .ui-row:focus-visible {
127
+ background: var(--bg-accent);
128
+ color: var(--text);
129
+ outline: 2px solid var(--focus-ring);
130
+ outline-offset: -2px;
131
+ }
132
+
133
+ /* Touch: a row is a primary target in a list, so it floats to the full
134
+ tap-target rather than the 24px minimum. */
135
+ @media (pointer: coarse) {
136
+ .ui-row {
137
+ min-block-size: var(--tap-target);
138
+ }
139
+ }
140
+
141
+ /* Forced colours strip the tinted hover/selected background, which would leave
142
+ the current row indistinguishable from its siblings. Re-assert a system
143
+ Highlight, the same way the menu and combobox rows do. */
144
+ @media (forced-colors: active) {
145
+ .ui-row[aria-selected='true'],
146
+ .ui-row[aria-current]:not([aria-current='false']),
147
+ .ui-row.is-selected,
148
+ .ui-row:hover,
149
+ .ui-row:focus-visible {
150
+ forced-color-adjust: none;
151
+ background: Highlight;
152
+ color: HighlightText;
153
+ }
154
+ }
package/css/workbench.css CHANGED
@@ -53,7 +53,7 @@
53
53
 
54
54
  .ui-toolstrip--compact {
55
55
  gap: var(--space-2xs);
56
- padding: 0.25rem;
56
+ padding: var(--space-2xs);
57
57
  }
58
58
 
59
59
  /* Pane-scale — a control bar belonging to ONE pane rather than to the app. The