Commit Graph

297 Commits

Author SHA1 Message Date
f8a1a00e0a fix: prevent snap-back after drag and click-opens-edit during drag
Two fixes:
- Remove onSettled clearing tempItems before refetch completes,
  let useEffect clear it when fresh server data arrives
- Track isDragging ref to suppress edit panel click after drag
- Remove layout="position" which interfered with reorder detection

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 18:49:39 +02:00
27c36b6b9a fix: make entire candidate row draggable instead of handle-only
Remove dragControls/dragListener pattern which prevented onReorder
from firing. The whole row is now the drag target with visual feedback
(scale + shadow). Grip icon remains as a visual indicator.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 18:46:52 +02:00
684cfd3789 fix: stabilize drag-to-reorder with layout animation fixes
- Remove transition-all from list items (fights framer-motion layout)
- Add layout="position" to Reorder.Item for proper sibling tracking
- Replace CSS gap with marginBottom (gap confuses layout engine)
- Add explicit short transition duration for snappy reorder

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 18:44:39 +02:00
52751ae9d4 fix: use onDragEnd on Reorder.Item instead of onPointerUp on Group
The previous approach used onPointerUp on the Reorder.Group which
fired unreliably — triggering on non-drag clicks and sometimes not
at all after a drag. Moving to onDragEnd on each Reorder.Item gives
clean, predictable drag-to-reorder behavior.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 18:42:16 +02:00
3fc737c872 fix: add tab navigation to collection page and skip 404 retries
Adds Gear/Planning/Setups pill tabs to the collection page so users
can switch tabs without going back to the dashboard. Also skips
React Query retries on 404 responses for immediate error display.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 18:31:57 +02:00
b993a0a831 fix: skip retries on 404 for single-resource queries
Prevents 10-second loading skeleton when navigating to non-existent
threads, setups, or items. Shows error/not-found state immediately.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 18:28:04 +02:00
15f146ee89 feat: add CSV import/export for gear collection
Some checks failed
CI / ci (pull_request) Failing after 22s
CI / e2e (pull_request) Has been skipped
Adds export (GET /api/items/export) and import (POST /api/items/import) routes
backed by a pure csv.service with no external deps, plus useExportItems/useImportItems
hooks and an Import/Export section in the Settings page.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 18:12:07 +02:00
8c1fe47a99 feat: add setup impact preview UI with delta badges across all views
Adds SetupImpactSelector dropdown and ImpactDeltaBadge inline badge, wired into the thread detail page. Delta badges appear on CandidateListItem, CandidateCard, and ComparisonTable (Weight Impact / Price Impact rows) whenever a setup is selected for comparison.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 18:11:57 +02:00
b9a06dd244 feat: add item duplication with copy-and-edit workflow
Adds POST /api/items/:id/duplicate endpoint, useDuplicateItem hook, and a
Duplicate button on ItemCard (collection view only) that opens the new item
for editing immediately after creation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 18:07:20 +02:00
818db73432 feat: add impact delta computation with TDD tests
Implements computeImpactDeltas pure function with 8 TDD tests covering replace/add/none modes and null weight/price handling. Adds useImpactDeltas hook, categoryId to ThreadWithCandidates, and selectedSetupId state to uiStore.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 18:06:46 +02:00
1a5e6a303e feat: add quantity support to totals, UI, and thread resolution
- totals.service: multiply weight/cost sums by quantity in category and global totals
- setup.service: multiply by quantity in getAllSetups SQL subqueries; expose quantity in getSetupWithItems item list
- thread.service: explicitly pass quantity: 1 when inserting resolved item
- ItemForm: add Quantity number input (min=1, default=1) after price field
- ItemCard: show ×N badge next to item name when quantity > 1
- CollectionView: pass quantity prop to ItemCard in both filtered and grouped views
- $setupId.tsx: pass quantity to ItemCard; multiply by quantity in client-side per-setup totals
- WeightSummaryCard: multiply by quantity in all chart and legend weight calculations
- useItems / useSetups: add quantity to ItemWithCategory / SetupItemWithCategory interfaces

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 18:04:27 +02:00
923a0f66b0 feat: add quantity field to items schema
Add integer quantity column (default 1) to the items table, generate
the corresponding Drizzle migration, and extend createItemSchema /
updateItemSchema with an optional positive-integer quantity field.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 18:04:15 +02:00
c4ce96ce4f test: add E2E tests for threads, auth, and error handling
Also fix CandidateListItem to not use Reorder.Item when isActive=false,
which caused a framer-motion crash on resolved thread detail pages.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 16:23:26 +02:00
0d7c4f476a test: add unit tests for rate limiter middleware 2026-04-03 16:05:54 +02:00
e9d33e59e9 refactor: add useFormatters hook to reduce boilerplate across 14 components
Created useFormatters() combining useWeightUnit + useCurrency + formatWeight/formatPrice
into a single hook returning weight(grams) and price(cents) bound functions plus
raw unit and currency values. Updated all 14 consumer files to use the new hook,
removing the repeated 4-import + 2-hook-call pattern from each.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 15:49:16 +02:00
a3061b22ca refactor: extract tab views from collection route into separate components
Moves CollectionView, PlanningView, and SetupsView out of the 634-line collection/index.tsx into dedicated component files. Pure extraction — zero logic changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 15:37:44 +02:00
1dff6abb3b feat: add error boundary to root route for crash resilience
Adds a TanStack Router error boundary to the root route so rendering errors or uncaught React Query failures show a friendly error page instead of white-screening the app. The error boundary displays a professional error message with a "Try again" button that resets state and invalidates router data.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 15:36:11 +02:00
2dddba9a08 feat: add rate limiting on login and setup endpoints
Implement in-memory rate limiter with 5 attempts per 15-minute window per IP address. Protects brute-force attacks on credential endpoints.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 15:36:03 +02:00
41a2910aeb fix: add centralized error handler for unhandled exceptions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 15:34:51 +02:00
ecff58500e fix: validate route ID parameters, return 400 for invalid IDs
Adds parseId helper in src/server/lib/params.ts and applies it across
all route files so non-positive-integer IDs return 400 instead of
silently passing NaN to services.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 15:34:06 +02:00
3016eb1a1a fix: add explicit DB context middleware for all API routes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 15:31:11 +02:00
4f434f39bf fix: replace @/ path alias with relative imports in MCP server
All checks were successful
CI / ci (push) Successful in 33s
The @/ alias resolves via tsconfig but not in production where
Bun runs server files directly. Use relative paths instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 14:22:23 +02:00
9191f0fe24 fix: resolve all lint errors — exclude generated dirs, auto-fix source
Some checks failed
CI / ci (push) Failing after 20s
Exclude drizzle/ and .planning/ from Biome (generated files with
incompatible formatting). Auto-fix import ordering and formatting
in existing source files.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 14:05:10 +02:00
4148833644 fix: only show onboarding wizard after account setup
The wizard creates categories via POST which requires auth.
Gate the wizard on isAuthenticated so users create their
account first via Sign In, then the wizard appears.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 13:49:44 +02:00
17d76761bb fix: address code review issues — MCP auth, error handling, password route
- MCP auth middleware now rejects requests without API key when users exist
- Image /from-url route distinguishes validation errors (400) from server errors (500)
- Password change route returns 401 when no session cookie instead of crashing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 13:42:34 +02:00
6f51432d42 feat: add MCP server with streamable HTTP transport at /mcp
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 13:38:18 +02:00
8919829167 feat: add MCP tool handlers, definitions, and collection resource
Wrap existing service layer with MCP-compatible tool handlers for items,
categories, threads/candidates, setups, and image fetching. Add collection
summary resource for overview data. All 14 MCP-specific tests passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 13:35:27 +02:00
5bb728e545 feat: add password change and API key management to settings
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 13:30:50 +02:00
511fece4c7 feat: add login button to header and conditional edit UI
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 13:29:01 +02:00
87a367d41b feat: add useAuth hook and login page
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 13:27:23 +02:00
66dc8ec8ee feat: register auth routes and apply write-protection middleware
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 13:25:51 +02:00
e0e7bfce3e feat: add auth routes for login, setup, and API key management
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 13:24:26 +02:00
8138458d8d feat: add auth middleware for write endpoint protection
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 13:22:00 +02:00
7c4fa9d9d2 feat: add auth service with user, session, and API key management
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 13:20:27 +02:00
32c7b41ce5 feat: add users, sessions, and api_keys tables
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 13:18:07 +02:00
b3a13fa974 feat: add POST /api/images/from-url route
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 13:17:10 +02:00
0004329895 feat: add image URL fetching service with tests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 13:15:56 +02:00
d104e9788f feat: add imageSourceUrl to Zod schemas and service functions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 13:14:13 +02:00
1eb4a786ce feat: add imageSourceUrl column to items and threadCandidates
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 13:12:33 +02:00
5b4026d36f feat(12-01): wire compare toggle and ComparisonTable into thread detail
- Extend uiStore candidateViewMode union to include "compare" value
- Add columns-3 compare toggle button, shown only when thread has 2+ candidates
- Hide "Add Candidate" button when in compare view (read-only intent)
- Import and render ComparisonTable when candidateViewMode === "compare"
- Pass displayItems so compare view reflects any pending reorder state
- Existing list/grid views unchanged; all 135 tests pass
2026-03-17 15:30:38 +01:00
e442b33a59 feat(12-01): add ComparisonTable component
- Side-by-side tabular comparison with all 10 attribute rows (Image, Name, Rank, Weight, Price, Status, Link, Notes, Pros, Cons)
- useMemo delta computation: blue-50 highlight on lightest weight, green-50 on cheapest price
- Gray delta string (+Xg, +$X.XX) shown below non-best cells
- Sticky left column with bg-white to prevent bleed-through on horizontal scroll
- Amber tint + trophy icon on winner column for resolved threads
- Em dash for missing weight/price data (never zero)
- Declarative ATTRIBUTE_ROWS array pattern for clean, maintainable row rendering
2026-03-17 15:29:30 +01:00
7e06c8526b fix(11): wire handleDragEnd to Reorder.Group for active threads
onPointerUp was incorrectly placed on the resolved-thread div instead
of the active-thread Reorder.Group, causing drag reorder to not persist.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 22:36:40 +01:00
94c07e79c2 feat(11-02): add view toggle, Reorder.Group drag-to-reorder, and rank badges in grid view
- Thread detail page: list/grid view toggle with LayoutList/LayoutGrid icons
- List view (active threads): Reorder.Group with CandidateListItem for drag-to-reorder
- List view (resolved threads): static CandidateListItem with rank badges, no drag handles
- Grid view: CandidateCard components with rank badges (gold/silver/bronze)
- tempItems pattern prevents React Query flicker during drag
- handleDragEnd fires PATCH /candidates/reorder after drag completes
- View toggle defaults to list view via uiStore candidateViewMode
2026-03-16 22:28:53 +01:00
acfa99516d feat(11-02): add useReorderCandidates hook, candidateViewMode, and CandidateListItem component
- Add useReorderCandidates mutation hook with apiPatch to /candidates/reorder endpoint
- Add candidateViewMode (list|grid) state and setCandidateViewMode to uiStore
- Create CandidateListItem component with drag handle, rank badge, horizontal layout
- Export RankBadge helper (gold/silver/bronze medal icons for top 3)
- Add style prop support to LucideIcon component
- Add pros/cons fields to CandidateWithCategory in useThreads.ts
2026-03-16 22:27:18 +01:00
d6acfcb126 feat(11-01): PATCH /api/threads/:id/candidates/reorder route + tests
- Import reorderCandidatesSchema and reorderCandidates into threads route
- Add PATCH /:id/candidates/reorder route with Zod validation
- Returns 200 + { success: true } on active thread, 400 on resolved thread
- Add 5 route tests: success, order persists, resolved guard, empty array, missing field
2026-03-16 22:22:31 +01:00
f01d71d6b4 feat(11-01): schema, service, and tests for sort_order + reorderCandidates
- Add sortOrder REAL column to threadCandidates schema (default 0)
- Add sort_order column to test helper CREATE TABLE
- Add reorderCandidatesSchema to shared/schemas.ts
- Add ReorderCandidates type to shared/types.ts
- getThreadWithCandidates now orders candidates by sort_order ASC
- createCandidate appends at max sort_order + 1000 (first = 1000)
- Add reorderCandidates service function (transaction, active-only guard)
- Add 5 new tests: ordering, appending, reorder success, resolved guard, missing thread
2026-03-16 22:21:42 +01:00
4f2aefe7a4 feat(10-01): wire pros/cons through client hooks, form, and card indicator
- CandidateResponse: add pros/cons string|null fields
- CandidateForm: add pros/cons to FormData, INITIAL_FORM, pre-fill, payload
- CandidateForm: add Pros/Cons textarea inputs (after Notes, before Product Link)
- CandidateCard: add pros/cons props, render purple +/- Notes badge when present
- Thread detail route: pass pros/cons props to CandidateCard
2026-03-16 21:36:10 +01:00
7a64a1887d feat(10-01): add pros/cons columns through backend
- Add pros/cons nullable TEXT columns to threadCandidates schema
- Generate and apply Drizzle migration (0004_soft_synch.sql)
- Mirror pros/cons columns in test helper CREATE TABLE
- createCandidate: pass pros/cons to values() object
- updateCandidate: add pros/cons to Partial type
- getThreadWithCandidates: include pros/cons in select projection
- createCandidateSchema: add optional pros/cons string fields
2026-03-16 21:32:38 +01:00
9647f5759d feat: redesign weight summary legend and add currency selector
Redesign WeightSummaryCard stats from a disconnected 4-column grid to a
compact legend-style list with color dots, percentages, and a divider
before the total row. Switch chart and legend colors to a neutral gray
palette.

Add a currency selector to settings (USD, EUR, GBP, JPY, CAD, AUD) that
changes the displayed symbol across the app. This is visual only — no
value conversion is performed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 20:33:07 +01:00
d098277797 feat(09-02): add WeightSummaryCard with donut chart and classification subtotals
- Install recharts dependency for donut chart visualization
- Create WeightSummaryCard component with pill toggle (category/classification views)
- Compute base/worn/consumable/total weight subtotals from items array
- Render donut chart with colored segments, center total, and hover tooltips
- Wire WeightSummaryCard into setup detail page below sticky bar
2026-03-16 15:20:41 +01:00