Skip to content

Commit ab72d62

Browse files
os-zhuangclaude
andauthored
docs(audits): Studio package-create UX dogfood — browser walkthrough findings (#2609)
Full first-run authoring loop driven in a real Chromium against the vendored console (pin-matched 7782698): create package -> object -> picklist field -> record -> app -> nav -> publish -> end-user app. Loop closes; findings ranked: late read-only gating with ADR-path error copy, field API name not following label, one-click publish with thin Changes panel, mixed-language screens, new-package wizard ID ergonomics, no nav scaffolding on app create, plus grid and copy polish. Claude-Session: https://claude.ai/code/session_01DHQc5BdGhTzPfazex3vWdt Co-authored-by: Claude <noreply@anthropic.com>
1 parent 0426d27 commit ab72d62

1 file changed

Lines changed: 153 additions & 0 deletions

File tree

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
# Studio "create a package and build in it" UX dogfood (browser)
2+
3+
Goal: walk the **whole first-run authoring loop a new admin actually experiences**
4+
boot the showcase example, log in, create a writable package in Studio, model an
5+
object with fields, enter a record, create an app with navigation, publish, and
6+
use the result as an end user — driving a real Chromium against `/_console`
7+
(vendored console build `7782698`, matching the `.objectui-sha` pin, so findings
8+
are not stale-bundle artifacts). Both sides of the read-only gate were exercised
9+
per ADR-0057 D10.
10+
11+
Run: `objectstack dev --ui --seed-admin` on the showcase example, fresh SQLite DB,
12+
`admin@objectos.ai` seeded admin, headless Chromium via CDP with screenshots at
13+
every step.
14+
15+
## The loop closes
16+
17+
Home → **Build an app** → Studio landing → **+ 新建软件包** (`维修中心` /
18+
`com.example.repairs`) → Data pillar → object `Repair Ticket` (identifier
19+
auto-suggested from the English display name) → picklist field `Status` with 3
20+
values → **Save draft** → Changes panel → **Publish** → record created in the
21+
runtime-faithful Records grid → **Create app** `Repair Center` (identifier
22+
auto-suggested) → Interfaces pillar → nav item bound to the object → **Publish**
23+
→ app appears in the Home launcher → end-user list renders the record with the
24+
picklist label chip. **Zero code, no dead ends, minutes end-to-end.** The
25+
draft→publish model reads consistently everywhere ("Unpublished draft" badge,
26+
`Changes · n` counter, "Published all drafts in this package (one atomic
27+
release)").
28+
29+
Other things that hold up well:
30+
31+
- **Empty states teach the next step at every level** (object list, nav editor,
32+
records grid hint bar).
33+
- **Previews are the runtime renderer** ("Preview = runtime · same renderer") —
34+
nothing to un-learn between builder and app.
35+
- **Field type catalog** is deep (40+ types, grouped) and the properties panel
36+
covers advanced needs (CEL conditional-required, track history, indexed).
37+
- **Server enforces the writable/read-only gate**: creating an object in the
38+
source-loaded showcase package is rejected server-side even though the client
39+
let the gesture through (see finding 1).
40+
- Package switcher clearly badges **Read-only vs Writable** and offers "+ New
41+
package (writable base)".
42+
43+
## Findings (ordered by user pain)
44+
45+
### 1. Read-only packages accept edit gestures client-side, then fail late — P1
46+
47+
On `com.example.showcase` (Read-only badge shown), **"+ Add field" actually
48+
mutates the canvas** (header count went 16 → 17 fields, Field properties panel
49+
opened on `field_17`), and **"New object" opens the inline create form**. Only on
50+
the eventual server write does the user learn it was all futile — via a toast
51+
whose copy is developer-facing: *"read-only code/installed package… See
52+
docs/adr/0070-package-first-authoring.md"*. An internal ADR path is not an
53+
end-user remediation.
54+
55+
Fix: client-side courtesy gating (disable/hide `Add field`, `New object`,
56+
`Save draft`, `Publish`, field-property inputs when the package is read-only,
57+
with a tooltip "Read-only package — create a writable package to edit"), and
58+
rewrite the server error copy to say what to *do* ("Switch to a writable package
59+
(top-left selector) or create one"), keeping the ADR pointer out of the UI.
60+
61+
### 2. Field API name doesn't follow the label before first save — P1
62+
63+
"Add field" creates `field_2` / label "New field". Renaming the label to
64+
`Status` leaves the API name `field_2`, and the data column is then `field_2`
65+
forever (verified: the saved record stores `"field_2": "in_progress"`). Object
66+
and app identifiers *do* auto-suggest from the display name, so authors expect
67+
the same here and won't look at the API name input.
68+
69+
Fix: sync API name from label while the field is new/unsaved (exactly the
70+
object-identifier behavior), lock it after first save.
71+
72+
### 3. Publish is one click, no confirmation, and the Changes panel is too thin — P1
73+
74+
Publish fires immediately (toast: "Published all drafts in this package (one
75+
atomic release)") with no confirm step, and the Changes panel shows only
76+
`repair_ticket · New` — no field-level diff, no drill-in. Review-before-release
77+
is the panel's whole job (ADR-0016 §3.6 step 4), and one mis-click currently
78+
releases everything.
79+
80+
Fix: Publish opens the pending-changes list with a confirm button; give the
81+
panel per-item detail (fields added/changed, updates that overwrite live).
82+
83+
### 4. Mixed languages within single screens — P2
84+
85+
Login/Home are English; the Studio landing is Chinese (应用构建 / 新建软件包);
86+
the builder is English chrome with embedded Chinese strings (添加字段 / 未分组 /
87+
请选择… / 拖动字段排序…); the record form placeholder is 请选择… inside an
88+
otherwise-English dialog. Whatever the intended locale, half-translated screens
89+
read as unfinished. Audit the console's i18n keys for both locales; make the
90+
studio landing and canvas hint strings go through the same locale resolution as
91+
the rest of the console.
92+
93+
### 5. New-package wizard: no ID suggestion, silent input munging, unexplained disabled button — P2
94+
95+
- Typing the name (维修中心) suggests nothing for the package ID (object/app
96+
identifiers do get suggestions — inconsistent).
97+
- Chinese names produce **no** identifier suggestion anywhere (no pinyin, no
98+
fallback), so zh-first users must guess the allowed charset.
99+
- Illegal characters are silently stripped while typing (`bad id!!``badid`).
100+
- With an invalid ID the create button is just disabled — no inline message
101+
explaining the required reverse-domain format (`com.example.repairs`).
102+
103+
Fix: suggest an ID from the name (transliterate or fall back to `com.<tenant>.appN`),
104+
show the format rule inline when the button is disabled, and don't silently eat
105+
keystrokes — show what's invalid.
106+
107+
### 6. A new app scaffolds no navigation — P2
108+
109+
"Create app" produces an app with zero nav items; the object the user just
110+
built isn't offered. The user must discover Interfaces → Edit → Add nav item →
111+
Label → Link to object. The create-app popover could offer "add existing
112+
objects as menu items" (checked by default) and pre-label the item with the
113+
object's plural label — that single step would have saved the entire manual
114+
wiring in this run.
115+
116+
### 7. Records grid shows a duplicated "Actions" column in Studio — P3
117+
118+
The Studio Records tab (and its Interfaces preview) renders an `Actions` data
119+
column *and* the pinned row-actions column, both headed "Actions"
120+
(`#/Name/Status/Actions/Actions/+`). The end-user app shows only one. Likely the
121+
grid injects its own actions column without deduping against the runtime one.
122+
123+
### 8. Verb/label inconsistencies around record creation — P3
124+
125+
Studio Records uses a big blue **Create** button adjacent to top-bar **Create
126+
app**; the runtime app calls the same action **New**. The record dialog's submit
127+
is **Create**. Pick one verb ("New" for records, keep "Create app" for apps) to
128+
de-collide the two adjacent buttons.
129+
130+
### 9. Cold `/_console` load sits on a bare "Loading…" for ~8s — P3
131+
132+
First hit renders unbranded "Loading…" before redirecting to login. A logo +
133+
skeleton (or a faster auth probe) would make the first impression match the
134+
otherwise polished login screen.
135+
136+
### 10. Small polish — P3
137+
138+
- Picklist value-editor inputs are so narrow their placeholders truncate
139+
("valu" / "Labe"), and CJK labels show ~3 chars (待处理 → 待处…).
140+
- The read-only toast can appear twice for one gesture.
141+
- Interfaces canvas showed a ghost gray rectangle artifact below the fold on an
142+
empty app.
143+
- "Save draft" disables after a successful save (good) but there's no "last
144+
saved" timestamp anywhere.
145+
146+
## Verdict
147+
148+
The MVP loop of ADR-0016 §9 genuinely works first-try, and the
149+
draft→publish→run mental model comes across. The pain concentrates in three
150+
places: **read-only gating happens too late and speaks ADR** (finding 1),
151+
**identifier ergonomics** (findings 2 & 5, worst for CJK-named things), and
152+
**publish/review confidence** (finding 3). All are console-side (objectui)
153+
except the read-only error copy, which is the framework's dispatcher message.

0 commit comments

Comments
 (0)