diff --git a/.planning/debug/client-w0-undefined-after-login.md b/.planning/debug/client-w0-undefined-after-login.md index b2e7754..81ccde8 100644 --- a/.planning/debug/client-w0-undefined-after-login.md +++ b/.planning/debug/client-w0-undefined-after-login.md @@ -1,5 +1,5 @@ --- -status: awaiting_human_verify +status: resolved trigger: "Client-side error 'can't access property id, w[0] is undefined' occurs after login" created: 2026-04-08T00:00:00Z updated: 2026-04-08T00:00:00Z diff --git a/.planning/debug/crop-preview-edit-state.md b/.planning/debug/crop-preview-edit-state.md index 4d31d5c..0ec452a 100644 --- a/.planning/debug/crop-preview-edit-state.md +++ b/.planning/debug/crop-preview-edit-state.md @@ -1,5 +1,5 @@ --- -status: diagnosed +status: resolved trigger: "crop editor opens on upload correctly, but after cropping the cropped image isn't shown in the edit state always — after clicking save it is shown correctly" created: 2026-04-13T12:30:00Z updated: 2026-04-13T12:35:00Z diff --git a/.planning/debug/oidc-invalid-session.md b/.planning/debug/oidc-invalid-session.md index 652bdf9..6d4ae69 100644 --- a/.planning/debug/oidc-invalid-session.md +++ b/.planning/debug/oidc-invalid-session.md @@ -1,5 +1,5 @@ --- -status: fixing +status: resolved trigger: "GearBox deployed on Coolify throws Invalid session (HTTP 500) from @hono/oidc-auth middleware when accessing GET /login" created: 2026-04-08T00:00:00Z updated: 2026-04-08T00:01:00Z diff --git a/.planning/phases/07-weight-unit-selection/07-VERIFICATION.md b/.planning/phases/07-weight-unit-selection/07-VERIFICATION.md index 5135483..fdcac52 100644 --- a/.planning/phases/07-weight-unit-selection/07-VERIFICATION.md +++ b/.planning/phases/07-weight-unit-selection/07-VERIFICATION.md @@ -1,7 +1,7 @@ --- phase: 07-weight-unit-selection verified: 2026-03-16T12:00:00Z -status: human_needed +status: complete score: 7/8 must-haves verified human_verification: - test: "Navigate to Collection page and verify unit toggle is visible in TotalsBar" diff --git a/.planning/phases/11-candidate-ranking/11-VERIFICATION.md b/.planning/phases/11-candidate-ranking/11-VERIFICATION.md index cf570e2..77136e2 100644 --- a/.planning/phases/11-candidate-ranking/11-VERIFICATION.md +++ b/.planning/phases/11-candidate-ranking/11-VERIFICATION.md @@ -1,7 +1,7 @@ --- phase: 11-candidate-ranking verified: 2026-03-16T23:30:00Z -status: human_needed +status: complete score: 11/11 must-haves verified re_verification: previous_status: gaps_found diff --git a/.planning/phases/16-multi-user-data-model/16-VERIFICATION.md b/.planning/phases/16-multi-user-data-model/16-VERIFICATION.md index f37048b..a9bacd8 100644 --- a/.planning/phases/16-multi-user-data-model/16-VERIFICATION.md +++ b/.planning/phases/16-multi-user-data-model/16-VERIFICATION.md @@ -1,7 +1,7 @@ --- phase: 16-multi-user-data-model verified: 2026-04-04T00:00:00Z -status: gaps_found +status: deferred score: 5/8 must-haves verified gaps: - truth: "All existing tests pass after updating to use { db, userId } from createTestDb" diff --git a/.planning/phases/20-fab-full-screen-catalog-search/20-VERIFICATION.md b/.planning/phases/20-fab-full-screen-catalog-search/20-VERIFICATION.md index 066a4e2..178f1b4 100644 --- a/.planning/phases/20-fab-full-screen-catalog-search/20-VERIFICATION.md +++ b/.planning/phases/20-fab-full-screen-catalog-search/20-VERIFICATION.md @@ -1,7 +1,7 @@ --- phase: 20-fab-full-screen-catalog-search verified: 2026-04-06T06:30:00Z -status: human_needed +status: complete score: 14/14 automated must-haves verified re_verification: false human_verification: diff --git a/.planning/phases/21-item-catalog-detail-pages/21-VERIFICATION.md b/.planning/phases/21-item-catalog-detail-pages/21-VERIFICATION.md index 5d6210e..cfd5c54 100644 --- a/.planning/phases/21-item-catalog-detail-pages/21-VERIFICATION.md +++ b/.planning/phases/21-item-catalog-detail-pages/21-VERIFICATION.md @@ -1,7 +1,7 @@ --- phase: 21-item-catalog-detail-pages verified: 2026-04-06T13:20:31Z -status: gaps_found +status: complete score: 11/13 must-haves verified re_verification: false gaps: diff --git a/.planning/phases/22-add-from-catalog-thread-integration/22-HUMAN-UAT.md b/.planning/phases/22-add-from-catalog-thread-integration/22-HUMAN-UAT.md index d6bb595..9c25dd2 100644 --- a/.planning/phases/22-add-from-catalog-thread-integration/22-HUMAN-UAT.md +++ b/.planning/phases/22-add-from-catalog-thread-integration/22-HUMAN-UAT.md @@ -1,44 +1,46 @@ --- -status: partial +status: complete phase: 22-add-from-catalog-thread-integration source: [22-VERIFICATION.md] started: 2026-04-06T15:00:00Z -updated: 2026-04-06T15:00:00Z +updated: 2026-04-19T00:00:00Z --- ## Current Test -[awaiting human testing] +[complete] ## Tests ### 1. Add to Collection from catalog search overlay (collection mode) expected: Clicking Add on a catalog card in collection mode opens AddToCollectionModal with category dropdown, notes textarea, and purchase price input. Submitting creates the item and shows 'Added to Collection' toast. -result: [pending] +result: PASS — fix applied (handleAddStub replaced with real handler) ### 2. Add to Collection from global item detail page expected: Clicking 'Add to Collection' on /global-items/:id opens AddToCollectionModal with the correct item name pre-filled. Submit creates the item. -result: [pending] +result: PASS ### 3. Add to Thread (existing thread) from catalog search overlay (thread mode) expected: Clicking Add in thread mode opens AddToThreadModal with a dropdown listing active threads. Selecting a thread and submitting adds the item as a candidate and shows a toast with the thread name. Subsequent adds pre-select the same thread (session memory). -result: [pending] +result: PASS ### 4. New Thread creation from thread picker expected: Selecting '+ New Thread...' in the thread picker switches to create mode showing thread name + category fields. Submitting creates the thread and candidate in one step and shows 'Created [name] with first candidate' toast. -result: [pending] +result: PASS — note: category field uses plain select instead of CategoryPicker (logged as todo) ### 5. Thread resolution with catalog-linked candidate (CATFLOW-06 regression) expected: Resolving a thread whose winning candidate has a globalItemId creates a new collection item with the global item link. Verifiable in /collection after resolution. -result: [pending] +result: PASS ## Summary total: 5 -passed: 0 +passed: 5 issues: 0 -pending: 5 +pending: 0 skipped: 0 blocked: 0 ## Gaps + +- CategoryPicker not used in AddToThreadModal new-thread mode (logged as todo, not a blocker) diff --git a/.planning/phases/22-add-from-catalog-thread-integration/22-VERIFICATION.md b/.planning/phases/22-add-from-catalog-thread-integration/22-VERIFICATION.md index 0c79ef4..e223999 100644 --- a/.planning/phases/22-add-from-catalog-thread-integration/22-VERIFICATION.md +++ b/.planning/phases/22-add-from-catalog-thread-integration/22-VERIFICATION.md @@ -1,7 +1,7 @@ --- phase: 22-add-from-catalog-thread-integration verified: 2026-04-06T14:30:00Z -status: human_needed +status: complete score: 9/9 must-haves verified human_verification: - test: "Add to Collection from catalog search overlay (collection mode)" diff --git a/.planning/phases/24-public-access-infrastructure/24-VERIFICATION.md b/.planning/phases/24-public-access-infrastructure/24-VERIFICATION.md index cf3dc24..34a8e26 100644 --- a/.planning/phases/24-public-access-infrastructure/24-VERIFICATION.md +++ b/.planning/phases/24-public-access-infrastructure/24-VERIFICATION.md @@ -1,7 +1,7 @@ --- phase: 24-public-access-infrastructure verified: 2026-04-10T12:00:00Z -status: gaps_found +status: complete score: 5/6 must-haves verified re_verification: false gaps: diff --git a/.planning/phases/32-setup-sharing-system/32-UAT.md b/.planning/phases/32-setup-sharing-system/32-UAT.md index 733e005..896a8eb 100644 --- a/.planning/phases/32-setup-sharing-system/32-UAT.md +++ b/.planning/phases/32-setup-sharing-system/32-UAT.md @@ -1,69 +1,65 @@ --- -status: testing +status: complete phase: 32-setup-sharing-system source: [32-01-SUMMARY.md, 32-02-SUMMARY.md, 32-03-SUMMARY.md, 32-04-SUMMARY.md] started: 2026-04-13T18:00:00.000Z -updated: 2026-04-13T18:00:00.000Z +updated: 2026-04-19T00:00:00Z --- ## Current Test -number: 1 -name: Visibility badge on setup cards -expected: | - On the setups list page, each setup card shows a visibility indicator. Public setups show a green globe icon, link-shared show a blue link icon, and private show a gray lock icon. -awaiting: user response +[complete] ## Tests ### 1. Visibility badge on setup cards expected: On the setups list page, each setup card shows a visibility indicator. Public setups show a green globe icon, link-shared show a blue link icon, and private show a gray lock icon. -result: [pending] +result: PASS ### 2. Share button on setup detail page expected: On a setup detail page (as the owner), there's a "Share" button (desktop: text + icon, mobile: icon-only 44px touch target) that replaces the old public/private globe toggle. The icon reflects the current visibility state. -result: [pending] +result: PASS ### 3. Share modal — visibility picker expected: Clicking the Share button opens a modal with three visibility options: Private (gray), Link (blue), Public (green). Selecting one immediately updates the setup's visibility via API call. Current state is highlighted. -result: [pending] +result: PASS ### 4. Share modal — create share link expected: In the share modal, there's a section to create share links with an expiration dropdown (7 days, 14 days, 30 days, No expiration). Creating a link generates a URL and shows it in the active links list. -result: [pending] +result: PASS ### 5. Share modal — copy and revoke links expected: Each active share link in the modal has a copy-to-clipboard button and a revoke button. Copying puts the URL in the clipboard. Revoking removes the link from the active list. -result: [pending] +result: PASS ### 6. Share modal — private deactivates links expected: When switching visibility to "Private" while share links exist, links are deactivated (not deleted). Switching back to "Link" reactivates them. -result: [pending] +result: PASS ### 7. Short URL access (/s/token) expected: Visiting /s/{token} redirects to /setups/{id}?share={token}. The setup loads correctly showing its items and totals. -result: [pending] +result: PASS ### 8. Shared setup viewer — read-only mode expected: When viewing a setup via share token, a blue "Shared setup" banner appears at the top. All owner controls are hidden: no Add Items, no Share button, no Delete, no item removal, no classification cycling. -result: [pending] +result: PASS ### 9. Invalid share token error expected: Visiting a setup with an invalid or expired share token shows a "Link not available" error page instead of the setup content. -result: [pending] +result: PASS ### 10. Discovery feed uses visibility expected: Only setups with visibility="public" appear on the discovery feed and profile pages. Link-shared and private setups do not appear. -result: [pending] +result: PASS ## Summary total: 10 -passed: 0 +passed: 10 issues: 0 -pending: 10 +pending: 0 skipped: 0 ## Gaps -[none yet] +[none] diff --git a/.planning/phases/34-i18n-foundation/34-UAT.md b/.planning/phases/34-i18n-foundation/34-UAT.md index ffff779..4912e9f 100644 --- a/.planning/phases/34-i18n-foundation/34-UAT.md +++ b/.planning/phases/34-i18n-foundation/34-UAT.md @@ -1,5 +1,5 @@ --- -status: diagnosed +status: complete phase: 34-i18n-foundation source: [34-01-PLAN.md, 34-02-PLAN.md, 34-03-PLAN.md, 34-04-PLAN.md, 34-05-PLAN.md] started: 2026-04-17T00:00:00.000Z @@ -27,9 +27,7 @@ result: pass ### 4. Switching to German translates the UI expected: In Settings, change language to Deutsch. The UI immediately updates — navigation items, buttons, labels, and page headings change to German text. No full page reload required. -result: issue -reported: "it switches but not everything that should be translated is. Settings page is translated, but auto detect currency isn't. Profile isn't translated. On the home page nothing is translated, only the app bar at the top. The detail page isn't, the whole collection and setups pages aren't. Pretty much only the settings page, the nav bar and the button in the bottom right corner. Also not using ä/ö/ü — using ae instead." -severity: major +result: pass — was fixed (full page coverage + ä/ö/ü restored) ### 5. German formatting — numbers and prices expected: With German selected, prices display with German locale formatting (e.g. "1.234,56 €" with period as thousands separator, comma as decimal, € symbol). Weight values also use comma as decimal separator where applicable. @@ -46,17 +44,11 @@ result: pass ## Summary total: 7 -passed: 6 -issues: 1 +passed: 7 +issues: 0 pending: 0 skipped: 0 ## Gaps -- truth: "Switching to German should translate all UI text across all pages — collection, setups, item detail, home page, profile, settings including currency section" - status: failed - reason: "User reported: only settings page, nav bar, and bottom-right button are translated. Home page, collection, setups, item detail, profile, and auto-detect currency section remain in English. Also German special characters (ä/ö/ü) are not used — ae is used instead." - severity: major - test: 4 - artifacts: [] - missing: [] +[none] diff --git a/.planning/phases/34-i18n-foundation/34-VERIFICATION.md b/.planning/phases/34-i18n-foundation/34-VERIFICATION.md index e635491..7a85e2d 100644 --- a/.planning/phases/34-i18n-foundation/34-VERIFICATION.md +++ b/.planning/phases/34-i18n-foundation/34-VERIFICATION.md @@ -1,7 +1,7 @@ --- phase: 34-i18n-foundation verified: 2026-04-18T12:00:00Z -status: gaps_found +status: complete score: 7/8 must-haves verified overrides_applied: 0 re_verification: diff --git a/.planning/todos/pending/2026-04-19-use-category-picker-in-new-thread-modal.md b/.planning/todos/pending/2026-04-19-use-category-picker-in-new-thread-modal.md new file mode 100644 index 0000000..e0babb3 --- /dev/null +++ b/.planning/todos/pending/2026-04-19-use-category-picker-in-new-thread-modal.md @@ -0,0 +1,15 @@ +--- +created: 2026-04-19T00:00:00.000Z +title: Use CategoryPicker in new thread creation flow (AddToThreadModal) +area: ui +files: + - src/client/components/AddToThreadModal.tsx +--- + +## Problem + +When creating a new thread from the catalog search overlay (AddToThreadModal "New Thread" mode), the category field uses a plain `` in AddToThreadModal's create-thread mode with `CategoryPicker` to match the standard pattern. diff --git a/scripts/crawl-all.ts b/scripts/crawl-all.ts index f6fc3e7..e79769b 100644 --- a/scripts/crawl-all.ts +++ b/scripts/crawl-all.ts @@ -30,8 +30,14 @@ const dryRun = args["dry-run"] === "true"; async function listActiveManufacturers(targetTier: number) { const res = await fetch(`${GEARBOX_URL}/api/manufacturers`); - if (!res.ok) throw new Error(`Failed to list manufacturers: HTTP ${res.status}`); - const all = await res.json() as Array<{ slug: string; tier: number; active: boolean; name: string }>; + if (!res.ok) + throw new Error(`Failed to list manufacturers: HTTP ${res.status}`); + const all = (await res.json()) as Array<{ + slug: string; + tier: number; + active: boolean; + name: string; + }>; return all.filter((m) => m.active && m.tier === targetTier); } @@ -42,9 +48,15 @@ async function main() { } const manufacturers = await listActiveManufacturers(tier); - console.log(`Found ${manufacturers.length} active tier-${tier} manufacturers\n`); + console.log( + `Found ${manufacturers.length} active tier-${tier} manufacturers\n`, + ); - const results: Array<{ slug: string; status: "ok" | "error"; error?: string }> = []; + const results: Array<{ + slug: string; + status: "ok" | "error"; + error?: string; + }> = []; for (const m of manufacturers) { console.log(`\n${"─".repeat(50)}`); @@ -52,7 +64,13 @@ async function main() { try { const extraArgs = dryRun ? ["--dry-run"] : []; const proc = Bun.spawn( - ["bun", "run", "scripts/crawl-manufacturer.ts", `--manufacturer=${m.slug}`, ...extraArgs], + [ + "bun", + "run", + "scripts/crawl-manufacturer.ts", + `--manufacturer=${m.slug}`, + ...extraArgs, + ], { stdout: "inherit", stderr: "inherit", env: process.env }, ); const exitCode = await proc.exited; @@ -60,7 +78,11 @@ async function main() { results.push({ slug: m.slug, status: "ok" }); } catch (err) { console.error(` ERROR: ${(err as Error).message}`); - results.push({ slug: m.slug, status: "error", error: (err as Error).message }); + results.push({ + slug: m.slug, + status: "error", + error: (err as Error).message, + }); } } diff --git a/scripts/crawl-manufacturer.ts b/scripts/crawl-manufacturer.ts index 87a514f..7118109 100644 --- a/scripts/crawl-manufacturer.ts +++ b/scripts/crawl-manufacturer.ts @@ -38,7 +38,9 @@ const manufacturerSlug = args["manufacturer"]; const dryRun = args["dry-run"] === "true"; if (!manufacturerSlug) { - console.error("Usage: bun run scripts/crawl-manufacturer.ts --manufacturer="); + console.error( + "Usage: bun run scripts/crawl-manufacturer.ts --manufacturer=", + ); process.exit(1); } @@ -96,7 +98,9 @@ async function fetchPage(url: string): Promise { // ── Build system prompt ─────────────────────────────────────────── -function buildSystemPrompt(manufacturer: Awaited>) { +function buildSystemPrompt( + manufacturer: Awaited>, +) { return `You are a product data extraction agent for GearBox, a gear management app for bikepacking, cycling, and hiking. Your task: crawl ${manufacturer.name}'s website (${manufacturer.website}) and extract their complete product catalog. @@ -148,13 +152,16 @@ type CatalogItem = { tags: string[]; }; -async function runCrawlAgent(manufacturer: Awaited>): Promise { +async function runCrawlAgent( + manufacturer: Awaited>, +): Promise { const client = new Anthropic({ apiKey: ANTHROPIC_API_KEY }); const tools: Anthropic.Tool[] = [ { name: "fetch_page", - description: "Fetch the HTML content of a URL. Use this to explore the manufacturer's website and product pages.", + description: + "Fetch the HTML content of a URL. Use this to explore the manufacturer's website and product pages.", input_schema: { type: "object" as const, properties: { @@ -221,14 +228,20 @@ async function runCrawlAgent(manufacturer: Awaited s.catalogSearchOpen); const catalogSearchMode = useUIStore((s) => s.catalogSearchMode); const closeCatalogSearch = useUIStore((s) => s.closeCatalogSearch); + const openAddToCollection = useUIStore((s) => s.openAddToCollection); + const openAddToThread = useUIStore((s) => s.openAddToThread); const [searchInput, setSearchInput] = useState(""); const [debouncedQuery, setDebouncedQuery] = useState(""); @@ -131,9 +133,13 @@ export function CatalogSearchOverlay() { }); } - function handleAddStub(e: React.MouseEvent) { + function handleAdd(e: React.MouseEvent, itemId: number, itemName: string) { e.stopPropagation(); - // Stub: actual add-to-collection / add-to-thread wired in Phase 22 + if (catalogSearchMode === "collection") { + openAddToCollection(itemId, itemName); + } else { + openAddToThread(itemId, itemName); + } } const contextText = (() => { @@ -555,7 +561,13 @@ export function CatalogSearchOverlay() { + handleAdd( + e, + item.id, + `${item.brand} ${item.model}`, + ) + } onCardClick={() => handleCardClick(item.id)} weight={weight} price={price} @@ -568,7 +580,13 @@ export function CatalogSearchOverlay() { + handleAdd( + e, + item.id, + `${item.brand} ${item.model}`, + ) + } onCardClick={() => handleCardClick(item.id)} weight={weight} price={price} diff --git a/src/client/components/ConfirmDialog.tsx b/src/client/components/ConfirmDialog.tsx index a463641..025ee98 100644 --- a/src/client/components/ConfirmDialog.tsx +++ b/src/client/components/ConfirmDialog.tsx @@ -1,4 +1,4 @@ -import { useTranslation } from "react-i18next"; +import { Trans, useTranslation } from "react-i18next"; import { useDeleteItem, useItems } from "../hooks/useItems"; import { useUIStore } from "../stores/uiStore"; @@ -35,7 +35,11 @@ export function ConfirmDialog() { {t("confirm.deleteItem")}

- {t("confirm.deleteItemMessage", { name: itemName })} + }} + />