From 708e5a6813ba1c9366d28db4d7030d8ae1fab4d1 Mon Sep 17 00:00:00 2001 From: os-zhuang Date: Thu, 2 Jul 2026 10:32:03 +0800 Subject: [PATCH] docs(builder-ui): document Automation/Interface as built + design Access/Settings (#2506) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Complete the living builder-ui design doc. Writeback the Data/shell sections to the shipped StudioDesignSurface reality (pencil edit affordance vs. header-click-sorts; three-pillars-first; Save/Publish placement; Interfaces nav tree), and replace the §5 "to design" stub with four real sections — Automation and Interface documented as-built, Access and Settings designed — each with an annotated shell.css mockup (data-build / data-reuse, dashed = reused). Addresses #2506. Co-Authored-By: Claude Opus 4.8 --- docs/design/builder-ui.md | 186 ++++++++++++++++-- docs/design/builder-ui/access-pillar.html | 101 ++++++++++ docs/design/builder-ui/automation-pillar.html | 113 +++++++++++ docs/design/builder-ui/interface-pillar.html | 114 +++++++++++ docs/design/builder-ui/settings.html | 112 +++++++++++ docs/design/builder-ui/shell.css | 55 ++++++ 6 files changed, 662 insertions(+), 19 deletions(-) create mode 100644 docs/design/builder-ui/access-pillar.html create mode 100644 docs/design/builder-ui/automation-pillar.html create mode 100644 docs/design/builder-ui/interface-pillar.html create mode 100644 docs/design/builder-ui/settings.html diff --git a/docs/design/builder-ui.md b/docs/design/builder-ui.md index 841a5e5f62..fdf3654e84 100644 --- a/docs/design/builder-ui.md +++ b/docs/design/builder-ui.md @@ -16,6 +16,16 @@ visual target and, because its DOM maps to the component tree and its regions ar tagged `data-build` / `data-reuse`, the implementation blueprint (see [Mockups](#mockups)). The ASCII sketches inline below are for quick reading. +> **Implementation status (2026-07).** The builder is live in the console as +> `StudioDesignSurface` (routed at `/studio/:packageId/:tab`). Shipping **today**: +> three pillars — **Data · Automation · Interface** — each composed around existing +> renderers, plus draft → publish. **Not yet built:** the **Access** and **Settings** +> pillars (designed below, §7–§8), and part of the Data pillar's own v1 scope (the +> `Fields` form designer, the `Validations` rules list, object `Settings`, and the +> left-rail search / New object — §4). Where the built UI deliberately improved on an +> earlier sketch, this doc has been updated to the built shape (noted inline). Each +> per-pillar surface below is marked **[built]** or **[designed]**. + --- ## 1. Core constraints — AI-first, human-confirmable, reuse-first @@ -87,7 +97,9 @@ Mockup: [`builder-ui/data-pillar.html`](./builder-ui/data-pillar.html) (open in - **Top bar** — the four pillar tabs (Data / Automation / Interface / Access), the ⚙ Settings entry, the app switcher, and the draft indicator + Save draft / - Publish (draft-gated, ADR-0033). + Publish (draft-gated, ADR-0033). *(Built today: three tabs — Access and ⚙ pending; + the package name stands in for the app switcher; Save draft / Publish currently + render per-pillar rather than in the top bar. Target remains as drawn.)* - **Left rail** — the current pillar's primary entities (objects / automations / surfaces / roles), with search and New. Collapsible. - **Main** — the pillar's primary work surface, shaped to the pillar (Data = grid, @@ -169,9 +181,12 @@ and both configure a selected field through the *same* right-hand **field editor - **`Records` — grid / list style (data-forward).** The functional grid: columns = fields, rows = real records. Preview and inline-edit data, `+` add a column - (= add a field), select a column header to configure that field's properties in - the inspector, `+ New record`. Ephemeral filter / sort / hide / group for looking - at the data (not saved — saved views are Interface). + (= add a field), select a column's **edit (pencil) affordance** to configure that + field's properties in the inspector, `+ New record`. Ephemeral filter / sort / hide + / group for looking at the data (not saved — saved views are Interface). *(A bare + header click **sorts** — the grid is the runtime `ListView`, so the header's native + gesture wins; field config is opened by the per-column pencil, not the header. This + is the built behaviour, corrected from the earlier "click the header" sketch.)* - **`Fields` — form style (layout-forward).** The field designer as a form canvas: drag to reorder fields, group them into sections, and configure field properties. No data rows — this is where the object's default field layout (order + grouping) @@ -225,24 +240,152 @@ union of six declarative rule types (`packages/spec/src/data/validation.zod.ts`) form-style designer (reorder + grouping + field editor, incl. field-level validation) · `Validations` (condition builder) · object `Settings` (label / icon / name field / compact / search). Relationships via the `lookup` field type. +- **Built so far** (2026-07): owned-objects rail · `Records` grid with inline data, + add-column / add-field, drag-reorder columns, and the reused object/field inspector + (field-level required · unique · options · default · help). Draft → publish wired. +- **Still to build** (v1 gap — the facet tabs never landed): left-rail **search + New + object** · the `Fields` **form-style layout designer** · the `Validations` **rules + list + condition builder** · the object **`Settings`** facet. These are the + Phase-B build (tracked against this doc); each reuses an existing Studio panel. - **Defer**: Extended objects (objectExtensions) · External objects (datasources) · the ERD / model view · formal `Lifecycle` (v1 status = a select field) · `Indexes` · seed-data UI · saved views / kanban / calendar (presentation → Interface). --- -## 5. Other pillars (to design — same shell) +## 5. Automation pillar · **[built]** + +Automation is the **behaviour workbench**: the package's flows, each edited on the +same flow **canvas** the runtime engine executes. Nothing here is a new editor — the +canvas and node config are the existing Studio flow builder, dropped into the shell. + +Mockup: [`builder-ui/automation-pillar.html`](./builder-ui/automation-pillar.html). + +``` +┌ flows ────┬ 可视化编排 · flow · showcase_task_completed ──────────┬ inspector ┐ +│ Notify… ◄ │ Trigger: autolaunched · draft · Run as: user · v1 │ Node │ +│ Reassign │ [Variables] [Runs] [Problems] [Debug] + 添加节点 │ config │ +│ Approval │ ┌ On Task Update (START) ┐ │ │ +│ Digest… │ └──────────┬─────────────┘ │ [reused │ +│ + … │ ┌ Send Completion Email (SCRIPT) ┐ → End │ node form]│ +└───────────┴──────────────────────────────────────────────────────┴────────────┘ +``` + +- **Left rail** — the package's flows. *Built today: a **flat list** (`client.list('flow')`). + The design target is **grouped by trigger** (record-change / scheduled / API / manual) — + that grouping is a pending refinement, not yet applied.* +- **Main** — the flow **canvas** = reused `FlowCanvas` (the Studio flow builder). Top + strip: Trigger · Status · Run as · version, plus **Variables · Runs · Problems · + Debug** and add-node / zoom. Flows land **default OFF — review-then-enable**. +- **Right inspector** — the selected node's config, reused (`getMetadataInspector('flow')`). +- **Runs** — a canvas-strip toggle showing execution history *inside the builder*. This + is the read-only peek that reconciles [ADR-0084](../adr/0084-application-builder-information-architecture.md) + ("flow runs live in **Operate**, out of the builder"): the builder gets a glance; + the full run inspector stays in Operate. +- **Build boundary** — rail + shell = built; **canvas = reused `FlowCanvas`**; + **inspector = reused node config**. + +--- + +## 6. Interface pillar · **[built]** + +Interface is the **presentation workbench**: the app's surfaces (pages · dashboards · +reports · views), each rendered by the **same renderer the end user gets** — edit the +live artifact, not a proxy. + +Mockup: [`builder-ui/interface-pillar.html`](./builder-ui/interface-pillar.html). + +``` +┌ app nav ─┬ 预览即运行 · page · showcase_start_here ────────┬ inspector ┐ +│ Workspace│ ┌ source (JSX) ─────┐ ┌ live preview ─────────┐ │ Component │ +│ Start ◄ │ │ │ │ Pick the right page │ │ props │ +│ Data │ │
Showcase │ │ authoring model │ │ │ +│ Tasks │ │ … │ │ [Decision tree] │ │ [reused │ +│ Analytics│ └───────────────────┘ └────────────────────────┘ │ props] │ +└──────────┴─────────────────────────────────────────────────┴────────────┘ +``` + +- **Left rail** — the app's **real navigation tree** (groups + typed leaves: + page / object / dashboard / report / view), read from the App metadata. *This + **supersedes** the earlier "Apps · Pages · Views · Dashboards · Reports" by-type + sketch: the built rail shows the app's actual IA (what a builder edits), which is + truer than a type index. A by-type filter can be a later lens.* +- **Main** — two modes, auto-selected by the surface: + - **source + live preview** for react / html pages — a code editor beside a live + `SchemaRenderer` ("预览即运行 · 同一渲染器" = preview *is* runtime; ADR-0080/0081). + - **structured canvas** for dashboards / slotted pages — widget blocks with + `+ add widget`, edited by selecting a block. +- **Right inspector** — component / block props, reused. +- **Build boundary** — rail (`NavTree` / `AppNavCanvas`) = built; **main = reused + `SchemaRenderer` (react/html) or the dashboard/page renderer (structured)**; + **inspector = reused component props**. + +--- + +## 7. Access pillar · **[designed]** + +Access is the **permission workbench**: who can do what to which object. Its natural +shape is a **matrix** (roles × objects × CRUD + record scope), which already exists as +a Studio panel — so the pillar is that matrix dropped into the shell, nothing new. -- **Automation** — left: automations grouped by trigger (record-change / scheduled / - API / manual action); main: the flow **canvas**; inspector: the selected node's - config; a Runs view for execution history. -- **Interface** — left: surfaces (Apps · Pages · Views · Dashboards · Reports); - main: the page **builder** (structured canvas) or **source + preview** (html/react - pages); inspector: component props. -- **Access** — left: roles; main: the permission **matrix** (objects × CRUD + record - scope); inspector: role / field-level detail. -- **Settings** (⚙) — General (app basic info) and Advanced (Code: hooks · functions; - Connections: datasources), grouped by audience. +Mockup: [`builder-ui/access-pillar.html`](./builder-ui/access-pillar.html). + +``` +┌ roles ───┬ Permission matrix · role: Sales Rep ──────────────────┬ inspector ┐ +│ Admin │ Create Read Update Delete Record scope │ Role │ +│ Sales ◄ │ Account ☑ ☑ ☑ ☐ Owned + team │ detail │ +│ Support │ Contact ☑ ☑ ☑ ☐ Owned │ │ +│ Viewer │ Invoice ☐ ☑ ☐ ☐ All │ [reused │ +│ + New │ Task ☑ ☑ ☑ ☑ Owned │ role form]│ +└──────────┴───────────────────────────────────────────────────────┴────────────┘ +``` + +- **Left rail** — roles (and permission sets), with search + New role. +- **Main** — the permission **matrix** = reused `PermissionMatrixEditor`: rows = + objects, columns = **C / R / U / D** plus a **record-scope** column (owned / team / + all). Toggling a cell edits the role's object-permission metadata as a draft. +- **Right inspector** — the selected role (or object-permission) detail = reused + protocol form for `role`, with `RolePreview` / `PermissionPreview` for a live "what + this role can see" read-out. +- **Build boundary** — rail + shell = built; **matrix = reused `PermissionMatrixEditor`**; + **inspector = reused `role` protocol form + `RolePreview`/`PermissionPreview`**. +- **v1 scope** — roles × objects **CRUD + record scope**. **Defer**: the field-level + permission matrix, sharing rules, and capability / permission-set *composition* + (the "concept overload" risk — v1 stays at roles × objects × CRUD, one clear grid). + +--- + +## 8. Settings (⚙) · **[designed]** + +Settings is **not** a content pillar — it is the app's own configuration, split by +**audience** (ADR-0084 §5): a builder opening Settings for app info must not land in +code. Every panel is a reused protocol form; the area only composes them. + +Mockup: [`builder-ui/settings.html`](./builder-ui/settings.html). + +``` +┌ ⚙ Settings ─────────────────────────────────────────────────────────────────┐ +│ General │ the app's identity & defaults [reused app form] │ +│ · Basics │ name · id · icon · description · branding │ +│ · Navigation │ default landing · nav structure │ +│ ───────────────── │ │ +│ Advanced (devs) │ Code Hooks (v1) · functions · components (later) │ +│ · Code │ [reused hooks list + hook form] │ +│ · Connections │ Connections datasource · connector · webhook (later) │ +└──────────────────────────────────────────────────────────────────────────────┘ +``` + +- **General** — the app's identity and defaults: name · id · icon · description · + branding · default landing · navigation structure. This is the `app` definition → + reused **`app` protocol form**. +- **Advanced** — the technical tier, kept visually separate from General so a builder + isn't dropped into code. Sub-grouped **Code** (v1: **Hooks** — a hooks list + reused + hook protocol form; later: functions, custom components, custom field types) and + **Connections** (later: datasources, connectors, webhooks, mappings). +- **Build boundary** — the ⚙ area chrome + audience grouping = built; **every panel = + reused protocol form** (`app`, `hook`, …). +- **v1 scope** — **General + Advanced/Hooks only** (ADR-0084 §7). The rest of Advanced + is deferred. --- @@ -265,8 +408,13 @@ still to design: Open any `*.html` in a browser to view it. Current mockups: -| Surface | Mockup | -|---|---| -| Data pillar | [`data-pillar.html`](./builder-ui/data-pillar.html) | +| Surface | Mockup | Status | +|---|---|---| +| Data pillar | [`data-pillar.html`](./builder-ui/data-pillar.html) | built (partial — see §4) | +| Automation pillar | [`automation-pillar.html`](./builder-ui/automation-pillar.html) | built | +| Interface pillar | [`interface-pillar.html`](./builder-ui/interface-pillar.html) | built | +| Access pillar | [`access-pillar.html`](./builder-ui/access-pillar.html) | designed | +| Settings (⚙) | [`settings.html`](./builder-ui/settings.html) | designed | -More are added per pillar as they are designed. +The **built** mockups depict the shipped `StudioDesignSurface` (2026-07); the +**designed** ones are the target for the not-yet-built pillars (§7–§8). diff --git a/docs/design/builder-ui/access-pillar.html b/docs/design/builder-ui/access-pillar.html new file mode 100644 index 0000000000..480c6a3ab1 --- /dev/null +++ b/docs/design/builder-ui/access-pillar.html @@ -0,0 +1,101 @@ + + + + + Builder — Access pillar + + + + +
+ + +
+ app ▾ + +
+ + Save draft + +
+
+ +
+ + + + + +
+
+ Permission matrix + role · Sales Rep +
+
+
+ + + + + + + + + + +
ObjectCreateReadUpdateDeleteRecord scope
AccountOwned + team
ContactOwned
InvoiceAll
TaskOwned
+
Toggle a cell to edit this role's object permission (draft)
+ matrix = existing PermissionMatrixEditor (reused) +
+
+
+ + + +
+ + +
+ solid = shell / composition (built in the builder) + dashed = existing Studio component (reused, not rebuilt) +
+
+ + diff --git a/docs/design/builder-ui/automation-pillar.html b/docs/design/builder-ui/automation-pillar.html new file mode 100644 index 0000000000..d8f1b750f2 --- /dev/null +++ b/docs/design/builder-ui/automation-pillar.html @@ -0,0 +1,113 @@ + + + + + Builder — Automation pillar + + + + +
+ + +
+ app ▾ + +
+ + Save draft + +
+
+ +
+ + + + + +
+ + +
+ Trigger: autolaunched + Status: draft + Run as: user + v1 + Variables + Runs + Problems + Debug + + + Add node + 100% +
+ + +
+
+
+ + On Task Update
START · status == "done"
+
+ +
+ </> + Send Completion Email
SCRIPT · email
+
+ +
+ + End
END
+
+ flow canvas = existing FlowCanvas (reused) +
+
+
+ + + +
+ + +
+ solid = shell / composition (built in the builder) + dashed = existing Studio component (reused, not rebuilt) +
+
+ + diff --git a/docs/design/builder-ui/interface-pillar.html b/docs/design/builder-ui/interface-pillar.html new file mode 100644 index 0000000000..6c49a0ea98 --- /dev/null +++ b/docs/design/builder-ui/interface-pillar.html @@ -0,0 +1,114 @@ + + + + + Builder — Interface pillar + + + + +
+ + +
+ app ▾ + +
+ + Save draft + +
+
+ +
+ + + + + +
+
+ 👁 preview = runtime + page · showcase_start_here +
+
+
+
+
<flex dir="col" gap={8}> + <div style={{ fontWeight: 600, + textTransform: "uppercase" }}> + ObjectStack Showcase + </div> + <div style={{ fontSize: 32, + fontWeight: 700 }}> + Pick the right page + authoring model + </div> + <card> Decision tree … </card> +</flex>
+
+
OBJECTSTACK SHOWCASE
+

Pick the right page authoring model

+
Every page has two axes — its type (home · record · list · app) and its kind (how you author it).
+
Decision tree · full/slotted → html → react
+
+
+ source + preview = SchemaRenderer (reused) +
+
+
+ + + +
+ + +
+ solid = shell / composition (built in the builder) + dashed = existing Studio component (reused, not rebuilt) +
+
+ + diff --git a/docs/design/builder-ui/settings.html b/docs/design/builder-ui/settings.html new file mode 100644 index 0000000000..97cb8ba25a --- /dev/null +++ b/docs/design/builder-ui/settings.html @@ -0,0 +1,112 @@ + + + + + Builder — Settings (⚙) + + + + +
+ + +
+ app ▾ + +
+ ⚙ Settings + Save draft + +
+
+ +
+ + + + + +
+ + +
+

General Builder

+
+
Basics
+
+
Name
+
Showcase
+
App id
+
com.example.showcase
+
Icon
+
◆ layout-dashboard
+
Default landing
+
Start Here ▾
+ app protocol form (reused) +
+
+
+
Navigation
+
+
Workspace · Data model · Analytics — drag to reorder groups & items
+ app navigation editor (reused) +
+
+
+ + +
+

Advanced Developers

+
+
Code v1: Hooks
+
+
+ Hooks — before/after write-path logic. Select a hook → reused hook form. +
+
+ beforeInsert: stampOwner + afterUpdate: notify + + add +
+ hooks list + hook form (reused) +
+
functions · custom components · custom field types — later
+
+
+
Connections
+
datasource · connector · webhook · mapping — later
+
+
+ +
+
+ + +
+ solid = shell / composition (built in the builder) + dashed = existing Studio component (reused, not rebuilt) +
+
+ + diff --git a/docs/design/builder-ui/shell.css b/docs/design/builder-ui/shell.css index cca7a5fd92..050a830992 100644 --- a/docs/design/builder-ui/shell.css +++ b/docs/design/builder-ui/shell.css @@ -97,3 +97,58 @@ table.grid th.col-selected { color: var(--accent); } .legend .sw { display: inline-block; width: 14px; height: 12px; vertical-align: -1px; margin-right: 6px; } .legend .sw.built { border: 1px solid var(--text); } .legend .sw.reused { border: 1px dashed #c8ccd2; } + +/* ================================================================ + Pillar-specific primitives — composed by the automation / interface + / access / settings mockups. Same tokens; one shared look. */ + +/* ---- rail sub-groups + kind badges (interface nav tree, access, settings) ---- */ +.rail .group { font-size: 10px; letter-spacing: .5px; color: var(--faint); text-transform: uppercase; margin: 14px 0 4px; } +.rail li { display: flex; align-items: center; gap: 6px; } +.rail li .kind { margin-left: auto; font-size: 8px; color: var(--faint); background: var(--chip); border-radius: 6px; padding: 1px 5px; text-transform: uppercase; letter-spacing: .3px; } + +/* ---- flow canvas (automation) ---- */ +.canvas-strip { display: flex; align-items: center; gap: 12px; padding: 7px 16px; border-bottom: 1px solid var(--border); color: var(--muted); font-size: 11px; } +.canvas-strip .pill { border: 1px solid var(--border); border-radius: 5px; padding: 2px 8px; } +.canvas-strip .pill.on { color: var(--accent); border-color: var(--accent); background: var(--accent-bg); } +.canvas-strip .spacer { flex: 1; } +.canvas-strip .add { color: var(--accent); font-weight: 600; } +.canvas-wrap { flex: 1; padding: 0 16px 16px; overflow: auto; } +.canvas { position: relative; height: 100%; border: 1px dashed #c8ccd2; border-radius: var(--radius); padding: 28px 12px; + background-image: radial-gradient(var(--border) 1px, transparent 1px); background-size: 16px 16px; } +.canvas .reuse-tag { position: absolute; left: 10px; bottom: 6px; font-size: 9.5px; font-style: italic; color: var(--faint); background: var(--surface-1); padding: 0 4px; } +.node { width: 260px; margin: 0 auto; background: var(--surface-1); border: 1px solid var(--border); border-radius: 8px; padding: 11px 13px; display: flex; gap: 10px; align-items: center; box-shadow: 0 1px 2px rgba(0,0,0,.04); } +.node .ic { width: 26px; height: 26px; border-radius: 7px; background: var(--accent-bg); color: var(--accent); display: flex; align-items: center; justify-content: center; font-size: 13px; flex-shrink: 0; } +.node .nlabel { font-weight: 600; font-size: 12px; } +.node .kind { font-size: 9px; letter-spacing: .5px; color: var(--muted); text-transform: uppercase; } +.node-link { width: 1px; height: 22px; background: var(--border); margin: 0 auto; } + +/* ---- source + live preview split (interface) ---- */ +.split { display: flex; height: 100%; } +.split .code { flex: 1; background: #f6f8fa; border-right: 1px solid var(--border); padding: 12px 14px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 10.5px; color: #4b5563; white-space: pre; overflow: auto; line-height: 1.75; } +.split .preview { flex: 1; padding: 16px 18px; overflow: auto; } +.split .preview .eyebrow { font-size: 9px; letter-spacing: 1px; color: var(--muted); text-transform: uppercase; } +.split .preview h2 { font-size: 19px; margin: 6px 0 10px; } +.split .preview .card { border: 1px solid var(--border); border-radius: var(--radius); padding: 12px 14px; margin-top: 10px; font-size: 11px; color: var(--muted); } + +/* ---- permission matrix (access) ---- */ +table.matrix { width: 100%; border-collapse: collapse; font-size: 11.5px; } +table.matrix th { text-align: center; background: var(--surface-2); color: var(--muted); font-weight: 600; padding: 8px 10px; border-bottom: 1px solid var(--border); } +table.matrix th:first-child { text-align: left; } +table.matrix td { padding: 9px 10px; border-bottom: 1px solid var(--line); text-align: center; } +table.matrix td:first-child { text-align: left; font-weight: 500; } +.ck { display: inline-block; width: 15px; height: 15px; border-radius: 4px; border: 1px solid #cfd4da; vertical-align: -2px; } +.ck.on { background: var(--accent); border-color: var(--accent); position: relative; } +.ck.on::after { content: "\2713"; color: #fff; font-size: 10px; line-height: 15px; position: absolute; left: 2.5px; top: -1px; } +.scope { font-size: 10px; color: var(--muted); background: var(--chip); border-radius: 10px; padding: 2px 9px; } + +/* ---- settings (audience groups) ---- */ +.settings { flex: 1; padding: 18px 22px; overflow: auto; } +.settings .sgroup { border: 1px solid var(--border); border-radius: 8px; margin-bottom: 16px; overflow: hidden; } +.settings .sgroup > h4 { margin: 0; padding: 9px 14px; background: var(--surface-2); border-bottom: 1px solid var(--border); font-size: 12px; display: flex; align-items: center; gap: 8px; } +.settings .sgroup > h4 .aud { margin-left: auto; font-size: 9px; color: var(--faint); background: var(--chip); border-radius: 6px; padding: 1px 7px; text-transform: uppercase; letter-spacing: .4px; } +.settings .sub { padding: 12px 14px; border-bottom: 1px solid var(--line); } +.settings .sub:last-child { border-bottom: 0; } +.settings .sub .subhead { font-size: 10.5px; color: var(--muted); text-transform: uppercase; letter-spacing: .5px; margin-bottom: 9px; display: flex; align-items: center; gap: 8px; } +.settings .later { color: var(--faint); font-style: italic; } +.settings .tag { font-size: 8.5px; color: var(--muted); background: var(--chip); border-radius: 7px; padding: 1px 6px; }