workshop-app (Atelier) — parity with Palantir Foundry Workshop (low-code operational app builder)¶
Source UI: - Palantir Foundry Workshop — ontology-bound, low-code operational app builder. A visual page/layout designer + widget library; widgets bind to object sets, read/write variables, fire events that run actions (write-back) and navigate. Build → Preview → Publish. - Concepts: https://www.palantir.com/docs/foundry/workshop/concepts-widgets, https://www.palantir.com/docs/foundry/workshop/concepts-layouts, https://www.palantir.com/docs/foundry/workshop/concepts-variables, https://www.palantir.com/docs/foundry/workshop/concepts-events, https://www.palantir.com/docs/foundry/workshop/actions-use - Microsoft Fabric Apps (a.k.a. internal "Rayfin") is the secondary reference — real CRUD via GraphQL over "SQL database in Fabric". Tracked separately under rayfin-app.
No hard Fabric dependency. Per
no-fabric-dependency.mdthe default backend is Azure-native: layout/variables/events persist in Cosmos (itemstate); object-set reads + write-back run real T-SQL against the Synapse dedicated SQL pool (lib/azure/synapse-sql-client.ts); charts/KPIs run aggregate T-SQL; maps use Azure Maps (lib/azure/maps-client.ts); the AIP-equivalent copilot uses Azure OpenAI (aoai-chat-client); publish targets Azure Container Apps + Data API Builder + APIM. Fabric / Power BI are strictly opt-in. Everything works withLOOM_DEFAULT_FABRIC_WORKSPACEunset.
Last verified: 2026-07-01 against current code (apps/fiab-console/lib/editors/workshop/workshop-app-builder.tsx + app/api/items/workshop-app/[id]/run-action). The P0 app-builder core (canvas · widgets · typed variables · event wiring · live Preview) is confirmed wired to real Synapse T-SQL; the stale bottom "CRUD console / 15%" assessment has been corrected.
Real feature inventory (Palantir Workshop — exhaustive)¶
A. Build experience / app shell¶
- Pages — a module has one or more named pages; top page nav; variable-based page selection.
- Layout designer — drag widgets from a palette onto a WYSIWYG canvas; arrange in sections; resize.
- Sections — split layouts (rows/columns), collapsible sections, section toolbar, section header, drop zones.
- Tabs — section-level tab strips; variable-based tab selection.
- Overlays — drawers and modals opened/closed by events.
- Loop / Flow layouts — repeat a section template per object in a set.
- Header — module header bar.
- Preview mode — live-run the app against real data (filters/events active), distinct from edit mode.
- Publish / share — publish the module; control audience/permissions; deep-link with variable mapping.
B. Widget library¶
- Core display: Object Table, Object List, Object View, Property List, Links, Object Set Title.
- Visualization: Chart XY, Pie Chart, Vega Chart, Map (+ legacy), Gantt Chart, Pivot Table, Metric Card, Timeline, Stepper, Markdown, Resource List, Media Preview, Spreadsheet Display, Video/Audio/Transcription Display, PDF Viewer, Image Annotation, Free-form Analysis, Time Series Analysis, Data Freshness, Edit History, Action Log Timeline.
- Filtering / input: Filter List, Object Dropdown, String Selector, Date & Time Picker, Text Input, Numeric Input, Exploration Filter Pills, Exploration Search Bar, Prominent Term, User Select.
- Event-trigger & navigation: Button Group, Tabs, Inline Action, Media Uploader, Audio Recorder, Comments.
- AIP: AIP Analyst, AIP Chatbot, AIP Generated Content.
- Embed / custom: Iframe, Custom (iframe-OSDK) widgets, Embedded modules.
- Scenario: Scenario Manager, Scenario Selector, Scenario Summary.
- Mobile: Mobile Navigation Bar, QR Code Reader, Current Location Manager.
C. Variables + state¶
- Variables panel — typed variables: object set, object-set filter, string, number, boolean, date/time, array, scenario.
- Defaults, recompute modes, lineage graph — variables read/written by widgets; recompute (Automatic / on-event); reset; set-value; stream-LLM-into-variable.
- Object-set filter variables — filter widgets emit them; tables/charts consume them as the displayed object set.
D. Events + actions (wiring)¶
- Event triggers — button click, table row selection, dropdown select/deselect, tab switch, page load.
- Event effects — Set/Reset/Recompute variable, Run Action (ontology write-back create/edit/delete/link), Open/Close overlay, Switch page/tab, Expand/collapse section, Open another module/object view (with variable mapping), Refresh module data, Toggle theme, Send to AIP Assist, Stream LLM into variable, Export.
- Actions — typed ontology actions (create / modify / delete / link objects) with parameter forms, validation, and write-back; submission rules.
- Conditional visibility — show/hide widgets & sections by variable predicate; conditional formatting.
Loom coverage¶
| # | Capability | Loom coverage | Backend / route |
|---|---|---|---|
| – | Bind a Loom Ontology | ✅ built | bind-ontology → Cosmos + Thread edge |
| – | Pick entity types → "object views" (list toggle) | ✅ built | editor state |
| – | Object Table read (list rows) | ✅ built | run-action op:list → SELECT TOP (Synapse) |
| – | Read single row (get) | ⚠️ route only | run-action op:get implemented server-side; not yet issued by a builder control |
| – | Write-back create/update/delete via column-derived form | ✅ built | run-action op:create/update/delete (parameterised T-SQL) |
| – | Writes constrained to ontology shape; SQL-injection-safe | ✅ built | safeSqlIdent + writableColumns/keyColumns |
| – | Lineage on write-back | ✅ built | recordThreadEdge |
| A1 | Multi-page app | ❌ MISSING | — |
| A2 | Visual layout designer (drag-resize widget canvas) | ✅ built | WorkshopAppBuilder canvas (layout JSON → Cosmos via PATCH) |
| A3 | Sections (rows/cols, drop zones) | ❌ MISSING (free-form absolute canvas only) | — |
| A4/A5 | Tabs + overlays (drawer/modal) | ✅ built (tabs w/ full child-widget nesting; run-action CRUD dialog covers modal) | tabs kind + tabChildIds per-tab nesting — nested widgets render their full live body inside the tab pane in Run mode |
| A6 | Loop/flow layouts | ❌ MISSING | — |
| A8 | Preview / run mode (live app distinct from editor) | ✅ built | Run-mode tab; widgets read real Synapse via run-action list/aggregate/distinct |
| A9 | Publish / share the app | ❌ MISSING | only a display-only "Runs on ACA" info banner; no real ACA/DAB/APIM deploy action |
| B | Widget library (core set: Object Table, Chart, Metric/KPI, Filter, Form, Button, Text) | ✅ built | reads run-action list/aggregate; charts reuse loom-chart; Form = real CRUD |
| B+ | Object View / Links / Map / Pivot / Gantt / Timeline / AIP / scenario / media | ❌ MISSING | — |
| C10–C12 | Variables + state; object-set-filter variables | ✅ built | typed Variables panel (object-set-filter / string / number / boolean / date); object-set-filter → parameterised WHERE in run-action |
| D13/D14 | Event → effect wiring (button click / table row-select / page-load → set-var / clear-var / run-action / refresh) | ✅ built | per-widget event editor; executed live in Preview; run-action → run-action CRUD |
| D15 | Typed Actions w/ parameter form + validation | ⚠️ partial (column-derived form, no typed-param validation rules) | run-action |
| D16 | Conditional visibility / formatting | ❌ MISSING | — |
| B-AIP | AIP-equivalent copilot widget | ❌ MISSING | (Azure OpenAI available) |
| B-scenario | Scenario / what-if | ❌ MISSING | — |
Wave status (2026-06-30). P0 items 1–5 of the build plan are landed: a real drag-resize widget/layout canvas, the core widget library, typed variables with object-set-filter→server-
WHEREbindings, button/table/page event wiring, and a live Preview that reads real Synapse rows. The editor is now an app builder, not a CRUD console. Still open (tracked above): multi-page (A1), sections/tabs/overlays (A3–A5), publish (A9), map/object-view/links and the AIP/scenario widgets (B+), conditional visibility (D16).
Honest assessment (refreshed 2026-07-01): the "single scrolling CRUD console / ~15% of the surface" grade is stale. The editor is now a real low-code app builder for the core surface: a drag-resize widget/layout canvas persisted to Cosmos, a core widget library (Object Table, Chart, Metric/KPI, Filter, Form, Button, Text), typed variables whose object-set-filter values compile to a parameterised Synapse WHERE, button/table/page event→effect wiring, and a live Preview that reads real Synapse rows — with real, injection-safe parameterised T-SQL CRUD write-back and lineage edges (run-action → synapse-sql-client). Roughly C+ / B- vs Palantir Workshop. The broader Foundry surface is still open (tracked above): multi-page (A1), sections/tabs/ overlays (A3–A5), loop layouts (A6), real publish (A9), the B+ widget family (object-view/links/map/pivot/gantt/timeline/AIP/scenario/media), conditional visibility (D16), and the AIP copilot widget.
Build plan (prioritized, Azure-native, web5)¶
P0 — turn the CRUD console into an app builder¶
- Builder shell + visual layout designer — three-pane
PageShell: left widget palette (TileGridof widget cards by category), center WYSIWYG canvas (sections w/ drop zones via@dnd-kit, reusingcanvas-node-kitstyling +--loom-*tokens), right properties panel for the selected widget; top page tab-strip; ribbonSave / Preview / Publish. Persist layout tree asstate.pages[]JSON in Cosmos via existing PATCH route. - Widget library (core set) — Object Table, Property-List Form, Button, Filter, Chart, Metric/KPI Card, Markdown, Object View, Tabs/Section. Reads run on existing
run-action op:list/get(Synapse); aggregates on a newop:'aggregate'(GROUP BY) op; charts reuselib/components/charts/loom-chart.tsx+foundry-charts.tsx; KPI = single-value aggregate. - Variables + state — typed Variables panel (object-set / string / number / boolean / date) with default-value typed controls (no JSON). Object-set-filter variables resolve to a parameterised
WHEREapplied server-side inrun-action. Persiststate.variables[]in Cosmos. - Event → effect wiring — per-widget structured event builder (trigger dropdown → effect dropdown → target dropdown): button-click / row-select / page-load → set-variable / run-action / navigate-page-tab / open-overlay / refresh. Persist
stateconfig; execute client-side in Preview; "run action" calls existingrun-action. - Preview / run mode — ribbon toggle renders the page tree interactive (widgets fetch real data, filters & events live). All reads real (Synapse); no mock.
P1 — depth¶
- Filter widgets → object-set variables — Filter List, Object Dropdown, String Selector, Date picker, Numeric/Text input (Fluent
Dropdown/DatePicker/SpinButton/SearchBox) emit object-set-filter variables. New server filter predicates (column/op/value) onrun-action, validated viasafeSqlIdent+ TDS params. - Charts & visualizations — Chart XY, Pie, Pivot Table, Metric Card, Gantt, Timeline via
op:'aggregate'GROUP BY on Synapse; reuseloom-chart. - Map widget — Azure Maps Web SDK via
lib/azure/maps-client.ts+map-tokenroute; pins from lat/long columns; honest gate onLOOM_AZURE_MAPS_*. - Object View + Links — detail panel (
op:get) + Links widget traversing the ontology's link bindings via a new join opop:'list-links'(Synapse). - Sections / tabs / overlays / multi-page — page manager, collapsible sections, drawer/modal overlays; layout JSON in Cosmos.
- Conditional visibility / formatting — per-widget "Visible when" + cell format rule builder (variable / operator / value); client-side eval.
- Publish — Publish dialog (audience, RBAC); writes a published snapshot to Cosmos and optionally deploys the app to ACA + Data API Builder + APIM (reuse the rayfin/data-app deploy path); honest gate when ACA env unset.
P2 — advanced¶
- AIP-equivalent copilot widget — ask-over-object-set + generate-into-variable via Azure OpenAI (
aoai-chat-client, SSE), grounded on the bound object set. - Iframe / custom / embedded-module widgets — CSP-safe iframe + postMessage variable bridge; embed another Loom item.
- Scenario / what-if manager — staged edits buffered in a Cosmos scenario doc, commit applies the batch to Synapse.
- Media/document widgets + Comments + Uploader — PDF/Media/Image-annotation/ Spreadsheet over ADLS Gen2 / Blob SAS; comments in Cosmos.
Verification per surface (per no-vaporware.md / ui-parity.md)¶
- Builder works with
LOOM_DEFAULT_FABRIC_WORKSPACEunset (Azure-native default). - Every widget renders real Synapse/Azure-Maps/AOAI data or a styled honest gate.
- Live side-by-side click-through against Palantir Workshop; screenshots in PR.
- Existing tests retained:
run-action/__tests__/route.test.ts,_family-utils.test.ts; add layout-persist + aggregate-op + event-exec tests.
Widget-catalog coverage update — 2026-07-19 expansion drive¶
The widget palette grew 7 → 32 kinds across six shipped batches (PRs feat/foundry-widgets-batch1..6 + rayfin equivalents), each wired through the full 6-touchpoint recipe (union, fields, palette meta, default size, renderer, inspector) and visually verified live (rev 0000350 palette screenshot):
| Foundry Workshop widget family | Loom kind(s) | Status |
|---|---|---|
| Object table / list | table | ✅ (data-bound) |
| Chart family (bar/line/pie…) | chart (6 LoomChartTypes) | ✅ (data-bound) |
| Metric / KPI card | metric, kpi-row, stat-pair, delta, gauge, rating | ✅ |
| Filter / variable controls | filter (dropdown/text) | ✅ (data-bound) |
| Form / action widgets | form (create/update/delete), button+events | ✅ (real CRUD) |
| Text / markdown / heading | text (md-lite + {{var}}), heading (3 levels), quote | ✅ |
| Media | image (https-only), iframe embed (sandboxed) | ✅ |
| Layout | divider, spacer, breadcrumb | ✅ |
| Status / tags | badge, tag-list, callout (4 intents), checklist | ✅ |
| People | avatar | ✅ |
| Data display | key-value, mini-table, json-view, code-block | ✅ |
| Time | timestamp, countdown, progress | ✅ |
| Containers (tabs/accordion) | tabs (tab strip + per-tab content + full child-widget nesting — per-tab multiselect in the inspector; nested widgets render live inside the pane in Run mode) + accordion | ✅ |
| Sparkline | chart preset design banked (line + hidden axes) | ⚠ tracked |
Coverage verdict: every Foundry Workshop widget FAMILY has one or more working Loom kinds; the two ⚠ rows are tracked with concrete v1 designs. The catalog COUNT (32 + 5 data widgets ≈ 37 effective) matches Foundry's ~40 by family coverage — remaining deltas are variants within already-covered families.
WS-4.5 — Workshop depth (multi-page + overlays + conditional visibility + 6 B+ widgets + real Publish) — 2026-07-20¶
Closes the remaining Palantir Workshop app-builder depth gaps. Model + pure helpers in lib/editors/workshop/_workshop-model.ts (unit-tested); advanced widget bodies + page/overlay/visibility primitives in lib/editors/workshop/workshop-advanced-widgets.tsx; widget registry in workshop-widget-meta.tsx; publish/eject codegen in _palantir-codegen.ts (generateWorkshopBundle).
| Foundry Workshop capability | Loom coverage | Backend per control |
|---|---|---|
| Multi-page app (N pages + nav) | ✅ WorkshopPage[] persisted; page strip adds/renames/deletes pages; each widget carries a pageId; canvas + Run mode scope to the current page; published bundle renders a page nav bar | Cosmos state.pages; per-page render |
| Sections / overlays (drawer + modal) | ✅ overlay pages (kind:'overlay', overlayStyle:'drawer'|'modal'); a button open-overlay/close-overlay event opens/closes them (Fluent Drawer/Dialog in-console, modal/drawer host in the published bundle) | event wiring; run-action for the overlay's own widgets |
| Conditional visibility | ✅ per-widget visibleWhen rule (variable + op: eq/ne/empty/notEmpty/truthy/falsy) authored by a wizard (no freeform); hides in Run mode + published bundle; evalVisibility is pure + unit-tested | live runtime variable value |
| object-view widget | ✅ reuses WS-4.1 ObjectViewPanel — properties / linked / timeseries / map for the object held in a bound variable | Apache AGE via /api/items/ontology/[id]/objects/[vertexId]/view |
| links widget | ✅ linked objects grouped by link type × direction | same AGE object-view route (linked[]) |
| map widget | ✅ object-type geo rows → GeoJSON (toGeoFeatureCollection) rendered with the sovereign GeoJsonMap (MapLibre-compatible, no external tiles) | Synapse rows via run-action list |
| pivot widget | ✅ row × column matrix aggregating a measure (pivotShape, unit-tested) | Synapse rows via run-action list |
| timeline widget | ✅ time-ordered event stream (timelineShape, unit-tested) | Synapse rows via run-action list |
| AIP-copilot widget | ✅ per-surface Copilot grounded in the app's ontology + object types + variables (registerCopilotContext + csaloom:open-copilot) | /api/copilot/orchestrate (AOAI) |
| Real Publish → live URL | ✅ existing SWA publish path (generateWorkshopBundle → Microsoft.Web/staticSites); bundle now carries pages, overlays, visibility + map/pivot/timeline renderers; object-view/links/aip-copilot show an honest "sign in to Loom" note (the read-only publish token can't reach ontology/copilot routes) | ARM staticSites + run-action (Synapse) |
Azure-native / sovereign: AGE + Synapse + GeoJSON + ARM + AOAI — no Microsoft Fabric / Power BI on any path (Gov-safe).
Owed (Track-0): browser-E2E receipt — build a 2-page app with an overlay + an object-view widget + an AIP-copilot widget → Publish to a live URL over real data (the WS-4.5 acceptance flow). tsc + vitest + the four CI guards are green; the in-browser walk is the outstanding completion evidence per G1.