@assembly-js/app-foundation 0.3.0 → 0.3.2
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
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.3.2
|
|
23
|
+
- [new] The `assembly-api-contract` skill teaches the build agent to write and maintain `.assembly/openapi.json` alongside `app/api` routes.
|
|
24
|
+
|
|
25
|
+
## 0.3.1
|
|
26
|
+
- [guidance] The build agent now upgrades an app's bridge to 2.1.0 and its foundation package instead of skipping a deep link, mounts the location reporter, and deep-links notifications to the specific record.
|
|
27
|
+
- [new] App destinations in `NavigateDestination` now accept an optional `path` for links to an app's inner page.
|
|
28
|
+
|
|
22
29
|
## 0.3.0
|
|
23
30
|
- [new] `LocationReporter` is exported from `@assembly-js/app-foundation/client`, so apps born before the deeplink image can mount it in their layout and report their inner location to the host with the same surface-prefix handling as new apps.
|
|
24
31
|
|
package/CLAUDE.md
CHANGED
|
@@ -48,6 +48,9 @@ Assembly-specific gotchas live in `.claude/skills/<name>/SKILL.md`. Invoke them
|
|
|
48
48
|
<!-- feature:deep-link-bridge-nav -->
|
|
49
49
|
- `assembly-platform-links` — link platform records (client, company, invoice, contract, form, file) to their portal detail page via `linkTo()`; never hand-build a host URL. Also links between workspace apps by install id. Check the app's installed app-bridge first (see the skill) — `navigateTo` doesn't exist on older peers — and only link staff-only destinations (client, company, form, etc.) from the dashboard surface, never the client portal.
|
|
50
50
|
<!-- /feature:deep-link-bridge-nav -->
|
|
51
|
+
<!-- feature:deep-link-app-inner-location -->
|
|
52
|
+
- `assembly-app-inner-location` — mount `<LocationReporter />` so the host mirrors the app's inner page in its address bar; deep-link a notification or link to a specific record with `linkTo({ to: 'app', installId, path })`, never to the app root. Never hand-write location reporting.
|
|
53
|
+
<!-- /feature:deep-link-app-inner-location -->
|
|
51
54
|
<!-- feature:cross-app-apis -->
|
|
52
55
|
- `assembly-app-clients` — calling another workspace app through its generated client in `generated/apps/<appId>/` (`installerClient`, by verb and path), and rendering an empty state that names the app when the call is refused instead of a 500.
|
|
53
56
|
<!-- /feature:cross-app-apis -->
|
|
@@ -65,6 +68,7 @@ Assembly-specific gotchas live in `.claude/skills/<name>/SKILL.md`. Invoke them
|
|
|
65
68
|
- `assembly-route-auth` — REQUIRED on every `app/api/.../[id]/route.ts` handler: `requireAuth` + `requireCompanyScope` before returning or mutating data.
|
|
66
69
|
- `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.
|
|
67
70
|
- `assembly-route-errors` — REQUIRED in every route handler: use `handleRouteError` from `@assembly-js/app-foundation/server` in catch blocks — never return `err.message`. Validate values before they hit typed DB columns (e.g. `parseUuid` before a uuid-column lookup).
|
|
71
|
+
- `assembly-api-contract` — REQUIRED whenever an `app/api` route is added, removed, or changes its request or response shape: keep `.assembly/openapi.json` (OpenAPI 3.1, `servers: [{"url": "/api"}]`, paths without the `/api` prefix) in step; cron, webhook, `assembly-*` and `privateRoutes` routes stay out.
|
|
68
72
|
- `assembly-action-results` — REQUIRED when a server action refuses work the UI should explain (validation, permission, not ready to send): return `{ ok: false, message }` instead of throwing. A thrown message is redacted in production.
|
|
69
73
|
- `assembly-dev-server-logs` — the dev server's stdout/stderr (server-side errors, stack traces, console output) is readable at `/tmp/dev-server.log`; check it before guessing at a 500 or broken preview.
|
|
70
74
|
- Your own preview is `localhost:3000`; never fetch the public `sb-*.vercel.run` host from inside the sandbox (egress to it is blocked).
|
package/dist/bridge-vnext.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: assembly-api-contract
|
|
3
|
+
description: REQUIRED whenever you add, remove, or change the request or response shape of a route under app/api. Write and maintain .assembly/openapi.json, the app's OpenAPI 3.1 API contract — one operation per callable (method, path) with JSON Schema bodies; servers /api and paths without the /api prefix. Skip cron, webhook, assembly-* and privateRoutes routes.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Keep `.assembly/openapi.json` in step with `app/api` in the same turn as the route change. It is the document other apps and tools read to learn what this app's API accepts and returns.
|
|
7
|
+
|
|
8
|
+
## When to touch it
|
|
9
|
+
|
|
10
|
+
- Route added: add an operation for each exported GET, POST, PUT, PATCH or DELETE handler; never document head or options, which are not proxied.
|
|
11
|
+
- Route removed, or a method removed from it: delete that operation (and the path entry once it has no operations).
|
|
12
|
+
- Request body, query parameter, or response field changed: update the schemas to match.
|
|
13
|
+
|
|
14
|
+
## Format
|
|
15
|
+
|
|
16
|
+
- OpenAPI `3.1.0`, JSON, at `.assembly/openapi.json`. Bodies are JSON Schema.
|
|
17
|
+
- Identity of an operation is `(method, path template)`. `operationId` is a label only; renaming it changes nothing.
|
|
18
|
+
- `servers` is `[{ "url": "/api" }]` and `paths` are written **without** the `/api` prefix, exactly as the route scanner templates them (`app/api/projects/[projectId]/tasks/route.ts` becomes `/projects/{projectId}/tasks`). Path parameters are `{name}` using the folder's bracket name.
|
|
19
|
+
- The stored document is capped at 1 MB and publishing refuses a larger one. Keep it small: describe fields, do not paste sample data.
|
|
20
|
+
|
|
21
|
+
## What stays out
|
|
22
|
+
|
|
23
|
+
A route is not callable by other apps, so it must not appear, when it is:
|
|
24
|
+
|
|
25
|
+
- under a `cron`, `webhook` or `webhooks` segment, or a segment starting `assembly-`
|
|
26
|
+
- wrapped in `cronRoute` or `automationActionRoute`
|
|
27
|
+
- matched by a glob in `privateRoutes` in `.assembly/app.json` (matched against the route template, not the file path). The scanner records these as excluded, so documenting one is reported as a drift.
|
|
28
|
+
|
|
29
|
+
## Worked example
|
|
30
|
+
|
|
31
|
+
`app/api/projects/[projectId]/tasks/route.ts` exports `GET` (list) and `POST` (create):
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"openapi": "3.1.0",
|
|
36
|
+
"info": { "title": "Project tasks", "version": "1.0.0" },
|
|
37
|
+
"servers": [{ "url": "/api" }],
|
|
38
|
+
"paths": {
|
|
39
|
+
"/projects/{projectId}/tasks": {
|
|
40
|
+
"get": {
|
|
41
|
+
"operationId": "listProjectTasks",
|
|
42
|
+
"summary": "List the tasks of a project",
|
|
43
|
+
"parameters": [
|
|
44
|
+
{ "name": "projectId", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }
|
|
45
|
+
],
|
|
46
|
+
"responses": {
|
|
47
|
+
"200": {
|
|
48
|
+
"description": "The project's tasks",
|
|
49
|
+
"content": {
|
|
50
|
+
"application/json": {
|
|
51
|
+
"schema": {
|
|
52
|
+
"type": "object",
|
|
53
|
+
"required": ["tasks"],
|
|
54
|
+
"properties": { "tasks": { "type": "array", "items": { "$ref": "#/components/schemas/Task" } } }
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"404": { "$ref": "#/components/responses/Error" }
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"post": {
|
|
63
|
+
"operationId": "createProjectTask",
|
|
64
|
+
"summary": "Create a task in a project",
|
|
65
|
+
"parameters": [
|
|
66
|
+
{ "name": "projectId", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }
|
|
67
|
+
],
|
|
68
|
+
"requestBody": {
|
|
69
|
+
"required": true,
|
|
70
|
+
"content": {
|
|
71
|
+
"application/json": {
|
|
72
|
+
"schema": {
|
|
73
|
+
"type": "object",
|
|
74
|
+
"required": ["title"],
|
|
75
|
+
"properties": { "title": { "type": "string", "minLength": 1 }, "dueDate": { "type": ["string", "null"], "format": "date" } }
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
"responses": {
|
|
81
|
+
"201": { "description": "The created task", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Task" } } } },
|
|
82
|
+
"400": { "$ref": "#/components/responses/Error" }
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
"components": {
|
|
88
|
+
"schemas": {
|
|
89
|
+
"Task": {
|
|
90
|
+
"type": "object",
|
|
91
|
+
"required": ["id", "title", "done"],
|
|
92
|
+
"properties": {
|
|
93
|
+
"id": { "type": "string", "format": "uuid" },
|
|
94
|
+
"title": { "type": "string" },
|
|
95
|
+
"done": { "type": "boolean" },
|
|
96
|
+
"dueDate": { "type": ["string", "null"], "format": "date" }
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
"responses": {
|
|
101
|
+
"Error": {
|
|
102
|
+
"description": "Error",
|
|
103
|
+
"content": {
|
|
104
|
+
"application/json": {
|
|
105
|
+
"schema": { "type": "object", "required": ["error"], "properties": { "error": { "type": "string" } } }
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Document the error shape your handlers really return (see `assembly-route-errors`: `{ error }`).
|
|
115
|
+
|
|
116
|
+
## Drift
|
|
117
|
+
|
|
118
|
+
The platform may compare the contract with the scanned routes after a turn and report disagreements. Fix each kind at its source:
|
|
119
|
+
|
|
120
|
+
- Documented operation with no route in code: remove the operation, or restore the route if it was deleted by mistake. A method the file does not export counts as missing.
|
|
121
|
+
- Route in code with no operation: add the operation, unless the route is one of the excluded kinds above.
|
|
122
|
+
- Same path under a different parameter name (`{id}` in the contract, `[taskId]` in the folder): Next allows one name per dynamic segment, so rename the contract's parameter to the folder's.
|
|
123
|
+
- Shape mismatch (a response field renamed, a body field now required): update the schemas to what the handler returns and accepts.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<!-- feature:deep-link-app-inner-location -->
|
|
2
|
+
---
|
|
3
|
+
name: assembly-app-inner-location
|
|
4
|
+
description: Use whenever the app has inner pages the host should mirror in its address bar, or when a notification, email or link should open this app on a specific record instead of its root. Covers mounting LocationReporter and pointing notifications at a specific record.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## What the reporter does
|
|
8
|
+
|
|
9
|
+
The host mirrors the app's inner page in its own address bar and can open the app straight at an inner page. That only works if the app reports its location, and `<LocationReporter />` from the foundation package does exactly that. The reporter needs `AssemblyBridge.location`, which ships in app-bridge 2.1.0, and a `navigateTo`-only 2.0.0 bridge lacks it. Check with `grep -q onNavigate node_modules/@assembly-js/app-bridge/dist/index.d.ts && echo supported || echo unsupported`; if `unsupported`, run `bun add @assembly-js/app-bridge@2.1.0` (exact stable release, never a prerelease or range), then `bunx tsc --noEmit`, before mounting the reporter.
|
|
10
|
+
|
|
11
|
+
- Mount `<LocationReporter />` once, in `app/layout.tsx`, imported from `@assembly-js/app-foundation/client`. If it is already there, leave it.
|
|
12
|
+
- Never write custom `location.report` or `location.onNavigate` code, and never hand-write a `LocationSync` component. The reporter already handles surface prefixes and route changes.
|
|
13
|
+
|
|
14
|
+
## Notifications deep-link to the record, not the app root
|
|
15
|
+
|
|
16
|
+
When the app sends an email or in-product notification about a specific record (a document request, a submission), the destination is set by the notification's `ctaParams` and surface, not by `linkTo`. Pass `ctaParams` that identify the record (`ctaParams: { requestId: request.id }`) and render that record from an existing inner route of this app, one that works when opened cold. Follow `assembly-notifications` for the send-and-open pattern.
|
|
17
|
+
|
|
18
|
+
To link from a button inside the app to an inner page, use `linkTo` as described in `assembly-platform-links` (the `app` destination takes an optional `path`).
|
|
19
|
+
<!-- /feature:deep-link-app-inner-location -->
|
|
@@ -14,7 +14,13 @@ Before writing any `linkTo()` or `AssemblyBridge.navigateTo()` call, check what
|
|
|
14
14
|
grep -q navigateTo node_modules/@assembly-js/app-bridge/dist/index.d.ts && echo supported || echo unsupported
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
If it reports `unsupported`,
|
|
17
|
+
If it reports `unsupported`, upgrade the app in this same turn instead of skipping the link:
|
|
18
|
+
|
|
19
|
+
1. Confirm the app is on the foundation package: `grep -q app-foundation package.json`. If it is not (vendored `lib/`), say the security update must land first and stop — do not attempt the migration in a feature turn.
|
|
20
|
+
2. Pin the bridge to the exact stable release: `bun add @assembly-js/app-bridge@2.1.0`. Never pin a prerelease (`-beta`, `-rc`, `-next`) or a range.
|
|
21
|
+
3. Bump the foundation dependency listed in `package.json` to `^0.3.0`, the floor that exports `linkTo` and `LocationReporter` (`bun add <that package name>@^0.3.0`).
|
|
22
|
+
4. Run `bunx tsc --noEmit` and `bun run build`, and confirm the existing header and toast bridge calls still type-check. Fix any break before continuing.
|
|
23
|
+
5. Re-run the check above, then proceed with this skill.
|
|
18
24
|
|
|
19
25
|
## When to reach for this skill
|
|
20
26
|
|
|
@@ -74,7 +80,7 @@ The host rejects staff-only destinations when the viewer is a client in the port
|
|
|
74
80
|
| `article` | `articleId` | — | Yes |
|
|
75
81
|
| `files` | — | `channelId` | Yes |
|
|
76
82
|
| `channel` | — | `channelId` | Yes |
|
|
77
|
-
| `app` | `installId` |
|
|
83
|
+
| `app` | `installId` | `path` | Yes, if the target app is installed for that viewer |
|
|
78
84
|
| `home` | — | — | No — staff only |
|
|
79
85
|
| `product` | — | — | No — staff only |
|
|
80
86
|
| `helpdesk` | — | — | Yes |
|