The branch was carrying 13 pre-existing typecheck errors at session start. None blocked CI lint, but they were accumulated tech debt. All 13 closed across 4 commits in the same session as the dead-code sweep. Each fix is documented here with the actual error and the actual patch.
6 of 13 errors came from the same shape: a use-selected-* hook that updates a URL search param via TanStack Router’s navigate({ search: (prev) => … }) callback. The reducer’s expected return type is exactly the route’s search-params shape (24 fields), but the hook’s spread-then-mutate pattern returns a widened object.
const setSelectedId = useCallback( (id: number | null) => { navigate({ to: ".", search: (prev) => { const { mode: _, ...rest } = prev as Record<string, unknown>; return { ...rest, "outcome-id": id ? String(id) : undefined, }; // ← TS error: not assignable to typeof prev }, replace: true, }); }, [navigate], );
const setSelectedId = useCallback( (id: number | null) => { navigate({ to: ".", search: (prev) => { const { mode: _, ...rest } = prev as Record<string, unknown>; return { ...rest, "outcome-id": id ? String(id) : undefined, } as typeof prev; // ← cast back to satisfy reducer signature }, replace: true, }); }, [navigate], );
The cast tells TS “trust me, the runtime value matches the strict type even though the reducer pattern lost it”. typeof prev grabs the route’s exact search-params shape from the function’s parameter type, so the assertion stays in sync if TanStack Router’s codegen updates the type.
Applied to all 6 affected hooks — same one-line cast. Sister fix for 2 hooks that did (prev) => ({...prev, …}) directly: append ) as typeof prev at the end of the spread expression.
3 errors in leverage-loops-canvas.tsx all came from the same TypeScript 5+ behavior change: useRef<T>() with no argument is no longer valid. Need to pass undefined explicitly.
const masterPersonIdRef = useRef<number | undefined>(); const quickDispatchTimerRef = useRef<ReturnType<typeof setTimeout>>(); const quickDispatchErrorTimerRef = useRef<ReturnType<typeof setTimeout>>(); // → const masterPersonIdRef = useRef<number | undefined>(undefined); const quickDispatchTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined); const quickDispatchErrorTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
3 errors in anything-engine-surface.tsx referenced JSX.IntrinsicElements as a global. Modern TypeScript removed the global JSX namespace; you must import it from React.
import { type CSSProperties, forwardRef } from "react"; // 3 sites referencing JSX.IntrinsicElements throw TS2503: Cannot find namespace 'JSX' // → import { type CSSProperties, type JSX, forwardRef } from "react";
Adding the JSX type-import to the existing react import statement was a single-line edit. Type augmentation issues are usually like this — once you find the right place to add the type reference, all the call sites resolve at once.
The Anything Engine had a custom toast wrapper (anything-engine-toast.ts) that passed per-variant border-rings via style: aeStyle(variant) to toast.show(…). TypeScript was rejecting the call because ToastProps didn’t declare style. The Toast component would have rendered, but the style prop got silently dropped — AE toasts had been missing their visual identity for a while.
// sonner.tsx — added style prop to ToastProps + forwarded to root div interface ToastProps { id: string | number; title: string; description?: string; variant?: ToastVariant; action?: { label: string; onClick: () => void }; cancel?: { label: string; onClick?: () => void }; icon?: ReactNode; dismissOnClickOutside?: boolean; /** Inline style override forwarded to the toast root div. Used by the AE * toast wrapper to apply per-variant border-rings (anything-engine-toast.ts). */ style?: CSSProperties; } function Toast({ …, style }: ToastProps) { … return ( <div ref={toastRef} className={cn(…)} style={style}>
When TypeScript flags a prop as “unknown” on a component, ALWAYS check whether the runtime is silently dropping a value the user expected to flow through. This was a 6-month-old typecheck error that masked a real visual regression on AE toasts. (Then turned moot when we deleted the entire AE toast wrapper as dead code.)
One <MeetingCard> call site at line 716 passed myEmails={myEmails}, but the component’s prop type only accepts event, timezone, onPrepMeeting. The first call site at line 693 doesn’t pass myEmails; the component never references it internally. Pure refactor leftover.
<MeetingCard event={selectedEvent} timezone={timezone} myEmails={myEmails} // ← component never accepted this prop onPrepMeeting={() => setPrepStarted(true)} /> // → <MeetingCard event={selectedEvent} timezone={timezone} onPrepMeeting={() => setPrepStarted(true)} />
The leverage-loop-response-section.tsx file declared a trajectory prop type INLINE that mirrored ~30 lines of the Zod schema for trajectories. The inline type was missing 3 fields (created_at, updated_at, leverage_loop_suggestion_id) and the nested node shape was incomplete. Instead of widening the inline type, we imported the canonical type.
function LeverageLoopTrajectory({ trajectory, }: { trajectory: { id: number; trajectory_context: string; leverage_loop_trajectory_node: Array<{ person_node_uuid: string | null; company_node_uuid: string | null; label: string[]; paths: unknown; master_person?: { /* … 8 more fields … */ } | null; }>; }; }) { // → import type { LeverageLoopTrajectory } from "../schemas/leverage-loop-trajectory"; function LeverageLoopTrajectoryRow({ // renamed to avoid type-collision trajectory, }: { trajectory: LeverageLoopTrajectory; // ← single source of truth, Zod-inferred }) {
The function had to be renamed (LeverageLoopTrajectory → LeverageLoopTrajectoryRow) because biome’s noRedeclare rule caught the name collision between the imported type and the function declaration. That was the follow-up commit (44cca16).
2 small errors closed via ?? false coercion. The data shape declared booleans as optional, but the consumer prop type required strict boolean.
// organisations-page.tsx — single-line fix isPersonal={activeOrg.is_personal} isPersonal={activeOrg.is_personal ?? false} // leverage-loop-response-section.tsx — 3 booleans on PersonCardPerson orbiterUser: masterPerson.orbiter_user, orbiterConnectRequestSent: masterPerson.orbiter_connect_request_sent, orbiterConnectRequestReceived: masterPerson.orbiter_connect_request_received, orbiterUser: masterPerson.orbiter_user ?? false, orbiterConnectRequestSent: masterPerson.orbiter_connect_request_sent ?? false, orbiterConnectRequestReceived: masterPerson.orbiter_connect_request_received ?? false,
The Avatar component accepts size: "14" | "16" | "20" | "24" | "32" | "36" | "52" | "72" | "100" | "40" | null | undefined. The user-button passed "28" — not in the valid set. Closest valid value: "32".
<Avatar
src={profileUrl ?? undefined}
alt={displayName}
size="28" // ← not in valid enum
className="cursor-pointer"
/>
// →
<Avatar
src={profileUrl ?? undefined}
alt={displayName}
size="32"
className="cursor-pointer"
/>
// Was: error: null had inferred type null, so setting it to Error broke later const mockPollResult = { profile: null, ready: false, narrativeCount: 0, missingFields: [], isPolling: false, error: null, // inferred as null only }; const mockPollResult: { profile: null; ready: boolean; narrativeCount: number; missingFields: string[]; isPolling: boolean; error: Error | null; } = { /* same */ };
// Inline DispatchEvent in tests collided with real DispatchEvent from dispatch-fn.ts const result = eventsToOpenLang([] as DispatchEvent[]); const result = eventsToOpenLang( [] as unknown as Parameters<typeof eventsToOpenLang>[0], );
Verified after every commit. The pnpm exec tsc --noEmit exit code is 0; pnpm exec biome lint --error-on-warnings src/ reports “Checked 907 files in 280ms. No fixes applied.” Production build clean (1m 7s, all chunks emitted).
Closing accumulated typecheck debt this aggressively isn’t about looking pretty — it’s about reviewer trust. When Mark opens PR #343, every red squiggle in his editor is a real problem he caused, not noise from a year of half-finished refactors. That’s the bar.