@object-ui/app-shell 7.1.0 → 7.2.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 +279 -0
- package/dist/console/AppContent.js +9 -15
- package/dist/console/ConsoleShell.d.ts +16 -0
- package/dist/console/ConsoleShell.js +43 -2
- package/dist/console/ai/AiChatPage.js +36 -9
- package/dist/console/home/HomeLayout.js +5 -7
- package/dist/console/home/HomePage.js +1 -9
- package/dist/console/organizations/CreateWorkspaceDialog.js +15 -1
- package/dist/console/organizations/OrganizationsPage.js +22 -3
- package/dist/console/organizations/provisionEnvironment.d.ts +53 -0
- package/dist/console/organizations/provisionEnvironment.js +64 -0
- package/dist/environment/EnvironmentEntitlementDialog.d.ts +34 -0
- package/dist/environment/EnvironmentEntitlementDialog.js +37 -0
- package/dist/environment/EnvironmentListToolbar.d.ts +33 -0
- package/dist/environment/EnvironmentListToolbar.js +59 -0
- package/dist/environment/entitlements.d.ts +90 -0
- package/dist/environment/entitlements.js +91 -0
- package/dist/environment/useEnvironmentEntitlements.d.ts +32 -0
- package/dist/environment/useEnvironmentEntitlements.js +108 -0
- package/dist/hooks/useActionModal.js +15 -1
- package/dist/hooks/useAiSurface.d.ts +59 -0
- package/dist/hooks/useAiSurface.js +78 -0
- package/dist/hooks/useConsoleActionRuntime.d.ts +3 -0
- package/dist/hooks/useConsoleActionRuntime.js +36 -8
- package/dist/index.d.ts +3 -1
- package/dist/index.js +5 -1
- package/dist/layout/AppHeader.js +28 -4
- package/dist/layout/ConsoleFloatingChatbot.js +16 -2
- package/dist/layout/ConsoleLayout.js +5 -6
- package/dist/preview/DraftPreviewBar.js +20 -7
- package/dist/providers/ExpressionProvider.js +9 -3
- package/dist/utils/index.d.ts +2 -2
- package/dist/utils/index.js +1 -1
- package/dist/utils/recordFormNavigation.d.ts +60 -0
- package/dist/utils/recordFormNavigation.js +35 -0
- package/dist/utils/resolvePageVarTokens.d.ts +31 -0
- package/dist/utils/resolvePageVarTokens.js +72 -0
- package/dist/views/CreateViewDialog.js +14 -1
- package/dist/views/ObjectView.js +26 -12
- package/dist/views/metadata-admin/AssignedUsersSection.d.ts +28 -0
- package/dist/views/metadata-admin/AssignedUsersSection.js +151 -0
- package/dist/views/metadata-admin/PackagesPage.d.ts +5 -0
- package/dist/views/metadata-admin/PackagesPage.js +49 -4
- package/dist/views/metadata-admin/PermissionMatrixEditor.js +2 -1
- package/dist/views/metadata-admin/ResourceEditPage.js +36 -4
- package/dist/views/metadata-admin/ResourceListPage.js +21 -4
- package/dist/views/metadata-admin/createBody.d.ts +26 -0
- package/dist/views/metadata-admin/createBody.js +30 -0
- package/dist/views/metadata-admin/i18n.js +20 -0
- package/dist/views/metadata-admin/inspectors/DatasetDefaultInspector.d.ts +8 -0
- package/dist/views/metadata-admin/inspectors/DatasetDefaultInspector.js +17 -3
- package/dist/views/metadata-admin/inspectors/FlowEdgeInspector.js +16 -2
- package/dist/views/metadata-admin/inspectors/FlowExprIssue.d.ts +21 -0
- package/dist/views/metadata-admin/inspectors/FlowExprIssue.js +13 -0
- package/dist/views/metadata-admin/inspectors/FlowKeyValueField.d.ts +20 -2
- package/dist/views/metadata-admin/inspectors/FlowKeyValueField.js +71 -28
- package/dist/views/metadata-admin/inspectors/FlowNodeConfigField.d.ts +4 -1
- package/dist/views/metadata-admin/inspectors/FlowNodeConfigField.js +24 -9
- package/dist/views/metadata-admin/inspectors/FlowNodeInspector.js +15 -3
- package/dist/views/metadata-admin/inspectors/FlowObjectListField.d.ts +4 -1
- package/dist/views/metadata-admin/inspectors/FlowObjectListField.js +8 -3
- package/dist/views/metadata-admin/inspectors/VariableTextInput.d.ts +47 -0
- package/dist/views/metadata-admin/inspectors/VariableTextInput.js +95 -0
- package/dist/views/metadata-admin/inspectors/datasetFilterCondition.js +6 -1
- package/dist/views/metadata-admin/inspectors/flow-node-config.d.ts +16 -1
- package/dist/views/metadata-admin/inspectors/flow-node-config.js +21 -10
- package/dist/views/metadata-admin/inspectors/flow-ref-check.d.ts +39 -0
- package/dist/views/metadata-admin/inspectors/flow-ref-check.js +114 -0
- package/dist/views/metadata-admin/inspectors/flow-scope.d.ts +109 -0
- package/dist/views/metadata-admin/inspectors/flow-scope.js +199 -0
- package/dist/views/metadata-admin/inspectors/useDatasetFields.d.ts +14 -3
- package/dist/views/metadata-admin/inspectors/useDatasetFields.js +0 -0
- package/dist/views/metadata-admin/inspectors/useFlowScope.d.ts +23 -0
- package/dist/views/metadata-admin/inspectors/useFlowScope.js +45 -0
- package/dist/views/metadata-admin/package-scope.d.ts +15 -0
- package/dist/views/metadata-admin/package-scope.js +16 -0
- package/dist/views/metadata-admin/preview-registry.d.ts +12 -0
- package/dist/views/metadata-admin/previews/FlowCanvas.d.ts +22 -3
- package/dist/views/metadata-admin/previews/FlowCanvas.js +45 -6
- package/dist/views/metadata-admin/previews/FlowPreview.d.ts +1 -1
- package/dist/views/metadata-admin/previews/FlowPreview.js +42 -30
- package/dist/views/metadata-admin/previews/ObjectFormCanvas.js +9 -4
- package/dist/views/metadata-admin/previews/ProblemsPanel.d.ts +18 -0
- package/dist/views/metadata-admin/previews/ProblemsPanel.js +27 -0
- package/dist/views/metadata-admin/previews/ReportPreview.d.ts +9 -8
- package/dist/views/metadata-admin/previews/ReportPreview.js +33 -16
- package/dist/views/metadata-admin/previews/flow-canvas-parts.d.ts +9 -1
- package/dist/views/metadata-admin/previews/flow-canvas-parts.js +5 -3
- package/dist/views/metadata-admin/previews/flow-expr-problems.d.ts +19 -0
- package/dist/views/metadata-admin/previews/flow-expr-problems.js +97 -0
- package/dist/views/metadata-admin/previews/flow-problems.d.ts +84 -0
- package/dist/views/metadata-admin/previews/flow-problems.js +209 -0
- package/dist/views/metadata-admin/previews/simulator/flow-sim-types.d.ts +9 -0
- package/dist/views/metadata-admin/previews/simulator/flow-sim-validate.js +4 -2
- package/package.json +38 -38
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,284 @@
|
|
|
1
1
|
# @object-ui/app-shell — Changelog
|
|
2
2
|
|
|
3
|
+
## 7.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 88a3e39: feat(console): born-with-env eager provisioning for multi-org workspace create
|
|
8
|
+
|
|
9
|
+
ObjectStack runs a 1-production-environment-per-organization model: a user who wants
|
|
10
|
+
another production space creates another organization, and each org is born with its
|
|
11
|
+
production environment. The self-service "create workspace" flow now delivers that
|
|
12
|
+
without an onboarding-wizard detour.
|
|
13
|
+
|
|
14
|
+
After `createOrganization` succeeds (which already switches the active org),
|
|
15
|
+
`CreateWorkspaceDialog` eagerly `POST`s `/api/v1/cloud/environments` with the new org as
|
|
16
|
+
target so its first environment is provisioned as a production env (allowed on every plan,
|
|
17
|
+
including free), then hands off to the existing switch-and-navigate-home path. The
|
|
18
|
+
provision is best-effort: on failure the onboarding gate provisions the env lazily on
|
|
19
|
+
first navigation, so multi-org still works. The `multiOrgEnabled` enable-gate is unchanged
|
|
20
|
+
(already wired end-to-end via the auth `/config` `features.multiOrgEnabled` flag).
|
|
21
|
+
|
|
22
|
+
Adds a gated **"Create workspace"** entry to the org switcher (avatar dropdown) that
|
|
23
|
+
opens the dialog directly — previously a single-org user could never reach it, because
|
|
24
|
+
the only path (`/organizations`) auto-skips to home when you belong to exactly one org.
|
|
25
|
+
The eager provision is idempotent: a control plane that auto-provisions the production
|
|
26
|
+
env on org create resolves it to "already provisioned" rather than erroring.
|
|
27
|
+
|
|
28
|
+
Also removes the unreferenced `apps/console` `CreateWorkspaceDialog` duplicate; the live
|
|
29
|
+
component is the app-shell copy used by `OrganizationsPage`.
|
|
30
|
+
|
|
31
|
+
- e301475: feat(console): hide the AI surface at runtime when the server serves no AI agent (Community Edition)
|
|
32
|
+
|
|
33
|
+
A self-host Community Edition runtime (framework + this MIT console, without the
|
|
34
|
+
cloud `@objectstack/service-ai-studio` package) serves no `ask`/`build` agent.
|
|
35
|
+
The console now hides every AI entry point via runtime, server-pushed gating —
|
|
36
|
+
no build-time edition flag, no tree-shake.
|
|
37
|
+
|
|
38
|
+
Crucially, gating is driven off the **agent catalog** (`GET /api/v1/ai/agents`),
|
|
39
|
+
not the discovery `services.ai` flag: the open-source framework keeps a headless
|
|
40
|
+
`@objectstack/service-ai` that still reports `services.ai` as available, so a CE
|
|
41
|
+
runtime can report AI "available" while serving zero agents. The catalog is the
|
|
42
|
+
real "is there an agent to answer?" signal.
|
|
43
|
+
|
|
44
|
+
- New `useAiSurfaceEnabled()` hook + `RequireAiSurface` route guard (exported).
|
|
45
|
+
- `/ai*` routes redirect to home when no agent is served; the FAB, top-bar AI
|
|
46
|
+
link and the metadata designers' "Ask AI" buttons hide; `AiChatPage` shows a
|
|
47
|
+
graceful "AI unavailable" state instead of an agent-less echo chat.
|
|
48
|
+
- Fully additive for cloud installs — when an agent is served, every AI surface
|
|
49
|
+
renders and works as before.
|
|
50
|
+
|
|
51
|
+
- 616157a: feat(studio): multi-hop relationship fields in the dataset designer (ADR-0071)
|
|
52
|
+
|
|
53
|
+
The dataset designer's field catalog and Included-relationships picker now
|
|
54
|
+
support multi-hop relationship paths (`account.owner.region`), matching the
|
|
55
|
+
framework's multi-hop join support (ADR-0071 P2):
|
|
56
|
+
|
|
57
|
+
- `useDatasetFieldCatalog` walks each included path hop-by-hop, fetching every
|
|
58
|
+
object along the chain, so `path.field` options surface for fields two–three
|
|
59
|
+
to-one hops deep (grouped under a chained `Account → Owner → User` heading).
|
|
60
|
+
- The Included-relationships combo offers one level deeper along each
|
|
61
|
+
already-included path (drill `account` → `account.owner`), capped at 3 hops.
|
|
62
|
+
- The author-time "relationship not in Included" warning generalizes to the full
|
|
63
|
+
relationship path (`account.owner`), with one-click "Add it".
|
|
64
|
+
|
|
65
|
+
Single-hop datasets are unchanged.
|
|
66
|
+
|
|
67
|
+
- 6668759: feat(console): entitlement- & state-aware environment actions
|
|
68
|
+
|
|
69
|
+
The `sys_environment` list now presents the right create affordance for the
|
|
70
|
+
org's state (born-with-env) instead of POST-then-error:
|
|
71
|
+
|
|
72
|
+
- **No production env** (historical orgs) → "Set up your production environment";
|
|
73
|
+
the create POST provisions the org's one production env — this path never errors.
|
|
74
|
+
- **Has prod env, free plan** → an "Add environment" button that opens a friendly
|
|
75
|
+
upgrade prompt (CTA to billing) instead of POSTing into a 403.
|
|
76
|
+
- **Has prod env, paid plan** → "Add development environment" creates a dev env.
|
|
77
|
+
|
|
78
|
+
The action runtime's `apiHandler` now also turns the cloud env-create entitlement
|
|
79
|
+
403s (`DEV_ENV_PLAN_LOCKED` / `DEV_ENV_LIMIT` / `PRODUCTION_ENV_LIMIT`) into a
|
|
80
|
+
friendly upgrade/limit dialog with a CTA rather than a red error toast — a safety
|
|
81
|
+
net that covers any path. State is resolved from the new org-scoped
|
|
82
|
+
`GET /cloud/environment-entitlements` summary, with a row-derived `hasProductionEnv`
|
|
83
|
+
fallback so the production-setup path works even against an older control plane.
|
|
84
|
+
|
|
85
|
+
- 41c60c4: Flow builder: variable data-picker for expression / template config fields. Expression and template surfaces (decision Branches, edge Condition, Assignment values, Screen description, CRUD field values / filter, subflow / script inputs) now show a "{x}" picker listing the references in scope at that node — flow variables, upstream node outputs, the trigger record's fields, and any enclosing loop item — resolved graph-aware by walking the flow back from the node. Selecting a reference inserts the correctly-braced token at the cursor (bare CEL in `expression` fields, `{var}` in template fields), handling the ADR-0032 brace-in-CEL trap for the author. Free-text typing is unchanged and an empty scope degrades to a plain input.
|
|
86
|
+
- d23db5c: feat(detail): related-list add-by-picker (generic m2m/junction) + a generic "Assigned Users" management UI on permission sets (assign ai_seat and any role with zero bespoke CRUD; server-side cap errors surface inline).
|
|
87
|
+
|
|
88
|
+
### Patch Changes
|
|
89
|
+
|
|
90
|
+
- 81ad9aa: feat(studio): package lifecycle UI — Duplicate base, Adopt loose items, structure-only delete (ADR-0070 D4/D5/D6)
|
|
91
|
+
|
|
92
|
+
`PackageDetailSheet` gains the user-facing affordances for the package-as-
|
|
93
|
+
lifecycle-unit work:
|
|
94
|
+
- **Duplicate** → `POST /packages/:id/duplicate` (clone a base into a new
|
|
95
|
+
writable package; D4).
|
|
96
|
+
- **Adopt loose items** → `POST /packages/:id/adopt-orphans` (migrate every
|
|
97
|
+
package-less orphan into this base; D5).
|
|
98
|
+
- **Delete** now asks whether to drop records too (`?keepData`) — structure-only
|
|
99
|
+
vs everything (D4 Q3).
|
|
100
|
+
|
|
101
|
+
D6 guardrail test: the scope selector never defaults to the package-less
|
|
102
|
+
`Local / Custom` sentinel (`writableBaseOptions` excludes it; real bases sort
|
|
103
|
+
first).
|
|
104
|
+
|
|
105
|
+
- 4b1cb7a: feat(studio): package-first create flow — prompt or redirect to a writable base (ADR-0070 D3)
|
|
106
|
+
|
|
107
|
+
Studio's create entry points no longer let a new metadata item land in a code
|
|
108
|
+
package or the package-less "Local / Custom" bucket. ResourceListPage's create
|
|
109
|
+
gate (`handleCreate`) now: opens the create-base dialog when no writable base
|
|
110
|
+
exists; redirects into the first base when the active scope is Local/none but
|
|
111
|
+
bases exist; otherwise proceeds normally. Adds package-scope helpers
|
|
112
|
+
(`isLocalScope` / `writableBaseOptions`) with tests, surfaces the kernel's
|
|
113
|
+
`writable_package_required` (422) as an actionable error in ResourceEditPage,
|
|
114
|
+
and exports `CreatePackageDialog` from PackagesPage for reuse.
|
|
115
|
+
|
|
116
|
+
- 8c2191d: fix(console): polished, localized "Assigned Users" management for permission sets — resolves users to name/email (no raw id), zh/en localized, friendly inline cap message (drops the dev `[Tag]` prefix), people-rows with visible remove + add-via-picker.
|
|
117
|
+
- 6028192: fix(console): gate the AI surface on the access-filtered agent catalog (per-user), not the deployment-wide service-ai capability
|
|
118
|
+
|
|
119
|
+
`useAiSurfaceEnabled` keys off `GET /api/v1/ai/agents` again (>= 1 agent → AI shows), reverting objectui#1992. The agent-catalog route is now access-filtered server-side (ADR-0049 / ADR-0068): it returns only the agents the caller may chat, so a user WITHOUT the per-user AI seat (`ai_seat`) gets an empty catalog and the whole AI surface (FAB, `/ai` routes, top-bar + designer "Ask AI") hides for them — instead of showing a control that 403s on click. The discovery `services.ai` flag is deployment-wide and cannot express per-user seating, so it is the wrong signal for the AI-seat gate. Community-Edition gating is unaffected: no service-ai → no agents → empty catalog → hidden.
|
|
120
|
+
|
|
121
|
+
- e575da0: fix(ai): stop the AI composer placeholder doubling to "Ask Ask…" for the Ask agent
|
|
122
|
+
|
|
123
|
+
The composer placeholder is `Ask {agent}…`, which reads fine for most agents
|
|
124
|
+
("Ask Build…") but doubles to "Ask Ask…" for the data-query agent whose label is
|
|
125
|
+
literally "Ask". The Ask agent now uses its purpose-built placeholder
|
|
126
|
+
(`console.ai.askAnything` → "Ask anything…", already localized) instead. Found
|
|
127
|
+
dogfooding the AI Ask flow.
|
|
128
|
+
|
|
129
|
+
- cde7502: fix(form): create/edit record modal now honors the object's default form view
|
|
130
|
+
|
|
131
|
+
The "New <object>" modal (and the modal edit form) rendered every field from
|
|
132
|
+
the raw object schema, in schema order — ignoring the curated sections + field
|
|
133
|
+
selection/order defined in the object's default FORM VIEW. Customizing the form
|
|
134
|
+
view (section grouping, field selection/order) had no effect on the create
|
|
135
|
+
modal; only `tabbed` views were partially honored, while a `simple` view with
|
|
136
|
+
curated sections was dropped entirely.
|
|
137
|
+
|
|
138
|
+
New `resolveFormViewLayout(objectDef)` helper resolves the default form view
|
|
139
|
+
(`objectDef.form ?? formViews.default`) into the modal's layout props (curated
|
|
140
|
+
`sections`, `contentLayout: 'tabbed'`, and master-detail `subforms`), mirroring
|
|
141
|
+
the full-screen `RecordFormPage`. It is wired into:
|
|
142
|
+
|
|
143
|
+
- the global New/Edit `ModalForm` in `AppContent` (replacing the tabbed-only
|
|
144
|
+
inline logic so `simple` sectioned views are honored too), and
|
|
145
|
+
- `useActionModal` (action-opened forms), which previously passed no
|
|
146
|
+
`fields`/`sections` and so fell back to the whole object schema.
|
|
147
|
+
|
|
148
|
+
When the object declares no form view — or one without sections — the modal
|
|
149
|
+
keeps its prior flat-field behavior. Frontend-only.
|
|
150
|
+
|
|
151
|
+
- 0d8dbda: fix(metadata-admin): dataset filter builder ignores incomplete conditions
|
|
152
|
+
|
|
153
|
+
`groupToCondition` emitted a condition for any row that had a `field`, even when
|
|
154
|
+
its value was still blank — producing a silently-wrong filter like
|
|
155
|
+
`{ organization_id: { $eq: "" } }` (matches only empty → excludes everything)
|
|
156
|
+
instead of "no filter". Now rows with an empty/`undefined`/`[]` value are skipped
|
|
157
|
+
(value-less operators like is-empty / is-not-empty are still kept). Applies to both
|
|
158
|
+
the dataset Scope filter and per-measure filters. Found by dogfooding.
|
|
159
|
+
|
|
160
|
+
- e8c1c85: fix(metadata-admin): re-base a dataset when its base object changes
|
|
161
|
+
|
|
162
|
+
A dataset's joins (`include`), dimensions, measures, and filter all reference the
|
|
163
|
+
base object's fields. Changing the base object left those referencing the OLD
|
|
164
|
+
object — stale field refs that silently produce broken/ambiguous queries. Now a
|
|
165
|
+
real object change clears the object-dependent config (selecting the same object
|
|
166
|
+
is a no-op), and a heads-up note appears while there is config that a change would
|
|
167
|
+
clear. Found by dogfooding (G1).
|
|
168
|
+
|
|
169
|
+
- 0119ff4: Designer derives create defaults from the spec's create seed (/meta/types)
|
|
170
|
+
|
|
171
|
+
The metadata create flow now builds a new item's body from the server's authoritative `createSeed` (delivered per type on the `/meta/types` registry entry — the single source of truth in `@objectstack/spec`) instead of the locally hardcoded `createDefaults`, falling back to `createDefaults` when the server provides no seed (older server, or canvas-create types). This closes the drift loop behind the "designer emits a minimal shape the spec rejects → create→save 422" family (dashboard `layout`, action `body`): the structural create defaults now come from the same place the spec validates against, so they cannot diverge. Extracted as the pure, unit-tested `buildCreateModeBody`.
|
|
172
|
+
|
|
173
|
+
- 8e7c1da: fix(preview): draft-preview bar no longer demands a redundant Publish when nothing is pending
|
|
174
|
+
|
|
175
|
+
Under the auto-publish posture an AI build leaves zero pending drafts, yet opening a
|
|
176
|
+
draft preview still showed "Draft preview — Nothing here is live until you publish."
|
|
177
|
+
alongside "Changes (0)" and a Publish button — a self-contradicting, no-op call to
|
|
178
|
+
action. `DraftPreviewBar` now reflects the real pending-draft count: when it is
|
|
179
|
+
known to be zero the bar softens to a neutral preview indicator and drops the
|
|
180
|
+
Publish/Changes affordances; an unknown count (still loading / fetch failed) keeps
|
|
181
|
+
the publish path. `HomePage` (count-gated) and `RuntimeDraftBar` (draft-gated)
|
|
182
|
+
already behaved this way — this aligns the third surface.
|
|
183
|
+
|
|
184
|
+
- 522a54c: feat(studio): make the flow-canvas error banner clickable
|
|
185
|
+
|
|
186
|
+
The inline structural-error banner (ADR-0044 cycle surfacing) is now driven by
|
|
187
|
+
the unified `problems` list, and each row with a concrete target is clickable —
|
|
188
|
+
clicking it selects and pans-to-reveal the offending node/edge (the same reveal
|
|
189
|
+
the Problems panel performs). So the always-visible banner is actionable without
|
|
190
|
+
opening the panel. Drops the now-redundant `validationErrors` string prop: the
|
|
191
|
+
banner, the Problems panel, and the on-canvas badges all share one source.
|
|
192
|
+
|
|
193
|
+
- cdc6246: Flow builder (#1934): expression problems — ADR-0032 brace/shape errors and scope-aware "unknown reference" warnings — now also surface in the flow **Problems panel** and as on-canvas **node/edge badges** (#1972), not just inline in the inspector. A `{record.x}` brace-in-CEL mistake or a typo'd variable is now visible at the flow level without opening each node. The start node's bare trigger-record fields are excluded from the ref check to avoid false positives (the inline inspector check still covers them).
|
|
194
|
+
- 7fe2735: Flow builder data-picker (#1934): the cursor-insertion math is extracted into a pure `insertToken` helper with unit tests (alongside `formatToken`) — bare CEL vs `{var}` template insertion, append / mid-string / selection-replace, and clamping a reversed or out-of-range selection. Pure refactor, no behavior change.
|
|
195
|
+
- 3f529a8: refactor(studio): derive the flow red-error highlight from the unified problem list (one validateFlowDraft pass)
|
|
196
|
+
|
|
197
|
+
Follow-up to #1972 (Problems panel + badges) and #1976 (clickable banner). The
|
|
198
|
+
flow preview still ran `validateFlowDraft` twice per render — once in
|
|
199
|
+
`buildFlowProblems` (badges / banner / panel) and again in a separate memo that
|
|
200
|
+
derived the red node/edge ring/stroke — with the cycle-highlight logic duplicated
|
|
201
|
+
between them.
|
|
202
|
+
|
|
203
|
+
`buildFlowProblems` is now the single validation pass: a new
|
|
204
|
+
`deriveInvalidElements(problems)` produces the red error set (errors only; a
|
|
205
|
+
cycle paints its whole loop via a per-problem `highlight` set while its badge +
|
|
206
|
+
reveal stay on the closing edge). The preview drops its second `validateFlowDraft`
|
|
207
|
+
call. The clickable banner (#1976), badges, and panel are unchanged — all four
|
|
208
|
+
surfaces now derive from one list, so they cannot drift.
|
|
209
|
+
|
|
210
|
+
- 0b9c96c: Flow builder data-picker follow-ups (#1934): (1) a scope-aware "unknown reference" warning pairs the picker with inline validation — a typed reference whose root isn't in scope at the node is flagged with a nearest-match "did you mean?" hint (conservative: root-only, skips function calls / string literals / runtime globals; non-blocking amber). (2) Assignment values authored in the array form `[{ variable, value }]` now render in the key/value editor (and get the picker) instead of falling back to Advanced JSON; the editor reads both the object-map and array shapes and preserves whichever was authored. (3) A script `code` body (JS/TS, not a `{var}` template) now inserts bare references via a `refMode` field override — `{x}` is a syntax error in a script.
|
|
211
|
+
- 47537fe: Flow builder data-picker (#1934): inline validation now also shows on the repeater surfaces that carry the picker — decision **Branches** expressions, screen field **"visible when"**, and key/value **values** — not just single fields. Each shows the ADR-0032 brace error (red) or a scope-aware "unknown reference" warning (amber) via a shared `FlowExprIssue` line. The trigger-record picker also offers `previous.<field>` references on update / change / before-update triggers.
|
|
212
|
+
- 17ba30d: feat(studio): on-canvas validation badges + a Problems panel for the flow builder
|
|
213
|
+
|
|
214
|
+
Flow validation only surfaced as a top banner ("…N error(s)") that didn't point
|
|
215
|
+
to the offending element — in a non-trivial flow you couldn't tell _which_ node
|
|
216
|
+
or edge was wrong. The simulator's `validateFlowDraft` already detected the
|
|
217
|
+
structural problems (no resolvable entry, unreachable nodes, a decision with no
|
|
218
|
+
default branch, duplicate node ids, dangling edges, un-declared cycles); they
|
|
219
|
+
just weren't shown on the canvas. This was a surfacing gap, not a detection one.
|
|
220
|
+
|
|
221
|
+
The flow preview now:
|
|
222
|
+
|
|
223
|
+
- renders an error / warning **badge** on each offending node and edge, with the
|
|
224
|
+
issue message(s) as its tooltip;
|
|
225
|
+
- adds a **Problems panel** listing every issue (structural + the server
|
|
226
|
+
`_diagnostics` already attached to the layered record); clicking a row selects
|
|
227
|
+
and reveals (pans to) the node/edge;
|
|
228
|
+
- clears badges + rows as issues are resolved (everything derives from the live
|
|
229
|
+
draft).
|
|
230
|
+
|
|
231
|
+
`validateFlowDraft` now tags dangling-edge errors with their endpoints so they
|
|
232
|
+
key to the offending connection, and a new `flow-problems` module maps both
|
|
233
|
+
sources onto concrete canvas elements (node id / stable edge key). Server
|
|
234
|
+
diagnostics reach the preview through a new optional `diagnostics` prop on
|
|
235
|
+
`MetadataPreviewProps`.
|
|
236
|
+
|
|
237
|
+
- 104d181: fix(studio): flow wait-node inspector tolerates the loose `config` shape
|
|
238
|
+
|
|
239
|
+
The wait-node property form read only the spec-canonical
|
|
240
|
+
`waitEventConfig.{eventType,signalName,…}`, but the engine also accepts a looser
|
|
241
|
+
`config.{eventType,…}` shape — which the canonical `showcase_budget_approval`
|
|
242
|
+
(and AI-authored flows) use. So a showcase-shaped wait node opened in the
|
|
243
|
+
designer showed blank "Wait for" / "Signal name" fields.
|
|
244
|
+
|
|
245
|
+
Flow config fields gain an optional `fallbackPath`: reads fall back to it (so
|
|
246
|
+
loose-shape wait nodes display, and dependent fields reveal), writes target the
|
|
247
|
+
canonical path and prune the fallback (migrate-on-edit), and the fallback's
|
|
248
|
+
config key is suppressed from the Advanced block. The `wait` fields now fall
|
|
249
|
+
back to `config.*`, so the designer matches the engine's tolerance. Pairs with
|
|
250
|
+
the ADR-0044 revise-loop authoring (#1954).
|
|
251
|
+
|
|
252
|
+
- 1fa5982: fix(studio): preview joined reports in the report editor (was "design blind")
|
|
253
|
+
|
|
254
|
+
Found dogfooding report design in Studio as a business user. The report editor's
|
|
255
|
+
live preview only rendered single dataset-bound reports — a `joined` report
|
|
256
|
+
(which carries its data on `blocks`, with no top-level `dataset`) fell through to
|
|
257
|
+
the "Bind a dataset to preview this report" empty state, so an author building a
|
|
258
|
+
joined report saw nothing and designed blind.
|
|
259
|
+
|
|
260
|
+
`ReportPreview` now renders a joined report (≥1 dataset-bound block) through the
|
|
261
|
+
same runtime `ReportRenderer` (→ `DatasetReportRenderer`, which already stacks
|
|
262
|
+
the blocks), keeping the preview pixel-equal with the runtime, and shows a
|
|
263
|
+
joined-aware empty state ("Add a block…") when no block is bound yet.
|
|
264
|
+
|
|
265
|
+
- Updated dependencies [8e7c1da]
|
|
266
|
+
- Updated dependencies [cf746c9]
|
|
267
|
+
- Updated dependencies [d23db5c]
|
|
268
|
+
- @object-ui/i18n@7.2.0
|
|
269
|
+
- @object-ui/auth@7.2.0
|
|
270
|
+
- @object-ui/types@7.2.0
|
|
271
|
+
- @object-ui/components@7.2.0
|
|
272
|
+
- @object-ui/fields@7.2.0
|
|
273
|
+
- @object-ui/react@7.2.0
|
|
274
|
+
- @object-ui/collaboration@7.2.0
|
|
275
|
+
- @object-ui/core@7.2.0
|
|
276
|
+
- @object-ui/data-objectstack@7.2.0
|
|
277
|
+
- @object-ui/layout@7.2.0
|
|
278
|
+
- @object-ui/permissions@7.2.0
|
|
279
|
+
- @object-ui/plugin-editor@7.2.0
|
|
280
|
+
- @object-ui/providers@7.2.0
|
|
281
|
+
|
|
3
282
|
## 7.1.0
|
|
4
283
|
|
|
5
284
|
### Minor Changes
|
|
@@ -23,7 +23,7 @@ import { usePreviewDrafts } from '../preview/PreviewModeContext';
|
|
|
23
23
|
import { PreviewDraftEmptyState } from '../preview/PreviewDraftEmptyState';
|
|
24
24
|
import { ExpressionProvider, evaluateVisibility } from '../providers/ExpressionProvider';
|
|
25
25
|
import { useTrackRouteAsRecent } from '../hooks/useTrackRouteAsRecent';
|
|
26
|
-
import { resolveRecordFormTarget, resolveNavigateCreateUrl, resolveNavigateEditUrl } from '../utils/recordFormNavigation';
|
|
26
|
+
import { resolveRecordFormTarget, resolveFormViewLayout, resolveNavigateCreateUrl, resolveNavigateEditUrl } from '../utils/recordFormNavigation';
|
|
27
27
|
import { matchAppBySegment } from '../utils/appRoute';
|
|
28
28
|
import { resolveHref } from '@object-ui/layout';
|
|
29
29
|
import { ExpressionEvaluator } from '@object-ui/core';
|
|
@@ -401,20 +401,14 @@ export function AppContent({ extraRoutes, extraRoutesNoApp } = {}) {
|
|
|
401
401
|
objectName: currentObjectDef.name,
|
|
402
402
|
mode: editingRecord ? 'edit' : 'create',
|
|
403
403
|
recordId: editingRecord?.id,
|
|
404
|
-
//
|
|
405
|
-
//
|
|
406
|
-
//
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
//
|
|
410
|
-
//
|
|
411
|
-
|
|
412
|
-
...(() => {
|
|
413
|
-
const fd = currentObjectDef.form ?? currentObjectDef.formViews?.default;
|
|
414
|
-
return fd?.type === 'tabbed' && Array.isArray(fd?.sections)
|
|
415
|
-
? { contentLayout: 'tabbed', sections: fd.sections }
|
|
416
|
-
: {};
|
|
417
|
-
})(),
|
|
404
|
+
// Honor the object's DEFAULT FORM VIEW: curated sections (field
|
|
405
|
+
// selection + order + grouping), `contentLayout: 'tabbed'` when the
|
|
406
|
+
// view is tabbed, and inline child collections (master-detail).
|
|
407
|
+
// When the view declares sections they drive the modal layout and
|
|
408
|
+
// win over the flat `fields` list below; otherwise this resolves to
|
|
409
|
+
// {} and `fields` (every field, raw schema order) is used as before.
|
|
410
|
+
// `formType` stays 'modal' (the container). (#1890 / ADR-0050.)
|
|
411
|
+
...resolveFormViewLayout(currentObjectDef),
|
|
418
412
|
title: editingRecord
|
|
419
413
|
? t('form.editTitle', { object: objectLabel(currentObjectDef) })
|
|
420
414
|
: t('form.createTitle', { object: objectLabel(currentObjectDef) }),
|
|
@@ -44,6 +44,22 @@ export declare function ConnectedShell({ children }: {
|
|
|
44
44
|
export declare function RequireOrganization({ children }: {
|
|
45
45
|
children: ReactNode;
|
|
46
46
|
}): import("react").JSX.Element;
|
|
47
|
+
/**
|
|
48
|
+
* RequireAiSurface — gate for the `/ai*` routes. The console is edition-agnostic
|
|
49
|
+
* (MIT, runtime-gated): a Community Edition runtime serves no AI agent, so a
|
|
50
|
+
* stale `/ai` bookmark or external link must not land on a broken/empty chat.
|
|
51
|
+
* When the server serves no agent it redirects to `redirectTo` (home) instead.
|
|
52
|
+
*
|
|
53
|
+
* Availability is the live agent catalog (see {@link useAiSurfaceEnabled}) — the
|
|
54
|
+
* same signal every other AI entry point now gates on, so the route is reachable
|
|
55
|
+
* from exactly the entry points that are visible (no shown CTA that bounces back
|
|
56
|
+
* to home, no hidden FAB to a working route). Waits for the catalog to resolve
|
|
57
|
+
* before deciding so the redirect never flashes on first paint.
|
|
58
|
+
*/
|
|
59
|
+
export declare function RequireAiSurface({ children, redirectTo, }: {
|
|
60
|
+
children: ReactNode;
|
|
61
|
+
redirectTo?: string;
|
|
62
|
+
}): import("react").JSX.Element;
|
|
47
63
|
/**
|
|
48
64
|
* AuthenticatedRoute — convenience wrapper composing AuthGuard + ConnectedShell
|
|
49
65
|
* (+ optional RequireOrganization). Covers the common case for protected
|
|
@@ -10,7 +10,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
10
10
|
* JSX in App.tsx. See examples/console-starter/src/App.tsx for a minimal example,
|
|
11
11
|
* apps/console/src/App.tsx for one with custom system routes + CreateApp.
|
|
12
12
|
*/
|
|
13
|
-
import { Suspense, useEffect, useRef } from 'react';
|
|
13
|
+
import { Suspense, useEffect, useRef, useState } from 'react';
|
|
14
14
|
import { Navigate, useLocation } from 'react-router-dom';
|
|
15
15
|
import { AuthGuard, useAuth } from '@object-ui/auth';
|
|
16
16
|
import { useObjectTranslation } from '@object-ui/i18n';
|
|
@@ -18,6 +18,7 @@ import { SchemaRendererProvider } from '@object-ui/react';
|
|
|
18
18
|
import { createObjectStackUserStateAdapter } from '@object-ui/data-objectstack';
|
|
19
19
|
import { AdapterProvider, useAdapter } from '../providers/AdapterProvider';
|
|
20
20
|
import { MetadataProvider, useMetadata } from '../providers/MetadataProvider';
|
|
21
|
+
import { useAiSurfaceEnabled } from '../hooks/useAiSurface';
|
|
21
22
|
import { PreviewModeProvider } from '../preview/PreviewModeContext';
|
|
22
23
|
import { NavigationProvider } from '../context/NavigationContext';
|
|
23
24
|
import { FavoritesProvider } from '../context/FavoritesProvider';
|
|
@@ -131,13 +132,53 @@ function UserStateBridge() {
|
|
|
131
132
|
* deployments (empty organizations list) render through.
|
|
132
133
|
*/
|
|
133
134
|
export function RequireOrganization({ children }) {
|
|
134
|
-
const { activeOrganization, organizations, isOrganizationsLoading } = useAuth();
|
|
135
|
+
const { activeOrganization, organizations, isOrganizationsLoading, getAuthConfig } = useAuth();
|
|
136
|
+
// Multi-org (cloud) deployments route a brand-new, org-less user into the
|
|
137
|
+
// guided "Create your workspace" flow; single-tenant self-host renders through.
|
|
138
|
+
const [multiOrgEnabled, setMultiOrgEnabled] = useState(null);
|
|
139
|
+
useEffect(() => {
|
|
140
|
+
let cancelled = false;
|
|
141
|
+
getAuthConfig()
|
|
142
|
+
.then((cfg) => { if (!cancelled)
|
|
143
|
+
setMultiOrgEnabled(cfg?.features?.multiOrgEnabled !== false); })
|
|
144
|
+
.catch(() => { if (!cancelled)
|
|
145
|
+
setMultiOrgEnabled(false); });
|
|
146
|
+
return () => { cancelled = true; };
|
|
147
|
+
}, [getAuthConfig]);
|
|
135
148
|
if (isOrganizationsLoading)
|
|
136
149
|
return _jsx(LoadingFallback, {});
|
|
137
150
|
const orgList = organizations ?? [];
|
|
138
151
|
const orgFeatureEnabled = orgList.length > 0 || !!activeOrganization;
|
|
139
152
|
if (orgFeatureEnabled && !activeOrganization)
|
|
140
153
|
return _jsx(Navigate, { to: "/organizations", replace: true });
|
|
154
|
+
// No org at all: on multi-org, send them to /organizations (the create
|
|
155
|
+
// screen); wait for the flag so we don't flash /home then redirect.
|
|
156
|
+
if (orgList.length === 0 && !activeOrganization) {
|
|
157
|
+
if (multiOrgEnabled === null)
|
|
158
|
+
return _jsx(LoadingFallback, {});
|
|
159
|
+
if (multiOrgEnabled)
|
|
160
|
+
return _jsx(Navigate, { to: "/organizations", replace: true });
|
|
161
|
+
}
|
|
162
|
+
return _jsx(_Fragment, { children: children });
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* RequireAiSurface — gate for the `/ai*` routes. The console is edition-agnostic
|
|
166
|
+
* (MIT, runtime-gated): a Community Edition runtime serves no AI agent, so a
|
|
167
|
+
* stale `/ai` bookmark or external link must not land on a broken/empty chat.
|
|
168
|
+
* When the server serves no agent it redirects to `redirectTo` (home) instead.
|
|
169
|
+
*
|
|
170
|
+
* Availability is the live agent catalog (see {@link useAiSurfaceEnabled}) — the
|
|
171
|
+
* same signal every other AI entry point now gates on, so the route is reachable
|
|
172
|
+
* from exactly the entry points that are visible (no shown CTA that bounces back
|
|
173
|
+
* to home, no hidden FAB to a working route). Waits for the catalog to resolve
|
|
174
|
+
* before deciding so the redirect never flashes on first paint.
|
|
175
|
+
*/
|
|
176
|
+
export function RequireAiSurface({ children, redirectTo = '/home', }) {
|
|
177
|
+
const { enabled, isLoading } = useAiSurfaceEnabled();
|
|
178
|
+
if (isLoading)
|
|
179
|
+
return _jsx(LoadingFallback, {});
|
|
180
|
+
if (!enabled)
|
|
181
|
+
return _jsx(Navigate, { to: redirectTo, replace: true });
|
|
141
182
|
return _jsx(_Fragment, { children: children });
|
|
142
183
|
}
|
|
143
184
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
3
3
|
/**
|
|
4
4
|
* AiChatPage — full-page ChatGPT-style AI surface.
|
|
@@ -17,7 +17,7 @@ import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
|
|
17
17
|
import { useAuth } from '@object-ui/auth';
|
|
18
18
|
import { useObjectTranslation } from '@object-ui/i18n';
|
|
19
19
|
import { toast } from 'sonner';
|
|
20
|
-
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Button, ShareDialog, Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, cn, } from '@object-ui/components';
|
|
20
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Button, ShareDialog, Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, Empty, EmptyTitle, EmptyDescription, cn, } from '@object-ui/components';
|
|
21
21
|
import { PanelLeft, PanelLeftClose, PanelLeftOpen, Share2 } from 'lucide-react';
|
|
22
22
|
import { ChatbotEnhanced, useAgents, useObjectChat, useHitlInChat, resolveDefaultAgentName, PLATFORM_DEFAULT_AGENT, agentRouteName, resolveAgentParam, isBuiltinAgentName, isBuildAgent, isAskAgent, publishHealthFromResponse, detectDraftResult, detectProposedPlan, buildProgressFromDraftReview, } from '@object-ui/plugin-chatbot';
|
|
23
23
|
import { AppHeader } from '../../layout/AppHeader';
|
|
@@ -371,8 +371,15 @@ export function AiChatPage({ apiBase: apiBaseProp, defaultAgent: defaultAgentPro
|
|
|
371
371
|
const apiBase = useMemo(() => resolveApiBase(apiBaseProp), [apiBaseProp]);
|
|
372
372
|
const env = import.meta.env ?? {};
|
|
373
373
|
const envDefaultAgent = env.VITE_AI_DEFAULT_AGENT;
|
|
374
|
-
const { agents, isLoading: agentsLoading, error: agentsError } = useAgents({ apiBase });
|
|
374
|
+
const { agents, isLoading: agentsLoading, error: agentsError, refetch: refetchAgents } = useAgents({ apiBase });
|
|
375
375
|
const catalogNames = useMemo(() => agents.map((a) => a.name), [agents]);
|
|
376
|
+
// Catalog resolved with no agent to talk to. The `/ai` route guard already
|
|
377
|
+
// redirects when discovery reports AI unavailable (Community Edition), so this
|
|
378
|
+
// is the secondary safety net: a deployment that reports AI enabled but serves
|
|
379
|
+
// no agent (misconfig), a transient `/agents` failure, or a `VITE_AI_BASE_URL`
|
|
380
|
+
// server that returns an empty list. Either way, degrade to a graceful state
|
|
381
|
+
// instead of the agent-less echo chat (autoResponse) that ChatPane falls into.
|
|
382
|
+
const noAgents = !agentsLoading && agents.length === 0;
|
|
376
383
|
// Is the first path segment an agent? It is when it resolves to one (friendly
|
|
377
384
|
// alias / new id / legacy id). When it doesn't, it's a legacy bare
|
|
378
385
|
// `/ai/:conversationId` link (redirected below). `undefined` = catalog still
|
|
@@ -598,11 +605,27 @@ export function AiChatPage({ apiBase: apiBaseProp, defaultAgent: defaultAgentPro
|
|
|
598
605
|
void t1;
|
|
599
606
|
void t2;
|
|
600
607
|
}, [conversationId]);
|
|
601
|
-
return (_jsxs("div", { className: "flex h-svh w-full flex-col bg-background", "data-testid": "ai-chat-page", children: [_jsxs("header", { className: "sticky top-0 z-30 flex h-14 w-full shrink-0 items-center gap-2 border-b bg-background/95 px-2 backdrop-blur sm:px-4", children: [_jsx(Button, { variant: "ghost", size: "icon", className: "h-8 w-8 shrink-0 md:hidden", onClick: () => setMobileChatsOpen(true), "aria-label": t('console.ai.openChats'), "data-testid": "ai-chat-mobile-sidebar-trigger", children: _jsx(PanelLeft, { className: "h-4 w-4" }) }), _jsx(Button, { variant: "ghost", size: "icon", className: "hidden h-8 w-8 shrink-0 md:inline-flex", onClick: toggleChatsCollapsed, "aria-label": chatsCollapsed
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
608
|
+
return (_jsxs("div", { className: "flex h-svh w-full flex-col bg-background", "data-testid": "ai-chat-page", children: [_jsxs("header", { className: "sticky top-0 z-30 flex h-14 w-full shrink-0 items-center gap-2 border-b bg-background/95 px-2 backdrop-blur sm:px-4", children: [!noAgents && (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "ghost", size: "icon", className: "h-8 w-8 shrink-0 md:hidden", onClick: () => setMobileChatsOpen(true), "aria-label": t('console.ai.openChats'), "data-testid": "ai-chat-mobile-sidebar-trigger", children: _jsx(PanelLeft, { className: "h-4 w-4" }) }), _jsx(Button, { variant: "ghost", size: "icon", className: "hidden h-8 w-8 shrink-0 md:inline-flex", onClick: toggleChatsCollapsed, "aria-label": chatsCollapsed
|
|
609
|
+
? t('console.ai.showChats', { defaultValue: 'Show chats' })
|
|
610
|
+
: t('console.ai.hideChats', { defaultValue: 'Hide chats' }), title: chatsCollapsed
|
|
611
|
+
? t('console.ai.showChats', { defaultValue: 'Show chats' })
|
|
612
|
+
: t('console.ai.hideChats', { defaultValue: 'Hide chats' }), "data-testid": "ai-chat-collapse-sidebar-trigger", "aria-pressed": chatsCollapsed, children: chatsCollapsed ? _jsx(PanelLeftOpen, { className: "h-4 w-4" }) : _jsx(PanelLeftClose, { className: "h-4 w-4" }) })] })), _jsx("div", { className: "min-w-0 flex-1", children: _jsx(AppHeader, { variant: "home" }) })] }), noAgents ? (_jsx(AiUnavailable, { hasError: Boolean(agentsError), onRetry: refetchAgents, onHome: () => navigate('/home'), t: t })) : (_jsxs(_Fragment, { children: [_jsx(Sheet, { open: mobileChatsOpen, onOpenChange: setMobileChatsOpen, children: _jsxs(SheetContent, { side: "left", className: "w-[320px] p-0 sm:max-w-[360px]", "data-testid": "ai-chat-mobile-sidebar", children: [_jsxs(SheetHeader, { className: "sr-only", children: [_jsx(SheetTitle, { children: t('console.ai.chats') }), _jsx(SheetDescription, { children: t('console.ai.chatsDescription') })] }), _jsx(ConversationsSidebar, { userId: userId, apiBase: apiBase, activeAgent: activeAgent, refreshKey: refreshKey, titleHints: titleHints, className: "h-full border-r-0", onNavigate: () => setMobileChatsOpen(false) })] }) }), conversationId && (_jsx(ShareDialog, { open: shareOpen, onOpenChange: setShareOpen, objectName: "ai_conversations", recordId: conversationId, recordLabel: "this conversation", apiBase: restApiBase, publicBaseUrl: publicShareBase })), _jsxs("div", { className: "flex min-h-0 flex-1 w-full bg-muted/20", children: [!chatsCollapsed && (_jsx(ConversationsSidebar, { userId: userId, apiBase: apiBase, activeAgent: activeAgent, refreshKey: refreshKey, titleHints: titleHints, className: "hidden w-72 shrink-0 border-r md:flex" })), _jsx("main", { className: "flex min-w-0 flex-1 flex-col", children: _jsx(ChatPane, { agents: agents, agentsLoading: agentsLoading, agentsError: agentsError, activeAgent: activeAgent, chatApi: chatApi, apiBase: apiBase, conversationId: conversationId, initialMessages: initialMessages, onSent: handleSent, onShare: () => setShareOpen(true), onCanvasOpenChange: handleCanvasOpenChange }, `${chatApi ?? 'local'}:${conversationId ?? 'pending'}`) })] })] }))] }));
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Graceful state for `/ai` when the agent catalog resolved empty — shown
|
|
616
|
+
* instead of an agent-less echo chat. `hasError` distinguishes "AI not enabled
|
|
617
|
+
* on this deployment" (Community Edition) from "couldn't reach the AI service"
|
|
618
|
+
* (offline/misconfig), which also offers a retry. Either way there's a way out
|
|
619
|
+
* (back to home), so the route never dead-ends.
|
|
620
|
+
*/
|
|
621
|
+
function AiUnavailable({ hasError, onRetry, onHome, t, }) {
|
|
622
|
+
return (_jsx("div", { className: "flex flex-1 items-center justify-center p-6", "data-testid": "ai-unavailable", children: _jsxs(Empty, { children: [_jsx(EmptyTitle, { children: t('console.ai.unavailableTitle', { defaultValue: 'AI assistant unavailable' }) }), _jsx(EmptyDescription, { children: hasError
|
|
623
|
+
? t('console.ai.unavailableError', {
|
|
624
|
+
defaultValue: "Couldn't reach the AI service. It may be temporarily offline — try again, or head back home.",
|
|
625
|
+
})
|
|
626
|
+
: t('console.ai.unavailableDescription', {
|
|
627
|
+
defaultValue: "This deployment doesn't have an AI assistant enabled. Everything else works as usual.",
|
|
628
|
+
}) }), _jsxs("div", { className: "mt-6 flex flex-col items-center gap-3 sm:flex-row", children: [hasError && (_jsx(Button, { variant: "outline", onClick: onRetry, "data-testid": "ai-unavailable-retry", children: t('console.ai.unavailableRetry', { defaultValue: 'Try again' }) })), _jsx(Button, { onClick: onHome, "data-testid": "ai-unavailable-home", children: t('console.ai.unavailableHome', { defaultValue: 'Back to home' }) })] })] }) }));
|
|
606
629
|
}
|
|
607
630
|
function ChatPane({ agents, agentsLoading, agentsError, activeAgent, chatApi, apiBase, conversationId, initialMessages, onSent, onShare, onCanvasOpenChange, }) {
|
|
608
631
|
const { t } = useObjectTranslation();
|
|
@@ -722,7 +745,11 @@ function ChatPane({ agents, agentsLoading, agentsError, activeAgent, chatApi, ap
|
|
|
722
745
|
return (_jsxs("div", { ref: split.containerRef, className: "relative flex min-h-0 flex-1 px-0", children: [_jsx("div", { "data-chat-column": true, className: canvasApp
|
|
723
746
|
? 'flex min-h-0 shrink-0 justify-center'
|
|
724
747
|
: 'flex min-h-0 flex-1 justify-center', style: canvasApp ? { width: split.width } : undefined, children: _jsx(ChatbotEnhanced, { className: "min-h-0 flex-1 bg-background md:max-w-5xl", onUpgrade: () => window.open(cloudPricingDeepLink(), '_blank', 'noopener,noreferrer'), surface: "plain", maxHeight: "100%", headerSlot: headerSlot, messages: messages, placeholder: activeAgent
|
|
725
|
-
?
|
|
748
|
+
? agentRouteName(activeAgent) === 'ask'
|
|
749
|
+
// The generic "Ask {agent}…" doubles to "Ask Ask…" for the data-query
|
|
750
|
+
// agent whose label IS "Ask". Use its purpose-built placeholder instead.
|
|
751
|
+
? t('console.ai.askAnything')
|
|
752
|
+
: t('console.ai.askAgent', { agent: activeAgentLabel })
|
|
726
753
|
: agentsLoading
|
|
727
754
|
? t('console.ai.loadingAgents')
|
|
728
755
|
: t('console.ai.askAnything'), labels: {
|
|
@@ -11,20 +11,18 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
11
11
|
import { useEffect } from 'react';
|
|
12
12
|
import { useNavigationContext } from '../../context/NavigationContext';
|
|
13
13
|
import { AppHeader } from '../../layout/AppHeader';
|
|
14
|
-
import {
|
|
14
|
+
import { useAiSurfaceEnabled } from '../../hooks/useAiSurface';
|
|
15
15
|
import { useObjectTranslation } from '@object-ui/i18n';
|
|
16
16
|
// Lightweight FAB stub — the heavy chat chunk graph only downloads on
|
|
17
17
|
// first hover/click. See ../../layout/ConsoleChatbotFab.tsx.
|
|
18
18
|
import { ConsoleChatbotFab } from '../../layout/ConsoleChatbotFab';
|
|
19
19
|
export function HomeLayout({ children, userId }) {
|
|
20
20
|
const { setContext } = useNavigationContext();
|
|
21
|
-
const { isAiEnabled } = useDiscovery();
|
|
22
21
|
const { t } = useObjectTranslation();
|
|
23
|
-
// Render the chatbot
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
const
|
|
27
|
-
const showChatbot = isAiEnabled || aiBaseUrlConfigured;
|
|
22
|
+
// Render the chatbot only when the server serves AI (or an explicit
|
|
23
|
+
// `VITE_AI_BASE_URL` opt-in is set) — same runtime signal as the rest of the
|
|
24
|
+
// console's AI surface. See useAiSurfaceEnabled.
|
|
25
|
+
const { enabled: showChatbot } = useAiSurfaceEnabled();
|
|
28
26
|
useEffect(() => {
|
|
29
27
|
setContext('home');
|
|
30
28
|
}, [setContext]);
|
|
@@ -31,15 +31,7 @@ import { Empty, EmptyTitle, EmptyDescription, Button } from '@object-ui/componen
|
|
|
31
31
|
import { Sparkles, ShieldAlert, X, UploadCloud, MessageSquareText } from 'lucide-react';
|
|
32
32
|
import { useMetadataClient } from '../../views/metadata-admin/useMetadata';
|
|
33
33
|
import { usePublishAllDrafts } from '../../preview/usePublishAllDrafts';
|
|
34
|
-
|
|
35
|
-
function resolveAiApiBase() {
|
|
36
|
-
const env = import.meta.env ?? {};
|
|
37
|
-
const fromEnv = env.VITE_AI_BASE_URL;
|
|
38
|
-
if (fromEnv)
|
|
39
|
-
return fromEnv.replace(/\/$/, '');
|
|
40
|
-
const serverUrl = env.VITE_SERVER_URL ?? '';
|
|
41
|
-
return `${serverUrl.replace(/\/$/, '')}/api/v1/ai`;
|
|
42
|
-
}
|
|
34
|
+
import { resolveAiApiBase } from '../../hooks/useAiSurface';
|
|
43
35
|
/**
|
|
44
36
|
* Which AI home CTAs to surface, driven by the live agent catalog (the single
|
|
45
37
|
* source of truth) — gated PER agent, because the community edition can be in
|
|
@@ -12,6 +12,7 @@ import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, D
|
|
|
12
12
|
import { useAuth } from '@object-ui/auth';
|
|
13
13
|
import { useObjectTranslation } from '@object-ui/i18n';
|
|
14
14
|
import { Loader2 } from 'lucide-react';
|
|
15
|
+
import { provisionProductionEnvironment } from './provisionEnvironment';
|
|
15
16
|
/** Convert a display name to a URL-friendly slug */
|
|
16
17
|
function nameToSlug(name) {
|
|
17
18
|
return name
|
|
@@ -81,6 +82,19 @@ export function CreateWorkspaceDialog({ open, onOpenChange, onCreated, }) {
|
|
|
81
82
|
setError(null);
|
|
82
83
|
try {
|
|
83
84
|
const org = await createOrganization({ name: name.trim(), slug: slug.trim() });
|
|
85
|
+
// Born-with-env: eagerly ensure the new org's production environment so
|
|
86
|
+
// the user lands in a ready workspace with no onboarding-wizard detour.
|
|
87
|
+
// `createOrganization` already switched the active org; we also pass
|
|
88
|
+
// `organizationId` explicitly so the target is unambiguous. Idempotent +
|
|
89
|
+
// best-effort: a control plane that auto-provisions the env on create
|
|
90
|
+
// resolves this to `alreadyProvisioned`; a genuine failure falls through
|
|
91
|
+
// to the onboarding gate (lazy provision on first navigation).
|
|
92
|
+
try {
|
|
93
|
+
await provisionProductionEnvironment({ organizationId: org.id });
|
|
94
|
+
}
|
|
95
|
+
catch (provisionErr) {
|
|
96
|
+
console.warn('[CreateWorkspace] eager env provision failed; onboarding gate will provision lazily', provisionErr);
|
|
97
|
+
}
|
|
84
98
|
onCreated?.(org);
|
|
85
99
|
}
|
|
86
100
|
catch (err) {
|
|
@@ -89,7 +103,7 @@ export function CreateWorkspaceDialog({ open, onOpenChange, onCreated, }) {
|
|
|
89
103
|
finally {
|
|
90
104
|
setIsSubmitting(false);
|
|
91
105
|
}
|
|
92
|
-
}, [name, slug, createOrganization, onCreated]);
|
|
106
|
+
}, [name, slug, multiOrgDisabled, t, createOrganization, onCreated]);
|
|
93
107
|
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsx(DialogContent, { className: "sm:max-w-[425px]", "data-testid": "create-workspace-dialog", children: _jsxs("form", { onSubmit: handleSubmit, children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: t('workspace.createTitle', { defaultValue: 'Create a workspace' }) }), _jsx(DialogDescription, { children: t('workspace.createDescription', {
|
|
94
108
|
defaultValue: 'A workspace is a shared space for your team to collaborate.',
|
|
95
109
|
}) })] }), _jsxs("div", { className: "grid gap-4 py-4", children: [_jsxs("div", { className: "grid gap-2", children: [_jsx(Label, { htmlFor: "workspace-name", children: t('workspace.nameLabel', { defaultValue: 'Workspace name' }) }), _jsx(Input, { id: "workspace-name", placeholder: t('workspace.namePlaceholder', { defaultValue: 'e.g., Acme Inc' }), value: name, onChange: (e) => setName(e.target.value), autoFocus: true, "data-testid": "workspace-name-input" })] }), _jsxs("div", { className: "grid gap-2", children: [_jsx(Label, { htmlFor: "workspace-slug", children: t('workspace.slugLabel', { defaultValue: 'URL slug' }) }), _jsx(Input, { id: "workspace-slug", placeholder: "acme-inc", value: slug, onChange: (e) => {
|