Repository navigation
Studio empty states carry the primary action (Validations, Hooks, Actions, Automations, Access) #12514
Description
Source
Measured on objectui main d4bc81eab, 2026-10-15, in a writable package with one object and nothing else:
- Validations, Hooks and Actions tabs (
ObjectValidationsPanel.tsxand its two siblings): a list card about 150 px wide with a "New" button and "No validation rules yet…", beside a card that fills the rest of the column reading "Select a rule on the left, or click New to create one" (engine.studio.rules.pick); Hooks says "Select a hook to edit it", Actions "Select an action to edit its properties". - Automations pillar with no flow: the rail says "No automations yet — click New to start" (
engine.studio.auto.none) while the control is a "+" icon with no visible word; the canvas card reads "Select an automation" (engine.studio.auto.pick); the aside reads "Click a node on the canvas, and its configuration appears here". Three columns, all empty. - Access pillar with no permission set: the content reads "Create a permission set to start configuring" (
engine.studio.access.emptyMain) with no control; the "+" is in the rail header.
Target
- Each empty surface is one centered block in the content column: the pillar's icon, one sentence, a primary button that opens the same create dialog as the rail's "+" (New validation rule / New hook / New action / New automation / New permission set).
- The aside and the right card show nothing while there is nothing to select; the "Select…" sentences retire.
- The rail's empty sentence names the control it points at, or the control is a labelled button inside the rail's empty state.
File surface
packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx (Automations and Access empty branches), ObjectValidationsPanel.tsx, ObjectHooksPanel.tsx, the actions panel beside them, packages/app-shell/src/views/metadata-admin/i18n.ts (retired and new keys in every locale), tests beside.
Acceptance
- Tests: each empty state's button opens the create dialog (the same handler as the rail's "+").
- Screenshots of the five empty states at 1280 px, light and dark.
pnpm --filter @object-ui/app-shell testgreen.
Constraints
- Lands after Automations pillar: the rail groups flows by trigger object with a trigger-kind glyph and a status dot; the Enabled switch and the Trigger / Run as / version meta move into the item header; Variables / Runs / Problems / Debug become tabs #12454 (its claim covers the
AutomationsPillarregion); the Access branch coordinates with Access pillar: the "Permission matrix" band retires into a permission-set header; Objects / System capabilities / Row-level security / Tab visibility / Delegated admin become tabs; the object matrix gets OWD chips, 18 px checkboxes and hover-only bulk actions #12456 if that card is claimed first. - Existing
data-testids stay.
Evidence
Screenshots from the test pass, kept on the temporary branch claude/studio-test-pass-evidence-20261015 of objectstack-ai/objectui (commit 72115588c, never merged: the maintainer ruled that screenshots do not enter main, and asked for a temporary branch referenced from the card). The branch's docs/design/studio-redesign/findings/2026-10-15/README.md maps every file to its card. Pane captures, 800 × 604 of a 1482 × 1119 viewport, dark unless named light.

docs/design/studio-redesign/findings/2026-10-15/12514-empty-access.jpg — empty access.

docs/design/studio-redesign/findings/2026-10-15/12514-empty-actions.jpg — empty actions.

docs/design/studio-redesign/findings/2026-10-15/12514-empty-automations.jpg — empty automations.

docs/design/studio-redesign/findings/2026-10-15/12514-empty-hooks.jpg — empty hooks.

