@vegastack/skills 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/index.js +5 -5
- package/package.json +1 -1
- package/skill/dev-architect/SKILL.md +93 -0
- package/skill/dev-architect/agents/openai.yaml +4 -0
- package/skill/dev-architect/references/ai-agents.md +89 -0
- package/skill/{architect → dev-architect}/references/data.md +43 -44
- package/skill/dev-architect/references/infra.md +98 -0
- package/skill/dev-architect/references/mobile.md +75 -0
- package/skill/{architect → dev-architect}/references/pinned-facts.md +17 -16
- package/skill/dev-architect/references/principles.md +117 -0
- package/skill/{architect → dev-architect}/references/security.md +37 -44
- package/skill/dev-architect/references/stack.md +38 -0
- package/skill/dev-architect/references/web.md +102 -0
- package/skill/{architect → dev-architect}/refresh/REFRESH.md +8 -6
- package/skill/{architect → dev-architect}/refresh/sources.json +5 -10
- package/skill/dev-implement/SKILL.md +3 -3
- package/skill/dev-intake/SKILL.md +2 -2
- package/skill/dev-setup/SKILL.md +8 -5
- package/skill/dev-setup/assets/dev-profile.md.template +19 -2
- package/skill/dev-setup/references/stack-playbooks.md +2 -2
- package/skill/skill-maintainer/references/release-ops.md +3 -3
- package/skill-integrity.json +20 -24
- package/skill/architect/SKILL.md +0 -68
- package/skill/architect/agents/openai.yaml +0 -4
- package/skill/architect/assets/adr-template.md +0 -21
- package/skill/architect/assets/arch-template.md +0 -20
- package/skill/architect/references/advisory.md +0 -102
- package/skill/architect/references/ai-agents.md +0 -95
- package/skill/architect/references/infra.md +0 -128
- package/skill/architect/references/mobile.md +0 -78
- package/skill/architect/references/principles.md +0 -91
- package/skill/architect/references/project-profile.md +0 -37
- package/skill/architect/references/stack.md +0 -38
- package/skill/architect/references/web.md +0 -152
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
# Mobile — Flutter production doctrine
|
|
2
|
-
|
|
3
|
-
**The Flutter app is a separate repo from the web/API repo** — it consumes the Next.js
|
|
4
|
-
REST/OpenAPI contract as one more client, never a `mobile/` directory in the web monorepo
|
|
5
|
-
(ADR-recorded, corroborated three ways; an expensive structural call to get wrong).
|
|
6
|
-
|
|
7
|
-
Flutter is the production mobile framework when a product needs a mobile app (not all do —
|
|
8
|
-
the profile records it). Baseline: Flutter 3.44.x stable; Impeller is the default renderer
|
|
9
|
-
on iOS and Android API 29+ (falls back below 29 — test one API<29 device before shipping).
|
|
10
|
-
Package versions here were verified 2026-08; re-verify on pub.dev before pinning.
|
|
11
|
-
|
|
12
|
-
Evidence tiers in this file: rules drawn from the shipped VegaStack app are stated plainly;
|
|
13
|
-
rules from official-docs research that MK has not yet ratified are tagged "(inferred)" —
|
|
14
|
-
confirm those on first use, per SKILL.md.
|
|
15
|
-
|
|
16
|
-
## Architecture (official-guidance derived — inferred where the shipped app is silent)
|
|
17
|
-
|
|
18
|
-
- MVVM per official flutter.dev/app-architecture: View (widget, no logic) → ViewModel
|
|
19
|
-
(state + commands, 1:1 with its View) → Repository (source of truth, caching/retry,
|
|
20
|
-
never depends on another repository) → Service (thin stateless API/platform wrapper).
|
|
21
|
-
Skip the optional domain/use-case layer until logic is reused across ≥2 ViewModels.
|
|
22
|
-
- Project structure is layer-first: `lib/{data, domain, ui, routing, config}`, feature
|
|
23
|
-
folders nested inside `ui/`, shared widgets in `ui/core/`. (Blog "feature-first is the
|
|
24
|
-
standard" claims don't survive verification — the official reference app, Compass, is
|
|
25
|
-
layer-first. Feature-first is a valid escalation once app/team size demands it.)
|
|
26
|
-
- Class names mirror roles: `HomeViewModel`, `HomeScreen`, `UserRepository`,
|
|
27
|
-
`ClientApiService`.
|
|
28
|
-
|
|
29
|
-
## State management — lean by default
|
|
30
|
-
|
|
31
|
-
- Plain `ChangeNotifier`/`ValueNotifier` ViewModels with hand-wired constructor injection
|
|
32
|
-
in `main.dart`. Zero extra dependencies, zero codegen — this matches both official
|
|
33
|
-
guidance ("personal preference") and what VegaStack has actually shipped.
|
|
34
|
-
- Escalate to Riverpod only when async state genuinely needs sharing across ≥3 widgets,
|
|
35
|
-
tests need provider-override mocking, or a second app shares a state-heavy module.
|
|
36
|
-
No get_it — official docs steer away from service locators; `provider` is the official
|
|
37
|
-
DI pick if constructor wiring ever gets unwieldy.
|
|
38
|
-
|
|
39
|
-
## Networking & auth
|
|
40
|
-
|
|
41
|
-
- One `dio` client centralized in a single `ApiClient` — never scattered HTTP calls.
|
|
42
|
-
The app consumes the same contract-first REST API as the web app (web.md).
|
|
43
|
-
- Auth is the same Better Auth instance as web, via the bearer plugin: capture the token
|
|
44
|
-
from the `set-auth-token` response header on sign-in; store it in
|
|
45
|
-
`flutter_secure_storage` (never shared_preferences); attach `Authorization: Bearer` via
|
|
46
|
-
a dio interceptor; clear storage and route to sign-in on 401.
|
|
47
|
-
- Do NOT depend on `better_auth_flutter` (0.1.0, negligible adoption as of 2026-08) —
|
|
48
|
-
hand-roll the interceptor; revisit at a real 1.0.
|
|
49
|
-
- `shared_preferences` for non-secret local metadata only. `drift` only if genuine
|
|
50
|
-
offline/relational needs exist — never speculatively.
|
|
51
|
-
|
|
52
|
-
## Design system on mobile
|
|
53
|
-
|
|
54
|
-
- Material 3 (default since 3.16); semantic colors via a hand-authored `ColorScheme` plus
|
|
55
|
-
`ThemeExtension` for tokens outside Material's roles — mirroring the web design-system
|
|
56
|
-
token names 1:1 from one Dart source of truth.
|
|
57
|
-
- House taste mapped: `CardTheme(elevation: 0)` with `outlineVariant` borders (flat,
|
|
58
|
-
borders-only); TextTheme capped at `FontWeight.w600`, never bold; subtle motion with
|
|
59
|
-
reduced-motion respected; Lucide-style iconography.
|
|
60
|
-
|
|
61
|
-
## Navigation, models, testing, deploy
|
|
62
|
-
|
|
63
|
-
- Navigation: the shipped app uses plain `Navigator`; `go_router` is the official Flutter
|
|
64
|
-
team pick for route-heavy apps (inferred) — adopt it when deep links/route state demand
|
|
65
|
-
it, not by reflex.
|
|
66
|
-
- Models: the shipped app hand-writes all models — stay hand-written by default; freezed +
|
|
67
|
-
json_serializable only when codegen demonstrably earns its build cost (inferred
|
|
68
|
-
threshold, not MK-ratified).
|
|
69
|
-
- Test where MVVM pays off: unit-test ViewModels and Repositories; widget-test critical
|
|
70
|
-
screens; integration tests only for can't-ship-broken flows (sign-in, payment). Golden
|
|
71
|
-
tests, if adopted: `alchemist` over the discontinued `golden_toolkit` (inferred — no
|
|
72
|
-
VegaStack golden-test precedent yet).
|
|
73
|
-
- Deploy: GitHub Actions is the house CI; Fastlane for store signing/upload and real build
|
|
74
|
-
flavors (`--flavor` + per-env entry points) are the researched defaults (inferred — no
|
|
75
|
-
shipped store-deploy precedent yet; confirm before wiring).
|
|
76
|
-
|
|
77
|
-
Undecided (ask MK rather than assume): push-notification provider preference, offline/sync
|
|
78
|
-
expectations per product, store-release cadence.
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
# Principles — how VegaStack decides
|
|
2
|
-
|
|
3
|
-
How MK actually makes architecture decisions, distilled from seven months of his sessions
|
|
4
|
-
and repos. Each rule carries its why — apply the reasoning, not just the rule.
|
|
5
|
-
|
|
6
|
-
## Build lean first
|
|
7
|
-
|
|
8
|
-
One deployable until a concrete requirement forces a split. A single, properly structured
|
|
9
|
-
Next.js app owns the UI, RSC, route handlers, auth, and the REST/OpenAPI control plane — no
|
|
10
|
-
NestJS or Hono beside it, no speculative queue, cache, or worker. A notification sender is a
|
|
11
|
-
cron handler inside the app, not its own deployable. **Why:** agents habitually propose
|
|
12
|
-
services MK then has to strip out ("why do we still need workers like extra bloat or
|
|
13
|
-
maintenance surface?"). On a 3-4 person team, every moving part is real maintenance, and the
|
|
14
|
-
lean version is usually also the faster and more reliable one.
|
|
15
|
-
|
|
16
|
-
Lean-first governs the *count of moving parts*, not the rigor inside each part.
|
|
17
|
-
Correctness-critical logic — money, auth, tenancy, audit, secrets — always gets full rigor.
|
|
18
|
-
|
|
19
|
-
## Every moving part names its trigger
|
|
20
|
-
|
|
21
|
-
Propose infrastructure only together with the trigger that justifies it, stated in the
|
|
22
|
-
recommendation: "a separate worker WHEN jobs exceed request timeouts", "a queue WHEN volume
|
|
23
|
-
makes inline processing lossy", "OpenBao WHEN self-hosting customer-managed secrets".
|
|
24
|
-
Provision only what the current build phase actually uses — infrastructure tracks real
|
|
25
|
-
usage, never anticipated usage. **Why:** MK stated "OpenBao mandatory in production" and
|
|
26
|
-
walked it back five days later ("some projects are just simple and straightforward").
|
|
27
|
-
Blanket mandates rot; triggers stay true as projects differ.
|
|
28
|
-
|
|
29
|
-
## Pre-launch means delete, not migrate
|
|
30
|
-
|
|
31
|
-
Zero real users = no backward compatibility, no legacy shims, no deprecation windows, no
|
|
32
|
-
feature flags hiding unfinished work. Delete outright; reset the dev database rather than
|
|
33
|
-
writing migration chains. This is the DEFAULT — most VegaStack projects are pre-launch at
|
|
34
|
-
any given time. Expand/migrate/contract discipline begins when real users exist, not before.
|
|
35
|
-
**Why:** carrying compatibility for users who don't exist is pure bloat ("NO FEATURE FLAGS
|
|
36
|
-
PLEASE... i dont want any backwards compatibility as this is an unreleased app").
|
|
37
|
-
One exception: a versioned API contract consumed by a shipped mobile app counts as having
|
|
38
|
-
real users even while the web side iterates freely — app-store install lag keeps old
|
|
39
|
-
clients alive (mobile.md).
|
|
40
|
-
|
|
41
|
-
## Reuse before you build new
|
|
42
|
-
|
|
43
|
-
Extend the existing table, service, or spine (ACL, change-log, outbox, realtime channel)
|
|
44
|
-
before creating a parallel one. When two components do the same job, merge them into one
|
|
45
|
-
canonical source. Promote a util to `/lib` the moment a second feature uses it. **Why:**
|
|
46
|
-
duplicated systems of record are the reference-architecture mistake MK explicitly engineers
|
|
47
|
-
against; two sources of the same truth always drift.
|
|
48
|
-
|
|
49
|
-
## Enforce boundaries mechanically
|
|
50
|
-
|
|
51
|
-
Architectural boundaries that matter get a CI guard script that fails the build — monorepo
|
|
52
|
-
import direction (apps → packages, never the reverse; workers never import UI/React),
|
|
53
|
-
runtime gravity (below), server/client code separation. **Why:** convention alone was tried
|
|
54
|
-
and failed; a guard script is cheaper than re-reviewing the same violation forever.
|
|
55
|
-
|
|
56
|
-
## Runtime gravity: long-running work never lives in the request tier
|
|
57
|
-
|
|
58
|
-
Anything that can run long, hold a connection, or outlive a request — agent execution, job
|
|
59
|
-
processing, media pipelines — runs in a separate worker/runner tier, never inside a route
|
|
60
|
-
handler or an OpenNext Worker. **Why:** request-scoped tiers have timeouts and body limits;
|
|
61
|
-
the reference codebase MK studied executed workflows inside Next.js handlers and it is the
|
|
62
|
-
single anti-pattern he cites most.
|
|
63
|
-
|
|
64
|
-
## Effort scales with stakes, not habit
|
|
65
|
-
|
|
66
|
-
Security, auth, tenancy, money, and architecture foundations get generous, thorough,
|
|
67
|
-
adversarial treatment. Routine features on a small team get medium thoroughness — do a cheap
|
|
68
|
-
reconnaissance pass before committing to full-cost work, and don't build governance ceremony
|
|
69
|
-
a 3-4 person team will never exercise. **Why:** MK's own most recent self-correction — his
|
|
70
|
-
previous architecture skill "got too complicated" by applying flagship-platform rigor
|
|
71
|
-
everywhere.
|
|
72
|
-
|
|
73
|
-
## Decisions are verified, recorded, and reversible for a reason
|
|
74
|
-
|
|
75
|
-
- Resolve uncertainty with evidence, never with the safer-sounding guess: check the live
|
|
76
|
-
official docs (current year), verify the claimed constraint, then decide. MK refused to
|
|
77
|
-
accept an unverified "Better Auth forces text UUID columns" claim — it was false.
|
|
78
|
-
- Present decisions as 2-3 options with a clear recommendation and the tradeoff that
|
|
79
|
-
matters, then record what was chosen and why (an ADR or one dated ledger line).
|
|
80
|
-
- MK reverses when: a verified assumption proves false · added cleverness regresses UX ·
|
|
81
|
-
a heavyweight mandate meets a simple project · his own tooling over-complicates. He
|
|
82
|
-
reverses toward less machinery on low stakes and toward more rigor only when a concrete
|
|
83
|
-
bug or vulnerability is found. Anticipate this: don't defend machinery he'd delete.
|
|
84
|
-
|
|
85
|
-
## Context gates rigor — ask, don't assume
|
|
86
|
-
|
|
87
|
-
Rigor flexes on facts, not labels: pre-launch or live · internal, client, or OSS ·
|
|
88
|
-
self-hosted or managed · handles money/PII or not · has real users or not. These live in
|
|
89
|
-
`.vegastack/arch.md`. Client work gets the same approval gates and honesty as internal work
|
|
90
|
-
— if anything, more documentation, never less. When two recorded decisions conflict or the
|
|
91
|
-
profile can't answer, ask MK with a recommendation instead of assuming.
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
# Project profile — `.vegastack/arch.md`
|
|
2
|
-
|
|
3
|
-
The per-project memory that stops every session from re-deriving the same facts. It is a
|
|
4
|
-
head start, never the source of truth: the repository wins every disagreement.
|
|
5
|
-
|
|
6
|
-
## First run in a project
|
|
7
|
-
|
|
8
|
-
If `.vegastack/arch.md` does not exist and the task is architectural:
|
|
9
|
-
|
|
10
|
-
1. First infer what you can from the repo — package.json/lockfile (runtime), wrangler
|
|
11
|
-
files (Cloudflare; a `d1_databases` binding with no Postgres driver = the D1-only
|
|
12
|
-
product class), Dockerfiles/compose (self-managed), drizzle config (database),
|
|
13
|
-
better-auth usage, `@aws-sdk/client-s3`/R2 bindings (storage), pg-boss dependency
|
|
14
|
-
(jobs), `eve`/`ai` packages (agents vs plain AI features), pubspec.yaml (mobile).
|
|
15
|
-
2. Ask MK or the team member only what the repo can't answer, in one short message:
|
|
16
|
-
- Where does this deploy? (Cloudflare Workers via OpenNext · self-managed server ·
|
|
17
|
-
both · exception: Vercel)
|
|
18
|
-
- Stage and kind? (pre-launch or live · internal, client, or oss)
|
|
19
|
-
- Anything non-default? (mobile app, agents, separate workers, unusual services)
|
|
20
|
-
3. Write `.vegastack/arch.md` from [the template](../assets/arch-template.md), show it,
|
|
21
|
-
and confirm before relying on it. Creating the file needs the same write authorization
|
|
22
|
-
as any other file.
|
|
23
|
-
|
|
24
|
-
Never create the file during a purely read-only task or an explanation — suggest it instead.
|
|
25
|
-
|
|
26
|
-
## Every later run
|
|
27
|
-
|
|
28
|
-
- Read the file, then trust the repo over it. If the repo disagrees (the file says bun but
|
|
29
|
-
the lockfile is pnpm-lock.yaml; a wrangler.jsonc appeared; a mobile/ directory exists),
|
|
30
|
-
say so and propose the exact one-line file update. Never silently follow a stale profile,
|
|
31
|
-
and never silently rewrite it either.
|
|
32
|
-
- Decisions and their dates belong in the `notes:` lines — one line per decision ("2026-08:
|
|
33
|
-
DO for realtime only, dropped for chat — SSE+Postgres instead"). This is the project's
|
|
34
|
-
decision ledger; keep entries short and dated.
|
|
35
|
-
- If the file records something that contradicts this skill's defaults, the file wins for
|
|
36
|
-
that project — it is a recorded decision. Report it as accepted risk only if it crosses
|
|
37
|
-
a red line.
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
# Stack — locked decisions
|
|
2
|
-
|
|
3
|
-
The VegaStack default stack. "Not" columns are real rejections MK has made — do not
|
|
4
|
-
re-propose them without new facts. Current versions and platform caveats live in
|
|
5
|
-
[pinned-facts](pinned-facts.md); check there before pinning a version.
|
|
6
|
-
|
|
7
|
-
| Area | Use | Not | Why |
|
|
8
|
-
|---|---|---|---|
|
|
9
|
-
| Web framework | Next.js 16 App Router, one app | NestJS, Hono, a second backend | A properly structured Next app owns web + API + auth for web, Flutter, MCP, and public consumers; a second framework is pure maintenance surface |
|
|
10
|
-
| Runtime / package manager | Bun (default for new projects); pnpm fully sanctioned where chosen | npm, yarn — never | One lockfile discipline per project; the profile records which. Commit text lockfiles (`bun.lock`, not `bun.lockb`) |
|
|
11
|
-
| Auth | Better Auth: email/password + Google sign-in by default; organizations plugin (teams enabled) for orgs/workspaces/teams; bearer plugin for mobile; apiKey plugin for API keys | Hand-rolled auth, custom user-groups schema, Auth0/Clerk | Owner-stated standing rule. Organization maps to workspace (`modelName: "workspace"` pattern); teams = Better Auth teams; prefer the shipped plugins over native builds unless a project records why |
|
|
12
|
-
| Database | PostgreSQL, always self-managed by us: PlanetScale Postgres server or self-hosted (Hetzner or similar). PG 17 behind Hyperdrive; 17/18 otherwise. D1-only is a recorded exception for the minimal Cloudflare-native product class (vegastack-pages/vegafactory precedent, locked 2026-05-20) — check `.vegastack/arch.md` before assuming Postgres | Neon (never), D1 as a secondary store beside Postgres, MySQL | "We don't use neon at all." One datastore per product; Hyperdrive caps at PG 17.x (see pinned-facts). D1's sanctioned sidecar case: a Worker-scoped idempotency table for Stripe webhooks — Postgres still owns the ledger |
|
|
13
|
-
| ORM | Drizzle + drizzle-kit, single `postgres-js` driver | Prisma | House standard across every repo since Feb 2026 |
|
|
14
|
-
| DB from Workers | Hyperdrive binding, per-request client (`prepare: false, max: 1`, request-scoped) | Global pools, TCP clients (ioredis) in Workers | workerd forbids I/O across requests; a module-level pool is a bug, not a style choice |
|
|
15
|
-
| Storage | Cloudflare R2 (S3-compatible API), presigned URLs, short-lived scoped access | S3 + CloudFront by reflex | R2 egress is $0 at any scale — the cost problem S3+CDN solves doesn't exist here. For licensed self-hosted deployments, keep the S3 protocol boundary so any S3 provider or MinIO works |
|
|
16
|
-
| Cache / Redis | None by default. Rate limiting and caching Postgres-native; Workers KV only with a named trigger (e.g. read-mostly config/session cache at request volumes where a Postgres round-trip measurably hurts — as in the recorded auth-cache migration); a Redis-class store never correctness-bearing | Upstash Redis (migrated off), Redis "because caching" | "No Redis in P1" — a cache layer is a moving part; Postgres already exists. Never cache authorization/role data aggressively |
|
|
17
|
-
| Jobs / cron | pg-boss on the existing Postgres, dispatcher-only — lease/heartbeat/retry state of authority lives in our own tables. Cron parsing/description: `croner` (DST/IANA-aware) + `cronstrue` (human-readable) | BullMQ+Redis, Temporal, CF Queues by default; `node-cron`, `cron-parser` | Jobs are simple; Postgres is already there. Owning durability state keeps the queue library swappable; croner/cronstrue are the battle-tested cron pair (a cron-parser swap was made and corrected once already) |
|
|
18
|
-
| Agent execution | EVE (Vercel's `eve`, beta): on Vercel as Functions with Fluid Compute (recorded per-project exception), or self-hosted as its own long-running Node/OCI service with `@workflow/world-postgres` | EVE inside an OpenNext Worker or any request-scoped/edge function | EVE's Postgres world explicitly requires a long-lived worker process — "not compatible with serverless platforms". EVE owns agent sessions; pg-boss owns generic jobs — complementary, never conflated |
|
|
19
|
-
| AI calls | AI SDK v7 (`ai@^7`) behind a thin adapter; Anthropic default provider; Cloudflare AI Gateway for routing/telemetry when on Cloudflare | Hardcoded model IDs scattered in code, per-provider SDKs everywhere | Adapter keeps providers swappable; the gateway centralizes cost/telemetry without building it |
|
|
20
|
-
| Design system | Consume `@vegastack/design` + `@vegastack/ui` (Base UI primitives, semantic tokens) | Creating/modifying components upstream, raw shadcn edits, Radix for new work | Consume, don't extend — upstream changes are MK's deliberate decision. Base UI locked over Radix 2026-07 |
|
|
21
|
-
| Frontend state | Server state via RSC + TanStack Query; URL state via nuqs; ephemeral UI state via Zustand | Persisting remote/tenant data client-side (IndexedDB/local-first) for multi-tenant SaaS | Governance: tenant data never rests on the client. Local-first only for a confirmed single-tenant/offline product — ask first |
|
|
22
|
-
| Realtime | SSE tailing an event log first; Durable Objects (SQLite, WebSocket hibernation) when on Cloudflare and bidirectional state is real. One DO per collaboration scope (a `WorkspaceHub` for presence/signals, a per-document `PageSync` for edit traffic) — never one monolithic DO | socket.io + Redis pub/sub, Ably, Pusher | SSE covers most "live" needs with zero new services; hibernating DOs bill ~nothing while idle. Third-party realtime vendors were migrated off |
|
|
23
|
-
| Collaborative editing | Tiptap editor (markdown as source of truth), Yjs CRDT sync via `y-partyserver` inside the document DO; server-side sanitization/versioning still applies to every CRDT write | CodeMirror (abandoned 2026-05-21), ElectricSQL (removed), RxDB (reversed to plain Dexie where local cache is needed) | Locked through real reversals; CRDT must never become an XSS/version/audit bypass |
|
|
24
|
-
| Analytics | Self-hosted Plausible (`pb.vegastack.com`), proxied via `next-plausible` where applicable | Google Analytics, Vercel Analytics, PostHog by default | Self-hosted, privacy-clean, already running — recurs across 5 projects |
|
|
25
|
-
| Hosting | Cloudflare Workers via OpenNext, or self-managed servers (Docker; Coolify for push-to-deploy). Both recorded in the profile | Vercel as a default (allowed only by explicit per-project exception, recorded); a parallel Vercel path once committed to Cloudflare | Cost and egress economics favor Cloudflare/self-host; split deploy paths drift. EVE-hosted workloads are a legitimate Vercel exception |
|
|
26
|
-
| Email | AWS SES (behind an adapter); handle bounces/complaints via the SNS-webhook pattern into a suppression list | Per-vendor SDK sprawl, sending to suppressed addresses | Cheap, boring, already proven in-house; unhandled bounces poison sender reputation |
|
|
27
|
-
| Payments | Stripe; webhook intake isolated (own worker or route) with idempotent event handling | Building billing logic inside request handlers | Money paths get the full rigor tier: idempotency keys, event dedupe, DLQ |
|
|
28
|
-
| Monorepo | Turborepo + workspaces (Bun or pnpm) when there is more than one package; apps → packages only, enforced by a boundary check | Deep relative cross-package imports, packages importing apps | Import direction is a guard script, not a convention |
|
|
29
|
-
| i18n | next-intl, `localePrefix: 'as-needed'`; no hardcoded user-facing strings | Ad-hoc string tables | Already the proven pattern; English-only today but scaffolded |
|
|
30
|
-
| APIs | Contract-first: zod schemas → OpenAPI 3.1; auth before parse; cursor pagination on every list; RFC 9457 problem-details errors | Route-local validation, raw `request.json()`, unbounded lists | One contract feeds web, Flutter, MCP, and public consumers; sanitize responses (never return secrets/credential refs) |
|
|
31
|
-
|
|
32
|
-
## Choosing between the two hosting targets
|
|
33
|
-
|
|
34
|
-
Cloudflare (OpenNext) when: public web product, global latency matters, R2/DO/Queues fit,
|
|
35
|
-
cost-per-request dominates. Self-managed (Docker/Hetzner) when: self-hosting is a product
|
|
36
|
-
requirement (licensed/enterprise), the workload needs long-lived processes (EVE, heavy
|
|
37
|
-
workers), or platform independence is worth ~30 min/month of ops. Many products use both:
|
|
38
|
-
OpenNext app on Cloudflare + a long-running worker container beside the database.
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
# Web — frontend, design system, backend/API taste
|
|
2
|
-
|
|
3
|
-
## Project layout
|
|
4
|
-
|
|
5
|
-
- Top-level `server/` (or `packages/*` server code) holds DB, services, integrations — it
|
|
6
|
-
is sacred: never imported from client code, enforced with `server-only` + a boundary
|
|
7
|
-
check. The app router lives in `src/app/`; features are self-contained modules under
|
|
8
|
-
`src/features/<domain>/` (components/hooks per feature); shared pieces in
|
|
9
|
-
`src/{components,hooks,lib,store,messages}/`. Pages stay thin — they compose features.
|
|
10
|
-
- The house convention scopes agent instructions per directory (a short CLAUDE.md/AGENTS.md
|
|
11
|
-
at each boundary-bearing directory) — follow it where a project already does this.
|
|
12
|
-
|
|
13
|
-
## Frontend architecture
|
|
14
|
-
|
|
15
|
-
- Server state → RSC + TanStack Query; URL state → nuqs; ephemeral UI state → Zustand.
|
|
16
|
-
Remote/tenant data is never persisted client-side (IndexedDB/local-first was evaluated and
|
|
17
|
-
rejected for multi-tenant SaaS on governance grounds — a confirmed single-tenant/offline
|
|
18
|
-
product is the only exception, and that's a decision to ask for, not assume).
|
|
19
|
-
- Next config: `output: 'standalone'`, `cacheComponents: true` (PPR flags no longer exist).
|
|
20
|
-
Query key factories live in non-`'use client'` `.keys.ts` modules so RSC pages can prefetch.
|
|
21
|
-
- Server Components by default; `'use client'` only at the lowest leaf that needs it.
|
|
22
|
-
`/server` (or `packages/*` server code) is never imported from client code — `server-only`
|
|
23
|
-
plus a boundary check, not convention.
|
|
24
|
-
- Request interception: on OpenNext/Cloudflare, `proxy.ts`/Node middleware does NOT work
|
|
25
|
-
as of 2026-08 (open adapter issues — see infra.md); use what the adapter actually
|
|
26
|
-
supports and re-check its docs per feature. Either way interception is never the auth
|
|
27
|
-
boundary (see security.md).
|
|
28
|
-
- Internal navigation uses `next/link`; bare `<a>` only for true external links/downloads.
|
|
29
|
-
- Never TypeScript `any` — `unknown` and narrow.
|
|
30
|
-
- Dependencies: prefer the native/browser API when it covers the need
|
|
31
|
-
(`Intl.RelativeTimeFormat` over date-fns); self-implement simple behavior (a tween, a diff
|
|
32
|
-
view) by composing design-system primitives before adding a library; any non-trivial new
|
|
33
|
-
dependency needs a stated justification and approval. **Why:** self-contained code has no
|
|
34
|
-
supply-chain, licensing, or upgrade surface, and MK rejects "library because library".
|
|
35
|
-
|
|
36
|
-
## Design system (consuming `@vegastack/design` / `@vegastack/ui`)
|
|
37
|
-
|
|
38
|
-
- First-time setup in a fresh project (npm install, Tailwind wiring, provider, registry
|
|
39
|
-
auth) is a separate, already-built skill — `vegastack-consume` — use it; don't
|
|
40
|
-
re-derive the wiring here.
|
|
41
|
-
- Consume through the package barrel, never deep imports; never edit shipped component files.
|
|
42
|
-
A missing component or variant is an upstream request MK decides — the recommended
|
|
43
|
-
interim pattern is a local presentational composition in the app (inferred — confirm on
|
|
44
|
-
first use); don't fork the system.
|
|
45
|
-
- Semantic tokens only: no inline `style={}`, no arbitrary values (`bg-[#123]`, `h-[13px]`),
|
|
46
|
-
no raw palette classes (`bg-neutral-900`), not even for opacity. Dynamic values go through
|
|
47
|
-
CSS custom properties.
|
|
48
|
-
- Visual language: borders-only cards, flat surfaces (shadows only for true overlays); accent
|
|
49
|
-
color rationed to the primary action and real selected/active state — never hover, roughly
|
|
50
|
-
≤10 accent elements a page. Focus stays visible always (WCAG 2.2 AA floor) — the house
|
|
51
|
-
mechanism is a darker border or native outline, never a `ring-*` utility. Control heights
|
|
52
|
-
on the 28/32/40 scale = `h-7`/`h-8`/`h-10` (no off-scale `h-9`); radius: `rounded-md`
|
|
53
|
-
controls, `rounded-sm` menu items, `rounded-lg` containers, `rounded-full` pills — never
|
|
54
|
-
`rounded-xl` on cards/dialogs. Touch-target minimum is genuinely unresolved (24px WCAG AA
|
|
55
|
-
vs 44px mobile-HIG both appear in the record) — ask MK when it matters.
|
|
56
|
-
- Typography: product code caps at `font-semibold` and uses it sparingly (inside the design
|
|
57
|
-
system package itself the cap is `font-medium`); headings `font-lora`; numbers, versions,
|
|
58
|
-
costs, and card digits always `font-mono`; uppercase only in mono at ≤14px. Fonts are
|
|
59
|
-
self-hosted via `next/font` — never a Google Fonts CDN request.
|
|
60
|
-
- Icons: Lucide only. Toasts: sonner, never native/OS prompts. 4px spacing scale.
|
|
61
|
-
- Motion: subtle and minimal — no button hover/press animations, no `transition-all`; motion
|
|
62
|
-
is for state feedback (success/error) and structural changes. `prefers-reduced-motion`
|
|
63
|
-
disables animation entirely (instant, not softened).
|
|
64
|
-
- No decorative filler: no gratuitous badges, eyebrow text, or "3 of 3" counters — MK cuts
|
|
65
|
-
these on sight as AI-generated design tells.
|
|
66
|
-
- Light AND dark theme on every shipped surface (internal-only tools may opt out, stated
|
|
67
|
-
up front).
|
|
68
|
-
|
|
69
|
-
## UI completeness bar (every real surface, before it's "done")
|
|
70
|
-
|
|
71
|
-
- Full state matrix: loading skeleton shaped like the real content · empty state with
|
|
72
|
-
guidance · error routed to the specific failure (401/403, 404, 410, 429, generic — never
|
|
73
|
-
swallowed into `{}`) · success feedback. Optimistic updates where a mutation changes
|
|
74
|
-
visible state; the UI reflects backend mutations without a manual refresh.
|
|
75
|
-
- Forms: Enter submits; spinner replaces the button icon (not beside it); first field
|
|
76
|
-
auto-focused; all fields disabled while the primary action runs; state resets on error or
|
|
77
|
-
navigation.
|
|
78
|
-
- Responsive: no horizontal scroll at any width; computed layout math, never hardcoded pixel
|
|
79
|
-
offsets; truncation driven by available space, not character counts; iOS inputs must not
|
|
80
|
-
zoom on focus; the mobile keyboard must never occlude the active input.
|
|
81
|
-
- Systemic fixes: a defect found in one component (scroll leaking through an overlay, missing
|
|
82
|
-
cursor, wrong token) is fixed across the whole family in one sweep, with evidence.
|
|
83
|
-
- Perceived speed is the named pattern "instant shell": the layout shell renders
|
|
84
|
-
immediately (RSC + `cacheComponents`), every data region streams in behind a skeleton
|
|
85
|
-
shaped like its content — never a blank page or a full-page spinner.
|
|
86
|
-
- Long lists (>~50 rows) virtualize with `@tanstack/react-virtual` (the approved package —
|
|
87
|
-
still state the justification when adding it).
|
|
88
|
-
- Cloning/rebranding a reference site: strip every trace of the source tool's provenance
|
|
89
|
-
(class names, external asset domains, meta) so the result is fully self-contained, and
|
|
90
|
-
verify parity by clicking through as a real user — screenshot diffing alone doesn't count.
|
|
91
|
-
|
|
92
|
-
## SEO and metadata (every public-facing app)
|
|
93
|
-
|
|
94
|
-
- Every route exports `metadata`/`generateMetadata` — not just the root layout. Public
|
|
95
|
-
apps ship `sitemap.ts` and `robots.ts`; `llms.txt` is generated at build time, never
|
|
96
|
-
hand-maintained. OG images follow the house convention (generated, mono for
|
|
97
|
-
names/numbers). i18n uses next-intl `localePrefix: 'as-needed'` (stack.md) so default-
|
|
98
|
-
locale URLs stay clean. **Why:** MK has demanded "100% SEO" coverage on shipped sites
|
|
99
|
-
twice — treat it as a completeness bar, not an enhancement.
|
|
100
|
-
|
|
101
|
-
## Testing (web/backend — test where it pays off)
|
|
102
|
-
|
|
103
|
-
- Unit-test services and pure logic (Vitest); component/a11y tests run in Vitest browser
|
|
104
|
-
mode with the Playwright provider (real Chromium — jsdom's ARIA/layout gaps produce
|
|
105
|
-
false a11y results and are not trusted).
|
|
106
|
-
- E2E is a walking skeleton: one real happy path (boot → auth → core mutation → verify)
|
|
107
|
-
plus targeted adversarial invariants — not a blanket suite. DB-gated tests are authored
|
|
108
|
-
even when execution is blocked, and gated behind an env flag (`RUN_DB_TESTS=1` pattern);
|
|
109
|
-
E2E doubles implement real ports, never a parallel mock pathway.
|
|
110
|
-
- Every confirmed bug gets a regression test; no coverage-percentage targets — "100%"
|
|
111
|
-
means audit completeness, never a coverage metric. VRT/demo content is deterministic
|
|
112
|
-
(no `Date.now()`/`Math.random()`).
|
|
113
|
-
|
|
114
|
-
## Backend / API design
|
|
115
|
-
|
|
116
|
-
- Contract-first: zod schemas are the single source → OpenAPI 3.1 under `/api/v1`, camelCase,
|
|
117
|
-
cursor pagination on every list, RFC 9457 problem-details errors with a stable error-code
|
|
118
|
-
catalog. One contract serves web, Flutter, MCP, and public consumers.
|
|
119
|
-
- Every route: auth before parse, through the shared route-handler wrapper — no route-local
|
|
120
|
-
zod, no raw `request.json()`. Sanitize responses: secrets and credential references never
|
|
121
|
-
leave the server. Enforce with a CI ratchet (unwrapped routes fail the build), not review.
|
|
122
|
-
- Return what the view renders, not everything — no mega-responses. Select columns explicitly.
|
|
123
|
-
- Body limits stream: count bytes as they arrive and abort at the limit; never buffer the
|
|
124
|
-
whole body first.
|
|
125
|
-
- Webhooks over polling; the database is the source of truth. Once provider state lands in
|
|
126
|
-
Postgres via webhook, don't re-poll the provider — and don't build intermediary "waiting"
|
|
127
|
-
pages; redirect on success and let the rest arrive in the background.
|
|
128
|
-
- Money paths (Stripe): isolated intake (own worker or route), idempotency keys on every
|
|
129
|
-
mutation, event dedupe, append-only ledgers as the single cost source of truth, audit
|
|
130
|
-
events written in the same transaction as the business change. Pricing/quoting math is
|
|
131
|
-
deterministic code with server-enforced invariants — AI drafts and judges; deterministic
|
|
132
|
-
code calculates and enforces (a proven client-project pattern; treat as the strong
|
|
133
|
-
default for money paths).
|
|
134
|
-
- Centralize the boring: one ID helper (never raw `crypto.randomUUID` scattered), one logger
|
|
135
|
-
(never `console.log`; request/org context auto-injected; never log secrets or PII beyond
|
|
136
|
-
user IDs), one typed fail-hard zod env parser validated at startup — no
|
|
137
|
-
`DISABLE_AUTH`-style escape hatches, no plaintext fallbacks.
|
|
138
|
-
- Dead endpoints get deleted, not deprecated-and-kept. (Exception: a versioned `/api/v1`
|
|
139
|
-
contract consumed by a shipped mobile app outlives web deploy cycles — app-store install
|
|
140
|
-
lag means mobile clients keep the old contract alive; see mobile.md.)
|
|
141
|
-
- Outbound webhooks to customers (when a product offers them): a defined event catalog,
|
|
142
|
-
Standard Webhooks-style signing with rotatable secrets, delivery via the transactional
|
|
143
|
-
outbox → queue with retries/backoff, auto-disable an endpoint after N consecutive
|
|
144
|
-
failures, and a replay surface. Never fire webhooks inline from request handlers.
|
|
145
|
-
- Platform-native mentions over plain text: when the data allows it, resolve and mention
|
|
146
|
-
the real entity (Slack user @mentions, Notion Person fields) instead of rendering names
|
|
147
|
-
as text — the recurring "don't just fix, improve" integration principle.
|
|
148
|
-
- Wizard/checkout flow-state persistence (Zustand + sessionStorage vs server-side draft)
|
|
149
|
-
is not yet a settled house rule — ask MK when building one.
|
|
150
|
-
- Rate-limited responses return 429 with a `Retry-After` header.
|
|
151
|
-
- Timestamps stored UTC (`timestamptz`), rendered in the user's timezone; durations as
|
|
152
|
-
integer milliseconds; money in integer minor units.
|