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 |
|
true |
|
|
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.mdFrom 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
AddCandidateModalcomponent and its interfaces/constants — all to delete
From src/client/hooks/useItems.ts (current state):
ItemWithCategoryinterface (lines 27-43) is missing these fields the server already returns:imageUrl: string | nulldominantColor: string | nullcropZoom: number | nullcropX: number | nullcropY: number | nullpriceCurrency: 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
useAuthhook check and auseNavigatefor already-authenticated users - Both the auth check and full UI need to be removed — replace with immediate useEffect redirect
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:
useCreateCandidatefrom../../../hooks/useCandidates— check if used elsewhere in the file; if only inAddCandidateModal, remove ituseCurrencyfrom../../../hooks/useCurrency— check if used elsewhere; if only in modal, remove itImageUploadfrom../../../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.
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.
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> |
- Navigate to a thread detail page — clicking "Add Candidate" must open CatalogSearchOverlay (not the old modal form)
- Confirm no AddCandidateModal UI appears anywhere on thread pages
- Collection overview cards with images must display images (imageUrl field now typed correctly)
- 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>