docs/design/studio-redesign/findings/2026-10-15/12514-empty-validations.jpg — empty validations.
Blocked-by: #12454
Filed by the epic PM session session_cdb7a717-def7-4e71-9988-f089fbaa18ff from the 2026-10-15 Studio test pass: objectui main d4bc81eab, HotCRM 63db83e2, the vite console on :5180 against objectstack serve --dev on :4001, viewport 1482 × 1119, dark theme with a light-theme check.
Parent: #12446
Body refreshed 2026-10-15T15:19Z by the epic PM session: an Evidence section with the test-pass screenshots added.
Activity
objectstack-fleet commentedon Oct 15, 2026
Claim: PM loop round 1 (domain:ui lane queue; an objectui#12446 test-pass card)
Session:
session_01SJn1GEkrbbBQkhGC26wEDwAccount:
os-sales(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-12514-empty-state-actionsWorktree:
objectui-issue-12514(in the cloud session's own container)Domain:
domain:uiSeat:
domain:ui#4File surface:
packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx(theAutomationsPillarandAccessPillarempty branches only),ObjectValidationsPanel.tsx,ObjectHooksPanel.tsx, the actions panel beside them,metadata-admin/i18n.ts(retired and new keys in every locale), new tests beside them, one.changeset/12514-*.md(stop on breach; explain in the report)Container & model:
M,mode:cloud,model: opus(cloud on the maintainer's stance 「可以的话,ui 后续开发尽量用云 session,并且要求截图给我看」; lane scope on 「你也可以接ui车道的卡」; batch 5 on 「上限提到 5,提速」, this session's chat, 2026-10-15)Clause-②: no
Responsibility:
n/a — not a defect cardThread-read: none
Serial constraints cleared: The card's own constraints are met: objectui#12454 (
AutomationsPillar) landed as8146afeand objectui#12456 (AccessPillar) as78e9fb2; objectui#12453 landed as2a69285. No open PR touches the rules, hooks or actions panels. In flight onStudioDesignSurface.tsxfrom this seat at this claim: objectui#12508 round 2 (theInterfacesPillarcanvas mount andstudio-canvas-preview.tsxonly), and the sibling cards of this wave: objectui#12510 (accessible names across the shell), objectui#12514 (the Automations and Access empty branches), objectui#12515 (the Form tab preview branch) and objectui#12522 (theInterfacesPillarpage branch). They are in disjoint regions; any textual overlap is resolved by mergingorigin/main.objectstack-fleet commentedon Oct 15, 2026
os-dev-report
{
"issue": 12514,
"status": "done",
"branch": "claude/issue-12514-empty-state-actions",
"pr": null,
"session": "session_018bpaEyeyB1SYJHvDWsn4pG",
"premise_still_valid": true,
"summary": "When a surface is empty it now shows one centred block. This covers an object with no validation rule, hook or action, and a package with no automation or permission set. The block holds the icon, one sentence and a primary button: New validation rule, New hook, New action, New automation or New permission set. The block is the new shared StudioEmptyState.tsx, which sits beside the panels. The button uses the existing 'New' or rail '+' path and adds no create path of its own. For Validations and Actions it opens the same presets popover. For Hooks it runs the same addHook. For Automations and Access it calls the handler the rail '+' now shares (openCreateFlow and openCreateSet, lifted out of the inline onAdd). Where nothing can be selected, the 'Select…' sentences are gone, and the Automations aside is not drawn while the package holds no flow. The Automations and Access rails' empty sentence now names the '+' above it. A read-only package shows the sentence alone, with no button and no rail pointer. Retired keys, en and zh: rules.pick, rules.explain, actions.pick, auto.pick, access.pick. Added: rules/hooks/actions.newButton and auto.emptyMain. Rewritten: auto.none, access.none, access.emptyMain. Premise checked against main 572bbce. The card's snippets still held, and the PM's assumptions 1–4 were measured (see open_questions and deviations). The card's assignee was not written. The channel was degraded, so no PR was opened and no card comment was posted; the PR body is below.",
"tests": "All runs on HEAD b780c69, from the worktree root. (1) Full run as the card's acceptance names it:
pnpm --filter @object-ui/app-shell testunder os-verify-lock, in the foreground then waited on its PID. Result: 'Test Files 1412 passed | 1 skipped (1413)', 'Tests 13479 passed | 9 skipped (13488)', PNPM_EXIT=0, captured before the echo. (2) The two new files:pnpm exec vitest run --maxWorkers=2 packages/app-shell/src/views/studio-design/StudioEmptyState.panels-12514.test.tsxgave 12 passed. StudioDesignSurface.emptyStates-12514.test.tsx gave 6 passed. (3) Every studio-design test plus studioAutomationCopy.plainLanguage-11659 was run before the pin updates: 5 files / 14 tests failed, all of them pins that read the retired paths. After the updates, those 7 files gave 78/78, and the Object{Actions,Validations,Hooks}Panel* group plus the new files gave 20 files / 162 tests passed. (4) Six ablations through objectstack scripts/ablation-replace.mjs, with literal anchors. Each one landed (anchor 1→0, blob moved), turned exactly its pin red, then restored to 'blob == HEAD and git diff HEAD is empty'. hooks-onclick: the empty button's onClick={addHook} set to undefined → 'shows one block once the read answers, and its button saves the new hook' red. rules-focus: the menuFromEmpty focus handoff disabled → 'once the first rule lists, the menu hands focus to the header's New' red. auto-onclick → 'the button opens the dialog the rail's "+" opens' red. auto-aside: flowsEmpty guard disabled → 'one block in the content column, no aside…' red. access-onclick → Access 'the button opens the dialog…' red. rail-readonly: rail condition forced true → Automations 'read-only: the sentence alone…' red. My first hooks attempt was refused by the tool (the replacement text already occurred in the file, so the count was not 0→1). Nothing ran on that attempt, and it was redone with a unique marker. No build or dist step is involved: the tests import src relatively.","gates": "Local, on b780c69, each exit code captured before any pipe.
pnpm --filter @object-ui/app-shell type-check= 0, after building the dependency closure withturbo run build --filter='@object-ui/app-shell^...' --concurrency=2: 28/28 tasks, lock VERDICT 0.pnpm --filter @object-ui/app-shell build= 0 (lock VERDICT command-exit 0).pnpm check:i18n-keys= 0.pnpm check:i18n-drift= 0 ('3 zh value(s) followed').pnpm check:i18n-dead-keys= 0, none of this diff's keys listed.pnpm check:i18n-designer-parity= 0.node scripts/check-changeset-presence.mjs= 0 (1 changeset declared).pnpm check:new-line-citations= 0. Plaineslinton the 13 touched .ts/.tsx files = 0 errors and 18 warnings; per-file warning counts equal origin/main's copies (Validations 1/1, Actions 0/0, Hooks 1/1, StudioDesignSurface 14/14). An intermediate addMenu-as-function shape added 2 'refs during render' warnings; it was restructured, and they are gone. With--no-inline-configthere are 2 errors, both pre-existing: react-hooks/static-components on Actions' Inspector and Hooks' HookInspector, held by inline disables on main too. Main's copy of ObjectActionsPanel gives the same error. Manual control-byte scan over the diff: no hits.node scripts/check-nul-bytes.mjs= MODULE_NOT_FOUND because that script lives only in objectstack; this is NOT MEASURED and not a failure. No dispatch-gates derivation exists in objectui (only check-half-states.mjs), so the list was derived by hand from package.json. CI: no PR yet, so no check-runs to cite; CI families are NOT MEASURED until the PM opens the draft. origin/main has moved 9 commits, to 3821a3a.git merge-treeis clean, main's StudioDesignSurface hunks are in InterfacesPillar, and no new consumer of a retired key appeared. No merge was needed.","mcp_calls": "6 — claude-code-remote add_repo ×2 (objectstack, hotcrm; read, nothing attached), ToolSearch ×1, github issue_read ×3 (12514 get, 12514 get_comments, 12446 get_comments). No write tool.",
"api_writes": "0 REST/MCP writes. git push ×3 (objectui): the empty feature branch, the feature branch at b780c69, and the evidence branch claude/issue-12514-evidence at 5313a50. The local HotCRM backend received a package create and an object draft PUT; that was localhost only, on a --fresh temp DB.",
"open_questions": [
{
"question": "Validations: the old list empty state showed two lines ('No validation rules yet.' plus rules.explain, 'Rules run when a record is saved…'). The ruling asks for one sentence, so rules.explain was retired, en and zh. Keep it retired, or bring the explanation back as the block's sentence?",
"options": [
"A keep retired: one sentence, as ruled",
"B reword one sentence that both says 'none yet' and explains what a rule does"
],
"recommendation": "A. Rulings 1–3 say one sentence. The startup axis favours fewer keys and no second line. The explanation still lives in the presets' hints that the button opens."
},
{
"question": "Hooks is the one tab that does not auto-select its first item, so with hooks listed the editor card still says 'Select a hook to edit it.' (engine.studio.hooks.pick, kept). Validations, Actions, Automations and Access auto-select, so their 'Select…' sentences could only ever show when empty and were retired. Should Hooks auto-select like its siblings, retiring hooks.pick too?",
"options": [
"A keep as shipped: the prompt stays only where items exist (PM assumption 4)",
"B a follow-up that auto-selects the first hook and retires hooks.pick"
],
"recommendation": "A for this card. Auto-selecting loads a draft per hook and changes selection behaviour outside this card's scope. B is a one-line follow-up only if the maintainer wants the siblings uniform."
}
],
"out_of_scope_findings": [
"carrier: none · noted, not filed — after a create started from the empty block's button in Hooks, Automations or Access, the button unmounts as the first item lists. The dialog or click then has no trigger to return focus to, so focus falls to body. Validations and Actions hand focus to the header's New, which this PR adds and pins. The other three are an a11y polish item, not a reproduced defect class.",
"carrier: none · noted, not filed — CreateItemDialog-free Hooks: the empty button's label 'New hook' (hooks.newButton) and the new hook's default label 'New hook' (hooks.newLabel) are separate keys with the same en text, deliberately, so a UI label and a data default do not share a key."
],
"deviations": [
"Contract conflict, stated rather than silently resolved: os-dev.md says the dev opens the draft PR and posts the os-dev-report comment itself. The dispatch says the fleet-write relay is refused (403) in this container. Following the dispatch's degraded channel: pr null, pr_body_draft below, zero GitHub writes.",
"File-surface breach (necessary and minimal): 5 existing pins read the retired paths and were updated, 1–3 lines each. ObjectValidationsPanel.presets-11861 and ObjectActionsPanel.presets-11861: openNew() now clicks the empty block's button while no item is listed. StudioDesignSurface.changesCount-11787: clicks the rail '+' by test id, because the block's button shares the name 'New permission set'. StudioDesignSurface.packageLessFlows-11553: asserts the auto-empty block in place of 'Select an automation'. studioAutomationCopy.plainLanguage-11659: auto.pick swapped for auto.emptyMain in its zh row list.",
"Beyond the card, to keep objectui#11861's focus contract: Validations and Actions hand focus to the header's New when the menu was opened from the empty block, because that trigger is gone once the first item lists. Pinned and ablated.",
"PM assumption 1, partly falsified: the three panels share the list-plus-detail layout, but none opens a create DIALOG. Validations and Actions open a presets popover, and Hooks creates at once. The empty button opens or runs exactly those, which is the same create path as ruled. One shared StudioEmptyState serves all five surfaces.",
"Rail sentences: the values of the existing keys auto.none and access.none were rewritten to name the '+' (access.none used to say 'create one below', the wrong direction). No new keys were added for this. access.emptyMain was reworded to a neutral 'No permission sets in this package yet.' because a read-only package shows it with no button. The package-less Automations scope (~org) moves org.none from the rail into the block, with no button.",
"On read-only or package-less surfaces, the rail shows no empty sentence (only loading or load-failed). The block speaks alone, so it does not point at a '+' that is not there."
],
"files_changed": [
".changeset/12514-studio-empty-states.md (new)",
"packages/app-shell/src/views/studio-design/StudioEmptyState.tsx (new)",
"packages/app-shell/src/views/studio-design/ObjectValidationsPanel.tsx",
"packages/app-shell/src/views/studio-design/ObjectActionsPanel.tsx",
"packages/app-shell/src/views/studio-design/ObjectHooksPanel.tsx",
"packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx (AutomationsPillar and AccessPillar empty branches, the two onAdd handlers lifted to named consts, one import)",
"packages/app-shell/src/views/metadata-admin/i18n.ts",
"packages/app-shell/src/views/studio-design/StudioEmptyState.panels-12514.test.tsx (new)",
"packages/app-shell/src/views/studio-design/StudioDesignSurface.emptyStates-12514.test.tsx (new)",
"packages/app-shell/src/views/studio-design/ObjectValidationsPanel.presets-11861.test.tsx",
"packages/app-shell/src/views/studio-design/ObjectActionsPanel.presets-11861.test.tsx",
"packages/app-shell/src/views/studio-design/StudioDesignSurface.changesCount-11787.test.tsx",
"packages/app-shell/src/views/studio-design/StudioDesignSurface.packageLessFlows-11553.test.tsx",
"packages/app-shell/src/views/metadata-admin/studioAutomationCopy.plainLanguage-11659.test.ts"
],
"screenshots": [
"https://github.com/objectstack-ai/objectui/blob/5313a5027f26d5a66472a628bb24145da1fc0d91/docs/design/studio-redesign/findings/12514/12514-validations-light.png?raw=true",
"https://github.com/objectstack-ai/objectui/blob/5313a5027f26d5a66472a628bb24145da1fc0d91/docs/design/studio-redesign/findings/12514/12514-validations-dark.png?raw=true",
"https://github.com/objectstack-ai/objectui/blob/5313a5027f26d5a66472a628bb24145da1fc0d91/docs/design/studio-redesign/findings/12514/12514-hooks-light.png?raw=true",
"https://github.com/objectstack-ai/objectui/blob/5313a5027f26d5a66472a628bb24145da1fc0d91/docs/design/studio-redesign/findings/12514/12514-hooks-dark.png?raw=true",
"https://github.com/objectstack-ai/objectui/blob/5313a5027f26d5a66472a628bb24145da1fc0d91/docs/design/studio-redesign/findings/12514/12514-actions-light.png?raw=true",
"https://github.com/objectstack-ai/objectui/blob/5313a5027f26d5a66472a628bb24145da1fc0d91/docs/design/studio-redesign/findings/12514/12514-actions-dark.png?raw=true",
"https://github.com/objectstack-ai/objectui/blob/5313a5027f26d5a66472a628bb24145da1fc0d91/docs/design/studio-redesign/findings/12514/12514-automations-light.png?raw=true",
"https://github.com/objectstack-ai/objectui/blob/5313a5027f26d5a66472a628bb24145da1fc0d91/docs/design/studio-redesign/findings/12514/12514-automations-dark.png?raw=true",
"https://github.com/objectstack-ai/objectui/blob/5313a5027f26d5a66472a628bb24145da1fc0d91/docs/design/studio-redesign/findings/12514/12514-access-light.png?raw=true",
"https://github.com/objectstack-ai/objectui/blob/5313a5027f26d5a66472a628bb24145da1fc0d91/docs/design/studio-redesign/findings/12514/12514-access-dark.png?raw=true",
"https://github.com/objectstack-ai/objectui/blob/5313a5027f26d5a66472a628bb24145da1fc0d91/docs/design/studio-redesign/findings/12514/12514-readonly-validations-light.png?raw=true"
],
"line_budget": "n/a",
"cleanup": "Stopped what I started: the HotCRM
objectstack dev --fresh -p 4123and the console vite on :5223, by recorded PID. Evidence worktree removed after its push. The feature worktree ../objectui-issue-12514 is left with a clean tree for the PM until the PR is opened.","pr_title_draft": "feat(app-shell): Studio empty states carry the primary action (objectui#12514)",
"pr_body_draft": "(elided by the seat: opened verbatim as the body of PR #12581, read back identical)"
}
Relayed verbatim by seat
domain:ui#4(sessionsession_01SJn1GEkrbbBQkhGC26wEDw) from the cloud session's final message, the degraded write channel. The seat opened the draft PR #12581 frompr_title_draftandpr_body_draft, with assigneeos-sales."pr": nullabove is the session's own value at report time.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Landed · PR #12581 merged as
f23eb01onmain· read 2026-10-15T22:53Zorigin/mainf23eb01studio-design/StudioEmptyState.tsxopenCreateFlow/openCreateSetinStudioDesignSurface.tsxauto-empty/access-emptytestidsrules.pick,rules.explain,actions.pick,auto.pick,access.pickinmetadata-admin/i18n.tsStudioEmptyState.panels-12514.test.tsx,StudioDesignSurface.emptyStates-12514.test.tsx,.changeset/12514-studio-empty-states.mdState change: the card closed as completed through
Fixes #12514, andpm:dispatchedis removed in this act. The cloud sessionsession_018bpaEyeyB1SYJHvDWsn4pGis archived.Generated by Claude Code
feat(app-shell): Studio empty states carry the primary action (object…