@percepta/create 4.1.7 → 4.1.8

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 (54) hide show
  1. package/dist/{git-ops-BD7JNnal.js → git-ops-BNpQnEc1.js} +1 -1
  2. package/dist/{git-ops-BD7JNnal.js.map → git-ops-BNpQnEc1.js.map} +1 -1
  3. package/dist/{github-D3YOEl91.js → github-BOp8VQCY.js} +1 -1
  4. package/dist/{github-D3YOEl91.js.map → github-BOp8VQCY.js.map} +1 -1
  5. package/dist/index.js +107 -15
  6. package/dist/index.js.map +1 -1
  7. package/dist/{init-BD3EyyLO.js → init-CsuO_mu2.js} +2 -4
  8. package/dist/{init-BD3EyyLO.js.map → init-CsuO_mu2.js.map} +1 -1
  9. package/dist/{register-app-DZg-Pmtd.js → register-app-mNc1oYVK.js} +3 -5
  10. package/dist/{register-app-DZg-Pmtd.js.map → register-app-mNc1oYVK.js.map} +1 -1
  11. package/dist/{register-os-blueprint-Cgq1rXzQ.js → register-os-blueprint-Gdyn0pN1.js} +3 -4
  12. package/dist/{register-os-blueprint-Cgq1rXzQ.js.map → register-os-blueprint-Gdyn0pN1.js.map} +1 -1
  13. package/dist/{status-K6raTwwu.js → status-BrK9v1yb.js} +3 -3
  14. package/dist/{status-K6raTwwu.js.map → status-BrK9v1yb.js.map} +1 -1
  15. package/dist/{sync-Bi958-2W.js → sync-DC5DhIBT.js} +3 -3
  16. package/dist/{sync-Bi958-2W.js.map → sync-DC5DhIBT.js.map} +1 -1
  17. package/dist/{upstream-CAraZeSS.js → upstream-PNL6DGtl.js} +3 -3
  18. package/dist/{upstream-CAraZeSS.js.map → upstream-PNL6DGtl.js.map} +1 -1
  19. package/package.json +3 -3
  20. package/template-versions.json +2 -2
  21. package/templates/monorepo/auth/src/drizzle/migrations/meta/0000_snapshot.json +547 -0
  22. package/templates/monorepo/package.json.template +6 -6
  23. package/templates/monorepo/pnpm-workspace.yaml +1 -1
  24. package/templates/webapp/AGENTS.md +38 -16
  25. package/templates/webapp/README.md +56 -58
  26. package/templates/webapp/agent-skills/access-control.md +10 -9
  27. package/templates/webapp/agent-skills/database.md +10 -4
  28. package/templates/webapp/agent-skills/inngest.md +10 -8
  29. package/templates/webapp/agent-skills/langfuse.md +7 -5
  30. package/templates/webapp/agent-skills/oneshot.md +15 -13
  31. package/templates/webapp/package.json.template +5 -5
  32. package/templates/webapp/playwright.config.ts +1 -2
  33. package/templates/webapp/src/app/(app)/page.tsx +5 -3
  34. package/templates/webapp/src/app/(auth)/layout.tsx +2 -2
  35. package/templates/webapp/src/app/(settings)/settings/page.tsx +20 -21
  36. package/templates/webapp/src/components/FaroProvider.tsx +1 -2
  37. package/templates/webapp/src/components/Header.tsx +2 -8
  38. package/templates/webapp/src/components/form/FormItem.tsx +1 -1
  39. package/templates/webapp/src/drizzle/db.ts +3 -1
  40. package/templates/webapp/src/drizzle/schema/utils/jsonbFromZod.ts +1 -1
  41. package/templates/webapp/src/instrumentation.ts +3 -6
  42. package/templates/webapp/src/lib/auth-client.ts +3 -2
  43. package/templates/webapp/src/lib/trpc.ts +1 -1
  44. package/templates/webapp/src/server/trpc.ts +1 -1
  45. package/templates/webapp/src/services/DatabaseService.ts +1 -1
  46. package/templates/webapp/src/services/access/AppAccessControl.ts +1 -3
  47. package/templates/webapp/src/services/inngest/AppWorkflowService.ts +1 -1
  48. package/templates/webapp/src/styles/globals.css +13 -2
  49. package/dist/manifest-By1SgOjC.js +0 -59
  50. package/dist/manifest-By1SgOjC.js.map +0 -1
  51. package/dist/template-versions-CEIP9vhl.js +0 -35
  52. package/dist/template-versions-CEIP9vhl.js.map +0 -1
  53. package/dist/validate-dssldJAj.js +0 -14
  54. package/dist/validate-dssldJAj.js.map +0 -1
@@ -82,23 +82,23 @@ src/
82
82
 
83
83
  ## Available Scripts
84
84
 
