@fusebase/fusebase-gate-sdk 2.5.4 → 2.6.1

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.
@@ -2,8 +2,946 @@
2
2
 
3
3
  - Current ref: `HEAD`
4
4
  - Previous tag: `v2.5.4`
5
- - Generated at: 2026-08-05T12:30:28.686Z
5
+ - Generated at: 2026-08-11T05:32:13.369Z
6
6
 
7
7
  ## Included Drafts
8
8
 
9
- - None
9
+ - `docs/release-notes/2026-07-09-portal-homepage.md` - Portal homepage settings (NIM-42205)
10
+ - `docs/release-notes/2026-08-03-isolated-sql-read-only-transaction.md` - Read-only isolated SQL is enforced by Postgres
11
+ - `docs/release-notes/2026-08-05-add-portal-chats-dashboard-menu-item.md` - addPortalChatsDashboardMenuItem — add a Chat dashboard navigation item (NIM-43021)
12
+ - `docs/release-notes/2026-08-05-add-portal-files-dashboard-menu-item.md` - addPortalFilesDashboardMenuItem — add a Files dashboard navigation item (NIM-43020)
13
+ - `docs/release-notes/2026-08-05-add-portal-tasks-dashboard-menu-item.md` - addPortalTasksDashboardMenuItem — add a Tasks dashboard navigation item (NIM-43022)
14
+ - `docs/release-notes/2026-08-05-remove-add-portal-block.md` - addPortalBlock removed from Fusebase Gate (NIM-43018)
15
+ - `docs/release-notes/2026-08-06-chats-dashboard-menu-item-duplicates.md` - addPortalChatsDashboardMenuItem — duplicate items allowed (NIM-43021)
16
+ - `docs/release-notes/2026-08-06-portal-block-discovery-move-delete.md` - Portal block discovery, move and delete (NIM-43062)
17
+ - `docs/release-notes/2026-08-06-portal-block-read-and-note-update.md` - Read a portal block, and edit a note-backed one (NIM-43065)
18
+ - `docs/release-notes/2026-08-06-portal-content-permissions.md` - Portal page and folder permissions (NIM-43064)
19
+ - `docs/release-notes/2026-08-06-portal-text-media-block-update.md` - Edit portal Heading, Simple text and Image blocks (NIM-43065)
20
+ - `docs/release-notes/2026-08-10-portal-ai-agent-block-update.md` - Summary
21
+ - `docs/release-notes/2026-08-10-portal-app-block-update.md` - Summary
22
+ - `docs/release-notes/2026-08-10-portal-bpmn-diagram-block-update.md` - Summary
23
+ - `docs/release-notes/2026-08-10-portal-card-group-block-update.md` - Card group block update
24
+ - `docs/release-notes/2026-08-10-portal-carousel-block-update.md` - Edit portal Carousel blocks (NIM-43065)
25
+ - `docs/release-notes/2026-08-10-portal-chat-block-update.md` - Summary
26
+ - `docs/release-notes/2026-08-10-portal-countdown-block-update.md` - Summary
27
+ - `docs/release-notes/2026-08-10-portal-custom-widget-block-update.md` - Custom widget block update
28
+ - `docs/release-notes/2026-08-10-portal-dynamic-table-block-update.md` - Dynamic Table block update
29
+ - `docs/release-notes/2026-08-10-portal-file-uploader-block-update.md` - Summary
30
+ - `docs/release-notes/2026-08-10-portal-form-block-update.md` - Form block update
31
+ - `docs/release-notes/2026-08-10-portal-html-css-block-update.md` - Summary
32
+ - `docs/release-notes/2026-08-10-portal-info-block-update.md` - Info block update
33
+ - `docs/release-notes/2026-08-10-portal-linked-block-update.md` - Linked block update
34
+ - `docs/release-notes/2026-08-10-portal-recent-files-block-update.md` - Recent files block update
35
+ - `docs/release-notes/2026-08-10-portal-timeline-block-update.md` - Summary
36
+ - `docs/release-notes/2026-08-11-portal-style.md` - Portal branding (NIM-42204)
37
+
38
+ ## Summary
39
+
40
+ ### Portal homepage settings (NIM-42205)
41
+
42
+ Gate now exposes `updatePortalHomepage`, letting CLI/SDK/MCP agents configure a portal's Homepage settings — the same panel the customizer shows. The change is staged in the portal's customizer draft (not published immediately), so the owner reviews it under Homepage settings before publishing.
43
+
44
+ ### Read-only isolated SQL is enforced by Postgres
45
+
46
+ `queryIsolatedStoreSql` decided "read-only" from the SQL text: the leading command plus a keyword scan for data-modifying CTEs. A `SELECT` over a volatile function that writes passed that check, and the scanner's lexer disagreed with Postgres about where an escape-string literal ends, which let a real data-modifying CTE through. The read path now opens its transaction with `BEGIN READ ONLY`, so the server rejects every write, including one made inside a function body. The keyword scan stays as an early, friendlier error.
47
+
48
+ ### addPortalChatsDashboardMenuItem — add a Chat dashboard navigation item (NIM-43021)
49
+
50
+ New public operation `addPortalChatsDashboardMenuItem`
51
+ (`POST /:orgId/portals/:portalId/chats-dashboard`, `portals.write`) adds a root
52
+ navigation item pointing at the portal's system Chat dashboard — the
53
+ programmatic equivalent of the Customizer's root **Dashboards > Chat dashboard**
54
+ action.
55
+
56
+ The dashboard page at `/chats-dashboard` and its canonical `chats-dashboard`
57
+ block are created with the portal (and backfilled on older portals by
58
+ `chatsDashboardMigration`), so this operation **never creates a page or a
59
+ block**. A portal without that page is inconsistent, not a portal that needs a
60
+ second dashboard: the request returns 409 and stages nothing.
61
+
62
+ `addPortalChatBlock` still owns the chat WIDGET (a channel or DM block on an
63
+ existing page). The dashboard selects no chat entity, so no channel or user
64
+ discovery is involved.
65
+
66
+ Gate is a thin semantic passthrough: exactly one portal-service call, and it
67
+ builds no menu item, id, index or change event. The page lookup, the per-bar
68
+ duplicate check, the canonical item shape, the atomic append and its optimistic
69
+ concurrency retry all live in portal-service.
70
+
71
+ ### addPortalFilesDashboardMenuItem — add a Files dashboard navigation item (NIM-43020)
72
+
73
+ New public operation `addPortalFilesDashboardMenuItem`
74
+ (`POST /:orgId/portals/:portalId/files-dashboard`, `portals.write`) adds a root
75
+ navigation item for the portal's system Files dashboard, staged in the
76
+ Customizer draft. If the portal has no `/files-dashboard` page yet, the page and
77
+ its canonical `files-dashboard` block are created in the same draft batch; if it
78
+ already has one, only the item is added, so several bars can link to the same
79
+ dashboard. The dashboard renders the portal's full File Manager, so it is a
80
+ system page — not a block an agent can place on a page of its choice.
81
+ `addPortalRecentFilesBlock` and `addPortalFileUploaderBlock` still own the
82
+ latest-files list and the upload area on existing pages.
83
+
84
+ This replaces the unreleased `createPortalFileDashboard` operation, which
85
+ answered `409 alreadyExists` for a portal that already had the page and created
86
+ no navigation item. Every portal gets its `/files-dashboard` page at setup, so
87
+ that op was unusable for its actual purpose: putting a Files entry in a bar.
88
+
89
+ Gate is a thin semantic passthrough: exactly one portal-service call, no draft
90
+ read, no page/block/id/change-event construction. The dashboard lookup, the
91
+ canonical page, item and block, the atomic batch and its optimistic-concurrency
92
+ retry all live in portal-service.
93
+
94
+ ### addPortalTasksDashboardMenuItem — add a Tasks dashboard navigation item (NIM-43022)
95
+
96
+ New public operation `addPortalTasksDashboardMenuItem`
97
+ (`POST /:orgId/portals/:portalId/tasks-dashboard`, `portals.write`) adds a root
98
+ navigation item for the portal's system Tasks dashboard, staged in the
99
+ Customizer draft. If the portal has no `/tasks-dashboard` page yet, the page and
100
+ its canonical `tasks-dashboard` block are created in the same draft batch; if it
101
+ already has one, only the item is added, so several bars can link to the same
102
+ dashboard. The dashboard shows each viewer the portal tasks they are allowed to
103
+ access, so it is a system page — not a block an agent can place on a page of its
104
+ choice. `addPortalTaskListBlock` and `addPortalKanbanBlock` still own the task
105
+ list and board blocks on existing pages.
106
+
107
+ Gate is a thin semantic passthrough: exactly one portal-service call, no draft
108
+ read, no page/block/id/change-event construction. The dashboard lookup, the
109
+ canonical page, item and block, the atomic batch and its optimistic-concurrency
110
+ retry all live in portal-service.
111
+
112
+ ### addPortalBlock removed from Fusebase Gate (NIM-43018)
113
+
114
+ The generic operation `addPortalBlock`
115
+ (`POST /:orgId/portals/:portalId/blocks`, `portals.write`) is **removed**: the
116
+ contract, the controller, its `portals` MCP prompt section, the generated
117
+ OpenAPI/SDK artifacts and its tests.
118
+
119
+ It was the universal escape hatch behind the customizer's "Add new block"
120
+ palette. Supported semantic workflows now use dedicated operations that own
121
+ their canonical blocks: `addPortalNoteBlock`, `addPortalBlankNoteBlock`,
122
+ `addPortalTaskListBlock`, `addPortalKanbanBlock`, `addPortalEmbedBlock`,
123
+ `addPortalChatBlock`, `addPortalAiAgentBlock`, `addPortalDatabaseBlock`,
124
+ `addPortalDynamicTableBlock`, `addPortalBpmnDiagramBlock`,
125
+ `addPortalHeadingBlock`, `addPortalTextBlock`, `addPortalImageBlock`,
126
+ `addPortalCardBlock`, `addPortalCardGroupBlock`, `addPortalInfoBlock`,
127
+ `addPortalCarouselBlock`, `addPortalCountdownBlock`, `addPortalFormBlock`,
128
+ `addPortalCustomWidgetBlock`, `addPortalHtmlCssBlock`, `addPortalLinkedBlock`,
129
+ `addPortalTimelineBlock`, `addPortalFileUploaderBlock` and
130
+ `addPortalRecentFilesBlock`. The generic path already answered 400 naming **20**
131
+ of those operations by hand, so its guard chain was most of what remained of it.
132
+
133
+ Gate no longer builds any portal block shape by hand. `buildBlockChangeEvent`,
134
+ `SUPPORTED_BLOCK_TYPES` and the `properties` type validator existed only for the
135
+ generic path and went with it; the note-block builder used by
136
+ `addPortalNoteBlock` is untouched.
137
+
138
+ ### addPortalChatsDashboardMenuItem — duplicate items allowed (NIM-43021)
139
+
140
+ The Customizer lets a menu bar carry any number of Chat dashboard items, so
141
+ `addPortalChatsDashboardMenuItem` no longer refuses one. The per-bar duplicate
142
+ check and its 409 `alreadyExists` reason are removed in portal-service and gate;
143
+ every item still points at the portal's system `/chats-dashboard` page.
144
+
145
+ ### Portal block discovery, move and delete (NIM-43062)
146
+
147
+ Gate can now find, reorder and remove an existing block on a portal page. Until
148
+ now it could only add blocks; changing one meant sending an internal block DTO
149
+ or raw draft change events, which the public API does not accept.
150
+
151
+ Three new operations, all staged in the active Customizer draft and never
152
+ published automatically:
153
+
154
+ - `listPortalPageBlocks` — every block of one page in display order, over the
155
+ published portal plus its active draft. The discovery step before a move or a
156
+ delete, and the order a move anchor refers to.
157
+ - `movePortalBlock` — reorders a block within its own page via a semantic
158
+ position: `first`, `last`, `beforeBlockId` or `afterBlockId`.
159
+ - `deletePortalBlock` — removes one removable block, keeping every backing
160
+ resource.
161
+
162
+ ### Read a portal block, and edit a note-backed one (NIM-43065)
163
+
164
+ NIM-43062 gave Gate block discovery, move and delete. This adds the read half
165
+ and the first typed edit, so an agent can go list → read → patch without ever
166
+ touching an internal block DTO:
167
+
168
+ - `getPortalBlock` — one block's semantic `kind` and current public settings,
169
+ over the published portal plus its active Customizer draft. The inverse of
170
+ the add operations and the step that lets an agent show the user what is set
171
+ today before changing it.
172
+ - `updatePortalNoteBlock` — a partial update of a note-backed block: the kind
173
+ behind `addPortalNoteBlock`, `addPortalBlankNoteBlock`, `addPortalKanbanBlock`
174
+ and `addPortalTaskListBlock`, which all persist as the same block kind.
175
+
176
+ `listPortalPageBlocks` now fills in `editOperation` for a block whose typed
177
+ update operation exists — today that is the note kind; it stays `null` for the
178
+ other kinds until their operations land.
179
+
180
+ The change is staged in the active Customizer draft and never published
181
+ automatically.
182
+
183
+ ### Portal page and folder permissions (NIM-43064)
184
+
185
+ Gate can now set who may open one published portal page or folder, by path and
186
+ email, in a single call. Until now an agent had to find the internal
187
+ `menuItemId`, resolve numeric `userId`s and post a raw permissions payload — a
188
+ multi-phase flow the public API does not expose.
189
+
190
+ `updatePortalContentPermissions` takes the item's exact full path and a list of
191
+ emails:
192
+
193
+ - `mode: "replace"` — the previous item ACL is dropped and access is left to the
194
+ supplied emails ("make this page only for X").
195
+ - `mode: "add"` — everyone who already has access keeps it and the supplied
196
+ emails are added ("also give X access").
197
+
198
+ Emails that are not portal members yet receive a targeted client/editor portal
199
+ invitation. Unlike `inviteToPortal`, the new members are **not** added to the
200
+ portal's private resources, so they reach only the item they were named for.
201
+
202
+ The target is resolved before any invitation is created, so a wrong path, an
203
+ unsupported item or a caller without portal console access fails without a side
204
+ effect. A `client`-role caller goes through the same client-invite policy
205
+ `inviteToPortal` applies (NIM-40541: the feature kill-switch, the portal's
206
+ `allowClientsToInvite` setting, and the caller's own portal access) — holding
207
+ `portals.manage` is not enough to invite.
208
+
209
+ The change is applied to the live portal, not staged in the Customizer draft:
210
+ it must **not** be followed by `publishPortalDraft`.
211
+
212
+ ### Edit portal Heading, Simple text and Image blocks (NIM-43065)
213
+
214
+ The second block family of the portal edit operations, on the seam
215
+ `getPortalBlock` / `updatePortalNoteBlock` established. Three typed updates for
216
+ the Customizer's Text & Media tiles, plus their `getPortalBlock` settings:
217
+
218
+ - `updatePortalHeadingBlock` and `updatePortalTextBlock` — partial updates of
219
+ `content` (text, colour, size, styles, alignment), `appearance` and `layout`.
220
+ - `updatePortalImageBlock` — `layout` only. The picture stays the Customizer's
221
+ built-in image.
222
+
223
+ `getPortalBlock` now reports these kinds' current `content` and `appearance`, so
224
+ an agent can show what is set today and preserve the fields the user did not ask
225
+ to change. `listPortalPageBlocks` fills in `editOperation` for them.
226
+
227
+ The change is staged in the active Customizer draft and never published
228
+ automatically.
229
+
230
+ ### Card group block update
231
+
232
+ Added support for updating an existing portal Card group block.
233
+
234
+ ### Edit portal Carousel blocks (NIM-43065)
235
+
236
+ Agents can now read and update an existing Carousel block. Changes are staged
237
+ in the active Customizer draft and are not published automatically.
238
+
239
+ ### Custom widget block update
240
+
241
+ Added support for updating an existing portal Custom widget block.
242
+
243
+ ### Dynamic Table block update
244
+
245
+ Added support for updating an existing portal Dynamic Table block.
246
+
247
+ ### Form block update
248
+
249
+ Added support for updating an existing portal Form block.
250
+
251
+ ### Info block update
252
+
253
+ Added support for updating an existing portal Info block.
254
+
255
+ ### Linked block update
256
+
257
+ Added support for changing the exact source of an existing Linked block.
258
+
259
+ ### Recent files block update
260
+
261
+ Added support for updating an existing portal Recent files block.
262
+
263
+ ### Portal branding (NIM-42204)
264
+
265
+ Gate now exposes `updatePortalStyle`, letting CLI/SDK/MCP agents set a portal's branding — the color theme, the header logo and the favicon. These are the same three fields the customizer's Portal style panel writes. The change is staged in the portal's customizer draft (not published immediately), so the owner reviews it before publishing. `getPortal` gained the matching read side.
266
+
267
+
268
+ ## API / SDK Changes
269
+
270
+ ### Portal homepage settings (NIM-42205)
271
+
272
+ - Added `updatePortalHomepage({ orgId, portalId }, body)` — `POST /:orgId/portals/:portalId/homepage`.
273
+ - Body fields (all optional, patch semantics; at least one is required):
274
+ - `title` — homepage title. Renames the Home menu item and, with it, the linked homepage.
275
+ - `showSidebarOnOtherPages`, `showSidebarOnHomePage`, `expandSidebarByDefault` — sidebar toggles.
276
+ - Omitted fields keep their current value. One call stages up to two change events — `updateThemeSettings` and `updateMenuItem` — in a single draft write, so a mixed update is all-or-nothing. Returns `{ branchId, seqs, staged: true }`. Requires `portals.write` access.
277
+
278
+ ### Read-only isolated SQL is enforced by Postgres
279
+
280
+ No signature changes. `queryIsolatedStoreSql`, and a `query` operation in a batch where **every** operation is a read (`query`, `count`, `select`), run inside a Postgres `READ ONLY` transaction.
281
+
282
+ A batch that also carries a write operation cannot be read-only — a transaction's access mode cannot be relaxed once set — so there a `query` operation is a raw write channel and now requires `isolated_store.execute` instead of `isolated_store.read`. This closes an escalation: the `client` role holds `isolated_store.data.write` but not `isolated_store.execute`, so an app token could previously run arbitrary DML/DDL through a `query` op smuggled into a write batch.
283
+
284
+ ### addPortalChatsDashboardMenuItem — add a Chat dashboard navigation item (NIM-43021)
285
+
286
+ - New op `addPortalChatsDashboardMenuItem`, MCP-enabled, prompt groups `authz`,
287
+ `sdk`, `portals`. Portals MCP prompt bumped to 1.66.0.
288
+ - New schemas `AddPortalChatsDashboardMenuItemRequest` and
289
+ `AddPortalChatsDashboardMenuItemResponse`.
290
+ - The body is optional. `title` (1..100 plain-text characters, default
291
+ `Chat Dashboard`) names the navigation item only — the page title, icon, slug
292
+ and route are fixed by the service. `positionType` (`sidebar` default, `top`,
293
+ `footer`) picks the root menu bar.
294
+ - There is no `pageId` and no `parentId`: the Customizer offers dashboards on
295
+ root only, never inside a folder or process.
296
+ - Response is `menuItemId`, `pageId` (the system page the item points at), `url`
297
+ (`/chats-dashboard`), `positionType`, `branchId`, `seqs` and `staged: true`.
298
+ Nothing is published.
299
+ - Duplicates are allowed, matching the Customizer: any bar may carry several
300
+ items, and every one of them points at the same dashboard. A portal with no
301
+ system dashboard page returns the terminal 409 `dashboardPageMissing`, unlike
302
+ the retryable `draftConflict`, so an agent is told whether a retry can help.
303
+
304
+ ### addPortalFilesDashboardMenuItem — add a Files dashboard navigation item (NIM-43020)
305
+
306
+ - New op `addPortalFilesDashboardMenuItem`, MCP-enabled, prompt groups `authz`,
307
+ `sdk`, `portals`.
308
+ - The earlier `createPortalFileDashboard` proposal is dropped rather than kept
309
+ alongside; it never reached `main`, so no consumer can be calling it.
310
+ - New schemas `AddPortalFilesDashboardMenuItemRequest` and
311
+ `AddPortalFilesDashboardMenuItemResponse`.
312
+ - The request body may be empty (`requestBody.required: false`). Optional
313
+ `title` (trimmed plain text, 1..100) names the navigation item and, when the
314
+ page is created, the dashboard block; optional `positionType` (`sidebar`
315
+ default, `top`, `footer`) chooses the bar. The page title `Files`, the
316
+ `/files-dashboard` path and the Files icon are fixed by the service. No
317
+ `pageId`, `parentId`, file/bucket ids, appearance, layout or raw persisted
318
+ fields are accepted.
319
+ - Response is `menuItemId`, `pageId`, `blockId` (only when this call created the
320
+ page), `url`, `pageCreated`, `branchId`, `seqs` and `staged: true` — nothing is
321
+ published automatically.
322
+ - `409` means only "the draft moved concurrently" and is worth a retry. An
323
+ existing dashboard is not a conflict.
324
+ - Portals MCP prompt bumped to 1.65.0.
325
+
326
+ ### addPortalTasksDashboardMenuItem — add a Tasks dashboard navigation item (NIM-43022)
327
+
328
+ - New op `addPortalTasksDashboardMenuItem`, MCP-enabled, prompt groups `authz`,
329
+ `sdk`, `portals`.
330
+ - New schemas `AddPortalTasksDashboardMenuItemRequest` and
331
+ `AddPortalTasksDashboardMenuItemResponse`.
332
+ - The request body may be empty. Optional `title` (trimmed plain text, 1..100)
333
+ names the navigation item and, when the page is created, the dashboard block;
334
+ optional `positionType` (`sidebar` default, `top`, `footer`) chooses the bar.
335
+ The page title `Task Dashboard`, the `/tasks-dashboard` path and the Tasks
336
+ icon are fixed by the service. No `pageId`, `parentId`, task/task-list ids,
337
+ appearance, layout or raw persisted fields are accepted.
338
+ - Response is `menuItemId`, `pageId`, `blockId` (only when this call created the
339
+ page), `url`, `pageCreated`, `branchId`, `seqs` and `staged: true` — nothing is
340
+ published automatically.
341
+ - `409` means only "the draft moved concurrently" and is worth a retry. An
342
+ existing dashboard is not a conflict.
343
+ - Portals MCP prompt bumped to 1.67.0.
344
+
345
+ ### addPortalBlock removed from Fusebase Gate (NIM-43018)
346
+
347
+ - Op `addPortalBlock` removed. The route answers 404 from this release.
348
+ - Schemas `AddPortalBlockRequest`, `AddPortalBlockResponse` and the
349
+ `PortalBlockType` enum removed from the runtime schema defs, the OpenAPI
350
+ document and the SDK types.
351
+ - `PortalsApi.addPortalBlock()` removed from the generated SDK client, and
352
+ `addPortalBlock` from the manifest's operation list.
353
+ - The `portals` MCP prompt (bumped to 1.63.0) loses the op's menu entry and its
354
+ 68-line detail section. The 17 "do not use generic `addPortalBlock` for X"
355
+ sentences in the dedicated ops' guidance are gone too: with no generic op there
356
+ is no detour left to warn about.
357
+
358
+ ### addPortalChatsDashboardMenuItem — duplicate items allowed (NIM-43021)
359
+
360
+ - 409 now carries only `dashboardPageMissing` (terminal) or `draftConflict`
361
+ (retryable). `alreadyExists` is gone — no caller can receive it any more.
362
+ - The documented default item title is `Chat Dashboard`, the name portal-service
363
+ actually gives the item (it was documented as `Chat dashboard`).
364
+ - Portals MCP prompt bumped to 1.68.0.
365
+
366
+ ### Portal block discovery, move and delete (NIM-43062)
367
+
368
+ - `GET /:orgId/portals/:portalId/pages/:pageId/blocks` → `{ blocks }` of
369
+ `blockId`, `type`, `templateType`, `title`, `editOperation`, `place`,
370
+ `draftOnly`. The order is the page area (`place`) first, then the position
371
+ inside it — a block's index is only unique within its own area.
372
+ - `PATCH /:orgId/portals/:portalId/pages/:pageId/blocks/:blockId/position` →
373
+ the shared staged result `{ blockId, pageId, branchId, seqs, staged: true }`.
374
+ - `DELETE /:orgId/portals/:portalId/pages/:pageId/blocks/:blockId` → the same
375
+ staged result. No request body.
376
+ - All three require `portals.write` with the org taken from the path.
377
+ - No raw block index is accepted anywhere. The internal append sentinel `999`
378
+ stays internal: the target index is resolved server-side against the effective
379
+ state, so a concurrent draft change cannot land the block in the wrong slot.
380
+ - `editOperation` is `null` for every block today. It names the typed
381
+ `updatePortal*Block` operation for that block once those land; a `null` does
382
+ not mean the block cannot be moved or deleted.
383
+ - A move anchor must carry the same `place` as the block being moved; anything
384
+ else is a 400 whose message names the reason.
385
+ - Portals MCP prompt bumped to 1.70.0.
386
+
387
+ ### Read a portal block, and edit a note-backed one (NIM-43065)
388
+
389
+ - `GET /:orgId/portals/:portalId/pages/:pageId/blocks/:blockId` →
390
+ `blockId`, `kind`, `type`, `templateType`, `title`, `place`, `draftOnly`,
391
+ `editOperation`, `layout` (`colspan`, `rowspan`, `height`) and the
392
+ kind-specific `settings`. `kind` is the vocabulary the typed updates use;
393
+ neither `type` nor `templateType` identifies a block on its own, because every
394
+ `flexible` block shares one type and is told apart by its template.
395
+ `settings` is `null` for a kind with no typed update operation yet.
396
+ - `PATCH /:orgId/portals/:portalId/pages/:pageId/blocks/:blockId/note` →
397
+ the shared staged result `{ blockId, pageId, branchId, seqs, staged: true }`.
398
+ Body: `title`, `layout.colspan` (3–6) and `layout.height` (≥ 50, or `null` for
399
+ the automatic height). Every field is optional; an omitted one keeps its
400
+ current value and the add operation's defaults are not applied.
401
+ - Both require `portals.write` with the org taken from the path.
402
+ - An empty patch, and one the block already satisfies, are 400s — staging
403
+ nothing would answer 200 and read as "applied".
404
+ - The block's content is not editable here: the note's text, the Kanban board
405
+ and the task list belong to their own operations, and `noteId` / `workspaceId`
406
+ are the block's identity.
407
+ - Portals MCP prompt bumped to 1.71.0.
408
+
409
+ ### Portal page and folder permissions (NIM-43064)
410
+
411
+ - `POST /:orgId/portals/:portalId/content/permissions` →
412
+ `{ item, mode, permissions, users, affectedItemIds, published: true }`.
413
+ Requires `portals.manage` with the org taken from the path — the same scope as
414
+ `inviteToPortal`, because the operation can create a portal invitation.
415
+ - Request is `{ path, emails, mode }` only. `menuItemId`, `pageId`, `userId` and
416
+ raw permissions are not accepted: they are implementation details that would
417
+ make the call two-phase for no benefit.
418
+ - `listPortalContent` items now carry `path` — the full portal path
419
+ (`/docs/start`), which is the selector the new operation takes. `slug` is
420
+ unchanged for backwards compatibility. `path` is absent for menu items that
421
+ have no path of their own (external links).
422
+ - For a folder the ACL cascades to every descendant folder and page, matching
423
+ the Customizer. `affectedItemIds` lists the root plus everything the cascade
424
+ touched.
425
+ - `add` against an item that is already open to everyone is a successful no-op:
426
+ the users already have access, and silently turning a public page private is
427
+ not what "add a user" asks for.
428
+ - A path that exists only in the Customizer draft answers `409` naming
429
+ `publishPortalDraft`, instead of a bare "not found".
430
+ - A path matching more than one published item answers `409` rather than picking
431
+ one.
432
+ - Portals MCP prompt bumped to 1.74.0.
433
+
434
+ ### Edit portal Heading, Simple text and Image blocks (NIM-43065)
435
+
436
+ - `PATCH /:orgId/portals/:portalId/pages/:pageId/blocks/:blockId/heading`
437
+ - `PATCH /:orgId/portals/:portalId/pages/:pageId/blocks/:blockId/text`
438
+ - `PATCH /:orgId/portals/:portalId/pages/:pageId/blocks/:blockId/image`
439
+
440
+ All three return the shared staged result
441
+ `{ blockId, pageId, branchId, seqs, staged: true }` and require
442
+ `portals.write` with the org taken from the path.
443
+
444
+ - Body — `heading` and `text`: `content`, `appearance`, `layout`; `image`:
445
+ `layout` only. Every field is optional; an omitted one keeps its current
446
+ value and the add operation's defaults are not applied.
447
+ - `layout.colspan` is 2–6 (wider than the note family's 3–6) and
448
+ `layout.height` is ≥ 50, or `null` for the automatic height.
449
+ - `getPortalBlock`'s `settings` gains three members:
450
+ `{ kind: "heading" | "text", content, appearance }` and `{ kind: "image" }`.
451
+
452
+ ### Card group block update
453
+
454
+ - Added `updatePortalCardGroupBlock` to the HTTP API, SDK and MCP surface.
455
+ - `content.items` replaces the complete visual card list while preserving omitted stored image, destination and per-card theme values by position.
456
+
457
+ ### Edit portal Carousel blocks (NIM-43065)
458
+
459
+ - Added `updatePortalCarouselBlock`.
460
+ - `getPortalBlock` now returns Carousel `settings.content.items` and
461
+ `editOperation: updatePortalCarouselBlock`.
462
+ - `content.items` is a full replacement list of 1–6 slides; `layout` remains a
463
+ field-level patch.
464
+ - Regenerated OpenAPI, SDK and MCP skill artifacts include the new operation.
465
+
466
+ ### Custom widget block update
467
+
468
+ - Added `updatePortalCustomWidgetBlock` to the HTTP API, SDK and MCP surface.
469
+ - `content.bricks` is the full desired semantic brick list; raw ids, indexes, classes and image sources remain internal.
470
+
471
+ ### Dynamic Table block update
472
+
473
+ - Added `updatePortalDynamicTableBlock` to the HTTP API, generated SDK and MCP surface.
474
+ - The operation updates title, appearance and layout only.
475
+ - Integration provider, OAuth connection, data source, columns and rows remain unchanged.
476
+
477
+ ### Form block update
478
+
479
+ - Added `updatePortalFormBlock` to the HTTP API, SDK and MCP surface.
480
+ - Existing fields preserve exact `fieldId` values; new fields omit them.
481
+
482
+ ### Info block update
483
+
484
+ - Added `updatePortalInfoBlock` to the HTTP API, SDK and MCP surface.
485
+ - Images and destinations use semantic variants; persisted image and link encodings remain internal.
486
+
487
+ ### Linked block update
488
+
489
+ - Added `updatePortalLinkedBlock` to the HTTP API, generated SDK and MCP surface.
490
+ - The operation accepts only an exact source returned by Linked block discovery.
491
+
492
+ ### Recent files block update
493
+
494
+ - Added `updatePortalRecentFilesBlock` to the HTTP API, generated SDK and MCP surface.
495
+ - The operation updates title, same-portal source and layout only.
496
+ - Sort order, item limit, offset, file type, other query data and appearance remain unchanged.
497
+
498
+ ### Portal branding (NIM-42204)
499
+
500
+ - Added `updatePortalStyle({ orgId, portalId }, body)` — `POST /:orgId/portals/:portalId/style`.
501
+ - Body fields (all optional, patch semantics; at least one is required):
502
+ - `theme` — color theme key, the same 13 named themes `createPortal` accepts.
503
+ - `logo` — `{ storedFileUUID }` of an uploaded image, shown in the portal header.
504
+ - `favicon` — `{ storedFileUUID }` of an uploaded image, the browser tab icon.
505
+ - Omitted fields keep their current value. One call stages one `updateThemeSettings` event. Returns `{ branchId, seq, staged: true }`. Requires `portals.write` access.
506
+ - `getPortal` now returns `style` — `{ theme, logoUrl, faviconUrl }` — read from the published theme.
507
+
508
+
509
+ ## Consumer Impact
510
+
511
+ ### Portal homepage settings (NIM-42205)
512
+
513
+ Agents can name a portal's homepage and control sidebar visibility as part of portal generation. The op covers exactly the two field groups both "Homepage settings" customizer screens show (`settings-homepage-screen.tsx` and `page-main-screen.tsx` for the Home item) — nothing else.
514
+
515
+ Three field groups from the original ticket body are deliberately **not** exposed:
516
+
517
+ - `heading`, `showGreeting`, `showSearch` — no customizer control produces `UPDATE_HERO_SETTING`, and the published portal stops rendering these entirely once the owner turns on the block-based Hero header (`theme.hero.show`). A field that silently loses its effect is worse than an absent one. Same reasoning as the header-blocks toggle below.
518
+ - `headerBlocksEnabled` — stored by portal-service, but no portal component renders it.
519
+ - `hero.subheading`, `hero.backgroundImage` — no such field exists anywhere in the customizer.
520
+
521
+ Page content is out of scope: to add blocks to the homepage, resolve its `pageId` from `listPortalContent` (menu item `type: "home"`, already returned today) and call the normal semantic block operations, exactly as for any other page.
522
+
523
+ Sidebar toggles live in the portal theme, which portal-service replaces wholesale on every `updateThemeSettings`. The op therefore reads the effective theme — published settings plus any theme change already staged in the draft — and writes it back with only the requested keys changed.
524
+
525
+ **Depends on portal-service:** the sidebar `minimized` field. portal-service !217 is merged and deployed.
526
+
527
+ ### Read-only isolated SQL is enforced by Postgres
528
+
529
+ SQL that used to pass the read endpoint and now fails with `cannot execute … in a read-only transaction`:
530
+
531
+ - `SELECT nextval('seq')` / `setval(...)` — pre-allocate ids with `insertIsolatedStoreSqlRow ... returning` or generate them in app code
532
+ - `SELECT … FOR UPDATE` / `FOR SHARE` — the read path commits immediately, so these locks were already released before the response
533
+ - `SELECT … INTO new_table` and `EXPLAIN ANALYZE <INSERT|UPDATE|DELETE>` — these write; use `executeIsolatedStoreSql` or a migration bundle
534
+ - any `SELECT` over a function that writes — the point of the change
535
+
536
+ Write paths are unaffected: `executeIsolatedStoreSql`, the structured row APIs, migrations and mixed batches all still run read-write.
537
+
538
+ ### addPortalChatsDashboardMenuItem — add a Chat dashboard navigation item (NIM-43021)
539
+
540
+ None.
541
+
542
+ ### addPortalFilesDashboardMenuItem — add a Files dashboard navigation item (NIM-43020)
543
+
544
+ None for existing callers.
545
+
546
+ ### addPortalTasksDashboardMenuItem — add a Tasks dashboard navigation item (NIM-43022)
547
+
548
+ None for existing callers.
549
+
550
+ ### addPortalBlock removed from Fusebase Gate (NIM-43018)
551
+
552
+ A vendored-SDK consumer that calls `addPortalBlock` fails to compile after
553
+ updating the SDK (the method is gone) and gets a 404 if it calls the route
554
+ directly with an older client. Use a dedicated operation where one is available;
555
+ each dedicated op takes semantic fields instead of a hand-written `properties`
556
+ bag. Legacy link cards, raw `flexible` payloads and `hero-*` templates have no
557
+ replacement.
558
+
559
+ ### Card group block update
560
+
561
+ Consumers can update Card group title, description, cards, appearance and layout without raw bricks or links.
562
+
563
+ ### Edit portal Carousel blocks (NIM-43065)
564
+
565
+ Call `getPortalBlock` before editing, then send the complete desired slide list.
566
+ Built-in images and internal Carousel fields are preserved by the service and
567
+ must not be sent by consumers.
568
+
569
+ ### Custom widget block update
570
+
571
+ Consumers can update Custom widget title, bricks, appearance and layout.
572
+
573
+ ### Dynamic Table block update
574
+
575
+ Consumers can update Dynamic Table presentation without replacing its integration configuration.
576
+
577
+ ### Form block update
578
+
579
+ Consumers can update Form content and presentation without raw bricks or response data.
580
+
581
+ ### Info block update
582
+
583
+ Consumers can update Info title, image, destination, new-tab behavior and layout.
584
+
585
+ ### Linked block update
586
+
587
+ Consumers can repoint a Linked block while its presentation remains mirrored from the source.
588
+
589
+ ### Recent files block update
590
+
591
+ Consumers can edit Recent files presentation and switch between the entire portal and one exact portal page.
592
+
593
+ ### Portal branding (NIM-42204)
594
+
595
+ Agents can re-brand a portal as part of portal generation without opening the customizer: pick a theme, attach a logo, attach a favicon.
596
+
597
+ Two field groups from the original ticket body are deliberately **not** exposed, because the portal theme model has nowhere to store them:
598
+
599
+ - **Semantic hex colors** (`colors.primary`, `background`, `surface`, `text`, `accent`, mapped onto daisyUI `--p`/`--b1`/`--bc`/`--a`). Portals are not daisyUI-themed. Theme color slots hold arrays of **Tailwind palette tokens** (`'neutral-900'`) and render as Tailwind classes (`bg-neutral-900`), safelisted by palette name in `apps/portals/tailwind.config.js`. A raw `#2563eb` would render as the class `bg-#2563eb` and style nothing. This is the same conclusion NIM-42208 published: overriding daisyUI variables re-brands nothing, because only ~7 cosmetic widgets read them.
600
+ - **`font: { family, source }`.** There is no font field anywhere in `PortalServiceThemeDto`, no customizer control that writes one, and no renderer that reads one.
601
+
602
+ For anything beyond the named themes, `updatePortalCustomCode` is the supported path: it targets the `data-portal` structural hooks published by NIM-42208 (see reference `portal-theme-variables`), where hex colors and `@font-face` do work. That is CNAME-domain portals only.
603
+
604
+ Removing a logo or favicon is also not exposed — the customizer's remove button is the way back. Add a nullable form if agents turn out to need it.
605
+
606
+ Images are stored as `/box/file/{storedFileUUID}`, the same relative URL the customizer writes (`STATIC_ASSET_PREFIX` is `/box/file/` on both dev and prod), so it resolves on whatever domain serves the portal.
607
+
608
+ The theme is replaced wholesale by portal-service on every `updateThemeSettings`, so the op reads the effective theme — published settings plus any theme change already staged in the draft — and writes it back with only `key` / `logo` / `icon` changed, on both the light and dark schemas. Both schemas are written together, exactly as the customizer does, so branding does not shift when dark mode is re-enabled.
609
+
610
+ `getPortal` reads branding with a second portal-service call; if that call fails or the portal has no settings entry, `style` is simply omitted rather than failing the read.
611
+
612
+ **No portal-service change:** the op writes through the existing draft-append endpoint and reads through the existing `/portals/settings`.
613
+
614
+
615
+ ## Verification
616
+
617
+ ### Portal homepage settings (NIM-42205)
618
+
619
+ - `npm test -- --runTestsByPath tests/unit/portals-controller.test.ts`
620
+ - `npm run build`
621
+ - `FEATURE_FLAGS=isolated_sql_stores,isolated_nosql_stores npm run build:sdk`
622
+
623
+ ### Read-only isolated SQL is enforced by Postgres
624
+
625
+ - Unit tests: `BEGIN READ ONLY` on the read path and plain `BEGIN` on the write path, all-read vs. mixed batch, and the mixed-batch `query` permission
626
+ - Against a real Postgres 16: a `SELECT` calling a writing volatile function, the escape-string CTE bypass, and an all-read batch calling the same function are all rejected, while plain reads, the write path and mixed batches keep working. Each of the three was accepted (and did write) on the pre-change code.
627
+ - `npm test`, `npm run lint`, `npm run build`
628
+
629
+ ### addPortalChatsDashboardMenuItem — add a Chat dashboard navigation item (NIM-43021)
630
+
631
+ - `npm run test:unit` — including the reworked `portals-chat-dashboard-contract`
632
+ and `portals-chat-dashboard-seam` suites and the `mcp-prompts` pins for the new
633
+ guidance.
634
+ - `npm run build`, `npm run build:sdk`, `npm run mcp:skills:validate` — clean.
635
+ - portal-service: `addPortalChatsDashboardMenuItem.spec.ts` covering duplicate
636
+ items in the same and other bars, the tombstoned-page exception, the
637
+ missing-page path, the three bars, the root index, the retry and a reducer
638
+ round-trip of the staged event.
639
+
640
+ ### addPortalFilesDashboardMenuItem — add a Files dashboard navigation item (NIM-43020)
641
+
642
+ - `npm test` — unit tests including the new
643
+ `portals-files-dashboard-contract` and `portals-files-dashboard-seam` suites
644
+ and the `mcp-prompts` pins.
645
+ - `npm run build`, `npm run build:sdk`, `npm run mcp:skills:generate` and
646
+ `npm run mcp:skills:validate`.
647
+ - portal-service: `addPortalFilesDashboardMenuItem` use-case, reducer round-trip
648
+ and swagger-contract specs.
649
+
650
+ ### addPortalTasksDashboardMenuItem — add a Tasks dashboard navigation item (NIM-43022)
651
+
652
+ - `npm test` — unit tests including the new
653
+ `portals-tasks-dashboard-contract` and `portals-tasks-dashboard-seam` suites
654
+ and the `mcp-prompts` pins.
655
+ - `npm run build`, `npm run build:sdk`, `npm run mcp:skills:generate` and
656
+ `npm run mcp:skills:validate`.
657
+ - portal-service: `addPortalTasksDashboardMenuItem` use-case, reducer round-trip
658
+ and swagger-contract specs.
659
+
660
+ ### addPortalBlock removed from Fusebase Gate (NIM-43018)
661
+
662
+ - `npm test` — 1018 unit tests green. The `PortalsController.addPortalBlock`
663
+ suite and the 21 sibling "generic op rejects X" suites go with the op (28 test
664
+ files touched, 22 describe blocks removed); the pins the removed prompt lines
665
+ carried are dropped from `mcp-prompts` and the sibling contract specs.
666
+ - `npm run build`, `npm run lint`, `npm run mcp:skills:validate`.
667
+ - `npm run build:sdk` — OpenAPI operation, SDK method, manifest entry and types
668
+ dropped.
669
+
670
+ ### addPortalChatsDashboardMenuItem — duplicate items allowed (NIM-43021)
671
+
672
+ - `npm run test:unit`, `npm run build`, `npm run build:sdk`,
673
+ `npm run mcp:skills:validate` — clean.
674
+ - portal-service: `addPortalChatsDashboardMenuItem.spec.ts` covers a second item
675
+ in the same bar, an item in another bar and a concurrent duplicate during the
676
+ retry.
677
+
678
+ ### Portal block discovery, move and delete (NIM-43062)
679
+
680
+ - `npm run test:unit` (1090 passing), `npm run build`, `npm run lint`,
681
+ `npm run build:sdk`, `npm run mcp:skills:generate`,
682
+ `npm run mcp:skills:validate` — clean.
683
+ - portal-service `portalBlockOperation.spec.ts`: 24 cases covering every move
684
+ position, self/foreign/other-area anchors, a draft-only block, the
685
+ not-removable guard, a block already staged for deletion, and the single
686
+ draft-conflict retry re-deriving the index from fresh state.
687
+
688
+ ### Read a portal block, and edit a note-backed one (NIM-43065)
689
+
690
+ - `npm run test:unit` (1138 passing), `npm run build`, `npm run lint` (0
691
+ errors), `npm run build:sdk`, `npm run mcp:skills:validate` — clean.
692
+ - portal-service `portalBlockUpdate.spec.ts`: 15 cases covering the kind map,
693
+ the note settings, `editOperation`, both draft events, the explicit-null
694
+ height, the wrong-kind guard, every validation bound and the draft-conflict
695
+ retry.
696
+ - Both guards were probed rather than trusted: reverting the full-DTO merge to a
697
+ changed-fields-only payload reds exactly the "stages the full current block"
698
+ case, and cutting the update-semantics line out of the prompt reds exactly the
699
+ new prompt pin.
700
+
701
+ ### Portal page and folder permissions (NIM-43064)
702
+
703
+ - portal-service `updatePortalContentPermissions.spec.ts`: 22 cases covering
704
+ nested-path resolution, trailing-slash and absolute-URL handling, the page and
705
+ its backing node, the folder cascade running once per item, `replace` dropping
706
+ the old lists, `add` preserving them and lifting the user out of `rejectUserIds`,
707
+ the public-item no-op, a system item, a foreign org, a missing path, an
708
+ ambiguous path, the permissions-service sync payload, a permissions-service
709
+ failure not being reported as success, the `mode` and empty-`userIds` guards,
710
+ `affectedItemIds` skipping items that were already identical, and `dryRun`
711
+ validating without writing.
712
+ - fusebase-gate `portal-content-permissions.spec.ts`: 25 cases covering email
713
+ normalization, deduplication and the 50 limit, an existing workspace member not
714
+ being re-invited, a new email getting a client/editor invite without full portal
715
+ access, every email resolved before the ACL write, the downstream 403/404/409
716
+ mapping, the dry run running before the first invitation, a `client` caller
717
+ refused by the invite policy, and one member-list fetch per batch.
718
+
719
+ ### Card group block update
720
+
721
+ - Portal-service typecheck, source lint and focused tests passed: 3 suites, 26 tests.
722
+ - Gate build, lint and focused tests passed: 3 suites, 135 tests.
723
+ - SDK and MCP artifacts were generated; MCP fallback validation passed.
724
+
725
+ ### Edit portal Carousel blocks (NIM-43065)
726
+
727
+ - Portal-service TypeScript and 36 focused block/update/schema/route tests pass.
728
+ The broader serial suite exceeded the 4 GB Node heap after its earlier tests
729
+ passed; its one stale Carousel expectation was updated and re-run successfully.
730
+ - Gate build, lint, SDK generation, MCP skill validation and 113 focused unit
731
+ tests pass. The full suite reached 1182 passing tests; only the existing
732
+ framework-link check failed because installed platform packages are 1.7.1
733
+ while package.json pins 1.7.3.
734
+
735
+ ### Custom widget block update
736
+
737
+ - Portal-service typecheck, source lint and focused tests passed: 3 suites, 25 tests.
738
+ - Gate build, lint and focused tests passed: 3 suites, 143 tests.
739
+ - SDK and MCP artifacts were generated; MCP fallback validation passed.
740
+
741
+ ### Dynamic Table block update
742
+
743
+ - Portal-service typecheck and focused tests passed: 5 suites, 41 tests.
744
+ - Gate build and focused tests passed: 3 suites, 122 tests.
745
+ - Gate lint passed with 12 existing warnings.
746
+ - SDK and MCP artifacts were generated; MCP fallback validation passed.
747
+ - Full Gate validation remains blocked by the existing platform version mismatch (installed 1.7.1, package requires 1.7.3).
748
+
749
+ ### Form block update
750
+
751
+ - Portal-service typecheck and focused tests passed: 3 suites, 25 tests.
752
+ - Gate build and focused tests passed: 3 suites, 131 tests.
753
+ - SDK and MCP artifacts were generated; MCP fallback validation passed.
754
+ - Full Gate validation remains blocked by the existing platform version mismatch (installed 1.7.1, package requires 1.7.3).
755
+
756
+ ### Info block update
757
+
758
+ - Portal-service typecheck, source lint and focused tests passed: 3 suites, 26 tests.
759
+ - Gate build, lint and focused tests passed: 3 suites, 139 tests.
760
+ - SDK and MCP artifacts were generated; MCP fallback validation passed.
761
+
762
+ ### Linked block update
763
+
764
+ - Portal-service typecheck and focused tests passed: 4 suites, 48 tests.
765
+ - Gate build and focused tests passed: 3 suites, 128 tests.
766
+ - Gate lint passed with 12 existing warnings.
767
+ - SDK and MCP artifacts were generated; MCP fallback validation passed.
768
+ - Full Gate validation remains blocked by the existing platform version mismatch (installed 1.7.1, package requires 1.7.3).
769
+
770
+ ### Recent files block update
771
+
772
+ - Portal-service typecheck and focused tests passed: 3 suites, 26 tests.
773
+ - Gate build and focused tests passed: 3 suites, 125 tests.
774
+ - Gate lint passed with 12 existing warnings.
775
+ - SDK and MCP artifacts were generated; MCP fallback validation passed.
776
+ - Full Gate validation remains blocked by the existing platform version mismatch (installed 1.7.1, package requires 1.7.3).
777
+
778
+ ### Portal branding (NIM-42204)
779
+
780
+ - `npm test -- --runTestsByPath tests/unit/portals-controller.test.ts tests/unit/mcp-prompts.test.ts`
781
+ - `npm run build`
782
+ - `FEATURE_FLAGS=isolated_sql_stores,isolated_nosql_stores npm run build:sdk`
783
+
784
+
785
+ ## Follow-ups
786
+
787
+ ### addPortalFilesDashboardMenuItem — add a Files dashboard navigation item (NIM-43020)
788
+
789
+ Older portals created by `filesDashboardMigration` keep the migration-era block
790
+ defaults; unifying them is a separate migration.
791
+
792
+ ### addPortalTasksDashboardMenuItem — add a Tasks dashboard navigation item (NIM-43022)
793
+
794
+ Older portals created by `tasksDashboardMigration` keep the migration-era block
795
+ defaults (`rowspan: 1`, `isOrigin`); unifying them is a separate migration.
796
+
797
+ ### addPortalBlock removed from Fusebase Gate (NIM-43018)
798
+
799
+ None within NIM-43018. The legacy link cards and raw `flexible` support are
800
+ intentionally gone. Replacement operations for `content` and the three system
801
+ dashboards are tracked by their own tasks (see Breaking).
802
+
803
+ ### Card group block update
804
+
805
+ None.
806
+
807
+ ### Edit portal Carousel blocks (NIM-43065)
808
+
809
+ Implement the remaining 15 typed portal block update families one at a time.
810
+
811
+ ### Custom widget block update
812
+
813
+ None.
814
+
815
+ ### Dynamic Table block update
816
+
817
+ None.
818
+
819
+ ### Form block update
820
+
821
+ None.
822
+
823
+ ### Info block update
824
+
825
+ None.
826
+
827
+ ### Linked block update
828
+
829
+ None.
830
+
831
+ ### Recent files block update
832
+
833
+ None.
834
+
835
+
836
+ ## Breaking
837
+
838
+ ### addPortalChatsDashboardMenuItem — add a Chat dashboard navigation item (NIM-43021)
839
+
840
+ None. `createPortalChatDashboardPage` never reached gate's contracts, SDK or
841
+ OpenAPI — only portal-service's internal `POST /portals/:id/pages/chat-dashboard`
842
+ route existed, and the paired MR removes it. It staged a second dashboard page on
843
+ the singular `/chat-dashboard` route, while the system page every portal actually
844
+ has is the plural `/chats-dashboard`.
845
+
846
+ ### addPortalFilesDashboardMenuItem — add a Files dashboard navigation item (NIM-43020)
847
+
848
+ None. `createPortalFileDashboard` never reached `main`.
849
+
850
+ ### addPortalTasksDashboardMenuItem — add a Tasks dashboard navigation item (NIM-43022)
851
+
852
+ None. The operation is additive.
853
+
854
+ ### addPortalBlock removed from Fusebase Gate (NIM-43018)
855
+
856
+ Any caller of `addPortalBlock` must stop using it and, where available, move to
857
+ the dedicated operation for the tile it wants. There is no deprecation window
858
+ and no server-side redirect.
859
+
860
+ The following generic paths are **dropped without replacement**, on purpose:
861
+
862
+ - the legacy link cards `docs`, `help`, `request`, `meetings`, `links`;
863
+ - raw `flexible` payloads and the `hero-*` templates.
864
+
865
+ Apps remain supported through `addPortalAppPage` for a new sidebar page and
866
+ `addPortalAppBlock` for an existing page; callers must not construct the
867
+ underlying `app-feature` template themselves.
868
+
869
+ The remaining blocks are **handled separately**:
870
+
871
+ - `content` — NIM-43019;
872
+ - `files-dashboard` — NIM-43020;
873
+ - `tasks-dashboard` — NIM-43022;
874
+ - `chats-dashboard` — NIM-43021.
875
+
876
+ This change does not implement those replacements; their availability depends
877
+ on the separate tasks shipping.
878
+
879
+ ### addPortalChatsDashboardMenuItem — duplicate items allowed (NIM-43021)
880
+
881
+ None for callers that treat 409 by `reason`: the removed reason was a rejection,
882
+ and those requests now succeed with a 201.
883
+
884
+ ### Portal block discovery, move and delete (NIM-43062)
885
+
886
+ None — three additive operations.
887
+
888
+ ### Read a portal block, and edit a note-backed one (NIM-43065)
889
+
890
+ None — two additive operations. `editOperation` was already in the
891
+ `listPortalPageBlocks` response; it now carries a value for note-backed blocks
892
+ instead of always `null`.
893
+
894
+ ### Portal page and folder permissions (NIM-43064)
895
+
896
+ None — one additive operation plus an additive `path` field on
897
+ `listPortalContent`.
898
+
899
+
900
+ ## Note for the next regeneration
901
+
902
+ ### Portal block discovery, move and delete (NIM-43062)
903
+
904
+ `npm run build:sdk` must run with
905
+ `FEATURE_FLAGS=isolated_sql_stores,isolated_nosql_stores`. Without it the
906
+ isolated-store ops are not registered, the generator silently drops that whole
907
+ SDK domain and the run then dies in `tsc` before writing the OpenAPI spec. This
908
+ is pre-existing on `main`, not introduced here.
909
+
910
+ ### Read a portal block, and edit a note-backed one (NIM-43065)
911
+
912
+ `npm run build:sdk` must run with
913
+ `FEATURE_FLAGS=isolated_sql_stores,isolated_nosql_stores`, as recorded on the
914
+ NIM-43062 note.
915
+
916
+ ### Portal page and folder permissions (NIM-43064)
917
+
918
+ `npm run build:sdk` must run with
919
+ `FEATURE_FLAGS=isolated_sql_stores,isolated_nosql_stores` (see
920
+ `2026-08-06-portal-block-discovery-move-delete.md`).
921
+
922
+
923
+ ## Notes
924
+
925
+ ### Edit portal Heading, Simple text and Image blocks (NIM-43065)
926
+
927
+ **`content` is a field-level patch; `appearance` is not.** A size, style or
928
+ alignment left out of `content` keeps what the block has today. `appearance` is
929
+ a discriminated variant and has to be sent complete — read the current one from
930
+ `getPortalBlock` first, or the fields left out revert to the variant's defaults.
931
+
932
+ **A `custom` backdrop keeps its background when the patch omits one.** A block
933
+ the Customizer left on the portal theme's surface colour has no background to
934
+ report, so `getPortalBlock` omits it and sending that appearance back is legal.
935
+
936
+ **An empty patch, and one the block already satisfies, are 400s** — staging
937
+ nothing would answer 200 and read as "applied".
938
+
939
+
940
+ ## Still to come
941
+
942
+ ### Read a portal block, and edit a note-backed one (NIM-43065)
943
+
944
+ The other 22 typed `updatePortal…Block` operations from the
945
+ `NIM-40621-edit-blocks.md` spec, and their `getPortalBlock` settings members.
946
+ The engine seam they plug into (`updatePortalBlock` in portal-service) ships
947
+ here.