Imported from wollemiahq/cavuno-tanstack-start-shadcn-job-board-template (
AGENTS.md). Install upstream withnpx skills add wollemiahq/cavuno-tanstack-start-shadcn-job-board-template. Copyright stays with the author.
Agent rules — Cavuno board frontend
Rules for any coding agent working in this repo. Keep this file concise and self-contained: it is the shared contract for automated customization.
Never greenfield
This repo is a customization template. Every change — restyle, new page, copy edit — customizes THIS codebase in place. Never rebuild the app from scratch, never replace the chassis wholesale, never fork the data layer. If a request seems to require starting over, it doesn't: find the smallest edit to the existing surface.
Repo orientation
Where things live — content search (grep) confirms anything not listed:
src/routes/— page composition (markup, layout, copy); keep loaders intact. Co-located-*.test.tsxfiles pin route behavior.src/board/— view-model mappers (locked layer, see below).src/components/board/+src/components/search-results/— board and search presentation; restructure freely.src/components/ui/— owned shadcn components (inventory in DESIGN.md); other subdirs (layout, marketing, employer, …) — shared presentation.src/server/— the only place the Board API is called.src/lib/— env, SDK client, session, correctness invariants.src/hooks/— shared presentation hooks.src/theme.css— canonical tokens: site-wide colors/fonts live HERE.src/theme/,src/paraglide/,DESIGN.md,design/tokens.dtcg.jsonare GENERATED — never edit; rungen:theme/gen:messages/gen:design.messages/— UI copy catalogs; edit thenpnpm run gen:messages.- Tests sit next to their sources (
foo.test.tsxbesidefoo.tsx).
Working style
- Search first: find copy, markup, and identifiers with content search rather than reading whole directories — catalogs and routes are large.
- Batch independent reads/searches; speculative multi-file reads in one go.
- One coherent change across files lands as ONE batched multi-file edit.
- After editing generator inputs, run the matching generator; never hand-edit generated output.
- Before final verification, run ONLY the co-located tests of changed files — never the whole suite.
Grounding config is not an agent edit
CAVUNO_API_URL and CAVUNO_BOARD (the pk_… publishable key) bind
this frontend to one specific board. They are set at deploy time by a
human operator (or the platform):
wrangler.jsonc vars in production, .dev.vars in dev/sandbox. That is
how a board goes live — an operator swaps CAVUNO_BOARD for their own
pk_… (README "Deploy"). You, the agent, never touch grounding: never
edit, hardcode, duplicate, or move these values in code. A change
request that appears to need different grounding is an operator/deploy
operation, not a code edit for you to make.
The customization surface
src/components/**— presentational, dumb, typed-props components. Data arrives from loaders; components never fetch.src/theme.css— the canonical, shadcn CLI-owned theme. Edit it directly or withshadcn apply, then runpnpm run gen:theme; never editsrc/theme/resolved.ts(generated). Fonts are part of this surface (docs/theming.md §Fonts): the 20-font catalog is pre-installed, so a font change edits ONLY this file — bannerfontSans/fontHeadingkeys, the fontsource import block (active families only), and--font-sans/--font-headingtokens — thenpnpm run gen:theme && pnpm run gen:design. Never setfont-familyon components (use thefont-sans/font-headingutilities); never add font packages — an AGENT rule, not a ban (operators may, via--only theme,font); token, import, and package must agree.src/styles.css— global resets, app-shell defaults, and shared layout utilities. Theme tokens and radii live insrc/theme.css.src/routes/*.tsx— page composition (markup, layout, copy). Keep loaders/server-function calls intact.messages/**— UI copy catalogs (Paraglide). Runpnpm run gen:messagesafter edits. Default compile is English only;pnpm locale:add deenables a dormant catalog (neveren-XA/ar-XB).
Outside the surface — change only with explicit reason
src/board/**— the view-model layer (Layer 1b): pure mappers (toJobCardVM,toJobDetailVM,toApplyButtonVM,toOverallSalaryVM, …) that call the@cavuno/boardSDK's correctness functions (formatters, breadcrumbs, path helpers, copy) and hand components plain, resolved data. Consume these mappers and the SDK (Layer 1a); never rewrite them — that is what stops a redesign mis-calling a correctness function. Presentation (src/components/**, Layer 2) is yours to restructure freely; if a new section needs a resolved datum (a formatted value, a label, a breadcrumb), add it to the mapper rather than re-deriving it inside a component.src/lib/**— env access, SDK client, session cookie + middleware, theme mapper, JSON-LD builder. These encode security and correctness invariants.src/server/**— the only place the Board API is called. Auth is enforced per server function here.vite.config.ts,wrangler.jsonc,tsconfig.json— build config is locked.
Hard rules
- Never read
process.envat module scope — it isundefinedon Workers. UsegetServerEnv()fromsrc/lib/env.tsinside handlers. - Never call the Board API from the browser — add data needs as
server functions in
src/server/. - Never persist private Cavuno auth/session credentials in browser storage or module state — the user session belongs only in the host-owned httpOnly cookie.
- HTML from the API (
job.description,post.html,company.description) is pre-sanitized — render as-is; never interpolate other strings intodangerouslySetInnerHTML. - Keep
head()meta + the JobPosting JSON-LD on the job-detail route intact — they are the board's SEO contract. - New components compose owned shadcn/ui components on Base UI under
src/components/ui/; merge classes withcnfrom@/lib/utils. App code consumes their canonical public APIs, never Base UI internals or data attributes, so adopters may swap in their own Base UI-backed shadcn source. Radix is an explicit migration, not a drop-in swap. There is one design system: never add a parallel component tree, icon set, CSS utility layer, or token system alongsidesrc/components/ui/+src/theme.css. - Board URL paths come from
@cavuno/board/paths(jobDetailPath,jobsCategoryPath,jobsSkillPath,companyPath,companySalaryPath, …) — never string-build a/companies/…/jobs/…or/jobs/…path. The canonical URL structure is a locked cross-surface contract (it must match the hosted board, sitemap, and emails); the absolute canonical URL for a job/company still comes from the API'slinks.public. Route<Link to>uses TanStack's typed route ids as usual.
Operator overrides win; tests protect behavior
Hosted-board parity is the DEFAULT, not a ceiling. When the operator
asks for a different presentation of a resolved value (salary/date
style, icons, labels), produce it: add a mapper field or transform the
VM's RAW wire values (salaryMin, publishedAt, …) with e.g.
Intl.NumberFormat. "Golden-tested" pins only the SDK's DEFAULT
rendering; never parse its locale-shaped formatted strings.
Tests protect stable behavior, not incidental markup, classes, implementation
details, or prose. Prefer roles/states, navigation, permissions, data flow,
and errors. Never test only sentence/heading presence; derive catalog
expectations from messages or fixtures. Component tests use neutral VM
fixtures; SDK goldens own exact salary/date/location formatting. Mapper tests
pin wire values and presence/absence — not SDK formatter re-pins (see
CODING_STANDARDS.md). Update tests with behavior; loosen brittle
copy/visual failures. A green structural gate proves little.
Dependencies
Package manager is pnpm 11, pinned in package.json. Installs in
CI and sandboxes run with a frozen lockfile. Supply-chain posture
(pnpm-workspace.yaml): dependency lifecycle scripts are blocked —
allowBuilds is an empty allowlist — and minimumReleaseAge keeps a
1-day cooldown (1440) on newly published versions. Add a dependency only
when the change genuinely needs it and the reviewed packages cannot solve
the problem; packages outside the platform allowlist fail deploy closed.
Verify every change
pnpm run typecheck && pnpm test && pnpm run build
Hosted agent environments provide a verify tool covering this whole
gate plus live page checks — there, call it once instead.
Design system
Visual identity, design tokens, the component inventory, and design
do's-and-don'ts live in DESIGN.md (generated from src/theme.css
- component source + the registry snapshot — regenerate with
pnpm run gen:design, never hand-edit; CI rejects drift). The machine-interchange token export isdesign/tokens.dtcg.json. Select from the DESIGN.md component inventory before writing new components. Page-level compositions follow the patterns indocs/patterns/— select a pattern before composing a route; never hand-roll a listing/detail/form/empty surface.
Primitives, in order: use or add current shadcn components under
src/components/ui/, backed by Base UI and styled from src/theme.css.
Route and pattern code depends only on canonical shadcn component props.
There is no parallel component or token system — structural tests reject
second component trees. New dependencies cannot be added at build time, so
compose from what is installed.
Framework skills
TanStack skill mappings for this stack: docs/tanstack-skills.md.