Repository navigation
Build the Tools menu from apps.json: the main app, then current-law calculators - #1424
Merged
Merged
Conversation
…alculators A tool now means a calculator that works for any household under current law. apps.json marks each one with a toolsMenu label, and both headers and the homepage read that flag instead of featuredTools.json. - Tools menu: PolicyEngine app first, then the flagged tools by label. US: CliffWatch, Coverage Compass, GiveCalc, Marriage calculator, QBI deduction calculator, SPM threshold calculator, TANF calculator. UK: Marriage calculator, Student loan calculator, UK CliffWatch. "All interactive tools" goes: the menu is complete by rule. - The OBBBA explorer and the bill tracker leave the menu and move to a "Featured research" section on the US homepage. - The homepage "Tools" section shows the menu's entries, on the UK homepage too. - apps.json gains entries for the SPM and QBI calculators, which were zones with no registry entry, and the tracker's description now matches what the live tracker shows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…mepage sections - toolsMenu: the US and UK menus Max ruled on; for any country id, the main app then exactly the flagged apps by label, and no menu without tools; links stay in-country and hit proxied zones; a differential test that the calculator header's lookup returns the same tools in the same order. - featuredResearch: the tracker then the OBBBA explorer, with their apps.json copy and existing images, never also in the Tools menu. - Both headers render the menu in order, desktop and mobile, as full page loads; ca/ng/il get none. - Homepage: a card per menu row in order; featured research cards; a mocked-apps.json test that new flagged tools appear in label order with their own copy; the hero button and the menu's main-app row agree. - apps.json: toolsMenu is exactly a short, trimmed label. - The OBBBA and CliffWatch research-search checks move to the search corpus tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he top The homepage tool cards show each entry's apps.json description, so the four that began "Interactive calculator showing" or "Interactive tool to" now start with the verb, and all end with a period. The claims are unchanged. Featured research images anchor to the top, so the tracker's screenshot keeps its title bar. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review fixes for #1424: - The homepage tool and research cards' focus ring read var(--color-primary-500), which the tw prefix renames to --tw-color-primary-500, so keyboard focus drew nothing. They now get a 2px primary-500 outline, and only the hover lift animates, so the outline appears at once. The blog cards' copy of the rule never matched (focus-within on a div inside the focused link) and is removed. - A guard test fails on any unprefixed read of a prefix-only theme variable, and the cards' focus classes are tested. - In the calculator's desktop header, dropdown rows with absolute links are now anchors, so modifier-clicks and "Open link in new tab" work. In-app rows stay buttons that route client-side. - A toolsMenu label is now bounded by its entry's title instead of a fixed 28 characters. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
12 tasks done
…ly in-app paths as router links - theme-variables.test.ts follows globals.css's imports, Tailwind's default theme included, and flags var(--x) and the -(--x) shorthand for any prefix-only variable, across lines, outside comments. A property test checks the scan itself. - alert.tsx read --spacing unprefixed; it now uses --spacing(4). - The card focus test requires a 2px outline with 3:1 contrast against the section behind it, and no transition that animates the outline. - NavItem treats "//host" and "/\host" as other sites, so those rows are real anchors; tests check that an anchor row never calls the router and that modifier-clicks reach the browser. - The toolsMenu label no longer has to be shorter than the app's title. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d keep test classes out of the CSS - The theme-variable guard scans globals.css whole, matches type-hinted shorthand reads, takes .ts/.tsx comments from TypeScript's parser, and checks every prefix-only name in every read shape. - The focus test rejects outline-hidden, catches a duration with no transition-property, reads arbitrary properties, and checks its rule against Tailwind's compiled CSS. - isInternalHref rejects tabs and line breaks, which the URL parser drops. - The modifier-click test adds its listener after the menu opens, and checks that nothing navigates another way. - Tests build class names at run time, and a new test checks that they add no rules to the site's CSS. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The comment finder reads the end-of-file token's trivia, so a file-final JSDoc no longer hides the comments before it. - The focus rule and its compiled-CSS oracle count CSS-wide keywords as all properties, and a nonzero delay as starting a transition. - The test-sources check compares every declaration in its context, so a test that names only a theme variable fails it. - The modifier-click test counts window.open as navigation and checks the URL is unchanged, which catches history.pushState. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The focus rule now treats a transition-property value as safe only when it is none or a comma-separated list of properties known to leave the outline out. Any other value, including one the browser rejects and so replaces with `all`, counts as including it. The rule strips the important modifier, reads -webkit- aliases, and compares values without regard to case; arbitrary property names stay as written, because Tailwind emits nothing for upper-case ones, which the differential checks. The dropdown test now runs whatever the click deferred to a timer or a microtask, checks that the row's click is the only one reaching the document, and counts form submissions as navigations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ength Tailwind emits an arbitrary property only when its name starts with `-` or a lower-case letter, and keeps the rest of the name as written; the browser reads the name case-insensitively. The focus rule now applies that gate, lower-cases the name, reads -webkit- aliases in any case, and counts a name with a CSS escape as a shorthand. The differential gains four mixed-case classes. The dropdown test now also requires that the click added no session-history entry, which catches a URL change a timer later undoes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Focus rule: an arbitrary transition property with a `/` modifier counts as animating (`/100` keeps the value; other modifiers make a colour the browser drops, keeping `all`), and a custom property never does. Add the cases, and two classes to the differential (53). - Comments: say that the differential hands each class to Tailwind's compiler directly, and that the site's scanner extracts none of the mixed-case or escaped names, so there the rule only over-counts. - NavItem: check the URL right after the click too, so a replaceState or location.replace that a timer undoes fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The focus rule's modifier match stopped at the first `)`, so a variable modifier with a fallback, such as `[transition-property:all]/(--x,var(--y))`, read as no transition while Chrome animates the outline. Any `/…` now counts (the round-8 review's patch), with two variable-modifier cards. The comments say which modifiers keep the value, emit nothing or mix it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
|
Merged with
|
MaxGhenis
added a commit
that referenced
this pull request
Oct 26, 2026
#1424 replaced featuredTools.json with apps.json's toolsMenu entries and getToolsMenuEntries. The More and phone menus now list that accessor's tools after Research, in its order. The headers' main-app row is left out because this bar is the main app, and the "All interactive tools" row goes, as it did in the headers. The tests follow the accessor. A mocked accessor keeps the order and no-tools cases covered whatever apps.json marks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5 of 7 tasks
MaxGhenis
added a commit
to PolicyEngine/policyengine-ui-kit
that referenced
this pull request
Oct 26, 2026
PolicyEngine/policyengine-app-v2#1424 replaced the hand-picked list with a rule: the PolicyEngine app first, then every calculator that works for any household under current law, sorted by label, from the `toolsMenu` labels in apps.json. Follow it. - PolicyEngineTools.ts holds each country's tools (slug and label), the main app's label and its default URL. US: CliffWatch, Coverage Compass, GiveCalc, Marriage calculator, QBI deduction calculator, SPM threshold calculator, TANF calculator. UK: Marriage calculator, Student loan calculator, UK CliffWatch. - The Tools menu lists the main app at <appUrl>/<country>, then the tools. "All interactive tools" is gone, as on policyengine.org. - getPolicyEngineNavItems takes an optional app URL; PolicyEngineHeader takes an appUrl prop. - The parity check applies app-v2's rule to apps.json on main and also compares the main app's label and URL; it retries a failed connection once. The workflow is renamed tools-menu-parity.yml. - Exports renamed: policyEngineTools, getPolicyEngineTools, getPolicyEngineAppUrl, POLICYENGINE_APP_LABEL, DEFAULT_POLICYENGINE_APP_URL, PolicyEngineTool. - Tests for the new rule; all 50 mutants are killed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch was successfully deployed
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.
This branch includes #1425, merged 2026-10-26. Vercel's website builds restored webpack's cache from an earlier deployment, and with it the CSS could miss new Tailwind classes; #1425 turned that cache off and has the details. This PR adds seven such classes:
aspect-[16/9],lg:grid-cols-4,object-top,transition-[box-shadow,translate], and threefocus-visible:outline-*classes. The preview at 026c73d lacked five of them. The preview at e3b09a3 has all seven (Verification).Why
#1421 added a hand-picked Tools menu: CliffWatch, the OBBBA explorer and the state legislative tracker. Max asked why those three, out of the many interactives PolicyEngine has. He then ruled where the line falls:
Change
toolsMenu: { "label": "…" }field; the label is the short name the menus show. Both headers and the homepage read that flag.featuredTools.jsonand its symlink are gone.app.policyengine.org/<country>, the same link as the homepage's "Enter PolicyEngine" button), then the tools sorted by label.research?types=interactivemixes tools with dated analyses.<a>. It used to be a<button>whose click setwindow.location.href. So Cmd/Ctrl-click opened the link in the same tab, and the row had no "Open link in new tab". This covers Tools and Model, and About in calculator mode. Rows with calculator paths stay buttons that route client-side. The mobile menu already used anchors.isInternalHref) used to accept anything starting with/. It now requires exactly one leading slash, because//hostand/\hostname another host, and no tab or line break: the URL parser drops those, so/<tab>/hostnames another host too. No current link has any of these shapes.var(--color-primary-500). Tailwind'stwprefix renames theme variables to--tw-*, and the compiled CSS defines no--color-primary-500, so the shadow drew nothing whileoutline-nonehid the browser's ring. Keyboard focus on a tool card was invisible; the old US section had the same bug. The cards now get a 2pxprimary-500outline with a 2px offset, and their transition covers only the hover lift (box-shadow,translate), so the outline appears at once instead of fading in from a 3px dark ring.focus-withinon the div inside the card's link. Focus lands on the link, which wraps that div, so the rule never matched. Those cards have always shown the browser's default ring and still do. I deleted the dead rule.alert.tsxhad the same kind of bug. Its icon column wascalc(var(--spacing)*4), but Tailwind's prefix emits the default theme's spacing as--tw-spacing, so that column was undefined. It now uses--spacing(4), which compiles tocalc(var(--tw-spacing) * 4); I checked by compiling it with the repo's Tailwind 4.2.1. No page draws it yet: the only<Alert>, on the API status page, has no icon./us/spm-calculator,/us/qbi-calculator) with no registry entry. They aren't listed with research (displayWithResearchunset, like GiveCalc's).Which interactives are tools
I opened each candidate on policyengine.org on 2026-10-25.
Invariants (tested)
For every country C:
<calculator>/C), then exactly the apps.json entries for C that have atoolsMenulabel, sorted by label. This is a property test over arbitrary country ids,__proto__andconstructorincluded (website/src/__tests__/lib/toolsMenu.test.ts). The US and UK lists above are pinned literally./C/<slug>, a path the website proxies to a zone (appZoneRoutes).app/src/utils/toolsMenu.ts) returns the same tools in the same order as the website's, for arbitrary country ids. Both name the main app the same way, and both read one apps.json (the website's copy is a symlink).next/linkis mocked to tag client links). In the calculator, each desktop row is an<a>with the same absolute URL as its mobile row.toolsMenuvalue is exactly{ label }: trimmed and non-empty.NavItem.test.tsx).//host,/\host,/<tab>/host,/<line feed>/host,/<carriage return>/host,#top,mailto:, relative paths and calculator paths, plus rows that only have a callback. A calculator path with a tab in it is left to the browser as a link.preventDefaultor stops it. The URL hasn't changed right after the click. Once the timers (through the global timer functions), animation frames and microtasks the click queued have run, the router hasn't been called, jsdom has reported no navigation,window.openor form submission, and neither the URL nor the history length has changed. That rules outlocation.assign,replaceorreload, settinglocation.hrefor its hash,window.open,history.pushStateorreplaceState(even when a timer later puts the URL back), submitting a form, clicking a second link, and any of these deferred to a timer, an animation frame or a microtask. A timer started through a reference tosetTimeouttaken before the test fakes timers is outside this. jsdom can't seehistory.back(), a reload by settinglocation.hrefto itself, or the Navigation API, so those aren't covered. The click also closes the dropdown. A calculator-path row is a button that routes client-side.outline-noneoroutline-hidden. Both setoutline-style: none, whichfocus-visible:outline-2keeps.none, or a comma-separated list of properties known to exclude it (box-shadow,translate,transform,scale,rotate,opacity), in any case. Any other value counts as including it:transition,transition-allandtransition-colors;all, anoutline…property or a variable; a CSS-wide keyword, since the initial value isall; and a value the browser rejects, such asbox-shadow translate, since the browser then keepsall. Anytransitionshorthand counts too, and so does an arbitrary transition property with any/modifier. One of 100% (/100,/[1],/[100%]) leaves its value as written; a variable one, such as/(--x), emits the value and acolor-mix()override; any other emits nothing or acolor-mix()the browser drops (fortransition-property, that keepsall). The rule counts them all, so here it can only over-count. An arbitrary custom property, such as[--card\.gap:1], sets no transition. The important modifier (!transition-all,transition-all!) and the-webkit-aliases, in any case, count as the plain utility and properties do.all, a duration or a delay needs a transition-property utility without a variant that leaves the outline out. A delay alone starts a transition: CSS Transitions starts one when max(duration, 0s) + delay is above zero, so the outline would appear late. Time units are case-insensitive, sodelay-[100MS]counts.[transition:box-shadow_1s], because the rule counts every shorthand as animating, which is intended. Tailwind emits an arbitrary property only when its name starts with-or a lower-case letter, and keeps the name as written; the browser reads it case-insensitively. So[TRANSITION-DELAY:100ms]compiles to nothing, and[transition-Delay:100ms]to a delay, and the test checks both. That is the compiler handed each class directly, as@source inlinedoes. On the site, Tailwind's scanner finds the classes in source files, and it extracts none of the test's mixed-case or escaped transition names (I checked all 11), so there they compile to nothing and the rule's reading only over-counts. A name with a CSS escape, such as[transition\-delay:100ms], counts as atransitionshorthand. The oracle doesn't decode escapes, so that one is checked on a card, not in the differential.theme-variables.test.ts). No website source reads a theme variable that exists only with thetwprefix.@themeblock, whatever its modifiers, in globals.css and every stylesheet it imports. That includes Tailwind's default theme, which is why--spacingcounts.@themevalues included. Tailwind doesn't prefix a read inside an@themevalue, so an alias such as--color-ring: var(--color-primary-500)reads an undefined variable too.var(--x), Tailwind's-(--x)shorthand, and the shorthand with a type hint,-(color:--x), even when a read spans lines.//or/*in a string, a template literal, a regular expression, JSX text or a JSDoc tag isn't one. That includes the comments after a file's last statement when the file ends with a JSDoc, which TypeScript files under the end-of-file token. In CSS, a comment is a/* … */outside a string.@themeis allowed, such as ui-kit's--font-sans.--spacing(4)is not flagged.tailwind-test-sources.test.ts). Tailwind scans all ofwebsite/, tests included. A compile of globals.css withsrc/__tests__and one without emit the same declarations, each with the rules and at-rules around it. So a test that names only a theme variable, which adds a:rootdeclaration and no rule, fails it too. While I wrote this, its own doc comment named a theme variable, and the check caught it. At 026c73d the focus test wrote class names out and added three classes to the CSS:tw:transition-[box-shadow,all],tw:transition-[outline-color]andtw:motion-safe:transition-all. Tests now build class names at run time.The lookup is written twice because the calculator can't import website code. The differential test imports the calculator's module into the website suite to compare the two directly.
Mutant checks, each run against the suites in a cloned tree. Round 1 (
mutants4.py): all 37 fail at least one test. I re-ran the 17 that touch code changed in round 2 at 026c73d, and all 17 still fail.next/link, hardcoded card copy, featured research reversed, its copy taken from the title, a wrong image path, US research featured on every country, and the hero button linking elsewhere than the menu's first row. The last survived at first because no UK entry shares a featured US slug. A mocked UK twin now kills it.toolsMenu, a 50-character label (caught by the pinned US list and the dropdown width test), and a changed label (caught by the pinned US list).transition-allor animating the outline color, the blog cards' dead rule restored, and globals.css readingvar(--color-border).Round 2 (
mutants5.py): 19 of 22 fail at least one test. The other 3 are changes the tests should allow, and they pass.alert.tsxreading--spacingunprefixed again; abg-(--color-primary-50)shorthand; a default-theme colour read throughvar(); avar(split across lines in globals.css; a variable from an@theme inlineblock read unprefixed.@import, strips every//(URLs included), misses the-(shorthand, or misses reads that span lines.transitionortransition-colors; aprimary-300outline (under 3:1); a 1px outline; the check treatingtransition-colorsas safe.isInternalHrefback tostartsWith('/'), or missing/\host; an anchor row that also routes client-side, prevents the browser default, or leaves the dropdown open.primary-600outline; a label longer than its app's title.Round 3 (
mutants7.py): all 31 fail at least one test.bg-(color:--color-primary-50); an@themealias--color-ring: var(--color-primary-500); a read afterbg-[url(//…)]on the same line; a read below a// … /us/*.comment. Plus the plainbg-(--color-primary-50)control.outline-none; the transition list dropped withduration-300kept;outline-hidden;focus-visible:outline-transparent;[transition-property:outline-color];hover:transition-all.transition-colorslisted as safe;duration-initialcounted as a duration.window.location.assign;isInternalHrefaccepting tabs and line breaks, or only carriage returns.Round 4 (
mutants9.py): all 19 mutants fail at least one test, the 2 allowed changes pass, and 3 runs with part of a fix removed show which assertion does the killing.transition-[initial],[transition-property:unset],transition-[inherit]ortransition-[revert-layer]with a duration; a card with a delay alone, asdelay-100or[transition-delay:50ms]; a test file naming a theme variable, a class or an animation; an anchor row that also callswindow.open(href, '_self'),location.assign, setslocation.hrefor callshistory.pushState.delay-0counted as a delay.window.opensurvives; without the URL check,history.pushStatesurvives.mutants7c.py, its anchors updated for the renamedsetsTiming) all still fail.Round 5 (
mutants-r6.py, the round-5 review's mutants against the fixed tests): 21 of its 26 mutants fail at least one test. Of the other five, two change nothing and three are beyond jsdom. S1 and S3 name a class and a variable the site's own code already uses, so the CSS is the same. N20, N25 and N26 navigate through the Navigation API, by settinglocation.hrefto itself, and byhistory.back().transition-[box-shadow_translate],transition-[box-shadow,none],!transition-all,transition-all!,!delay-100,transition-[ALL],[-webkit-transition:all_300ms]and[-webkit-transition-delay:100ms];replaceState,reload,window.open(href)orlocation.replace, deferslocation.assignor a router push to a timer or a microtask, clicks a second link, or submits a form.-webkit-alias in the rule, in its timing or in the oracle; list names compared case-sensitively;nonenot special-cased; the rule's or the oracle's zero test case-sensitive; arbitrary property names lower-cased; and the round-5 denylist back.location.assign(timer and microtask) and the deferred router push survive. Without the click-target check, the second link survives. With the old message filter, the form submission survives.mutants7c-r6.py,mutants9-r6.py, anchors updated for the rewritten rule): all 31 and all 19 still fail, the 2 allowed changes pass, and the 3 runs with part of a fix removed survive, as before.Round 6 (
mutants-r7b.py, the round-6 review's mutants against the fixed tests): all 17 of its mutants fail at least one test, and the 3 allowed changes pass.[transition-Delay:100ms],[transition-Duration:300ms],[transitioN:all_300ms],[-webkit-Transition:all_300ms],[-WEBKIT-transition:all_300ms],[transition-Property:all]after the safe list,[-webkit-transition-Delay:100ms]and[transition\-delay:100ms];pushStateand has a timer put the URL back; this one also fails the 5795ac0 test;MessageChannelorpostMessage; a click on a detached second link, or on an attached one that stops propagation.[transition-Delay:100ms]or[transition-Property:all], and a menu that closes a tick late (which the 5795ac0 test failed).mutants-r7a.py): all 5 fail a test. They are: no first-letter gate; arbitrary names not lower-cased; no escape guard; arbitrary names keeping a-webkit-prefix; and timing ignoring arbitrary properties.pushStatethe timer undoes survives.mutants7c-r7.py,mutants9-r7.py,mutants-r7a.py): round 5's 21 still fail, its 2 no-op changes pass, and the 3 beyond jsdom survive. The 7 checks of its fix parts that still apply fail, and its 5 runs with part of a fix removed survive, as before. Round 4's 19 and round 3's 31 still fail, round 4's 2 allowed changes pass, and 2 of its 3 runs with part of a fix removed survive. Round 4'shistory.pushStaterow without the URL check is now killed by the history-length check; it survives only with both checks removed.Round 7 (
mutants-r8-review.py, the round-7 review's mutants against the fixed tests): all 11 of its mutants fail at least one test, and the 5 allowed changes pass./modifier:[transition:all_1s]/100,[transition-delay:100ms]/100,[transition-duration:300ms]/100,[-webkit-transition:all_1s]/100and[transition-delay:100ms]/[1], and, after the safe list,[transition-property:all]/100,[transition-property:all]/100!and[transition-property:all]/[100%];replaceStateorlocation.replace('#x')and has a timer put the URL back, or callspushStateand has a timer callhistory.back(). The first two also fail the 5795ac0 test, which checked the URL before running timers.[transition:all_1s]/100,[transition-property:all]/100,[transition-property:box-shadow]/50or[transition-delay:100ms]/[1], and a card with[color:red]/50.replaceStatethe timer undoes survives.setTimeoutcaptured before the test fakes timers. Its real timer fires during a later test, so which tests fail varies by run (2 of the 4 link-row tests here, 3 in round 7). Invariant 11 doesn't claim it.mutants-r8b.py,mutants-r8a.py,mutants9-r8.py,mutants7c-r8.py, anchors updated): every result is as before. Round 6's 17 fail and its 3 allowed changes pass; itspushStaterow survives only with both the history-length and the after-click URL checks removed. Round 5's 21 and the 12 checks of rounds 5 and 6's fix parts fail, its 2 no-op changes pass, and the 3 beyond jsdom and its 5 runs with part of a fix removed survive. Round 4's 19 and round 3's 31 fail, round 4's 2 allowed changes pass, and 2 of its 3 runs with part of a fix removed survive; the third survives only with the URL, after-click URL and history-length checks all removed.Round 8 (
mutants-r9.py: the round-7 set plus the round-8 review's cases, against the fixed tests). The clone predates a comment-only edit in 4e6742b.[transition-property:all]/(--x,var(--y)),/(--x)or/[var(--x)]after the safe list. All 3 fail a test.replaceStaterun without the after-click check survives. The timer captured before the test fakes timers is still unscored (it failed in this run).mutants-r9b.py,mutants-r9a.py,mutants9-r9.py,mutants7c-r9.py): every result is as in round 7.Verification
NavItem.test.tsx(19 tests), ESLint and Prettier on the changed file; the full suite runs in CI. At 5795ac0: full suite 456 files / 5,881 tests (10 skipped). At fe60492:bun run checkand Stylelint/uk: four tool cards, no featured research, and no link tocliffwatch,obbbaorbill-tracker.solid 2px rgb(49, 151, 149), offset 2px, from the first frame (no running animations). A blog card keeps the browser's defaultautoring./us/spm-calculator./uk/reports: the legacy header's Tools dropdown lists "PolicyEngine app", "Marriage calculator", "Student loan calculator" and "UK CliffWatch", each 40 px.box-shadow, translateover 0.3 s.object-position: 50% 0%.translate: 0px -2pxand gets the larger shadow. A reading taken during the move gave -0.52px, so the lift animates.solid 2px rgb(49, 151, 149), offset 2px, with no running animations.//hostrows, a test that anchor rows never route, the Cmd/Ctrl wording, the label rule and the tracker's copy. All eight are addressed in 026c73d; the tracker's title stays, for the reason under Change.@themeand type-hinted reads, comment stripping that blanked real code, the scan sampling its names, a stale flag in the modifier-click test, two ways to break the outline, tab and line-break hrefs, and the preview item's wording. All seven are addressed in 5b2270b.window.open. All four are fixed in the tests in 5795ac0, and the differential's claim was narrowed to Tailwind's half.noneinside a list, the important modifier, upper-case keywords and the-webkit-aliases. And the navigation check missed a click deferred to a timer or a microtask, a second link clicked, and a form submission. Both are fixed in 4ccc029: the rule is now an allowlist, and the NavItem test runs the click's timers and checks every click that reaches the document. Invariants 11 and 12 now say what they cover.pushState's URL change. Both are fixed in 144284c: the rule reads arbitrary names case-insensitively, as the compiler emits them and the browser parses them, and the NavItem test checks the history length./100modifier, which the scanner does extract (as it does every modified class the test uses) and Tailwind ships with its value as written, read as no transition; and an escaped custom property read as a shorthand. Third, areplaceStatethat a timer undoes still passed the anchor-row test. All three are fixed in 54478b5, the code fixes being the review's patches.), so a variable modifier with a fallback,[transition-property:all]/(--x,var(--y)), read as no transition while Chrome 152 animates the outline; invariant 12 misdescribed/[1]and/[100%]; and the round-7 entry called the escaped name mixed-case. All three are fixed in 4e6742b, the code fix being the review's patch./[calc(100%)], which mixes. That comment is left as is: the rule counts every/…modifier, so neither case changes what the test checks.Notes
HomeBlogPreview,ResearchClient); other pages now load it too. The calculator gains it as well. I didn't measure built chunk sizes.Not in this PR
FlagshipTopBar(the calculator's v3 top bar) and ui-kit's shared nav get their tools from their own PRs, Add featured interactive tools to the flagship top bar #1422 and Add a Tools menu to the PolicyEngine header, built by policyengine.org's rule policyengine-ui-kit#39. Once this merges, those should readtoolsMenufrom apps.json instead offeaturedTools.json./toolsindex page (d1990) would be one more link target built fromgetToolsMenu.axiom: n/a: website navigation, homepage and registry data only
🤖 Generated with Claude Code