@object-ui/app-shell 7.0.0 → 7.1.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.
Files changed (73) hide show
  1. package/CHANGELOG.md +281 -0
  2. package/dist/console/AppContent.js +14 -2
  3. package/dist/console/ai/AiChatPage.js +11 -7
  4. package/dist/console/ai/LiveCanvas.d.ts +8 -2
  5. package/dist/console/ai/LiveCanvas.js +6 -4
  6. package/dist/hooks/useChatConversation.d.ts +30 -0
  7. package/dist/hooks/useChatConversation.js +63 -0
  8. package/dist/hooks/useConsoleActionRuntime.js +6 -2
  9. package/dist/index.d.ts +2 -1
  10. package/dist/index.js +5 -1
  11. package/dist/layout/ConsoleFloatingChatbot.d.ts +6 -4
  12. package/dist/layout/ConsoleFloatingChatbot.js +25 -8
  13. package/dist/layout/ContextSelectors.js +59 -35
  14. package/dist/layout/agentPicker.d.ts +56 -0
  15. package/dist/layout/agentPicker.js +40 -0
  16. package/dist/preview/CommitTimeline.d.ts +15 -0
  17. package/dist/preview/CommitTimeline.js +82 -0
  18. package/dist/preview/UnpublishedAppBar.js +11 -7
  19. package/dist/preview/commitHistory.d.ts +28 -0
  20. package/dist/preview/commitHistory.js +48 -0
  21. package/dist/providers/MetadataProvider.js +9 -0
  22. package/dist/views/FlowRunner.d.ts +2 -30
  23. package/dist/views/FlowRunner.js +18 -50
  24. package/dist/views/ScreenView.d.ts +70 -0
  25. package/dist/views/ScreenView.js +73 -0
  26. package/dist/views/metadata-admin/DirectoryPage.js +2 -14
  27. package/dist/views/metadata-admin/JsonSourceEditor.d.ts +3 -1
  28. package/dist/views/metadata-admin/JsonSourceEditor.js +21 -3
  29. package/dist/views/metadata-admin/PackagesPage.js +9 -1
  30. package/dist/views/metadata-admin/ResourceEditPage.js +47 -20
  31. package/dist/views/metadata-admin/ResourceListPage.js +8 -16
  32. package/dist/views/metadata-admin/StudioHomePage.js +6 -14
  33. package/dist/views/metadata-admin/anchors.js +20 -2
  34. package/dist/views/metadata-admin/i18n.js +88 -2
  35. package/dist/views/metadata-admin/inspectors/DatasetDefaultInspector.d.ts +2 -2
  36. package/dist/views/metadata-admin/inspectors/DatasetDefaultInspector.js +122 -8
  37. package/dist/views/metadata-admin/inspectors/FlowEdgeInspector.js +84 -3
  38. package/dist/views/metadata-admin/inspectors/FlowNodeInspector.js +67 -2
  39. package/dist/views/metadata-admin/inspectors/ObjectDefaultInspector.js +5 -4
  40. package/dist/views/metadata-admin/inspectors/ObjectFieldInspector.js +47 -12
  41. package/dist/views/metadata-admin/inspectors/ReportDefaultInspector.d.ts +1 -1
  42. package/dist/views/metadata-admin/inspectors/ReportDefaultInspector.js +60 -2
  43. package/dist/views/metadata-admin/inspectors/_shared.d.ts +5 -1
  44. package/dist/views/metadata-admin/inspectors/_shared.js +2 -2
  45. package/dist/views/metadata-admin/inspectors/datasetFilterCondition.d.ts +24 -0
  46. package/dist/views/metadata-admin/inspectors/datasetFilterCondition.js +97 -0
  47. package/dist/views/metadata-admin/inspectors/flow-node-config.js +46 -1
  48. package/dist/views/metadata-admin/issuePath.d.ts +22 -0
  49. package/dist/views/metadata-admin/issuePath.js +65 -0
  50. package/dist/views/metadata-admin/package-scope.d.ts +26 -0
  51. package/dist/views/metadata-admin/package-scope.js +43 -0
  52. package/dist/views/metadata-admin/previews/DatasetPreview.js +21 -5
  53. package/dist/views/metadata-admin/previews/FlowCanvas.d.ts +7 -1
  54. package/dist/views/metadata-admin/previews/FlowCanvas.js +104 -16
  55. package/dist/views/metadata-admin/previews/FlowPreview.js +31 -3
  56. package/dist/views/metadata-admin/previews/FlowSimulatorPanel.js +37 -3
  57. package/dist/views/metadata-admin/previews/PagePreview.js +112 -3
  58. package/dist/views/metadata-admin/previews/ScreenPreview.d.ts +38 -0
  59. package/dist/views/metadata-admin/previews/ScreenPreview.js +61 -0
  60. package/dist/views/metadata-admin/previews/flow-canvas-layout.d.ts +14 -0
  61. package/dist/views/metadata-admin/previews/flow-canvas-layout.js +37 -0
  62. package/dist/views/metadata-admin/previews/flow-canvas-parts.d.ts +9 -1
  63. package/dist/views/metadata-admin/previews/flow-canvas-parts.js +21 -6
  64. package/dist/views/metadata-admin/previews/object-fields-io.d.ts +21 -0
  65. package/dist/views/metadata-admin/previews/object-fields-io.js +37 -2
  66. package/dist/views/metadata-admin/previews/screen-spec.d.ts +43 -0
  67. package/dist/views/metadata-admin/previews/screen-spec.js +108 -0
  68. package/dist/views/metadata-admin/previews/simulator/flow-sim-types.d.ts +11 -0
  69. package/dist/views/metadata-admin/previews/simulator/flow-sim-validate.d.ts +7 -0
  70. package/dist/views/metadata-admin/previews/simulator/flow-sim-validate.js +72 -0
  71. package/dist/views/metadata-admin/previews/simulator/flow-simulator.d.ts +32 -3
  72. package/dist/views/metadata-admin/previews/simulator/flow-simulator.js +119 -9
  73. package/package.json +38 -38
package/CHANGELOG.md CHANGED
@@ -1,5 +1,286 @@
1
1
  # @object-ui/app-shell — Changelog
2
2
 
