@assembly-js/app-foundation 0.6.5 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/CLAUDE.md +6 -6
- package/bin/foundation-exports.mjs +1 -1
- package/dist/app-storage.d.ts +1 -0
- package/dist/app-storage.js +12 -0
- package/package.json +1 -1
- package/skills/assembly-storage/SKILL.md +27 -1
package/CHANGELOG.md
CHANGED
|
@@ -19,6 +19,13 @@ A patch release never changes what an app's build requires; a minor release may
|
|
|
19
19
|
not by hand; see `changes/README.md`. CI fails a PR that changes a check script without a
|
|
20
20
|
`bump: minor` change file.
|
|
21
21
|
|
|
22
|
+
## 0.7.0
|
|
23
|
+
- [new] `deleteFile(key)` in `@assembly-js/app-foundation/server` permanently deletes a file stored with app-storage.
|
|
24
|
+
- [guidance] The assembly-storage skill documents `deleteFile`: deletion is permanent, so apps that want recovery hide the record first and delete the file later.
|
|
25
|
+
|
|
26
|
+
## 0.6.6
|
|
27
|
+
- [guidance] The build agent ends a chat turn by describing what changed instead of reporting its type-check, build, or the screens it didn't open, and knows the platform builds the app after every turn. It still claims "fixed" or "working" only for what it exercised, and talks about verification when asked or when fixing a reported bug.
|
|
28
|
+
|
|
22
29
|
## 0.6.5
|
|
23
30
|
- [guidance] The `assembly-storage` skill says the `assembly` option of `detectPdfFields` is only an opt-in flag. The `assembly-ai` skill and the `assembly.fetch()` prose now call the AI proxy on the installer client (`getInstallerAssembly()`, gated by `await getAssembly()` first) instead of the viewer's client.
|
|
24
31
|
- [behavior] `detectPdfFields` now runs its AI step on the installer client, and degrades to a warning when there is no verified viewer. `options.assembly` is deprecated and ignored; passing it still opts in to the AI step.
|
package/CLAUDE.md
CHANGED
|
@@ -684,17 +684,17 @@ export PATH="$HOME/.bun/bin:$PATH" && bun add <extra-package>
|
|
|
684
684
|
|
|
685
685
|
### Inner verification loop — type-check, don't full-build
|
|
686
686
|
|
|
687
|
-
Use `bunx tsc --noEmit` to
|
|
687
|
+
Use `bunx tsc --noEmit` to check changes while iterating (2–3s, full type errors). After every chat turn that changes app code, the platform runs the type-check and a full build and sends failures back for you to fix, so skip `bun run build` unless the task asks for a build or you changed `lib/schema.ts` and need the new tables now.
|
|
688
688
|
|
|
689
689
|
```bash
|
|
690
690
|
# Inner loop — after edits, every few changes:
|
|
691
691
|
export PATH="$HOME/.bun/bin:$PATH" && bunx tsc --noEmit
|
|
692
692
|
|
|
693
|
-
#
|
|
693
|
+
# Full build — only when the task asks for one:
|
|
694
694
|
export PATH="$HOME/.bun/bin:$PATH" && bun run build
|
|
695
695
|
```
|
|
696
696
|
|
|
697
|
-
|
|
697
|
+
When the task says "verify the build", run `bun run build` once.
|
|
698
698
|
|
|
699
699
|
`bun run build` also validates `.assembly/app.json` (it runs the manifest validator first) and **fails** if the manifest is malformed or a key is misplaced — most commonly a surface like `notification` written at the top level instead of under `surfaces`. If the build stops with a `.assembly/app.json is invalid` error, fix the manifest as the message instructs — every surface belongs under the `surfaces` key. Run `bun run validate:manifest` to check it on its own.
|
|
700
700
|
|
|
@@ -713,9 +713,9 @@ If `tsc --noEmit` is clean and the task says "verify the build", run `bun run bu
|
|
|
713
713
|
|
|
714
714
|
`tsc --noEmit` and `bun run build` prove the code compiles and the static checks pass. Neither renders a page, so both stay green while a page 500s for every visitor — a function passed into a client component, a browser-only import in server code, a field the sample record happened to have. A query against the sandbox database or an SDK call shows the data is there, not that the page reading it renders.
|
|
715
715
|
|
|
716
|
-
You cannot open the app as a signed-in client or team member, and you cannot see the published app or its logs. The builder preview is the user's own signed-in session, so the one runtime signal you have is the dev-server log (`assembly-dev-server-logs`): after changing a page or route, read it
|
|
716
|
+
You cannot open the app as a signed-in client or team member, and you cannot see the published app or its logs. The builder preview is the user's own signed-in session, so the one runtime signal you have is the dev-server log (`assembly-dev-server-logs`): after changing a page or route, read it and fix any new errors before calling the change done. Mention it only if something is wrong.
|
|
717
717
|
|
|
718
|
-
Close
|
|
718
|
+
Close a turn by describing what changed, in product terms — no tool or command names, no list of what you did not open or run. A limitation the user will hit is worth one line ("test cases still reset on reload; I can move them into the database"). Speak about verification only when the user asks whether something works or is safe to publish, or the turn fixes a bug they reported; then say in product terms what you exercised, or whether you reproduced the bug and saw it gone. Say "fixed", "working", or "good to publish" only for behaviour you exercised.
|
|
719
719
|
|
|
720
720
|
When the user reports that the published app errors, do not blame a cold start, caching, or a stale tab without evidence. Ask for the page, who was signed in, and the exact error text or code; reproduce it in the sandbox — the same screen in the preview or the same request against the dev server — and read the dev-server log. If you cannot reproduce it, say so and ask for those details rather than asking the user to reload.
|
|
721
721
|
|
|
@@ -1510,7 +1510,7 @@ Use `AssemblyBridge.header.*` for breadcrumbs, CTAs, and actions menu. See `.cla
|
|
|
1510
1510
|
- ❌ Don't import a native or browser-only library into server code without the serverless setup — `bun run build` passes but the deployed route crashes at request time. Decide the layer first per "Server runs in serverless Node" above.
|
|
1511
1511
|
- ❌ Don't preview a PDF (or any file) in an `<iframe>`, `<embed>` or `<object>` — the portal frame is sandboxed and Chrome won't run its PDF viewer inside it, so the preview renders blank while the build passes. Open the pre-signed URL in a new tab (`assembly-storage` Pattern A) or render pages to a canvas client-side with `next/dynamic` + `ssr: false`.
|
|
1512
1512
|
- ❌ Don't build a PDF-to-form feature on AcroForm fields alone. Call `detectPdfFields` and branch on `method`: `none` and `xfa` need a visible explanation plus a manual add-field path, and `text` fields are suggestions the user must be able to edit before saving. Plenty of real forms are Word exports, scans or XFA, so an AcroForm-only app tells those users their form is empty.
|
|
1513
|
-
- ❌ Don't call a change verified, working, or safe to publish because `tsc --noEmit` and `bun run build` passed
|
|
1513
|
+
- ❌ Don't call a change verified, working, or safe to publish because `tsc --noEmit` and `bun run build` passed, and don't close a turn with a report of your checks. See "What a passing build proves" above.
|
|
1514
1514
|
- ❌ Don't pass a function — or an object that holds one, like a question/field config with `visibleWhen`, `validate`, `render`, or `format` callbacks — as a prop from a server component or page into a `'use client'` component. Props cross that boundary as serialized data, so the render throws `Functions cannot be passed directly to Client Components` and the page 500s for every visitor no matter what the record holds. `tsc --noEmit` and `bun run build` both pass. Keep such a config in a shared module the client component imports itself, or express the logic as data (`{ showWhen: { field: 'entityType', equals: 'llc' } }`) and interpret it client-side.
|
|
1515
1515
|
- ❌ Don't call a function imported from a `'use client'` file from a server component, layout, or server action — e.g. an answer formatter defined in the client component file that the `/n` notification page also renders. The server gets a client reference, not the function, so the render throws and the route 500s for every visitor; `tsc --noEmit` passes. A `'use client'` file should export only components and hooks; put a shared helper in a plain module (`lib/…`) both sides import. `bun run build` fails on this (`check:client-boundary`).
|
|
1516
1516
|
- ❌ Don't use `Select` or `Combobox` to pick any Assembly person or organization record (clients, internal users, team members, staff, companies). Always use `UserCompanySelector` — in forms, filters, dropdowns, "assign to" controls, everywhere.
|
|
@@ -44,7 +44,7 @@ export const NON_CLIENT_EXPORTS = new Set([
|
|
|
44
44
|
'createAssemblyReexchangeHandler', 'cronRoute',
|
|
45
45
|
'decodeBearer', 'detectPdfFields', 'expandClientsByCompany', 'extractPdfText', 'fetchSessionFlags',
|
|
46
46
|
'findResource', 'formatAirtableRecords', 'generateAppClient', 'getAirtableTableSchema', 'getCapturedRecords',
|
|
47
|
-
'getClientAccessScope', 'getCookielessBearer', 'getDownloadUrl', 'getInstalledAppIds', 'getIntegration',
|
|
47
|
+
'getClientAccessScope', 'getCookielessBearer', 'getDownloadUrl', 'deleteFile', 'getInstalledAppIds', 'getIntegration',
|
|
48
48
|
'getInternalUserAccess', 'getLabels', 'getSessionIdentity', 'getUploadUrl',
|
|
49
49
|
'getVerifiedIdentity', 'gmailSendEmail', 'handlePaintedBeacon', 'handleRouteError',
|
|
50
50
|
'hasClientFlag', 'hasFlag', 'installFetchCapture', 'installNetworkCapture',
|
package/dist/app-storage.d.ts
CHANGED
package/dist/app-storage.js
CHANGED
|
@@ -40,3 +40,15 @@ export async function getDownloadUrl(key) {
|
|
|
40
40
|
throw new Error('Download URL request failed: ' + res.status);
|
|
41
41
|
return res.json();
|
|
42
42
|
}
|
|
43
|
+
// NOTE: takes ONE argument. Deletion is permanent — the platform keeps no copy
|
|
44
|
+
// to restore, so apps that want recovery must hide the record first.
|
|
45
|
+
export async function deleteFile(key) {
|
|
46
|
+
if (JWT_SHAPE.test(key)) {
|
|
47
|
+
throw new Error('deleteFile: first argument looks like a session token. ' +
|
|
48
|
+
'The token argument was removed — call deleteFile(key) with the storage key only.');
|
|
49
|
+
}
|
|
50
|
+
const assembly = await getInstallerAssemblyForCaller();
|
|
51
|
+
const res = await assembly.fetch('/v1/app-storage/object?key=' + encodeURIComponent(key), { method: 'DELETE' });
|
|
52
|
+
if (!res.ok)
|
|
53
|
+
throw new Error('Delete request failed: ' + res.status);
|
|
54
|
+
}
|
package/package.json
CHANGED
|
@@ -11,10 +11,11 @@ Use app-storage for files that belong to YOUR app. For files the user expects to
|
|
|
11
11
|
|
|
12
12
|
## Server helpers — `@assembly-js/app-foundation/server`
|
|
13
13
|
|
|
14
|
-
**
|
|
14
|
+
**All three helpers ship in `@assembly-js/app-foundation/server`, an installed dependency** — import them; do NOT recreate or rewrite them:
|
|
15
15
|
|
|
16
16
|
- `getUploadUrl(fileName)` → `{ key, uploadUrl }` — POSTs `{ fileName }` to the platform. Takes **one argument** (the file name only). If you were calling it as `getUploadUrl(token, fileName)` (an old two-arg shape), remove the token — auth is in the bearer cookie and the function will throw if a token is passed as the file name.
|
|
17
17
|
- `getDownloadUrl(key)` → `{ downloadUrl }` — mints a fresh pre-signed GET. Takes **one argument** (the storage key only). Same note: remove any leading token argument.
|
|
18
|
+
- `deleteFile(key)` → `void` — permanently deletes the stored object. Takes **one argument** (the storage key only); the function throws if a token is passed as the key. Deletion is irreversible, so keep the keys in your own database to know what to delete.
|
|
18
19
|
|
|
19
20
|
Store the `key` it returns; never construct an S3 key yourself, and never parse the original file name back out of the key — the platform normalizes the name when forming the key, so the stored `key` is not guaranteed to contain your exact `fileName`. Keep the display name as your own metadata (see "Storing and downloading"). `fileName` must be a bare file name (`invoice.pdf`), never a path — the platform rejects any fileName containing `/` or `..`. Don't prefix folders or namespaces onto it; the platform already scopes the key to the workspace and makes it unique. If you need grouping, store that in your own database alongside the key.
|
|
20
21
|
|
|
@@ -123,6 +124,30 @@ await downloadViaBlob(`/api/items/${id}/download`, 'report.pdf');
|
|
|
123
124
|
|
|
124
125
|
Do NOT use `<a href>` links to the download route — plain links can't send the CSRF header `authFetch` adds, so the route rejects them with 403. Always use a button with an `onClick` that calls `authFetch` or `downloadViaBlob`.
|
|
125
126
|
|
|
127
|
+
## Deleting a file
|
|
128
|
+
|
|
129
|
+
`deleteFile(key)` removes the object from storage. Deletion is **permanent and irreversible** — there is no trash, no undo, and no version to restore. If your app needs recovery (an "undo", a trash view, soft-delete), hide the record in your own database first and call `deleteFile` only later, once the user can no longer bring it back.
|
|
130
|
+
|
|
131
|
+
**Route handler** (`app/api/app-storage/delete/route.ts`):
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
135
|
+
import { handleRouteError, deleteFile } from '@assembly-js/app-foundation/server';
|
|
136
|
+
|
|
137
|
+
export async function DELETE(request: NextRequest) {
|
|
138
|
+
try {
|
|
139
|
+
const id = request.nextUrl.searchParams.get('id') ?? '';
|
|
140
|
+
const key = await getStoredFileKey(id); // your own database lookup by record id
|
|
141
|
+
await deleteFile(key);
|
|
142
|
+
return new NextResponse(null, { status: 204 });
|
|
143
|
+
} catch (err) {
|
|
144
|
+
return handleRouteError(err, 'Delete request failed');
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Only delete keys you stored yourself (look the key up from your own database by record id rather than trusting a key sent by the client), and remove or hide your own record at the same time.
|
|
150
|
+
|
|
126
151
|
## Server needs the bytes (parse / process an uploaded file)
|
|
127
152
|
|
|
128
153
|
When the server must read the file too — extract PDF text, parse a CSV, generate a thumbnail — the
|
|
@@ -189,5 +214,6 @@ switch (detection.method) {
|
|
|
189
214
|
- Do NOT embed a pre-signed download URL in rendered HTML — it expires; mint a fresh one per click.
|
|
190
215
|
- Do NOT construct the S3 key yourself — store the key the platform returns.
|
|
191
216
|
- Do NOT put `/` in `fileName` (e.g. `uploads/${file.name}`) — the API rejects it; send the bare file name.
|
|
217
|
+
- Do NOT call `deleteFile` expecting to recover the file later — deletion is permanent; hide the record first and delete the file after the recovery window.
|
|
192
218
|
- Do NOT use app-storage for files the user wants in the portal Files tab — use `createFile`.
|
|
193
219
|
- Do NOT pass any auth token as a URL query parameter for downloads — use `authFetch`, which adds the CSRF header while the bearer rides the cookie.
|