Repository navigation
feat(console): a header action shows the reader already pressed it (pressed drawn as aria-pressed) - #28301
Conversation
…ared actions bar Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibzrVVJotCXhG4v7ddq
…ced, and the not-yet-drawn callout is gone Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibzrVVJotCXhG4v7ddq
…ed flip Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibzrVVJotCXhG4v7ddq
📓 Docs Drift Check
What this run could not see
Coarse fallback — 168 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
|
domain:ui#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract-review-claim: |
|
domain:ui#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract reviewServed-tier: 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 ① Derived judgments
② 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: VERDICT: PASS |
|
domain:ui#1 · os-tesla · session_01L9qibzC6RxMfB59EsgAoNN 席位核对ACCEPT · seat 席位意见:动作声明了
|
Requested by Tesla · project thread
Fixes #28242
Refs #27946
Clause-②: yes (widening:
@object-ui/coreActionDefgains the optional memberpressed, typed by derivation from the spec'sActionInput['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
ActionDefderives 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.tslists it inACTION_DEF_KEYS, and theSPEC_ACTION_KEYScomment no longer says it is inventory-only.The record header (
page:header,PageHeaderActionsincontainers.tsx).pressedForevaluates a declaredpressedthrough the header's one predicate entry,evalHeaderPredicate(the entry and bindingsvisible/disableduse), withfallback: 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 noaria-pressed.The declared actions bar (
DeclaredActionsBar.tsx).useDeclaredActionPressedevaluates it withuseConditionandthrowOnError, on the same record binding as the bar'svisiblegate, 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 spellingPanelTogglesalready 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 booleanpressedis 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.jsonpressed:planned→live, citing the read points;authorWarn/authorHintdropped. The generated count shardpackages/spec/liveness/state-counts/action.mdmoves with it (gen:liveness-counts: live 48 → 49, planned 1 → 0).packages/qa/dogfood/test/expression-conformance.ledger.tscel-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) andui/packages/app-shell/src/views/__tests__/DeclaredActionsBar.pressed-28242.test.tsx(6), the same six cases per surface: the vote that holds isaria-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 noaria-pressedand no face.Acceptance notes
⋯overflow, and a lever in the declared actions bar's override menu.aria-pressedis 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 declaredpressedmakes the button a toggle, which assistive technology announces as "not pressed". The look is the plain button, as the spec describes.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.action:*renderers andrecord:quick_actionsdo not readpressed; the card names the header and the bar only.Verification
Tree: objectstack-ai/objectstack, branch
claude/project-thread-schd4q, head7650f47c(the last commit only regenerates the liveness count shard; every gate it can move was re-run on it).New suites
Ablation: the read removed, the new pins go red
node scripts/ablation-replace.mjsreplacedconst pressed = pressedFor(action);(containers.tsx) andconst pressed = useDeclaredActionPressed(action, predicateRecord);(DeclaredActionsBar.tsx) withconst pressed = undefined, one leg each (anchor 1 → 0, blob changed), same commands: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 HEADempty.Neighbouring suites and type-check
Derived gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 94 families; all 94 were run with stdin closed and recorded with their exit codes, and--ranreconciles: "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) andcheck-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