85
- | Script | Description |
86
- |--------|-------------|
87
- | `pnpm dev` | Start development server with Turbopack |
88
- | `pnpm build` | Build for production |
89
- | `pnpm start` | Start production server |
90
- | `pnpm typecheck` | Type-check with `tsc` |
91
- | `pnpm access:validate` | Validate the access manifest and schema |
92
- | `pnpm access:apply-local` | Apply the merged customer access schema to local SpiceDB |
93
- | `pnpm auth:db:migrate` | Run migrations for the shared customer auth database |
94
- | `pnpm inngest:dev` | Start the local Inngest dev server for this app |
95
- | `pnpm db:generate` | Generate Drizzle migrations |
96
- | `pnpm db:migrate` | Run database migrations |
97
- | `pnpm db:studio` | Run migrations, then open Drizzle Studio |
98
- | `pnpm db:seed` | Seed default shared-auth dev users and local access grants |
99
- | `pnpm test:e2e:install` | Install the Chromium browser used by Playwright |
100
- | `pnpm test:e2e` | Run Playwright e2e tests after local setup |
101
- | `pnpm test:e2e:ui` | Run Playwright e2e tests in UI mode after local setup |
85
+ | Script | Description |
86
+ | ------------------------- | ---------------------------------------------------------- |
87
+ | `pnpm dev` | Start development server with Turbopack |
88
+ | `pnpm build` | Build for production |
89
+ | `pnpm start` | Start production server |
90
+ | `pnpm typecheck` | Type-check with `tsc` |
91
+ | `pnpm access:validate` | Validate the access manifest and schema |
92
+ | `pnpm access:apply-local` | Apply the merged customer access schema to local SpiceDB |
93
+ | `pnpm auth:db:migrate` | Run migrations for the shared customer auth database |
94
+ | `pnpm inngest:dev` | Start the local Inngest dev server for this app |
95
+ | `pnpm db:generate` | Generate Drizzle migrations |
96
+ | `pnpm db:migrate` | Run database migrations |
97
+ | `pnpm db:studio` | Run migrations, then open Drizzle Studio |
98
+ | `pnpm db:seed` | Seed default shared-auth dev users and local access grants |
99
+ | `pnpm test:e2e:install` | Install the Chromium browser used by Playwright |
100
+ | `pnpm test:e2e` | Run Playwright e2e tests after local setup |
101
+ | `pnpm test:e2e:ui` | Run Playwright e2e tests in UI mode after local setup |
102
102
 
103
103
  ## End-to-End Tests
104
104
 
@@ -136,15 +136,11 @@ const logger = getLogger();
136
136
  // Unsafe data (PII) is redacted
137
137
  logger.info(
138
138
  { safe: { requestId }, unsafe: { email } },
139
- "User action completed"
139
+ "User action completed",
140
140
  );
141
141
 
142
142
  // Errors should be passed as the third parameter
143
- logger.error(
144
- { safe: { documentId } },
145
- "Processing failed",
146
- error
147
- );
143
+ logger.error({ safe: { documentId } }, "Processing failed", error);
148
144
  ```
149
145
 
150
146
  ## Authentication
@@ -165,6 +161,7 @@ database Secret. Local development can omit it and use the root-created local
165
161
  `auth` database.
166
162
 
167
163
  To create dev users:
164
+
168
165
  ```bash
169
166
  pnpm db:seed
170
167
  # Creates customer-admin@example.com / password as a customer admin
@@ -181,56 +178,57 @@ App permissions are authored in `src/access/schema.zed`; `src/access/access.mani
181
178
 
182
179
  ### Application
183
180
 
184
- | Variable | Description | Default |
185
- |----------|-------------|---------|
186
- | `NODE_ENV` | Environment mode | `development` |
187
- | `APP_BASE_URL` | Base URL for the app | - |
188
- | `DEPLOYMENT_ENVIRONMENT` | Deployment environment label for telemetry | `NODE_ENV` |
181
+ | Variable | Description | Default |
182
+ | ------------------------ | ------------------------------------------ | ------------- |
183
+ | `NODE_ENV` | Environment mode | `development` |
184
+ | `APP_BASE_URL` | Base URL for the app | - |
185
+ | `DEPLOYMENT_ENVIRONMENT` | Deployment environment label for telemetry | `NODE_ENV` |
189
186
 
190
187
  ### App Database
191
188
 
192
- | Variable | Description | Default |
193
- |----------|-------------|---------|
189
+ | Variable | Description | Default |
190
+ | -------------- | ----------------------------- | ------------------------------------------------------------- |
194
191
  | `DATABASE_URL` | App PostgreSQL connection URL | `postgresql://postgres:postgres@localhost:5434/__DB_NAME__` |
195
192
 
196
193
  ### Shared Auth Database
