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
57 changes: 52 additions & 5 deletions .github/scripts/audit-app-zone-shell.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,29 @@ export const REQUIRED_NAV_LABELS = ["Research", "Model", "API", "Donate"];
const TOP_SHELL_SELECTOR =
'header, nav, [data-testid*="header" i], [data-testid*="site-header" i], a, button, img, [aria-label]';

// Routes intentionally served WITHOUT a child-rendered PolicyEngine header.
// These tools are embedded under policyengine.org, which already provides the
// site header/nav, so the maintainers chose not to duplicate the PolicyEngine
// shell inside the child app. These routes are still audited for liveness
// (HTTP status, runtime errors, blank pages) — only the top-shell brand/nav
// assertion is skipped. Remove an entry to re-enforce the full shell on it.
// /uk/scotland-income-tax-reform — PolicyEngine/scotland-income-tax-reform#8
// /uk/student-loan-visualisation — PolicyEngine/student-loan-visualisation#3
// /us/obbba-household-explorer — PolicyEngine/obbba-household-by-household#240
// /uk/uc-rebalancing — PolicyEngine/uc-rebalancing
export const SHELL_BRAND_EXEMPT_SOURCES = [
"/uk/scotland-income-tax-reform",
"/uk/student-loan-visualisation",
"/us/obbba-household-explorer",
"/uk/uc-rebalancing",
];

export function isShellBrandExempt(source) {
return SHELL_BRAND_EXEMPT_SOURCES.some(
(base) => source === base || source.startsWith(`${base}/`),
);
}

