Skip to content

Webapp Dead-Surface Trim

Status: Phase 1 + 2a SHIPPED in #2640 (Phase 2b/3 gated on route traffic & SQL checks) Scope: apps/webapp/src pages + components Context: apps/docs/src/content/docs/design/inbox-is-the-product.md is fully rolled out. /inbox (InboxCockpit.tsx) + orders/review-v2 (ReviewResponsiveBody.tsx) inside CockpitShell.astro are the product. Everything below is what the pre-inbox app left behind.

Validated candidate inventory: 60 files / 11,080 LOC — measured by deleting all of them at once, running the full unit suite and astro check clean, then reverting. A further ~5.3k LOC is a candidate behind one SQL answer (Phase 3).

Not all 60 are equally safe, and the inventory is not the shipping unit:

Files LOC Behavior change Status
Phase 1 + 2a 40 ~7,022 none for the 37 modules; 2 unlinked routes start 404-ing SHIPPED (#2640)
Phase 2b 19 ~4,051 6 served routes retired redirect first, delete after traffic check

The “60” figure is what the analysis found. The “41” is what was safe to ship without traffic data.

Grep-by-component-name is not sufficient here — several components read as “in use” because they are mentioned in doc comments, and several pages read as “dead” because their only inbound link is a string built at runtime (an email body, a notification payload). Three independent passes:

  1. Import-graph BFS (scratchpad/reach.ts): parse every static/dynamic/export ... from specifier in all 941 code files, resolve @/ + relative + barrel + extension-less forms, BFS from all 305 Astro entry points (src/pages/**, src/middleware*, src/actions/index.ts). Anything not visited is unreachable by import.
  2. Route inbound-reference scan (scratchpad/routes.ts): for each of the 51 page routes, regex the entire monorepo (all apps, all packages, scripts, data-engineering, Python workers, docs) for a link/redirect to that path, with dynamic segments wildcarded. Results split into code / test-only / docs-only.
  3. Runtime string audit — manual read of every place a URL is constructed rather than imported: NotificationsDrawer, buildWorkItemReviewUrl, MailingService, the middleware redirect chain, and Temporal notification bodies. This pass is what caught the three false positives below; neither (1) nor (2) alone would have.

Then the whole deletion set was applied for real and validated against tests + astro check.

v1 claim Verified reality
ConsentManager.astro is dead Wrong. Imported by Layout, AuthLayout, PublicLegalLayout.
QuoteSplitter.tsx is shared with review-v2 — keep Wrong, and inverted. 760 LOC, imported only by the dead DocumentOrderReview. QuoteSplitPanel.tsx:10 says outright that it replaces the legacy QuoteSplitter. It is the second-biggest dead file.
/verify is safe to delete Wrong, and would have broken production. MailingService.sendVerificationEmail emails ${selfURL}/verify?token=…, reached from the Clerk webhook → generateAliasesForConnection → VerificationService. Live, external, already-sent links.
/orders/email/[emailId] is a secondary “Open email” link Wrong. buildWorkItemReviewUrl returns /orders/email/${emailEventId} whenever a work item has no pdfDocumentId — it is the primary review destination for email-only items, used by InboxCockpit, cockpit/RowGroup, and SupervisorDetailDrawer. inbound-email.ts creates exactly such work items.
/orders/[id] only renders pre-inbox orders Incomplete. NotificationsDrawer.getNotificationTargetUrl falls back to /orders/${meta.orderId} when a notification has no pdfDocumentId. Deleting the page silently breaks those notifications.
/action-items → /inbox?lifecycle=needs_fix Wrong param. InboxLifecycleView is only active | archived | deleted. needs_fix is a uxState, and the cockpit already renders a needs_fix group (InboxCockpit.tsx:299). The overlap claim holds; the migration is not a lifecycle= value.
/orders/index.astro redirects to /inbox?lifecycle=submitted Dead code. ordersInboxRedirectMiddleware intercepts /orders earlier in the chain and redirects to /inbox?ref=orders_redirect. The page never executes, and its lifecycle=submitted intent is already silently lost.
~14.4k LOC 11,080 LOC / 60 files, measured.

Phase 0 — Pre-flight (gates Phase 3 only)

Section titled “Phase 0 — Pre-flight (gates Phase 3 only)”
-- Do any orders lack a PDF? These are the ONLY rows /orders/[id] and /orders/edit/[id]
-- still render — both already redirect to review-v2 when a pdf_document_id exists.
select count(*) from orders o
left join extracted_orders e on e.id = o.extracted_order_id
where e.pdf_document_id is null;
-- Are there email-only work items? These are the only rows that route to /orders/email/[emailId].
select count(*) from work_items
where pdf_document_id is null and email_event_id is not null and deleted_at is null;

Also pull 90-day PostHog $pageview counts for /action-items, /orders/email/*, /orders/[id], /admin/flags. Note the caveat in [reference_posthog_three_runtimes_key_gap]: webapp-prod’s POSTHOG_API_KEY gap means low counts may mean “not instrumented”, not “not used” — treat a zero as unproven, not as permission.

Phases 1 and 2 need none of this.

Phase 1 — Zero-import orphans (36 files, 6,885 LOC)

Section titled “Phase 1 — Zero-import orphans (36 files, 6,885 LOC)”

Unreachable from every Astro entry point. Verified: suite green, astro check clean.

Two need a manual smoke test rather than trust in the graph:

  • auth/ClerkInitializer.tsx (186) + services/authSync.ts (263) + hooks/useAuth.ts (98) + stores/clerkStore.ts (198). astro.config.mjs mounts clerk(), which injects the clerk-js bootstrap that keeps __session fresh, and chrome/AppChrome.astro documents that no Clerk island is mounted any more. The old island stack is orphaned. Smoke-test sign-in, sign-out, and org switch before merging — a broken session is not something a unit test will tell you.
  • lib/api/auth-enforcement-anomalies.ts (288) has 19 passing tests and no production caller. It is security-adjacent instrumentation for the default-deny API middleware. 19 tests is not the shape of abandoned code — it is the shape of code someone finished and forgot to wire in. Confirm with whoever wrote apiAuthEnforcementMiddleware before this goes; “dead” and “wired up wrong” look identical from an import graph, and only one of them is safe to delete.
File LOC
components/orders/DocumentOrderReview.tsx — the v1 review page 1318
components/orders/QuoteSplitter.tsx — superseded by review-v2/QuoteSplitPanel 760
components/orders/CustomerProfileCard.tsx 381
components/orders/ResponseDraftDrawer.tsx 352
lib/api/auth-enforcement-anomalies.ts (+ test) 288
components/orders/RetryExtractionButton.tsx 274
services/authSync.ts 263
hooks/useExtractionProgress.ts 258
components/orders/DocumentMetadataCard.tsx 238
components/orders/POHeaderCard.tsx 210
stores/clerkStore.ts 198
components/auth/ClerkInitializer.tsx (+ test) 186
components/orders/AgentValidationBadge.tsx 132
lib/inbox/source-document.ts 132
components/inbox/InboxFeedbackModal.tsx (+ dead POST /api/inbox/feedback) 129
components/orders/OrderTotalsCard.tsx 121
components/ui/ErrorResolutionGuide.tsx 121
components/inbox/InboxKeyboardHelp.tsx — duplicated into settings/sections/ShortcutsSection 115
components/inbox/InboxTabNav.tsx — legacy flag-off surface 96
hooks/useAuth.ts 98
components/inbox/InboxZero.tsx — superseded by InboxEmptyState 90
components/inbox/UploadModal.tsx — cockpit uses InboxUploadForm directly 85
components/customers/CustomersList.tsx 59
utils/errorContext.ts 59
lib/langfuse-search-config.ts 56
utils/customer-profile-meta.ts (+ test) 56
components/ui/DragOverlay.tsx — name-collides with dnd-kit’s, which is the one actually used 50
components/auth/AuthPerformanceMonitor.tsx 47
components/orders/ReviewStatusBadge.tsx 41
components/customers/CustomerDetail.tsx (+ test) 35
components/inbox/cockpit/SkeletonRow.tsx 15
src/tmp.ts — contents: // placeholder 2

Phase 2 — Unlinked pages + their cascade (24 files, 4,172 LOC)

Section titled “Phase 2 — Unlinked pages + their cascade (24 files, 4,172 LOC)”

Pages with zero inbound reference anywhere in the monorepo, plus the components that become unreachable once they go. Verified with Phase 1: suite green, astro check clean, cascade stable after one round.

Codex review changed this phase. An independent adversarial pass (review report) correctly attacked the word “delete”: an Astro page with no inbound link is still a served, functioning route for anyone who types or bookmarks the URL. Static analysis proves nothing renders a link to it; it does not prove nobody visits it. Phase 2 is therefore split into 2a (genuinely no surface to lose) and 2b (redirect via middleware, do not delete).

Page LOC Evidence
erp/customers/index.astro 63 No inbound link, and its onSelectCustomer handler is a console.log — the page cannot complete its own task.
thank-you.astro 27 Waitlist-era. Zero references anywhere, including email code.
erp/edit-connection/[id].astro 7 Withdrawn — do not delete. It reads as an unlinked stub, but it is the only thing 301-ing old bookmarks to /admin/edit-connection/:id, which is still a live page. Deleting it orphaned that page (routes.ts reported /admin/edit-connection/[id] as NO-REF immediately after), for a 7-line gain. Restored.
erp/connections.astro 4 301 → /admin/connections. Fold into the middleware chain instead of a page file.
orders/index.astro 3 Shadowed by ordersInboxRedirectMiddleware — never executes.
Section titled “2b. Redirect, do not delete — functioning routes with no inbound link”

Each of these still authenticates, queries, and renders. Add a middleware 301 to the sensible destination and delete the page + its cascade only after Phase 0 shows no traffic.

Page LOC Why it is not a free delete Redirect to
organizations/[orgId]/members/index.astro 584 Its feature gate is getFeatureFlag('org_members_enabled', Astro.locals) || true (line 39) — forced on, so the flag is dead code and the page is always live. It fetches members server-side and renders working management rows. /settings?section=users
orders/new.astro 543 Live ERP write path. POST /api/erp/orders at line 414, with upload/customer/shipping/item islands hydrated. Nothing intercepts /orders/new — ordersInboxRedirectMiddleware is an exact-match guard on /orders only (line 5), which is why it passes through, not an intentional allowance. Given the repo rule that ERP writes stay flag-gated, removing a write surface deserves the same care as adding one. /inbox
orders/wherefour-submit.astro 56 Gated on wherefour-order-validation, a flag registered in the deployed Flagsmith config, whose description names this exact route (infrastructure/flagsmith/configs/flags.json:398). Check Flagsmith for per-org overrides before assuming CK/WhereFour isn’t using it. retire flag first, then page
orgs/[slug]/pipeline.astro ~40 Resolves the Clerk org and hydrates PipelineDashboard, which calls /api/orgs/[slug]/pipeline-metrics. A working ops page. /analytics or delete with the API
sys-admin/revenue-leaks.astro ~130 Reachable whenever the sys-admin gate passes (middleware.ts:625, :646); does authenticated DB work and records a page view. /sys-admin
error.astro 24 Reads Astro.props.status, which Astro never supplies for a page — so it always renders the generic branch. But /error is still a served route. /404

Keep the /api/organizations/[orgId]/members endpoint regardless — packages/ui/src/components/sys-admin/OrganizationMembers.tsx calls it.

Cascade (components orphaned once 2a+2b pages go): CustomerSearch · ItemSearch · ItemSearchResultRow · components/OrderItemsList · ShippingAddressSelector (+ test) · integrations/WhereFourValidateOrder · orders/DocumentUpload · orders/OrderCreationTabs · pipeline/PipelineDashboard · ui/MemberRow · types/item.ts

Do NOT delete — verified live and shared with review-v2 / cockpit: PDFViewerWithHighlights, ImageViewerWithHighlights, SpreadsheetFullView, CSVPreview, TextPreview, EmailPreview, PrintDocButton, modals/CustomerSearchModal, modals/ShippingSearchModal, modals/SearchModalShell, actions/orderActions.ts, TrashReasonModal, ui/LockedControl, BulkResolveModal, ConsentManager.astro.

Must stay — live email-borne flow: verify.astro. See the corrections table.

middleware.ts already has three of these: legacyOrganizationPageRedirectsMiddleware (ENG-57), notificationsPageRedirectMiddleware (ENG-50), ordersInboxRedirectMiddleware (ENG-61). Follow that pattern — a 301 in the chain, not a stub .astro file whose redirect the middleware then shadows (which is exactly the bug orders/index.astro is). Add entries for any retired path with plausible bookmarks; skip it for paths nothing ever linked.

Phase 3 — Live-but-redundant surfaces (needs decisions, ~6.7k LOC)

Section titled “Phase 3 — Live-but-redundant surfaces (needs decisions, ~6.7k LOC)”

Each of these is genuinely reachable. Do not batch them with Phase 1–2.

3a. Manual-order path — /orders/[id] + /orders/edit/[id] + the OrderEditor tree (5,279 LOC)

Section titled “3a. Manual-order path — /orders/[id] + /orders/edit/[id] + the OrderEditor tree (5,279 LOC)”

Both pages redirect to review-v2 when the order has a PDF, so this tree only renders PDF-less orders. It is reachable two ways: NotificationsDrawer’s metadata.orderId fallback, and /orders/[id]’s own “Edit” link.

Correction (2026-07-30): the second reachability path does not hold — /orders/[id].astro’s “Edit” <a href={/orders/edit/${order.id}}> is commented out (git blame: 6f15683edb, 2025-05-09, “fixing order viewer page”), 4 days after 4f6190c21 (2025-05-05) gutted OrderEditor.tsx’s manual-mode support down to PDF-only. Grepping the whole repo for orders/edit today finds zero live inbound references — only that dead comment and two unrelated code comments. Two further findings from the same investigation, both consistent with this tree being unreachable in practice, neither proving it on their own:

  • OrderEditor.tsx’s mode/initialOrder props (still declared in OrderEditorProps, the only remnant of the pre-4f6190c21 dual-mode design) are dead — never destructured or read anywhere in the component body. orders/edit/[id].astro still passes mode="manual" initialOrder={order} into them for nothing.
  • Even if a user reaches /orders/edit/[id] by typed/bookmarked URL, OrderEditor reads stores/pdfOrder’s editingOrder atom (not the initialOrder prop), which is only ever populated via selectedOrderId’s listener against extractedOrders — both populated by review-v2’s PDF-document data flow, never by this route. So today OrderEditor itself renders “No order selected or order not found” here regardless of what db.query.orders.findFirst fetched server-side; modals/ManualItemSearchModal (which reads the separate, likewise never-populated stores/orderEditStore) is doubly so.
  • modals/ItemSearchModal (distinct from review-v2’s modals/ERPSearchModal) has exactly one importer repo-wide: OrderEditor.tsx. It is not an independent “also used elsewhere” component — it lives or dies with this same tree.

None of this proves nobody ever hits /orders/edit/[id] (a stale bookmark or search-index entry still resolves it), which is exactly what Phase 0 query 1 is for — it’s stronger evidence the answer will come back 0, not a substitute for running it. Do not delete on the strength of this alone.

If Phase 0 query 1 returns 0: change getNotificationTargetUrl to resolve the review URL (or return null), redirect both routes to /inbox, delete the tree — OrderEditor (460), CustomerInfoSection, ShippingInfoSection, OrderActionButtons, BillingAddressSearch, SkippedItemsNotification, SubscriptionLimitWarning, OrderStatusAndValidation, OrderSuccessNotification, orders/OrderItemsList, orders/OrderItemCard, orders/OrderItemDiscountMatching, modals/ItemSearchModal, modals/ManualItemSearchModal, modals/BaseSearchModal, stores/orderEditStore, stores/orderStore.

If non-zero: leave it. This is the only surface that can open those rows.

Nit either way: stores/orderStore.ts survives Phase 2 solely because modals/ShippingSearchModal.tsx:26 does import type { Address } from it. Move the type to types/ and the store is free.

3b. /action-items (147 + ActionItemsStack 210)

Section titled “3b. /action-items (147 + ActionItemsStack 210)”

ActionItemsStack’s own header comment states it uses the inbox’s buildWorkItemInboxConditions and uxStateSql() (filtered in SQL by loadActionItems in lib/inbox/action-items.ts), and that V1 is “five uxState groups + a basic count” with bulk-select “in a follow-up” that never shipped. The cockpit already renders needs_fix, needs_review and failed as groups; the page’s Price review group is the needs_review rows that have an extracted order. Its UX_STATE_GROUPS also includes awaiting_ref_data and awaiting_approval, which the UxState union never defined — those two groups render empty, permanently.

Recommend: delete both, point the rail entry at /inbox. If a deep-link to the needs-fix group is wanted, add a ?group=needs_fix param to the cockpit (it already tracks per-group collapse state) — do not invent a lifecycle= value; that enum is storage-level.

3c. /orders/email/[emailId] (587 + EmailSenderBar + InboxDetailActions 497)

Section titled “3c. /orders/email/[emailId] (587 + EmailSenderBar + InboxDetailActions 497)”

Load-bearing — do not delete as written in v1. It is where buildWorkItemReviewUrl sends any work item without a pdfDocumentId, and api/webhook/inbound-email.ts creates work items with emailEventId and no PDF. Retiring it means first giving email-only items a review surface inside the cockpit. That is a feature, not a cleanup. Gate on Phase 0 query 2.

3d. /admin/flags — unlinked but working (FlagsConsole + FeatureFlagDashboard + OrgFeatureFlags, ~850 LOC)

Section titled “3d. /admin/flags — unlinked but working (FlagsConsole + FeatureFlagDashboard + OrgFeatureFlags, ~850 LOC)”

Reachable only by typing the URL: admin/index.astro links analytics/billing/connections/roi/usage and not flags. (/sys-admin/flags in SysAdminLayout is a different, proxied surface.) Recommend adding the link, not deleting the console — it is functioning org-scoped flag tooling and one <a> is cheaper than 850 LOC of rebuild later.

/team offers Table (ConservativeInbox, 547) and Kanban (SupervisorKanban, 583) via ViewSwitcher. Both wired, both reachable. Pull view-switch counts before touching either; do not guess.

Zero callers monorepo-wide (webapp, apps/internal, Python + TS Temporal workers, ops-mcp, scripts, playwright):

  • GET/PUT /api/work-items/sla-policy + /sla-policy-history
  • POST /api/alias/new-member
  • POST /api/erp/token
  • POST /api/inbox/feedback (Phase 1 removes its only would-be caller)
  • POST /api/billing/report-usage (docs-only references)
  • POST /api/orders/generate-email-body-pdf — and it is in auth-allowlist.ts as “Runtime-public — stateless PDF generation”. An unauthenticated, uncalled PDF generator is attack surface for nothing. Highest-value item in this phase despite being the smallest.

Non-code dead weight found by the Codex pass (all four verified):

  • components/inbox/inbox-email-list.css — imported by nothing; its selectors appear only in itself. Missed by the import BFS, which only walked .ts/.tsx/.astro/.js.
  • Unused deps in apps/webapp/package.json: @astrojs/partytown (line 33 — and Partytown is absent from astro.config.mjs’s integration list), mupdf (85), pdf-parse (88), @types/pdf-parse (133). The only mupdf-ish hits in the repo are Python pymupdf, a different package.
  • Dead feature flag org_members_enabled — read at organizations/[orgId]/members/index.astro:39 and immediately || true’d. Retire it in Flagsmith when 2b lands.

Explicitly keep — these look dead from webapp alone but have external callers: /api/work-items/dashboard, /api/work-items/test-order, /api/work-items/supervisor-actions, /api/admin/system-stats, /api/admin/unblock-ip (called by apps/internal), /api/admin/generate-aliases (apps/internal + playwright), /api/erp/orders/filter-invalid (apps/temporal-worker/activities/agentic_validation.py), /api/auth/organization (packages/auth), /api/trigger/*, /api/organizations/[orgId]/members.

Add knip to apps/webapp and run it in the existing lint job.

Terminal window
pnpm --filter @repo/webapp add -D knip

Config: entry: ["src/pages/**/*.{astro,ts}", "src/middleware.ts", "src/actions/index.ts"], project: ["src/**"]. Ship it warn-only first — a red gate on day one gets the tool disabled instead of used. Promote to error once Phases 1–2 land and the baseline is zero.

knip replaces pass (1) of the Method above. It does not replace passes (2) and (3): it cannot see a route referenced only from an email body, a notification payload, a Flagsmith flag description, or another app. Those need the route scan, and the route scan needs a human reading the runtime string-builders — which is where all three of v1’s false positives lived. Say so in the PR description so the next person doesn’t trust the tool further than it goes.

Also configure knip to include .css — the BFS here missed inbox-email-list.css for exactly that reason.

Phase Files LOC Risk Gate
1 36 6,885 very low (proven) suite + astro check + sign-in/out/org-switch smoke test; confirm auth-enforcement-anomalies was abandoned deliberately
2a 5 ~104 very low (proven) suite + astro check
2b 19 ~4,068 medium Phase 0 traffic check per route; middleware 301 lands before the page is deleted; Flagsmith override check for wherefour-order-validation
3a ~19 5,279 medium Phase 0 query 1 == 0; patch getNotificationTargetUrl first
3b 2 357 low product call
3c — — do not do yet needs an in-cockpit surface for email-only items
3d 0 +1 line none add the link
4 ~7 + 4 deps + 1 CSS small low re-grep monorepo per route at merge time
5 +1 dep — none warn-only

Phase 1 + 2a ship as one PR (~7k LOC, both proven, zero reachable surface). Base staging. 2b is a second PR: redirects first, deletions after traffic confirms zero. Phase 3 is three more.

The methodological lesson worth carrying forward: three passes found 11k LOC and still produced five overstated calls, because “no code links here” and “nobody goes here” are different claims. Only Phase 0’s traffic data settles the second one.

Both scripts are checked in under scripts/dead-surface/ (~80 lines each, bun, no deps):

Terminal window
bun run scripts/dead-surface/reach.ts "$PWD/apps/webapp" # import-graph BFS → unreachable files
bun run scripts/dead-surface/routes.ts "$PWD" # per-route inbound refs, monorepo-wide

reach.ts should report 0 unreachable at any commit where this plan’s shipped phases are complete — it is a cheap regression check until Phase 5’s knip gate lands. routes.ts has no tool equivalent and is the one Phase 2b needs again, which is why both are tracked rather than left in a scratchpad. Note the classifier hole that cost this analysis a false positive: routes.ts buckets hits by file extension and drops anything outside its code/test/docs sets, which is how a route named in infrastructure/flagsmith/configs/flags.json went unreported. Widen CODE_EXT before trusting a NO-REF verdict on a config-referenced path.

The verification run: apply the deletion set, then

Terminal window
cd apps/webapp && bash ../../scripts/with-native-node.sh npx vitest run && bash ../../scripts/with-native-node.sh npx astro check

Test counts. Three different deletion sets were measured, so the numbers differ; the baseline row is the one an earlier draft of this document got wrong by reusing a post-deletion figure.

Run Deletion set Test files Tests passing astro check
Baseline (measured at b57c939c6) none 249 2545 0 errors
Phase 1 + 2a (shipped) 41 files 245 2510 0 errors, 0 warnings
Full inventory (trial, reverted) 60 files 244 2507 0 errors, 0 warnings

Phase 1 + 2a removes 4 whole test files, and the 35-test drop is exactly their contents: auth-enforcement-anomalies 19 + ClerkInitializer 7 + customer-profile-meta 6 + CustomerDetail 3 = 35, and 2545 − 2510 = 35. No surviving test changed behavior. The full-inventory trial drops 3 further tests because Phase 2b also removes ShippingAddressSelector.test.tsx.

Worth naming: 19 of those 35 tests covered lib/api/auth-enforcement-anomalies.ts. A module with 19 tests and no production caller is a stronger “someone meant to wire this up” signal than a module with none — see the Phase 1 note asking for confirmation before it goes.

admin/* (except the flags link), sys-admin/*, settings, analytics, audit, customers, missed-orders, revenue-leaks, team, support, inbox, orders/pdf/*/review, billing, auth, onboarding, and legal pages are all reachable and current. /analytics vs /admin/analytics vs /admin/roi do overlap conceptually, but all three are linked and maintained — consolidating them is a design question, not fat to trim.

Review Trigger Why Runs Status Findings
CEO Review /plan-ceo-review Scope & strategy 0 not run —
Codex Review /codex review Independent 2nd opinion 1 issues_found 8 findings (6 P1, 2 P2), 8/8 folded in
Eng Review /plan-eng-review Architecture & tests (required) 0 not run —
Design Review /plan-design-review UI/UX gaps 0 not run —
DX Review /plan-devex-review Developer experience gaps 0 not run —

CODEX: Attacked the framing, not the arithmetic. Confirmed all three load-bearing findings (orders/email/[emailId] via work-item-review-url.ts, orders/[id] via NotificationsDrawer, verify.astro via mailing-service.ts), found no false positive in Phase 1, and found no glob/island loader reviving any Phase 1 module. Its real contribution: five Phase 2 pages were labelled deletable on the strength of “no inbound link”, when each is a served, authenticating, rendering route — orders/new is a live POST /api/erp/orders write path, and wherefour-submit’s flag is named by description in the deployed Flagsmith config (flags.json:398), which the route scan dropped because .json fell outside its code/test/docs classifier. Phase 2 is now split 2a (delete) / 2b (redirect first, delete after traffic data). Also surfaced three non-code items the import BFS structurally could not see: an unimported CSS file, four unused npm deps, and the always-true org_members_enabled flag.

One Codex sub-claim does not hold: orders-inbox-redirect.ts:5 does not “explicitly pass /orders/new through” — it is an exact-match guard on /orders, so the pass-through is incidental, not intentional. The conclusion (the route is served) is right; the stated reason is not.

VERDICT: Phase 1 + 2a is CONDITIONALLY CLEARED in #2640 — automated evidence is complete (2510 of 2545 baseline tests passing, the 35-test delta accounted for file-by-file above; astro check 0 errors, 0 warnings; Codex, CodeRabbit and Devin all raised zero objections to the deletion set itself). Three prerequisites are not met and are merge blockers, not follow-ups:

  1. Sign-in / sign-out / org-switch smoke test — not run. The Clerk island removal is not covered by any test.
  2. Author confirmation that lib/api/auth-enforcement-anomalies.ts (19 tests, 0 callers, security-adjacent) was abandoned deliberately.
  3. Eng review — not run.

Phase 2b downgraded from “delete” to “redirect, then delete after traffic check”. Eng review also required before Phase 3.

UNRESOLVED DECISIONS:

  • Phase 0 SQL + PostHog traffic data not yet pulled — gates 2b and 3a.
  • Whether lib/api/auth-enforcement-anomalies.ts was abandoned deliberately or never wired up (security-adjacent; needs the author of apiAuthEnforcementMiddleware).
  • Flagsmith per-org override state for wherefour-order-validation — blocks retiring orders/wherefour-submit.
  • 3b (/action-items retirement) and 3d (/admin/flags link) are product calls, not yet made.