The code cited its plan instead of saying why: `E28 review J4`, `(R6 S3b)`, `S1 justified…`,
`deep-review round 5`. That plan lives in the private ops repo, so for every reader of this one the
citation was noise, and a sentence whose subject was the plan step ("round 2 showed…") hid its
lesson behind an id. Each one is gone and the "why" kept: a bare citation is deleted, a sentence
carrying a lesson is reworded around it ("a later attempt showed…").
Measured (`scripts/audit_metrics.py`, product code): 652 lines in 270 files carried an epic code by
the definition the next commit holds (507 / 241 by the old, two-digit-only one) -> 0. Across all
code, tests, scripts and configs: ~1100 epic-code lines plus ~320 sprint/round ids.
Behaviour unchanged, checked mechanically: every changed .py file has the same AST once docstrings
are blanked; css/sh/toml/yml/json files are identical once comments are stripped; the only
non-comment changes in TS/JS are test titles, one description string in a contract table, and five
ESLint messages, whose text is all the gates snapshot diff holds. Not touched: Markdown (docs and
the changelog keep their history), Alembic migrations, TV episode notation (`S2 · E5`), DESIGN.md
ids (`C1`, `B4`, `S3h`), tracker ids (`SEC-16`, `DX-17`).
One stale claim fixed on the way: test_hls_reaper's docstring said the player checkpoints "every
10 s whether or not it is playing", which plex/stream.py calls wrong; it now says what the code does.
107 lines
5.9 KiB
TypeScript
107 lines
5.9 KiB
TypeScript
import { useEffectEvent, useLayoutEffect, useState, type RefObject } from "react";
|
|
|
|
// The shared DOM-measure-and-fit core for a single-line chip lane that collapses its overflow into a
|
|
// trailing "+N" pill. Owns the layout-effect + ResizeObserver + initial-measure wiring and reads the
|
|
// common geometry off a hidden mirror (each chip's right edge, the real rem gap, the pill width, and
|
|
// the lane's available width); each caller layers its OWN fit policy in `decide` and gets that result
|
|
// back. This was hand-rolled twice — the feed's applied-filter row (ActiveFilterChips, whose policy
|
|
// also reserves a leading label + a trailing "Clear all" and can collapse to a single count pill) and
|
|
// the channel tag cell (ChannelTagsControl, chips-only) — with the same measurement half copied in
|
|
// both. The policy halves live in lib/chipOverflow.ts; this owns the measurement half.
|
|
//
|
|
// Coordinate note: `chipRights` are offsets within the hidden mirror (its children's offsetParent),
|
|
// while `avail` is the VISIBLE lane's clientWidth — the two are comparable because the mirror is laid
|
|
// out `absolute left-0 top-0` over the lane's start, so a mirror-relative right edge is the position a
|
|
// chip would take in the real lane. Reads real rem boxes, so nothing is frozen in px (the text-size
|
|
// slider scales the gap and chip widths, and the fit follows).
|
|
|
|
/**
|
|
* The class the chip LANE must carry — both halves are load-bearing.
|
|
*
|
|
* `relative` is the measurement contract: the hidden mirror is `absolute left-0 top-0`, so the lane is
|
|
* its offsetParent and a mirror-relative right edge is directly comparable to the lane's clientWidth
|
|
* (see the coordinate note above).
|
|
*
|
|
* `overflow-clip` is the containment half. The mirror is laid out UNBOUNDED — that is the whole point,
|
|
* it reads natural widths — so its box sticks out far past the lane. An absolutely-positioned box still
|
|
* contributes to its ancestors' SCROLLABLE overflow, and `visibility: hidden` does NOT exempt it, so
|
|
* without a clip the mirror leaks all the way up to <html>. Measured on the channel manager at a 1024px
|
|
* viewport: the mirror dragged the document's scroll width to 1258px — the whole app scrolled
|
|
* 249px sideways and the fixed header slid to x=-177 — and widened the Tags cell 84px past the width its
|
|
* fixed-layout column had assigned it. `clip`, not `hidden`, because clip creates no scrollport, so
|
|
* nothing can ever scroll the mirror into view. The clip cannot disturb the measurement: offsetLeft and
|
|
* offsetWidth are layout values, and clipping does not change layout.
|
|
*
|
|
* `overflow-clip-margin` is what keeps the clip from eating the KEYBOARD FOCUS RING. A bare
|
|
* `overflow: clip` cuts at the padding box, while the app-wide base ring is `outline: 2px` at
|
|
* `outline-offset: 2px` — i.e. it paints 4px OUTSIDE the border box. These lanes have no padding and
|
|
* their height is set by the chip itself, so the first commit of this fix silently cut the ring's top
|
|
* and bottom bands off every applied-filter chip and the tag lane's `+` trigger, leaving two floating
|
|
* side bars. 0.25rem is exactly the ring's reach and nowhere near the ~600px the
|
|
* mirror needs, so containment is unaffected.
|
|
*/
|
|
export const chipLaneClass = "relative overflow-clip [overflow-clip-margin:0.25rem]";
|
|
|
|
export interface ChipRowGeometry {
|
|
/** Available lane width — the visible row's clientWidth. */
|
|
avail: number;
|
|
/** Right edge (offsetLeft + offsetWidth) of each chip in the measurer, index-aligned to the chips. */
|
|
chipRights: number[];
|
|
/** Width of the trailing sample "+N" pill in the measurer. */
|
|
pillW: number;
|
|
/** The real rendered inter-item gap (rem-scaled), read off the box between the last chip and the pill. */
|
|
gap: number;
|
|
}
|
|
|
|
export function useChipOverflowFit<R>(args: {
|
|
/** The VISIBLE lane; its clientWidth is the available width, and resize is observed on it. */
|
|
rowRef: RefObject<HTMLElement | null>;
|
|
/** The measurer's chip elements, index-aligned (a hidden mirror of the full chip set). */
|
|
chipsRef: RefObject<Array<HTMLElement | null>>;
|
|
/** The sample "+N" pill in the measurer. */
|
|
pillRef: RefObject<HTMLElement | null>;
|
|
/** Re-measure when this changes (a key over the chip set — count + labels, so a rename re-fits too). */
|
|
deps: unknown;
|
|
/** The caller's fit policy over the measured geometry. */
|
|
decide: (geom: ChipRowGeometry) => R;
|
|
/** Value used until the first measure (and while the refs aren't mounted). */
|
|
initial: R;
|
|
}): R {
|
|
const { rowRef, chipsRef, pillRef, deps, decide, initial } = args;
|
|
const [result, setResult] = useState<R>(initial);
|
|
// Read `decide` fresh each run (an effect event) without making it an effect dependency: the effect re-runs on `deps`
|
|
// (and resize), and a caller's decide closure is rebuilt every render — depending on it would
|
|
// resubscribe the ResizeObserver on every render.
|
|
const decideNow = useEffectEvent(decide);
|
|
|
|
useLayoutEffect(() => {
|
|
const row = rowRef.current;
|
|
const pill = pillRef.current;
|
|
if (!row || !pill) return;
|
|
const rightOf = (el: HTMLElement | null | undefined) =>
|
|
el ? el.offsetLeft + el.offsetWidth : 0;
|
|
const run = () => {
|
|
const chips = (chipsRef.current ?? []).filter((c): c is HTMLElement => c != null);
|
|
const lastChip = chips[chips.length - 1];
|
|
setResult(
|
|
decideNow({
|
|
avail: row.clientWidth,
|
|
chipRights: chips.map(rightOf),
|
|
pillW: pill.offsetWidth,
|
|
gap: lastChip ? Math.max(0, pill.offsetLeft - rightOf(lastChip)) : 0,
|
|
}),
|
|
);
|
|
};
|
|
run();
|
|
// jsdom has no ResizeObserver — the one-shot measure above still runs there (unit tests drive it).
|
|
if (typeof ResizeObserver === "undefined") return;
|
|
const ro = new ResizeObserver(run);
|
|
ro.observe(row);
|
|
return () => ro.disconnect();
|
|
// The three refs are stable by construction, so naming them costs nothing and `deps` stays the
|
|
// only thing that actually re-measures.
|
|
}, [deps, rowRef, chipsRef, pillRef]);
|
|
|
|
return result;
|
|
}
|