Imported from igor-siergiej/jewellery-catalogue (
AGENTS.md). Install upstream withnpx skills add igor-siergiej/jewellery-catalogue. Copyright stays with the author.
Jewellery Catalogue: agent notes
Goal
Make it easier to organise and keep track of jewellery designs and the materials that go into them for an Etsy shop, and keep a catalogue of everything listed there.
Use this goal to judge and prioritise work: favour changes that serve it, and question tasks that don't.
Product overview, architecture rationale and CI summary live in README.md. This file covers what you need to change the code safely.
Stack
- Monorepo: Bun workspaces.
packages/web,packages/api,packages/types(shared interfaces and Zod form schemas, imported as@jewellery-catalogue/types) andpackages/etsy-mock(stateful fake of the Etsy v3 API for local dev and e2e).bun.lockis checked in. CI pins Bun to1.4.2. - api: Hono 4 on the Bun runtime.
createAppandAPIErrorcome from@imapps/api-utils/hono. The DI container,MongoDbConnection,ObjectStoreConnection,ConfigServiceandLoggercome from@imapps/api-utils. MongoDB native driver 6 (the connection is api-utils', so driver upgrades go through that package first). MinIO for images. - web: React 19, Vite 8, Tailwind 4, Radix/shadcn (
src/components/ui), TanStack Query, React Router 7, react-hook-form + Zod, tiptap rich text, lucide-react 1.x. PWA viavite-plugin-pwainjectManifest (src/sw.ts).useAuthcomes from@imapps/web-utils. - Tooling: Biome 2.5 (lint and format, run by lint-staged on commit), TypeScript 7
(
strict: false,types: ["bun"]in the root tsconfig), semantic-release, fallow-audit on pre-push.
API layout (packages/api/src)
routes/index.tsholds the route list. Every route is under/api/*and all except/api/healthand the Etsy OAuth callback go throughauthenticate. The resource groups aredesigns,materials,drafts,goals,tasks,user-settings,images/image/:name(authenticateImageRequestalso accepts a token in the query string) andetsy/*. Design Etsy actions live under/api/designs/:id/etsy-*.middleware/auth.tsverifies the bearer token against the Kivo auth service (AUTH_URL/verify) and setsuserIdon the context. Every repository query is scoped byuserId.handlers/<Resource>are the HTTP edge.domain/holds the services and repository interfaces, andinfrastructure/the Mongo, bucket and fal.ai implementations.dependencies/wires the container (DependencyToken).- Domain services: Design, Material, Draft, Goal, Task, UserSettings, Image. The Etsy
cluster is
EtsyConnectionService(OAuth,EtsyOAuthStateStore),EtsyClient,EtsyPushService,EtsyReconcileService,EtsyStatusServiceandEtsyListingCopyService(AI title/description/tags viaFalVisionClient). OnlyEtsy*code talks to Etsy. - Done tasks are purged 30 days after completion by a daily interval in
index.ts. - Logging. The API logs JSON lines to stdout with the
@imapps/api-utilsLogger; on foundry promtail ships container stdout to Loki (Grafana at grafana.imapps.uk, 30-day retention). The web app'sutils/loggerbatchesinfo/warn/errorlines (20 per POST, 5 s delay, flushed withkeepalivewhen the page is hidden) to the authenticatedPOST /api/logs(handlers/Log), which writes them withsource: 'frontend',userIdanduserAgent. Uncaught errors, unhandled rejections and failedmakeRequestcalls (except 401s) are logged automatically. Both sides cap a user at 120 lines a minute. Lines logged before login are dropped. TheLoggernests context in ametaarray, which plain| jsonskips, so extract it by path:{container=~".*jewellerycatalogueapi.*"} | json source="meta[0].source", userId="meta[0].userId" | source="frontend".
Env (packages/api/.env, template packages/api/.env.example)
PORT, AUTH_URL, CONNECTION_URI, DATABASE_NAME, BUCKET_NAME, BUCKET_ACCESS_KEY,
BUCKET_SECRET_KEY, BUCKET_ENDPOINT, ETSY_API_KEY, ETSY_SHARED_SECRET,
ETSY_REDIRECT_URI and WEB_APP_URL are required. FAL_KEY and FAL_MODEL are optional:
without FAL_KEY the "Generate with AI" action reports that it isn't configured.
ETSY_MOCK_URL is optional and local-only: when set, EtsyClient talks to the mock
(bun start:etsy-mock, port 3010) instead of Etsy. ETSY_API_KEY/ETSY_SHARED_SECRET can
then be any non-empty values.
Commands
bun lint # Biome
bun --filter @jewellery-catalogue/api test # API unit tests (bun:test)
bun --filter @jewellery-catalogue/etsy-mock test # Etsy mock unit tests
RUN_INTEGRATION_TESTS=1 bun --filter @jewellery-catalogue/api test integration # needs Mongo on :27018 (docker-compose.test.yml)
bun --filter @jewellery-catalogue/web build
cd packages/web && bun pw:e2e # Playwright: functional + visual
Gotchas:
- Root
bun tsc --noEmitchecks nothing because the root tsconfig hasfiles: []. Usebunx tsc -p packages/api --noEmit(and the equivalent forpackages/types). Web has pre-existing project-reference errors (TS6305) undertsc -p. - Web vitest:
flattenTaxonomyNodesandgetSuggestedPricetests importbun:test, so they fail under vitest. CI does not run web unit tests.
E2E (packages/web/tests/e2e)
- When no remote target is set,
global-setup.tsstarts a mock Kivo auth server on :3008. TheauthenticatedPagefixture (fixtures/) logs in with a mock token, and specs seed data through the API (utils/api-helpers.ts,E2E_API_SERVICE_URL, default :3001). *.visual.spec.tsruns under themobile-visual(Pixel 7) anddesktop-visualprojects. Baselines are committed in*-snapshots/. The dev build pins the version tolocalhostso release bumps don't drift the baselines.@smokeruns post-deploy against production (E2E_BASE_URL, no mock auth). Only tag read-only specs that need no seeded data.
Etsy mock (packages/etsy-mock)
- Connecting through the app opens the mock's consent page. Pick an existing shop or create
one from a scenario:
established,empty,no-shop,two-shipping-profiles. - http://localhost:3010/ is a dashboard for seller actions (publish, sell, expire tokens, inject a failure).
/__mock/*is the JSON control API used by e2e (tests/e2e/utils/etsy-mock.ts).- State is in memory and is lost on restart.
- The mock models only what
EtsyClientcalls. WhenEtsyClientgains a call, add it to the mock in the same PR. - Order sync runs on
ETSY_ORDER_SYNC_INTERVAL_MS, so a sale from the dashboard shows up in the app on the next tick or after an API restart.
Deploy
Merging to main runs ci-cd.yml: checks, semantic-release, then the root Dockerfile.api
and Dockerfile.web images are built by .github/workflows-utils (a submodule; don't init it
locally, because its biome.json breaks the root lint). Dokploy webhooks then deploy to
https://jewellery-catalogue.imapps.uk.
Planned work
The Kanban board jewellery-catalogue.board.md in the notes kanban/ directory, worked with
kanban-cli.