3
+ ## 7.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 7b5d0f0: Build-history timeline + revert UI for AI builds (ADR-0067)
8
+
9
+ The unpublished-app banner gains a **History** button that opens a commit timeline (`GET /packages/:id/commits`): every change an AI build/edit landed, newest-first, with **Revert** per apply commit (`POST /packages/:id/commits/:cid/revert`). The history-not-confirm model — review the timeline and revert, instead of approving each publish.
10
+
11
+ - `commitHistory.ts` — `fetchCommits` / `revertCommit` helpers.
12
+ - `CommitTimeline.tsx` — slide-over panel (sibling of `DraftChangesPanel`).
13
+ - `UnpublishedAppBar` — History button + timeline mount (package-scoped).
14
+
15
+ - 7cd950e: feat(metadata-admin): dataset create opens the rich designer + dual-axis preview
16
+
17
+ - **Create → rich designer.** `dataset` joins `object` / `report` in
18
+ `CREATE_MODE_CANVAS_TYPES`, so "New dataset" opens the structured designer
19
+ (base-object picker, joins, dimension/measure editors, live preview) instead
20
+ of the degraded generic SchemaForm. `DatasetDefaultInspector` gains a
21
+ create-mode **Name** field that auto-derives a snake_case identifier from the
22
+ label until edited (mirrors `ReportDefaultInspector` / `ObjectDefaultInspector`),
23
+ so a dataset created through the canvas saves with a valid identity instead of
24
+ dead-ending.
25
+ - **Mixed-scale preview.** When a dataset preview mixes a ratio/percent measure
26
+ (e.g. `utilization`, `0.0%`) with magnitude measures (currency in the
27
+ hundred-thousands), the ratio measures now plot as a line on a secondary
28
+ (right) Y axis via the existing `combo` chart — they're no longer crushed to an
29
+ invisible sliver beside the large bars. Same-scale selections stay a plain bar
30
+ chart.
31
+
32
+ - fccebfe: feat(metadata-admin): visual filter authoring in the dataset designer
33
+
34
+ The dataset designer gains a visual filter editor (reusing the shared
35
+ `FilterBuilder`) for both the dataset-level **Scope filter** (`dataset.filter`)
36
+ and per-measure **Filter** (`measure.filter`) — previously only settable via the
37
+ raw Source/JSON tab. Both are backed by real runtime: the analytics executor ANDs
38
+ the scope filter into every query and runs measure-scoped filters as supplementary
39
+ grouped queries, so e.g. `won_amount = sum(amount) where stage = won` and an
40
+ "exclude archived" dataset scope are now authorable without hand-writing JSON.
41
+
42
+ A small, unit-tested converter bridges the builder's flat `{field, op, value}`
43
+ group ⇄ the spec `FilterCondition` (Mongo-style `$and` / `$op`). Conditions it
44
+ can't faithfully round-trip (nested groups, `$or`, multi-operator objects) are
45
+ detected and shown as "edit in Source" rather than being silently rewritten.
46
+
47
+ - 0acf0c8: feat(metadata-admin): friendlier + safer dataset measure authoring
48
+
49
+ The `dataset` designer's measure editor gets three improvements so a business
50
+ user can author measures without spec knowledge and without saving a broken
51
+ dataset:
52
+
53
+ - **Display-format picker** — replaces the raw `format` / `currency` numeral
54
+ text inputs with a structured Kind (Raw / Number / Currency / Percent) +
55
+ Decimals + Currency selection and a live sample (e.g. `US$1,234.50`). Parses
56
+ an existing format string back into the picker, so editing an existing measure
57
+ round-trips.
58
+ - **Auto-name from field** — picking a dimension/measure field when the row is
59
+ still unnamed defaults the name to the field's leaf (`account.region` →
60
+ `region`).
61
+ - **Author-time validation** — a `relationship.field` dimension/measure whose
62
+ relationship isn't in `include` now shows an inline warning with a one-click
63
+ "Add it", catching at design time the "relationship not declared in include"
64
+ error that previously only surfaced when the live preview query ran. A derived
65
+ measure with too few operands is flagged too.
66
+
67
+ - 3e1fcf5: feat(chatbot): reveal the Build/Ask switcher in the app floating assistant when AI dev is unlocked
68
+
69
+ The bottom-right FAB assistant bound each app to a single agent and hid the
70
+ agent picker unless `VITE_AI_SHOW_AGENT_PICKER` was set, so a user on an
71
+ AI-unlocked environment could not switch from `ask` (read-only data/query) to
72
+ `build` (authoring) without leaving for the full `/ai` page.
73
+
74
+ The picker now auto-reveals when AI development is unlocked for the viewer — the
75
+ live agent catalog serves BOTH an `ask` and a `build` agent (alias-aware, so
76
+ legacy `data_chat`/`metadata_assistant` count) AND authoring isn't
77
+ deployment-disabled (`aiStudio`). Pure end-user apps (only `ask`) stay clean and
78
+ never see a picker. An explicit `showAgentPicker` prop or
79
+ `VITE_AI_SHOW_AGENT_PICKER` still forces it on.
80
+
81
+ - e2b0072: Flow builder: live preview for Screen nodes (#1944)
82
+
83
+ Screen-flow nodes were authored blind — there was no way to see the form an end user would get, and the Debug simulator showed only `paused` when it reached a screen. Add a live preview that renders the screen exactly as it runs.
84
+
85
+ The runtime `FlowRunner`'s screen body (flat input fields + object-form mode) is extracted into a shared `ScreenView`, so the preview reuses the **same** renderer as runtime and can't drift (the design↔runtime divergence #1927 fixed). A new `ScreenPreview` builds a `ScreenSpec` from the node's authored `config` and feeds it to `ScreenView`.
86
+
87
+ - Reflects `title`, `description` (with `{var}` interpolation), input `fields`, and object-form mode (`objectName` / `mode` / `defaults`, rendered via `plugin-form`'s `ObjectForm`).
88
+ - Updates live as the node config changes.
89
+ - Two homes: the **flow node inspector** (interpolates against the flow's declared variable defaults) and the **Debug simulator** when paused at a screen (interpolates against the live simulated run state, replacing the bare `paused`).
90
+
91
+ - 780cabc: feat(studio): add a "Local / Custom (this env)" scope to the package selector
92
+
93
+ In a self-hosted, metadata-customizable environment (single-tenant — no org
94
+ dimension), the package selector only listed code packages, so metadata authored
95
+ at runtime (`package_id = null` / `sys_metadata` provenance) was filtered out of
96
+ every code-package view and became un-navigable — opening such an item redirected
97
+ to "new". This complements framework #2252 + objectui #1937, which stop runtime
98
+ metadata from being stamped into a loaded code package and keep it editable.
99
+
100
+ - Surface a stable, always-present "Local / Custom (this env)" entry in the
101
+ Studio package context-selector (`ContextSelectors`), mapped to the
102
+ `sys_metadata` scope the metadata list/get API already understands.
103
+ - Accept that scope in the metadata-admin pages (`StudioHomePage`,
104
+ `DirectoryPage`, `ResourceListPage`) via a shared `buildPackageScopeOptions`
105
+ helper, so it no longer redirects, and the list shows this environment's
106
+ runtime-authored items (`package_id = null`).
107
+ - On the Studio home grid, the Local scope shows every runtime-creatable type so
108
+ the user can start authoring locally even with zero items yet.
109
+
110
+ - 93cf2b1: feat(studio): preview record pages against a real sample record
111
+
112
+ The Studio page editor's Preview tab rendered a `type: 'record'` page's
113
+ `record:*` blocks (details / highlights / path / alert / quick_actions) as the
114
+ "bind a record to preview" placeholder — the metadata editor has no record
115
+ route, so the author designed blind.
116
+
117
+ The preview now fetches a handful of real records of the bound object (with
118
+ lookup / master_detail fields `$expand`ed so they show display names, not raw
119
+ foreign-key IDs), auto-binds the first one, and wraps the canvas in a
120
+ `<RecordContextProvider>` — mirroring the runtime `RecordDetailView`. A
121
+ "Preview record" dropdown lets the author switch records, so `visible` CEL
122
+ expressions (e.g. `record.status == 'in_review'`) and per-record field values
123
+ re-render live.
124
+
125
+ ### Patch Changes
126
+
127
+ - 68d82ae: New script action seeds a valid body; add create-roundtrip conformance guard
128
+
129
+ A new action defaults to `type: 'script'`, which the spec requires to carry an executable `body` or `target` — the create form seeded neither, so "New action → Save" failed validation (422). Seed a no-op L2 body in `createDefaults` so the default create round-trips. Adds a conformance guard that asserts every authorable type's default create-form output passes spec validation (catches the "designer minimal shape ≠ spec required" family before it ships).
130
+
131
+ - aae8791: Flow Screen preview: render inline master-detail subforms (follow-up to #1944)
132
+
133
+ The object-form mode of the Screen-node preview now renders inline master-detail
134
+ child grids, matching runtime. `ScreenPreview` feeds the SAME enriched object
135
+ list the runtime `FlowRunner` uses (`useMetadata().objects`, which derives
136
+ `form.subforms` from `inlineEdit` relationships via `attachInlineSubforms`), so
137
+ e.g. a `showcase_invoice` object-form step previews its **Line Items** grid
138
+ (with live Subtotal/Tax/Total) — only fetched in object-form mode.
139
+
140
+ To keep the preview non-persisting — consistent with the flat-field preview
141
+ (disabled Submit) and the simple object-form preview (no Save) — `MasterDetailForm`
142
+ now honours a `showSubmit` flag (default shown; backward-compatible) that
143
+ `ObjectForm` forwards, so the preview hides the master-detail Save bar. Also drops
144
+ a dead `e = formData` assignment in `ObjectForm` (lint `no-useless-assignment`).
145
+
146
+ - 4014bc9: Flow Screen preview: gate fields by `visibleWhen` (follow-up to #1944)
147
+
148
+ The Screen-node preview now evaluates each input field's `visibleWhen` against
149
+ the active variables — reusing the simulator's own condition evaluator
150
+ (`evalCondition`), normalising `{var}` placeholders to bare identifiers — so it
151
+ hides/shows conditional fields exactly as the runtime `screen` executor does
152
+ (which filters server-side before emitting the `ScreenSpec`).
153
+
154
+ - Debug simulator (live run state): gates faithfully, e.g. a screen whose
155
+ `opportunityName`/`opportunityAmount` are `visibleWhen: "{createOpportunity} == true"`
156
+ hides them while `createOpportunity` is false.
157
+ - Inspector (no run state): fails open — an unparseable or not-yet-decidable
158
+ condition keeps the field visible, so configured fields are never hidden on
159
+ missing data — and a footnote reports how many fields are gated out.
160
+
161
+ - d27f045: fix(metadata-admin): remove the unwired "Certified" measure toggle from the dataset designer
162
+
163
+ `measure.certified` is dead in the spec liveness ledger (declared but read by
164
+ nothing — no certifier authority, no provenance, not surfaced at point-of-use).
165
+ A self-asserted checkbox the dataset author flips on their own work isn't
166
+ certification — it's a fake trust signal. Drop the toggle (and the create
167
+ default) until real metric governance exists (separate `dataset.certify`
168
+ authority + `certifiedBy`/`certifiedAt` + a badge where reports pick measures).
169
+ The spec field stays (dormant, liveness=dead) so existing data is untouched.
170
+
171
+ - d23ed60: feat(studio): author the approval revise loop in the flow designer (ADR-0044)
172
+
173
+ The ADR-0044 send-back-for-revision loop — an approval node's `revise` out-edge to a wait point, closed by a declared `type: 'back'` edge re-entering the approval (round N+1) — was previously reachable only by hand-editing flow JSON. The flow designer now authors it visually:
174
+
175
+ - **Revise branch** — an approval out-edge offers `approve` / `reject` / `revise` via a new Approval-branch picker in the edge inspector; `maxRevisions` surfaces on the approval node's property form (from the engine's published configSchema when online, with a hardcoded fallback offline).
176
+ - **Back-edge authoring** — a new Connection-type select marks an edge as `back` (also `fault` / `conditional`). A back-edge renders distinctly on the canvas as a dashed amber return arc and is excluded from the layered auto-layout (exactly as the engine excludes it from DAG validation), so the loop reads top-to-bottom instead of dragging its target node below the wait point.
177
+ - **Client-side DAG validation** — the simulator's preflight now flags an UNmarked cycle as an error (the graph minus declared back-edges must be a DAG, mirroring `registerFlow`), while a declared revise loop passes and a self-loop is caught.
178
+ - **One-click "add revision loop"** — an amber affordance on an approval node drops the signal `wait` node + the `revise` edge + the declared `back` edge in a single gesture, reproducing the canonical `showcase_budget_approval` shape.
179
+
180
+ Refs framework#1770. Follows the flow-builder work in #1927 and #1930.
181
+
182
+ - 47c6e25: fix(studio/flow): wire decision branches to edges, expand screen config, align simulator with engine
183
+
184
+ Four fixes for the Studio Flow Builder, found dogfooding it as a business user:
185
+
186
+ - **Decision branches now route.** The "Branches" editor wrote `node.config.conditions`
187
+ but never the outgoing edges, so a decision built entirely in Studio left every
188
+ out-edge unconditional — the engine and simulator (which branch on `edge.condition`)
189
+ ran _all_ branches. Branches now mirror onto the node's out-edges (by order):
190
+ `FlowCanvas.addNode` carries the matching branch onto a newly-connected edge, and
191
+ `FlowNodeInspector` re-syncs existing edges when branches are edited (a `true`
192
+ expression marks the default/else edge).
193
+ - **Screen node config expanded.** The form exposed only `fields`; it now also edits
194
+ `title`, `description` (interpolates `{var}`), `waitForInput`, and the object-form
195
+ keys (`objectName`, `idVariable`, `mode`, `defaults`) — so a message screen or an
196
+ object-form wizard step no longer requires dropping to Advanced JSON.
197
+ - **Simulator applies assignment nodes.** Assignment was a no-op pass-through, so a
198
+ Debug run never reflected `Set variables`. It now normalizes the same shapes the
199
+ engine accepts (`assignments` map/array + flat) and interpolates `{var}`.
200
+ - **Simulator screen-pause parity.** The simulator paused on every screen; it now
201
+ pauses only when the screen collects input (`fields`) or sets `waitForInput`,
202
+ matching the engine's `shouldPause` — a field-less screen passes through.
203
+ - **Palette HTTP de-duplicated.** The base palette hardcoded the deprecated
204
+ `http_request` alias while the engine publishes the canonical `http`, showing
205
+ two HTTP entries. The base now uses `http` (merging into one), aliased to the
206
+ `http_request` config form so the inspector is unchanged.
207
+
208
+ - 4c2f910: feat(studio): surface flow validation errors inline on the canvas
209
+
210
+ The flow designer's structural validation (an un-declared cycle, missing entry node, duplicate ids, dangling edges, …) was only visible in the Debug panel. It now surfaces **inline on the canvas**: an un-declared cycle paints its offending edges + nodes red — using the same `validateFlowDraft` the simulator preflight runs — and an error banner lists the messages, so the author sees a broken graph without opening Debug. Each edge that closes the cycle carries a tooltip pointing at the fix ("mark the edge that closes the loop as a back-edge"). A declared revise loop (ADR-0044 back-edge) is excluded from cycle detection and stays un-flagged.
211
+
212
+ Follows #1954 (revise-loop authoring) and #1955 (simulating approval decisions).
213
+
214
+ - 1b3ccd1: feat(studio): simulate approval decisions in the flow debugger
215
+
216
+ The designer-time flow simulator treated an `approval` node as a pass-through that fanned out to every out-edge at once — so an ADR-0044 revise loop couldn't be debugged: it walked approve / reject / revise simultaneously and hit the step ceiling on the back-edge.
217
+
218
+ The simulator now models an approval as a durable pause (like `wait` / `screen`): it suspends at the node, and the Debug panel offers the node's out-edge labels (`approve` / `reject` / `revise`) as decision buttons. Resuming routes down ONLY the chosen branch — mirroring how the engine resumes a suspended approval by branch label — so a full revise loop is now walkable in the debugger: revise → wait → resubmit (back-edge) → round 2 → approve. An unmatched decision falls back to fanning out (mirroring the engine's label-fallback), logged so the author notices.
219
+
220
+ Follows #1954 (ADR-0044 revise-loop authoring).
221
+
222
+ - 05584aa: feat(studio/flow): context-aware Start trigger fields + explicit decision-branch binding
223
+
224
+ Two flow-builder UX improvements (follow-ups to the decision/screen/simulator fixes in #1927):
225
+
226
+ - **Start node trigger fields are now context-aware.** The Start node showed `Object`
227
+ and `Entry condition` (record-trigger config) even on screen / manual flows where
228
+ they don't apply. They're now gated by the chosen `triggerType` — shown for record /
229
+ schedule / webhook / event triggers, hidden for manual / unset (screen wizards). A
230
+ field that already holds a value is never hidden, so existing flows are unaffected.
231
+ - **Decision branches can be bound to edges explicitly.** Selecting a decision out-edge
232
+ now shows a **Branch** picker listing the source decision's branches (label · condition,
233
+ or "· default"). Picking one writes that branch's expression / label (or marks the
234
+ default) onto the edge — so routing stays correct even when edges are connected out of
235
+ branch order, instead of relying solely on the implicit by-order auto-wire. A
236
+ "— Custom —" option preserves manual editing.
237
+
238
+ Adds `flow-node-config.test.ts` covering the trigger-field gating.
239
+
240
+ - 44d4582: fix(studio): localize lookup picker config + keep published org objects editable
241
+
242
+ - The lookup field's "Picker config" sub-panel (display/description field,
243
+ selectable-records filters, depends-on, page size, quick-create) was
244
+ hard-coded English in an otherwise-Chinese designer. Routed every literal
245
+ through `t()`/`tFormat()` with new `designer.field.lookup.*` keys (en + zh).
246
+ - A freshly-published org object read back as read-only: after publish its
247
+ active version surfaces in the layered `code` slot tagged with the
248
+ `sys_metadata` provenance sentinel, and `ResourceEditPage` treated any
249
+ non-null `code` as a packaged artifact (needs `allowOrgOverride`, which the
250
+ `object` type lacks). Mirror the server's `isArtifactBacked` — which excludes
251
+ `_packageId === 'sys_metadata'` — so org-authored items stay editable.
252
+
253
+ - b419a7c: fix(studio): enable report authoring (create flow, chart render, dataset-aware inspector)
254
+
255
+ Found dogfooding report design in Studio as a business user — you could not create a report at all, plus several follow-on gaps.
256
+
257
+ - **Report create now uses the canvas + `ReportDefaultInspector`.** Only `object` was in `CREATE_MODE_CANVAS_TYPES`, so report-create fell back to a stale name-first form whose create-config (`objectName`, `columns: []`) predates the ADR-0021 dataset-bound model — saving failed server validation (_"a report needs `dataset` + `values`"_) with no field to fix it. Add `'report'` to the canvas set; the inspector exposes an auto-derived snake_case Name in create mode; fix the create-config (drop `objectName`/`columns`, seed `type: 'summary'` + `drilldown: true`).
258
+ - **Preserve `?package=` on post-create navigation** — it was dropped, so the editor reloaded a blank draft in the user's default package.
259
+ - **Render a report's embedded `chart`** in `DatasetReportRenderer` (authorable in Studio but never rendered) via the lazily-registered generic chart component; requests a non-animated render for export/background-tab safety.
260
+ - **Dedicated Chart panel in the inspector** — chart type + dataset-aware X-Axis (dimension) / Y-Axis (measure) dropdowns + title, replacing free-text axis fields and the vague "Chart: Required text value" validation.
261
+
262
+ - 15f140d: Validation messages name the offending widget + field
263
+
264
+ A nested Zod issue (e.g. `widgets.2.layout`) was shown as just its head field label — "Widgets: Invalid input" — so an author couldn't tell which widget or sub-field was at fault. `labelForIssuePath` now appends a readable trail, resolving each array index to the item's stable identity (id/name/title, incl. I18nLabel objects) from the draft: "Widgets → priority_split → layout". Single-segment paths are unchanged.
265
+
266
+ - Updated dependencies [677f7ed]
267
+ - Updated dependencies [08c47da]
268
+ - Updated dependencies [a71be60]
269
+ - Updated dependencies [cb03bc3]
270
+ - @object-ui/types@7.1.0
271
+ - @object-ui/core@7.1.0
272
+ - @object-ui/react@7.1.0
273
+ - @object-ui/auth@7.1.0
274
+ - @object-ui/collaboration@7.1.0
275
+ - @object-ui/components@7.1.0
276
+ - @object-ui/data-objectstack@7.1.0
277
+ - @object-ui/fields@7.1.0
278
+ - @object-ui/layout@7.1.0
279
+ - @object-ui/permissions@7.1.0
280
+ - @object-ui/plugin-editor@7.1.0
281
+ - @object-ui/providers@7.1.0
282
+ - @object-ui/i18n@7.1.0
283
+
3
284
  ## 7.0.0
4
285
 
5
286
  ### Minor Changes
@@ -373,8 +373,20 @@ export function AppContent({ extraRoutes, extraRoutesNoApp } = {}) {
373
373
  return (_jsx(Suspense, { fallback: _jsx(LoadingScreen, {}), children: _jsxs(Routes, { children: [_jsx(Route, { path: "create-app", element: _jsx(CreateAppPage, {}) }), _jsx(Route, { path: "system/marketplace", element: _jsx(MarketplacePage, {}) }), _jsx(Route, { path: "system/marketplace/installed", element: _jsx(MarketplaceInstalledPage, {}) }), _jsx(Route, { path: "system/marketplace/:packageId", element: _jsx(MarketplacePackagePage, {}) }), _jsx(Route, { path: "metadata", element: _jsx(MetadataDirectoryPage, {}) }), _jsx(Route, { path: "metadata/_diagnostics", element: _jsx(MetadataDiagnosticsPage, {}) }), _jsx(Route, { path: "metadata/:type", element: _jsx(MetadataResourceListPage, {}) }), _jsx(Route, { path: "metadata/:type/new", element: _jsx(MetadataResourceEditPage, { createMode: true }) }), _jsx(Route, { path: "metadata/:type/:name", element: _jsx(MetadataResourceEditPage, {}) }), _jsx(Route, { path: "metadata/:type/:name/history", element: _jsx(MetadataResourceHistoryPage, {}) }), extraRoutesNoApp] }) }));
374
374
  }
375
375
  const expressionUser = user
376
- ? { name: user.name, email: user.email, role: user.role ?? 'user' }
377
- : { name: 'Anonymous', email: '', role: 'guest' };
376
+ ? {
377
+ id: user.id,
378
+ name: user.name,
379
+ email: user.email,
380
+ role: user.role ?? 'user',
381
+ roles: user.roles,
382
+ // Surface the platform-admin flag so action `visible` CEL predicates
383
+ // gated on `ctx.user.isPlatformAdmin == true` (e.g. sys_environment
384
+ // "Change Plan (admin)") evaluate correctly. Previously only
385
+ // name/email/role were forwarded → isPlatformAdmin-gated actions were
386
+ // hidden even for platform admins.
387
+ isPlatformAdmin: user.isPlatformAdmin ?? false,
388
+ }
389
+ : { name: 'Anonymous', email: '', role: 'guest', isPlatformAdmin: false };
378
390
  return (_jsxs(ExpressionProvider, { user: expressionUser, app: activeApp, data: {}, features: features, children: [_jsx(NavigationSyncEffect, {}), _jsxs(ConsoleLayout, { activeAppName: activeApp.name, activeApp: activeApp, onAppChange: handleAppChange, objects: allObjects, connectionState: connectionState, userId: user?.id, children: [_jsx(CommandPalette, { apps: apps, activeApp: activeApp, objects: allObjects, onAppChange: handleAppChange, dataSource: dataSource }), _jsx(KeyboardShortcutsDialog, {}), _jsx(OnboardingWalkthrough, {}), _jsx(DraftReviewNavigator, { appName: appName }), _jsx(ErrorBoundary, { children: _jsx(Suspense, { fallback: _jsx(LoadingScreen, {}), children: _jsx(RouteFader, { className: "h-full", children: _jsxs(Routes, { children: [_jsx(Route, { path: "/", element: (() => {
379
391
  // When the app declares a landing target (home page or
380
392
  // first nav route) honour it; otherwise — e.g. the
@@ -631,10 +631,11 @@ function ChatPane({ agents, agentsLoading, agentsError, activeAgent, chatApi, ap
631
631
  }, [canvasOpen, onCanvasOpenChange]);
632
632
  // Draggable chat ↔ preview split (active only while the preview is open).
633
633
  const split = useResizableChatPane(canvasOpen);
634
- const handleDraftArtifacts = useCallback((artifacts) => {
634
+ const handleDraftArtifacts = useCallback((artifacts, appSegment) => {
635
635
  const app = artifacts.find((a) => a.type === 'app');
636
+ // Route the preview on the app's package id (ADR-0048), not its name.
636
637
  if (app)
637
- setCanvasApp((prev) => prev ?? { name: app.name, materialized: false });
638
+ setCanvasApp((prev) => prev ?? { name: app.name, segment: appSegment, materialized: false });
638
639
  if (canvasTimerRef.current)
639
640
  window.clearTimeout(canvasTimerRef.current);
640
641
  canvasTimerRef.current = window.setTimeout(() => setCanvasRefreshKey((k) => k + 1), 800);
@@ -644,7 +645,7 @@ function ChatPane({ agents, agentsLoading, agentsError, activeAgent, chatApi, ap
644
645
  // app URL — the reload that follows shows live rows in every list.
645
646
  const handleBuildMaterialized = useCallback((appName) => {
646
647
  setCanvasApp((prev) => prev && prev.name === appName && !prev.materialized
647
- ? { name: appName, materialized: true }
648
+ ? { ...prev, materialized: true } // keep the package-id segment
648
649
  : prev ?? { name: appName, materialized: true });
649
650
  }, []);
650
651
  // A different conversation is a different build session — close the pane.
@@ -734,6 +735,9 @@ function ChatPane({ agents, agentsLoading, agentsError, activeAgent, chatApi, ap
734
735
  toolRunning: t('console.ai.toolRunning'),
735
736
  toolAwaitingApproval: t('console.ai.toolAwaitingApproval'),
736
737
  toolFailed: t('console.ai.toolFailed'),
738
+ connectionWaiting: t('console.ai.connectionWaiting', { defaultValue: 'Waiting for server…' }),
739
+ connectionStalledLabel: t('console.ai.connectionStalled', { defaultValue: 'Still working…' }),
740
+ connectionOfflineLabel: t('console.ai.connectionOffline', { defaultValue: 'Connection lost — reconnecting…' }),
737
741
  toolDetailsHidden: t('console.ai.toolDetailsHidden'),
738
742
  copy: t('console.ai.copy'),
739
743
  copied: t('console.ai.copied'),
@@ -746,7 +750,7 @@ function ChatPane({ agents, agentsLoading, agentsError, activeAgent, chatApi, ap
746
750
  viewTrace: t('console.ai.viewTrace'),
747
751
  }, suggestions: suggestions, onSendMessage: handleSend, onClear: clear, hideClearBar: true, onStop: isLoading ? stop : undefined, onReload: reload, isLoading: isLoading, error: errorSuppressed ? undefined : error, enableMarkdown: true, onToolApprove: hitl.decide, toolDecisions: hitl.decisions, toolApproveLabel: "Approve & run", toolDenyLabel: "Reject", toolDenyReason: "Operator rejected from chat",
748
752
  // Build-tree "Open app": jump straight into the app the agent just built.
749
- onOpenBuiltApp: (appName) => navigate(`/apps/${encodeURIComponent(appName)}`), openBuiltAppLabel: t('console.ai.openBuiltApp', { defaultValue: 'Open app' }),
753
+ onOpenBuiltApp: (appName, appSegment) => navigate(`/apps/${encodeURIComponent(appSegment ?? appName)}`), openBuiltAppLabel: t('console.ai.openBuiltApp', { defaultValue: 'Open app' }),
750
754
  // Live lifecycle truth for draft cards: the server's pending count per
751
755
  // package, so reloaded conversations show Published/Publish honestly.
752
756
  fetchPendingDraftCount: fetchPendingDraftCount, onPublishDrafts: async (packageId) => {
@@ -799,7 +803,7 @@ function ChatPane({ agents, agentsLoading, agentsError, activeAgent, chatApi, ap
799
803
  }
800
804
  }, publishDraftsLabel: t('console.ai.publishDrafts', { defaultValue: 'Publish' }), publishedLabel: t('console.ai.published', { defaultValue: 'Published' }), nextStepsLabel: t('console.ai.nextSteps', { defaultValue: "What's next" }), planTitleLabel: t('console.ai.planTitle', { defaultValue: 'Proposed plan' }), planQuestionsLabel: t('console.ai.planQuestions', { defaultValue: 'Confirm before building' }), planAssumptionsLabel: t('console.ai.planAssumptions', { defaultValue: 'Assumptions' }), planApproveHintLabel: t('console.ai.planApproveHint', {
801
805
  defaultValue: 'Reply to approve or adjust this plan.',
802
- }), planApproveLabel: t('console.ai.planApprove', { defaultValue: 'Build it' }), planAdjustLabel: t('console.ai.planAdjust', { defaultValue: 'Adjust' }), planApproveMessage: t('console.ai.planApproveMessage', {
806
+ }), planApproveLabel: t('console.ai.planApprove', { defaultValue: 'Build it' }), planAdjustLabel: t('console.ai.planAdjust', { defaultValue: 'Adjust' }), planBuiltLabel: t('console.ai.planBuilt', { defaultValue: 'Built' }), planApproveMessage: t('console.ai.planApproveMessage', {
803
807
  defaultValue: 'Looks good — build it as proposed.',
804
808
  }), planApproveDefaultsMessage: t('console.ai.planApproveDefaultsMessage', {
805
809
  defaultValue: 'Build it with your best assumptions; use sensible defaults for the open questions.',
@@ -814,10 +818,10 @@ function ChatPane({ agents, agentsLoading, agentsError, activeAgent, chatApi, ap
814
818
  autoPublishDrafts: getRuntimeConfig().features.autoPublishAiBuilds,
815
819
  // ADR-0037 Live Canvas: open/refresh the draft-preview pane as the
816
820
  // agent's artifacts land; Preview buttons deep-link the same route.
817
- onDraftArtifacts: handleDraftArtifacts, onPreviewDraftApp: (appName, opts) => setCanvasApp({ name: appName, materialized: opts?.materialized === true }),
821
+ onDraftArtifacts: handleDraftArtifacts, onPreviewDraftApp: (appName, opts) => setCanvasApp({ name: appName, segment: opts?.appSegment, materialized: opts?.materialized === true }),
818
822
  // ADR-0045: build materialized → canvas leaves the draft overlay for
819
823
  // the real (unlisted) app; the reload shows live seed rows.
820
- onBuildMaterialized: handleBuildMaterialized, previewDraftLabel: t('console.ai.previewDraft', { defaultValue: 'Preview' }), "data-testid": "ai-chat-panel" }) }), canvasApp ? (_jsxs(_Fragment, { children: [_jsxs("div", { role: "separator", "aria-orientation": "vertical", "aria-label": t('console.ai.resizeSplit', { defaultValue: 'Resize chat and preview' }), tabIndex: 0, onPointerDown: split.onHandlePointerDown, onKeyDown: split.onHandleKeyDown, onDoubleClick: split.reset, "data-testid": "ai-chat-split-handle", className: cn('group relative hidden w-1.5 shrink-0 cursor-col-resize touch-none select-none md:block', 'focus:outline-none'), children: [_jsx("span", { "aria-hidden": true, className: "absolute inset-y-0 -left-1.5 -right-1.5" }), _jsx("span", { "aria-hidden": true, className: cn('absolute inset-y-0 left-1/2 w-px -translate-x-1/2 bg-border transition-colors', 'group-hover:bg-primary/60 group-focus-visible:bg-primary', split.dragging && 'bg-primary') })] }), _jsx(LiveCanvas, { appName: canvasApp.name, materialized: canvasApp.materialized, refreshKey: canvasRefreshKey, onClose: () => setCanvasApp(null) }), split.dragging ? _jsx("div", { className: "fixed inset-0 z-50 cursor-col-resize", "data-testid": "ai-chat-split-overlay" }) : null] })) : null] }));
824
+ onBuildMaterialized: handleBuildMaterialized, previewDraftLabel: t('console.ai.previewDraft', { defaultValue: 'Preview' }), "data-testid": "ai-chat-panel" }) }), canvasApp ? (_jsxs(_Fragment, { children: [_jsxs("div", { role: "separator", "aria-orientation": "vertical", "aria-label": t('console.ai.resizeSplit', { defaultValue: 'Resize chat and preview' }), tabIndex: 0, onPointerDown: split.onHandlePointerDown, onKeyDown: split.onHandleKeyDown, onDoubleClick: split.reset, "data-testid": "ai-chat-split-handle", className: cn('group relative hidden w-1.5 shrink-0 cursor-col-resize touch-none select-none md:block', 'focus:outline-none'), children: [_jsx("span", { "aria-hidden": true, className: "absolute inset-y-0 -left-1.5 -right-1.5" }), _jsx("span", { "aria-hidden": true, className: cn('absolute inset-y-0 left-1/2 w-px -translate-x-1/2 bg-border transition-colors', 'group-hover:bg-primary/60 group-focus-visible:bg-primary', split.dragging && 'bg-primary') })] }), _jsx(LiveCanvas, { appName: canvasApp.name, appSegment: canvasApp.segment, materialized: canvasApp.materialized, refreshKey: canvasRefreshKey, onClose: () => setCanvasApp(null) }), split.dragging ? _jsx("div", { className: "fixed inset-0 z-50 cursor-col-resize", "data-testid": "ai-chat-split-overlay" }) : null] })) : null] }));
821
825
  }
822
826
  function dataChatSuggestions(t) {
823
827
  return [
@@ -6,8 +6,14 @@
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
8
  export interface LiveCanvasProps {
9
- /** The drafted app to render (its `app` metadata name). */
9
+ /** The drafted app to render (its `app` metadata name) — used for display. */
10
10
  appName: string;
11
+ /**
12
+ * ADR-0048: the app's ROUTE SEGMENT — its package id (`app.<slug>`), which is
13
+ * globally unique, unlike the display name (two AI apps can both be `library`).
14
+ * The iframe URL keys on this; falls back to `appName` when absent.
15
+ */
16
+ appSegment?: string;
11
17
  /**
12
18
  * ADR-0045: the build was materialized — the app is live (real tables and
13
19
  * seed rows) but unlisted. The canvas then renders the REAL app URL: full
@@ -19,4 +25,4 @@ export interface LiveCanvasProps {
19
25
  refreshKey: number;
20
26
  onClose: () => void;
21
27
  }
22
- export declare function LiveCanvas({ appName, materialized, refreshKey, onClose }: LiveCanvasProps): import("react").JSX.Element;
28
+ export declare function LiveCanvas({ appName, appSegment, materialized, refreshKey, onClose }: LiveCanvasProps): import("react").JSX.Element;
@@ -37,15 +37,17 @@ function canvasSrc(appName, materialized) {
37
37
  const base = `${spaBase()}/apps/${encodeURIComponent(appName)}`;
38
38
  return materialized ? base : `${base}?preview=draft`;
39
39
  }
40
- export function LiveCanvas({ appName, materialized = false, refreshKey, onClose }) {
40
+ export function LiveCanvas({ appName, appSegment, materialized = false, refreshKey, onClose }) {
41
41
  const { t } = useObjectTranslation();
42
42
  const iframeRef = useRef(null);
43
+ // Route on the package-id segment (unique), display by name (friendly).
44
+ const routeSeg = appSegment && appSegment.length ? appSegment : appName;
43
45
  // Materialized world swap: changing src on the SAME iframe element
44
46
  // navigates it in place (no white-flash remount).
45
47
  useEffect(() => {
46
48
  if (!iframeRef.current)
47
49
  return;
48
- const next = canvasSrc(appName, materialized);
50
+ const next = canvasSrc(routeSeg, materialized);
49
51
  try {
50
52
  if (iframeRef.current.getAttribute('src') !== next)
51
53
  iframeRef.current.setAttribute('src', next);
@@ -53,7 +55,7 @@ export function LiveCanvas({ appName, materialized = false, refreshKey, onClose
53
55
  catch {
54
56
  /* not ready — the mount src covers it */
55
57
  }
56
- }, [appName, materialized]);
58
+ }, [routeSeg, materialized]);
57
59
  // Refresh in place (src reload) instead of remounting the iframe — keeps
58
60
  // the pane from flashing white on every invalidation.
59
61
  useEffect(() => {
@@ -74,5 +76,5 @@ export function LiveCanvas({ appName, materialized = false, refreshKey, onClose
74
76
  : t('console.ai.liveCanvas', {
75
77
  app: appName,
76
78
  defaultValue: 'Live preview — {{app}} (draft)',
77
- }) }), _jsx(Button, { size: "sm", variant: "ghost", onClick: onClose, "data-testid": "live-canvas-close", children: _jsx(X, { className: "h-3.5 w-3.5" }) })] }), _jsx("iframe", { ref: iframeRef, title: `Draft preview: ${appName}`, src: canvasSrc(appName, materialized), className: "h-full w-full flex-1 border-0 bg-background", "data-testid": "live-canvas-frame" })] }));
79
+ }) }), _jsx(Button, { size: "sm", variant: "ghost", onClick: onClose, "data-testid": "live-canvas-close", children: _jsx(X, { className: "h-3.5 w-3.5" }) })] }), _jsx("iframe", { ref: iframeRef, title: `Draft preview: ${appName}`, src: canvasSrc(routeSeg, materialized), className: "h-full w-full flex-1 border-0 bg-background", "data-testid": "live-canvas-frame" })] }));
78
80
  }