197
194
 
198
- | Variable | Description | Default |
199
- |----------|-------------|---------|
200
- | `AUTH_DATABASE_URL` | Shared auth database URL from the monorepo auth Secret | - |
195
+ | Variable | Description | Default |
196
+ | ------------------- | ------------------------------------------------------ | ------- |
197
+ | `AUTH_DATABASE_URL` | Shared auth database URL from the monorepo auth Secret | - |
201
198
 
202
199
  ### Security
203
200
 
204
- | Variable | Description |
205
- |----------|-------------|
201
+ | Variable | Description |
202
+ | ----------------------- | ----------------------------------- |
206
203
  | `ENCRYPTION_SECRET_KEY` | 32-character key for URL encryption |
207
204
 
208
205
  Generate a secret key:
206
+
209
207
  ```bash
210
208
  node -e "console.log(require('crypto').randomBytes(16).toString('hex'))"
211
209
  ```
212
210
 
213
211
  ### Access Control
214
212
 
215
- | Variable | Description | Default |
216
- |----------|-------------|---------|
217
- | `SPICEDB_ENDPOINT` | SpiceDB gRPC endpoint | `localhost:50051` |
218
- | `SPICEDB_PRESHARED_KEY` | SpiceDB preshared key | `dev-spicedb-token` |
219
- | `SPICEDB_INSECURE` | Use insecure local gRPC transport | `true` |
213
+ | Variable | Description | Default |
214
+ | ----------------------- | --------------------------------- | ------------------- |
215
+ | `SPICEDB_ENDPOINT` | SpiceDB gRPC endpoint | `localhost:50051` |
216
+ | `SPICEDB_PRESHARED_KEY` | SpiceDB preshared key | `dev-spicedb-token` |
217
+ | `SPICEDB_INSECURE` | Use insecure local gRPC transport | `true` |
220
218
 
221
219
  ### Inngest (Background Jobs)
222
220
 
223
- | Variable | Description |
224
- |----------|-------------|
225
- | `INNGEST_BASE_URL` | Inngest server URL |
221
+ | Variable | Description |
222
+ | --------------------- | ------------------- |
223
+ | `INNGEST_BASE_URL` | Inngest server URL |
226
224
  | `INNGEST_SIGNING_KEY` | Inngest signing key |
227
- | `INNGEST_EVENT_KEY` | Inngest event key |
225
+ | `INNGEST_EVENT_KEY` | Inngest event key |
228
226
 
229
227
  ### Langfuse (LLM Observability)
230
228
 
231
- | Variable | Description |
232
- |----------|-------------|
233
- | `LANGFUSE_BASE_URL` | Langfuse server URL |
229
+ | Variable | Description |
230
+ | --------------------- | ------------------- |
231
+ | `LANGFUSE_BASE_URL` | Langfuse server URL |
234
232
  | `LANGFUSE_PUBLIC_KEY` | Langfuse public key |
235
233
  | `LANGFUSE_SECRET_KEY` | Langfuse secret key |
236
234
 
@@ -238,12 +236,12 @@ Set these values through the target deployment platform when Langfuse is enabled
238
236
 
239
237
  ### LLM Providers
240
238
 
241
- | Variable | Description |
242
- |----------|-------------|
243
- | `ANTHROPIC_API_KEY` | Anthropic API key |
244
- | `OPENAI_API_KEY` | OpenAI API key for local or non-demo deployments |
245
- | `LLM_PROVIDER` | Optional provider override: `anthropic` or `openai` |
246
- | `LLM_MODEL` | Optional model override |
239
+ | Variable | Description |
240
+ | ------------------- | --------------------------------------------------- |
241
+ | `ANTHROPIC_API_KEY` | Anthropic API key |
242
+ | `OPENAI_API_KEY` | OpenAI API key for local or non-demo deployments |
243
+ | `LLM_PROVIDER` | Optional provider override: `anthropic` or `openai` |
244
+ | `LLM_MODEL` | Optional model override |
247
245
 
248
246
  Use `LLMService` for backend model calls:
249
247
 
@@ -261,11 +259,11 @@ For local development, `pnpm dev` also loads `~/.config/percepta/create.env` whe
261
259
 
262
260
  ### OpenTelemetry / LGTM
263
261
 
264
- | Variable | Description |
265
- |----------|-------------|
266
- | `OTEL_EXPORTER_OTLP_ENDPOINT` | Base OTLP HTTP collector endpoint |
267
- | `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | Optional trace-specific OTLP endpoint |
268
- | `OTEL_TRACES_EXPORTER` | Set to `none` to disable server trace export |
262
+ | Variable | Description |
263
+ | ------------------------------------ | -------------------------------------------- |
264
+ | `OTEL_EXPORTER_OTLP_ENDPOINT` | Base OTLP HTTP collector endpoint |
265
+ | `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | Optional trace-specific OTLP endpoint |
266
+ | `OTEL_TRACES_EXPORTER` | Set to `none` to disable server trace export |
269
267
 
