From 867fca78eb31572c9043bc954457aec00dbced0c Mon Sep 17 00:00:00 2001 From: hoce1n Date: Sat, 8 Aug 2026 14:55:43 +0000 Subject: [PATCH] fix(devtools): use stable key in PendingLane and add typecheck to CI PendingLane rendered every item with key={undefined} because QueueItem has no key field, breaking React reconciliation for the Web APIs lane. Build a stable key from the item id (p) matching the pattern already used in Workbench for the same data. Also close the TypeScript CI gap: - add "typecheck": "tsc --noEmit" to package.json - run it in CI between lint and build - fix all pre-existing tsc errors so the typecheck passes cleanly: - dataset/search index-signature access (noPropertyAccessFromIndexSignature) - optional props that receive explicit undefined (exactOptionalPropertyTypes) - 404 Link missing required search param Co-authored-by: monkeycode-ai --- .github/workflows/ci.yml | 3 +++ package.json | 1 + src/components/devtools/AppShell.tsx | 4 ++-- src/components/devtools/LoopDiagram.tsx | 2 +- src/components/devtools/PatternLibrary.tsx | 6 +++--- src/components/devtools/SourcesView.tsx | 4 ++-- src/components/devtools/Timeline.tsx | 4 ++-- src/routes/__root.tsx | 1 + src/routes/index.tsx | 7 ++++--- 9 files changed, 19 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 46bb5da..1988b9e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,5 +40,8 @@ jobs: - name: Lint run: bun run lint + - name: Typecheck + run: bun run typecheck + - name: Build run: bun run build diff --git a/package.json b/package.json index d4bed7b..0fd2ce1 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "build:dev": "vite build --mode development", "generate:sitemap": "bun run scripts/generate-sitemap.ts", "preview": "vite preview", + "typecheck": "tsc --noEmit", "lint": "eslint .", "test": "bun test", "format": "prettier --write ." diff --git a/src/components/devtools/AppShell.tsx b/src/components/devtools/AppShell.tsx index b67b968..34d9851 100644 --- a/src/components/devtools/AppShell.tsx +++ b/src/components/devtools/AppShell.tsx @@ -27,7 +27,7 @@ function useTablistKeyboard(tab: Tab, onTabChange: (tab: Tab) => void) { const buttons = Array.from( navRef.current?.querySelectorAll('[role="tab"]') ?? [], ); - const current = buttons.findIndex((b) => b.dataset.tab === tab); + const current = buttons.findIndex((b) => b.dataset["tab"] === tab); if (current === -1 || buttons.length === 0) return; e.preventDefault(); let next: number; @@ -37,7 +37,7 @@ function useTablistKeyboard(tab: Tab, onTabChange: (tab: Tab) => void) { else next = (current - 1 + buttons.length) % buttons.length; const target = buttons[next]; target?.focus(); - const id = target?.dataset.tab as Tab | undefined; + const id = target?.dataset["tab"] as Tab | undefined; if (id) onTabChange(id); }; diff --git a/src/components/devtools/LoopDiagram.tsx b/src/components/devtools/LoopDiagram.tsx index 71465ab..327adc5 100644 --- a/src/components/devtools/LoopDiagram.tsx +++ b/src/components/devtools/LoopDiagram.tsx @@ -153,7 +153,7 @@ function PendingLane({ items, idle }: { items: QueueItem[]; idle: boolean }) {
{items.map((item) => ( {item.label} diff --git a/src/components/devtools/PatternLibrary.tsx b/src/components/devtools/PatternLibrary.tsx index 5c388cd..816a199 100644 --- a/src/components/devtools/PatternLibrary.tsx +++ b/src/components/devtools/PatternLibrary.tsx @@ -20,8 +20,8 @@ export function PatternLibrary({ onConsumeFocusedPattern, }: { onRun: (code: string) => void; - focusedPattern?: string; - onConsumeFocusedPattern?: () => void; + focusedPattern?: string | undefined; + onConsumeFocusedPattern?: (() => void) | undefined; }) { const total = INTERVIEW_PATTERNS.length; @@ -75,7 +75,7 @@ function PatternRow({ pattern: InterviewPattern; onRun: (code: string) => void; focused: boolean; - onConsumeFocus?: () => void; + onConsumeFocus?: (() => void) | undefined; }) { const [open, setOpen] = useState(false); diff --git a/src/components/devtools/SourcesView.tsx b/src/components/devtools/SourcesView.tsx index 8e054d6..6283706 100644 --- a/src/components/devtools/SourcesView.tsx +++ b/src/components/devtools/SourcesView.tsx @@ -12,8 +12,8 @@ export function SourcesView({ }: { activeEra: string; expandedConcept: string | null; - focusedPattern?: string; - onConsumeFocusedPattern?: () => void; + focusedPattern?: string | undefined; + onConsumeFocusedPattern?: (() => void) | undefined; onSelectEra: (eraId: string) => void; onToggleConcept: (conceptId: string | null) => void; onRun: (code: string) => void; diff --git a/src/components/devtools/Timeline.tsx b/src/components/devtools/Timeline.tsx index 5002e5f..16b675b 100644 --- a/src/components/devtools/Timeline.tsx +++ b/src/components/devtools/Timeline.tsx @@ -34,7 +34,7 @@ export function Timeline({ const buttons = Array.from( railRef.current?.querySelectorAll('[data-era]') ?? [], ); - const current = buttons.findIndex((b) => b.dataset.era === era.id); + const current = buttons.findIndex((b) => b.dataset["era"] === era.id); if (current === -1 || buttons.length === 0) return; e.preventDefault(); let next: number; @@ -43,7 +43,7 @@ export function Timeline({ else if (e.key === "ArrowDown") next = (current + 1) % buttons.length; else next = (current - 1 + buttons.length) % buttons.length; const target = buttons[next]; - const id = target?.dataset.era; + const id = target?.dataset["era"]; if (!id) return; target.focus(); onSelectEra(id); diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx index 52e4a6a..cf1e818 100644 --- a/src/routes/__root.tsx +++ b/src/routes/__root.tsx @@ -25,6 +25,7 @@ function NotFoundComponent() {
Go home diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 96d76af..a7dac81 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -10,9 +10,10 @@ import { SITE_ORIGIN, SITE_TITLE, SITE_DESCRIPTION, websiteJsonLd } from "@/lib/ const Workbench = lazy(() => import("@/components/devtools/Workbench")); export const Route = createFileRoute("/")({ - validateSearch: (search: Record): { tab: Tab; focusPattern?: string } => ({ - tab: search.tab === "loop" || search.tab === "console" ? search.tab : "timeline", - focusPattern: typeof search.focusPattern === "string" ? search.focusPattern : undefined, + validateSearch: (search: Record): { tab: Tab; focusPattern?: string | undefined } => ({ + tab: search["tab"] === "loop" || search["tab"] === "console" ? search["tab"] : "timeline", + focusPattern: + typeof search["focusPattern"] === "string" ? search["focusPattern"] : undefined, }), head: () => ({ meta: [