@assembly-js/app-foundation 0.0.16 → 0.0.18

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/dist/nango.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { type ResourceFieldMap, type ResourcePage } from './resource-resolution.js';
2
- export type Provider = 'slack' | 'airtable' | 'gmail' | 'google-calendar' | 'google-drive' | 'google-sheet' | 'hubspot' | 'salesforce' | 'notion' | 'linear' | 'stripe' | 'github';
2
+ export type Provider = 'slack' | 'gmail' | 'google-calendar' | 'google-drive' | 'google-sheet' | 'hubspot' | 'stripe' | 'airtable' | 'dropbox' | 'calendly' | 'clickup' | 'docusign' | 'quickbooks' | 'xero' | 'twilio' | 'zoom';
3
3
  export type ConnectorErrorCode = 'app_key_required' | 'provider_not_declared' | 'connection_required' | 'needs_reauth' | 'needs_reauth_scope' | 'connection_throttled' | 'connector_unavailable' | 'action_failed' | 'invalid_request';
4
4
  export interface ConnectionStatus {
5
5
  provider: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assembly-js/app-foundation",
3
- "version": "0.0.16",
3
+ "version": "0.0.18",
4
4
  "description": "Versioned foundation helpers and build context for Assembly generated apps",
5
5
  "type": "module",
6
6
  "files": [
@@ -70,20 +70,6 @@ useAssemblyHeader({ onRootClick: goHome, breadcrumbs: [{ label: 'Reports' }] });
70
70
 
71
71
  Use breadcrumbs only for hierarchical navigation, not branding. A flat one-page app omits `breadcrumbs` (or passes `[]`). A multi-level app uses one entry per level: `[{ label: 'Reports' }, { label: 'Q4 2026' }]`. A breadcrumb's `onClick` makes it clickable; omit it for the current/leaf level.
72
72
 
73
- ## Loading state on a `primaryCta` — no `disabled`/`loading` prop
74
-
75
- Header CTAs have no `disabled` or `loading` prop. Indicate progress by changing the `primaryCta` value itself — swap the label and replace `onClick` with a no-op while the action runs, then restore both when it completes:
76
-
77
- ```tsx
78
- useAssemblyHeader({
79
- primaryCta: isSaving
80
- ? { label: 'Saving…', icon: 'Check', onClick: () => {} }
81
- : { label: 'Save', icon: 'Check', onClick: save },
82
- });
83
- ```
84
-
85
- Because the hook re-applies whatever value you pass on every render, this is just a state change — no imperative set/clear needed. Don't let the user click a button and see nothing happen.
86
-
87
73
  ## Icon and color constraints
88
74
 
89
75
  - **`icon` must be a `CtaIcon`** — the only valid values are `'Archive' | 'Plus' | 'Templates' | 'Trash' | 'Check'`. Any other string (e.g. `'Download'`, `'Settings'`, `'Edit'`) is a type error — omit the icon (it's optional) when none fit.
@@ -69,10 +69,6 @@ await assembly.updateClient({
69
69
 
70
70
  Send the structured object — a bare string is accepted for backwards compatibility with older data, but it collapses to a single `fullAddress` string and loses city/region/postal code/country as separate values. Every property is optional except that the field is only considered set if `country` or `fullAddress` is non-empty; `fullAddress` is derived automatically from the other components if you omit it.
71
71
 
72
- ## Every other field type
73
-
74
- Non-option, non-address fields take a direct string value — stringify numbers and booleans yourself: `{ mrr: '5000' }`, not `{ mrr: 5000 }`.
75
-
76
72
  ## Symptoms when it goes wrong
77
73
 
78
74
  | Mistake | What you'll see |
@@ -21,6 +21,6 @@ tail -n 100 /tmp/dev-server.log
21
21
  grep -n "Error" /tmp/dev-server.log
22
22
  ```
23
23
 
24
- Read or grep the file directly — it's a plain text file on the sandbox's own filesystem, not something fetched over a tool call. The file is truncated and capped automatically, so `tail` always shows recent activity; it does not accumulate forever.
24
+ Read or grep the file directly — it's a plain text file on the sandbox's own filesystem, not something fetched over a tool call. The file persists across dev server restarts — each restart appends a `=== dev server launch <timestamp> ===` marker, so you can see output from before a restart (earlier crashes, port-bind failures) and tell launches apart. Size is capped automatically, so `tail` always shows recent activity; it does not accumulate forever.
25
25
 
26
26
  If the log doesn't explain the symptom (e.g. a client-side rendering issue), the problem is likely in the browser, not the server — this file only covers the server side.
@@ -8,6 +8,7 @@
8
8
  - **Pagination:** `list-bases` takes `cursor` and returns `nextOffset`. `list-records` takes `offset` and returns `offset`. Passing the wrong key silently drops the next page.
9
9
  - **`fields` is overloaded:** on `list-records` it is an array of field names to return; on `create-record` / `update-record` it is a name-or-id-keyed map of values to write. An array on a write, or a map on a read, is rejected.
10
10
  - Resolve `baseId` with `list-bases` and table/field names with `get-base-schema` before writes. `tableIdOrName` accepts either; schema field ids survive a user renaming the column.
11
+ - **`delete-record` is permanent.** It returns `{ id, deleted }` and there is no undo through the API — a deleted record is only recoverable from Airtable's own trash by a human. Confirm destructive intent in the UI before calling it, and never wire it to a bulk action that runs without review.
11
12
 
12
13
  ```typescript
13
14
  import { getIntegration, getAirtableTableSchema, formatAirtableRecords } from '@assembly-js/app-foundation/server';
@@ -6,6 +6,8 @@ Event `start` and `end` are objects, not strings: `{ dateTime: "2026-08-27T14:00
6
6
 
7
7
  `attendees` is `[{ email }]`. Creating or updating with attendees does **not** email them unless `sendUpdates: "all"` is passed on `update-event` (create sends per Google defaults) — surface this as an explicit choice in the UI, not a surprise.
8
8
 
9
+ `get-event` fetches one event by id and returns the same item shape `list-events` does — use it instead of listing and filtering client-side. Cancelled events still return (with `status: "cancelled"`) rather than 404ing, so check `status` before rendering. For a recurring series, the id of an expanded instance (from `singleEvents: true`) and the id of the recurring master are different events; `get-event` returns whichever you ask for.
10
+
9
11
  For listing over a window pass `timeMin`/`timeMax` (RFC3339) and `singleEvents: true` so recurring events expand into instances — without it you get the recurring master only, which renders as one event instead of many. `recurrence` on create takes RRULE strings, e.g. `["RRULE:FREQ=WEEKLY;BYDAY=MO"]`.
10
12
 
11
13
  <!-- ASSEMBLY_ACTIONS:google-calendar -->
@@ -9,7 +9,11 @@ When `google-drive` is on the supported-provider list, declare it too and resolv
9
9
  const id = url.match(/\/spreadsheets\/d\/([a-zA-Z0-9-_]+)/)?.[1];
10
10
  ```
11
11
 
12
- Ranges are A1 notation (`Sheet1!A1:D10`; quote sheet names with spaces: `'Q3 Data'!A:D`); `values` is a 2D array of rows — one inner array per row, cells as strings/numbers, never objects. `get-values` reads, `append-values-to-spreadsheet` appends to the table found in the range, `update-values` overwrites an exact range.
12
+ Then call `get-spreadsheet` with that id before doing anything else with it. It returns `spreadsheetTitle` and `sheets[]` (one entry per tab, `{ sheetId, title, index }`) and no cell data, so it is cheap. Two things fall out of it: a pasted id that is wrong or not shared with the connected account fails here, at paste time, instead of halfway through a write; and `sheets[]` is the tab list, so present a picker over its `title`s rather than asking the user to type a sheet name blind. The workbook's own name is `spreadsheetTitle` — it is a heading, never a tab option.
13
+
14
+ Ranges are A1 notation (`Sheet1!A1:D10`; quote sheet names with spaces: `'Q3 Data'!A:D`); `values` is a 2D array of rows — one inner array per row, cells as strings/numbers, never objects. A bare tab title is itself a valid range covering the whole tab, so a picked `title` can go straight into `range`, optionally with a cell span appended. `get-values` reads, `append-values-to-spreadsheet` appends to the table found in the range, `update-values` overwrites an exact range, `clear-values` empties one.
15
+
16
+ `clear-values` clears **values only**. Formatting, data validation, conditional formatting, notes, and the cells themselves all survive — the range is left blank, not deleted, so rows below do not shift up. There is no vendored way to delete rows or columns outright; that needs `spreadsheets.batchUpdate` through the proxy.
13
17
 
14
18
  Writes take `valueInputOption`: `RAW` stores values verbatim; `USER_ENTERED` parses them as if typed in the UI (strings that look like numbers/dates convert, `=SUM(...)` becomes a formula). Default to `USER_ENTERED` for user-facing data entry and `RAW` when round-tripping values the app will read back.
15
19
 
@@ -4,7 +4,7 @@ Writes need membership, not just visibility. `send-message` reaches public chann
4
4
 
5
5
  - **Post with `slackPostMessage` from `@assembly-js/app-foundation/server` — never hand-roll `chat.postMessage`.** It resolves a `#name` to the channel ID, posts, and on `not_in_channel` joins the channel and retries once. Slack-level failures come back as `{ ok: false, error }`; on `not_in_channel` render "invite the bot to #channel in Slack" (private channels cannot be self-joined).
6
6
  - **Resolve channel names with `slackFindChannelId` from `@assembly-js/app-foundation/server` for every other per-channel call.** It handles cursor pagination (a single `conversations.list` misses channels in large workspaces), name normalization, scope fallback, rate limits, and caching. Use the returned ID; most Slack methods reject a `#name` with `invalid_arguments`.
7
- - **Other writes (reactions, uploads):** on `not_in_channel`, call `join-channel` with the channel ID, then retry once. Private channels fail `method_not_supported_for_channel_type` — tell the user to invite the bot instead of retrying.
7
+ - **Other writes (reactions, invites):** on `not_in_channel`, call `join-channel` with the channel ID, then retry once. Private channels fail `method_not_supported_for_channel_type` — tell the user to invite the bot instead of retrying.
8
8
  - **Scopes:** `chat:write.public` lets posting reach any public channel without joining, so only the other writes need `join-channel`. Add `groups:read` if the app must find private channels.
9
9
 
10
10
  ```typescript
@@ -24,6 +24,7 @@ const history = await slack.action('get-conversation-history', {
24
24
 
25
25
  For an operation no action covers, relay through the proxy — e.g. `slack.post({ endpoint: '/chat.update', data: { channel: channelId, ts, text } })`.
26
26
 
27
+ - **File uploads are not supported.** No upload action is vendored, and Slack's upload flow needs a multipart POST to the non-Slack URL `files.getUploadURLExternal` hands back, which the proxy cannot reach. Post a link to the file instead.
27
28
  - **DMs are not supported.** The integration does not grant `im:write`, and `conversations.open` is not vendored. A Slack user ID (`U…`) or DM channel ID (`D…`) also fails `slackPostMessage`'s channel-ID check, so it falls through to a channel-name lookup and returns `channel_not_found` instead of opening the DM. If a user asks for DMs, say so up front and offer `createNotification` (in-product + email) or posting to a channel instead.
28
29
 
29
30
  <!-- ASSEMBLY_ACTIONS:slack -->