Files
GearBox/.planning/phases/35-bug-fixes/35-01-PLAN.md

14 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
35-bug-fixes 01 execute 1
src/client/routes/threads/$threadId/index.tsx
src/client/hooks/useItems.ts
src/client/routes/login.tsx
true
FIX-01
FIX-02
FIX-04
truths artifacts key_links
Clicking Add Candidate on the thread page opens CatalogSearchOverlay in thread mode
The AddCandidateModal component and addCandidateOpen state are deleted from the thread route file
ItemWithCategory includes imageUrl, dominantColor, cropZoom, cropX, cropY, priceCurrency fields
Navigating to /login immediately redirects to the server /login route with no intermediate UI
path provides contains
src/client/routes/threads/$threadId/index.tsx Thread detail page — Add Candidate button calls openCatalogSearch('thread') openCatalogSearch
path provides contains
src/client/hooks/useItems.ts ItemWithCategory interface with image fields imageUrl: string | null
path provides contains
src/client/routes/login.tsx Auto-redirect login page window.location.href = "/login"
from to via pattern
thread detail toolbar button useUIStore.openCatalogSearch('thread') onClick handler openCatalogSearch("thread")
from to via pattern
LoginPage useEffect window.location.href = "/login" useEffect with empty deps useEffect.*window.location.href
Three self-contained type/wiring fixes that resolve wrong-modal, missing-image, and login-redirect bugs from the v2.3 backlog.

Purpose: Clear the modal confusion on thread pages (FIX-01), surface item images that the server already returns but the TypeScript type hides (FIX-02), and skip the redundant intermediate login UI (FIX-04). Output: Updated thread route, useItems hook, and login route.

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/phases/35-bug-fixes/35-CONTEXT.md @.planning/phases/35-bug-fixes/35-UI-SPEC.md

From src/client/stores/uiStore.ts:

// Catalog search actions
openCatalogSearch: (mode: "collection" | "thread") => void;
closeCatalogSearch: () => void;
catalogSearchOpen: boolean;
catalogSearchMode: "collection" | "thread" | null;
// Session thread tracking (used by CatalogSearchOverlay to scope to a thread)
catalogSessionThreadId: number | null;
setCatalogSessionThreadId: (id: number | null) => void;

From src/client/routes/threads/$threadId/index.tsx (current state):

  • Line 44: const [addCandidateOpen, setAddCandidateOpen] = useState(false);
  • Line 144: onClick={() => setAddCandidateOpen(true)} — this is the broken Add Candidate button
  • Lines 307-313: {addCandidateOpen && <AddCandidateModal ... />} — the modal to remove
  • Lines 317-639: Full AddCandidateModal component and its interfaces/constants — all to delete

From src/client/hooks/useItems.ts (current state):

  • ItemWithCategory interface (lines 27-43) is missing these fields the server already returns:
    • imageUrl: string | null
    • dominantColor: string | null
    • cropZoom: number | null
    • cropX: number | null
    • cropY: number | null
    • priceCurrency: string | null

From src/client/routes/login.tsx (current state):

  • Renders full card UI with a sign-in button that calls window.location.href = "/login"
  • Has useAuth hook check and a useNavigate for already-authenticated users
  • Both the auth check and full UI need to be removed — replace with immediate useEffect redirect
Task 1: Wire Add Candidate button and delete AddCandidateModal (FIX-01) src/client/routes/threads/$threadId/index.tsx - src/client/routes/threads/$threadId/index.tsx (read the full file — understand current modal state, imports, and FAB wiring pattern) - src/client/stores/uiStore.ts (confirm openCatalogSearch and setCatalogSessionThreadId signatures) Make two changes to src/client/routes/threads/$threadId/index.tsx:

1. Wire the toolbar button (per D-01, D-03):

Replace the openCatalogSearch and setCatalogSessionThreadId Zustand selectors in the component — add these two lines to the existing useUIStore selectors at the top of ThreadDetailPage:

const openCatalogSearch = useUIStore((s) => s.openCatalogSearch);
const setCatalogSessionThreadId = useUIStore((s) => s.setCatalogSessionThreadId);

