From fcdaa32f4d33ff14663b5b75805ace73a57bc800 Mon Sep 17 00:00:00 2001 From: Aravind Date: Fri, 18 Sep 2026 12:47:04 +0530 Subject: [PATCH] chore(ts-migration): migrate the non-agent pages to TypeScript Phase 9, first batch: 18 pages. Seventeen emit byte-identical JavaScript, the eighteenth differs only by a JSDoc cast becoming a real annotation, and the production bundle is byte-identical to be49184. SCOPE, decided by imports. Twenty-two of the forty pages import `lib/skills`, `lib/agents`, `ai-assistant`, `components/skills`, `ui-tree` or `ui-editor`, and are deferred to Phase 11 with the rest of that region. `Owliver.jsx` is in THIS batch despite the name: it imports only `krowHooks`, `krowScore`, `krowAi` and `ui/button`, and is a profile-building conversation page rather than any part of the agent runtime. `DesignSystem` is the headline result. It was the single worst file in Phase 6 - 111 errors when the design system was first renamed - and it arrived here with none, because those were never its errors: they were the primitives' inferred props, and fixing them at the source fixed every consumer. Three pages needed real work, and each was the author's own intent made explicit: - `admin/Login` carried `useState(/** @type {{email?: string, password?: string}} */ ({}))`. JSDoc casts stop applying in a `.tsx` file, so that became a real type argument, and `validate`'s accumulator - built empty and filled per failed rule - needed the same shape. The login flow itself is untouched: the generic 401 message, the 429 branch and the `remember` field all stand. - `Owliver` gets `new Promise`, because its `resolve()` takes no argument. - `Candidates` names the element type of a `Set` built from `any[]`, which otherwise infers `Set` and makes every option a `ReactNode` error. LINT CAUGHT A REGRESSION THE OTHER CHECKS DID NOT. The generator adds an entity import when it sees the type NAME anywhere in the file, which for four pages with no props at all left an import nothing used - four `unused-imports` ERRORS, taking `npm run lint` from exit 0 to exit 1 while typecheck, tests, the bundle and seventeen of eighteen emit comparisons all stayed green. Removed. The generator's import rule is too eager and wants narrowing before the next batch. Verified: tsc 21 -> 21, set-difference showing zero introduced and zero removed; zero errors in any of the 18 pages; lint back to 0 errors and 289 warnings; npm test 1684/1691 with the same seven failures; Owliver baseline 59/59; production bundle byte-identical; baseline artifacts untouched. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8 --- src/pages/{Analytics.jsx => Analytics.tsx} | 0 src/pages/{Apply.jsx => Apply.tsx} | 0 src/pages/{Candidates.jsx => Candidates.tsx} | 4 ++- .../{CourseDetail.jsx => CourseDetail.tsx} | 0 .../{DesignSystem.jsx => DesignSystem.tsx} | 18 ++++++++++--- ...yeeDashboard.jsx => EmployeeDashboard.tsx} | 0 .../{HiredHistory.jsx => HiredHistory.tsx} | 16 +++++++++--- .../{KrowIdentity.jsx => KrowIdentity.tsx} | 8 +++++- ...tunityDetail.jsx => OpportunityDetail.tsx} | 7 +++++- src/pages/{Overview.jsx => Overview.tsx} | 11 +++++++- src/pages/{Owliver.jsx => Owliver.tsx} | 25 +++++++++++++++---- src/pages/{Positions.jsx => Positions.tsx} | 0 src/pages/{Profile.jsx => Profile.tsx} | 0 src/pages/{TalentPool.jsx => TalentPool.tsx} | 0 .../{WorkerProfile.jsx => WorkerProfile.tsx} | 0 .../admin/{AdminRoute.jsx => AdminRoute.tsx} | 0 src/pages/admin/{Login.jsx => Login.tsx} | 6 +++-- .../{TalentProfile.jsx => TalentProfile.tsx} | 14 +++++++++-- 18 files changed, 89 insertions(+), 20 deletions(-) rename src/pages/{Analytics.jsx => Analytics.tsx} (100%) rename src/pages/{Apply.jsx => Apply.tsx} (100%) rename src/pages/{Candidates.jsx => Candidates.tsx} (96%) rename src/pages/{CourseDetail.jsx => CourseDetail.tsx} (100%) rename src/pages/{DesignSystem.jsx => DesignSystem.tsx} (99%) rename src/pages/{EmployeeDashboard.jsx => EmployeeDashboard.tsx} (100%) rename src/pages/{HiredHistory.jsx => HiredHistory.tsx} (97%) rename src/pages/{KrowIdentity.jsx => KrowIdentity.tsx} (98%) rename src/pages/{OpportunityDetail.jsx => OpportunityDetail.tsx} (98%) rename src/pages/{Overview.jsx => Overview.tsx} (98%) rename src/pages/{Owliver.jsx => Owliver.tsx} (98%) rename src/pages/{Positions.jsx => Positions.tsx} (100%) rename src/pages/{Profile.jsx => Profile.tsx} (100%) rename src/pages/{TalentPool.jsx => TalentPool.tsx} (100%) rename src/pages/{WorkerProfile.jsx => WorkerProfile.tsx} (100%) rename src/pages/admin/{AdminRoute.jsx => AdminRoute.tsx} (100%) rename src/pages/admin/{Login.jsx => Login.tsx} (97%) rename src/pages/admin/{TalentProfile.jsx => TalentProfile.tsx} (97%) diff --git a/src/pages/Analytics.jsx b/src/pages/Analytics.tsx similarity index 100% rename from src/pages/Analytics.jsx rename to src/pages/Analytics.tsx diff --git a/src/pages/Apply.jsx b/src/pages/Apply.tsx similarity index 100% rename from src/pages/Apply.jsx rename to src/pages/Apply.tsx diff --git a/src/pages/Candidates.jsx b/src/pages/Candidates.tsx similarity index 96% rename from src/pages/Candidates.jsx rename to src/pages/Candidates.tsx index 8d544e9..88fb34a 100644 --- a/src/pages/Candidates.jsx +++ b/src/pages/Candidates.tsx @@ -43,7 +43,9 @@ export default function Candidates() { const sorted = [...applications].sort((a, b) => (b.ai_score || 0) - (a.ai_score || 0)); - const positionOptions = Array.from(new Set(applications.map(resolveTitle).filter(Boolean))).sort(); + /* `new Set(x)` where x is `any[]` infers `Set`; naming the element + type is the whole fix. The values put into it are unchanged. */ + const positionOptions = Array.from(new Set(applications.map(resolveTitle).filter(Boolean))).sort(); const filtered = sorted.filter(a => { const title = resolveTitle(a).toLowerCase(); diff --git a/src/pages/CourseDetail.jsx b/src/pages/CourseDetail.tsx similarity index 100% rename from src/pages/CourseDetail.jsx rename to src/pages/CourseDetail.tsx diff --git a/src/pages/DesignSystem.jsx b/src/pages/DesignSystem.tsx similarity index 99% rename from src/pages/DesignSystem.jsx rename to src/pages/DesignSystem.tsx index c19718f..6e4eed5 100644 --- a/src/pages/DesignSystem.jsx +++ b/src/pages/DesignSystem.tsx @@ -30,8 +30,14 @@ import { AXIS_PROPS, CHART_TONES } from '@/components/ds'; * place. Not linked from the product navigation — reach it at /design-system. */ -/** @param {any} props */ -function Spec({ title, description, children, className = '' }) { +interface SpecProps { + title?: string; + description?: any; + children: React.ReactNode; + className?: string; +} + +function Spec({ title, description, children, className = '' }: SpecProps) { return ( @@ -40,8 +46,12 @@ function Spec({ title, description, children, className = '' }) { ); } -/** @param {any} props */ -function Row({ label = '', children }) { +interface RowProps { + label?: string; + children: React.ReactNode; +} + +function Row({ label = '', children }: RowProps) { return (
{label && {label}} diff --git a/src/pages/EmployeeDashboard.jsx b/src/pages/EmployeeDashboard.tsx similarity index 100% rename from src/pages/EmployeeDashboard.jsx rename to src/pages/EmployeeDashboard.tsx diff --git a/src/pages/HiredHistory.jsx b/src/pages/HiredHistory.tsx similarity index 97% rename from src/pages/HiredHistory.jsx rename to src/pages/HiredHistory.tsx index dc46c8a..f403632 100644 --- a/src/pages/HiredHistory.jsx +++ b/src/pages/HiredHistory.tsx @@ -23,7 +23,12 @@ function formatDate(d) { } catch { return ''; } } -function Stars({ count, size = 14 }) { +interface StarsProps { + count?: any; + size?: any; +} + +function Stars({ count, size = 14 }: StarsProps) { return (
{Array.from({ length: 5 }).map((_, i) => ( @@ -215,8 +220,13 @@ export default function HiredHistory() { ); } -/** @param {any} props */ -function StatPill({ label, value, accent = false }) { +interface StatPillProps { + label?: string; + value?: any; + accent?: any; +} + +function StatPill({ label, value, accent = false }: StatPillProps) { return (
{label} diff --git a/src/pages/KrowIdentity.jsx b/src/pages/KrowIdentity.tsx similarity index 98% rename from src/pages/KrowIdentity.jsx rename to src/pages/KrowIdentity.tsx index f2c989c..73b5c86 100644 --- a/src/pages/KrowIdentity.jsx +++ b/src/pages/KrowIdentity.tsx @@ -177,7 +177,13 @@ export default function KrowIdentity() { ); } -function Chips({ items, onRemove, tone = 'gray' }) { +interface ChipsProps { + items?: any; + onRemove?: (...args: any[]) => void; + tone?: any; +} + +function Chips({ items, onRemove, tone = 'gray' }: ChipsProps) { if (!items?.length) return null; const cls = tone === 'blue' ? 'bg-[#EEF3FE] text-[#1E40AF] border-[#DBEAFE]' : 'bg-[#F3F4F6] text-[#374151]'; return ( diff --git a/src/pages/OpportunityDetail.jsx b/src/pages/OpportunityDetail.tsx similarity index 98% rename from src/pages/OpportunityDetail.jsx rename to src/pages/OpportunityDetail.tsx index c6f911f..b344493 100644 --- a/src/pages/OpportunityDetail.jsx +++ b/src/pages/OpportunityDetail.tsx @@ -42,7 +42,12 @@ function matchedCerts(profile, job) { }); } -function Section({ title, children }) { +interface SectionProps { + title?: string; + children: React.ReactNode; +} + +function Section({ title, children }: SectionProps) { return (

{title}

diff --git a/src/pages/Overview.jsx b/src/pages/Overview.tsx similarity index 98% rename from src/pages/Overview.jsx rename to src/pages/Overview.tsx index 8c3c517..fecc117 100644 --- a/src/pages/Overview.jsx +++ b/src/pages/Overview.tsx @@ -16,7 +16,16 @@ const METRICS = [ -function MetricCard({ icon: Icon, value, label, bg, glow, index }) { +interface MetricCardProps { + icon?: any; + value?: any; + label?: string; + bg?: any; + glow?: any; + index?: number; +} + +function MetricCard({ icon: Icon, value, label, bg, glow, index }: MetricCardProps) { const isYellow = bg === 'bg-[#F9E547]'; return ( { - return new Promise((resolve) => { + return new Promise((resolve) => { if (!ttsEnabled || !window.speechSynthesis) { resolve(); return; } window.speechSynthesis.cancel(); const u = new SpeechSynthesisUtterance(text); @@ -529,8 +533,14 @@ export default function Owliver({ compact = false }) { ); } -/** @param {any} props */ -function SummaryField({ label, value, full = false, empty = '—' }) { +interface SummaryFieldProps { + label?: string; + value?: any; + full?: any; + empty?: any; +} + +function SummaryField({ label, value, full = false, empty = '—' }: SummaryFieldProps) { const has = value && String(value).trim(); return (
@@ -540,7 +550,12 @@ function SummaryField({ label, value, full = false, empty = '—' }) { ); } -function DnaScore({ label, value }) { +interface DnaScoreProps { + label?: string; + value?: any; +} + +function DnaScore({ label, value }: DnaScoreProps) { const v = typeof value === 'number' ? value : 0; const tone = v >= 75 ? 'text-[#16A34A]' : v >= 50 ? 'text-[#0838E0]' : v > 0 ? 'text-[#D97706]' : 'text-[#9CA3AF]'; return ( diff --git a/src/pages/Positions.jsx b/src/pages/Positions.tsx similarity index 100% rename from src/pages/Positions.jsx rename to src/pages/Positions.tsx diff --git a/src/pages/Profile.jsx b/src/pages/Profile.tsx similarity index 100% rename from src/pages/Profile.jsx rename to src/pages/Profile.tsx diff --git a/src/pages/TalentPool.jsx b/src/pages/TalentPool.tsx similarity index 100% rename from src/pages/TalentPool.jsx rename to src/pages/TalentPool.tsx diff --git a/src/pages/WorkerProfile.jsx b/src/pages/WorkerProfile.tsx similarity index 100% rename from src/pages/WorkerProfile.jsx rename to src/pages/WorkerProfile.tsx diff --git a/src/pages/admin/AdminRoute.jsx b/src/pages/admin/AdminRoute.tsx similarity index 100% rename from src/pages/admin/AdminRoute.jsx rename to src/pages/admin/AdminRoute.tsx diff --git a/src/pages/admin/Login.jsx b/src/pages/admin/Login.tsx similarity index 97% rename from src/pages/admin/Login.jsx rename to src/pages/admin/Login.tsx index a3a268f..acc527d 100644 --- a/src/pages/admin/Login.jsx +++ b/src/pages/admin/Login.tsx @@ -33,12 +33,14 @@ export default function AdminLogin() { const [form, setForm] = useState({ email: DEMO_EMAIL, password: '', remember: true }); const [showPassword, setShowPassword] = useState(false); - const [errors, setErrors] = useState(/** @type {{ email?: string, password?: string }} */ ({})); + const [errors, setErrors] = useState<{ email?: string; password?: string }>({}); const [authError, setAuthError] = useState(''); const [status, setStatus] = useState('idle'); const validate = () => { - const next = {}; + /* Same shape the state carries; built empty and filled per failed rule, so + inference alone fixes it at `{}` and neither assignment compiles. */ + const next: { email?: string; password?: string } = {}; if (!form.email.trim()) next.email = 'Enter your work email'; else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email.trim())) next.email = 'Enter a valid email address'; // Only "did you type anything". A client-side length rule here would be a diff --git a/src/pages/admin/TalentProfile.jsx b/src/pages/admin/TalentProfile.tsx similarity index 97% rename from src/pages/admin/TalentProfile.jsx rename to src/pages/admin/TalentProfile.tsx index 19f2832..bfb14c2 100644 --- a/src/pages/admin/TalentProfile.jsx +++ b/src/pages/admin/TalentProfile.tsx @@ -1,3 +1,4 @@ +import type { ProfileView } from '@/types/views'; import React, { useMemo, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { ArrowLeft, Briefcase, CalendarDays, MapPin, Plus, Sparkles } from 'lucide-react'; @@ -36,7 +37,11 @@ const stageLabel = (application, interviews) => { return 'Applied'; }; -function Facts({ profile }) { +interface FactsProps { + profile: ProfileView; +} + +function Facts({ profile }: FactsProps) { const facts = [ profile.current_position && { icon: Briefcase, label: profile.current_position }, profile.address && { icon: MapPin, label: profile.address }, @@ -56,7 +61,12 @@ function Facts({ profile }) { ); } -function Tags({ title, values = [] }) { +interface TagsProps { + title?: string; + values?: any; +} + +function Tags({ title, values = [] }: TagsProps) { if (!values.length) return null; return (