270
268
  The generated app sets its OpenTelemetry service name and deployment
271
269
  environment resource label internally. Configure the collector endpoint through
@@ -12,11 +12,11 @@ The customer monorepo has one shared identity layer and one shared SpiceDB deplo
12
12
 
13
13
  There are three separate authorization layers:
14
14
 
15
- | Layer | Owner | Stored as |
16
- |-------|-------|-----------|
17
- | Customer admins | Customer bootstrap / Applications admin | `core/customer:main#admin@core/user:<users.id>` |
18
- | Default application roles | Customer Applications admin | `core/application:<app>#admin/user@core/user:<users.id>` or `core/group:<groups.id>#member` |
19
- | App roles/resources | This app's admin UI and app code | `<app>/system:main#<role>@<subject>` and app resource relationships |
15
+ | Layer | Owner | Stored as |
16
+ | ------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------- |
17
+ | Customer admins | Customer bootstrap / Applications admin | `core/customer:main#admin@core/user:<users.id>` |
18
+ | Default application roles | Customer Applications admin | `core/application:<app>#admin/user@core/user:<users.id>` or `core/group:<groups.id>#member` |
19
+ | App roles/resources | This app's admin UI and app code | `<app>/system:main#<role>@<subject>` and app resource relationships |
20
20
 
21
21
  Customer admins do not automatically get application access. They can manage default app roles, but to enter the app as a user, a user or group must be an application `admin` or `user`.
22
22
 
@@ -195,9 +195,7 @@ const [canReadSalary] = await getAccessControl().permissions.canMany(checks);
195
195
  Use a lifecycle sync wrapper in repositories/services that create, update, or delete rows with SpiceDB-backed relationships. Configure it once with either direct SpiceDB writes for authorization changes that must be visible immediately, or outbox enqueueing for transactional retry.
196
196
 
197
197
  ```ts
198
- import {
199
- createPermissionedResourceLifecycleSync,
200
- } from "@percepta/access-control/drizzle";
198
+ import { createPermissionedResourceLifecycleSync } from "@percepta/access-control/drizzle";
201
199
 
202
200
  const employeeLifecycle = createPermissionedResourceLifecycleSync({
203
201
  apply: {
@@ -222,7 +220,10 @@ Do not model ordinary Postgres columns as SpiceDB objects. Model business permis
222
220
  Use the app access service instead of constructing raw SpiceDB clients in feature code:
223
221
 
224
222
  ```ts
225
- import { getAccessControl, toUserSubject } from "@/services/access/AppAccessControl";
223
+ import {
224
+ getAccessControl,
225
+ toUserSubject,
226
+ } from "@/services/access/AppAccessControl";
226
227
 
