Manifest V3 Chrome extension that monkeypatches the Kinetic main.js in place to fix three
virtual-scroll grid defects:
- Group/ungroup memory leak — grouping then ungrouping a large virtual grid balloons into a render-all state (~2 GB / 469k-DOM-node leak on a 4,466-row Job Entry grid).
- Blank grid after bulk load (v3.1) — after a large Search → Download Records → Get More → Select All → OK load (~25k rows), the grid renders blank even though the rows are in the DOM and the toolbar is enabled, until you manually scroll.
- Checkbox glyph styling drift after group/ungroup (v3.2) — boolean columns render as Material Design Icon font glyphs (☐/☑); after a group→ungroup re-render some re-materialized cells come back with drifted presentation (different size, mis-alignment, or color) so the column's checkboxes look inconsistent. A scoped stylesheet pins them back to the column's own canonical look.
All three fixes ship together, default OFF, behind the same toggle.
Multi-field grouping legitimately expands the grid's view to every row (the grouped display
shows all rows). The bug is that on ungroup the windowed view is never restored: the Kinetic
grid-binding directive's rebind() recomputes view with a state.take that grouping nulled and
ungroup never restores, so view = the whole dataset and ~4,466 rows materialize into one <tbody>.
The fix hooks the binding directive's rebind() (anchor rebind(){this.checkForPrismSkip()): when
state.group.length === 0 and state.take is pathological (missing / far above the natural window),
it restores state.take (+ grid.pageSize) to the captured natural window (~80) before the
original rebind recomputes view. Two things make it work:
- Right layer. The content-directive
createScroller(an earlier attempt) fires 0× on ungroup;rebind()fires on the ungroup edge andstate.takeis the lever. - Right store. The binding directive AND the Kendo grid component are recreated when grouping
changes, so a per-instance capture is lost. The natural window is stashed on the Kinetic wrapper
bd.epGrid, which survives the recreation.
Live-validated (full scale): all 4,466 rows loaded, multi-field group→ungroup → post-ungroup
tbodyRows 80 (vs 4,466), DOM 8,971 (vs 469,501), heap 248 MB (vs 2,037 MB), stable
across cycles; mounts at 80 (no window shrink / scroll-snap regression); scroll-to-bottom populates
with no snap. Evidence: .output/chrome-plugin-grid-fix/v3-validation-success.md.
- Default OFF.
chrome.storage.local.gridFixEnabledinitializes tofalse; a freshly installed extension does nothing until you flip the toggle. - No-debug default (
gridFixMode: "runtime"): the MAIN-worlddocument_startruntime hook runs with nodebuggerpermission and no Chrome debugging banner. It installs early webpack traps and best-effort wraps the binding-directive class. On builds where the binding directive lives only inside the parser-loadedmain.*.jsclosure, runtime mode reportstrapInstalled: truebutapplied: false/prototypeWrapped: false. - Validated text-rewrite mechanism (
gridFixMode: "debugger"): an opt-inchrome.debuggerpermission grant enables CDPFetchresponse-stage interception of Kineticmain.*.js, served viapatchBundleText(one-line hook call injected at therebindanchor + the hook def appended). This remains the only extension-only path that reliably rewrites the parser-loaded bundle before it executes. - Feasibility boundary: in normal Manifest V3,
declarativeNetRequestcan block/redirect/modify headers but cannot transform arbitrary response bodies, and blockingwebRequestis unavailable to non-policy extensions. Withoutdebugger(or an external proxy/server-side delivery), the extension can support runtime/DOM/CSS fixes but cannot honestly claim a pre-executionmain.jstext rewrite. - When applied, the page exposes
window.__KINETIC_GRID_FIX__ = { version, enabled, applied, mode, bundleHash, anchorsHit, corrections, trapInstalled, prototypeWrapped }(correctionsincrements each time the fix re-windows). Treatapplied: trueas meaningful only whenprototypeWrapped: trueor mode isrebind-text. The blank-fix addsblankFixArmed/blankCorrections; the checkbox standardizer addscheckboxStyleFixArmed/checkboxCanonical/checkboxStyleReassertsto the same shared marker.
Live-diagnosed 2026-06-04 on Job Status Maintenance (JCGO3033, SaaS950 Education) with 25,276 jobs.
Kendo positions virtual rows with transform: translateY(N) on .k-grid-table, where N tracks the
scroller's scrollTop (N ≈ skip × rowHeight). After a large bulk load the table is left translated
for a non-zero data window (N ≈ 10,000px) while scrollTop is 0 and no scroll event fires to
reconcile them, so the rendered rows sit ~10,000px below the viewport — the grid looks empty. It
persists (observed >90 s) until the user scrolls. It is intermittent / race-dependent.
The fix is a DOM-geometry alignment watchdog (src/grid-blank-fix.js). On grid mutations + a
1.5 s safety scan it checks each virtual grid; if rendered rows exist but none intersect the
viewport while scrollTop is near the top (the bug signature), it performs a page-crossing scroll
nudge then returns to top, which forces Kendo to recompute translateY in sync with scrollTop.
Proven live: a sub-page nudge does not trigger Kendo's translateY rewrite, but a page-crossing
scrollTop change does — so far-jump-then-0 realigns. It is near-top-only (never fights a user who
has scrolled away) and a no-op on healthy grids (validated: 0 false corrections at top or mid-scroll).
Delivered both ways, alongside the group/ungroup fix: M1 (debugger) appends the watchdog's
self-installing source to the patched bundle; M2 (runtime) installs it at document_start. When armed
the page marker carries blankFixArmed: true and blankCorrections (increments on each realign).
Live before/after: a populated grid forced blank (80 rows rendered, 0 in viewport) → watchdog →
38 rows in view at the top. Harness: verify/grid-live-harness.mjs (workflow automation + scroll
smoothness test + blank detector) and verify/blank-fix-live-repro.mjs (re-trigger + auto-fix check).
v1.1 hardening (extension v3.1.1) — live-validated on the JCGO3033 grid: (1) a stability gate
re-confirms the blank ~70ms before acting, so a single transient frame during fast scrolling is never
corrected; (2) symmetric detection catches rows rendered above or below the viewport at the top
(v1.0 caught only below); (3) a faster 800ms safety interval plus a relevance-filtered
MutationObserver (re-checks only on grid-related DOM churn, not page-wide); (4) an in-flight guard
so concurrent triggers don't stack corrections on one grid; (5) richer diagnostics — the per-correction
log() records the trigger source and signed topGap, lastCorrection() exposes the latest, and the
shared marker gains lastCorrectionAt / lastTopGap for field debugging.
Reported 2026-06-04; confirmed live on Job Status Maintenance (JCGO3033, SaaS950 Education). Kinetic
boolean columns (To Firm, Firm, Locked, Mass Print, Engineered, Released, …) do not render as
<input type=checkbox> — each is a Material Design Icon font glyph:
<span class="ep-grid-cell-check mdi mdi-checkbox-{blank,marked}-outline">, sized and positioned
purely by CSS (font-size on the span; the icon drawn via ::before). The canonical (healthy)
presentation is uniform: font-size:24px; line-height:12px; display:flex; justify-content:center; align-items:center; color:#000. After a multi-field group→ungroup the grid re-materializes its rows
during the render-all window, and some re-rendered glyph cells come back with drifted presentation
(different font-size, mis-alignment, or color) — so the column's checkboxes look inconsistent even
though every value is still a correct glyph.
The fix (src/grid-checkbox-style-fix.js) injects one scoped stylesheet that pins the boolean
glyph's size + alignment (and the enabled-cell color) to the column's own canonical value, appended
last in <head> with !important so it wins the cascade over whatever class a drifted cell lost/gained.
It is:
- Adaptive / theme-safe — the canonical values are sampled from the majority of currently
rendered glyphs (modal font-size/line-height/justify/align/color), not hardcoded, so a compact-density
or re-themed grid pins to its own healthy values. The captured
24pxdefaults are only the bootstrap fallback used before any glyph exists (atdocument_start). - Disabled-state-preserving — color is pinned only for cells outside
.ep-row-rule-disabled(:not()selector), so the value-driven greying of disabled booleans is never overridden. - A visual no-op on healthy grids — on a consistent grid the pin equals the existing values, so nothing moves; it only takes visible effect on a cell that actually drifted. Live-validated: on the real 480-glyph JCGO3033 grid the standardizer sampled the true canonical and reported 0 deviations / 0 visual change; a glyph forced to 40px was pulled back to 24px by the pin.
- Preventive — injected CSS applies to every matching glyph the instant it enters the DOM, so a re-rendered cell can never display drifted before being corrected.
Delivered both ways alongside the other fixes: M1 (debugger) appends the standardizer's self-installing
source to the patched bundle; M2 (runtime) installs it at document_start. When armed the shared marker
carries checkboxStyleFixArmed: true, checkboxCanonical (the sampled values), and
checkboxStyleReasserts. A relevance-filtered MutationObserver + a light safety interval re-assert the
stylesheet if a full SPA re-mount strips it.
v3.3 adds two independent, additive theming toggles that are completely separate from the three grid fixes above — different delivery path, no debugger, no tab reload, no new required permissions:
- Disable theming — neutralizes a themed tenant so it renders in the stock (un-themed) palette.
- Override colors — per-family color pickers that recolor the Kinetic UI to colors you choose.
- Surface / neutral tint (v3.6) — one picker blends a hue into Kinetic's grayscale ramp (borders + surfaces) while preserving each shade's lightness, so contrast stays intact.
The finding that drives it (captured live 2026-06-05 on two tenants): Epicor Kinetic theming is
delivered as exactly 25 CSS custom properties set in an inline style attribute on the <html>
element, in 10 color families (--primary, --secondary, --tertiary, --accent, --base,
--interactive, --focus, --error, --success, --warning + their lightness variants). A later
CDP-9100 palette census found the base stylesheet declares two more --success tints
(--success-4566, --success-3682) that are not in the inline attribute (so the tenant never rotates
them) — the full theme namespace is 27 tokens / 10 families. The stock value of every token is
identical across tenants; a "themed" tenant simply hue-rotates the brand families (e.g. Education's
primary is hue 0 = maroon; stock is hue 192 = teal). An !important html{ --token:value } rule injected
as a <style> beats the inline (non-!important) theme value, and removing the <style> restores the
page exactly. So:
- Disable = pin all 27 tokens to their stock values with
!important→ the themed tenant looks un-themed (the 2 base-only success tints are pinned as a harmless no-op). - Override = pin the chosen families to user-derived values. From the picked hex the base token takes the exact color and each lightness variant keeps its own designed lightness (picked hue+saturation), preserving the contrast ramp Epicor designed — so changing only the hue reproduces native-style theming.
- Both on = stock everywhere, your colors where you set them (later-wins source order).
- Both off = the
<style>is removed entirely and the page reverts to its native appearance.
Surface / neutral tint (v3.6). A CDP-9100 full-screen census established that the 10 family pickers
already control 100% of the brand/chromatic color on screen; the only colors they don't touch are the
7-token --neutral-* grayscale ramp (hsl(0,0%,L): borders/dividers/surfaces ≈ 87% of on-screen gray,
never tenant-rotated). The neutral-tint control pins those 7 to hsl(pickedHue, pickedSat, stockL) — it
injects a hue + saturation but keeps each token's stock lightness, so contrast ratios (which depend on
lightness) are unchanged and text/surfaces stay legible. Deep text grays (Kendo/Bootstrap tokens) are left
alone on purpose. Default-OFF, same ISOLATED live-injection path, no new required permissions. Live-proven 7/7
(.tmp/palette-audit/verify-neutral-tint.mjs): the border gray recolors rgb(204,204,204)→tinted while
brand colors stay put, exact revert.
Delivery (src/theme-control.js). A declarative ISOLATED-world content script
(run_at: document_start, scoped to the already-granted *://*.epicorsaas.com/* host, plus any
user-granted custom hosts) reads the three chrome.storage.local keys (themeDisableEnabled,
colorOverrideEnabled, colorOverrideValues),
builds the <style id="kinetic-theme-control">, and subscribes to chrome.storage.onChanged so a
color-picker drag previews live on already-open tabs with no reload. It runs ISOLATED (not MAIN)
world — it never touches page JS, only the shared DOM — which is a lower-risk surface than the grid
fix's debugger path. Both toggles default OFF; when both are off the injector is fully inert (no
<style>, status marker active:false). The injector publishes a status marker to
document.documentElement.dataset.kineticThemeControl (and the ISOLATED window) that the popup reads
in MAIN world ({ version, active, themeDisabled, colorOverride, families, tokensPinned, reasserts }).
Live-validated (verify/theme-live-harness.mjs, CDP-9100 against the themed Education and un-themed
Third tenants): disable flips all 25 computed :root tokens to stock and the home-card tiles visibly
shift maroon→teal (rgb(100,2,2)→rgb(2,80,100)); an override of --primary to #1a73e8 turns the
tiles blue (rgb(28,116,233)); turning everything off reverts every token exactly to baseline; and
the un-themed Third tenant already matches stock — proving "disable on Education ⇒ looks like Third".
Evidence: .output/chrome-plugin-grid-fix/theme-verification.md.
v3.20 adds a "Wrap column headers" toggle. By default a Kinetic grid header forces each title onto one
line (white-space:nowrap + ellipsis) with table-layout:fixed columns, so a column whose data is just a
checkbox is still pinned wide enough to fit its whole title on one line — e.g. on ABC Code Maintenance
the Exclude from Cycle Count column is 229px while its content needs ~20px. The toggle flips header
titles to white-space:normal so multi-word titles stack vertically at word boundaries, centers the
header label independently from the column's body-data alignment, and narrows each column whose width was
dictated only by its header down to the width of its widest word plus the live Kendo header inset. That
keeps dense labels such as UOM on one line and labels such as Renewal Number on two intact-word lines
instead of cutting whole words apart.
Same delivery class as auto-size/theming: a separate ISOLATED-world content script
(src/grid-header-wrap.js, run_at: document_start), no debugger, no main.js rewrite, no tab reload, no
new permission, applied live, default OFF. Turning it off removes the wrap CSS and restores the
native widths.
Race-free coordination with Auto-size columns. Both features write <col> widths, so exactly one owns
them: when Auto-size columns is ON, header-wrap injects only the wrap CSS and defers sizing to
grid-autofit.js, which (seeing the header-wrap flag) measures each header by its widest word and narrows
it as part of its own fit while preserving enough header text area for that word. When auto-size is OFF,
header-wrap owns the shrink-only narrowing itself.
Live-validated (CDP 9100, Education SaaS950, Order Tracker Lines grid, 2026-06-10; harness
verify/header-wrap-live-harness.mjs): headers center regardless of text/numeric/boolean body alignment;
UOM remains a single intact word; Renewal Number wraps to intact words; and numeric/text body alignment is
unchanged. Earlier ABC Code Maintenance validation remains the wide-header compaction proof case.
v3.5 adds per-component-family density sliders — fine-tune the size of each Kinetic component type
independently: grids, buttons, text fields, dropdowns, tabs, and field labels. Same delivery class as
theming: a separate ISOLATED-world content script (src/padding-control.js, run_at: document_start, already-granted or user-approved host), no debugger, no main.js rewrite, no tab
reload, no new required permission, applied live, default OFF.
v3.30 adds a default-off "Text area auto-size" switch in the same Padding & spacing panel. When enabled,
Kinetic ep-text-area fields remove the native vertical resize handle and the runtime writes reversible
inline heights from each textarea's measured scrollHeight; turning it off clears those inline heights and
restores Kinetic's native resize: vertical behavior.
v3.31 adds a default-off "Full width mode" switch in that same live injector. It removes the Kinetic
ep-view.page-content.header-width cap and stretches the fixed page header, panel cards, panel-card grids,
and their AppStudio container wrappers to the full available splitter-pane width. It is intended for
ultrawide monitors and high-density layouts: the page can consume the unused right-side viewport without
requiring a reload or changing any data.
Why per-family overrides, not token scaling (captured live 2026-06-05; .tmp/padding-iter/). The
earlier v3.4 approach scaled Kendo's :root design tokens (--kendo-spacing-*, --kendo-font-size-*).
That was proven a near-no-op on real Epicor forms: spacing 1.8× and font 1.4× left the Order Entry
form visually identical — only pure-Kendo buttons/toolbar moved. The reason (found by scanning the 21k
live CSS rules): Epicor's ep-*/erp-* Angular layer overrides Kendo's token-driven sizing with
hardcoded px, much of it !important (e.g. .ep-dropdown … .k-input-inner{height:40px},
.ep-short-control …{height:24px!important}, ep-… .k-grid td{height:22px;padding:0 7px}), and px
font-size throughout. So the mechanism that actually reaches Epicor's chrome is a small set of
!important rules matched to each family's real selectors, with values scaled by that family's
user factor.
Each family exposes the dimensions that genuinely move it (default 1.0 = stock; a slider at 100% emits no rule at all ⇒ a true no-op):
- Grids — Row height · Cell padding · Text size. Row height is the information-density lever:
the real row-height floor in Epicor grids is not the
<td>(its padding is 0) but the cell wrapper (.ep-grid .k-grid tr td .ep-grid-cell { line-height: 22px }, spec 0,3,1) and the tallest cell — often a fixed-height<input>in a select/edit column. So the lever scales the cell line-height/height and in-cell inputs (at ≥ that specificity,!important) alongsidetd/thheight, which actually compacts the row (live: 25→15px, ~75 rows visible vs ~46). It coexists with Kendo virtual scrolling: because the override is present atdocument_start, Kendo measures the already-compact row and sizes its height-container +translateYto match — verified by scrolling deep with real wheel events, rows fill the viewport at every depth with no blank tail (post-render CSS orzoomwould desync the spacer; only the pre-measure path is consistent). Cell padding scales the horizontal inset of data cells (.ep-grid-cell) and headers (.k-header) by the same factor (stock 7px) so columns pack tighter while staying aligned. - Buttons — Padding (
.k-buttonpadding-block) · Text size. - Text fields — Field height (text/numeric
.k-input-innerheight + the floating-label padding-top; scoped away from dropdowns) · Text size. - Dropdowns — Field height (combo/list/picker; coordinated with
.k-input-value-textheight + padding-top so the bigger value never clips itsoverflow:hiddenbox) · Text size. - Tabs — Padding (
.k-tabstrip-items .k-link) · Text size. - Field labels — Text size.
Emission mechanism. A <style id="kinetic-padding-control"> whose textContent is buildCss(state)
— plain !important rules like .k-grid td.k-table-td { height: 38.4px !important; }. (A text <style>
is sufficient here: unlike the theme tokens there are no fractional custom-prop names.) It wins via
!important + last-in-<head> source order and reverts exactly when the element is removed; it never
touches the inline <html style> attribute (where tenant theme tokens live), so an Angular theme-rewrite
can't wipe it and it can't disturb theme-control. The pure engine is buildCss(state) → CSS text; the
component-family FAMILIES table (each dim → selectors + per-prop stock base) is the single source of truth
(mirrored in popup.js, lockstep-asserted by the tests). Storage keys: componentDensity
({family→{dim→factor}}; only non-default entries stored, empty families pruned ⇒ empty map is fully
inert), textAreaAutoSizeEnabled, and fullWidthEnabled. Marker: document.documentElement.dataset.kineticPaddingControl
= {version, active, adjustments:[{family,dim,factor}], ruleCount, reasserts, textAreaAutoSize, textAreaAutoSizeCount, fullWidth}.
Live-validated (verify/padding-live-harness.mjs, CDP-9100): the mechanism proof injects the engine
CSS under a distinct probe id and asserts each family's representative rendered element moves the right
way and reverts exactly; --e2e performs an Unpacked Extension Reload
(chrome.developerPrivate.reload), reloads Education, waits for the SPA to paint, drives
componentDensity from the popup page, and asserts per-family movement + marker through the real
content-script path, then OFF → exact revert (OVERALL PASS 2026-06-05).
Evidence: .output/chrome-plugin-grid-fix/padding-verification.json.
Click the toolbar icon: an accessible on/off switch bound to gridFixEnabled (visually OFF by
default), a "Reload the Kinetic tab to apply" hint, a live status readout for the active tab
read from window.__KINETIC_GRID_FIX__, and an Advanced section (Debugger vs Runtime mechanism +
scope). Below the grid fix are the two theming switches (Disable theming, Override colors) with a
revealable 10-family color-picker panel (per-row Reset + "Reset all to stock") and a live theming
status line — these apply immediately, with no reload hint, because the content script reacts to
storage changes live. Below them a "Padding & spacing" disclosure (like the Advanced link) reveals
the Full width mode and Text area auto-size switches plus per-component-family density sliders,
grouped by family (Grids / Buttons / Text fields / Dropdowns / Tabs / Field labels) with live % readouts,
per-row Reset, "Reset all to default", and a live spacing status line — also applied live with no reload. The toolbar badge reads ON (green)
when any feature is active and the title enumerates which (e.g. grid fix on (reload Kinetic tab to apply) · theming off · custom colors · spacing adjusted).
Icons: node icons/make-icons.mjs.
- Open
chrome://extensions, enable Developer Mode. - Load unpacked → select
apps/kinetic-grid-fix-extension/. - Flip the popup toggle ON and reload the Kinetic tab (runtime hooks and any debugger rewrite act
at the next
main.jsload). Runtime mode is the no-debug default; selecting Debugger mode requests Chrome's optional debugger permission and shows the expected debugging banner while ON. - To support a non-
epicorsaas.comKinetic domain, open Advanced, add an exact host such astenant.example.comor a wildcard such as*.example.com, approve Chrome's host-permission prompt, then reload matching tabs.
Reload the extension after manifest/service-worker/content-script changes.
npm run package # check + 105 tests + build
# or just the build:
npm run build # node scripts/build-store-zip.mjs
This emits, under dist/ (gitignored):
unpacked/— the loadable unpacked dir (chrome://extensions → Load unpacked).kinetic-grid-grouping-fix-<version>.zip— upload this to the Chrome Web Store (manifest.json sits at the archive root). The build is content-reproducible (fixed mtime + sorted entries) and contains only the 14 runtime files (incl.src/theme-control.js) — no tests, nopackage.json, nopatch-transform.js. The build cross-checks everycontent_scriptsentry against the file allowlist, so a declared content script can never be silently dropped from the package.kinetic-grid-grouping-fix-<version>.zip.sha256andbuild-manifest.json(per-file + zip hashes).
The build enforces the store's hard limits (manifest description ≤ 132 chars, name ≤ 75) and
refuses to ship the falsified provider transform. Full submission guide — listing copy,
single-purpose statement, per-permission justifications, privacy disclosures, the optional debugger
review-risk callout, visibility recommendation, and reviewer notes — is in
STORE_LISTING.md. Bump version in manifest.json for every re-upload.
The leak only reproduces with the whole dataset preloaded client-side. The toolkit has a first-class command for that iterable step:
dotnet run --project src/EpicorToolkit.App -- cdp grid-download-all --page-id <id> --port 9100
It enters preload mode (grid header download button) and real-CDP-clicks "Get More" until "All N records loaded" (synthetic dispatched clicks do not register on that button). Then enable grouping (grid vertical-dots → "Toggle Grouping") and run the efficacy gate:
node apps/kinetic-grid-fix-extension/verify/leak-cycle.mjs --page-id <id> --fields Job,Part,Rev --cycles 2 --gc
Gate: post-ungroup tbodyRows ≈ 80 (vs ~4,466 unpatched). Recover a ballooned tab with a reload + GC.
The transform anchors on preserved property names, not the bundle hash or byte offsets, so it
survives rebuilds. To re-validate against a new main.*.js:
npm test(in this dir) — the unit suite includes real-bundle fixtures (verify/fixtures/binding-slice-{437c1f00,a25a4062}.js) that anchor the contract.- Confirm the anchor occurs exactly once in the new bundle:
rebind(){this.checkForPrismSkip()(the transform refuses 0 or >1 matches and serves the bundle unchanged — fail-safe, the leak simply returns until refreshed). Refresh the fixtures from the new bundle if the property-level pattern changed, and re-run the live efficacy gate above. - The
rebindoverride body (this.checkForPrismSkip()||(this.customLoading?this.loader.query( this.epGrid,this.state):super.rebind())) has been byte-identical across437c1f00anda25a4062.
npm test runs the full unit suite (136 tests). Group/ungroup fix: the corrector against a faithful
binding-directive + recreation model, patchBundleText (idempotent / fail-safe / real-bundle
fixtures), the appended hook def standalone, and the M2 binding-class wrap. The service-worker M1
contract test (verify/background-m1.test.mjs) proves the debugger Fetch path fulfills with the
patched body. Blank-grid fix (verify/grid-blank-fix.test.mjs): the watchdog against a fake-DOM grid
that models Kendo's translateY-tracks-scrollTop response (detect + correct, no false positives at
top / mid-scroll / scrolled-away, self-contained WATCHDOG_SOURCE, bounded retries, the exact
background.js M1 combine of rebind-patch + watchdog-append, and the v1.1 cases — stability gate drops a
transient blank, symmetric above-viewport detection, and diagnostics/log fields). Checkbox standardizer
(verify/grid-checkbox-style-fix.test.mjs): the style fix against a fake-DOM grid that models
getComputedStyle — adaptive modal sampling (majority wins / theme-safe), the injected scoped CSS,
disabled-state-preserving color (:not(.ep-row-rule-disabled)), diagnose() deviation counting,
SPA-re-mount re-assert, document_start bootstrap-then-refine, idempotent/fail-safe/teardown, and the
three-way M1 combine of rebind-patch + blank-append + checkbox-append.
Theming controls (verify/theme-control.test.mjs): the pure color util (hexToHsl/hslToHex
round-trips, hslStr, isValidHex), the §4.2 derivation worked example + single-token/multi-variant
families + literal-base/hue-only/hue-sat modes, stockBlock (25 !important decls, one html
host), buildCss build order (disable / override / both-on later-wins / inert→''),
validFamilies/countTokens, and the fake-DOM runtime (live storage.onChanged apply+revert, the §4.4
marker shape, host gating, idempotent install/uninstall). verify/manifest-shape.test.mjs adds the
ISOLATED content_scripts entry shape + the no-new-permissions assertion;
verify/popup-logic.test.mjs covers the popup's pure theme helpers
(nextOverrideValues/stockHex/themeStatusText/isThemeActive); verify/background-theme-coexist.test.mjs
proves a theme-key change refreshes only the badge and never attaches the debugger or reloads a tab
(with a grid-key positive control). The live mechanism is verified by verify/theme-live-harness.mjs
(CDP-9100, above).
Density / padding sliders (verify/padding-control.test.mjs): the pure per-family engine (FAMILIES
integrity incl. each dim's selectors + per-prop base, the dropdown value-text anti-clip coordination,
text-fields scoped away from dropdowns, clampFactor/isDefaultFactor/round2, buildCss scaling +
FAMILIES ordering + inert/skip-at-default cases, activeAdjustments/ruleCount) and the fake-DOM runtime
that models the <style>.textContent applier (scaled !important CSS, the marker shape, host gating,
live storage.onChanged apply+revert, full-width mode, idempotent install/uninstall). verify/popup-logic.test.mjs
adds the popup's pure density helpers (nested nextComponentDensity RMW + empty-family pruning,
countComponentAdjustments, status helpers) and a popup↔engine FAMILIES lockstep assertion;
verify/manifest-shape.test.mjs asserts the second ISOLATED content_scripts entry; the
background coexist test proves a componentDensity change is badge-only (never attach/reload). The
live per-family movement + the Unpacked Extension Reload end-to-end path are verified by
verify/padding-live-harness.mjs (CDP-9100, above).