Delete the addCandidateOpen state (line 44):

// DELETE THIS LINE:
const [addCandidateOpen, setAddCandidateOpen] = useState(false);

Change the toolbar button's onClick from () => setAddCandidateOpen(true) to:

onClick={() => {
  setCatalogSessionThreadId(threadId);
  openCatalogSearch("thread");
}}

Remove the cursor-default: the button already has class string — ensure cursor-pointer is present (the button has no explicit cursor class currently, so browsers default to pointer for <button> — leave as-is, no change needed here).

2. Delete all dead code (per D-02):

Remove from the JSX:

{addCandidateOpen && (
  <AddCandidateModal
    threadId={threadId}
    onClose={() => setAddCandidateOpen(false)}
  />
)}

Delete the entire block from line ~317 to end of file:

  • interface AddCandidateModalProps { ... }
  • interface ModalFormData { ... }
  • const INITIAL_MODAL_FORM: ModalFormData = { ... }
  • function AddCandidateModal({ ... }) { ... } (the entire function, ~300 lines)

Remove any imports that were only used by AddCandidateModal and are no longer needed:

  • useCreateCandidate from ../../../hooks/useCandidates — check if used elsewhere in the file; if only in AddCandidateModal, remove it
  • useCurrency from ../../../hooks/useCurrency — check if used elsewhere; if only in modal, remove it
  • ImageUpload from ../../../components/ImageUpload — check if used elsewhere; if only in modal, remove it

Keep all other imports (CategoryPicker, ComparisonTable, etc.) since they are used in the main page body. cd /home/jlmak/Projects/jlmak/GearBox && bun run lint 2>&1 | grep -E "threads/\$threadId|error" | head -20 <acceptance_criteria> - grep -n "addCandidateOpen" src/client/routes/threads/\$threadId/index.tsx returns no matches - grep -n "AddCandidateModal" src/client/routes/threads/\$threadId/index.tsx returns no matches - grep -n "openCatalogSearch" src/client/routes/threads/\$threadId/index.tsx shows at least one match - grep -n "setCatalogSessionThreadId" src/client/routes/threads/\$threadId/index.tsx shows at least one match - bun run lint passes with no errors on the modified file </acceptance_criteria> Thread detail page Add Candidate button calls openCatalogSearch("thread") with the current threadId set as catalogSessionThreadId. The AddCandidateModal and all associated dead code (interfaces, constants, component function) are deleted.

Task 2: Extend ItemWithCategory interface with image fields (FIX-02) src/client/hooks/useItems.ts - src/client/hooks/useItems.ts (read fully — see current ItemWithCategory interface at lines 27-43) Add the six missing fields to the `ItemWithCategory` interface in `src/client/hooks/useItems.ts` (per D-04).

Current interface ends at line 43. Add these fields before the closing }:

imageUrl: string | null;
dominantColor: string | null;
cropZoom: number | null;
cropX: number | null;
cropY: number | null;
priceCurrency: string | null;

The updated ItemWithCategory interface should be:

interface ItemWithCategory {
  id: number;
  name: string;
  weightGrams: number | null;
  priceCents: number | null;
  quantity: number;
  categoryId: number;
  notes: string | null;
  productUrl: string | null;
  imageFilename: string | null;
  globalItemId: number | null;
  brand: string | null;
  createdAt: string;
  updatedAt: string;
  categoryName: string;
  categoryIcon: string;
  imageUrl: string | null;
  dominantColor: string | null;
  cropZoom: number | null;
  cropX: number | null;
  cropY: number | null;
  priceCurrency: string | null;
}

