LLM defaults that would otherwise be wrong here:
- Biome only for lint + format. No ESLint, no Prettier.
pnpm devruns every workspacedevscript through portless —portless.json'sappsmap only overrides per-app config (name/script/port/proxy); it is NOT an allow-list. portless auto-discovers all pnpm-workspace packages and the only way to drop one from the run-all set is to point itsscriptat a target that doesn't exist (so portless silently skips it). Docs is excluded this way (apps/docs.script: "dev:disabled-in-run-all"); run it on its own withpnpm dev:docs.pnpm build:packagesbefore lint/typecheck/test in a fresh checkout — apps import workspace packages from builtdist/, not source.- Top-level scripts run through
dotenv -c -- turbo …;.env/.env.localload automatically. Don't prefix env vars manually. - Use
pnpm <script>whenever apackage.jsonscript exists —pnpm build,pnpm lint,pnpm typecheck,pnpm test:e2e, etc. Don't hand-rollpnpm dotenv -c -- vitest run …orpnpm turbo run build …. If a script doesn't forward extra args, fix the script inpackage.json, don't bypass it. pnpm testruns per-package through turbo, not one root vitest —pnpm test=turbo run test --concurrency=50%(each package's ownvitest run --coverage, cache-keyed on that package's inputs) followed bypnpm test:coverage, which merges every package'scoverage/coverage-final.jsonshard viascripts/coverage-gate.tsand enforces the storefront/admin floors (invitest.shared.ts) against the merged report. So an unchanged package replays its shard from the turbo cache instead of re-running. To run one package's tests, usepnpm --filter <pkg> test(NOTpnpm test --project …— that flag no longer threads through turbo).pnpm test:allkeeps the legacy single-processvitest run --coverageover every project as a fallback/coverage oracle (it does not gate). Eachvitestis capped at--maxWorkers=2so the 24 concurrent package runs don't oversubscribe CI cores. Per-package floors moved out ofvitest.config.tsinto the gate; raise them invitest.shared.ts.pnpm cms:genreruns the descriptor-driven CMS codegen — the admin editor-action wrappers, the storefront content types (packages/cms/src/types/content-types.ts), and the Convex content-table validators (packages/convex/convex/tables/cms.ts) — after touching CMS field descriptors or editor manifests. CI gate:pnpm cms:gen:check.- Storefront GraphQL is
gql.tada—graphql()from@nordcom/commerce-shopify-graphql/graphql, not Apollo'sgql. - Call
mcp__next-devtools__init(next-devtoolsmcp) first when starting Next.js work. - Dev + e2e read the Convex deployment in
CONVEX_URL(browser:NEXT_PUBLIC_CONVEX_URL).pnpm convex:devboots/attaches the local backend (packages/convexowns the deployment config; leaveCONVEX_DEPLOY_KEYempty locally). Integration suites launch ephemeral local backends through@nordcom/commerce-test-convex(startConvex()+ theseedCanonicalfixtures); unit tests runconvex-testin-memory, no backend. Touchingpackages/convex/**orpackages/test-convex/**triggers the limit-boundary CI gate (pnpm --filter @nordcom/commerce-test-convex run test src/limits).
Prefer LSP over Grep/Read for navigation — faster, precise, no whole-file reads.
- Find a symbol by name →
lspmeshMCP (find_symbol,find_references,find_implementations). The built-in LSP tool has noqueryparam, so itsworkspaceSymbolalways returns nothing (claude-code#30948).lspmesh(packages/ai/lspmesh) fills that gap and aggregates the TypeScript, Tailwind, and Biome language servers behind one endpoint; it's thelspmesh@commerce-pluginsplugin, launched from the workspace build (pnpm --filter lspmesh build), and replaces the oldlsp-symbolsMCP plus thetypescript-lsp/tailwind-lsp-adapterplugins. If it isn't connected, fall back toGrepfor the name, then point position-based LSP ops at the hit. - The remaining built-in LSP ops are position-based (
filePath+line+character):findReferencesfor every usage across the repo.goToDefinition/goToImplementationto jump to source.hoverfor type info without opening the file.documentSymbolto list a file's symbols (works; it's file-scoped).
- Check LSP diagnostics after writing or editing code and fix errors before moving on.
Applies to every spec-driven workflow — superpowers (writing-plans, executing-plans, brainstorming), claude-mem:make-plan/do, and any other tool that emits specs, plans, or task lists.
Group artifacts per topic under .specs/<YYYY-MM-DD-kebab-slug>/{spec,plan,tasks}.md — e.g. .specs/2026-05-26-storefront-stale-times/spec.md, …/plan.md, …/tasks.md. Never write specs/plans/tasks to .claude/, the tool's default location, or the project root.
Never leave specs on master when using worktrees or branches. The spec/plan/tasks belong on the same branch as the work — commit them as the first commit on that branch, in the correct .specs/<slug>/ path. Don't author them on master and carry them across.
- Multi-tenant by hostname. Middleware resolves hostname → shop and rewrites to
/[domain]/[locale]/…. The App Router never sees an un-tenanted request. New tenant = ashopsrow in Convex, written through thedb/shop_write:upsertShopmutation from the admin; no redeploy. - Tenant context is never implicit. Every Shopify call goes through
ShopifyApolloApiClient({ shop, locale }). New data-fetching helpers must take{ shop, locale }explicitly. - Locale fallback:
request locale → shop default → platform default. Locales live on the shop record, not a global list.
noUncheckedIndexedAccess: true— index access isT | undefined. Don't paper over with!.- Trailing slashes on internal links (
trailingSlash: true). - Server Components by default. Add
'use client'only when needed (hooks, event handlers, browser APIs). Never import aserver-onlymodule — or any file that transitively does — from a Client Component. - Async APIs in Next.js 16.
params,searchParams,cookies(),headers(), anddraftMode()are promises.awaitin async functions;React.use()in sync. - Provider tokens guarded with
experimental_taintUniqueValue— preserve that. - Throw via
@nordcom/commerce-errors, nevernew Error(...). If no class fits, add one (plus*ErrorKindand agetErrorFromCodecase) in the errors package. - Comments must earn their place. Document the WHY — hidden constraints, workarounds, surprising behavior. If the code already says it, no comment. No section headers, no task notes, no restatements.
- JSDoc on every function and component. Required for all exported and internal functions — including React components. Block must include purpose plus
@param,@returns, and@throwswhere applicable. Same no-fluff rule applies inside the block — describe intent and contract, not implementation. - Root cause before symptom. Don't revert versions, return empty arrays, or disable features as a first-guess fix — especially for Next.js cache/PPR, build-tool errors, OIDC, or Shopify GraphQL field mismatches.
- No unused variables, parameters, imports, or destructured props. Delete them — don't suppress with a leading underscore (
const _params = …,function f(_unused)),voidcasts, or// biome-ignore. If a destructure exists only to drop a key, remove the destructure entirely. The only exception is destructured rest patterns where the named keys are genuinely discarded to build the rest object (const { skip, ...rest } = props). - Environment-tier gates go through
@nordcom/commerce-utils. Never hand-rollprocess.env.NODE_ENV/BuildConfig.environmentcomparisons to gate behavior. UseisProduction()/isDevelopment()from@nordcom/commerce-utils— they readVERCEL_ENVso a Vercel preview deploy (which runs withNODE_ENV='production') is correctly treated as non-production, the gap that leaked the live-chat launcher onto previews. Both are client-safe; client bundles can't seeVERCEL_ENV, so for client-side, host-aware preview gating (suppressing onpreview./staging.hosts) use the storefront'sisPreviewEnv(hostname)instead. RawNODE_ENVreads are fine only for non-gating concerns (e.g. a cookiesecureflag, instrumentation tier). - Canonical Tailwind classes. Collapse to the shorthand/canonical utility —
size-4noth-4 w-4,inset-0nottop-0 right-0 bottom-0 left-0,mx-2notml-2 mr-2,p-4notpx-4 py-4. Biome can't enforce this (useSortedClassesonly sorts;suggestCanonicalClassesis an editor-only Tailwind LSP diagnostic, unimplemented in Biome — see discussion #8675), so it's on you when writing or editing markup. - American English —
color,behavior,organization,canceled,analyze.
Every new user-facing flow in apps/admin or apps/storefront ships with a Playwright spec under that app's e2e/ dir (*.spec.ts). A flow added without e2e coverage is incomplete — treat the spec as part of the feature, not a follow-up.
- Reuse the harness, don't rebuild it.
e2e/global-setup.tsseeds the canonical tenant viaseedCanonicaland (admin) writes the pre-auth NextAuth cookie; specs readE2E_SHOP_DOMAIN(defaultnordcom-demo-shop.com). Drive the REAL app end to end, not mocks. - Storefront product data is live
mock.shop(the seededcommerceProvider.domain). Use REAL handles — productsslides/sweatpants/men-t-shirt, collectionsmen/women/tops/bottoms— never invent handles, and never.skipa flow "for mock-shop limitations". - Admin editor flows assert through the native field shells (
[data-testid="field-<dotted.path>"]), the array/blocks widgets (array-add-<path>,blocks-picker/add/row-<path>), the toolbar (Publish / Save Draft /editor-toolbar-error), and/versions/restore. Wait on autosave QUIESCENCE, not the sticky "Last saved" label. - Runs locally alongside
pnpm dev.pnpm test:e2e --filter @nordcom/commerce-<app>boots its own server on a fixed port (storefront 1337, admin 3000) into an isolatedE2E_DIST_DIR(.next-e2e), so it never collides with a running dev server's Next dev-lock. CI builds +next starts the default.next. - Keep specs rerun-safe against the shared deployment: stamp a unique run token and restore any state the spec mutates.
- Conventional Commits with scope —
<type>(<scope>): <subject>.. Types:feat,fix,chore,refactor,docs,test,perf,ci,build. Imperative subject, lowercase, trailing period. - Body explains the WHY, not the WHAT — motivation, hidden context, trade-offs, breaking-change notes. Skip the body entirely if subject + diff are self-explanatory. Per item: omit anything the diff already makes obvious.
- Never merge — always rebase. No merge commits on any branch. Integrate via
git rebase(orgit pull --rebase). If a PR can't fast-forward, rebase the branch onto the target before merging. - Prefer amend over fixup commits. When iterating on the most recent commit (review feedback, typo, missed file),
git commit --amendrather than stacking afixup!/ follow-up commit. Only create a new commit when the change is logically distinct or the prior commit is already pushed and shared.
Touching any package not in .changeset/config.json's ignore list requires a changeset (pnpm changeset). Pick the level per semver: patch for bugfix/internal-only, minor for additive API, major for breaking change. One changeset per logical change. Summary follows the same WHY-only rule as commit bodies.
GitHub Issues at filiphsps/commerce, driven by the gh CLI. See docs/agents/issue-tracker.md.
Canonical five — needs-triage, needs-info, ready-for-agent, ready-for-human, wontfix. See docs/agents/triage-labels.md.
Single-context — CONTEXT.md at the repo root (optional glossary), with historical decisions in .specs/<YYYY-MM-DD-kebab-slug>/. No ADRs. See docs/agents/domain.md.