The holographic layer, <os-switch>, and the Components tab - #480
Merged
Conversation
The brand ships five mesh gradients and one instruction about them: "meshes reserved for hero surfaces." This puts them in the component kit as a moment rather than a skin — a control wears the mesh at the instant it is on, selected, primary or filled, and ordinary Obsidian the rest of the time. A panel where every surface is iridescent has no identity moments left to spend, which is the failure mode the whole shape of this is built to avoid. The meshes, in CSS ------------------ `--os-mesh-holo` / `-pulse` / `-auro` / `-star` / `-mio` in `variables.css` are the brand's SVGs transcribed stop-for-stop: Holomesh is its base linear plus all eight radial glows, at the artboard's own offsets, radii, colours and alphas, converted from 1440x960 into percentages so they scale to any box. Gradient stacks rather than `url()`s, and that is the point rather than an optimisation. `background-position` can slide a gradient, and that slide IS the holographic effect — a foil card shifting hue as you tilt it. An SVG could not be animated, could not be tinted, and would rasterise one flat corner of a 1440x960 artboard onto a 44px switch track. src/ui/holo.ts -------------- Seven `css` fragments so no component re-decides what holographic means: `holoFill` (the mesh), `holoSheen` (a ~10%-alpha hover film), `holoEdge` (a mask-composited iridescent hairline, since `border-color` takes a colour and this is a gradient), `holoField`, `holoCheck`, `holoDrift`, and `holoTokens`, which reads every public token into a private `--_holo-*` alias so the palette and every desktop theme stay reachable. Two rings, not one. `--os-ui-focus-ring` is for TARGETS — buttons, switches, checkboxes, swatches — and carries a Void spacer plus a bloom so it survives landing on a bright mesh, where a flat 2px line vanishes into the pink. `--os-ui-focus-ring-field` is quieter and tightens a field's own border: a form of twelve inputs should not look alarmed. Around twenty `outline: 2px solid var(--wp-admin-theme-color)` rules collapse onto the two. <os-switch> ----------- `role="switch"` on a real `<button>`, so Space, Enter, the disabled semantics and the focus ring come from the platform. Off is a sunken Void well; on is the Holomesh with a Pulse glow and Void ink. Tap, drag (the knob tracks the pointer live and snaps past the midpoint), and ArrowLeft/Home + ArrowRight/End for absolute off/on, which a screen-reader user can use without tracking the current state. Travel is `clientWidth - clientHeight` — the stylesheet's `w - knob - 2*pad` with the pads cancelled — deliberately measured from the track and never from the knob, which widens to 1.28x under `:active`. Whether `:active` has landed by the time `pointerdown` dispatches is not something the spec pins down, so measuring the knob would have shortened travel by a quarter on some presses and not others: a switch that flips early, intermittently. It emits `os-checkbox-change` alongside `os-switch-change`, so swapping a checkbox for a switch is a tag change and nothing else. The sweep --------- `accent-color` had to go from the checkbox and the range slider. It takes a COLOUR, and checked here is a GRADIENT — so both are repainted from scratch. The checkbox tick is two borders of a rotated box (CSS, not an inline SVG, so it inherits `--_holo-ink` and follows a theme that re-points it); the slider paints the mesh under an opaque wedge whose boundary is one custom property the component writes per input, so the mesh never rescales mid-drag. Retinted in the palette rather than in the components — which keeps each component's literal as the no-stylesheet floor and keeps Legacy able to revert them: `--os-ui-progress-fill` and `--os-ui-step-chip-bg`. `<os-button variant="holo">` is the hero CTA. `primary` deliberately did NOT become the mesh: it is three-to-a-row in OS Settings, and a mesh three-to-a-row is wallpaper. `danger` keeps its red edge and a red focus ring all the way through the hover, because that border is the only warning the user gets and an iridescent one says the wrong thing. Also: an iridescent tab underline that animates out from the centre, the hairline on selected chips / cards / swatches, the mesh on checked menu items and the selected segment, a leading-edge marker on selected table rows (the 10%-alpha wash is correct and is not there at all for anyone who cannot separate those two greys), and the shared field chrome across text / textarea / number / select / user-search / multiselect / tag-input. Two specificity traps --------------------- `holoField` uses bare `input` / `select` / `textarea` selectors, which are safe inside a shadow root. Spelled honestly with `:not()` they weigh (0,3,1) — heavier than `input[aria-invalid='true']:focus` at (0,2,1) — so a shared fragment would have outranked every component's own error ring and an invalid field would focus in Pulse instead of red. `:where()` contributes nothing, so the exclusions are wrapped in it and every component keeps the last word about its own states. No test that renders a valid form would have caught this. `--_holo-fill` is a nine-layer gradient list, so a trailing `center / 220% 220%` in a `background` shorthand binds to the LAST layer only and the other eight fall back to `auto`. Every consumer uses longhands. Reduced motion stops the tilt, never the fill: a control that lost its mesh would lose its STATE, not just its animation. Tests pin the mesh transcriptions against the brand's own hexes (a hex typo in a nine-layer gradient is invisible in review and wrong forever), the alias privacy, the `:where()` specificity, and the drag arithmetic including its independence from the knob's laid-out width. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Round two on the holographic kit: the four shared motions the surfaces
were missing, the mesh on the surfaces that had earned it, and one
knob for how loud the station is.
Then three defects the review found, two of them accessibility
failures that measured far worse than they looked.
Motion
------
Four fragments in `src/ui/holo.ts`, all `prefers-reduced-motion`-aware
and all timed from one scale:
glint a specular band crossing once on hover. This is the gesture
people mean by "holographic" far more than any static
gradient is — foil does not glow, it CATCHES.
ring a press response that expands and fades, so a click reads
as received before its result paints.
shimmer the mesh travelling, for waits of unknown length.
enter scale-and-fade arrival for menus, dialogs and popovers.
Plus the ones that belong to a single component: the `<os-segmented>`
thumb that SLIDES between segments (the group owns one lit pill that
moves, rather than each child owning a fill that appears — so the
selection travels and the eye follows it), the `<os-tabs>` underline
growing from the centre, `<os-toast>` arriving from above and leaving
sideways, `<os-modal>`'s scrim fading while its dialog springs in, and
`<os-avatar>`'s presence ring — which pulses ONLY on `online`, so "who
is here" stops depending on telling three dot colours apart.
`--os-ui-motion-{fast,slow,ambient}` + `--os-ui-ease-{spring,out,loop}`
join `--os-ui-holo-transition`. Not for tidiness: a panel where the
switch settles in 220ms, the thumb in 300 and the underline in 150
does not read as three well-tuned controls, it reads as one surface
that cannot keep time.
The pseudo-element budget
-------------------------
`holoSheen` owns `::before` and `holoEdge` owns `::after`, which is the
whole budget for a control wearing both — as `<os-button>` does. So
glint and ring are ELEMENT-based: the component stamps a `<span>` and
the fragment styles it. Both are driven from the parent's state
through the CHILD combinator, which is load-bearing: `:active` matches
an activated element AND every ancestor of it, so `:active .os-holo-ring`
would fire every ring on the page the moment anything in the panel was
pressed.
Dimming Pulse
-------------
Per the designer's note, and the measurement says the fix is not where
the note pointed. Pulse #f252fc carries 6.2:1 against Obsidian — it is
not a contrast problem. It is SATURATED (HSL 296, 97, 65), and what
hurts is the places it is spread rather than stated: a bloom behind a
focused control, an 18% wash under a selected row.
So `--os-ui-accent` stays #f252fc — that one is the brand's, not ours,
and `brand-palette.test.ts` pins it — and `--os-ui-accent-dim` #d92ee3
(same hue, S and L pulled down together) takes over everything
ambient. Turning the station up or down is now one edit. The focus
RING deliberately does not follow it; only the bloom behind the ring
does, because a focus indicator is the last place to trade legibility
for calm.
The switch changed size between states
--------------------------------------
A 1px border plus the default border-box background clip: the off
state drew a grey ring OVER the fill's outer edge, and the on state
(`border-color: transparent`) let the mesh through it. The visible
pill therefore grew a pixel on each side as it turned on. The border
is gone and the off edge is an inset shadow, which occupies no layout
— so both states are the same box, and the swap for the lit glow moves
nothing.
The unlit half was not drawn
----------------------------
`--os-ui-holo-track` was `rgba( 12, 11, 15, 0.55 )`, which composites
to #121017 on an Obsidian panel: **1.07:1**. An off switch was, to a
good approximation, invisible — and the same token is the unchecked
checkbox and the empty half of every slider. WCAG 1.4.11 asks 3:1 of a
control's boundary.
The well is now LIFTED rather than sunk (a Starlight wash, which also
tracks whatever surface a theme puts behind it), and the 3:1 comes
from a new `--os-ui-holo-track-edge`: Pewter #66636b, the first step on
the Shade ramp that reaches it — Silver manages 2.03 and Astro 1.37.
The checkbox border moved off `--os-ui-border-strong` for the same
reason.
The knob vanished on its own mesh
---------------------------------
Holomesh's white glow is #fffdff, so the Starlight knob measured
**1.01:1** where the lit track is brightest. Not dim: absent. Its ring
was Void at 12% alpha, nowhere near enough to rescue it. At 55% the
ring composites to ~#7d7c7f over that glow and carries the knob at
3.5:1 there, 6.9:1 over the mesh's darkest stop — one declaration
covering both ends of the gradient and the unlit track too.
Text selection had no rule at all
---------------------------------
The shell shipped no `::selection`, so every selection in it was the
browser default — a colour chosen for a white page landing on a
near-black one, around 1.3:1 against the surface. The one rule that
did exist (inside form fields) read `--os-ui-accent-soft`, a 14% hover
tint, at ~1.2:1.
`--os-ui-selection-bg` is Lagoon at 60%: the one primary colour in the
guidelines the shell had no job for, and violet-blue is what every OS
has trained people to read as "selected". The alpha is squeezed from
both ends — the selected text has to stay readable (Starlight over it
is 5.1:1 on Obsidian, 5.5:1 on Void) AND the highlight has to separate
from the unselected background (3.4:1 and 3.5:1). Raising the alpha
helps the second and hurts the first; 60% is the middle of the band
where both clear on both surfaces. An accent wash cannot do this at
all: the Pulse family only separates above 75%, by which point it is a
fill rather than a highlight.
Both halves are set. Setting only `background` is the common half-fix
— the UA then keeps its own selected-text colour, frequently forced to
black, landing on this violet at ~2:1. The text colour is Starlight,
the colour body text already is: a selection that RECOLOURS text
destroys every distinction the text was carrying.
Scoped to `body.os-active`, so an admin page inside a window keeps the
selection it has outside one. `::-moz-selection` is its own rule
because an unrecognised pseudo-element invalidates the whole selector
list, which would have left Chrome unstyled too.
A guard for the trap that caused three of these builds to fail
--------------------------------------------------------------
A backtick inside a `css``` template closes the template early, and the
error the toolchain reports points at prose and says "expected a
semicolon". `tests/vitest/css-template-hygiene.test.ts` scans every
template under `src/ui` and says what is actually wrong. It caught a
live one in `<os-progress-bar>` on its first run.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Seventeen of the kit's live examples rendered nothing, in three
distinct ways, none of which fails loudly — a blank example is not an
error, it is an empty div.
A <script> that could never run
------------------------------
`<os-crumb-chain>` populated its `segments` from an inline `<script>`
in the help template. `html``` compiles by assigning to a
`<template>`'s `innerHTML`; the HTML fragment-parsing algorithm sets a
parsed script's *already started* flag, and the cloning steps copy that
flag to every clone. The script was inert in the template and inert in
the rendered output. It had never run once.
Property-driven components with nothing to render
-------------------------------------------------
Half the kit takes its data through a JS property rather than an
attribute — `segments`, `data`, `columns`, `entries`, `ratings` — so no
markup can fill them. `<os-table>` and `<os-log>` were rendering their
empty states; the vestigial `id="sample-table"` / `id="sample-log"`
attributes are the fossil of a script that was meant to populate them
and never did.
`OsHelp` gains `exampleInit?: ( root: HTMLElement ) => void`, run by
the panel after the example renders and scoped to the example's own
container. `customElements.upgrade( host )` first, and that is not
belt-and-braces: on the first paint the section is still detached, so
assigning `.segments` to an un-upgraded element would define an OWN
property that permanently shadows the accessor the upgrade installs on
the prototype — the setter never runs and the component sits there
empty, holding data it cannot see.
The hook must be idempotent; the panel re-runs it on every keystroke in
the filter box. Examples wire triggers with `onclick =` rather than
`addEventListener`, and a test enforces it.
Twelve classes with no example at all
-------------------------------------
The tab lists every entry in `OS_COMPONENT_TAGS`, so a file defining
three components is three rows — and only the parent had an example.
- Overlays (`<os-modal>`, `<os-confirm-dialog>`, `<os-context-menu>`)
are `display: none` until opened, so mounting one demonstrates
nothing. They now ship a trigger; the context menu is pinned back
into normal flow with an inline `position: relative` so it can be
looked at rather than floating over the settings window at
coordinates nothing set.
- Children (`<os-tab>`, `<os-tabpanel>`, `<os-segment>`,
`<os-menu-item>`, `<os-context-menu-option>`) have no shape outside
their parent — an unpadded row on the panel background — so each
example is the parent in miniature, showing every modifier the
child actually has.
- `<os-option>` paints nothing BY DESIGN (`:host { display: none }`,
it is a data carrier). Its example shows what the parent builds out
of it, because a blank Example section reads as a bug rather than
as the deliberate choice it is.
- `<os-tile>` is the one light-DOM component, so it only looks like a
tile where `desktop-files.css` is loaded. Shown on a dark strip,
because tiles live on the wallpaper and their labels are Starlight
— on the settings panel's own surface they would be white on white.
- `<os-role-picker>` passes `roles` explicitly rather than leaning on
the global config, which is empty on a site that has never shared a
folder.
Two more from the same review
-----------------------------
`<os-tag-input>` rendered "++ Add": the default `add-label` was the
literal string `'+ Add'` AND the button draws its own SVG plus. The
icon is `aria-hidden`, so the label is also the accessible name — a
screen reader was announcing "plus Add", the same duplication in the
other modality. The default is now `'Add'`.
The Components detail pane is its own scroll container and `render()`
diffs, so the same `<div>` survived every repaint and kept its
`scrollTop`. Picking a component while scrolled down through a long one
opened the next one halfway down, which reads as a component with
nothing at the top. It resets on selection change only — resetting on
every repaint would yank the pane to the top on each keystroke in the
filter box.
Guards
------
`tests/vitest/help-examples.test.ts` pins all of it: every class with
`static help` has an `example`, no example contains a `<script>`, every
property-driven component has an `exampleInit`, every overlay ships a
trigger, and no `exampleInit` accumulates listeners. The
addEventListener check strips comments first — every one of these files
explains in prose why it does not use it, and a scan that read the
explanation as the offence would fail on exactly the files that got it
right.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two labels in the Components tab that had stopped telling the truth. `since` was a version-history annotation rendered into the UI, which `AGENTS.md` already bans in docs and comments for the reason it is wrong here too: git is the changelog, and a help descriptor's job is to say what a component does NOW. Removed from all 63 declarations, from `OsHelp`, from the panel, and from the stylesheet. A change big enough that "since when?" is a real question for a plugin author is a breaking change and wants a `docs/migration-*.md` note instead. `experimental` was on 27 components that several windows already depend on. A badge saying "experimental" on a shipped component is not a caveat, it is stale metadata — it tells plugin authors not to rely on something they safely can. All promoted to stable. `OsHelpStatus` still accepts the value for a component that genuinely is one. Guarded in `help-examples.test.ts` so neither creeps back. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The brand ships five mesh gradients and one instruction about them — "meshes reserved for hero surfaces" — and nothing in the component kit was wearing them. This puts them in, as a moment rather than a skin: a control paints the mesh when it is on, selected, primary or filled, and wears ordinary Obsidian the rest of the time. A panel where every surface is iridescent has no identity moments left to spend, which is the failure mode the whole shape of this is built to avoid.
Four commits: the layer, its motion, a pile of Components-tab bugs the review turned up, and two stale labels.
The meshes, in CSS
--os-mesh-holo/-pulse/-auro/-star/-mioare the brand's SVGs transcribed stop-for-stop — Holomesh is its base linear plus all eight radial glows, at the artboard's own offsets, radii, colours and alphas, converted from 1440×960 into percentages.Gradient stacks rather than
url()s, and that is the point rather than an optimisation.background-positioncan slide a gradient, and that slide IS the holographic effect — a foil card shifting hue as you tilt it. An SVG could not be animated, could not be tinted, and would rasterise one flat corner of a 1440×960 artboard onto a 44 px switch track.src/ui/holo.tsEleven
cssfragments so no component re-decides what holographic means. Three surfaces —holoEdge(an iridescent hairline),holoSheen(a 10%-alpha film),holoFill(the mesh itself, Void ink on top) — and four motions:Plus the ones that belong to a single component: the
<os-segmented>thumb that slides between segments (the group owns one lit pill that moves, rather than each child owning a fill that appears — so the selection travels and the eye follows it), the<os-tabs>underline growing from the centre,<os-toast>arriving from above and leaving sideways, and the<os-avatar>presence ring, which pulses only ononlineso "who is here" stops depending on telling three dot colours apart.Every fragment honours
prefers-reduced-motionby stopping the tilt — never by removing the fill. A control that lost its mesh would lose its state, not just its animation.<os-switch>New component.
role="switch"on a real<button>, so Space, Enter, the disabled semantics and the focus ring come from the platform. Off is a lifted well; on is the Holomesh with a Pulse glow and Void ink. Tap, drag (the knob tracks the pointer live and snaps past the midpoint), plus ArrowLeft/Home and ArrowRight/End for absolute off/on — the pair a screen-reader user can use without tracking the current state.It emits
os-checkbox-changealongsideos-switch-change, so swapping a checkbox for a switch is a tag change and nothing else.Dimming Pulse
Per a designer note, and the measurement disagrees with the premise. Pulse
#f252fcis not a contrast problem — it carries 6.2:1 against Obsidian. It is saturated (HSL 296, 97, 65), and what hurts is where it is spread rather than stated.So
--os-ui-accentstays#f252fc(that one is the brand's, not ours, andbrand-palette.test.tspins it) and--os-ui-accent-dim#d92ee3takes over everything ambient — glows, washes, focus blooms. Turning the station up or down is now one edit. The focus ring deliberately does not follow it; only the bloom behind it does, because a focus indicator is the last place to trade legibility for calm.Three contrast fixes, all measured
The switch changed size between states. A 1px border plus the default border-box background clip: off drew a grey ring over the fill's outer edge, on (
border-color: transparent) let the mesh through it. The visible pill grew a pixel per side as it turned on. The border is gone; the off edge is an inset shadow, which occupies no layout.The unlit half was not drawn.
--os-ui-holo-trackwasrgba( 12, 11, 15, 0.55 ), which composites to#121017on an Obsidian panel: 1.07:1. An off switch was, to a good approximation, invisible — and the same token is the unchecked checkbox and every empty slider half. The well is now lifted rather than sunk, and the 3:1 WCAG 1.4.11 asks of a control boundary comes from a new--os-ui-holo-track-edge: Pewter#66636b, the first step on the Shade ramp that reaches it (Silver manages 2.03, Astro 1.37).The knob vanished on its own mesh. Holomesh's white glow is
#fffdff, so the Starlight knob measured 1.01:1 where the lit track is brightest. Not dim — absent. Its ring was Void at 12% alpha; at 55% it carries the knob at 3.5:1 there and 6.9:1 over the mesh's darkest stop.Text selection had no rule at all. The shell shipped no
::selection, so every selection was the browser default — a colour chosen for a white page landing on a near-black one.--os-ui-selection-bgis Lagoon at 60%: the one primary colour in the guidelines the shell had no job for, and violet-blue is what every OS has trained people to read as "selected". The alpha is squeezed from both ends — the selected text must stay readable (Starlight over it, 5.1:1 on Obsidian) and the highlight must separate from the unselected background (3.4:1). An accent wash cannot do this at all: the Pulse family only separates above 75%, by which point it is a fill rather than a highlight. Both halves are set, because leaving the text colour to the UA is how a selection ends up black-on-violet.The Components tab was showing empty examples
Seventeen of them, in three ways, none of which fails loudly — a blank example is not an error, it is an empty
<div>.<script>that could never run.<os-crumb-chain>set itssegmentsfrom an inline script. Thehtmltag compiles by assigning to a<template>'sinnerHTML; the fragment-parsing algorithm sets a parsed script's already-started flag, and the cloning steps copy it. Inert in the template, inert in every clone.segments,data,columns,entries,ratingsare JS properties — no markup can fill them, so<os-table>and<os-log>rendered their empty states.OsHelpgainsexampleInit( root ), run after the example renders and scoped to its own container. It callscustomElements.upgrade()first, and that is not belt-and-braces: on the first paint the section is still detached, so assigning to an un-upgraded element defines an own property that permanently shadows the accessor the upgrade installs — the setter never runs and the component sits there empty holding data it cannot see.display: noneuntil opened, so they now ship a trigger; children like<os-tab>have no shape outside their parent, so each example is the parent in miniature.Two more from the same review:
<os-tag-input>rendered "++ Add" (the default label was literally'+ Add'and the button draws its own SVG plus — and since the icon isaria-hidden, a screen reader was announcing "plus Add" too), and the detail pane kept itsscrollTopwhen switching components, so a long entry opened the next one halfway down.Two labels that had stopped telling the truth
sincewas a version-history annotation rendered into the UI, whichAGENTS.mdalready bans in docs and comments for the reason it is wrong here too. Removed from all 63 declarations, fromOsHelp, from the panel and from the stylesheet.experimentalwas on 27 components that several windows already depend on. A badge saying "experimental" on a shipped component is not a caveat, it is stale metadata — it tells plugin authors not to rely on something they safely can. All promoted to stable;OsHelpStatusstill accepts the value for a component that genuinely is one.Guards
holo-layer.test.ts— the mesh transcriptions against the brand's own hexes (a typo in a nine-layer gradient is invisible in review and wrong forever), alias privacy, the:where()specificity below, the dim routing, andprefers-reduced-motionon every fragment.help-examples.test.ts— every class withstatic helphas an example, no example contains a<script>, every property-driven component has anexampleInit, every overlay ships a trigger, noexampleInitaccumulates listeners, and no descriptor carries a version stamp or an experimental badge.css-template-hygiene.test.ts— a backtick inside acsstemplate closes it early and the toolchain reports "expected a semicolon" pointing at prose. This one says what is actually wrong; it caught a live defect in<os-progress-bar>on its first run.Two traps worth knowing about
holoFielduses bareinput/select/textareaselectors — safe inside a shadow root. Spelled honestly with:not()they weigh (0,3,1), heavier thaninput[aria-invalid='true']:focusat (0,2,1) — so the shared fragment would have outranked every component's own error ring and an invalid field would focus in Pulse instead of red. The exclusions are wrapped in:where()to hold the selector at (0,1,1). No test that renders a valid form would have caught this.The pseudo-element budget is spent.
holoSheenowns::beforeandholoEdgeowns::after. Glint and ring are therefore element-based, driven from the parent's state through the child combinator —:activematches an activated element and every ancestor of it, so:active .os-holo-ringwould fire every ring on the page.Green:
lint,typecheck, 3634 vitest,build. No PHP touched.Docs: a holographic layer section in
docs/components-reference.md(which treatment goes where, the token tables, how to use it in your own component), the new themeable tokens and motion scale indocs/desktop-themes.md, and rules inAGENTS.mdfor the holo module, the accent-dim knob and the backtick trap.Worth looking at on a real install: OS Settings (the sliding segmented thumb, switches off and on), any dialog (spring entry), a Posts table (selection marker, checkboxes), the Components tab end to end, and drag-selecting some text anywhere.
🤖 Generated with Claude Code