@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.
Files changed (35) hide show
  1. package/README.md +3 -3
  2. package/dist/index.js +5 -5
  3. package/package.json +1 -1
  4. package/skill/dev-architect/SKILL.md +93 -0
  5. package/skill/dev-architect/agents/openai.yaml +4 -0
  6. package/skill/dev-architect/references/ai-agents.md +89 -0
  7. package/skill/{architect → dev-architect}/references/data.md +43 -44
  8. package/skill/dev-architect/references/infra.md +98 -0
  9. package/skill/dev-architect/references/mobile.md +75 -0
  10. package/skill/{architect → dev-architect}/references/pinned-facts.md +17 -16
  11. package/skill/dev-architect/references/principles.md +117 -0
  12. package/skill/{architect → dev-architect}/references/security.md +37 -44
  13. package/skill/dev-architect/references/stack.md +38 -0
  14. package/skill/dev-architect/references/web.md +102 -0
  15. package/skill/{architect → dev-architect}/refresh/REFRESH.md +8 -6
  16. package/skill/{architect → dev-architect}/refresh/sources.json +5 -10
  17. package/skill/dev-implement/SKILL.md +3 -3
  18. package/skill/dev-intake/SKILL.md +2 -2
  19. package/skill/dev-setup/SKILL.md +8 -5
  20. package/skill/dev-setup/assets/dev-profile.md.template +19 -2
  21. package/skill/dev-setup/references/stack-playbooks.md +2 -2
  22. package/skill/skill-maintainer/references/release-ops.md +3 -3
  23. package/skill-integrity.json +20 -24
  24. package/skill/architect/SKILL.md +0 -68
  25. package/skill/architect/agents/openai.yaml +0 -4
  26. package/skill/architect/assets/adr-template.md +0 -21
  27. package/skill/architect/assets/arch-template.md +0 -20
  28. package/skill/architect/references/advisory.md +0 -102
  29. package/skill/architect/references/ai-agents.md +0 -95
  30. package/skill/architect/references/infra.md +0 -128
  31. package/skill/architect/references/mobile.md +0 -78
  32. package/skill/architect/references/principles.md +0 -91
  33. package/skill/architect/references/project-profile.md +0 -37
  34. package/skill/architect/references/stack.md +0 -38
  35. 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.