@@ -136,6 +136,36 @@ interface ServerConversation {
136
136
  messages?: ServerMessage[];
137
137
  }
138
138
  export declare function toUIMessages(rows: ServerMessage[] | undefined): HydratedUIMessage[];
139
+ /**
140
+ * A FLAT `ai_messages` row as returned by the public share endpoint
141
+ * (`GET /api/v1/share-links/:token/messages`), which streams the raw object
142
+ * rows rather than the reconstructed ModelMessage history that the
143
+ * authenticated `GET /conversations/:id` returns.
144
+ */
145
+ export interface RawAiMessageRow {
146
+ id?: string;
147
+ role: string;
148
+ /** Persisted text (assistant/user/system) OR a JSON-stringified tool-result array (tool role). */
149
+ content?: unknown;
150
+ /** Assistant tool CALLS, JSON-stringified (`tool-call` parts). */
151
+ tool_calls?: string | null;
152
+ /** Tool RESULT's owning call id (legacy plain-string tool rows). */
153
+ tool_call_id?: string | null;
154
+ }
155
+ /**
156
+ * Reconstruct the ModelMessage-shaped `content` that {@link toUIMessages}
157
+ * expects from the FLAT columns the public share endpoint returns raw.
158
+ *
159
+ * The authenticated path gets this reconstruction server-side
160
+ * (`ObjqlConversationService.toMessage`): an assistant turn's tool CALLS live
161
+ * in the separate `tool_calls` column, and a `tool` row's RESULTS are a
162
+ * JSON-stringified array in `content`. The share endpoint skips that step and
163
+ * dumps the rows verbatim — so the shared transcript previously rendered the
164
+ * raw `{"type":"tool-result",…}` envelope as text instead of a card. Mirroring
165
+ * `toMessage` here lets the share page reuse the exact same hydrate → render
166
+ * pipeline as the live chat. Keep this in lockstep with `toMessage`.
167
+ */
168
+ export declare function aiMessageRowsToServerMessages(rows: RawAiMessageRow[] | undefined): ServerMessage[];
139
169
  export declare function fetchConversation(apiBase: string, id: string): Promise<ServerConversation | null>;