export function parseArgs(argv) {
const options = {};
for (let i = 0; i < argv.length; i += 1) {
Expand Down Expand Up @@ -411,7 +434,7 @@ async function inspectTopShell(page) {
return inspectTopShellData(elements);
}

async function inspectShell(page, url, timeout) {
async function inspectShell(page, url, timeout, enforceShell = true) {
let response;

try {
Expand Down Expand Up @@ -446,6 +469,15 @@ async function inspectShell(page, url, timeout) {
};
}

if (!enforceShell) {
return {
ok: true,
status,
reason: "loaded — exempt from PolicyEngine shell brand/nav check",
exempt: true,
};
}

const { hasBrand, navHits } = await inspectTopShell(page);

if (!hasBrand) {
Expand Down Expand Up @@ -474,14 +506,20 @@ async function auditRoute(browser, route, baseUrl, timeout, allowDestinationFall
viewport: { width: 1440, height: 1000 },
userAgent: "policyengine-app-zone-shell-audit/1.0",
});
const enforceShell = !isShellBrandExempt(route.source);
const sourceUrl = resolveUrl(baseUrl, route.source);
let result = await inspectShell(page, sourceUrl, timeout);
let result = await inspectShell(page, sourceUrl, timeout, enforceShell);
let testedUrl = sourceUrl;
let usedFallback = false;

if (!result.ok && result.status === 404 && allowDestinationFallback) {
const destinationUrl = resolveUrl(baseUrl, route.destination);
const fallbackResult = await inspectShell(page, destinationUrl, timeout);
const fallbackResult = await inspectShell(
page,
destinationUrl,
timeout,
enforceShell,
);
if (fallbackResult.ok || fallbackResult.status !== 404) {
result = fallbackResult;
testedUrl = destinationUrl;
Expand Down Expand Up @@ -569,7 +607,7 @@ export async function main(argv = process.argv.slice(2), env = process.env) {
timeout,
allowDestinationFallback,
);
const mark = result.ok ? "OK" : "FAIL";
const mark = result.ok ? (result.exempt ? "SKIP" : "OK") : "FAIL";
console.log(`${mark} ${result.source}`);
console.log(` ${result.reason}`);
if (result.usedFallback) {
Expand All @@ -586,7 +624,16 @@ export async function main(argv = process.argv.slice(2), env = process.env) {
await browser.close();

const failures = results.filter((result) => !result.ok);
console.log(`\n${results.length - failures.length}/${results.length} app-zone routes have the PolicyEngine shell.`);
const exempt = results.filter((result) => result.exempt);
const enforced = results.length - exempt.length;
console.log(`\n${enforced - failures.length}/${enforced} enforced app-zone routes have the PolicyEngine shell.`);

if (exempt.length > 0) {
console.log(`\n${exempt.length} route(s) skipped the PolicyEngine shell brand/nav check (loaded OK, header intentionally omitted):`);
for (const route of exempt) {
console.log(` - ${route.source}`);
}
}

if (failures.length > 0) {
console.error("\nRoutes missing the PolicyEngine shell:");
Expand Down
18 changes: 18 additions & 0 deletions .github/scripts/audit-app-zone-shell.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
extractRoutes,
extractSitemapLocs,
inspectTopShellData,
isShellBrandExempt,
resolveDestinationForSource,
shouldAllowDestinationFallback,
sourcePathFromSitemapLoc,
Expand Down Expand Up @@ -237,3 +238,20 @@ describe("shouldAllowDestinationFallback", () => {
);
});
});

describe("isShellBrandExempt", () => {
test("exempts configured routes and their subpaths", () => {
assert.equal(isShellBrandExempt("/uk/scotland-income-tax-reform"), true);
assert.equal(
isShellBrandExempt("/uk/student-loan-visualisation/budget-impact"),
true,
);
assert.equal(isShellBrandExempt("/uk/uc-rebalancing"), true);
assert.equal(isShellBrandExempt("/us/obbba-household-explorer"), true);
});

test("does not exempt other routes or partial-name collisions", () => {
assert.equal(isShellBrandExempt("/uk/marriage"), false);
assert.equal(isShellBrandExempt("/uk/uc-rebalancing-extended"), false);
});
});
Binary file added app/public/assets/posts/uc-rebalancing.avif
Binary file not shown.
13 changes: 13 additions & 0 deletions app/src/data/apps/apps.json
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,19 @@
"date": "2026-05-19 12:00:00",
"authors": ["david-trimmer"]
},
{
"type": "iframe",
"slug": "uc-rebalancing",
"title": "UK Universal Credit rebalancing analysis dashboard",
"description": "PolicyEngine analysed the household and fiscal impact of the Universal Credit Act 2025 rebalancing package: an above-inflation uplift to the standard allowance and a fixed monthly health element",
"source": "https://uc-rebalancing.vercel.app/uk/uc-rebalancing",
"tags": ["uk", "featured", "policy", "interactives"],
"countryId": "uk",
"displayWithResearch": true,
"image": "uc-rebalancing.avif",
"date": "2026-06-01 12:00:00",
"authors": ["vahid-ahmadi"]
},
{
"type": "iframe",
"slug": "wv-sb392-tax-cut",
Expand Down
2 changes: 1 addition & 1 deletion changelog_entry.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
- bump: patch
changes:
added:
- Add QBI deduction calculator multizone at /us/qbi-calculator
- Add UK Universal Credit rebalancing analysis dashboard at /uk/uc-rebalancing
22 changes: 13 additions & 9 deletions website/src/data/appZoneRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,7 @@ export const appZoneRoutes: AppZoneRoute[] = [
},
{
source: "/us/working-parents-tax-relief-act",
destination:
"https://wptra.vercel.app/us/working-parents-tax-relief-act",
destination: "https://wptra.vercel.app/us/working-parents-tax-relief-act",
},
{
source: "/us/utah-2026-tax-changes",
Expand All @@ -84,12 +83,12 @@ export const appZoneRoutes: AppZoneRoute[] = [
},
{
source: "/us/qbi-calculator",
destination:
"https://qbi-visualizer.vercel.app/us/qbi-calculator",
destination: "https://qbi-visualizer.vercel.app/us/qbi-calculator",
},
{
source: "/us/watca",
destination: "https://working-americans-tax-cut-act-one.vercel.app/us/watca",
destination:
"https://working-americans-tax-cut-act-one.vercel.app/us/watca",
},
{
source: "/us/california-wealth-tax",
Expand Down Expand Up @@ -130,7 +129,8 @@ export const appZoneRoutes: AppZoneRoute[] = [
},
{
source: "/uk/marriage",
destination: "https://marriage-zeta-beryl.vercel.app/us/marriage?country=uk",
destination:
"https://marriage-zeta-beryl.vercel.app/us/marriage?country=uk",
deepDestination:
"https://marriage-zeta-beryl.vercel.app/us/marriage/:path*?country=uk",
},
Expand Down Expand Up @@ -175,6 +175,10 @@ export const appZoneRoutes: AppZoneRoute[] = [
destination:
"https://uk-public-services-imputation.vercel.app/uk/public-services-spending",
},
{
source: "/uk/uc-rebalancing",
destination: "https://uc-rebalancing.vercel.app/uk/uc-rebalancing",
},
{
source: "/us/aca-calc",
destination: "https://aca-calc.vercel.app/us/aca-calc",
Expand All @@ -200,8 +204,7 @@ export const appZoneRoutes: AppZoneRoute[] = [
},
{
source: "/us/state-eitcs-ctcs",
destination:
"https://us-state-eitcs-ctcs.vercel.app/us/state-eitcs-ctcs",
destination: "https://us-state-eitcs-ctcs.vercel.app/us/state-eitcs-ctcs",
},
{
source: "/us/2024-election-calculator",
Expand Down Expand Up @@ -233,7 +236,8 @@ export const appZoneRoutes: AppZoneRoute[] = [
},
{
source: "/us/ads-dashboard",
destination: "https://policyengine-ads-dashboard.vercel.app/us/ads-dashboard",
destination:
"https://policyengine-ads-dashboard.vercel.app/us/ads-dashboard",
},
{
source: "/us/ai-inequality",
Expand Down
Loading