Skip to content

feat(console): a header action shows the reader already pressed it (pressed drawn as aria-pressed) - #28301

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-schd4q
Oct 31, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-schd4q

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Tesla · project thread

Fixes #28242
Refs #27946

Clause-②: yes (widening: @object-ui/core ActionDef gains the optional member pressed, typed by derivation from the spec's ActionInput['pressed']; no export, key or route is added)
Metadata-protocol: none

维护者速读

  • 改了什么:记录页头部按钮和「声明的动作栏」现在会读动作的 pressed 条件。条件成立时按钮显示为「已按下」(高亮底色,读屏念出「已按下」),不成立时是普通按钮。例如知识文章的「有帮助 / 没帮助」投票,读者已经投过哪一票,那个按钮就是按下状态。
  • 为什么改:pressed 已在 feat(spec): an action can show the reader already pressed it (pressed, a per-reader CEL predicate) #28235 写进 spec,但控制台一直不画,读者看不出自己是否已经投过票。
  • 风险与回滚:只改显示层,按下的按钮照样可点、照样执行动作;条件算不出来时画成未按下并在控制台提示一次。没有声明 pressed 的动作完全不变。回滚即 revert 本 PR。
  • 先看:ui/packages/components/src/renderers/layout/containers.tsx(pressedFor 与 renderButton 里的 aria-pressed);ui/packages/app-shell/src/views/DeclaredActionsBar.tsx(useDeclaredActionPressed);packages/qa/dogfood/test/expression-conformance.ledger.ts 的 cel-action-pressed 行。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/components/src/__tests__/page-header-pressed-28242.test.tsx packages/app-shell/src/views/__tests__/DeclaredActionsBar.pressed-28242.test.tsx,预期 2 个文件、12 个测试全绿。
  • 需要你做的:无,席位落地。

What changes

ActionDef derives the key (ui/packages/core/src/actions/ActionRunner.ts): pressed?: SpecActionInput['pressed'], so it has the spec's arms (a CEL string or the { dialect, source } envelope, no boolean). actionKeys.ts lists it in ACTION_DEF_KEYS, and the SPEC_ACTION_KEYS comment no longer says it is inventory-only.

The record header (page:header, PageHeaderActions in containers.tsx). pressedFor evaluates a declared pressed through the header's one predicate entry, evalHeaderPredicate (the entry and bindings visible / disabled use), with fallback: false. An inline button whose action declares the predicate is a toggle button: aria-pressed="true" while it holds, aria-pressed="false" while it does not. An action that declares none carries no aria-pressed.

The declared actions bar (DeclaredActionsBar.tsx). useDeclaredActionPressed evaluates it with useCondition and throwOnError, on the same record binding as the bar's visible gate, and draws the same attribute.

The pressed face on both: Shadcn's toggle-on colours keyed off the attribute (aria-pressed:bg-accent aria-pressed:text-accent-foreground hover:aria-pressed:bg-accent, the spelling PanelToggles already uses), so the face and the attribute cannot disagree.

Display only. Nothing gates, greys or routes on the verdict: a pressed action stays enabled and still dispatches. A predicate that cannot be evaluated answers false (the unpressed button, never a claim that the reader acted) and is reported once. A boolean pressed is refused by the spec at parse, so neither surface gives it a reading.

Re-stated in the same PR (the card's cross-domain exception):

  • packages/spec/liveness/action.json pressed: planned → live, citing the read points; authorWarn / authorHint dropped. The generated count shard packages/spec/liveness/state-counts/action.md moves with it (gen:liveness-counts: live 48 → 49, planned 1 → 0).
  • packages/qa/dogfood/test/expression-conformance.ledger.ts cel-action-pressed: experimental / unevaluated → enforced / fail-soft-log, naming both evaluators.
  • content/docs/protocol/objectui/actions.mdx: the "not drawn yet" callout is gone; one bullet says which surfaces draw it and that a menu item does not.

Tests

  • ui/packages/components/src/__tests__/page-header-pressed-28242.test.tsx (6) and ui/packages/app-shell/src/views/__tests__/DeclaredActionsBar.pressed-28242.test.tsx (6), the same six cases per surface: the vote that holds is aria-pressed="true" with the face and its sibling "false"; the envelope arm; a row without the served block draws both unpressed; a pressed action is enabled and dispatches; a faulting predicate draws "false" and warns; an action with no predicate has no aria-pressed and no face.

Acceptance notes

  • A menu item draws no pressed state: an action folded into the header's ⋯ overflow, and a lever in the declared actions bar's override menu. aria-pressed is not a menu item attribute; a checkable item (menuitemcheckbox) would be a different control. Recorded in the liveness note and the doc bullet.
  • aria-pressed="false" (not an absent attribute) when a declared predicate is false: a declared pressed makes the button a toggle, which assistive technology announces as "not pressed". The look is the plain button, as the spec describes.
  • The bar's fault warning comes from useCondition's shared text, which says "was hidden/disabled"; the label it carries ends in (pressed), and the button is drawn unpressed. The text is outside this card's surface.
  • The four action:* renderers and record:quick_actions do not read pressed; the card names the header and the bar only.

Verification

Tree: objectstack-ai/objectstack, branch claude/project-thread-schd4q, head 7650f47c (the last commit only regenerates the liveness count shard; every gate it can move was re-run on it).

New suites
pnpm --dir ui exec vitest run packages/components/src/__tests__/page-header-pressed-28242.test.tsx
 Test Files  1 passed (1)
      Tests  6 passed (6)
pnpm --dir ui exec vitest run packages/app-shell/src/views/__tests__/DeclaredActionsBar.pressed-28242.test.tsx
 Test Files  1 passed (1)
      Tests  6 passed (6)
Ablation: the read removed, the new pins go red

node scripts/ablation-replace.mjs replaced const pressed = pressedFor(action); (containers.tsx) and const pressed = useDeclaredActionPressed(action, predicateRecord); (DeclaredActionsBar.tsx) with const pressed = undefined, one leg each (anchor 1 → 0, blob changed), same commands:

page header:  Tests  4 failed | 2 passed (6)
actions bar:  Tests  4 failed | 2 passed (6)

The four reds per surface are the pressed / envelope / no-block / fault cases; the dispatch and no-predicate controls stay green. Each leg restored by the tool: blob == HEAD and git diff HEAD empty.

Neighbouring suites and type-check
pnpm --dir ui exec vitest run packages/core/src/actions/ packages/components/src/__tests__/page-header packages/app-shell/src/views/__tests__/DeclaredActionsBar
 Test Files  81 passed (81)
      Tests  1038 passed (1038)
pnpm --filter @object-ui/core run type-check        :: exit 0
pnpm --filter @object-ui/components run type-check  :: exit 0
pnpm --filter @object-ui/app-shell run type-check   :: exit 0
pnpm --filter @objectstack/dogfood exec vitest run test/expression-conformance.test.ts
 Test Files  1 passed (1)
      Tests  8 passed (8)
pnpm --dir ui run check:action-forward-parity :: exit 0
pnpm --dir ui run check:new-line-citations     :: exit 0 (0 new citations)
Derived gates

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 94 families; all 94 were run with stdin closed and recorded with their exit codes, and --ran reconciles: "94 derived famil(ies) accounted for — 94 run, 0 NOT-MEASURED". Two were red on the first pass and are green on the head: check:liveness (the count shard, regenerated) and check-plugin-teardown-shape.mjs --self-test (its pinned fixture commit was absent from the shallow clone; fetched). node scripts/check-sdui-manifest.mjs :: exit 0. Record: /mnt/project-files/ui-seat-1/r2026-10-30/out/28242-gates-ran-7650f47c.list.


Generated by Claude Code

@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tooling labels Oct 31, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️ 2 changed file(s) yielded no anchor (packages/spec/liveness/action.json, packages/spec/liveness/state-counts/action.md), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/spec/liveness/action.json, packages/spec/liveness/state-counts/action.md) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 168 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 53749673ba7a0005d09e8a79544da10d5faa10bc → packageMentionDocs.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz

Contract-review-claim: session_01L9qibw58ehQ2gRHP2h2Puz · head 7650f47c002350d62df3cc3b8a39ccb3fafe3ab5 · 2026-10-31T04:14Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz

Contract review

Served-tier: TIER_DEFAULT
Head-sha: 7650f47c002350d62df3cc3b8a39ccb3fafe3ab5
Local-runs: none

Inputs: card #28242 (body and all five comments: triage 6531296993 with its three-item cross-domain exception, the unblock 6534356916, the seat's claim 6537991945, the dev report 6538647503, the claim amendment 6538656802 which flips Clause-② to widening and accepts the generated count shard), PR #28301 (body, the one docs-drift comment, the file list of 11 files, the diff of 489 lines against main read whole, the four commits from merge-base 6285203), the 70 check-runs on this head (read 2026-10-31T04:15Z: all 70 runs completed, none failed, none cancelled, none in progress; success: Build & E2E UI, Build Core, Build Docs, Build UI Deps, Check Changeset, Check Documentation Links, Dogfood Regression Gate, Flag docs affected by code changes, Governed Surface Queue Guard, Lint & Repo Gates, Lint UI, Lint · CI, release & engine gates, Lint · ESLint, Lint · PM dispatch-gates, Lint · docs & code guards, Lint · repo & tooling gates, Lint · scoped · query-options erasure, Lint · scoped · slot lookup & corpus, Lint · scoped · tooling self-tests, Live E2E UI, PR-body guards (claim · duplicate · Part-of · single-writer path), Plan Test Core shards, Spec property liveness, Test Core, Test Core (all 8), Test UI, Test UI (all 16), Type Check UI, Type Check · consumer gates, Type Check · debt ledger, Type Check · source gates, Type Check · workspace, TypeScript Type Check, filter; skipped: 18.x upgrade harness (opt-in), Auto Label, Check PR Size, Dogfood Regression Gate (matrix shard/3), Dogfood Verify CLI, Packed-tarball smoke (opt-in), Schedule gate · skip when a green push run covers the commit, Temporal Conformance (live PG + MySQL), UI Published Artifacts), and the tree at this head for evalHeaderPredicate (containers.tsx:2008, evalRowPredicate with the caller's fallback and warnOnError: true) and useCondition (useExpression.ts:201, throwOnError answers false on a fault and warns once per label and predicate). The head is mergeable and clean against today's main; none of the files main moved since the merge-base is in this diff.

① Derived judgments

  • The card's effect, as ordered - right. Both named surfaces evaluate action.pressed per record for this reader: the record header's inline buttons through pressedFor (a declared string or envelope source, else undefined; evalHeaderPredicate with fallback: false) and the declared actions bar through useDeclaredActionPressed (useCondition with throwOnError, the same record binding as visible). TRUE draws aria-pressed="true" and Shadcn's toggle-on classes keyed off the attribute; FALSE draws aria-pressed="false"; no predicate draws no aria-pressed and no face (both CONTROL cases). A fault answers false and is reported once, so the button never claims the reader acted. Display only: a pressed action stays enabled and dispatches (pinned on both surfaces). A menu item draws nothing, which the docs and the liveness note now say.
  • ActionDef derives the key - right. pressed?: SpecActionInput['pressed'] beside disabled, ACTION_DEF_KEYS lists it, and the inventory-only comment in SPEC_ACTION_KEYS is replaced; the runner reads nothing new.
  • The three re-statements the triage exception allows, and one mechanical consequence - right. liveness/action.json pressed goes planned to live with authorWarn / authorHint dropped and the five read points cited by path and symbol; the cel-action-pressed ledger row goes experimental to enforced with fail-soft-log, naming both evaluators and the unpressed fault face; the "not yet drawn" callout leaves actions.mdx and one bullet states the drawn behaviour. state-counts/action.md (live 48 to 49, planned 1 to 0, classified 50) is the generated shard check:liveness requires after the flip; the seat accepted it in amendment 6538656802.
  • Accept-set: nothing an author writes moves - right. No spec schema changes; Metadata-protocol: none is right.
  • Check-runs as gate verdicts - read as above; every run on this head completed and none is red, cancelled or in progress at the read.

② Semver level

③ Boundary flags

Verdict reasoning. The diff draws the key where the card said, with the fault direction the ledger now states, display-only as the contract requires, and each face pinned on both surfaces with an ablation shown red; the three cross-domain re-statements are exactly the triage exception, plus the generated shard the seat accepted; semver is patch with the widening declared. The one thing to watch is the ordering with #28188 on the shared ledger line.

Implemented-by: claude/project-thread-schd4q
Reviewed-by: session_01L9qibw58ehQ2gRHP2h2Puz

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · os-tesla · session_01L9qibzC6RxMfB59EsgAoNN

席位核对

ACCEPT · seat domain:ui#1 · head 7650f47c · 2026-10-31T04:18Z

席位意见:动作声明了 pressed 后,记录页头的内联按钮和声明式动作栏现在会按记录求值,为真时画出 aria-pressed="true" 和按下态样式,为假时画 aria-pressed="false";没声明的动作不变。求值出错按「未按下」处理并只告警一次。按下态只是显示,按钮仍可点击、照常执行。菜单项不画按下态,文档和 liveness 注释已写明。已按扩面申报 Clause-②: yes (widening)。

  1. Shape: Fixes #28242, Refs #27946, draft, base main, 11 files, +316/−22. ActionDef.pressed is derived from the spec's ActionInput. PageHeaderActions gains pressedFor, and DeclaredActionsBar gains useDeclaredActionPressed. The liveness action.json row, the cel-action-pressed ledger row and the actions.mdx callout are the triage's three-item exception. The generated state-counts/action.md shard was accepted in claim amendment 6538656802.
  2. Seat read: the record binding is the same one visible uses. Both surfaces pin TRUE, FALSE, undeclared and fault, and an ablation was shown red. A pressed action still dispatches on both surfaces.
  3. Contract review: PASS at this head, record 6538888897 (TIER_DEFAULT), by the contract-review lane, not the drafter. No should-fix. Its boundary flag is serial overlap with open feat(spec): an action's param dialog can name its own submit verb — submitText #28188 (submitText) on the state-counts/action.md row. This PR lands first, so feat(spec): an action's param dialog can name its own submit verb — submitText #28188 must merge main, re-run gen:liveness-counts and take a delta review. The seat notes this on the spec lane's PR when this one merges.
  4. Gates / check-runs: on 7650f47c: 59 success, 13 skipped, 0 failure. One Check Changeset re-run is in progress after a label event, and auto-merge waits for it.
  5. Landing: check-governed-merges --pr 28301 reports NOT governed, 338 changed lines ≤ 3000. The seat readies the PR and arms auto-merge, on the maintainer's 「按 pm-dispatch 审过的pr你可以自主落地」 (seat thread, 2026-10-30T14:19Z).

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 size/m tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Header action shows it is already pressed: page:header and DeclaredActionsBar read action.pressed

1 participant