140
170
  export declare function useChatConversation(options: UseChatConversationOptions): UseChatConversationReturn;
141
171
  export {};
@@ -267,6 +267,69 @@ export function toUIMessages(rows) {
267
267
  });
268
268
  return out;
269
269
  }
270
+ function safeParseArray(value) {
271
+ if (typeof value !== 'string' || !value)
272
+ return undefined;
273
+ try {
274
+ const parsed = JSON.parse(value);
275
+ return Array.isArray(parsed) ? parsed : undefined;
276
+ }
277
+ catch {
278
+ return undefined;
279
+ }
280
+ }
281
+ /**
282
+ * Reconstruct the ModelMessage-shaped `content` that {@link toUIMessages}
283
+ * expects from the FLAT columns the public share endpoint returns raw.
284
+ *
285
+ * The authenticated path gets this reconstruction server-side
286
+ * (`ObjqlConversationService.toMessage`): an assistant turn's tool CALLS live
287
+ * in the separate `tool_calls` column, and a `tool` row's RESULTS are a
288
+ * JSON-stringified array in `content`. The share endpoint skips that step and
289
+ * dumps the rows verbatim — so the shared transcript previously rendered the
290
+ * raw `{"type":"tool-result",…}` envelope as text instead of a card. Mirroring
291
+ * `toMessage` here lets the share page reuse the exact same hydrate → render
292
+ * pipeline as the live chat. Keep this in lockstep with `toMessage`.
293
+ */
294
+ export function aiMessageRowsToServerMessages(rows) {
295
+ if (!rows)
296
+ return [];
297
+ return rows.map((row) => {
298
+ const id = row.id;
299
+ const text = typeof row.content === 'string' ? row.content : '';
300
+ if (row.role === 'assistant') {
301
+ const toolCalls = safeParseArray(row.tool_calls);
302
+ if (toolCalls && toolCalls.length > 0) {
303
+ const content = [];
304
+ if (text)
305
+ content.push({ type: 'text', text });
306
+ content.push(...toolCalls);
307
+ return { id, role: 'assistant', content };
308
+ }
309
+ return { id, role: 'assistant', content: text };
310
+ }
311
+ if (row.role === 'tool') {
312
+ const results = safeParseArray(row.content);
313
+ if (results && results.length > 0 && results[0]?.type === 'tool-result') {
314
+ return { id, role: 'tool', content: results };
315
+ }
316
+ // Back-compat: pre-array tool rows persisted a plain string.
317
+ return {
318
+ id,
319
+ role: 'tool',
320
+ content: [
321
+ {
322
+ type: 'tool-result',
323
+ toolCallId: row.tool_call_id ?? '',
324
+ toolName: 'unknown',
325
+ output: { type: 'text', value: text },
326
+ },
327
+ ],
328
+ };
329
+ }
330
+ return { id, role: row.role, content: text };
331
+ });
332
+ }
270
333
  export async function fetchConversation(apiBase, id) {
271
334
  const res = await fetch(`${apiBase}/conversations/${encodeURIComponent(id)}`, {
272
335
  credentials: 'include',
@@ -24,6 +24,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
24
24
  import { useCallback, useMemo, useRef, useState } from 'react';
25
25
  import { useNavigate } from 'react-router-dom';
26
26
  import { useAuth, createAuthenticatedFetch } from '@object-ui/auth';
27
+ import { usePermissions } from '@object-ui/permissions';
27
28
  import { useObjectLabel } from '@object-ui/i18n';
28
29
  import { ActionProvider, useGlobalUndo } from '@object-ui/react';
29
30
  import { toast } from 'sonner';
@@ -37,6 +38,9 @@ export function useConsoleActionRuntime(opts) {
37
38
  const { dataSource, objects, objectName, onRefresh } = opts;
38
39
  const navigate = useNavigate();
39
40
  const { user, activeOrganization } = useAuth();
41
+ // [ADR-0066 D4] System capabilities for the action capability gate (fail-open
42
+ // when no PermissionProvider is mounted — usePermissions returns []).
43
+ const { systemPermissions } = usePermissions();
40
44
  const { fieldLabel, fieldOptionLabel, actionParamText, actionParamOptionLabel, actionDescription } = useObjectLabel();
41
45
  const objectDef = useMemo(() => (objectName ? objects?.find((o) => o.name === objectName) : undefined), [objects, objectName]);
42
46
  // Object name used for API paths / generic CRUD. Falls back to the action's
@@ -103,8 +107,8 @@ export function useConsoleActionRuntime(opts) {
103
107
  });
104
108
  }, [objectName, objectDef, objects, fieldLabel, fieldOptionLabel, actionParamText, actionParamOptionLabel]);
105
109
  const currentUser = user
106
- ? { id: user.id, name: user.name, avatar: user.image, isPlatformAdmin: user?.isPlatformAdmin ?? false }
107
- : FALLBACK_USER;
110
+ ? { id: user.id, name: user.name, avatar: user.image, isPlatformAdmin: user?.isPlatformAdmin ?? false, systemPermissions: systemPermissions ?? [] }
111
+ : { ...FALLBACK_USER, systemPermissions: systemPermissions ?? [] };
108
112
  const toastHandler = useCallback((message, options) => {
109
113
  if (options?.type === 'error') {
110
114
  toast.error(message);
package/dist/index.d.ts CHANGED
@@ -48,8 +48,9 @@ export { MembersPage as DefaultMembersPage } from './console/organizations/manag
48
48
  export { InvitationsPage as DefaultInvitationsPage } from './console/organizations/manage/InvitationsPage';
49
49
  export { SettingsPage as DefaultSettingsPage } from './console/organizations/manage/SettingsPage';
50
50
  export { AcceptInvitationPage as DefaultAcceptInvitationPage } from './console/organizations/manage/AcceptInvitationPage';
51
- export { AiChatPage as DefaultAiChatPage, AiChatPage } from './console/ai/AiChatPage';
51
+ export { AiChatPage as DefaultAiChatPage, AiChatPage, hydratedMessagesToChatMessages, } from './console/ai/AiChatPage';
52
52
  export { ConversationsSidebar } from './console/ai/ConversationsSidebar';
53
+ export { toUIMessages, aiMessageRowsToServerMessages, type HydratedUIMessage, type RawAiMessageRow, } from './hooks/useChatConversation';
53
54
  export { registerAppComponent, getAppComponent, listAppComponents, componentRefToUrlSegments, urlSegmentsToComponentRef, } from './services/componentRegistry';
54
55
  export type { AppComponentRegistryEntry } from './services/componentRegistry';
55
56
  import './services/builtinComponents';
package/dist/index.js CHANGED
@@ -52,8 +52,12 @@ export { MembersPage as DefaultMembersPage } from './console/organizations/manag
52
52
  export { InvitationsPage as DefaultInvitationsPage } from './console/organizations/manage/InvitationsPage';
53
53
  export { SettingsPage as DefaultSettingsPage } from './console/organizations/manage/SettingsPage';
54
54
  export { AcceptInvitationPage as DefaultAcceptInvitationPage } from './console/organizations/manage/AcceptInvitationPage';
55
- export { AiChatPage as DefaultAiChatPage, AiChatPage } from './console/ai/AiChatPage';
55
+ export { AiChatPage as DefaultAiChatPage, AiChatPage, hydratedMessagesToChatMessages, } from './console/ai/AiChatPage';
56
56
  export { ConversationsSidebar } from './console/ai/ConversationsSidebar';
57
+ // Conversation-history hydration helpers — reused by the public read-only
58
+ // share page (`/s/:token`) so a shared transcript renders identically to the
59
+ // live chat (tool cards included) instead of dumping raw envelopes.
60
+ export { toUIMessages, aiMessageRowsToServerMessages, } from './hooks/useChatConversation';
57
61
  // Phase 3b: Component nav registry — plugins use this to register
58
62
  // admin/setup UI surfaces that are addressable from App metadata via
59
63
  // `{ type: 'component', componentRef: 'ns:name' }` nav items.
@@ -36,10 +36,12 @@ export interface ConsoleFloatingChatbotProps {
36
36
  */
37
37
  defaultAgent?: string;
38
38
  /**
39
- * Show the in-header agent switcher. Off by default: end users get the
40
- * single agent bound to their app and never have to choose. Enable for
41
- * power users / admins (or via `VITE_AI_SHOW_AGENT_PICKER`) when a
42
- * surface genuinely exposes multiple agents.
39
+ * Force the in-header agent switcher on (`true`) or off (`false`),
40
+ * overriding the default. When left undefined the switcher auto-reveals
41
+ * only when AI development is unlocked for the viewer — the live catalog
42
+ * serves BOTH an `ask` and a `build` agent and `aiStudio` isn't disabled —
43
+ * so pure end-user apps (only `ask`) stay clean while builders can flip
44
+ * Ask↔Build inline. `VITE_AI_SHOW_AGENT_PICKER=true` also forces it on.
43
45
  */
44
46
  showAgentPicker?: boolean;
45
47
  /** Whether the floating panel should open immediately on mount. */