@percepta/create 4.1.7 → 4.1.9

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 (63) 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 +185 -28
  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-B9vKTkoI.js} +89 -37
  10. package/dist/register-app-B9vKTkoI.js.map +1 -0
  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/infra/os.blueprint.yaml.template +13 -0
  22. package/templates/monorepo/auth/README.md +2 -2
  23. package/templates/monorepo/auth/package.json +1 -1
  24. package/templates/monorepo/auth/src/drizzle/migrations/meta/0000_snapshot.json +547 -0
  25. package/templates/monorepo/package.json.template +6 -6
  26. package/templates/monorepo/pnpm-workspace.yaml +9 -1
  27. package/templates/webapp/AGENTS.md +39 -17
  28. package/templates/webapp/README.md +58 -59
  29. package/templates/webapp/agent-skills/access-control.md +13 -12
  30. package/templates/webapp/agent-skills/database.md +12 -5
  31. package/templates/webapp/agent-skills/inngest.md +10 -8
  32. package/templates/webapp/agent-skills/langfuse.md +7 -5
  33. package/templates/webapp/agent-skills/oneshot.md +15 -13
  34. package/templates/webapp/next.config.ts +1 -1
  35. package/templates/webapp/package.json.template +6 -6
  36. package/templates/webapp/playwright.config.ts +1 -2
  37. package/templates/webapp/scripts/seed.ts +3 -3
  38. package/templates/webapp/src/app/(app)/page.tsx +5 -3
  39. package/templates/webapp/src/app/(auth)/layout.tsx +2 -2
  40. package/templates/webapp/src/app/(settings)/settings/page.tsx +21 -22
  41. package/templates/webapp/src/components/FaroProvider.tsx +1 -2
  42. package/templates/webapp/src/components/Header.tsx +2 -8
  43. package/templates/webapp/src/components/form/FormItem.tsx +1 -1
  44. package/templates/webapp/src/drizzle/db.ts +3 -1
  45. package/templates/webapp/src/drizzle/schema/index.ts +1 -1
  46. package/templates/webapp/src/drizzle/schema/utils/jsonbFromZod.ts +1 -1
  47. package/templates/webapp/src/instrumentation.ts +3 -6
  48. package/templates/webapp/src/lib/auth/index.ts +2 -2
  49. package/templates/webapp/src/lib/auth-client.ts +3 -2
  50. package/templates/webapp/src/lib/trpc.ts +1 -1
  51. package/templates/webapp/src/server/trpc.ts +1 -1
  52. package/templates/webapp/src/services/DatabaseService.ts +1 -1
  53. package/templates/webapp/src/services/access/AppAccessControl.ts +1 -3
  54. package/templates/webapp/src/services/inngest/AppWorkflowService.ts +1 -1
  55. package/templates/webapp/src/startup-checks.ts +1 -1
  56. package/templates/webapp/src/styles/globals.css +13 -2
  57. package/dist/manifest-By1SgOjC.js +0 -59
  58. package/dist/manifest-By1SgOjC.js.map +0 -1
  59. package/dist/register-app-DZg-Pmtd.js.map +0 -1
  60. package/dist/template-versions-CEIP9vhl.js +0 -35
  61. package/dist/template-versions-CEIP9vhl.js.map +0 -1
  62. package/dist/validate-dssldJAj.js +0 -14
  63. 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,20 +136,16 @@ 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
151
147
 
152
- This app consumes the customer monorepo's shared [Better Auth](https://better-auth.com) package, `@__CUSTOMER_SLUG__/auth`. The app still serves local development auth routes, but the users, sessions, accounts, groups, and group memberships live in the shared customer auth database. Deployed apps should receive that shared database through `AUTH_DATABASE_URL` from the monorepo auth Secret; `DATABASE_URL` is reserved for this app's own database.
148
+ This app consumes the customer monorepo's shared [Better Auth](https://better-auth.com) package, `@__REPO_NAME__/auth`. The app still serves local development auth routes, but the users, sessions, accounts, groups, and group memberships live in the shared customer auth database. Deployed apps should receive that shared database through `AUTH_DATABASE_URL` from the monorepo auth Secret; `DATABASE_URL` is reserved for this app's own database.
153
149
 
154
150
  Required auth environment variables:
155
151
 
@@ -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,58 @@ 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
+ <!-- prettier-ignore -->
190
+ | Variable | Description | Default |
191
+ | -------------- | ----------------------------- | ------------------------------------------------------------- |
194
192
  | `DATABASE_URL` | App PostgreSQL connection URL | `postgresql://postgres:postgres@localhost:5434/__DB_NAME__` |
