/** * __theming-probe — the before/after computed-style probe of PLAN-theming §7.2/§7.4. * * node scripts/__theming-probe.ts [url] * * Temporary instrument (the `__` prefix keeps it out of the tracked surface). * Walks the component demo, and for every node matching `[data-{c}…]` records * the knob computed values at rest, per size, hovered and focused. The size is * forced by WRITING `data-size` on the nodes that already carry it — the recipe * selects on that attribute, so the computed result is the same one the real * interaction produces, without depending on each demo's chips. * * Run it with plain `node`, NOT with tsx: the tsx loader injects helpers that * are not defined inside `page.evaluate`, and every probe throws. * * Must run from the repo ROOT (playwright does not resolve from the scratchpad) * and against a live dev server; a hidden screen freezes rAF, so headless is the * reliable surface. */ import { writeFileSync } from 'node:fs'; import { chromium } from 'playwright'; const PROPS = [ 'backgroundColor', 'backgroundImage', 'color', 'borderTopColor', 'borderTopWidth', 'borderTopLeftRadius', 'borderBottomRightRadius', 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft', 'rowGap', 'columnGap', 'fontSize', 'fontWeight', 'fontFamily', 'lineHeight', 'letterSpacing', 'blockSize', 'inlineSize', 'minBlockSize', 'minInlineSize', 'boxShadow', 'opacity', 'fill', 'stroke', 'strokeWidth', 'strokeOpacity', 'outlineColor', 'outlineWidth', 'filter', 'backdropFilter' ]; const SIZES = ['xs', 'sm', 'md', 'lg', 'xl']; /** * Demo controls that must be ON before measuring: a part the demo does not * mount is a part the diff never compares, and a probe over two nodes passes * in false (the F2-B lesson — count the nodes before believing a green gate). * text-gradient boots with `showBorder=false`, so its whole frame — three of * its seven tokens — is absent from the default stage. */ /** * Components whose PARTS are not tagged `data-{c}-*`: prose styles bare HTML * (h1, code, table…) through `:where([data-prose] el)`, so the attribute filter * below sees exactly ONE node and the gate proves nothing. The selector's * matches join the measured set. */ const EXTRA_NODES: Record = { prose: '[data-prose] *', // Its parts carry the MEDIA-PLAYER attrs (it is the audio skin of that // chassis, not a component with its own part names): the `data-audio-player` // filter matched ZERO nodes. 'audio-player': '[data-media-player][data-variant], [data-media-player][data-variant] *', // Its SVG innards hook on CLASSES, not on `data-onion-*`: without this the // filter sees the five attribute nodes and misses every sector, label, icon // and glyph — the whole surface the recipe paints. 'onion-menu': '.onion-menu-sector, .onion-menu-label, .onion-menu-icon, .onion-menu-icon svg, .onion-menu-trigger, .onion-menu-trigger-glyph', // Its mega-menu rows are the CONSUMER's bare `` (the recipe styles them // through `[data-navigation-menu-content] :is(a, …)`), so the attribute // filter saw 10 nodes and NONE of them was the panel or a row — 9 of its 19 // knobs were outside the diff. They only exist while the panel is open, so // the `open` pass below honours this selector too. 'navigation-menu': '[data-navigation-menu-content] a', // Its interaction chrome IS an embedded canonical Slider that the recipe // re-tints (the thumb becomes the playhead): those nodes carry // `data-slider*`, so the attribute filter saw 4 nodes and none of them was // the playhead the recipe paints. waveform: '[data-waveform] [data-slider], [data-waveform] [data-slider] *', // The scrolling viewport is the composed VirtualList's (`data-virtual-list-viewport`), // so the two knobs the log paints on it — its inline/block padding — had no // node in the filter; the to-latest glyph is a bare ``. 'chat-log': '[data-chat-log] [data-virtual-list-viewport], [data-chat-log-to-latest] svg, [data-chat-log-separator-day] > *', // Both glyphs are bare `` children of the parts that carry the attrs, // and the recipe sizes them by descendant selector. 'chat-composer': '[data-chat-composer-send] > svg, [data-chat-composer-context-close] > svg', // A FAB is ONE node (the composed `