The MCP auth spec (2025-06-18+) requires /.well-known/oauth-protected-resource
in addition to /.well-known/oauth-authorization-server. Claude fetches
the protected resource metadata first after receiving a 401, then discovers
the authorization server from it. Also fixes WWW-Authenticate header to
use absolute URL pointing to the protected resource endpoint.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
verifyAccessToken is async and returns a Promise. Without await,
the Promise object is always truthy, so any Bearer token (even
invalid ones) was accepted. This fixes MCP OAuth authentication.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Required for claude.ai browser-based OAuth flows that make
cross-origin requests to discovery, token, and MCP endpoints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add well-known metadata, dynamic client registration, authorization
flow with PKCE, and token exchange/refresh endpoints with route-level
integration tests.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The MCP SDK v1.29.0 changed server.tool() to require Zod schemas
(raw shapes) instead of plain JSON Schema objects. The old format
triggered "expected a Zod schema or ToolAnnotations" errors.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the plain "Sign out" button in the header with a user icon
that opens a dropdown menu containing Settings and Sign out options.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Quantity was missing from three places in item.service.ts:
- getAllItems didn't select it (API returned undefined)
- createItem didn't pass it to insert (always used DB default of 1)
- updateItem type didn't include it (silently stripped from updates)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
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>