No server-side changes needed (per D-05) — GET /api/items already returns these fields via withImageUrls(). cd /home/jlmak/Projects/jlmak/GearBox && bun run lint 2>&1 | grep -E "useItems|error" | head -10 <acceptance_criteria> - grep -n "imageUrl: string | null" src/client/hooks/useItems.ts returns a match inside ItemWithCategory - grep -n "dominantColor: string | null" src/client/hooks/useItems.ts returns a match - grep -n "cropZoom: number | null" src/client/hooks/useItems.ts returns a match - grep -n "cropX: number | null" src/client/hooks/useItems.ts returns a match - grep -n "cropY: number | null" src/client/hooks/useItems.ts returns a match - grep -n "priceCurrency: string | null" src/client/hooks/useItems.ts returns a match - bun run lint passes with no errors on the modified file </acceptance_criteria> ItemWithCategory includes all six image and currency fields. TypeScript no longer reports missing properties when collection overview cards pass imageUrl/dominantColor/crop values to ItemCard.

Task 3: Replace login page UI with immediate useEffect redirect (FIX-04) src/client/routes/login.tsx - src/client/routes/login.tsx (read fully — understand current imports, auth check, and full card UI) Replace the entire content of `src/client/routes/login.tsx` with the following (per D-09, UI-SPEC Auth Redirect Contract):
import { createFileRoute } from "@tanstack/react-router";
import { useEffect } from "react";

export const Route = createFileRoute("/login")({
  component: LoginPage,
});

function LoginPage() {
  useEffect(() => {
    window.location.href = "/login";
  }, []);

  return (
    <div className="flex items-center justify-center h-screen">
      <p className="text-sm text-gray-500">Signing in...</p>
    </div>
  );
}

Remove all now-unused imports: useNavigate from @tanstack/react-router, useTranslation from react-i18next, useAuth from ../hooks/useAuth.

The /login server route handles the Logto OIDC redirect. If the user is already authenticated, the server redirects back to /. No client-side auth check is needed (per D-09). cd /home/jlmak/Projects/jlmak/GearBox && bun run lint 2>&1 | grep -E "login|error" | head -10 <acceptance_criteria> - grep -n "window.location.href" src/client/routes/login.tsx returns exactly one match inside useEffect - grep -n "useAuth" src/client/routes/login.tsx returns no matches - grep -n "useNavigate" src/client/routes/login.tsx returns no matches - grep -n "useTranslation" src/client/routes/login.tsx returns no matches - grep -n "SignIn\|signInToGearBox\|redirectDescription" src/client/routes/login.tsx returns no matches (full UI removed) - File line count is under 25 lines: wc -l src/client/routes/login.tsx outputs a number ≤ 25 - bun run lint passes with no errors </acceptance_criteria> LoginPage renders only a minimal "Signing in..." indicator and immediately redirects via useEffect to the server /login route. No intermediate card UI, no auth check, no translation keys.

<threat_model>

Trust Boundaries

Boundary Description
client→server /login Browser navigates to server-controlled route; server issues Logto OIDC redirect

STRIDE Threat Register

Threat ID Category Component Disposition Mitigation Plan
T-35-01 Spoofing login route redirect accept Server /login route is Hono-controlled; client just triggers the navigation. No sensitive data exposed client-side.
T-35-02 Information Disclosure ItemWithCategory type accept Type extension only exposes fields already returned by the API to authenticated users. No new data surface.
</threat_model>
After all three tasks complete:
  1. Navigate to a thread detail page — clicking "Add Candidate" must open CatalogSearchOverlay (not the old modal form)
  2. Confirm no AddCandidateModal UI appears anywhere on thread pages
  3. Collection overview cards with images must display images (imageUrl field now typed correctly)
  4. Navigate to /login (client-side) — page must immediately redirect to Logto, showing only the brief "Signing in..." text

Run: bun run lint — zero errors Run: bun test — all existing tests pass

<success_criteria>

  • Add Candidate toolbar button on thread page opens CatalogSearchOverlay in thread mode
  • AddCandidateModal component is fully deleted (no dead code remaining)
  • ItemWithCategory has imageUrl, dominantColor, cropZoom, cropX, cropY, priceCurrency fields
  • LoginPage is ≤ 25 lines, redirects immediately via useEffect, renders no form UI
  • bun run lint passes with zero errors </success_criteria>
After completion, create `.planning/phases/35-bug-fixes/35-01-SUMMARY.md`