195
193
 
196
194
  ### Shared Auth Database
197
195
 
198
- | Variable | Description | Default |
199
- |----------|-------------|---------|
200
- | `AUTH_DATABASE_URL` | Shared auth database URL from the monorepo auth Secret | - |
196
+ | Variable | Description | Default |
197
+ | ------------------- | ------------------------------------------------------ | ------- |
198
+ | `AUTH_DATABASE_URL` | Shared auth database URL from the monorepo auth Secret | - |
201
199
 
202
200
  ### Security
203
201
 
204
- | Variable | Description |
205
- |----------|-------------|
202
+ | Variable | Description |
203
+ | ----------------------- | ----------------------------------- |
206
204
  | `ENCRYPTION_SECRET_KEY` | 32-character key for URL encryption |
207
205
 
208
206
  Generate a secret key:
207
+
209
208
  ```bash
210
209
  node -e "console.log(require('crypto').randomBytes(16).toString('hex'))"
211
210
  ```
212
211
 
213
212
  ### Access Control
214
213
 
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` |
214
+ | Variable | Description | Default |
215
+ | ----------------------- | --------------------------------- | ------------------- |
216
+ | `SPICEDB_ENDPOINT` | SpiceDB gRPC endpoint | `localhost:50051` |
217
+ | `SPICEDB_PRESHARED_KEY` | SpiceDB preshared key | `dev-spicedb-token` |
218
+ | `SPICEDB_INSECURE` | Use insecure local gRPC transport | `true` |
220
219
 
221
220
  ### Inngest (Background Jobs)
222
221
 
223
- | Variable | Description |
224
- |----------|-------------|
225
- | `INNGEST_BASE_URL` | Inngest server URL |
222
+ | Variable | Description |
223
+ | --------------------- | ------------------- |
224
+ | `INNGEST_BASE_URL` | Inngest server URL |
226
225
  | `INNGEST_SIGNING_KEY` | Inngest signing key |
227
- | `INNGEST_EVENT_KEY` | Inngest event key |
226
+ | `INNGEST_EVENT_KEY` | Inngest event key |
228
227
 
229
228
  ### Langfuse (LLM Observability)
230
229
 
231
- | Variable | Description |
232
- |----------|-------------|
233
- | `LANGFUSE_BASE_URL` | Langfuse server URL |
230
+ | Variable | Description |
231
+ | --------------------- | ------------------- |
232
+ | `LANGFUSE_BASE_URL` | Langfuse server URL |
234
233
  | `LANGFUSE_PUBLIC_KEY` | Langfuse public key |
235
234
  | `LANGFUSE_SECRET_KEY` | Langfuse secret key |
236
235
 
@@ -238,12 +237,12 @@ Set these values through the target deployment platform when Langfuse is enabled
238
237
 
239
238
  ### LLM Providers
240
239
 
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 |
240
+ | Variable | Description |
241
+ | ------------------- | --------------------------------------------------- |
242
+ | `ANTHROPIC_API_KEY` | Anthropic API key |
243
+ | `OPENAI_API_KEY` | OpenAI API key for local or non-demo deployments |
244
+ | `LLM_PROVIDER` | Optional provider override: `anthropic` or `openai` |
245
+ | `LLM_MODEL` | Optional model override |
247
246
 
248
247
  Use `LLMService` for backend model calls:
249
248
 
@@ -261,11 +260,11 @@ For local development, `pnpm dev` also loads `~/.config/percepta/create.env` whe
261
260
 
262
261
  ### OpenTelemetry / LGTM
263
262
 
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 |
263
+ | Variable | Description |
264
+ | ------------------------------------ | -------------------------------------------- |
265
+ | `OTEL_EXPORTER_OTLP_ENDPOINT` | Base OTLP HTTP collector endpoint |
266
+ | `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | Optional trace-specific OTLP endpoint |
267
+ | `OTEL_TRACES_EXPORTER` | Set to `none` to disable server trace export |
269
268
 
270
269
  The generated app sets its OpenTelemetry service name and deployment
271
270
  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",
