@assembly-js/app-foundation 0.0.27 → 0.0.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CLAUDE.md CHANGED
@@ -52,6 +52,7 @@ Assembly-specific gotchas live in `.claude/skills/<name>/SKILL.md`. Invoke them
52
52
  - `assembly-semantic-colors` — semantic color variants (info, success, warning, destructive) for Badge and Alert
53
53
  - `assembly-workspace-labels` — workspace-customized display labels for clients/companies. The server helper comes from `@assembly-js/app-foundation/server`, the `useLabels()` hook from the repo — import them, don't rewrite them.
54
54
  - `assembly-db-transactions` — REQUIRED when a route makes more than one DB write: `db.transaction()` is unsupported on neon-http and 500s. Use `db.batch([...])` for atomic multi-writes, sequential awaits otherwise.
55
+ - `assembly-record-writes` — REQUIRED when a form creates a record that a later save also writes, especially with nested rows (packages, add-ons, line items, blocks): create and save call one writer so create cannot drop submitted fields.
55
56
  - `assembly-session-token` — the bearer lifecycle and how to read identity or fetch data: `getSessionIdentity()`/`useAssembly()`, `useServerAction`/`callAction`, `authFetch`. You never handle a token yourself.
56
57
  - `assembly-route-auth` — REQUIRED on every `app/api/.../[id]/route.ts` handler: `requireAuth` + `requireCompanyScope` before returning or mutating data.
57
58
  - `assembly-scheduled-work` — REQUIRED when the prompt implies anything on a schedule or while nobody has the app open (remind, daily digest, every week, follow up in N days, at a specific time): one `*/30 * * * *` Vercel cron in `vercel.json` hitting one dispatcher route, every scheduled action a row in a schedule table, `CRON_SECRET` declared in `requiredSecrets` and verified on every tick, `getOfflineAssembly()` for platform calls. Checking for due work on page load ships a schedule that never fires.
@@ -349,6 +350,7 @@ export const items = pgTable('items', {
349
350
  - Use `drizzle-kit push` (not `migrate`) — no migration files; schema is pushed directly.
350
351
  - Use `@neondatabase/serverless` with `drizzle-orm/neon-http` — not `pg` or `postgres.js`. It's the standard driver for the Vercel serverless (Node) runtime this app deploys to; `lib/db.ts` already wires it up.
351
352
  - **No interactive transactions.** `db.transaction(async (tx) => …)` is unsupported on neon-http and throws (a 500). For atomic multi-writes use `db.batch([...])`; otherwise sequential `await`s. See the `assembly-db-transactions` skill.
353
+ - **Create writes every field the create form submits.** If a later save writes nested rows (packages, add-ons, blocks), create calls that same writer with the submitted payload. A success toast and a redirect to the editor do not save. See `assembly-record-writes`.
352
354
  - `DATABASE_URL` is already in the shell env. Do NOT hardcode it or write it to `.env.local`.
353
355
  - Use `db` from `lib/db.ts` in **server components, server actions, and route handlers only** — never in client components. This is enforced: `lib/db.ts` imports `server-only`, so pulling it (or anything that imports it, e.g. a lib file re-exporting constants) into a `'use client'` module fails `bun run build`. Keep client-shared constants/types in their own file, not next to server queries.
354
356
  - **Validate values before they hit typed columns — not because they arrived as route params.** Route segments can be slugs or external ids. When the next step is a uuid-column lookup, use `parseUuid` / `isUuid` from `@assembly-js/app-foundation/server` so a bad value 400/404s instead of Postgres throwing `invalid input syntax for type uuid`. In server components/pages: `if (!isUuid(id)) notFound()` before the query.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assembly-js/app-foundation",
3
- "version": "0.0.27",
3
+ "version": "0.0.28",
4
4
  "description": "Versioned foundation helpers and build context for Assembly generated apps",
5
5
  "type": "module",
6
6
  "files": [
@@ -0,0 +1,39 @@
1
+ ---
2
+ name: assembly-record-writes
3
+ description: REQUIRED when a form creates a persisted record that a later save or update also writes, especially when the payload includes nested rows (packages, add-ons, line items, blocks, children). Create and save must call one writer so create cannot drop fields the form submitted. A success toast after create does not mean those rows were stored.
4
+ ---
5
+
6
+ A create form often submits the same payload a later edit form saves: parent columns plus nested collections. If create inserts only the parent (and maybe one child table and an audit event) while save writes the rest, the first submit looks successful and the editor opens empty. The redirect after create does not call save.
7
+
8
+ ## The rule
9
+
10
+ One function writes the submitted payload. Create calls it for a new id; save calls it for an existing id.
11
+
12
+ ```ts
13
+ async function writeProposal(id: string, input: ProposalInput) {
14
+ await db.insert(proposals).values({ id, title: input.title, intro: input.intro })
15
+ .onConflictDoUpdate({ target: proposals.id, set: { title: input.title, intro: input.intro } });
16
+ await replaceRows(packages, id, input.packages);
17
+ await replaceRows(addOns, id, input.addOns);
18
+ await replaceRows(blocks, id, input.blocks);
19
+ }
20
+
21
+ export async function createProposal(input: ProposalInput) {
22
+ const id = crypto.randomUUID();
23
+ await writeProposal(id, input);
24
+ await recordEvent(id, 'draft_created');
25
+ return id;
26
+ }
27
+
28
+ export async function saveProposal(id: string, input: ProposalInput) {
29
+ await writeProposal(id, input);
30
+ }
31
+ ```
32
+
33
+ Leave these off the shared payload. They belong to the path that owns them:
34
+
35
+ - ids, timestamps, a draft status, an audit-event row
36
+ - a control flag that chooses an action (for example "send immediately") and is not itself stored, unless save stores that same flag
37
+ - fields that appear only on a later edit form
38
+
39
+ If the form submits it, the create call chain writes it, including by calling the shared writer.