Imported from JessePomeroy/angelsrest (
AGENTS.md). Install upstream withnpx skills add JessePomeroy/angelsrest. Copyright stays with the author.
AGENTS.md — angelsrest
Canonical rules for working in this repository.
Project context
- Stack: SvelteKit 5 (runes), scoped CSS, Convex, Stripe, LumaPrints, Resend, and Cloudflare R2
- Frontend and platform hub:
~/Documents/work/angelsrest→ https://angelsrest.online - Current architecture:
docs/ARCHITECTURE.md - CRM spec:
~/Documents/Obsidian/quilt/02_reference/project-support/photographer-crm/historical/implementation-spec.md - User guide:
~/Documents/Obsidian/quilt/02_reference/project-support/photographer-crm/crm-user-guide.md
Technical constraints
- Use Svelte 5 runes (
$props(),$state(),$derived(),$effect()). - Public components use scoped CSS and native Svelte markup. Shared palette,
type scale, reset and article styles live in
src/lib/styles/; preserve the cascade findings indocs/CSS_MIGRATION.md. Tailwind and Skeleton are not dependencies. Do not introduce their utility/component classes or directives. - Site-owned surface colors (
--color-surface-50through--color-surface-950) are defined insrc/lib/styles/theme.cssand may be referenced from scoped styles. - Admin pages use scoped styles and
--admin-*variables, not Tailwind. - Server secrets use
$env/dynamic/private. Never import private env modules from browser-reachable code. - Do not hand-edit generated Convex files.
- Biome and Svelte checks are enforced. Run relevant checks before reporting a change complete.
System boundaries
- Convex owns published content and operations: public portfolio galleries, products, blog, about/contact copy, site settings, orders, inquiries, CRM clients, invoices, quotes, contracts, email templates, platform clients/messages, and private delivery galleries.
- Convex is the sole content and commerce authority: no runtime provider switch, preview adapter, migration endpoint, or historical purchase fallback may restore a retired provider.
- SvelteKit owns transport and composition: SSR/load functions, public and admin HTTP routes, webhook verification, and external-client composition.
- The hub owns commerce webhooks: this repository's commerce webhook is the
single order-intake owner for Angels Rest and Stripe Connect client sites.
Client spokes may request Checkout sessions through the signed bridge, but
must not run a second
checkout.session.completedorder/fulfillment path. Checkout creation stamps the server-resolved tenant key into reserved Session and PaymentIntent metadata. Webhooks useevent.accountwhen present and that marker for platform-account tenant checkouts; never trust a browser-supplied tenant name, origin, sender, or notification recipient. - The hub owns LumaPrints shipment intake:
/api/webhooks/lumaprintsverifies the central provider's Basic credentials and resolves order numbers only in the legacy central supplier scope./api/webhooks/lumaprints/[connectionRef]authenticates dedicated client supplier credentials before lookup and carries the saved context through every shipment checkpoint; a bare provider order number does not establish tenant ownership. Client spokes must not receive the broad Convex webhook secret or run a duplicate shipment handler. - External systems: Stripe, LumaPrints, Resend, Convex, and the gallery worker are network boundaries. Make their failure and retry behavior explicit; avoid speculative interfaces around pure in-process code.
There are two gallery domains:
- Portfolio galleries are public Convex content under
/galleryand the admin portfolio tab. - Delivery galleries are private Convex records and R2 objects under
/delivery/[token]and the admin delivery tab.
Use these full names in new code and documentation when the distinction matters.
Key files
- Convex client helper:
src/lib/server/convexClient.ts - Convex schema/functions:
packages/crm-api/convex/ - Site config:
src/lib/config/site.ts - Convex Shop boundary:
src/lib/server/current/convexShop.server.ts - Current checkout authority:
src/lib/server/current/currentCheckoutCommerce.server.ts - Commerce webhook:
src/routes/api/webhooks/stripe/+server.ts - Webhook orchestration:
src/lib/server/orderIntake.ts - Print fulfillment:
src/lib/server/printFulfillment.ts - LumaPrints client/payload builder:
src/lib/server/lumaprints.ts - Tenant checkout authentication registry:
src/lib/server/checkoutBridgeConfig.ts - Admin host config:
src/lib/config/admin.tsandadmin.server.ts - Server hooks:
src/hooks.server.ts(security headers and errors)
The $convex alias points to
packages/crm-api/convex/_generated through svelte.config.js.
Admin authentication and transport
All /admin/* pages use Better Auth. src/routes/admin/+layout.server.ts
validates the session and stored site membership before child loaders fetch
sensitive data, and the shared AuthGuard handles login/session UI.
The browser Convex WebSocket is authenticated manually in
src/routes/admin/+layout.svelte with setupConvex and setupAuth. This avoids
the historical createSvelteAuthClient session-pause race during SvelteKit
navigation. Do not replace the manual setup without explicitly reproducing and
testing navigation/session behavior.
Admin mutations use HTTP:
src/routes/api/admin/mutation/+server.tsvalidates the Better Auth cookie.AdminConfig.mutationTransportis"http".- The shared package forwards each mutation through a fresh authenticated
ConvexHttpClient, avoiding sharedsetAuthstate between requests. - Authenticated server loaders use
createAuthenticatedConvexClient(token); never callsetAuthon the cachedgetConvex()client. - Queries continue over the authenticated browser WebSocket.
New admin server handlers must authorize the required creator/site membership;
token validity alone is authentication, not authorization. Use the host's
per-request site-admin verifier for shared HTTP handlers; do not restore an
identity-only verifyAdmin callback.
Public inquiry writes must enter through /api/contact. That route validates
Turnstile through the managed siteverify Worker and supplies the server-only
WEBHOOK_SECRET to Convex. Do not make inquiries.create directly writable by
the browser or move Turnstile verification into browser-only code. The temporary
missing-secret compatibility path used for the staged rollout has been removed.
Customer-order lookup is owned by the hub broker and uses a dedicated
server-only ORDER_LOOKUP_SECRET. The broker must supply it only after abuse
verification. Never distribute this capability or the broader WEBHOOK_SECRET
to a client spoke or browser, and do not add an unauthenticated Convex lookup
path.
CRM subscription and tier onboarding is operator-controlled. The platform Stripe webhook reconciles provider events for existing subscriptions; it is not authority for a browser to choose tenant identity, billing email, or redirects. Do not add a public self-service subscription checkout without a designed, tenant-authenticated cross-origin billing boundary.
| Admin area | Primary source |
|---|---|
| Dashboard, orders | Convex orders |
| Inquiries | Convex inquiries |
| Galleries: portfolio tab | Convex portfolio galleries |
| Galleries: delivery tab | Convex galleries + gallery worker/R2 |
| CRM, board | Convex photography clients/kanban |
| Invoices, quotes, contracts | Convex |
| Email templates, messages, platform | Convex |
Checks
pnpm lint
pnpm check
pnpm test
pnpm --filter @jessepomeroy/print-catalog check
pnpm --filter @jessepomeroy/print-catalog test
pnpm --filter @jessepomeroy/crm-api exec tsc -p tsconfig.json --noEmit
Use pnpm build when production bundling is relevant. Do not run Biome with
--write during an audit or other read-only task.
Living design handbooks
The public website and mounted admin application have separate Paper handbooks.
See docs/DESIGN_HANDBOOK.md and docs/design/screen-inventory.json for their
links, scope, source mapping, verification status, and safe capture setup.
New screens and material UI changes must include this workflow in the same task: implement → inspect in browser → update Paper → compare → update inventory. Update existing editable reference boards in place; preserve unrelated studies. Document implemented behavior faithfully rather than silently redesigning it. Never mark a board verified without comparing the same viewport, data, theme, and state. Report unavailable fonts, authentication, providers, or states as gaps; do not substitute production customer records or private gallery assets.
Git workflow
- Work on a focused branch unless the user specifies another workflow.
- Do not push to
mainwithout explicit permission. - Do not add AI-assistant co-author trailers.
- Preserve unrelated user changes in a dirty worktree.
Platform context
- angelsrest is the public site and platform hub.
- packages/crm-api owns the shared Convex schema/functions and publishable generated API surface.
- @jessepomeroy/admin is an installed shared admin package.
- reflecting-pool is a spoke/client site that consumes the shared platform.
This project uses Convex as its operational backend.
Before inspecting or editing Convex code, read
packages/crm-api/convex/_generated/ai/guidelines.md completely. Its rules
override assumptions learned elsewhere.