diff --git a/.changeset/adr-0047-interface-pages.md b/.changeset/adr-0047-interface-pages.md new file mode 100644 index 000000000..271bd9581 --- /dev/null +++ b/.changeset/adr-0047-interface-pages.md @@ -0,0 +1,28 @@ +--- +"@object-ui/app-shell": minor +"@object-ui/plugin-list": minor +"@object-ui/plugin-grid": minor +"@object-ui/data-objectstack": minor +--- + +feat: ADR-0047 — interface pages, visualization switcher, and Airtable-parity filters + +End-user interface/list pages reach full rendering and authoring parity: + +- **Spec tabs + visualization switcher** — `ObjectView` now forwards + `viewDef.tabs` (stored/served but never rendered) and `viewDef.appearance` + (`allowedVisualizations` whitelist), turning on the dormant `ViewSwitcher` when + more than one type is whitelisted; effective options = author whitelist ∩ + capability-resolvable types (kanban needs `groupBy`, calendar a date field, …). + `ListView` accepts the canonical `ViewFilterRule[]` tab-filter shape. +- **User filters** — render only when `userFilters` is explicitly configured; + selections (dropdown values + active tab) mirror into `uf_*` URL params and + restore on load, so filtered lists survive reload and are shareable. +- **Toolbar polish** — the visualization switcher becomes a compact right-side + "Grid ▾" dropdown inside the tool cluster (no extra row); filter tabs and + dropdown filters are mutually exclusive. +- **Studio authoring** — a usable, schema-driven interface-page inspector + (collapsible sections honoured, array-of-enum → multi-select, a None/Tabs/ + Dropdown `filter-mode` selector where None maps to ABSENCE of `userFilters`), + and the Design/Preview tabs render the live list via `InterfaceListPage` + (including a non-empty grid when the source view is hollow). diff --git a/.changeset/adr-0048-app-shell-package-routing.md b/.changeset/adr-0048-app-shell-package-routing.md new file mode 100644 index 000000000..100d99474 --- /dev/null +++ b/.changeset/adr-0048-app-shell-package-routing.md @@ -0,0 +1,21 @@ +--- +"@object-ui/app-shell": minor +--- + +feat(app-shell): ADR-0048 (option A) — package-id app routing + prefer-local resolution + +Apps are now routed by their canonical package id rather than name: + +- **Resolution layer** — new `appRoute` helpers: `appRouteSegment(app)` + (canonical link segment = package id, name fallback) and + `matchAppBySegment(apps, seg)` (prefers `_packageId`, falls back to `name`). + `AppContent` selects the active app via `matchAppBySegment`, so + `/apps/` resolves while `/apps/` keeps working (a per-tenant + alias / legacy URL). +- **Emission layer** — nav generates `/apps/` links across app + switching (AppSwitcher/AppSidebar/CommandPalette), sidebar base paths, + create/edit-app, and the hidden-app switch, all via `appRouteSegment(app)`. +- **Prefer-local resolution** — `preferLocal(list, name, ownerPackageId)` resolves + a bare metadata name to the item whose `_packageId` matches the active app's + package (falling back to first match), wired at PageView/DashboardView/ + ReportView and AppHeader so two installed packages can ship the same bare name. diff --git a/.changeset/ai-workspace-console-ux.md b/.changeset/ai-workspace-console-ux.md new file mode 100644 index 000000000..a12aee0ff --- /dev/null +++ b/.changeset/ai-workspace-console-ux.md @@ -0,0 +1,19 @@ +--- +"@object-ui/app-shell": minor +--- + +feat(console/ai): AI workspace UX — date-grouped conversations, draggable split, keyboard shortcuts + +ChatGPT/Claude-parity polish for the console AI workspace: + +- **Date-grouped conversations** — the flat conversations list groups into + recency sections (Today / Yesterday / Previous 7 days / Previous 30 days / + Older) with calendar-day boundaries, via a pure exported + `groupConversationsByDate()`. +- **Draggable chat ↔ preview split** — a draggable, double-click-to-reset divider + between chat and the Live Canvas preview; width persists to `localStorage`, + clamped so neither pane collapses (chat ≥ 360px, preview ≥ 420px), keyboard- + accessible (`role="separator"`, ←/→ resize). +- **Collapsible conversations list** — auto-tucks when the preview opens, with a + manual toggle. +- **Keyboard shortcuts** — ⌘⇧O new chat, ⌘⇧S toggle the conversations list. diff --git a/.changeset/app-shell-ai-current-object-context.md b/.changeset/app-shell-ai-current-object-context.md new file mode 100644 index 000000000..2948a2298 --- /dev/null +++ b/.changeset/app-shell-ai-current-object-context.md @@ -0,0 +1,15 @@ +--- +"@object-ui/app-shell": patch +--- + +fix(app-shell): send the current-page object to the AI assistant context + +The floating console assistant forwarded only `appName` + the full objects list, +never the object the user is actually viewing — so asking it to "analyse this +object" (especially in a non-English prompt) gave the agent nothing to anchor on +and it replied that it couldn't find the object. The current object/record are +now derived from the route (mirroring `useTrackRouteAsRecent`'s URL layout, +tolerant of a `_console` shell prefix) and passed as `context.objectName` / +`context.recordId`, so the backend injects that object's schema into the system +prompt and scopes data queries to it. Pairs with the framework current-object +resolution fix. diff --git a/.changeset/app-shell-newtab-fast-path.md b/.changeset/app-shell-newtab-fast-path.md new file mode 100644 index 000000000..29744ced8 --- /dev/null +++ b/.changeset/app-shell-newtab-fast-path.md @@ -0,0 +1,16 @@ +--- +"@object-ui/app-shell": minor +"@object-ui/core": minor +--- + +feat(app-shell): zero-roundtrip `newTabUrl` fast path for `opensInNewTab` actions + +Actions that declare `newTabUrl` (a path template with a `{recordId}` placeholder +whose target endpoint performs all auth/authz itself) now drive the pre-opened +popup straight to that URL on click, skipping the action POST entirely — applied +to both server-action paths (list rows via `useConsoleActionRuntime`, record +header via `RecordDetailView`). The popup paints the existing spinner page until +the (possibly slow) endpoint commits its redirect; the URL is resolved absolute +because `about:blank` gives a bare-relative href no reliable base. The +popup-blocked toast fallback is unchanged. Removes one full round trip of +white-screen latency from every such Open click. diff --git a/.changeset/chatbot-data-chart-inline.md b/.changeset/chatbot-data-chart-inline.md new file mode 100644 index 000000000..56832ec8e --- /dev/null +++ b/.changeset/chatbot-data-chart-inline.md @@ -0,0 +1,17 @@ +--- +"@object-ui/plugin-chatbot": minor +--- + +feat(plugin-chatbot): render AI data-query charts inline (`data-chart`) + +Companion to the framework `visualize_data` tool: the data-query assistant can +now answer with a CHART rendered right in the assistant bubble. + +- `mapMessages.ts` — `extractCharts()` lifts every `data-chart` custom stream + part onto `ChatMessage.charts` (defensive narrowing; preserves multiple charts + in order), mirroring the existing `data-build-progress` → `buildProgress` path. +- `ChatbotEnhanced.tsx` — renders each chart via `` (decoupled — no hard dep on `plugin-charts`), giving the + chart a definite `width: min(520px, 80vw)` so recharts' `ResponsiveContainer` + measures a stable non-zero width inside the `w-fit` bubble (otherwise the + circular width dependency leaves bars unpainted). diff --git a/.changeset/chatbot-turn-liveness.md b/.changeset/chatbot-turn-liveness.md new file mode 100644 index 000000000..cc121c3f5 --- /dev/null +++ b/.changeset/chatbot-turn-liveness.md @@ -0,0 +1,21 @@ +--- +"@object-ui/plugin-chatbot": minor +--- + +feat(plugin-chatbot): honest liveness indicator on running AI turns + +AI app builds run 1–3 min with long quiet gaps (LLM thinking, sample-data +generation) where a static spinner is indistinguishable from a dropped +connection. The chat now shows a Claude-Code-style liveness indicator driven by +REAL observed stream activity, not a free-running clock: + +- `useTurnLiveness(active, activityKey)` stamps the moment real data arrives (a + streamed token / tool delta / `data-build-progress` update) and measures + seconds-since-last-byte. +- `LivenessIndicator` renders three honest states — *receiving* (emerald pulse + + m:ss, bytes arrived recently), *waiting* (request in flight, nothing back yet), + and *stalled* (amber + "no response for Ns", genuinely silent past 6s). +- The build panel prefers the server's monotonic `seq` keep-alive heartbeat as + its activity key (falling back to the content signature on older runtimes), so + a long quiet seed-generation window reads as honestly *receiving* rather than + flipping to amber. diff --git a/.changeset/gantt-navigation-year-scale-pdf.md b/.changeset/gantt-navigation-year-scale-pdf.md new file mode 100644 index 000000000..7941e18a1 --- /dev/null +++ b/.changeset/gantt-navigation-year-scale-pdf.md @@ -0,0 +1,18 @@ +--- +"@object-ui/plugin-gantt": minor +--- + +feat(gantt): year scale, navigation, saved layout, and PDF export (follow-up to #1672) + +- **Year scale** — new `year` granularity (one column per year, with a "20XXs" + decade group band above); ResourceWorkload follows the same column width/label. +- **Navigation** — toolbar gains *This week* / *This month* jump buttons (beside + the existing *Today*), scrolling the timeline to the current week/month start. +- **Saved layout** — `persistLayoutKey` / `onLayoutChange` plus a "Save layout" + button snapshot the current granularity + zoom + collapsed task columns to + `localStorage` (`gantt-layout::`) and restore on next load (an + explicit `viewMode` prop still wins). `ObjectGantt` derives the key from the + data object by default; `persistLayout: false` opts out. +- **PDF export** — rasterizes the whole chart SVG to JPEG embedded in a + zero-dependency single-page PDF (DCTDecode), alongside PNG export + (`buildExportSvg` shared by both). diff --git a/.changeset/gantt-tooltip-parent-stretch.md b/.changeset/gantt-tooltip-parent-stretch.md new file mode 100644 index 000000000..3eff426ec --- /dev/null +++ b/.changeset/gantt-tooltip-parent-stretch.md @@ -0,0 +1,18 @@ +--- +"@object-ui/plugin-gantt": minor +--- + +feat(gantt): configurable hover tooltip + live parent-stretch (follow-up to #1672) + +- **Configurable tooltip** — a view declares `tooltipFields` on its gantt config + (field names, or `{ field, label }` to override the label); `ObjectGantt` + resolves each against the record (select options → label, lookups → embedded + record name, dates/numbers/currency/percent through the shared `@object-ui/ + fields` formatters) and feeds `GanttView` a `task.fields` array that replaces + the default hover detail. +- **Live parent-stretch** — a summary bar's displayed range rolls up from its + children live, so dragging a child past the parent's edge stretches the parent. +- Also replaces six prebuilt-CSS utilities the components stylesheet never emits + (connector dot `-right-2` was occluding the progress label, resize-handle + width, progress-fill radius, grid z-index, `sm:` variants) with inline styles + / a scoped media query so the chart renders correctly in consuming apps. diff --git a/.changeset/plugin-list-select-schema-gate.md b/.changeset/plugin-list-select-schema-gate.md new file mode 100644 index 000000000..245743e82 --- /dev/null +++ b/.changeset/plugin-list-select-schema-gate.md @@ -0,0 +1,16 @@ +--- +"@object-ui/plugin-list": patch +--- + +fix(plugin-list): gate speculative `$select` fields by the object's real schema + +A list view auto-includes view-binding fields (kanban `groupBy`, calendar/gantt/ +timeline dates, gallery image, timeline status/priority) in `$select` so +alternate view modes render populated. These were added unconditionally on the +assumption that "the projection ignores unknown names" — but some backends +(notably the cloud multi-tenant runtime) reject an unknown `$select` column with +an EMPTY result set, so a single phantom field zeroed the whole list (an AI-built +`product` view requesting `status`/`due_date`/`image` showed "no data" though +rows existed). The speculative additions now go through `addSpeculative()`, which +keeps only fields present in the object schema; user-declared columns and expand +roots are untouched. diff --git a/.changeset/plugin-report-dataset-matrix-totals.md b/.changeset/plugin-report-dataset-matrix-totals.md new file mode 100644 index 000000000..e64dd9ebe --- /dev/null +++ b/.changeset/plugin-report-dataset-matrix-totals.md @@ -0,0 +1,13 @@ +--- +"@object-ui/plugin-report": minor +--- + +feat(plugin-report): server-supplied totals in dataset matrix reports + +Pairs with the framework's server-side `queryDataset` totals. The matrix renderer +now requests `totals: { groupings: [rows, columns, []] }` and renders the +returned pre-aggregated rows — a trailing Total column per measure (row +subtotals), a trailing Total row (column subtotals), and the grand total at their +intersection — matched to pivot headers via the same `bucketId` logic. A response +without totals (older server) renders exactly as before; the client never +re-aggregates (ADR-0021).