Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,10 @@ jobs:
cache-on-failure: true

# The host is the browser, and the browser is not a crate a site can
# install: `chuzz-headless` is built from source by the composite action
# below, which also installs the driver. That replaced `qa-inspect-host`,
# install: the composite action below downloads the verified
# `chuzz-headless` release matching its own revision (building that
# revision from source only if no release exists), and installs the
# driver. That replaced `qa-inspect-host`,
# which was a second headless browser with the web platform in only the
# other one -- no `URLSearchParams`, no `matchMedia`, no storage, no
# observers -- so every gap closed for the browser had to be closed a
Expand All @@ -54,12 +56,18 @@ jobs:
#
# 0.1.12 is still on crates.io, so nothing broke when the crate was
# deleted from source; this is the last consumer to move off it.
#
# Pinned to a chuzz revision that has a published host (0.1.40). The
# previous pin predated the releases, so every run compiled that old
# source against today's registry, and it stopped compiling once its
# unlocked dependencies moved on (tokio features, blitz-control-protocol
# 0.5.3): every PR here went red on a browser build, not on this code.
- name: Headless browser host
id: qa
uses: pathscale/chuzz/.github/actions/headless-host@b0e4ce0d35fe96230c90d2a3ed55a64686abe370
uses: pathscale/chuzz/.github/actions/headless-host@094d77b6f28f78ffcdc20b8b19e160ee6c4992ee
with:
token: ${{ secrets.SIBLING_REPOS_TOKEN }}
chuzz-ref: b0e4ce0d35fe96230c90d2a3ed55a64686abe370
chuzz-ref: 094d77b6f28f78ffcdc20b8b19e160ee6c4992ee
ps-qa-version: =0.7.3

- name: Install Dependencies
Expand Down
6 changes: 3 additions & 3 deletions docs/reviews/2026-07-27-ai-smell-deadcode-tests-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -901,9 +901,9 @@ bun run playground:dev # fastest visual loop; aliases @pathscale/ui ->
```

**Reproduce the duplication census:** the 6-line normalized-window scanner used for SEV-4 is
at
`/private/tmp/claude-501/-Users-revenge-code/4526fe76-867f-4d6a-a325-84ff907ebbb8/scratchpad/dup.ts`
(scratchpad, may be cleaned up; it is ~40 lines and trivial to rewrite). Spot checks:
was a
`dup.ts` in the reviewer's scratch directory, not kept in the repository (it is ~40
lines and trivial to rewrite). Spot checks:

```bash
rg -n "const invokeEventHandler" src | wc -l # 25
Expand Down
2 changes: 1 addition & 1 deletion docs/reviews/2026-07-27-theming-css-build-perf.md
Original file line number Diff line number Diff line change
Expand Up @@ -733,7 +733,7 @@ Read in this order:
Commands that work in this checkout (no `node_modules` present):

```bash
git -C /Users/revenge/code/UI rev-parse --short HEAD
git rev-parse --short HEAD
diff <(sed -n '88,135p' src/styles/themes/light.css) <(sed -n '87,134p' src/styles/themes/dark.css) # SEV-6: silent
rg --no-filename -o "z-index: [^;]+;" src/components --glob '*.css' | sort | uniq -c | sort -rn # SEV-7
rg -n -- "--z-overlay\s*:" src # SEV-7: no hits
Expand Down
14 changes: 14 additions & 0 deletions src/components/_shared/overlayHost.css
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,24 @@
*
* Import this file and put `ui-overlay-host` on the component's root, alongside
* whatever positioning that root already sets.
*
* ## Why an open host lifts itself
*
* The stacking context has a cost: the overlay's own `z-index` now only ranks
* it inside its host. The host itself paints at `z-index: auto`, in tree order,
* so any later positioned sibling of an ancestor paints over the open overlay.
* A Select in a table row is the case that shipped: `.table__row` is
* `position: relative`, so the next row covered the open list and its options
* could not be pressed. While anything inside the host is open, the host takes
* the overlay's layer so the whole subtree paints above what follows it.
*/
@layer components {
.ui-overlay-host {
position: relative;
isolation: isolate;
}

.ui-overlay-host:has([data-open="true"]) {
z-index: 80;
}
}
35 changes: 26 additions & 9 deletions src/components/_shared/overlayPosition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,32 @@ export const createOverlayPosition = (
options.anchorRect?.(),
);
if (!triggerRect) return;
const overlayRect = overlay.getBoundingClientRect();
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;

// Size before measuring. The width limits below arrive through the
// style signal, which lands only after this pass, so the overlay was
// measured at its unconstrained width: for Select, whose CSS floor is
// `max(100%, 14rem)` on a fixed box, that is the whole viewport. The
// left clamp then pinned it to the screen edge. Chromium corrected it a
// frame later through the ResizeObserver, which read as a blink; an
// engine whose observer does not fire kept it there. Writing the same
// limits onto the element first makes the one measurement the real one.
const maxWidth = `${round(Math.max(0, viewportWidth - viewportPadding * 2))}px`;
const minWidth = options.matchTriggerWidth?.()
? `${round(Math.max(triggerRect.width, options.minWidth?.() ?? 0))}px`
: undefined;
// A throw here would halt every reactive owner in the app, and an
// overlay stand-in without a style declaration (tests measure with
// one) is still positionable, so the write is skipped rather than
// assumed.
const overlayStyle = overlay.style as CSSStyleDeclaration | undefined;
if (overlayStyle) {
overlayStyle.maxWidth = maxWidth;
if (minWidth) overlayStyle.minWidth = minWidth;
}

const overlayRect = overlay.getBoundingClientRect();
const offset = options.offset();

const placement = resolvePlacement(
Expand Down Expand Up @@ -201,14 +224,8 @@ export const createOverlayPosition = (
position: "fixed",
top: `${round(clamp(top, viewportPadding, maxTop))}px`,
left: `${round(clamp(left, viewportPadding, maxLeft))}px`,
"max-width": `${round(Math.max(0, viewportWidth - viewportPadding * 2))}px`,
...(options.matchTriggerWidth?.()
? {
"min-width": `${round(
Math.max(triggerRect.width, options.minWidth?.() ?? 0),
)}px`,
}
: {}),
"max-width": maxWidth,
...(minWidth ? { "min-width": minWidth } : {}),
});
};

Expand Down
Loading