Skip to content

Commit 77b40db

Browse files
authored
feat(flow-designer): localize palette headings + cloud-sync recents + guide (#2553)
Three follow-ups to the add-node palette search (#1943): 1. Localize the palette category headings (Data / Logic / Human / Integration / Flow) to the active console language via five new engine.flowPalette.category.* keys (en + zh). The NodeCategory literals stay English as in-memory bucket keys / render-order tokens. 2. Cloud-sync the "Recently used" list per-user via sys_user_preference (new FlowPaletteRecentsProvider / useFlowPaletteRecents), reusing the existing UserDataAdapter stack — localStorage-first, debounced, cross-tab, offline-degrading — with one-shot migration of the legacy localStorage key and a localStorage fallback outside a provider. 3. Add a Flow Designer guide (content/docs/guide/flow-designer.md).
1 parent b02c072 commit 77b40db

12 files changed

Lines changed: 592 additions & 21 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@object-ui/app-shell': minor
3+
---
4+
5+
Flow designer add-node palette follow-ups (#1943): localize the category section headings (Data / Logic / Human / Integration / Flow) to the active console language, and upgrade the "Recently used" list from browser-local storage to per-user cloud sync via `sys_user_preference` (new `FlowPaletteRecentsProvider` / `useFlowPaletteRecents`), with one-shot migration of the legacy localStorage key and a localStorage fallback when offline or outside a provider. Adds a Flow Designer guide to the docs.
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
---
2+
title: "Flow Designer"
3+
description: "Authoring automation flows on the visual canvas — nodes, edges, the searchable add-node palette, the node inspector, validation, and simulation."
4+
---
5+
6+
# Flow Designer
7+
8+
The **Flow Designer** is the visual editor for `flow` metadata in the console's
9+
metadata admin. It renders a flow's nodes and edges on a pan/zoom canvas so you
10+
can assemble automation — create/update records, branch on conditions, wait for
11+
events, call APIs, route for approval — without hand-editing JSON. The designer
12+
is a thin, **spec-driven** view over `flow` metadata: everything you drop on the
13+
canvas is a node in the flow's schema, so the same document runs unchanged on
14+
the automation engine.
15+
16+
Open it from a `flow` record's **Design** tab. The **Form** tab holds the same
17+
flow as an editable tree; the two stay in sync.
18+
19+
## The canvas
20+
21+
Each node is a card showing its icon, label, `type`, and a one-line summary of
22+
its config. Edges are the arrows between them.
23+
24+
| Gesture | Result |
25+
|---|---|
26+
| Click a card | Select it (opens the node inspector) |
27+
| Drag a card | Reposition it |
28+
| Hover a card → click the bottom **+** | Append a connected child node |
29+
| Zoom controls / **Fit** | Scale and re-center the graph |
30+
31+
Structural problems (an undeclared cycle, an unreachable node) surface three
32+
ways at once: a red ring on the offending card, a badge in its corner, and an
33+
inline banner at the top-left of the canvas. Clicking a banner row selects and
34+
pans to the element it refers to.
35+
36+
## Adding nodes — the palette
37+
38+
The **Add node** button (top-right, edit mode only) opens the node palette: a
39+
searchable, grouped list of every node type the flow can use.
40+
41+
### Search and keyboard
42+
43+
Type in the box at the top to filter across **all** categories at once — the
44+
match is a case-insensitive substring test over each node's **label**, **hint**,
45+
and **type**, so `scr` finds *Screen*, `http` finds *HTTP request*, and a word
46+
that only appears in a hint (`concurrently`*Parallel*) still surfaces the
47+
node. Clearing the box restores the full grouped list.
48+
49+
The palette is keyboard-navigable end to end:
50+
51+
| Key | Action |
52+
|---|---|
53+
| `` / `` | Move the highlight (wraps around) |
54+
| `Enter` | Insert the highlighted node |
55+
| `Esc` | Close the palette |
56+
57+
The search box autofocuses when the palette opens, so you can open, type a few
58+
letters, and press `Enter` without touching the mouse.
59+
60+
### Categories
61+
62+
Nodes are grouped into five sections, in this order:
63+
64+
| Category | Node types |
65+
|---|---|
66+
| **Data** | Create / Update / Get / Delete record |
67+
| **Logic** | Decision, Loop, Set variables, Parallel, Try / Catch |
68+
| **Human** | Approval, Screen |
69+
| **Integration** | HTTP request, Connector, Script |
70+
| **Flow** | Subflow, Wait, End |
71+
72+
Section headings are localized to the active console language (e.g. *数据 /
73+
逻辑 / 人工 / 集成 / 流程* in Chinese); the underlying node types are unchanged.
74+
75+
### Recently used
76+
77+
When the search box is empty, a **Recently used** group tops the list with the
78+
node types you inserted most recently (up to five, most-recent first) — so the
79+
nodes you reach for repeatedly stop needing a scroll or a search. The list is
80+
per-user and, when the console is connected to a backend, syncs across devices
81+
(see [User-Scoped State Persistence](/docs/guide/user-state-persistence)); it
82+
falls back to browser-local storage when offline. Types from a plugin that was
83+
since uninstalled drop out of the list automatically.
84+
85+
### Server-merged, plugin-extensible
86+
87+
The palette is **server-driven**. Beyond the built-in node types, the running
88+
engine publishes its registered actions at `GET /api/v1/automation/actions`, and
89+
plugins contribute their own node types there (for example an `approval` node
90+
from an approvals plugin, or third-party `connector_action` providers). The
91+
designer overlays those descriptors onto the built-in list — adopting the
92+
engine's labels and descriptions and appending engine-only types — so the
93+
palette always matches what the connected backend actually supports. Plugin
94+
nodes are searchable exactly like built-ins, including by their registered
95+
`type`. When the endpoint is unreachable the designer falls back to its
96+
built-in defaults, so authoring still works offline.
97+
98+
## The node inspector
99+
100+
Selecting a node opens its inspector: **ID**, **Label**, **Node Type**, an
101+
optional **Description**, and a **Configuration** section. New nodes start with
102+
spec-valid defaults (a *Wait* node already carries a timer config, an *HTTP*
103+
node defaults to `GET`) so a freshly dropped block is never in a broken
104+
intermediate state.
105+
106+
For node types whose engine executor publishes a `configSchema` (ADR-0018), the
107+
inspector renders a **server-driven property form** from that schema — so a
108+
plugin's node gets a real config UI without the designer hardcoding its fields.
109+
110+
## Validate, simulate, inspect runs
111+
112+
The toolbar toggles four side panels:
113+
114+
| Panel | What it shows |
115+
|---|---|
116+
| **Variables** | The flow's declared variables |
117+
| **Problems** | Structural + server validation issues, each clickable to reveal on canvas |
118+
| **Debug** | A step-through **simulator** that walks the graph and highlights the active / visited nodes |
119+
| **Runs** | Execution history for the published flow, fetched from the engine |
120+
121+
## See also
122+
123+
- [User-Scoped State Persistence](/docs/guide/user-state-persistence) — how the
124+
"Recently used" list is stored and synced.
125+
- [Console App](/docs/guide/console) — the reference app that hosts the metadata
126+
admin and its designers.

content/docs/guide/meta.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"slotted-pages",
2525
"console",
2626
"console-architecture",
27+
"flow-designer",
2728
"metadata-diagnostics",
2829
"user-state-persistence",
2930
"objectos-integration",

content/docs/guide/user-state-persistence.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
title: "User-Scoped State Persistence"
33
---
44

5-
Object UI keeps two pieces of per-user UI state — **Favorites** (pinned apps, starred records, and pinned navigation entries) and **Recent Items** (last visited entities) — alive across reloads, devices, and accounts. The persistence layer is **backend-agnostic**: drop in an adapter, or just let it run on localStorage.
5+
Object UI keeps per-user UI state — **Favorites** (pinned apps, starred records, and pinned navigation entries), **Recent Items** (last visited entities), and **Flow-palette recents** (node types recently added in the flow designer) — alive across reloads, devices, and accounts. The persistence layer is **backend-agnostic**: drop in an adapter, or just let it run on localStorage.
66

77
## Design
88

@@ -16,21 +16,21 @@ export interface UserDataAdapter<T> {
1616
save(items: T[]): Promise<void>;
1717
}
1818

19-
export type UserStateKind = 'favorites' | 'recent';
19+
export type UserStateKind = 'favorites' | 'recent' | 'flowPaletteRecents';
2020
```
2121

2222
Three layers, top-down:
2323

2424
| Layer | Package | Role |
2525
|---|---|---|
26-
| **Providers** (`FavoritesProvider`, `RecentItemsProvider`) | `@object-ui/app-shell` | Own the state, debounce writes, scope storage by user. |
26+
| **Providers** (`FavoritesProvider`, `RecentItemsProvider`, `FlowPaletteRecentsProvider`) | `@object-ui/app-shell` | Own the state, debounce writes, scope storage by user. |
2727
| **Adapter registry** (`UserStateAdaptersProvider`) | `@object-ui/app-shell` | Lets a bridge component inject adapters at runtime once `dataSource` + `user.id` are available. |
2828
| **Adapters** (`createObjectStackUserStateAdapter`, your own) | `@object-ui/data-objectstack`, custom | Translate `load/save` into HTTP / GraphQL / ObjectQL calls. |
2929

3030
### Three guarantees
3131

3232
1. **localStorage-first.** First paint never blocks on the network. If no adapter is attached, persistence is purely local.
33-
2. **Scoped per `user.id`.** Storage key is `objectui-favorites:u:<id>` (and `objectui-recent-items:u:<id>`). Two accounts on the same browser never see each other's state.
33+
2. **Scoped per `user.id`.** Storage key is `objectui-favorites:u:<id>` (and `objectui-recent-items:u:<id>`, `flow-palette-recents:u:<id>`). Two accounts on the same browser never see each other's state.
3434
3. **Silent degrade.** Adapters must never throw. A 404 / network error means "behave like there is no backend"; the UI keeps working from localStorage.
3535

3636
## Provider tree
@@ -179,11 +179,12 @@ Each provider keeps a monotonic `hydrationToken`. If the user switches accounts
179179
| `useFavorites()` | `@object-ui/app-shell` | `{ favorites, addFavorite, removeFavorite, toggleFavorite, isFavorite, clearFavorites, setPinned, isPinned, pinnedNavIds }` |
180180
| `useNavPins()` | `@object-ui/app-shell` | Thin shim over `useFavorites` for sidebar pinning — `{ pinnedIds, togglePin, isPinned, applyPins, clearPins }`. |
181181
| `useRecentItems()` | `@object-ui/app-shell` | `{ recentItems, addRecentItem, clearRecentItems }` |
182+
| `useFlowPaletteRecents()` | `@object-ui/app-shell` | `{ recents, recordRecent }` — flow-designer add-node MRU; falls back to localStorage outside a provider. |
182183
| `createObjectStackUserStateAdapter(opts)` | `@object-ui/data-objectstack` | Official adapter against the `user_app_state` object. |
183184

184185
## Limits
185186

186-
- **20** favorites and **20** nav-pins per user (independent buckets), **8** recent items. Enforced by the providers; older entries roll off within their own bucket without evicting the other.
187+
- **20** favorites and **20** nav-pins per user (independent buckets), **8** recent items, **5** flow-palette recents. Enforced by the providers; older entries roll off within their own bucket without evicting the other.
187188
- One JSON blob per (user, kind). Not designed for high-frequency / large payloads — this is UI state, not data.
188189
- No automatic cross-tab sync today (see the roadmap).
189190

packages/app-shell/src/console/ConsoleShell.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { PreviewModeProvider } from '../preview/PreviewModeContext';
2424
import { NavigationProvider } from '../context/NavigationContext';
2525
import { FavoritesProvider } from '../context/FavoritesProvider';
2626
import { RecentItemsProvider } from '../context/RecentItemsProvider';
27+
import { FlowPaletteRecentsProvider } from '../context/FlowPaletteRecentsProvider';
2728
import {
2829
UserStateAdaptersProvider,
2930
useAttachUserStateAdapters,
@@ -83,9 +84,11 @@ export function ConsoleShell({ children }: { children: ReactNode }) {
8384
<UserStateAdaptersProvider>
8485
<FavoritesProvider>
8586
<RecentItemsProvider>
86-
<Suspense fallback={<LoadingFallback />}>{children}</Suspense>
87-
{/* ADR-0069 — full-screen gate (expired password / required MFA) above all routes */}
88-
<RemediationOverlay />
87+
<FlowPaletteRecentsProvider>
88+
<Suspense fallback={<LoadingFallback />}>{children}</Suspense>
89+
{/* ADR-0069 — full-screen gate (expired password / required MFA) above all routes */}
90+
<RemediationOverlay />
91+
</FlowPaletteRecentsProvider>
8992
</RecentItemsProvider>
9093
</FavoritesProvider>
9194
</UserStateAdaptersProvider>
@@ -175,6 +178,7 @@ function UserStateBridge() {
175178
if (!user?.id || !dataSource) {
176179
attach('favorites', null);
177180
attach('recent', null);
181+
attach('flowPaletteRecents', null);
178182
return;
179183
}
180184
const favorites = createObjectStackUserStateAdapter({
@@ -187,11 +191,18 @@ function UserStateBridge() {
187191
userId: user.id,
188192
key: 'ui.recent',
189193
});
194+
const flowPaletteRecents = createObjectStackUserStateAdapter<string>({
195+
dataSource,
196+
userId: user.id,
197+
key: 'ui.flow.palette.recents',
198+
});
190199
attach('favorites', favorites);
191200
attach('recent', recent);
201+
attach('flowPaletteRecents', flowPaletteRecents);
192202
return () => {
193203
attach('favorites', null);
194204
attach('recent', null);
205+
attach('flowPaletteRecents', null);
195206
};
196207
}, [user?.id, dataSource, attach]);
197208

0 commit comments

Comments
 (0)