grist-widget-sdk 0.2.0 → 0.2.2-rc.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 +16 -723
- package/LICENSE +18 -12
- package/dist/index.cjs +5 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +67 -1
- package/dist/index.d.ts +67 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/package.json +16 -13
package/CHANGELOG.md
CHANGED
|
@@ -1,730 +1,23 @@
|
|
|
1
|
-
#
|
|
1
|
+
# grist-widget-sdk
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
tracked on [`/TASKS.md`](./TASKS.md) and [`/ROADMAP.md`](./ROADMAP.md); landed
|
|
5
|
-
changes accumulate under **Unreleased** until a version is cut.
|
|
3
|
+
## 0.2.1
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
and [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
5
|
+
### Patch Changes
|
|
9
6
|
|
|
10
|
-
|
|
7
|
+
- 3c0315f: Relicense to MIT and ship the `LICENSE` in the published tarball. No runtime or
|
|
8
|
+
API changes — `dist` is identical to 0.2.0; this release corrects the package
|
|
9
|
+
license metadata (0.2.0 shipped as ISC) and lands the release tooling (OIDC
|
|
10
|
+
Trusted Publishing + external-install smoke test).
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
This file is generated by [Changesets](https://github.com/changesets/changesets)
|
|
13
|
+
and is the canonical changelog for the published `grist-widget-sdk` npm
|
|
14
|
+
package, starting at `0.2.1`. Every entry corresponds to a version actually
|
|
15
|
+
published to npm.
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
`.github/workflows/deploy-widgets.yml` + `scripts/deploy/publish.mjs`
|
|
17
|
-
(node builtins only). Two channels: push to `main` publishes **immutable**
|
|
18
|
-
`/<repo>/<widget>/v<version>/` + a mutable `/<widget>/latest/` alias, and
|
|
19
|
-
regenerates a root `manifest.json` (Grist widget-repository format, usable via
|
|
20
|
-
`GRIST_WIDGET_LIST_URL`); a widget is (re)built only when its `package.json`
|
|
21
|
-
`version` has no `v<version>/` dir yet, so bumping the version *is* the release
|
|
22
|
-
and a `packages/core`-only push no longer moves shipped widget URLs. Concurrent
|
|
23
|
-
runs are serialized and the publisher rebases-and-retries its push; one failing
|
|
24
|
-
widget build no longer blocks the others. Widgets carry a `grist` metadata
|
|
25
|
-
block (`name`, `widgetId`, `accessLevel`, …) consumed by the manifest;
|
|
26
|
-
`v0-minimal-demo` is intentionally unlisted. The former single mutable
|
|
27
|
-
`/<widget>/` URL is kept but deprecated in favor of pinned `v<version>/` /
|
|
28
|
-
`latest/`.
|
|
17
|
+
For the fuller project history — widgets, docs, tooling, process — see the
|
|
18
|
+
monorepo root [`/CHANGELOG.md`](https://github.com/ArthurBlanchon/grist-widget-sdk/blob/main/CHANGELOG.md).
|
|
29
19
|
|
|
30
|
-
|
|
20
|
+
## 0.2.0
|
|
31
21
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
self-reload snippet that polls for new builds and hot-swaps the widget inside
|
|
35
|
-
an open Grist document (cache-busting `__dev=<sha>` navigation that preserves
|
|
36
|
-
the host's query params). Prod builds are unchanged. Enables a push → CI →
|
|
37
|
-
review-in-real-Grist loop without a tunnel.
|
|
38
|
-
|
|
39
|
-
### Fixed
|
|
40
|
-
|
|
41
|
-
- **Gantt widget dates** — Grist Date cells (UTC midnight) are normalized to local
|
|
42
|
-
calendar days before timeline layout, so bars align with table dates in all timezones.
|
|
43
|
-
- **Gantt weekly timeline positioning** — event bars and the Today marker now walk
|
|
44
|
-
the same week columns as the header (weeks that straddle two months are no longer
|
|
45
|
-
skipped in offset math), fixing ~3-month placement drift on the weekly scale.
|
|
46
|
-
- **Gantt weekly headers** — columns show ISO week numbers (Monday-based, e.g. W26)
|
|
47
|
-
with the week start date below.
|
|
48
|
-
- **Upload CORS error message** — includes the widget `location.origin` and notes that
|
|
49
|
-
changing deploy URL (e.g. ngrok → GitHub Pages) requires updating server CORS.
|
|
50
|
-
|
|
51
|
-
### Added
|
|
52
|
-
|
|
53
|
-
- **`uploadGristAttachment` / `w.uploadAttachment`** — canonical widget upload
|
|
54
|
-
(`POST /attachments`, `X-Requested-With`, `?auth=`) returning `{ ids, firstId }`.
|
|
55
|
-
- **`parseGristAttachmentUploadResponseIds`**, **`gristAttachmentCellValues`**,
|
|
56
|
-
**`mergeGristAttachmentCellValue`** — full id list parsing and Attachments cell
|
|
57
|
-
encode/merge helpers aligned with Grist’s `["L", …]` wire format.
|
|
58
|
-
|
|
59
|
-
### Fixed
|
|
60
|
-
|
|
61
|
-
- **`fetchWithAuth` CORS on attachment upload** — non-GET REST calls from custom
|
|
62
|
-
widgets now attach the access token as `?auth=` (same as downloads) instead of
|
|
63
|
-
`Authorization: Bearer`, which many Grist hosts block in CORS preflight.
|
|
64
|
-
- **`parseGristAttachmentUploadResponse` / `gristAttachmentCellValue`** — helpers
|
|
65
|
-
for `POST /attachments` (response is a JSON array of ids, e.g. `[42]`) and
|
|
66
|
-
writing a single id into an Attachments column as `["L", id]`.
|
|
67
|
-
- **`mapBack` erased unrelated columns on partial updates** —
|
|
68
|
-
`grist-plugin-api.js`'s `mapColumnNamesBack` applies transformations for
|
|
69
|
-
*all* mapped columns, injecting `undefined` for fields absent from the
|
|
70
|
-
patch. Those `undefined` values JSON-serialise to `null` over RPC,
|
|
71
|
-
causing Grist to erase the corresponding cells. `mapBack` now strips
|
|
72
|
-
all `undefined` entries from the result so only fields explicitly
|
|
73
|
-
included in the patch are sent in the update.
|
|
74
|
-
- **Access-insufficient alerts for all SDK hooks** — `useGristSchema`,
|
|
75
|
-
`useGristRowsFromTable`, and `useGristAttachmentsRest` now surface
|
|
76
|
-
access-insufficient errors through the provider's `readError` so the SDK alert
|
|
77
|
-
system displays a smooth "Access level" alert instead of failing silently.
|
|
78
|
-
`useGristSchema` and `useGristRowsFromTable` delegate to the provider's guarded
|
|
79
|
-
read methods when inside a `GristWidgetProvider`; `useGristAttachmentsRest` uses
|
|
80
|
-
its own `guardedRpc` wrapper whose `readError` is merged into `UseGristResult`.
|
|
81
|
-
- **Heartbeat false-positive on semantic errors** — `applyActions` failures (e.g.
|
|
82
|
-
"No such column") no longer briefly flash the connection-degraded indicator.
|
|
83
|
-
RPC failures are no longer coalesced into the heartbeat; the regular probe
|
|
84
|
-
detects real transport issues on its own schedule.
|
|
85
|
-
- **`fetchTable` / `fetchTableRows` / `fetchRow` / `listColumns` / `buildReplicaDocumentFromDocApi`
|
|
86
|
-
access guard** — these methods now check the granted access level before making
|
|
87
|
-
an RPC call. When the widget only has `"read table"` access, the SDK throws
|
|
88
|
-
immediately with a descriptive message and sets `readError`, preventing a
|
|
89
|
-
looping RPC failure cycle. The `access-insufficient` SDK alert is emitted
|
|
90
|
-
automatically so the `<GristSdkAlerts>` / `useGristSdkAlertDescriptors` shell
|
|
91
|
-
shows actionable instructions.
|
|
92
|
-
- **`@access` annotations** — corrected `fetchTable`, `fetchTableRows`, `fetchRow`,
|
|
93
|
-
`listColumns`, and `buildReplicaDocumentFromDocApi` from `@access "read table"`
|
|
94
|
-
to `@access "full"` in `UseGristResult` JSDoc.
|
|
95
|
-
|
|
96
|
-
### Added
|
|
97
|
-
|
|
98
|
-
- **`gristAddVisibleColumnAction(tableId, colId, colInfo)`** — new action builder
|
|
99
|
-
that emits `["AddVisibleColumn", ...]`. Unlike `gristAddColumnAction`, it also
|
|
100
|
-
adds the column to the current view section so it is immediately visible.
|
|
101
|
-
- **`w.listColumns(tableId, options?)`** — new lightweight API to retrieve
|
|
102
|
-
column metadata (id, label, type, formula, description) for a given table
|
|
103
|
-
without fetching all row data. Noise columns (`id`, `manualSort`,
|
|
104
|
-
`gristHelper_*`, logging formulas) are filtered out by default.
|
|
105
|
-
- **`w.listTables(options?)` — system-table filtering** — `listTables()` now
|
|
106
|
-
accepts `{ includeSystem?: boolean }` and filters system/hidden tables
|
|
107
|
-
(`_grist*`, `GristHidden_*`) by default.
|
|
108
|
-
- **`useGristWidgetOptionsFromContext<T>()`** — typed widget options hook
|
|
109
|
-
designed for use inside `<GristWidgetProvider>`. Provides `options`,
|
|
110
|
-
`loading`, `setOptions`, `patchOptions`, and `reset` with debounced writes
|
|
111
|
-
and a `namespace` option. Unlike `useGristWidgetOptions()` (advanced), this
|
|
112
|
-
hook does not call `grist.ready()` and is fully compatible with the provider.
|
|
113
|
-
- **`UseGristResult` JSDoc** — documents that all function-typed fields are
|
|
114
|
-
referentially stable (`useCallback`-wrapped) and safe in `useEffect` deps,
|
|
115
|
-
while the container object itself is not.
|
|
116
|
-
- **`UseGristResult` access-level annotations** — every field now carries an
|
|
117
|
-
`@access` JSDoc tag (`"none"`, `"read table"`, or `"full"`) so editors and
|
|
118
|
-
documentation show the minimum `requiredAccess` at a glance. Fields are
|
|
119
|
-
grouped by access tier in the type definition and in the API reference.
|
|
120
|
-
- **`suppressAlerts` on `UseGristOptions`** — widgets that intentionally
|
|
121
|
-
operate without a link source can now declare
|
|
122
|
-
`suppressAlerts: ["section-not-linked"]` in their `GRIST_OPTIONS`. The alert
|
|
123
|
-
system (`useGristSdkAlertDescriptors`) reads it automatically from the widget
|
|
124
|
-
slice — no extra wiring needed. A lower-level `suppressKinds` option on
|
|
125
|
-
`GetGristSdkAlertDescriptorsOptions` is also available as an override.
|
|
126
|
-
- **`source-not-wired` SDK alert** — when a widget declares
|
|
127
|
-
`allowSelectBy: true` but no other section is linked to read from it, a
|
|
128
|
-
distinct `source-not-wired` alert is emitted instead of `section-not-linked`.
|
|
129
|
-
This clearly distinguishes "widget expects an incoming link" from "widget is
|
|
130
|
-
a selector but nothing listens yet".
|
|
131
|
-
- **`access-insufficient` SDK alert** — when a write or REST call fails because
|
|
132
|
-
of insufficient access (`"Access not granted"`, etc.), the alert system now
|
|
133
|
-
emits a dedicated `access-insufficient` alert with actionable copy instead
|
|
134
|
-
of the generic `action-error`. Hosts render it as an error-severity callout.
|
|
135
|
-
- **API reference grouped by access tier** — the `useGrist` API reference page
|
|
136
|
-
organizes fields under `"none"`, `"read table"`, and `"full"` headings so
|
|
137
|
-
developers can quickly see which features require which access level.
|
|
138
|
-
- **create-email-draft widget: diffusion lists** — users can configure a
|
|
139
|
-
"diffusion list" table via the widget's _Open configuration_ panel (select
|
|
140
|
-
table, display-name column, and emails column). Typing `/` in the Bcc field
|
|
141
|
-
opens a picker to insert all emails from a diffusion list at once. Config
|
|
142
|
-
panel now uses `listColumns()` for lightweight column discovery.
|
|
143
|
-
|
|
144
|
-
### Changed
|
|
145
|
-
|
|
146
|
-
- **Monorepo tooling** — upgrade to pnpm 11 (`packageManager` pin), root
|
|
147
|
-
`engines` (Node 22+, pnpm 11+), and `pnpm-workspace.yaml` settings
|
|
148
|
-
(`engineStrict`, `minimumReleaseAge` 7 days, `allowBuilds`). README
|
|
149
|
-
documents `corepack enable`.
|
|
150
|
-
|
|
151
|
-
### Added
|
|
152
|
-
|
|
153
|
-
- **`<GristBoundary>` shell UX** — blocking states (booting, unavailable, error, preparing)
|
|
154
|
-
use centered layout via `GristBoundaryScreen` with neutral typography, visible card borders,
|
|
155
|
-
and shell background (`#f8f8f8` fallback). Access-denied copy is short and points to Custom
|
|
156
|
-
widget settings. Widget HTML templates include inline background styles to reduce the initial
|
|
157
|
-
white flash before the bundle loads. Helpers `formatBoundaryUserMessage`, `GRIST_BOUNDARY_PREPARING_COPY`.
|
|
158
|
-
|
|
159
|
-
- **Host access level enforcement** — `interaction.access_level` from `grist.onOptions` is
|
|
160
|
-
applied to the handshake `authz` axis (`AUTHZ_REPORT`). When Grist grants less than
|
|
161
|
-
`requiredAccess` (e.g. widget requests `read table` but the document is set to no access),
|
|
162
|
-
`useGrist().status` becomes `error` and `<GristBoundary>` shows the error fallback instead
|
|
163
|
-
of widget content. After **Try reconnecting** / `reload()`, the cached `onOptions`
|
|
164
|
-
level is re-checked when the handshake goes online so insufficient access stays blocked
|
|
165
|
-
even when Grist does not send a fresh `onOptions` event.
|
|
166
|
-
|
|
167
|
-
- **`section-not-linked` SDK alert** — `getGristSdkAlertDescriptors` emits a warning when
|
|
168
|
-
Grist reports `widgetInteraction.linking.asTarget === null` (including when a stale row
|
|
169
|
-
is still shown). `onOptions` settings are normalized (`accessLevel` → `access_level`,
|
|
170
|
-
`linking` parsed) before they reach `w.widgetInteraction`.
|
|
171
|
-
(section not driven by a linked table/selector). Helpers `isWidgetSectionNotLinked`,
|
|
172
|
-
`formatSectionNotLinkedAlertMessage`; type `GristWidgetLinkingInfo`. Older hosts without
|
|
173
|
-
`linking` on `onOptions` are unchanged (no false positive).
|
|
174
|
-
|
|
175
|
-
- **`grist-widget-sdk/advanced` build export** — `advanced` entry in `tsup` and `package.json` `exports` so documented advanced hooks resolve from npm.
|
|
176
|
-
- **`useGrist().capabilities`** — projects handshake `GristCapabilities` (`canRender`, `canWriteRecords`, `missingMappings`, …) on the primary hook; type `GristCapabilities` exported from the main entry.
|
|
177
|
-
- **Guide: [Raw plugin API vs SDK](/guide/raw-plugin-api-vs-sdk)** — comparison table and migration snippets vs calling `grist` directly.
|
|
178
|
-
- **Docs home** — eight VitePress feature cards (four « One … » product links + four guide links); original hero tagline.
|
|
179
|
-
- **Vite template DX** — ESLint `no-restricted-globals` for `grist`, `grist-types.example.ts`, `GristBoundary gate="canRender"` when `columns` are set (no bundled tests — see `/guide/testing`).
|
|
180
|
-
|
|
181
|
-
- **Handshake-aware boundary + alert helpers** — `task-070`.
|
|
182
|
-
`deriveBoundaryView`, `deriveBoundaryBootLabel`, extended
|
|
183
|
-
`getGristSdkAlertDescriptors` (mapping-pending, mapping-unreported,
|
|
184
|
-
link-stale, current-table-error; `title` / `severity` on descriptors),
|
|
185
|
-
`useGristSdkAlertDescriptors`, and `<GristBoundary gate="canRender">` with
|
|
186
|
-
phase-aware boot labels when the manager is mounted.
|
|
187
|
-
- **`useGristHandshake()` / `useGristCapabilities()` hooks** — `task-062`.
|
|
188
|
-
Exported from `grist-widget-sdk/advanced`. Returns the full
|
|
189
|
-
`GristWidgetSnapshot` (lifecycle / link / authz / config / sync), derived
|
|
190
|
-
`status`, error message, and pre-computed `GristCapabilities` (`canRead`,
|
|
191
|
-
`canRender`, `canWriteRecords`, `canWriteSchema`, `canFetchTable`,
|
|
192
|
-
`hasFreshSelection`, …). Includes `reload()` and `restart()` controls.
|
|
193
|
-
Independent of the existing `useGrist*` hooks — no breaking change to the
|
|
194
|
-
current API surface.
|
|
195
|
-
- **`<GristHandshakeProvider>` + `useGristHandshakeContext()` /
|
|
196
|
-
`useGristHandshakeContextOptional()`** — `task-064`. Opt-in React provider
|
|
197
|
-
that mounts a single `GristHandshakeManager` per app tree and broadcasts
|
|
198
|
-
its snapshot to all descendants. Coexists with the legacy
|
|
199
|
-
`<GristWidgetProvider>` without interference (ready calls are deduped at
|
|
200
|
-
the singleton level).
|
|
201
|
-
- **Public snapshot types** — `GristWidgetSnapshot`, `GristCapabilities`,
|
|
202
|
-
`GristLifecycle*`, `GristLink*`, `GristAuthz`, `GristConfig`, `GristSync`,
|
|
203
|
-
`GristMapping*`, `GristStreamFreshness`, `GristCurrentTableState`,
|
|
204
|
-
`GristGeneration`, `GristTerminationReason` re-exported from `/advanced`.
|
|
205
|
-
|
|
206
|
-
### Fixed
|
|
207
|
-
|
|
208
|
-
- **`mapBack` injected spurious `id` field** — `grist-plugin-api.js`'s
|
|
209
|
-
`mapColumnNamesBack` unconditionally copies `from.id → to.id` (a side-effect
|
|
210
|
-
of sharing code with forward mapping). When the input patch has no `id` key,
|
|
211
|
-
the result contained `id: undefined`, causing Grist to reject writes with
|
|
212
|
-
"Invalid column 'id'". The SDK now strips the injected `id` key.
|
|
213
|
-
|
|
214
|
-
- **Playground theme-demo stuck on `"light"`** — `useGristTheme` listens on
|
|
215
|
-
`grist.on("message")` only (production `grist-plugin-api.js`). Emulator transports
|
|
216
|
-
post the same `msg.theme` object shape (`appearance`, `name`, `colors`); removed
|
|
217
|
-
emulator-only `themeInitialChange` / `themeChange`. Playground shell theme (**d**) is
|
|
218
|
-
mirrored via `emulator.theme.set`.
|
|
219
|
-
|
|
220
|
-
- **`useGrist()` cursor updates in production Grist** — merged widget state is
|
|
221
|
-
rebuilt from live slice contexts (`useGristFromProvider`) instead of a
|
|
222
|
-
memoized `GristContext` snapshot that could keep `w.record.id` on the first
|
|
223
|
-
row after `onRecord` fired again. `recordEvent` also listens for host
|
|
224
|
-
`message` events with a new numeric `rowId` and refetches via
|
|
225
|
-
`fetchSelectedRecord` (same path as `grist-plugin-api.js` `onRecord`).
|
|
226
|
-
- **Playground iframe: row stuck on first selection** — iframe transport now
|
|
227
|
-
sends `dataChange: true` on `cursor-change` (same contract as inline
|
|
228
|
-
`pushRecord`), so `grist-plugin-api.js` refetches the record when the
|
|
229
|
-
inspector changes the cursor. Cursor-only messages left `w.record.id`
|
|
230
|
-
frozen on the initial row.
|
|
231
|
-
- **Selected row missing after handshake** — `useGristSelection` now binds
|
|
232
|
-
`grist.onRecord` / `onRecords` on mount instead of waiting for `docApi`
|
|
233
|
-
(`lifecycle.phase === "online"`). The host can push the initial cursor
|
|
234
|
-
record during `grist.ready` before `docApi` exists; late binding left
|
|
235
|
-
`w.record` / `w.mode` stuck at `null` / `"empty"`. A follow-up mount effect
|
|
236
|
-
that cleared stream state whenever `docApi` was falsy ran after the
|
|
237
|
-
`recordEvent` replay and wiped the first row; clearing now happens only after
|
|
238
|
-
a real disconnect, and cached payloads are re-applied when `docApi` turns
|
|
239
|
-
ready. The handshake manager also wires stream subscriptions when
|
|
240
|
-
negotiation starts. Regression tests in
|
|
241
|
-
`tests/sdk/selection-initial-record.test.tsx`.
|
|
242
|
-
- **Selection stuck on the first row** — `useGristSelection` now shallow-copies
|
|
243
|
-
`onRecord` / `onRecords` payloads. Grist can reuse one record object and
|
|
244
|
-
mutate fields in place; React skipped re-renders when the reference was
|
|
245
|
-
unchanged, so `w.record` looked frozen (e.g. always `{ "id": 1 }`).
|
|
246
|
-
|
|
247
|
-
### Changed
|
|
248
|
-
|
|
249
|
-
- **Widget Pages deploy concurrency** — GitHub Actions deploy workflows use
|
|
250
|
-
`queue: max` on the shared `pages-gh-pages` group so multiple widget deploys
|
|
251
|
-
triggered by one push (e.g. `packages/core` changes) queue instead of canceling
|
|
252
|
-
each other while waiting.
|
|
253
|
-
|
|
254
|
-
- **SDK alerts use classic `severity` only** — `info` / `warning` / `error` on
|
|
255
|
-
each descriptor; host shells style from `severity` (template `GristSdkAlerts`
|
|
256
|
-
maps warning → amber, info → muted, error → destructive).
|
|
257
|
-
- **Full SOTA handshake — no legacy connectivity path** — all SDK hooks now
|
|
258
|
-
route through `GristHandshakeManager` only. Removed
|
|
259
|
-
`useGristCoreFromLegacy`, the inline `mergeGristStatus()` ladder in the
|
|
260
|
-
compose path, and the parallel mapping bootstrap in
|
|
261
|
-
`useGristSelection` (mappings + `columnMappingStatus` now project from
|
|
262
|
-
`snapshot.config.mappings` via `deriveColumnMappingStatus` /
|
|
263
|
-
`extractResolvedMappings`). Standalone `useGrist()` / mid-level hooks
|
|
264
|
-
without `<GristWidgetProvider>` share a ref-counted page-level embedded
|
|
265
|
-
manager (`acquireEmbeddedHandshakeManager`). `useGristReady` and
|
|
266
|
-
`useGristAvailability` are thin wrappers over `useGristCore` (FSM-backed).
|
|
267
|
-
`useGristCurrentTable` feeds `CURRENT_TABLE_*` actions into the reducer and
|
|
268
|
-
reads `currentTableId` / loading / errors from `snapshot.sync.currentTable`.
|
|
269
|
-
Mid-level `useGristTableOps` / `useGristRowsFromTable` participate in
|
|
270
|
-
heartbeat coalescence via `useRpcHeartbeatCoalesce`. Deleted
|
|
271
|
-
`tests/unit/handshake-legacy-equivalence.test.ts` (obsolete).
|
|
272
|
-
- **Heartbeat auto-coalescence across the slice hooks** — `task-066`.
|
|
273
|
-
Every successful Grist RPC issued through the SDK's slice hooks
|
|
274
|
-
(`useGristWrites().applyActions` / `.table.*` / `.getTable(…)`,
|
|
275
|
-
`useGrist().fetchTable` / `.fetchTableRows` / `.fetchRow` /
|
|
276
|
-
`.listTables` / `.getDocName` / `.fetchSelectedTable` /
|
|
277
|
-
`.fetchSelectedRecord` / `.buildReplicaDocumentFromDocApi`,
|
|
278
|
-
`useGrist().getAttachmentUrl` / `.fetchAttachmentBase64` /
|
|
279
|
-
`.fetchAttachmentBlob` / `.getAccessToken` / `.fetchWithAuth`,
|
|
280
|
-
`useGrist().getWidgetOptions` / `.getWidgetOption` / `.setWidgetOption` /
|
|
281
|
-
`.setWidgetOptions` / `.patchWidgetOptions` / `.clearWidgetOptions`,
|
|
282
|
-
`useGrist().setCursorPosition` / `.setLinkedRowSelection`,
|
|
283
|
-
`useGristSectionApi().configure` / `.refreshMappings`,
|
|
284
|
-
`useGrist().refreshCurrentTable`, and `useGristActions().apply` and
|
|
285
|
-
derivatives) is now reported to the handshake manager via a single
|
|
286
|
-
internal `useRpcHeartbeatCoalesce()` helper. The heartbeat treats each
|
|
287
|
-
success as a free `HEARTBEAT_OK` and skips the next scheduled probe;
|
|
288
|
-
failures shorten the next probe to ≤ 1 s for fast re-confirmation.
|
|
289
|
-
Chatty widgets cost zero extra round-trips; quiet widgets keep their
|
|
290
|
-
baseline 30 s health check. Outside `<GristWidgetProvider>` /
|
|
291
|
-
`<GristHandshakeProvider>` the helper is a zero-cost passthrough.
|
|
292
|
-
- The legacy `<GristWidgetProvider>` was restructured into a two-layer
|
|
293
|
-
component so the internal manager context is mounted *before* the
|
|
294
|
-
slice composer runs (`GristWidgetContextTree` under the manager). Without this,
|
|
295
|
-
`useRpcHeartbeatCoalesce()` resolved to `null` inside the slice hooks and
|
|
296
|
-
coalescence silently no-op'd — `tests/sdk/handshake-rpc-coalesce.test.tsx`
|
|
297
|
-
pins the contract.
|
|
298
|
-
- Defensive `typeof grist === "undefined"` guards added to
|
|
299
|
-
`useGristCurrentTable` and `useGristSelection` so late passive
|
|
300
|
-
effects firing after a test's `emulator.dispose()` no longer surface
|
|
301
|
-
`ReferenceError: grist is not defined`. Pre-existing flake, surfaced
|
|
302
|
-
by the new test layout, now 0/10 in the stability loop.
|
|
303
|
-
- **`<GristWidgetProvider>` now mounts the handshake manager internally** —
|
|
304
|
-
`task-065`. The legacy provider creates a single `GristHandshakeManager` per
|
|
305
|
-
instance and exposes it through a private context. `useGristCore` reads from
|
|
306
|
-
that manager (via `useSyncExternalStore`) and projects the FSM snapshot into
|
|
307
|
-
the same `{ status, isAvailable, isReady, error, docApi, reload }` shape the
|
|
308
|
-
slice composer expects, so every existing slice hook
|
|
309
|
-
(`useGristSelection`, `useGristReads`, `useGristWrites`, …) transparently
|
|
310
|
-
benefits from the FSM-driven timing without any API change. Outside the
|
|
311
|
-
provider, `useGristCore` falls back to the pre-FSM
|
|
312
|
-
`useGristAvailability` + `useGristReady` chain so standalone escape hatches
|
|
313
|
-
keep working byte-for-byte.
|
|
314
|
-
- The manager's negotiate effect routes through the existing
|
|
315
|
-
`ensureGristReady()` singleton (so any stray `useGristReady()` user
|
|
316
|
-
coalesces with the manager's ready call), and the provider passes
|
|
317
|
-
`onBeforeReload: resetGristReadySingleton()` so a user-triggered
|
|
318
|
-
`reload()` actually re-issues `grist.ready` instead of replaying the
|
|
319
|
-
cached promise.
|
|
320
|
-
- Heartbeat is on by default for the provider (same defaults as
|
|
321
|
-
`<GristHandshakeProvider>`); pass nothing to keep it, or thread
|
|
322
|
-
`options.heartbeat = false` through the manager if a widget needs to opt out.
|
|
323
|
-
- Migration is transparent — no widget code change required. Two pre-existing
|
|
324
|
-
flaky integration tests (`tests/sdk/sdk-react.test.tsx` and
|
|
325
|
-
`tests/sdk/column-mapping-pending.test.tsx`) were tightened from a
|
|
326
|
-
"snapshot once" assertion to a single `waitFor` block on the fully-settled
|
|
327
|
-
state, because `pendingColumnMappingStatus.ok` is `true` (no `missing`
|
|
328
|
-
columns reported yet) and the legacy assertion was racing against the
|
|
329
|
-
transient pending-but-ok window.
|
|
330
|
-
|
|
331
|
-
### Internal
|
|
332
|
-
|
|
333
|
-
- **Handshake state machine (foundation)** — `task-060`. New
|
|
334
|
-
`packages/core/src/sdk/internal/handshake/` module models the widget ↔ Grist
|
|
335
|
-
relationship as five orthogonal axes (`LIFECYCLE`, `LINK`, `AUTHZ`, `CONFIG`,
|
|
336
|
-
`SYNC`) feeding a pure reducer + status projection + capability derivation.
|
|
337
|
-
- **Handshake effects layer** — `task-061`. `internal/handshake/effects/`
|
|
338
|
-
wires the pure machine to a real (or stubbed) runtime:
|
|
339
|
-
- `detect.ts` — exponential-backoff polling for `window.grist`, adaptive
|
|
340
|
-
budget driven by `navigator.connection.effectiveType` (30 s on 4g, 60 s
|
|
341
|
-
on 3g, 120 s on 2g/slow-2g).
|
|
342
|
-
- `negotiate.ts` — issues `grist.ready` with a 30 s timeout and an external
|
|
343
|
-
`AbortSignal`; bridges promise/sync ready impls.
|
|
344
|
-
- `subscriptions.ts` — pluggable binder; default wires SDK singletons,
|
|
345
|
-
`noopSubscriptionsBinder` available for tests.
|
|
346
|
-
- `mappings.ts` — declare + `sectionApi.mappings()` fetch + stream-payload
|
|
347
|
-
ingestion + 5 s `MAPPING_TIMEOUT` fallback to `unreported`.
|
|
348
|
-
- `manager.ts` — `GristHandshakeManager` owns the snapshot, runs effects in
|
|
349
|
-
response to lifecycle transitions, bumps generation on `reload()`,
|
|
350
|
-
cancels through a per-generation `AbortController`. Implements the
|
|
351
|
-
`subscribe` / `getSnapshot` interface React's `useSyncExternalStore`
|
|
352
|
-
requires. Exposes `recordRpcSuccess()` / `recordRpcFailure()` for
|
|
353
|
-
external coalescing with the heartbeat.
|
|
354
|
-
- **Heartbeat effect** — `task-063`. `internal/handshake/effects/heartbeat.ts`:
|
|
355
|
-
- Interval probe (default 30 s) calls `grist.docApi.getDocName()` (or any
|
|
356
|
-
custom `probe`); per-probe timeout default 10 s.
|
|
357
|
-
- `recordRpcSuccess()` coalesces with natural RPC traffic and pushes the
|
|
358
|
-
next probe out by a full interval — we don't burn requests.
|
|
359
|
-
- `recordRpcFailure()` immediately degrades the link signal and shortens
|
|
360
|
-
the next probe to ≤ 1 s for fast re-confirmation.
|
|
361
|
-
- Pauses on `visibilitychange:hidden` and resumes (with an immediate probe)
|
|
362
|
-
on `visible`. Listens to `online` events for instant network-recovery
|
|
363
|
-
re-probe.
|
|
364
|
-
- Reducer transitions: `connected → stale` after `staleAfterMissed`
|
|
365
|
-
misses, `→ lost` after `lostAfterMissed`. `lost` escalates to global
|
|
366
|
-
`"error"` status via `deriveStatus`.
|
|
367
|
-
- **Environment abstraction** — `internal/handshake/environment.ts` exposes a
|
|
368
|
-
`GristEnvironment` interface (`now`, `setTimeout`, `probeGrist`,
|
|
369
|
-
`effectiveTypeHint`, …) so effects are unit-testable with a virtual clock
|
|
370
|
-
via `createTestEnvironment`. Production code uses `createBrowserEnvironment`.
|
|
371
|
-
- **Mapping resolver** — `MappingResolver` merges column mappings from
|
|
372
|
-
`section_api` / `stream_record` / `stream_records` / `stream_new_record`
|
|
373
|
-
with a fixed priority order, making the final resolution a pure function of
|
|
374
|
-
the set of received payloads. Payloads stamped with a stale generation are
|
|
375
|
-
dropped silently.
|
|
376
|
-
|
|
377
|
-
### Tests
|
|
378
|
-
|
|
379
|
-
- **Handshake reducer + resolver** — 50 new unit tests covering: 24-permutation
|
|
380
|
-
resolver determinism, generation-stamped action drop, reducer idempotence
|
|
381
|
-
for duplicate stream payloads, link transitions `connected → stale → lost`,
|
|
382
|
-
mapping invalidation/recovery, current-table local-error containment, and
|
|
383
|
-
capability gates (`canRender` / `canWriteRecords` / `canWriteSchema` /
|
|
384
|
-
`hasFreshSelection`).
|
|
385
|
-
- **Manager lifecycle** — 14 unit tests with virtual time: detect budget
|
|
386
|
-
exhaustion, NOT_EMBEDDED detection, negotiate success/failure/timeout,
|
|
387
|
-
`reload()` generation bump + lifecycle reset, stale-generation drop,
|
|
388
|
-
capability transitions through online + mapping completion, no error
|
|
389
|
-
escalation on incomplete mappings, and `subscribe` / `getSnapshot`
|
|
390
|
-
contract for `useSyncExternalStore`.
|
|
391
|
-
- **`useGristHandshake()` integration** — 3 emulator-driven tests in
|
|
392
|
-
`tests/sdk/handshake-react.test.tsx` covering ready transition, mapping
|
|
393
|
-
state propagation, and stream-subscription wiring against `renderWithGrist`.
|
|
394
|
-
- **Heartbeat unit tests** — 11 tests in `tests/unit/handshake-heartbeat.test.ts`:
|
|
395
|
-
interval start (no t=0 probe), probe success / reject / timeout dispatches,
|
|
396
|
-
repeated firing, RPC coalesce, visibility pause + resume, `online` event,
|
|
397
|
-
`cancel()` cleanup.
|
|
398
|
-
- **Heartbeat ↔ manager integration** — 5 tests in
|
|
399
|
-
`tests/unit/handshake-manager.test.ts`: link degradation `connected → stale
|
|
400
|
-
→ lost`, `recordRpcSuccess()` reset, heartbeat shutdown on `stop()`,
|
|
401
|
-
`heartbeat: false` disablement.
|
|
402
|
-
- **`<GristHandshakeProvider>` integration** — 4 tests in
|
|
403
|
-
`tests/sdk/handshake-provider.test.tsx`: shared snapshot across consumers,
|
|
404
|
-
optional vs throwing context hooks.
|
|
405
|
-
- **Property-based / chaos tests** — `task-067`. 12 tests in
|
|
406
|
-
`tests/unit/handshake-properties.test.ts` using `fast-check`:
|
|
407
|
-
resolver determinism (200 random runs per property), generation gate,
|
|
408
|
-
reducer idempotence, `terminated` absorption, fuzz sequences of up to 30
|
|
409
|
-
random actions (200 runs) confirming no throws, monotone generation,
|
|
410
|
-
and `link.state` stays in its closed domain. Capability gates are
|
|
411
|
-
asserted to form a conjunctive chain
|
|
412
|
-
`canWriteSchema ⇒ canWriteRecords ⇒ canRender ⇒ canRead` over 300
|
|
413
|
-
randomized snapshots.
|
|
414
|
-
|
|
415
|
-
### Dev dependencies
|
|
416
|
-
|
|
417
|
-
- Added `fast-check ^4.8.0` (used only by `tests/unit/handshake-properties.test.ts`).
|
|
418
|
-
|
|
419
|
-
### Docs
|
|
420
|
-
|
|
421
|
-
- **Handshake module documentation** — `task-068`. New API reference page
|
|
422
|
-
at `/api/handshake` covering the public hooks
|
|
423
|
-
(`useGristHandshake` / `useGristCapabilities` / `<GristHandshakeProvider>` /
|
|
424
|
-
`useGristHandshakeContext` / `useGristHandshakeContextOptional`), the full
|
|
425
|
-
snapshot shape (`GristWidgetSnapshot`, `GristLifecycle`, `GristLink`,
|
|
426
|
-
`GristAuthz`, `GristConfig`, `GristSync`), derived `GristCapabilities`,
|
|
427
|
-
heartbeat coalescence semantics, and `reload()` vs `restart()`. New
|
|
428
|
-
conceptual guide page at `/guide/handshake` covering when to use the
|
|
429
|
-
new hooks, the five-axis state machine, the capability chain, the
|
|
430
|
-
heartbeat, the mapping states, and generation discipline.
|
|
431
|
-
- Updated `/api/index.md`, `/api/provider-boundary.md`, `/guide/concepts.md`,
|
|
432
|
-
and `/guide/error-handling.md` to cross-link the new module and to
|
|
433
|
-
describe the FSM-backed implementation of `<GristWidgetProvider>`.
|
|
434
|
-
- VitePress navigation: handshake guide listed under "Advanced topics",
|
|
435
|
-
handshake API ref listed in the unified Reference sidebar.
|
|
436
|
-
|
|
437
|
-
### Process
|
|
438
|
-
|
|
439
|
-
- **Lightweight workflow.** Dropped the seven-step `/ITERATION.md` cycle;
|
|
440
|
-
planning lives in chat. Roadmap + task board + `apps/docs/work.md` replace
|
|
441
|
-
the formal spec file.
|
|
442
|
-
|
|
443
|
-
### Changed
|
|
444
|
-
|
|
445
|
-
- **Slice hook return stability** — slice hooks memoize their result objects so
|
|
446
|
-
React context consumers and `React.memo` children keep stable callable references
|
|
447
|
-
(`table`, `mapBack`, `reload`, …) across unrelated slice updates.
|
|
448
|
-
|
|
449
|
-
### Tests
|
|
450
|
-
|
|
451
|
-
- **Slice identity** — `slice-identity.test.tsx` asserts zero extra renders for
|
|
452
|
-
memoized children when selection, writes, theme, or status slices change in isolation;
|
|
453
|
-
1000-row `records` list stays stable on cursor-only changes.
|
|
454
|
-
|
|
455
|
-
- **Render budget bench** — `pnpm --filter grist-widget-sdk bench` runs
|
|
456
|
-
`tests/bench/render-budget.test.tsx` and writes `packages/core/bench/results.json`
|
|
457
|
-
(full-record / slice / write / schema-fetch render deltas on `presets.todoList()`).
|
|
458
|
-
|
|
459
|
-
- **`useGristSchema` snapshots** — `use-grist-schema.snapshot.test.tsx` guards
|
|
460
|
-
`blank` / `todoList` / `contacts` × `schema-only` / `schema+samples` /
|
|
461
|
-
`schema+data` replica output from the emulator.
|
|
462
|
-
|
|
463
|
-
### Docs
|
|
464
|
-
|
|
465
|
-
- **Render budgets** — `/guide/performance` documents measured re-renders per
|
|
466
|
-
operation from the bench harness and slice-isolation expectations.
|
|
467
|
-
|
|
468
|
-
### Fixed
|
|
469
|
-
|
|
470
|
-
- **`waitForEvent`** no longer resolves immediately from bus history; waits for
|
|
471
|
-
the next matching event. Kind overload (`ready`, `record`, `records`, `options`,
|
|
472
|
-
`theme`, `cursor`) and clearer timeout errors.
|
|
473
|
-
- **Column mapping on load** — `columnMappingStatus.pending` stays true until Grist
|
|
474
|
-
reports mappings (`onRecord` / `onRecords` or `sectionApi.mappings()`). Widgets
|
|
475
|
-
no longer show a false "Column mapping is incomplete" alert during the brief
|
|
476
|
-
window after `status === "ready"`. `getGristSdkAlertDescriptors` ignores
|
|
477
|
-
pending mapping status.
|
|
478
|
-
|
|
479
|
-
### Changed
|
|
480
|
-
|
|
481
|
-
- **`columns` vs `safeParse` on reads** — `/guide/reading-data` documents the
|
|
482
|
-
contract: `columns` alone yields plain decoded rows; `safeParse` adds per-cell
|
|
483
|
-
issue tracking. Covered by unit tests in `grist-table-data.test.ts`.
|
|
484
|
-
- **Retired `/design/api-surface.md`** — export list lives in
|
|
485
|
-
`public-api.test.ts` + `/api/index`; conventions in `/design/principles`.
|
|
486
|
-
`tests/docs/structure.test.ts` fails CI if the page returns.
|
|
487
|
-
- **Cleared shipped items** from `/design/open-questions` → *Pending API
|
|
488
|
-
tightenings* (0.3+ work stays on the task board).
|
|
489
|
-
|
|
490
|
-
### Tests
|
|
491
|
-
|
|
492
|
-
- `packages/core/tests/unit/grist-table-data.test.ts` — `columns` vs `safeParse`
|
|
493
|
-
materialization shapes.
|
|
494
|
-
- `packages/core/tests/docs/structure.test.ts` — docs project in Vitest;
|
|
495
|
-
`api-surface.md` must not exist.
|
|
496
|
-
|
|
497
|
-
**Learnings:** The duplicate api-surface page was pure drift risk once
|
|
498
|
-
`public-api.test.ts` existed; documenting `columns` without `safeParse` stops
|
|
499
|
-
readers from wrapping every fetch in safe-parse cell types.
|
|
500
|
-
|
|
501
|
-
### Documentation & DX
|
|
502
|
-
|
|
503
|
-
- **Developer pathway is now template-first.** `/guide/getting-started`
|
|
504
|
-
opens with a two-minute `degit` TL;DR, then keeps manual install +
|
|
505
|
-
hello-world below the fold.
|
|
506
|
-
- **Cheat sheet, cookbook, troubleshooting, templates, and demos.**
|
|
507
|
-
Four new guide pages under `/guide/` (cookbook = 10 end-to-end
|
|
508
|
-
recipes, cheatsheet, troubleshooting, templates) plus a
|
|
509
|
-
top-level demo catalogue at `/demos`.
|
|
510
|
-
- **Three live demo widgets.** `form-edit`, `task-board`, and
|
|
511
|
-
`attachment-gallery` under `apps/playground/src/widgets/`,
|
|
512
|
-
reachable at `https://demo.grist-widgets.com/widget.html?id=<id>`
|
|
513
|
-
(raw, pasteable into a Grist Custom Widget URL) and
|
|
514
|
-
`https://demo.grist-widgets.com/?url=widget.html?id=<id>` (preview
|
|
515
|
-
in the playground shell, embedded in `/demos`).
|
|
516
|
-
- **Agent guide consolidation.** `/files/*` is reduced to 9 pages
|
|
517
|
-
(`AGENT.md`, `architecture.md`, `testing-patterns.md`,
|
|
518
|
-
`replica-document.md` deleted). Architecture / replica content
|
|
519
|
-
lives canonically under `/design/`. `/files/start-here.md` becomes
|
|
520
|
-
the single dense entry: operating contract, eight-step workflow
|
|
521
|
-
summary, commands, path map, decision tree, anti-patterns,
|
|
522
|
-
hand-off checklist. Every remaining `/files/*` page carries an
|
|
523
|
-
Audience / Companion / Verified-in preamble.
|
|
524
|
-
- **`llms.txt` + `llms-full.txt`** at `apps/docs/public/`. The first
|
|
525
|
-
is the standard llmstxt.org index for AI tool discovery; the
|
|
526
|
-
second concatenates every page in the slim `/files/*` set in
|
|
527
|
-
alphabetical order with `# <path>` delimiters so a single fetch
|
|
528
|
-
primes a model with the entire agent operating manual.
|
|
529
|
-
- **"Choose your path" tiles** on the docs home page: four entry
|
|
530
|
-
points keyed on intent (writing a widget / AI agent / evaluator /
|
|
531
|
-
see-it-work).
|
|
532
|
-
- **Code-block contract.** Every fenced `tsx` / `ts` block in
|
|
533
|
-
`/guide/getting-started`, `/guide/cookbook`,
|
|
534
|
-
`/guide/cheatsheet`, and the landing page (`/`) whose first
|
|
535
|
-
line is `// @example` is type-checked against the SDK at test
|
|
536
|
-
time. Catches API drift between docs and code automatically.
|
|
537
|
-
The contract caught five real bugs on first run
|
|
538
|
-
(gristAddTableAction arg shape, safeParseGristTableData result
|
|
539
|
-
field name, useGristSchema option name, missing presets.simple,
|
|
540
|
-
renderWithGrist option shape) — all fixed.
|
|
541
|
-
- **Landing page reworked for one-look DX + agent triage.** Three
|
|
542
|
-
hero CTAs (Get started / Reference / Demos), an inline `npx
|
|
543
|
-
degit` TL;DR scaffold block, a complete fifteen-line widget
|
|
544
|
-
shown as a `// @example` `tsx` block (type-checked alongside the
|
|
545
|
-
rest of the docs), and a "Choose your path" markdown table that
|
|
546
|
-
routes by intent — writing a widget, AI coding agent (links to
|
|
547
|
-
`/files/start-here`), evaluating the SDK, or seeing it work.
|
|
548
|
-
Feature cards rewritten to name the concrete APIs each one
|
|
549
|
-
represents and link to the most relevant `/api/` page.
|
|
550
|
-
- **Reference consolidation.** `Design` is no longer a top-level
|
|
551
|
-
nav entry; `Reference` collapses Design + API rationale under
|
|
552
|
-
one umbrella in nav and sidebar. URLs unchanged — `/api/*` and
|
|
553
|
-
`/design/*` still resolve where they did, and both share the
|
|
554
|
-
same sidebar grouping. `apps/docs/design/api-surface.md` is
|
|
555
|
-
retired: its conventions (naming / slot / empty-null / promise)
|
|
556
|
-
move to `/design/principles.md` as a `## Conventions` section,
|
|
557
|
-
and its "breaking changes to do" list moves to
|
|
558
|
-
`/design/open-questions.md` as `## Pending API tightenings`.
|
|
559
|
-
- **Landing-page polish.** Body region order is now What it looks
|
|
560
|
-
like → Start with Vite → Choose your path → Highlights: code
|
|
561
|
-
first, scaffold second, routing third, marketing last. The
|
|
562
|
-
`npx degit` block lives under a new `## Start with Vite` heading
|
|
563
|
-
that explicitly flags more templates (Next.js, plain HTML) are
|
|
564
|
-
planned, linking to `/guide/templates` as the running roster.
|
|
565
|
-
YAML feature cards (which rendered above the wayfinder and
|
|
566
|
-
carried deep links) are replaced by a `## Highlights` markdown
|
|
567
|
-
section below the Choose-your-path table — six capability blurbs
|
|
568
|
-
with no outbound links so the table is the only navigation
|
|
569
|
-
surface on the home page.
|
|
570
|
-
- **`/demos` promoted to a top-level URL.** The catalogue page
|
|
571
|
-
was previously at `/guide/demos`, miscategorising the showcase
|
|
572
|
-
as a learning step. Now lives at `/demos` with its own top-nav
|
|
573
|
-
entry, no sidebar (matches the catalogue shape). All cross-
|
|
574
|
-
links — landing page CTAs, getting-started "Next steps",
|
|
575
|
-
`llms.txt` index, demos.md internal Cookbook links — migrated
|
|
576
|
-
in the same commit so the build stays green at every HEAD.
|
|
577
|
-
- **Hero image.** The home page hero now has a real screenshot
|
|
578
|
-
next to the headline: the `form-edit` demo widget rendered
|
|
579
|
-
beside the playground's emulator panel. ~39 KB PNG at
|
|
580
|
-
`apps/docs/public/hero.png`, served via VitePress's
|
|
581
|
-
`hero.image` frontmatter slot. Real product surface, zero
|
|
582
|
-
illustration work, zero external network dep on first paint.
|
|
583
|
-
|
|
584
|
-
- Consolidated the docs `/work/` folder (roadmap + task board + guidelines +
|
|
585
|
-
release process) into a single iteration workflow described in
|
|
586
|
-
[`apps/docs/work.md`](/work).
|
|
587
|
-
The source of truth for in-flight scope is now `/ITERATION.md`; this
|
|
588
|
-
`CHANGELOG.md` is the source of truth for history.
|
|
589
|
-
- Root `pnpm test` now runs the SDK suite **plus** the docs build
|
|
590
|
-
**plus** the playground's widget bundle. Dead links and unbound
|
|
591
|
-
widget metadata trip CI without a separate workflow change.
|
|
592
|
-
- New `packages/core/tests/docs/` vitest project (node env). Four
|
|
593
|
-
files: `code-blocks.test.ts` (type-checks `// @example` blocks
|
|
594
|
-
in the three guide pages **and** the landing page),
|
|
595
|
-
`structure.test.ts` (slim /files/* set, preamble blocks, cookbook
|
|
596
|
-
recipe count, cookbook → demo cross-links, demos catalogue
|
|
597
|
-
shape, **landing-page hero / TL;DR / @example / table shape,
|
|
598
|
-
`/design/api-surface.md` retired, `/design/principles.md`
|
|
599
|
-
Conventions block, `/design/open-questions.md` Pending API
|
|
600
|
-
tightenings, body order (code-before-scaffold), uniform feature-
|
|
601
|
-
card `linkText: "Learn more →"`, hero `image.src: /hero.png`
|
|
602
|
-
with the file on disk, `/demos` top-level move**),
|
|
603
|
-
`links.test.ts` (resolves every relative link across
|
|
604
|
-
`apps/docs/**` and the three root files), `llms-txt.test.ts`
|
|
605
|
-
(asserts both `llms.txt` and `llms-full.txt`).
|
|
606
|
-
|
|
607
|
-
### Learnings
|
|
608
|
-
|
|
609
|
-
- The single highest-leverage change was the `// @example`
|
|
610
|
-
type-check: it caught five real API mismatches between docs and
|
|
611
|
-
code on the first run. Future iterations should keep adding the
|
|
612
|
-
sentinel to new code blocks rather than relying on review.
|
|
613
|
-
- Splitting the docs site into two audiences (`/guide/` for
|
|
614
|
-
developers, `/files/` for agents) with strict no-duplication
|
|
615
|
-
rules and a slim, deterministic agent set produced a much cleaner
|
|
616
|
-
navigation than the previous mixed structure. The `/files/`
|
|
617
|
-
preamble (Audience / Companion / Verified-in) is the gate that
|
|
618
|
-
keeps the agent surface honest.
|
|
619
|
-
- Embedding demos via the existing playground shell (preview URL =
|
|
620
|
-
emulated table next to the widget) is a much better
|
|
621
|
-
reading-experience than a bare widget — the reader sees both the
|
|
622
|
-
data and the widget's reaction to it. Worth keeping that pattern
|
|
623
|
-
for any future demo iteration.
|
|
624
|
-
- Including agent-facing top-level files (`ITERATION.md`,
|
|
625
|
-
`CHANGELOG.md`) into VitePress pages via the `@include`
|
|
626
|
-
directive is convenient but couples two link-resolution
|
|
627
|
-
contexts: the source file (read from repo root, GitHub, IDE)
|
|
628
|
-
and the included page (read from the docs site). Relative
|
|
629
|
-
links in the source silently break in one of the two. Rule of
|
|
630
|
-
thumb: any link that lives inside the included range must use
|
|
631
|
-
an absolute URL — prefer the GitHub permalink so the source
|
|
632
|
-
file still works outside the docs build.
|
|
633
|
-
- Nav consolidation under `Reference` (instead of separate `API` /
|
|
634
|
-
`Design` entries) without moving files is a strict improvement:
|
|
635
|
-
unifies the mental model for readers and AI agents, keeps every
|
|
636
|
-
external URL stable. Prefer sidebar-level grouping over directory
|
|
637
|
-
reshuffles whenever the cost is borne by future external links.
|
|
638
|
-
- The opposite intuition holds when a page is genuinely
|
|
639
|
-
miscategorised: `/demos` belonged at the top level, not under
|
|
640
|
-
`/guide/`. The move broke a handful of cross-links (cookbook,
|
|
641
|
-
getting-started Next steps, `llms.txt`, demos.md's own
|
|
642
|
-
`./cookbook` references) but every break was caught at build
|
|
643
|
-
time by the dead-link detector + the existing `links.test.ts`,
|
|
644
|
-
with no manual auditing required. Lesson: trust the safety
|
|
645
|
-
net, ship the structural fix, watch the build fail loudly, fix
|
|
646
|
-
the breaks. Cheaper than living with the miscategorisation.
|
|
647
|
-
- Two-pillar landing-page surface — a code block ("what does this
|
|
648
|
-
look like") plus an image ("what does a real widget look like")
|
|
649
|
-
— answers more pre-commitment questions than either alone. The
|
|
650
|
-
image being a real product screenshot (the form-edit demo +
|
|
651
|
-
emulator side by side) rather than an illustration carries
|
|
652
|
-
more weight: the reader trusts the surface they're shown is
|
|
653
|
-
the one they'll be building. Worth preserving as the SDK
|
|
654
|
-
matures — swap the asset, keep the slot.
|
|
655
|
-
- When build-green-at-HEAD is a hard constraint and two changes
|
|
656
|
-
are coupled (the `/demos` URL was simultaneously consumed by
|
|
657
|
-
the landing page and produced by the moved file), the
|
|
658
|
-
three-commit plan from the spec collapsed to a two-commit
|
|
659
|
-
reality. Cleaner to acknowledge this in the commit message
|
|
660
|
-
and bundle than to leave a broken HEAD or carry a fake "the
|
|
661
|
-
page exists at the new location but no one links to it" interim
|
|
662
|
-
state.
|
|
663
|
-
|
|
664
|
-
### Added
|
|
665
|
-
|
|
666
|
-
- `useGristStatus()` now exposes `currentTableId`, `currentTableLoading`,
|
|
667
|
-
`refreshCurrentTable`, `tableError`, and the raw `docApi` handle. The hook
|
|
668
|
-
is a single subscription point for "status + selected table" UIs.
|
|
669
|
-
- `mapBack(patch)` reports skipped logical names via `w.mapBackSkipped`
|
|
670
|
-
(and on `useGristSelection().mapBackSkipped`). A new alert descriptor
|
|
671
|
-
`kind: "map-back-skip"` surfaces them through
|
|
672
|
-
`getGristSdkAlertDescriptors(...)`.
|
|
673
|
-
- `formatMapBackSkipMessage(skipped, hint?)` for hosts that build alert copy
|
|
674
|
-
themselves.
|
|
675
|
-
- `presets` (`blank`, `todoList`, `contacts`) are now re-exported from
|
|
676
|
-
`grist-widget-sdk/emulator/testing`.
|
|
677
|
-
- `grist-widget-sdk/emulator/testing` re-exports the most-used
|
|
678
|
-
`@testing-library/react` primitives (`screen`, `fireEvent`, `waitFor`,
|
|
679
|
-
`act`, `cleanup`, `within`, `render`).
|
|
680
|
-
- `emulator.theme.set("light" | "dark")` convenience for tests that drive
|
|
681
|
-
theme transitions.
|
|
682
|
-
|
|
683
|
-
### Changed
|
|
684
|
-
|
|
685
|
-
- **Breaking:** Slice hooks (`useGristStatus`, `useGristSelection`,
|
|
686
|
-
`useGristWrites`, `useGristTheme`) now require a parent
|
|
687
|
-
`<GristWidgetProvider>` and throw outside of one. Use `useGrist()` for the
|
|
688
|
-
standalone single-leaf case.
|
|
689
|
-
- The inline emulator transport emits `themeInitialChange` /
|
|
690
|
-
`themeChange` instead of a single `theme` event, matching production
|
|
691
|
-
`grist-plugin-api.js`. Late listeners get replayed.
|
|
692
|
-
|
|
693
|
-
### Tests
|
|
694
|
-
|
|
695
|
-
- Public-API snapshot pins every documented symbol across the four entry
|
|
696
|
-
points (`/`, `/advanced`, `/emulator`, `/emulator/testing`).
|
|
697
|
-
- Slice-hook integration tests for status / selection / writes / theme using
|
|
698
|
-
the emulator (`renderWithGrist`).
|
|
699
|
-
- Action-builder shape tests for every supported user action.
|
|
700
|
-
- `mapBack` + `allowMultiple` end-to-end test exercising the alert path.
|
|
701
|
-
|
|
702
|
-
### Process
|
|
703
|
-
|
|
704
|
-
- Consolidated the docs `/work/` folder (roadmap + task board + guidelines +
|
|
705
|
-
release process) into a single iteration workflow described in
|
|
706
|
-
[`apps/docs/work.md`](/work).
|
|
707
|
-
The source of truth for in-flight scope is now `/ITERATION.md`; this
|
|
708
|
-
`CHANGELOG.md` is the source of truth for history.
|
|
709
|
-
|
|
710
|
-
## 0.1.0
|
|
711
|
-
|
|
712
|
-
### Added
|
|
713
|
-
|
|
714
|
-
- Slice hooks: `useGristSelection`, `useGristWrites`, `useGristStatus`, `useGristTheme`
|
|
715
|
-
- `patchWidgetOptions`, `configure`, `refreshMappings` on `useGrist()`
|
|
716
|
-
- `fetchAttachmentBlob`, schema table action builders, brief REST token cache
|
|
717
|
-
- Theme subscription via `grist.on("themeInitialChange" | "themeChange")`
|
|
718
|
-
|
|
719
|
-
### Changed
|
|
720
|
-
|
|
721
|
-
- **Breaking:** Removed deprecated `updateRecord` / `addRecord` / `bulk*` helpers from `useGrist()`
|
|
722
|
-
- **Breaking:** Removed `buildDocument()` — use `buildReplicaDocumentFromDocApi()` only
|
|
723
|
-
- `useGristSchema()` defaults to `requiredAccess: "read table"`
|
|
724
|
-
- `GristBoundary` unavailable grace period increased to 5s
|
|
725
|
-
- Refactored `useGrist` into composable internal hooks + context slices
|
|
726
|
-
|
|
727
|
-
### Fixed
|
|
728
|
-
|
|
729
|
-
- `currentTableId` now refreshes when the selected row changes
|
|
730
|
-
- `validateColumnMappings` no longer double-counts missing `allowMultiple` columns
|
|
22
|
+
Published manually (pre-Changesets); see the root changelog's **0.1.0** →
|
|
23
|
+
**Unreleased — 0.2.0** history for everything that shipped in this version.
|