@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.
- package/CHANGELOG.md +281 -0
- package/dist/console/AppContent.js +14 -2
- package/dist/console/ai/AiChatPage.js +11 -7
- package/dist/console/ai/LiveCanvas.d.ts +8 -2
- package/dist/console/ai/LiveCanvas.js +6 -4
- package/dist/hooks/useChatConversation.d.ts +30 -0
- package/dist/hooks/useChatConversation.js +63 -0
- package/dist/hooks/useConsoleActionRuntime.js +6 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.js +5 -1
- package/dist/layout/ConsoleFloatingChatbot.d.ts +6 -4
- package/dist/layout/ConsoleFloatingChatbot.js +25 -8
- package/dist/layout/ContextSelectors.js +59 -35
- package/dist/layout/agentPicker.d.ts +56 -0
- package/dist/layout/agentPicker.js +40 -0
- package/dist/preview/CommitTimeline.d.ts +15 -0
- package/dist/preview/CommitTimeline.js +82 -0
- package/dist/preview/UnpublishedAppBar.js +11 -7
- package/dist/preview/commitHistory.d.ts +28 -0
- package/dist/preview/commitHistory.js +48 -0
- package/dist/providers/MetadataProvider.js +9 -0
- package/dist/views/FlowRunner.d.ts +2 -30
- package/dist/views/FlowRunner.js +18 -50
- package/dist/views/ScreenView.d.ts +70 -0
- package/dist/views/ScreenView.js +73 -0
- package/dist/views/metadata-admin/DirectoryPage.js +2 -14
- package/dist/views/metadata-admin/JsonSourceEditor.d.ts +3 -1
- package/dist/views/metadata-admin/JsonSourceEditor.js +21 -3
- package/dist/views/metadata-admin/PackagesPage.js +9 -1
- package/dist/views/metadata-admin/ResourceEditPage.js +47 -20
- package/dist/views/metadata-admin/ResourceListPage.js +8 -16
- package/dist/views/metadata-admin/StudioHomePage.js +6 -14
- package/dist/views/metadata-admin/anchors.js +20 -2
- package/dist/views/metadata-admin/i18n.js +88 -2
- package/dist/views/metadata-admin/inspectors/DatasetDefaultInspector.d.ts +2 -2
- package/dist/views/metadata-admin/inspectors/DatasetDefaultInspector.js +122 -8
- package/dist/views/metadata-admin/inspectors/FlowEdgeInspector.js +84 -3
- package/dist/views/metadata-admin/inspectors/FlowNodeInspector.js +67 -2
- package/dist/views/metadata-admin/inspectors/ObjectDefaultInspector.js +5 -4
- package/dist/views/metadata-admin/inspectors/ObjectFieldInspector.js +47 -12
- package/dist/views/metadata-admin/inspectors/ReportDefaultInspector.d.ts +1 -1
- package/dist/views/metadata-admin/inspectors/ReportDefaultInspector.js +60 -2
- package/dist/views/metadata-admin/inspectors/_shared.d.ts +5 -1
- package/dist/views/metadata-admin/inspectors/_shared.js +2 -2
- package/dist/views/metadata-admin/inspectors/datasetFilterCondition.d.ts +24 -0
- package/dist/views/metadata-admin/inspectors/datasetFilterCondition.js +97 -0
- package/dist/views/metadata-admin/inspectors/flow-node-config.js +46 -1
- package/dist/views/metadata-admin/issuePath.d.ts +22 -0
- package/dist/views/metadata-admin/issuePath.js +65 -0
- package/dist/views/metadata-admin/package-scope.d.ts +26 -0
- package/dist/views/metadata-admin/package-scope.js +43 -0
- package/dist/views/metadata-admin/previews/DatasetPreview.js +21 -5
- package/dist/views/metadata-admin/previews/FlowCanvas.d.ts +7 -1
- package/dist/views/metadata-admin/previews/FlowCanvas.js +104 -16
- package/dist/views/metadata-admin/previews/FlowPreview.js +31 -3
- package/dist/views/metadata-admin/previews/FlowSimulatorPanel.js +37 -3
- package/dist/views/metadata-admin/previews/PagePreview.js +112 -3
- package/dist/views/metadata-admin/previews/ScreenPreview.d.ts +38 -0
- package/dist/views/metadata-admin/previews/ScreenPreview.js +61 -0
- package/dist/views/metadata-admin/previews/flow-canvas-layout.d.ts +14 -0
- package/dist/views/metadata-admin/previews/flow-canvas-layout.js +37 -0
- package/dist/views/metadata-admin/previews/flow-canvas-parts.d.ts +9 -1
- package/dist/views/metadata-admin/previews/flow-canvas-parts.js +21 -6
- package/dist/views/metadata-admin/previews/object-fields-io.d.ts +21 -0
- package/dist/views/metadata-admin/previews/object-fields-io.js +37 -2
- package/dist/views/metadata-admin/previews/screen-spec.d.ts +43 -0
- package/dist/views/metadata-admin/previews/screen-spec.js +108 -0
- package/dist/views/metadata-admin/previews/simulator/flow-sim-types.d.ts +11 -0
- package/dist/views/metadata-admin/previews/simulator/flow-sim-validate.d.ts +7 -0
- package/dist/views/metadata-admin/previews/simulator/flow-sim-validate.js +72 -0
- package/dist/views/metadata-admin/previews/simulator/flow-simulator.d.ts +32 -3
- package/dist/views/metadata-admin/previews/simulator/flow-simulator.js +119 -9
- 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
|
-
? {
|
|
377
|
-
|
|
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
|
-
? {
|
|
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(
|
|
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
|
-
}, [
|
|
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(
|
|
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
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
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. */
|