From ecf5e75d767d388b08709557d3489dcaaaf8a4e7 Mon Sep 17 00:00:00 2001 From: Aravind Date: Fri, 18 Sep 2026 13:18:59 +0530 Subject: [PATCH] chore(ts-migration): migrate the app shell, routing and the last non-agent pages Phase 10: `App`, `main`, `index.html`, and the four remaining `src/pages` files. ROUTING IS PROVEN UNCHANGED, not assumed. The 49 `` elements were fingerprinted before the rename and compared after: byte-identical, all 44 paths, all 13 `` redirects, the same nesting under `ProtectedRoute` and `AdminRoute`, and the same provider order - AuthProvider, then QueryClientProvider, then Router, with ScrollToTop and AuthenticatedApp inside and both toasters as siblings. There is no lazy loading to preserve; there never was any. Two coordinated edits the rename forced. `main` imported `@/App.jsx` by explicit extension, which stops resolving the moment `App` is `.tsx`; it is now extensionless `@/App`. `index.html` pointed its module script at `/src/main.jsx`; it points at `/src/main.tsx`. Both are required, and missing either would have been a blank page rather than a type error. ONE GENUINE SEMANTIC DIFFERENCE, INVESTIGATED AND ACCEPTED. esbuild elides unused imports under the TypeScript loader but keeps them under the JavaScript one, so `App` drops thirteen imports: `Layout`, `Overview`, `Positions`, `Candidates`, `HiredHistory`, `TalentPool`, `UserTracking`, `Analytics`, `Profile`, `WorkerProfile`, `KrowIdentity`, `Owliver` and `DesignSystem`. All thirteen are dead - each has zero JSX uses in `App`, because every route that once rendered them now ``s to an `/admin/*` equivalent. Before accepting it I checked that none of the thirteen modules can do anything when evaluated: no bare side-effect imports, no top-level calls, and every top-level binding a literal or a function declaration. The consequence is 942 fewer bytes in the index chunk and thirteen modules no longer evaluated at startup. Nothing observable changes, and the routes those pages are reached through are unaffected - they are reached through the `/admin` tree, which is untouched. That is the first time in this migration the production bundle has changed for a reason other than a comment, so it is recorded here rather than left to be noticed later. `CreatePosition` needed the only real typing. `vetting_criteria` is a `jsonb` column holding the five weighting percentages the page edits, typed `unknown` by the registry, and both the total and the three render sites read through it. `onDone` is called with the created id, so it takes arguments - the generator had classed it as zero-arg, and that rule is now narrowed to `onClose` alone. The generator's entity-import rule was narrowed first, as instructed: it now counts a type as used only when it appears in a type position inside a generated interface, rather than anywhere in the file text. That is what produced four unused-import lint errors in the previous batch. Verified: tsc 21 -> 20, set-difference showing one removed and none added; zero errors in any of the six files; all four pages emit byte-identical JavaScript; route fingerprint identical; npm test 1684/1691 with the same seven failures; Owliver baseline 59/59; lint 0 errors; no deferred agent-region file touched; baseline artifacts untouched. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8 --- index.html | 2 +- src/{App.jsx => App.tsx} | 0 src/{main.jsx => main.tsx} | 2 +- .../{CreatePosition.jsx => CreatePosition.tsx} | 15 ++++++++++++--- .../{PositionDetail.jsx => PositionDetail.tsx} | 8 +++++++- src/pages/{University.jsx => University.tsx} | 0 src/pages/{UserTracking.jsx => UserTracking.tsx} | 0 7 files changed, 21 insertions(+), 6 deletions(-) rename src/{App.jsx => App.tsx} (100%) rename src/{main.jsx => main.tsx} (82%) rename src/pages/{CreatePosition.jsx => CreatePosition.tsx} (97%) rename src/pages/{PositionDetail.jsx => PositionDetail.tsx} (99%) rename src/pages/{University.jsx => University.tsx} (100%) rename src/pages/{UserTracking.jsx => UserTracking.tsx} (100%) diff --git a/index.html b/index.html index 5caebd7..b8bf14e 100644 --- a/index.html +++ b/index.html @@ -12,6 +12,6 @@
- + diff --git a/src/App.jsx b/src/App.tsx similarity index 100% rename from src/App.jsx rename to src/App.tsx diff --git a/src/main.jsx b/src/main.tsx similarity index 82% rename from src/main.jsx rename to src/main.tsx index 6037e58..ce397dc 100644 --- a/src/main.jsx +++ b/src/main.tsx @@ -1,5 +1,5 @@ import ReactDOM from 'react-dom/client' -import App from '@/App.jsx' +import App from '@/App' import '@/index.css' ReactDOM.createRoot(document.getElementById('root')).render( diff --git a/src/pages/CreatePosition.jsx b/src/pages/CreatePosition.tsx similarity index 97% rename from src/pages/CreatePosition.jsx rename to src/pages/CreatePosition.tsx index fb5e8f9..b72b5ee 100644 --- a/src/pages/CreatePosition.jsx +++ b/src/pages/CreatePosition.tsx @@ -22,7 +22,13 @@ import { usePublishPageActions, usePublishPageContext } from '@/components/ai-as * and both paths share one field definition — `lib/positionModel.js` — so * neither can drift into a different shape of position. */ -export default function CreatePosition({ prefill: prefillProp, embedded = false, onDone }) { +interface CreatePositionProps { + prefill?: any; + embedded?: any; + onDone?: (...args: any[]) => void; +} + +export default function CreatePosition({ prefill: prefillProp, embedded = false, onDone }: CreatePositionProps) { const navigate = useNavigate(); /* Owliver opens this page with the fields it could infer from the request. It is a starting point, not a submission: every field stays editable and @@ -79,7 +85,10 @@ export default function CreatePosition({ prefill: prefillProp, embedded = false, update('vetting_criteria', { ...form.vetting_criteria, [key]: parseInt(value) || 0 }); }; - const totalWeight = Object.values(form.vetting_criteria).reduce((a, b) => a + b, 0); + /* `vetting_criteria` is a `jsonb` column, so the registry types its values + `unknown`. It holds the five weighting percentages this page edits. */ + const totalWeight = Object.values(form.vetting_criteria as Record) + .reduce((a, b) => a + b, 0); /* What a skill on this surface reads: the position being specified, as it stands right now. The draft carries the same fields a saved position does, @@ -402,7 +411,7 @@ export default function CreatePosition({ prefill: prefillProp, embedded = false, {showWeights && (
- {Object.entries(form.vetting_criteria).map(([key, val]) => ( + {Object.entries(form.vetting_criteria as Record).map(([key, val]) => (
diff --git a/src/pages/PositionDetail.jsx b/src/pages/PositionDetail.tsx similarity index 99% rename from src/pages/PositionDetail.jsx rename to src/pages/PositionDetail.tsx index 3bd7204..41a346f 100644 --- a/src/pages/PositionDetail.jsx +++ b/src/pages/PositionDetail.tsx @@ -1,3 +1,4 @@ +import type { JobPosting } from '@/types/entities'; import React, { useState } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { useJobPosting, useApplications, useScreenAllCandidates, useUpdateJobPosting, useHireCandidate, useUpdateApplication, useCreateApplication, useWorkerProfiles, useCourses, useAssignments, useStaff } from '@/lib/krowHooks'; @@ -35,7 +36,12 @@ const STATUS_ACTIONS = [ * interviews on file. Nothing is derived here; sections that have nothing to * report say so in one line rather than reserving a panel for it. */ -function WorkforceStatus({ workforce, job }) { +interface WorkforceStatusProps { + workforce?: any; + job: JobPosting; +} + +function WorkforceStatus({ workforce, job }: WorkforceStatusProps) { const { demand, existingAvailable, applicants, newToday, strong, interviewReady } = workforce; /* Coverage figures depend on a declared headcount; the rest are facts about diff --git a/src/pages/University.jsx b/src/pages/University.tsx similarity index 100% rename from src/pages/University.jsx rename to src/pages/University.tsx diff --git a/src/pages/UserTracking.jsx b/src/pages/UserTracking.tsx similarity index 100% rename from src/pages/UserTracking.jsx rename to src/pages/UserTracking.tsx