@@ -256,13 +257,13 @@ await getAccessControl().app.assignAppRole(
256
257
 
257
258
  ## Shared Auth Boundary
258
259
 
259
- This app imports Better Auth from `@__CUSTOMER_SLUG__/auth` via `src/lib/auth/index.ts`. The app does not own `users`, `groups`, `group_members`, sessions, accounts, or verification tables.
260
+ This app imports Better Auth from `@__REPO_NAME__/auth` via `src/lib/auth/index.ts`. The app does not own `users`, `groups`, `group_members`, sessions, accounts, or verification tables.
260
261
 
261
262
  When app code needs users for display, import from the shared auth package:
262
263
 
263
264
  ```ts
264
- import { db as authDb } from "@__CUSTOMER_SLUG__/auth/db";
265
- import { users } from "@__CUSTOMER_SLUG__/auth/schema";
265
+ import { db as authDb } from "@__REPO_NAME__/auth/db";
266
+ import { users } from "@__REPO_NAME__/auth/schema";
266
267
  ```
267
268
 
268
269
  Use `users.id` in SpiceDB refs. `users.external_id`, email, and group external IDs are ingestion lookup keys only.
@@ -10,7 +10,7 @@ Create a new schema file alongside the existing ones:
10
10
 
11
11
  ```typescript
12
12
  import { pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
13
- import { users } from "@__CUSTOMER_SLUG__/auth/schema";
13
+ import { users } from "@__REPO_NAME__/auth/schema";
14
14
 
15
15
  export const documents = pgTable("documents", {
16
16
  id: uuid("id").defaultRandom().primaryKey(),
@@ -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,9 @@ pnpm --dir ../.. run docker:down
129
135
 
130
136
  ## Environment Variables
131
137
 
132
- | Variable | Default | Description |
133
- |----------|---------|-------------|
138
+ <!-- prettier-ignore -->
139
+ | Variable | Default | Description |
140
+ | -------------- | ------------------------------------------------------------- | ----------------------------- |
134
141
  | `DATABASE_URL` | `postgresql://postgres:postgres@localhost:5434/__DB_NAME__` | App PostgreSQL connection URL |
135
142
 
136
143
  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. |
@@ -9,7 +9,7 @@ const nextConfig: NextConfig = {
9
9
  // Enable standalone output for Docker:
10
10
  output: "standalone",
11
11
  outputFileTracingRoot: monorepoRoot,
12
- transpilePackages: ["@__CUSTOMER_SLUG__/auth"],
12
+ transpilePackages: ["@__REPO_NAME__/auth"],
13
13
  turbopack: {
14
14
  root: monorepoRoot,
15
15
  },
@@ -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,10 +43,10 @@
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:*",
46
+ "@__REPO_NAME__/auth": "workspace:*",
50
47
  "@percepta/access-control": "^1.0.0",
51
48
  "@percepta/ai": "^0.1.0",
52
- "@percepta/database": "^0.1.2",
49
+ "@percepta/database": "0.1.3",
53
50
  "@percepta/design": "^0.4.1",
54
51
  "@percepta/inngest": "^0.1.0",
55
52
  "@percepta/logger": "^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",
@@ -52,9 +52,9 @@ async function main(): Promise<void> {
52
52
  // oxlint-disable-next-line typescript/no-explicit-any
53
53
  (globalThis as any).AsyncLocalStorage = AsyncLocalStorage;
54
54
 
55
- const { auth } = await import("@__CUSTOMER_SLUG__/auth");
56
- const { db: authDb } = await import("@__CUSTOMER_SLUG__/auth/db");
57
- const { users } = await import("@__CUSTOMER_SLUG__/auth/schema");
55
+ const { auth } = await import("@__REPO_NAME__/auth");
56
+ const { db: authDb } = await import("@__REPO_NAME__/auth/db");
57
+ const { users } = await import("@__REPO_NAME__/auth/schema");
58
58
  const { getAccessControl, toUserSubject } =
59
59
  await import("../src/services/access/AppAccessControl");
60
60
  const { getEnvConfig } = await import("../src/config/getEnvConfig");
@@ -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,14 +1,14 @@
1
- import type {
2
- AccessRoleDefinition,
3
- ApplicationGrant,
4
- SubjectRef,
1
+ import { listPrincipals } from "@__REPO_NAME__/auth/principals";
2
+ import {
3
+ type AccessRoleDefinition,
4
+ type ApplicationGrant,
5
+ type SubjectRef,
6
+ groupSubjectRef,
5
7
  } from "@percepta/access-control";
6
- import { groupSubjectRef } from "@percepta/access-control";
7
8
  import {
8
9
  PrincipalMultiInput,
9
10
  type PrincipalOption,
10
11
  } from "@percepta/access-control/react";
11
- import { listPrincipals } from "@__CUSTOMER_SLUG__/auth/principals";
12
12
  import {
13
13
  Badge,
14
14
  Table,
@@ -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