Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
9 changes: 4 additions & 5 deletions scripts/mutation/equivalent-mutants.txt
Original file line number Diff line number Diff line change
Expand Up @@ -928,11 +928,10 @@ src/shared/db/attendees/select.ts:383:26 ?? → || # query.join: AttendeeJoin
# placeholder-free message (locked by the "fast path renders every
# placeholder-free message exactly as full ICU would" test), so a mutant that
# makes needsIcu always-true is a pure performance change with no observable effect.
src/shared/i18n.ts:160:16 { → "" # needsIcu `msg.includes("{")` → `includes("")`: `includes("")` is always true, so needsIcu returns true for every message and all copy is routed through ICU. ICU output equals the fast-path string for plain copy, so no input changes t()'s output — only whether a formatter is built
src/shared/i18n.ts:160:37 ' → "" # needsIcu `msg.includes("'")` → `includes("")`: same as above — always-true makes every message use ICU, which is output-identical to the fast path; unobservable
src/shared/i18n.ts:166:37 ?? → || # resolveMessage `locales[locale]?.[key] ?? locales.en?.[key]`: `??` and `||` differ only if the left side is "" (falsy but not nullish). No catalog message is "" (verified: 0 empty values), and only "en" is ever registered so left and right are the same object anyway — the left is a non-empty string or undefined on every reachable input, where `??` and `||` agree
src/shared/i18n.ts:105:19 → "mutated" # buildReplacer `const [from = ""` default: `pair.split("|")` always returns at least one element, so element 0 (`from`) is always present and the default is never used — dead code, unobservable
src/shared/i18n.ts:240:21 → "mutated" # parseAcceptLanguage `const [lang = ""` default: `part.trim().split(";")` always returns at least one element, so `lang` (element 0) is always present and the default is never used — dead code, unobservable
src/shared/i18n.ts:224:16 { → "" # needsIcu `msg.includes("{")` → `includes("")`: `includes("")` is always true, so needsIcu returns true for every message and all copy is routed through ICU. ICU output equals the fast-path string for plain copy, so no input changes t()'s output — only whether a formatter is built
src/shared/i18n.ts:224:37 ' → "" # needsIcu `msg.includes("'")` → `includes("")`: same as above — always-true makes every message use ICU, which is output-identical to the fast path; unobservable
src/shared/i18n.ts:167:19 → "mutated" # buildReplacer `const [from = ""` default: `pair.split("|")` always returns at least one element, so element 0 (`from`) is always present and the default is never used — dead code, unobservable
src/shared/i18n.ts:306:21 → "mutated" # parseAcceptLanguage `const [lang = ""` default: `part.trim().split(";")` always returns at least one element, so `lang` (element 0) is always present and the default is never used — dead code, unobservable

# listing edit panel (features/admin/listing-page-management-panels.ts) — group-id fallback.
src/features/admin/listing-page-management-panels.ts:46:39 ?? → || # `selectedGroupIds ?? ctxData.selectedGroupIds`: selectedGroupIds is number[]|undefined — a submitted array (even []) is always truthy, so both operators return it unchanged, and undefined maps to the stored ids under both. No input distinguishes them (same class as attributes.tsx:151:61)
Expand Down
7 changes: 3 additions & 4 deletions scripts/mutation/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,11 +228,10 @@ const runTestBatch = (
"--no-check",
"--allow-all",
"--parallel",
// Load the fast `toContain` override in every isolate — the barrel that
// used to pull it into each test file's graph is gone, so without this
// mutation batches fall back to the slow matcher. See test-harness.ts.
// Match the standard harness: install the fast matcher and preload the
// complete catalog for tests that render templates without routing.
"--preload",
"./test/test-utils/fast-expect.ts",
"./test/test-utils/preload.ts",
"--v8-flags=--expose-gc",
...batch,
],
Expand Down
11 changes: 4 additions & 7 deletions scripts/test-harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,11 @@ const buildDenoTestArgs = (
"--allow-sys",
"--allow-ffi",
"--parallel",
// Install the fast `toContain` override (test/test-utils/fast-expect.ts) in
// every test isolate. Deno runs each test file in its own isolate — and
// spreads them across worker processes under --parallel — so a side-effect
// import in one file never reaches the others. --preload runs the module
// before every test module, giving the whole suite the fast matcher without
// each file having to import it. See fast-expect.ts for why it exists.
// Install shared test state in every isolate before test-module imports:
// the fast `toContain` override and the full locale catalog used by direct
// template tests that do not enter through a production route loader.
"--preload",
"./test/test-utils/fast-expect.ts",
"./test/test-utils/preload.ts",
// Expose `globalThis.gc` so the test DB layer can reclaim libsql's
// file-descriptor leak (a fresh client per test + every interactive
// transaction leaks an fd that close() never releases — only GC does). Under
Expand Down
13 changes: 4 additions & 9 deletions src/features/admin/guide.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
} from "#shared/config.ts";
import { settings } from "#shared/db/settings.ts";
import { EMAIL_PROVIDER_LABELS, getHostEmailConfig } from "#shared/email.ts";
import { ensureGuideMessages } from "#shared/guide-messages.ts";
import {
adminFormattingHelpPage,
adminGuidePage,
Expand All @@ -20,9 +19,7 @@ import {
/**
* Handle GET /admin/guide
*/
const handleAdminGuideGet = sessionPage(async (session) => {
// The guide's ~120KB of translations are loaded on demand, not at cold boot.
await ensureGuideMessages();
const handleAdminGuideGet = sessionPage((session) => {
const hostEmail = getHostEmailConfig();
return adminGuidePage(session, {
builderEnabled: isBuilderEnabled(),
Expand All @@ -45,11 +42,9 @@ const handleAdminGuideGet = sessionPage(async (session) => {
* markdown field hints. Content roles (incl. editors) may open it; it shows only
* the editor-safe Text Formatting section, never the full staff guide.
*/
const handleAdminFormattingGet = contentPage(async (session) => {
// The formatting help renders a guide section, so it needs the guide bundle.
await ensureGuideMessages();
return adminFormattingHelpPage(session);
});
const handleAdminFormattingGet = contentPage((session) =>
adminFormattingHelpPage(session),
);

/** Guide routes */
export const adminHandlers = handlersFor("guide")({
Expand Down
112 changes: 72 additions & 40 deletions src/features/admin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@
*/

import { once } from "#fp";
import { withMessageGroups } from "#i18n";
import {
ADMIN_BASE_MESSAGE_GROUPS,
type AdminAreaMessageGroup,
} from "#locales/groups.ts";
import type { MessageGroup } from "#locales/manifest.ts";
import { routeMapForArea } from "#routes/admin/handlers.ts";
import { createRouter } from "#routes/router.ts";
import type { PathMethodRoute } from "#routes/types.ts";
Expand All @@ -30,36 +36,47 @@ type HandlerMap = Record<string, (...args: never[]) => unknown>;
/** One admin area's lazy route-ID handlers. */
export type AdminAreaLoader = {
load: () => Promise<HandlerMap>;
messageGroups: readonly AdminAreaMessageGroup[];
};

/** Declare an area without importing its handlers until that area is requested. */
const area = <M extends { adminHandlers: HandlerMap }>(
load: () => Promise<M>,
): AdminAreaLoader => ({ load: async () => (await load()).adminHandlers });
messageGroups: readonly AdminAreaMessageGroup[] = [],
): AdminAreaLoader => ({
load: async () => (await load()).adminHandlers,
messageGroups,
});

// Import specifiers stay literal so esbuild can still bundle every target.
export const ADMIN_AREA_LOADERS: Record<AdminAreaId, AdminAreaLoader> = {
apiKeys: area(() => import("#routes/admin/api-keys.ts")),
attendeeNotes: area(() => import("#routes/admin/attendee-notes.ts")),
attendeeNotes: area(
() => import("#routes/admin/attendee-notes.ts"),
["notes"],
),
attendeeRefunds: area(() => import("#routes/admin/attendee-refunds.ts")),
attendees: area(() => import("#routes/admin/attendees.ts")),
attendees: area(() => import("#routes/admin/attendees.ts"), ["notes"]),
attributes: area(() => import("#routes/admin/attributes.ts")),
auth: area(() => import("#routes/admin/auth.ts")),
backup: area(() => import("#routes/admin/backup.ts")),
builder: area(() => import("#routes/admin/builder.ts")),
backup: area(() => import("#routes/admin/backup.ts"), ["backup"]),
builder: area(() => import("#routes/admin/builder.ts"), ["builder"]),
builtSites: area(() => import("#routes/admin/built-sites.ts")),
bulkActions: area(() => import("#routes/admin/bulk-actions.ts")),
bulkEmail: area(() => import("#routes/admin/bulk-email.ts")),
bulkActions: area(
() => import("#routes/admin/bulk-actions.ts"),
["bulk-actions"],
),
bulkEmail: area(() => import("#routes/admin/bulk-email.ts"), ["bulk-email"]),
calendar: area(() => import("#routes/admin/calendar.ts")),
catalogTransfer: area(
() => import("#routes/admin/catalog-transfer/routes.ts"),
),
contactHistory: area(() => import("#routes/admin/contact-history.ts")),
dashboard: area(() => import("#routes/admin/dashboard.ts")),
debug: area(() => import("#routes/admin/debug.ts")),
deliveries: area(() => import("#routes/admin/deliveries.ts")),
groups: area(() => import("#routes/admin/groups.ts")),
guide: area(() => import("#routes/admin/guide.ts")),
debug: area(() => import("#routes/admin/debug.ts"), ["debug"]),
deliveries: area(() => import("#routes/admin/deliveries.ts"), ["deliveries"]),
groups: area(() => import("#routes/admin/groups.ts"), ["groups"]),
guide: area(() => import("#routes/admin/guide.ts"), ["builder", "guide"]),
holidays: area(() => import("#routes/admin/holidays.ts")),
images: area(() => import("#routes/admin/images.ts")),
ledger: area(() => import("#routes/admin/ledger.ts")),
Expand All @@ -68,20 +85,20 @@ export const ADMIN_AREA_LOADERS: Record<AdminAreaId, AdminAreaLoader> = {
markdownPreview: area(() => import("#routes/admin/markdown-preview.ts")),
modifiers: area(() => import("#routes/admin/modifiers.ts")),
news: area(() => import("#routes/admin/news.ts")),
privacy: area(() => import("#routes/admin/privacy.ts")),
privacy: area(() => import("#routes/admin/privacy.ts"), ["privacy"]),
questions: area(() => import("#routes/admin/questions.ts")),
scanner: area(() => import("#routes/admin/scanner.ts")),
seeds: area(() => import("#routes/admin/seeds.ts")),
servicing: area(() => import("#routes/admin/servicing.tsx")),
servicing: area(() => import("#routes/admin/servicing.tsx"), ["servicing"]),
sessions: area(() => import("#routes/admin/sessions.ts")),
settings: area(() => import("#routes/admin/settings.ts")),
settingsLogistics: area(() => import("#routes/admin/settings-logistics.ts")),
settingsStatuses: area(() => import("#routes/admin/settings-statuses.ts")),
site: area(() => import("#routes/admin/site.ts")),
sitePages: area(() => import("#routes/admin/site-pages.ts")),
sms: area(() => import("#routes/admin/sms.ts")),
support: area(() => import("#routes/admin/support.ts")),
update: area(() => import("#routes/admin/update.ts")),
support: area(() => import("#routes/admin/support.ts"), ["support"]),
update: area(() => import("#routes/admin/update.ts"), ["update"]),
users: area(() => import("#routes/admin/users.ts")),
};

Expand All @@ -93,10 +110,12 @@ export const adminPathSegment = (path: string): string =>
* One lazy router per segment, derived from the manifest at module load
* (pure and cheap — no area module is imported until its `once` fires).
*/
const buildSegmentRouters = (): Record<
string,
() => Promise<PathMethodRoute>
> => {
type AdminSegment = {
load: () => Promise<PathMethodRoute>;
messageGroups: readonly MessageGroup[];
};

const buildSegmentRouters = (): Record<string, AdminSegment> => {
const areasBySegment: Record<
string,
Array<{ id: AdminAreaId; loader: AdminAreaLoader }>
Expand All @@ -109,16 +128,24 @@ const buildSegmentRouters = (): Record<
areasBySegment[segment] = list;
}
}
const routers: Record<string, () => Promise<PathMethodRoute>> = {};
const routers: Record<string, AdminSegment> = {};
for (const [segment, areas] of Object.entries(areasBySegment)) {
routers[segment] = once(async () => {
const maps = await Promise.all(
areas.map(async ({ id, loader }) =>
routeMapForArea(id, await loader.load()),
),
);
return createRouter(Object.assign({}, ...maps));
});
routers[segment] = {
load: once(async () => {
const maps = await Promise.all(
areas.map(async ({ id, loader }) =>
routeMapForArea(id, await loader.load()),
),
);
return createRouter(Object.assign({}, ...maps));
}),
messageGroups: [
...new Set([
...ADMIN_BASE_MESSAGE_GROUPS,
...areas.flatMap(({ loader }) => loader.messageGroups),
]),
],
};
}
return routers;
};
Expand All @@ -139,19 +166,24 @@ export const routeAdmin: PathMethodRoute = async (
// An unknown segment 404s before any session work, so probing traffic
// never costs a session lookup.
const segment = adminPathSegment(path);
if (!Object.hasOwn(segmentRouters, segment)) return null;
const segmentRouter = segmentRouters[segment];
Comment thread
stefan-burke marked this conversation as resolved.
Resolved
if (!segmentRouter) return null;

// Query recording is turned on earlier (prepareRequestEnvironment) for admin
// GETs, so the route's settings load is captured. Here we only unlock the
// footer for back-office staff — delivery agents and content editors (who are
// excluded from operational/debug access) never see the SQL/cache debug menu.
const { getAuthenticatedSession } = await import("#routes/auth.ts");
const session = await getAuthenticatedSession(request);
return await withMessageGroups(segmentRouter.messageGroups, async () => {
// Query recording is turned on earlier (prepareRequestEnvironment) for admin
// GETs, so the route's settings load is captured. Here we only unlock the
// footer for back-office staff — delivery agents and content editors (who are
// excluded from operational/debug access) never see the SQL/cache debug menu.
const { getAuthenticatedSession } = await import("#routes/auth.ts");
const session = await getAuthenticatedSession(request);

if (method === "GET" && session && isStaffRole(session.adminLevel)) {
enableFooterDebug();
}
if (method === "GET" && session && isStaffRole(session.adminLevel)) {
enableFooterDebug();
}

const router = await segmentRouters[segment]!();
return router(request, path, method, server);
// Copy is loaded before importing an area: some area modules translate form
// schemas at module evaluation time.
const router = await segmentRouter.load();
return router(request, path, method, server);
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
};
Loading