Repository navigation
feat(spec): an action can show the reader already pressed it (pressed, a per-reader CEL predicate) - #28235
Conversation
ActionSchema gains an optional per-reader pressed-state predicate (CEL over the visible scope, display only, no boolean arm). The expression lint judges it in the record scope as a served-row site, so an attachedOnRead block resolves there. Liveness row planned; console half is a follow-up. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibvXVNub8x3B5XhGFrP
…ounts for `pressed` Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibvXVNub8x3B5XhGFrP
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibvXVNub8x3B5XhGFrP
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibvXVNub8x3B5XhGFrP
📓 Docs Drift Check3 anchor(s) derived from 2 changed package(s); no hand-written page names any of them. 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): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin b0a815910ee73a080db90286f8b96238afd76e93 && git checkout b0a815910ee73a080db90286f8b96238afd76e93
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 03069caf2c7fac33e4aa4d4a9bf13eaa85876e1e 698c3d65d604e35fb2ca98415ef35fc8dc0cdc1b && git checkout -B drift-repro 03069caf2c7fac33e4aa4d4a9bf13eaa85876e1e && git merge --no-ff 698c3d65d604e35fb2ca98415ef35fc8dc0cdc1b
node scripts/docs-audit/affected-docs.mjs --json 03069caf2c7fac33e4aa4d4a9bf13eaa85876e1e |
|
domain:spec#2 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract-review-claim: |
…console card on the planned row The served action schema gains `pressed`, so the declared total moves 48 -> 49 and the sample names it. The liveness row and the docs callout name the console card that will draw the pressed face. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibvXVNub8x3B5XhGFrP
|
domain:spec#2 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract-review-claim: Re-claim after the head moved from 64d80fe (claim 6530497615): delta is the /meta/types action property count 48 to 49 and the follow-up card #28242 named in the liveness row and the docs callout. Record follows on green. |
|
domain:spec#2 · os-tesla · session_01L9qibvXVNub8x3B5XhGFrP Test UI (1/16) red on Tracked as #28219, fix in #28227 (open). Per the seat, the test stays out of this PR: once #28227 is on Generated by Claude Code |
|
domain:spec#2 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract-review-claim: |
|
domain:spec#2 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract reviewServed-tier: Inputs: card #27946 (body, claim 6528706898, surface amendment 6528737693 ruling A, os-dev-report 6530445276), PR body, the 20-file diff against merge base 3739d8d plus the delta 64d80fe to 21d81b6 (one commit, read under re-claim), the head's check-runs (read 2026-10-30T19:43Z: check-runs on e83f4fa: 65 total, 58 success (Auto Label, Build & E2E UI, Build Core, Build Docs, Build UI Deps, Check Changeset, Check Documentation Links, Check PR Size, 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); 7 skipped path-filtered (18.x upgrade harness (opt-in), 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); 0 failure, 0 in progress). Author Delta since the 21d81b6 review (claims 6530497615 and 6530906593; re-claimed on this head): one commit, e83f4fa, a merge of origin/main 39da1bd, taken to pick up the #28227 fix for the main-red that held this head's Test UI. The PR's own hunks against the new merge base are identical to the reviewed ones (same file names, no content difference beyond hunk offsets), and none of the four main commits the merge brings (#28195, #28213, #28227, #28197) touches a file this PR touches; #28213 moves the connector instance auth union, not ① Derived judgments
② Semver levelminor for ③ Boundary flags
④ Design judgmentThe change holds against the code it cites and the scenario it is accepted against. Against the card: the key is an optional per-reader CEL predicate over Implemented-by: VERDICT: PASS |
|
domain:spec#2 · os-tesla · session_01L9qibx5r5nhDMwpRs4SL6D 维护者速读
席位核对 · ACCEPT ·
|
…-nnpg3i # Conflicts: # packages/spec/src/system/metadata-form-zod-reconciliation.test.ts
…rence after main's percentDisplay and allowStageChange Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibvXVNub8x3B5XhGFrP
|
domain:spec#2 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract-review-claim: |
|
domain:spec#2 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract reviewServed-tier: Inputs: card #27946 (body, claim 6528706898, surface amendment 6528737693 ruling A, os-dev-report 6530445276), PR body, the 20-file diff against merge base 3739d8d plus the delta 64d80fe to 21d81b6 (one commit, read under re-claim), the head's check-runs (read 2026-10-30T21:07Z: check-runs on 698c3d6: 65 total, 58 success (Auto Label, Build & E2E UI, Build Core, Build Docs, Build UI Deps, Check Changeset, Check Documentation Links, Check PR Size, 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); 7 skipped path-filtered (18.x upgrade harness (opt-in), 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); 0 failure, 0 in progress). Author Delta since the 21d81b6 review (claims 6530497615 and 6530906593; re-claimed on this head): one commit, e83f4fa, a merge of origin/main 39da1bd, taken to pick up the #28227 fix for the main-red that held this head's Test UI. The PR's own hunks against the new merge base are identical to the reviewed ones (same file names, no content difference beyond hunk offsets), and none of the four main commits the merge brings (#28195, #28213, #28227, #28197) touches a file this PR touches; #28213 moves the connector instance auth union, not Second delta (e83f4fa to 698c3d6; re-claimed on this head): 0119b3b merges origin/main 49a078e, which now carries #28222 ( ① Derived judgments
② Semver levelminor for ③ Boundary flags
④ Design judgmentThe change holds against the code it cites and the scenario it is accepted against. Against the card: the key is an optional per-reader CEL predicate over Implemented-by: VERDICT: PASS |
domain:spec#2 · os-tesla · session_01L9qibx5r5nhDMwpRs4SL6D
Requested by Tesla · project thread
Fixes #27946
Clause-②: yes (widening)
Metadata-protocol: added
Skills-docs: follow-up #28243 (skills/objectstack-ui/rules/actions.md, to land with the console half #28242)
维护者速读
pressed,是一条 CEL 条件(作用域同visible:当前记录和当前读者),为真时按钮画成"已按下"(aria-pressed),用来告诉读者"你已经投过有用"。planned并对作者告警;画法是后续 ui 卡。packages/spec/src/ui/action.zod.ts中紧跟disabledReason的pressed声明,packages/lint/src/validate-expressions.ts的checkAction与SERVED_ROW_SITES,ui/packages/core/src/actions/actionKeys.ts唯一的一行清单。pnpm --filter @objectstack/spec exec vitest run src/ui/action-pressed.test.ts,预期 12 条全绿。What changes
A record page header action the reader toggles (HotCRM's
mark_article_helpful/mark_article_not_helpful) could not tell the reader they already acted. This is the spec half of the card: the key, its expression lint, its docs and its liveness grade.packages/spec/src/ui/action.zod.ts).ActionSchemadeclarespressed: EvaluatedExpressionInputSchema.optional(), placed afterdisabledReason. A CEL string normalizes to{ dialect: 'cel', source }; the envelope is accepted as written. TRUE = the surface draws the button pressed (aria-pressed="true"); FALSE or absent = a plain button. Display only: it never gates, greys or routes the action.active,activeWhenandpressedWhenare renamed onto it by the strict-object alias table.packages/lint/src/validate-expressions.ts).checkActionjudgespressedin therecordscope exactly as it judgesvisible(a bare field reference is an error, unknown fields are refused), at the build (os build/os validate) and at the object save door.'action pressed'joinsSERVED_ROW_SITES, so a block the object declares underattachedOnReadresolves there and a misspelt leaf is refused.ui/packages/core/src/actions/actionKeys.ts, one line, seat ruling A in claim amendment 6528737693).SPEC_ACTION_KEYSlistspressed, inventory only, thedescription/executionprecedent.actionKeys.pin.test.ts(Test UI) compares that array with the specActionSchemakeys, so any new spec key reds Test UI without this line.ActionDefis untouched and nothing underui/reads the key.packages/spec/liveness/action.jsonrowpressed:planned,evidenceScope: in-repo,authorWarnwith anauthorHint(measured:lintLivenessPropertiesFromLedgerDiremitsliveness-planned-propertyon an action that sets it).liveness/object.jsonattachedOnReadevidence and theObjectSchema.attachedOnReadTSDoc namepressedamong the served-row sites.content/docs/protocol/objectui/actions.mdxgains "The reader's own state:pressed" besidedisabledReason, with a callout that the console does not draw it yet.packages/qa/dogfood/test/expression-conformance.ledger.ts) gainscel-action-pressed(experimental,unevaluated, PARSE ONLY / NO EVALUATOR FOUND); the evaluated-slot census (evaluated-slot-population.test.ts) goes 31 → 32 positions; the metadata form reconciliation recordsaction.pressedas a planned-not-renderedomit; the lint key-read pin (validate-expressions / validate-security-posture 也有同形的 spec 不声明键的??别名读法(#5009 建议 3 的核对结果) #5017 meta-test) listspressed.content/docs/references/ui/action.mdx,data/object.mdx,kernel/metadata-plugin.mdx,authorable-surface/ui.json,liveness/state-counts/action.md..changeset/27946-action-pressed.md(@objectstack/specminor,@objectstack/lintminor, both declaration lines) and.changeset/27946-action-keys-pressed.md(@object-ui/corepatch,Clause-②: no,Metadata-protocol: none, the 26619 inventory precedent).The shape chosen, and the alternatives not taken
pressed, notactive.pressedis the toggle-button word the reader of the key implements (aria-pressed), and it says nothing about access;activereads as a status or an enabled flag besidevisible/disabled.activeis kept as a rename so the card's other spelling lands on the key.visible/disabled). A literaltruedraws every reader's button pressed on every record, which is a static style, not a reader's state; it is refused at parse with the evaluated-slot sentence. Alternative not taken: reuseActionConditionInputSchemafor symmetry.visible. Alternative not taken: a server-computed pressed flag on the action (a new wire field per action per row) — it would need a second contract for what the row-level block already carries.attachedOnReadblock, not a record field and not a related-record lookup. Measured: the HotCRM vote is a per-user child row (crm_article_feedback, counted inhotcrmsrc/service/actions/knowledge_article.actions.ts), so no column of the article can hold it, and CEL in an action predicate has no related-record lookup. What it CAN read today:ObjectSchema.attachedOnReaddeclares a per-caller block on served rows, and an appafterFindhook may shape the rows it returns (packages/objectql/src/verb-hook-result-shape.ts: shaping stays legal), so the article can carryviewer: { vote }for the caller and the predicate readshas(record.viewer) && record.viewer.vote == 'helpful'. The lint half makes that read checkable. Alternative not taken: a CEL related-record function (a new evaluator capability on every surface, with its own fetch).Tests
packages/spec/src/ui/action-pressed.test.ts(new, 12 cases): a CEL string normalizes to the envelope; the envelope withmetais kept; absent stays absent;true/falserefused on the key's path; a blank string, a blanksourceand anast-only envelope refused withEVALUATED_EXPRESSION_SOURCE_REQUIRED;active,activeWhen,pressedWhenrefused with a rename topressed; an object declaringattachedOnRead: { viewer: { vote } }with apressedaction parses.packages/lint/src/validate-expressions.test.ts: a bare-fieldpressedis one error located ataction '…' pressed; therecord.-qualified form passes. The validate-expressions / validate-security-posture 也有同形的 spec 不声明键的??别名读法(#5009 建议 3 的核对结果) #5017 key-read pin listspressed.packages/lint/src/validate-expressions.attached-on-read.test.ts:pressedis a served-row site — a declared leaf passes, a misspelt leaf is refused namingreader.vot, and with the block undeclared the read is the unknown field it is at run time.packages/spec/src/shared/evaluated-slot-population.test.ts:actionObject.pressedjoins the census (32 positions), refusing the three blank shapes with the one sentence.Acceptance notes
domain:uicard, carried by the seat:page:headerbuttons andDeclaredActionsBarevaluatepressedand drawaria-pressedwith a pressed face;ActionDefderives the key; the liveness row flipsplanned→livewithauthorWarn/authorHintdropped in the same edit; the expression ledger row is re-statedenforcedwith its fail face (an unevaluable predicate draws a plain button); the docs callout goes.attachedOnRead: { viewer: { vote: 'text' } }oncrm_knowledge_article, attach it in anafterFindhook from the caller'scrm_article_feedbackrow, and setpressedon both vote actions. Note for that card: the hook runs on every read path of the article (grid, search, lookups), so it should batch one feedback query per read.Verification
tests, typecheck, ablation
packages/spec(under the verify lock, before the merge oforigin/main):vitest run src/ui/ src/shared/evaluated-slot-population.test.ts src/system/metadata-form-zod-reconciliation.test.ts src/kernel src/data/object→ 235 files, 7705 passed, 1 failed (the form reconciliation asking foraction.pressed); after the planned-not-renderedomitrow, that file andaction-pressed.test.ts→ 2/2 files, 88/88 tests.packages/lint:vitest run src/validate-expressions src/lint-liveness-properties src/runtime-gate.object-action-predicate-writes.test.ts→ 19/19 files, 864/864 tests (first run red only on the validate-expressions / validate-security-posture 也有同形的 spec 不声明键的??别名读法(#5009 建议 3 的核对结果) #5017 key-read pin, now listingpressed).ui:pnpm exec vitest run packages/core/src/actions/__tests__/actionKeys.pin.test.tsfromui/→ 1/1 file, 19/19 tests, against the spec dist rebuilt withpressed.packages/qa/dogfood:test/expression-conformance.test.ts→ 1/1 file, 8/8 tests.pnpm --filter @objectstack/spec --filter @objectstack/lint typecheckat64d80fe0: lock VERDICT command-exit 0.lintLivenessPropertiesFromLedgerDir: an action settingpressedgets oneliveness-planned-propertyfinding carrying the row'sauthorHint.scripts/ablation-replace.mjs, lint tests readsrc): deleting thecheckActionline that judgespressed, anchor 1 → 0 on disk;-t pressedover both lint files → 2 failed, 456 skipped; restored, blob == HEAD andgit diff HEADempty.generated artefacts and the merge
./packages/**+ the dogfood closure (76/76 tasks),check:generatednamedcontent/docs/references/**andliveness/state-counts/stale;--fixregenerated both, re-check 14/14.origin/main3739d8d6(3 commits, no overlap with this diff, clean); the merge driver deferredauthorable-surface/ui.jsonandreferences/data/object.mdx; after a second full buildcheck:generated --fixrewrote them from the merged tree (main'sKanbanConfig.lanes/ReportChart.seriesByrows), commit64d80fe0, marker cleared.derived gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackat64d80fe0: 107 families, each run with stdin from/dev/null, exit code recorded before any pipe (record:27946-gates-ran-64d80fe0.listin the seat's out folder).check-plugin-teardown-shape --self-test's pinned control621a4876was fetched into the shallow clone first.--ranreconciliation: 107 derived, 107 run, 0 NOT-MEASURED, 0 UNRUN, all exit 0.Generated by Claude Code