Remove unused functions and imports from route tests, fix array index key
warnings in skeleton components, apply biome formatting.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add upsertGlobalItemSchema and bulkUpsertGlobalItemsSchema to schemas.ts
- Add UpsertGlobalItemInput and BulkUpsertGlobalItemsInput types to types.ts
- Implement upsertGlobalItem with onConflictDoUpdate and tag sync
- Implement bulkUpsertGlobalItems processing array in single transaction
- Fix migration 0003 to only add new columns + unique constraint
- All 21 tests pass including 8 new upsert operation tests
- OAuth: add userId to oauth_codes schema and migration, derive userId
from stored auth code/token record instead of passing separately
- Auth middleware tests: destructure {db, userId} from createTestDb,
pass userId to createApiKey, fix error message assertion
- MCP tests: add missing await on getCollectionSummary and
createSecondTestUser calls
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix unused function parameters (prefix with _)
- Fix unused imports in test files
- Fix import ordering in test files
- Auto-fix formatting issues across 22 files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Create tags table in schema with id, name (unique), createdAt
- Generate migration for tags table
- Create tag.service.ts with getAllTags (id+name, alphabetical order)
- Create tags.ts route with GET / handler
- Register /api/global-items and /api/tags routes in index.ts
- Add auth skip for GET /api/tags and GET /api/global-items
- Setup service: LEFT JOIN globalItems in getAllSetups totals and getSetupWithItems
- Totals service: LEFT JOIN globalItems in getCategoryTotals and getGlobalTotals
- Profile service: LEFT JOIN globalItems in getPublicProfile totals and getPublicSetupWithItems
- CSV service: LEFT JOIN globalItems in exportItemsCsv for merged name/weight/price
- searchGlobalItems now accepts tagNames param with AND intersection logic
- Owner count uses items.globalItemId instead of removed itemGlobalLinks
- Removed linkItemToGlobal and unlinkItemFromGlobal functions
- Route handlers now async with tags query param support
- Rewrote tests to async PGlite pattern, added tag filtering tests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- getThreadWithCandidates LEFT JOINs globalItems with COALESCE for name, weight, price, image
- createCandidate accepts and stores globalItemId
- resolveThread branches: reference item (globalItemId set) vs standalone (full data copy)
- Removed link/unlink endpoints from items route (replaced by direct globalItemId FK)
- 6 new tests for catalog-linked candidates and branched resolution
- getAllItems and getItemById LEFT JOIN globalItems with COALESCE for name, weight, price, image
- createItem accepts globalItemId and purchasePriceCents, stores brand+model as fallback name
- duplicateItem preserves globalItemId and purchasePriceCents
- updateItem type includes globalItemId and purchasePriceCents
- 10 new tests for reference item creation and merged data retrieval
- Add auth redirect in root layout for unauthenticated users
- Proxy OIDC routes (/login, /callback, /logout) through Vite dev server
- Strip Secure flag from OIDC cookies in dev mode (HTTP localhost)
- Disable retry on auth query to prevent stale cookie loops
- Fix SQLite .get()/.all()/.run() calls in category and global-item
services for PostgreSQL compatibility
- Add userId scoping to category service functions
- Add OIDC error logging in auth middleware
- Apply linter auto-formatting across affected files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- GET /api/users/:id/profile: public profile with public setups (no auth)
- PUT /api/auth/profile: update own profile (requires auth)
- GET /api/setups/:id/public: public setup view with items (no auth)
- Auth middleware skips public profile and public setup GET endpoints
- Register profileRoutes at /api/users in index.ts
- Add getOrCreateUncategorized to category service (Rule 3 fix)
- 10 route tests covering auth, public access, and 404 cases
- updateProfile: update displayName, avatarUrl, bio for a user
- getPublicProfile: return user info with only public setups
- getPublicSetupWithItems: return setup details only if isPublic is true
- createSetup now accepts and persists isPublic field
- updateSetup can toggle isPublic
- getAllSetups includes isPublic in response
- searchGlobalItems with LIKE-based case-insensitive search and wildcard escaping
- getGlobalItemWithOwnerCount with owner count from junction table
- linkItemToGlobal/unlinkItemFromGlobal for item-global linking
- seedGlobalItems idempotent seed from JSON catalog
- Integrated seed into seedDefaults startup
- Replace Bun.write/mkdir with uploadImage() from storage.service
- Remove uploadsDir parameter from fetchImageFromUrl
- Update tests to mock storage service instead of checking filesystem
- All functions accept userId, no more prodDb defaults
- Thread operations verify ownership via and(eq(id), eq(userId))
- Candidate operations verify parent thread ownership before proceeding
- resolveThread includes userId in new item insert and verifies category ownership
- Setup operations use and() for composite id+userId conditions
- syncSetupItems validates both setup and item ownership via inArray
- updateItemClassification and removeSetupItem verify setup ownership
- Auth service: reordered createApiKey params to (db, userId, name)
- verifyApiKey unchanged (already returns { userId } from Plan 01)
- All functions accept userId as second parameter, no more prodDb defaults
- All queries filter by eq(table.userId, userId) for data isolation
- Get-by-id, update, delete use and() for composite id+userId conditions
- deleteCategory uses dynamic getOrCreateUncategorized(db, userId) not hardcoded ID
- CSV import scopes category lookup/creation and item creation to userId
- CSV export filters items by userId
- Category service converted from sync SQLite to async Postgres patterns
- verifyApiKey returns { userId } | null instead of boolean
- verifyAccessToken returns { userId } | null instead of boolean
- Add getOrCreateUser upsert function in auth.service
- Add getOrCreateUncategorized helper in category.service
- requireAuth sets userId on Hono context for all 3 auth methods
- Remove GET bypass: all API routes require auth for userId resolution
- Keep bypass for /api/auth and /api/health paths
The executor agents wrote sync SQLite-style calls (.get(), .all(), .run())
instead of the async Postgres pattern established in Phase 14. Fixed:
- auth.service.ts: use await + destructuring for all DB operations
- auth routes: await listApiKeys
- All auth test files: async createTestDb(), await service calls
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Install @hono/oidc-auth and jose for OIDC integration
- Rewrite requireAuth middleware with three-way auth: API key, MCP Bearer, OIDC session
- Strip auth.service.ts to API key functions only (remove user/session management)
- Remove all references to getUserCount, getSession, refreshSession from middleware
- All beforeEach now use async/await createTestDb()
- All service calls in tests now awaited
- All direct DB calls (.run()/.all()) replaced with await
- All test callbacks made async
- Fixed PostgreSQL GROUP BY strictness in totals.service.ts (categories.name and categories.icon added to groupBy)
- db type changed to 'any' to accommodate PGlite type differences
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>
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 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>
- 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 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
- Add classification text column (default 'base') to setupItems schema
- Add classificationSchema and updateClassificationSchema Zod validators
- Add UpdateClassification type inferred from Zod schema
- Implement updateItemClassification service function
- Modify getSetupWithItems to return classification field
- Modify syncSetupItems to preserve classifications across re-sync
- Add tests for classification CRUD, preservation, and cross-setup independence
- Generate and apply Drizzle migration
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Schema: status TEXT NOT NULL DEFAULT 'researching' on thread_candidates
- Zod: candidateStatusSchema enum (researching/ordered/arrived) added to createCandidateSchema
- Service: getThreadWithCandidates selects status, createCandidate sets status, updateCandidate accepts status
- Client hooks: CandidateWithCategory and CandidateResponse types include status field
- Migration generated and applied
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Run biome check --write --unsafe to fix tabs, import ordering, and
non-null assertions across entire codebase. Disable a11y rules not
applicable to this single-user app. Exclude auto-generated routeTree.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Rename emoji column to icon in schema, Zod schemas, and all services
- Add Drizzle migration with emoji-to-icon data conversion
- Update test helper, seed, and all test files for icon field
- All 87 tests pass with new icon-based schema
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Setup service with CRUD, syncSetupItems, removeSetupItem
- SQL aggregation for itemCount, totalWeight, totalCost via COALESCE
- Hono routes for all 7 endpoints with zValidator
- Mount setupRoutes at /api/setups
- All 87 tests pass (24 new setup tests)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>