Skip to content

feat(spec): add explicit Action.order for deterministic action ordering (#2670)#2682

Merged
os-zhuang merged 2 commits into
mainfrom
claude/approval-buttons-overflow-3wtgr7
Jul 8, 2026
Merged

feat(spec): add explicit Action.order for deterministic action ordering (#2670)#2682
os-zhuang merged 2 commits into
mainfrom
claude/approval-buttons-overflow-3wtgr7

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Background

Closes the backend half of #2670. When a record is pending approval, the console injects Approve / Reject decision buttons for the approver — but the record-header primary button ends up being an app action (e.g. "申请关闭商机"), and Approve / Reject get buried in the overflow menu.

Two root causes:

  1. Who becomes the primary button is decided purely by array registration order. defineStack({ actions }) merges actions in cross-file registration order with no explicit sort, and system Approve/Reject are appended after all app actions.
  2. The record header renders only the first visible record_header action as the primary button; the rest fall into overflow.

The reporter's preferred fix (and the one implemented here) is the general one: give Action an explicit ordering field so priority is declarative instead of depending on fragile cross-file spread order.

What this PR does (framework / backend)

  • New Action.order field (packages/spec/src/ui/action.zod.ts) — optional number, lower = higher / more prominent, treated as 0 when unset. Matches the repo's existing order convention (object-designer, plugin, settings-manifest).
  • Stable sort in mergeActionsIntoObjects() (packages/spec/src/stack.zod.ts) — every action group (each object's actions and the top-level actions) is stable-sorted by order. This is the single choke point for both defineStack() and composeStacks(), so one change covers the single-stack (cross-file) case from the issue and multi-stack composition.
    • Fully backward compatible: the sort is stable and unset-order is 0, so groups where nobody sets order keep their exact registration order and array reference — a no-op until an author opts in.
  • Docs — Action Protocol page gains the order field and an "Ordering & the Primary Button" section (content/docs/protocol/objectui/actions.mdx).
  • Tests — schema acceptance/rejection of order (action.test.ts) and merge-sort behaviour incl. stability, interleaving, backward-compat, and cross-stack composition (compose-stacks.test.ts).

Because the current objectui bundle picks the primary button by array position, this framework-side sort already makes order take effect for app-declared record-header actions today.

Follow-up (objectui / frontend)

The Approve/Reject buttons are synthesized by the objectui record-header renderer at render time (they don't pass through framework's merge), so making those injected buttons outrank app actions needs a companion change in objectstack-ai/objectui:

  • pick the record-header primary via ordervariant:'primary' → registration order (instead of naive actions[0]);
  • give injected Approve/Reject a low default order + primary variant.

That change is being prepared separately and will bump .objectui-sha.

Verification

  • pnpm --filter @objectstack/spec test6713 passed (incl. new order tests), full suite, no regressions.
  • pnpm --filter @objectstack/spec exec tsc --noEmit — clean.
  • pnpm --filter @objectstack/spec check:api-surface — unchanged ✓ (additive optional field).

Refs #2670

🤖 Generated with Claude Code

https://claude.ai/code/session_018H4wb2JGo68NggKiTh5Dag


Generated by Claude Code

…ng (#2670)

The record-header renders the first visible `record_header` action as the
primary button and pushes the rest into the `⋯` overflow menu. Which action
wins was decided purely by cross-file `defineStack({ actions })` registration
order, so system Approve/Reject decisions were appended after app actions and
buried in the overflow menu — the approver's first-glance decision was hidden.

Add an optional `Action.order` (number, lower = higher / more prominent,
default 0) and stable-sort every action group by it in
`mergeActionsIntoObjects()` — the single choke point for both `defineStack()`
and `composeStacks()`. The sort is stable and treats unset `order` as 0, so
groups where nobody sets `order` keep their exact registration order and array
reference: the change is a no-op until an author opts in. A plugin (e.g.
plugin-approvals) or app author can now give an action a low `order` to make it
stably hold the primary-button slot, instead of hiding other actions to "make
room".

Includes schema tests, merge-sort regression tests, and Action Protocol docs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018H4wb2JGo68NggKiTh5Dag
@vercel

vercel Bot commented Jul 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jul 8, 2026 1:55am

Request Review

@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests protocol:ui tooling labels Jul 8, 2026
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

94 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via packages/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/cli.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via packages/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/profiles.mdx (via @objectstack/spec)
  • content/docs/permissions/roles.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via packages/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via packages/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

The spec property-liveness gate (check:liveness) requires every authorable
property on a governed type to be classified. Classify the new `action/order`
property as `live` (framework stable-sorts each action group by it in
mergeActionsIntoObjects; objectui record_header consumes it for primary-button
selection).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018H4wb2JGo68NggKiTh5Dag
@os-zhuang
os-zhuang marked this pull request as ready for review July 8, 2026 05:51
@os-zhuang
os-zhuang merged commit 6cebf22 into main Jul 8, 2026
17 checks passed
@os-zhuang
os-zhuang deleted the claude/approval-buttons-overflow-3wtgr7 branch July 8, 2026 05:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:ui size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants