- admin-tags.ts: wrap createTag in try/catch, detect UNIQUE constraint violations and return 409 with friendly message
- tags/index.tsx: surface server error message in catch block via err.message (ApiError carries the message from response body)
- tags/index.tsx: replace bare form row with card-style wrapper — label for Name and Parent, card border/bg, shrink-0 submit button
- images.ts: import getImageUrl from storage service, call after fetchImageFromUrl and include presignedUrl in response
- $itemId.tsx: update handleFetchFromUrl to use presignedUrl and dominantColor from response, set imageUrl in form state so ImageUpload component shows preview immediately
Crop values were stored in DB but never passed back to ImageUpload on reload,
causing images to revert to object-contain. Now both admin and user item pages
pass persisted crop data so the cropped view displays correctly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Admin item edit page now uses the same ImageUpload component as the rest
of the app (file upload, preview, crop editor). Also adds a "Fetch from URL"
input that uses /api/images/from-url. Renames "Source URL" to "Product Page URL".
Backend updated to accept imageFilename, dominantColor, cropZoom/X/Y fields
and return presignedImageUrl for display.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Admin item/tag edit pages weren't rendering because TanStack Router treated
them as children of the list route (which had no Outlet). Moving to
directory-based routing (items/index.tsx + items/$itemId.tsx) makes them
siblings that render directly in the admin layout.
Also adds UAT results for phases 35-38 and backlog item 999.12 (Admin UX Polish).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Create admin-tags.ts with GET list, GET single, POST, PUT (cycle guard → 400), DELETE
- Register /tags route in admin.ts
- Add 13-test integration suite covering CRUD, cycle detection, orphan behavior
- Add parentId self-ref FK to tags table (ON DELETE SET NULL)
- Generate Drizzle migration 0010_yielding_random.sql
- Extend tag.service.ts with getAdminTags, getTagWithCounts, createTag, updateTag, deleteTag, isDescendant
- Add service tests (14 tests, all pass)
- Add listGlobalItemsForAdmin: paginated with batched tag/ownerCount queries
- Add updateGlobalItemById: partial update in transaction, syncs tags
- Add deleteGlobalItem: nullifies FK refs, removes tag associations before delete
- Create src/server/routes/admin-items.ts with GET/GET:id/PUT/DELETE endpoints
- Mount adminItemRoutes at /items in admin.ts (protected by requireAuth+requireAdmin)
- Extend global-item.service.test.ts with 13 new tests (all passing)
Closes ADMN-02, ADMN-03, ADMN-04 (server side)
- Import eq from drizzle-orm and users from schema
- Export requireAdmin(c, next) that returns 401 if userId not in context, 403 if user.isAdmin is falsy
- Add useState(false) loaded state to ItemCard, CandidateCard, GlobalItemCard
- Show bg-gray-100 animate-pulse skeleton overlay while image loads
- Fade in image via transition-opacity duration-200 on onLoad callback
- No-image placeholders (icon on bg-gray-50) unchanged
- Add import { useState } from react to all three files with correct Biome import order
- Add optional onLoad prop to GearImageProps interface
- Destructure onLoad in function signature
- Forward loading="lazy" and onLoad to all three img render paths (cover, hasCrop, default)
- Remove auth check, useNavigate, useTranslation, and full card UI
- LoginPage now renders only "Signing in..." and immediately navigates to server /login
- Server /login route handles Logto OIDC redirect; no client-side logic needed
- Add imageUrl, dominantColor, cropZoom, cropX, cropY, priceCurrency to interface
- Server already returns these fields via withImageUrls(); type was just incomplete
- Replace setAddCandidateOpen(true) with openCatalogSearch("thread") + setCatalogSessionThreadId
- Remove addCandidateOpen useState
- Delete entire AddCandidateModal component (~300 lines of dead code)
- Remove imports only used by the deleted modal: useCreateCandidate, useCurrency, ImageUpload, CategoryPicker
Replaces plain <select> with CategoryPicker for consistency with
ManualEntryForm, CreateThreadModal, and other thread creation flows.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- storage.service.ts: use dynamic import() inside each function so the
current @aws-sdk mock is always picked up regardless of module load order
- images.test.ts + image.service.test.ts: replace module-level storage.service
mock with @aws-sdk/client-s3 mock to avoid contaminating storage.service.test.ts
- routes/auth.test.ts: remove unnecessary oauth.service mock (no test uses
verifyAccessToken) which was contaminating oauth.service.test.ts
- middleware/auth.test.ts: complete oauth.service mock shape with all exports
All 464 tests now pass in a single bun test run.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- CatalogSearchOverlay: replace handleAddStub with real openAddToCollection/openAddToThread routing based on catalogSearchMode
- ConfirmDialog + __root.tsx: swap t() for Trans component on deleteItemMessage, deleteCandidateMessage, pickWinnerMessage — fixes <bold> rendering as literal text
- Biome format pass: fix 23 lint/format errors across scripts, services, tests
- Planning: mark all UAT and verification gaps resolved for phases 07, 11, 16, 20, 21, 22, 24, 32, 34; close debug sessions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace brand text field with manufacturerSlug in DEV_GLOBAL_ITEMS,
global-items-seed.json, and seed-global-items.ts. Add DEV_MANUFACTURERS
for dev-only brands not in SEED_MANUFACTURERS. Expand SEED_MANUFACTURERS
with 8 additional manufacturers referenced by seed JSON (Nemo, Therm-a-Rest,
Toaks, Katadyn, HydraPak, Nitecore, Outdoor Research, Exposure Lights).
Update dev-seed.ts to resolve slug→id before insert and use manufacturerId
as the deduplication key.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Biome auto-fix for formatting (line length, ternary wrapping) and
import organization in files touched by phase 34 i18n work.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace hardcoded English strings in SetupsView.tsx with t() calls
using existing setups namespace keys. Closes the 1 gap found during
phase 34 verification.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>