227
228
  const allowed = await getAccessControl().permissions.can({
228
229
  permission: "view_private",
@@ -60,13 +60,19 @@ import { eq } from "drizzle-orm";
60
60
  const db = DatabaseService.create().getDatabase();
61
61
 
62
62
  // Select
63
- const docs = await db.select().from(documents).where(eq(documents.userId, userId));
63
+ const docs = await db
64
+ .select()
65
+ .from(documents)
66
+ .where(eq(documents.userId, userId));
64
67
 
65
68
  // Insert
66
69
  await db.insert(documents).values({ title: "New Doc", userId });
67
70
 
68
71
  // Update
69
- await db.update(documents).set({ title: "Updated" }).where(eq(documents.id, docId));
72
+ await db
73
+ .update(documents)
74
+ .set({ title: "Updated" })
75
+ .where(eq(documents.id, docId));
70
76
 
71
77
  // Delete
72
78
  await db.delete(documents).where(eq(documents.id, docId));
@@ -129,8 +135,8 @@ pnpm --dir ../.. run docker:down
129
135
 
130
136
  ## Environment Variables
131
137
 
132
- | Variable | Default | Description |
133
- |----------|---------|-------------|
138
+ | Variable | Default | Description |
139
+ | -------------- | ------------------------------------------------------------- | ----------------------------- |
134
140
  | `DATABASE_URL` | `postgresql://postgres:postgres@localhost:5434/__DB_NAME__` | App PostgreSQL connection URL |
135
141
 
136
142
  Shared auth data belongs to the customer monorepo auth package. Deployed apps
@@ -13,7 +13,9 @@ Create a Zod schema for the event payload:
13
13
  ```typescript
14
14
  import z from "zod";
15
15
 
16
- export type DocumentProcessedPayload = z.infer<typeof DocumentProcessedPayload.SCHEMA>;
16
+ export type DocumentProcessedPayload = z.infer<
17
+ typeof DocumentProcessedPayload.SCHEMA
18
+ >;
17
19
  export namespace DocumentProcessedPayload {
18
20
  export const SCHEMA = z.object({
19
21
  documentId: z.string(),
@@ -135,13 +137,13 @@ The Inngest Dev Server auto-discovers functions by calling the serve endpoint at
135
137
 
136
138
  ## Environment Variables
137
139
 
138
- | Variable | Required | Description |
139
- |----------|----------|-------------|
140
- | `INNGEST_BASE_URL` | Yes | Inngest server URL (`http://localhost:8288` locally) |
141
- | `INNGEST_EVENT_KEY` | Yes (prod) | Event authentication key |
142
- | `INNGEST_SIGNING_KEY` | Yes (prod) | Function registration signing key |
143
- | `INNGEST_APP_URL` | No | Override app URL for Inngest to call back |
144
- | `SKIP_INNGEST_SYNC` | No | Set `true` to skip Inngest app sync on startup |
140
+ | Variable | Required | Description |
141
+ | --------------------- | ---------- | ---------------------------------------------------- |
142
+ | `INNGEST_BASE_URL` | Yes | Inngest server URL (`http://localhost:8288` locally) |
143
+ | `INNGEST_EVENT_KEY` | Yes (prod) | Event authentication key |
144
+ | `INNGEST_SIGNING_KEY` | Yes (prod) | Function registration signing key |
145
+ | `INNGEST_APP_URL` | No | Override app URL for Inngest to call back |
146
+ | `SKIP_INNGEST_SYNC` | No | Set `true` to skip Inngest app sync on startup |
145
147
 
146
148
  ## Key Concepts
147
149
 
@@ -7,6 +7,7 @@ Langfuse is an open-source LLM observability platform. It captures traces, spans
7
7
  ## When to Use Langfuse
8
8
 
9
9
  **Use Langfuse when the app calls LLMs** (OpenAI, Bedrock/Claude, etc.). It gives you:
10
+
10
11
  - Trace visualization of multi-step LLM chains
11
12
  - Token usage and cost tracking per request
12
13
  - Prompt versioning and A/B testing
@@ -69,6 +70,7 @@ platform's environment-scoped variable or secret mechanism.
69
70
  ### Self-Hosted / Langfuse Cloud
70
71
 
71
72
  For external projects, you can:
73
+
72
74
  - Use [Langfuse Cloud](https://cloud.langfuse.com) (free tier available)
73
75
  - Self-host with `docker run langfuse/langfuse`
74
76
 
@@ -109,10 +111,10 @@ Simply don't set the `LANGFUSE_*` env vars. This is the default local developmen
109
111
 
110
112
  ## Environment Variables
111
113
 
112
- | Variable | Required | Description |
113
- |----------|----------|-------------|
114
- | `LANGFUSE_BASE_URL` | No | Langfuse server URL. If unset, Langfuse integration is disabled. |
115
- | `LANGFUSE_PUBLIC_KEY` | No | Public key from Langfuse dashboard |
116
- | `LANGFUSE_SECRET_KEY` | No | Secret key from Langfuse dashboard |
114
+ | Variable | Required | Description |
115
+ | --------------------- | -------- | ---------------------------------------------------------------- |
116
+ | `LANGFUSE_BASE_URL` | No | Langfuse server URL. If unset, Langfuse integration is disabled. |
117
+ | `LANGFUSE_PUBLIC_KEY` | No | Public key from Langfuse dashboard |
118
+ | `LANGFUSE_SECRET_KEY` | No | Secret key from Langfuse dashboard |
117
119
 
118
120
  All three must be set for Langfuse to activate. If any is missing, the integration silently no-ops.
@@ -74,6 +74,7 @@ This is the ONLY point where you pause for user confirmation. After they confirm
74
74
  Based on the confirmed requirements, design the architecture. Do NOT write a plan document — think through it and write it as a brief message to the user (so they can see your thinking), then start building.
75
75
 
76
76
  Cover:
77
+
77
78
  1. **Database schema** — which tables, columns, relationships
78
79
  2. **API routes** — which tRPC routers and procedures
79
80
  3. **Pages** — which routes/pages in the app
@@ -162,6 +163,7 @@ pnpm dev
162
163
  ### Step 3: Verify the app works
163
164
 
164
165
  Open the app in a browser and walk through the core workflows from the requirements. Check:
166
+
165
167
  - Pages load without errors
166
168
  - Data can be created, read, updated, deleted
167
169
  - Auth works (if configured)
@@ -215,16 +217,16 @@ Report the deployment URL to the user when done.
215
217
 
216
218
  ## Common Mistakes
217
219
 
218
- | Mistake | Fix |
219
- |---------|-----|
220
- | Stopping after design to ask permission | After requirements are confirmed, build autonomously. |
221
- | Not running `pnpm build` between chunks | Build after every chunk. Fix errors immediately. |
222
- | Writing custom UI instead of using `@percepta/design` | Check the design system first. |
223
- | Using `console.log` | Use `getLogger()` with safe/unsafe fields. |
224
- | Using `process.env` | Use `getEnvConfig()`. |
225
- | Forgetting to export new schema from the schema index | Every new table must be exported from the index. |
226
- | Forgetting to register Inngest functions in the serve endpoint | Function collections must be added to the serve endpoint. |
227
- | Forgetting to add new router to the root appRouter | Every new tRPC router must be composed in the root. |
228
- | Not verifying the app in the browser | Build + lint passing is necessary but not sufficient. Test the UI. |
229
- | Creating the GitHub repo before the app works locally | Verify locally first, then create the repo. |
230
- | Deploying without the user asking | Only deploy when explicitly requested. |
220
+ | Mistake | Fix |
221
+ | -------------------------------------------------------------- | ------------------------------------------------------------------ |
222
+ | Stopping after design to ask permission | After requirements are confirmed, build autonomously. |
223
+ | Not running `pnpm build` between chunks | Build after every chunk. Fix errors immediately. |
224
+ | Writing custom UI instead of using `@percepta/design` | Check the design system first. |
225
+ | Using `console.log` | Use `getLogger()` with safe/unsafe fields. |
226
+ | Using `process.env` | Use `getEnvConfig()`. |
227
+ | Forgetting to export new schema from the schema index | Every new table must be exported from the index. |
228
+ | Forgetting to register Inngest functions in the serve endpoint | Function collections must be added to the serve endpoint. |
229
+ | Forgetting to add new router to the root appRouter | Every new tRPC router must be composed in the root. |
230
+ | Not verifying the app in the browser | Build + lint passing is necessary but not sufficient. Test the UI. |
231
+ | Creating the GitHub repo before the app works locally | Verify locally first, then create the repo. |
232
+ | Deploying without the user asking | Only deploy when explicitly requested. |
@@ -3,9 +3,6 @@
3
3
  "version": "0.1.0",
4
4
  "private": true,
5
5
  "type": "module",
6
- "engines": {
7
- "node": ">=18.0.0"
8
- },
9
6
  "scripts": {
10
7
  "dev": "tsx ./scripts/with-local-env.ts next dev --turbopack",
11
8
  "build": "next build",
@@ -46,9 +43,9 @@
46
43
  "@opentelemetry/auto-instrumentations-node": "^0.75.0",
47
44
  "@opentelemetry/exporter-trace-otlp-proto": "^0.217.0",
48
45
  "@opentelemetry/sdk-node": "^0.217.0",
49
- "@__CUSTOMER_SLUG__/auth": "workspace:*",
50
46
  "@percepta/access-control": "^1.0.0",
51
47
  "@percepta/ai": "^0.1.0",
48
+ "@__CUSTOMER_SLUG__/auth": "workspace:*",
52
49
  "@percepta/database": "^0.1.2",
53
50
  "@percepta/design": "^0.4.1",
54
51
  "@percepta/inngest": "^0.1.0",
@@ -93,8 +90,8 @@
93
90
  "zod": "^4.1.5"
94
91
  },
95
92
  "devDependencies": {
96
- "@playwright/test": "^1.58.2",
97
93
  "@percepta/build": "^1.0.0",
94
+ "@playwright/test": "^1.58.2",
98
95
  "@tailwindcss/postcss": "^4.1.11",
99
96
  "@types/formidable": "^3.4.5",
100
97
  "@types/he": "^1.2.3",
@@ -112,5 +109,8 @@
112
109
  "tailwindcss": "^4.0.12",
113
110
  "vitest": "^4.0.0",
114
111
  "yargs": "^17.7.2"
112
+ },
113
+ "engines": {
114
+ "node": ">=18.0.0"
115
115
  }
116
116
  }
@@ -2,8 +2,7 @@ import { defineConfig, devices } from "@playwright/test";
2
2
 
3
3
  const port = Number(process.env.PLAYWRIGHT_PORT ?? 3000);
4
4
  const host = process.env.PLAYWRIGHT_HOST ?? "localhost";
5
- const baseURL =
6
- process.env.PLAYWRIGHT_BASE_URL ?? `http://${host}:${port}`;
5
+ const baseURL = process.env.PLAYWRIGHT_BASE_URL ?? `http://${host}:${port}`;
7
6
 
8
7
  export default defineConfig({
9
8
  testDir: "./e2e",
@@ -59,8 +59,8 @@ export default function HomePage() {
59
59
  <p className="app-kicker">Overview</p>
60
60
  <h1 className="app-title">Good afternoon, App Admin</h1>
61
61
  <p className="app-subtitle">
62
- Welcome to __APP_TITLE__. Three items want attention; the workspace
63
- is ready for your team&apos;s first workflows.
62
+ Welcome to __APP_TITLE__. Three items want attention; the workspace is
63
+ ready for your team&apos;s first workflows.
64
64
  </p>
65
65
  </div>
66
66
  <div className="app-actions">
@@ -77,7 +77,9 @@ export default function HomePage() {
77
77
  <span className="app-metric-label">{metric.label}</span>
78
78
  <span
79
79
  className={
80
- metric.tone === "warn" ? "app-chip app-chip-warn" : "app-chip"
80
+ metric.tone === "warn"
81
+ ? "app-chip app-chip-warn"
82
+ : "app-chip"
81
83
  }
82
84
  >
83
85
  {metric.delta}
@@ -24,8 +24,8 @@ export default function AuthLayout({
24
24
  A quieter place to do your team&apos;s most considered work.
25
25
  </h2>
26
26
  <p className="app-auth-copy">
27
- Keep decisions, handoffs, and daily operations in one calm
28
- workspace built for focused teams.
27
+ Keep decisions, handoffs, and daily operations in one calm workspace
28
+ built for focused teams.
29
29
  </p>
30
30
  </div>
31
31
 
@@ -1,9 +1,9 @@
1
- import type {
2
- AccessRoleDefinition,
3
- ApplicationGrant,
4
- SubjectRef,
1
+ import {
2
+ type AccessRoleDefinition,
3
+ type ApplicationGrant,
4
+ type SubjectRef,
5
+ groupSubjectRef,
5
6
  } from "@percepta/access-control";
6
- import { groupSubjectRef } from "@percepta/access-control";
7
7
  import {
8
8
  PrincipalMultiInput,
9
9
  type PrincipalOption,
@@ -32,6 +32,10 @@ import {
32
32
  getCustomerAccessControl,
33
33
  toUserSubject,
34
34
  } from "../../../services/access/AppAccessControl";
35
+ import {
36
+ AccessControlTabs,
37
+ type AccessControlTab,
38
+ } from "./_components/AccessControlTabs";
35
39
  import {
36
40
  type AccessAppRole,
37
41
  type SettingsPermissions,
@@ -39,10 +43,6 @@ import {
39
43
  readAssignableRole,
40
44
  requireAnySettingsPermission,
41
45
  } from "./_lib/accessSettings";
42
- import {
43
- AccessControlTabs,
44
- type AccessControlTab,
45
- } from "./_components/AccessControlTabs";
46
46
 
47
47
  export const metadata: Metadata = {
48
48
  title: "Settings - __APP_TITLE__",
@@ -73,10 +73,10 @@ interface RoleAssignmentRow {
73
73
  readonly definition: RoleDefinition;
74
74
  readonly disabled: boolean;
75
75
  readonly disabledReason?: string;
76
- readonly hiddenFields?: readonly {
76
+ readonly hiddenFields?: ReadonlyArray<{
77
77
  readonly name: string;
78
78
  readonly value: string;
79
- }[];
79
+ }>;
80
80
  readonly options: readonly PrincipalAssignmentOption[];
81
81
  readonly selectedSubjects: readonly SubjectRef[];
82
82
  readonly updateAction: (formData: FormData) => Promise<void>;
@@ -123,7 +123,7 @@ const roleDefinitions = [
123
123
  source: "Application access",
124
124
  },
125
125
  ...(
126
- accessRoleDefinitions as readonly AccessRoleDefinition<AccessAppRole>[]
126
+ accessRoleDefinitions as ReadonlyArray<AccessRoleDefinition<AccessAppRole>>
127
127
  ).map(
128
128
  (definition): RoleDefinition => ({
129
129
  ...definition,
@@ -139,7 +139,9 @@ const appRoleDefinitionMap = new Map<AccessAppRole, RoleDefinition>(
139
139
  .map((definition) => [definition.role as AccessAppRole, definition]),
140
140
  );
141
141
 
142
- export default async function SettingsPage({ searchParams }: SettingsPageProps) {
142
+ export default async function SettingsPage({
143
+ searchParams,
144
+ }: SettingsPageProps) {
143
145
  const permissions = await requireAnySettingsPermission();
144
146
  const params = await searchParams;
145
147
  const section = readSettingsSection(params?.section);
@@ -211,10 +213,7 @@ function RolesTab() {
211
213
  </TableHeader>
212
214
  <TableBody>
213
215
  {roleDefinitions.map((role) => (
214
- <TableRow
215
- key={`${role.source}:${role.role}`}
216
- className="align-top"
217
- >
216
+ <TableRow key={`${role.source}:${role.role}`} className="align-top">
218
217
  <TableCell className="py-4 whitespace-normal">
219
218
  <div className="font-medium text-foreground">{role.label}</div>
220
219
  <div className="text-muted-foreground">{role.description}</div>
@@ -492,10 +491,10 @@ async function listAssignableRoleSubjects(): Promise<
492
491
  > {
493
492
  const app = getAccessControl().app;
494
493
  const entries = await Promise.all(
495
- assignableRoles.map(async (role) => [
496
- role,
497
- new Set(await app.listAppRoleSubjects(role)),
498
- ] as const),
494
+ assignableRoles.map(
495
+ async (role) =>
496
+ [role, new Set(await app.listAppRoleSubjects(role))] as const,
497
+ ),
499
498
  );
500
499
 
501
500
  return new Map(entries);
@@ -2,8 +2,7 @@
2
2
 
3
3
  import { Alert, AlertDescription, AlertTitle, Button } from "@percepta/design";
4
4
  import { FaroProvider as BaseFaroProvider } from "@percepta/next-utils/faro";
5
- import { type ReactNode } from "react";
6
-
5
+ import type { ReactNode } from "react";
7
6
  // Import to trigger Faro initialization at module scope (earliest possible)
8
7
  import "../services/observability/initFaro";
9
8
 
@@ -40,10 +40,7 @@ export default function Header({
40
40
  return (
41
41
  <header className="app-header">
42
42
  <nav aria-label="Primary navigation" className="app-header-nav">
43
- <Link
44
- className="app-header-brand"
45
- href="/"
46
- >
43
+ <Link className="app-header-brand" href="/">
47
44
  <span className="app-header-mark" aria-hidden={true}>
48
45
  {appInitial}
49
46
  </span>
@@ -66,10 +63,7 @@ export default function Header({
66
63
  </div>
67
64
  <DropdownMenu>
68
65
  <DropdownMenuTrigger asChild={true}>
69
- <button
70
- className="app-account-button"
71
- aria-label="Open account menu"
72
- >
66
+ <button className="app-account-button" aria-label="Open account menu">
73
67
  <div className="app-account-text">
74
68
  <p className="text-sm font-medium text-foreground">
75
69
  {user.name || "User"}
@@ -2,7 +2,7 @@ import { Label } from "@percepta/design";
2
2
  import { Slot } from "@radix-ui/react-slot";
3
3
  import { compact } from "lodash-es";
4
4
  import React, { useId } from "react";
5
- import { type ControllerFieldState } from "react-hook-form";
5
+ import type { ControllerFieldState } from "react-hook-form";
6
6
  import { cn } from "../../utils/cn";
7
7
 
8
8
  interface FormItemProps extends React.ComponentProps<"div"> {
@@ -8,7 +8,9 @@ export const { client, db } = createDb();
8
8
  function createDb(): { client: Pool; db: NodePgDatabase } {
9
9
  const { DATABASE_URL: databaseUrl, NODE_ENV: nodeEnv } = getEnvConfig();
10
10
  const pool = createPgPool(
11
- readDatabaseConfig({ env: { DATABASE_URL: databaseUrl, NODE_ENV: nodeEnv } }),
11
+ readDatabaseConfig({
12
+ env: { DATABASE_URL: databaseUrl, NODE_ENV: nodeEnv },
13
+ }),
12
14
  );
13
15
 
14
16
  return { client: pool, db: drizzle(pool) };
@@ -1,5 +1,5 @@
1
1
  import { customType } from "drizzle-orm/pg-core";
2
- import { type z } from "zod";
2
+ import type { z } from "zod";
3
3
 
4
4
  export function jsonbFromZod<TValue>(schema: z.Schema<TValue>): ReturnType<
5
5
  typeof customType<{
@@ -9,14 +9,11 @@ import { getLogger } from "./services/logger/AppLogger";
9
9
  type SpanProcessor = tracing.SpanProcessor;
10
10
 
11
11
  function setDefaultOpenTelemetryEnv(): void {
12
- const {
13
- DEPLOYMENT_ENVIRONMENT: deploymentEnvironment,
14
- NODE_ENV: nodeEnv,
15
- } = getEnvConfig();
12
+ const { DEPLOYMENT_ENVIRONMENT: deploymentEnvironment, NODE_ENV: nodeEnv } =
13
+ getEnvConfig();
16
14
 
17
15
  process.env.OTEL_SERVICE_NAME ??= "__APP_NAME__";
18
- process.env.OTEL_RESOURCE_ATTRIBUTES ??=
19
- `deployment.environment=${deploymentEnvironment ?? nodeEnv}`;
16
+ process.env.OTEL_RESOURCE_ATTRIBUTES ??= `deployment.environment=${deploymentEnvironment ?? nodeEnv}`;
20
17
  }
21
18
 
22
19
  function getOtlpTracesEndpoint(): string | undefined {