Skip to content

A record_header action cannot show per-reader state (an active / pressed predicate) #27946

Closed

Description

@objectstack-fleet

domain:ui#3 · os-bill · session_01Utp6CaJoinvGQSbHeDaWBe

Filing gate: ③ triage-directed. The order on #27434 said: triage rows 1–7, TAKE what is ui-only, CARD the rest. The card thread measured each row on origin/main e41642dd (report 6515174308, out_of_scope_findings).
Reach: a record page header action the reader toggles, such as HotCRM mark_article_helpful / mark_article_not_helpful on a knowledge article.
Reader who acts: domain:spec first (the key), then domain:ui (header buttons draw it).
Duplicate search: search_issues "header action pressed active state per user toggle aria-pressed" → no results.

Observed

A header action button cannot tell the reader they already acted ("you voted Helpful"). packages/spec/src/ui/action.zod.ts has no per-reader state key.

Wanted

An optional per-reader predicate on ActionSchema (active or pressed, a CEL expression over record and user). The spec half is the key, its expression lint and docs. The ui half (a follow-up domain:ui card) is page:header buttons and DeclaredActionsBar drawing it as aria-pressed with a pressed face.

Dedupe words: action pressed state, toggle action active, per-user action state, aria-pressed header action.

Related: #27434 (row 5).


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 30, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#2 · os-tesla · session_01L9qibx5r5nhDMwpRs4SL6D

    Claim: PM loop round 3
    Session: session_01L9qibx5r5nhDMwpRs4SL6D
    Account: os-tesla
    Branch: claude/project-thread-nnpg3i (the card thread's designated push branch, the recorded platform exception to claude/issue-<n>-<slug>)
    Worktree: objectstack-issue-27946
    Domain: domain:spec
    Seat: domain:spec#2
    File surface: packages/spec/src/ui/action.zod.ts (ActionSchema per-reader predicate), the expression lint that judges action predicates (packages/lint/**), tests, regenerated artefacts and reference docs, liveness row (planned until the ui half reads it), changeset. The ui half (page:header / DeclaredActionsBar drawing aria-pressed) is a follow-up domain:ui card the seat files. ⛔ no ui/** and no governed path (stop on breach; explain in the report)
    Container & model: S, mode:thread, model: TIER_DEFAULT, effort: high; Clause-②: yes ⇒ the contract review at CONTRACT_REVIEW_TIER is the seat's own, on the maintainer's 「你有 fable,可以自己契约复审」
    Clause-②: yes (widening)
    Metadata-protocol: added
    Responsibility: n/a — not a defect card
    Thread-read: none
    Premise: holds (re-read at origin/main 9f13ee81; the declaration named on the card still lacks the key)
    Serial constraints cleared: seat 1's #27944 also edits action.zod.ts: ordinary concurrency, merge origin/main before opening the PR
    Ruling-ref: none on the shape; dispatch authority = the maintainer's 「p3 也可以开发」 (2026-10-30T12:41Z) and 「每个座位并发可以5」 (2026-10-30T15:04Z); the card's filing gate ③ (triage-directed)

    Dispatched 2026-10-30T17:09Z to a card thread of the seat's project; the order lives in the project. ⛔ The thread posts no claim and writes no assignee.

  2. objectstack-fleet commented on Oct 30, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#2 · os-tesla · session_01L9qibx5r5nhDMwpRs4SL6D

    Claim amendment · file surface · #27946 · 2026-10-30T17:11Z

    Amends claim 6528706898 (same session, same branch claude/project-thread-nnpg3i). The card thread measured at origin/main 3a2f4c00 that any new ActionSchema key reds Test UI on its own PR: ui/packages/core/src/actions/__tests__/actionKeys.pin.test.ts compares the spec ActionSchema keys with SPEC_ACTION_KEYS in ui/packages/core/src/actions/actionKeys.ts. The seat takes option A: one inventory-only line, the new key in SPEC_ACTION_KEYS with a comment naming the ui follow-up card, ActionDef untouched (the description / execution precedent). Option B (no ui/**, PR red on Test UI until the ui card lands) is not landable.

    File surface adds: ui/packages/core/src/actions/actionKeys.ts, one line in SPEC_ACTION_KEYS only; the ⛔ on every other ui/** path stands. The served-row lint site (SERVED_ROW_SITES, so an attachedOnRead block reads in the predicate) is inside the claimed lint surface. Serial constraints for the added file: seat 1's #27944 adds an action key too and would add its own line there: ordinary concurrency, the later lander merges main.

  3. objectstack-fleet commented on Oct 30, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#2 · os-tesla · session_01L9qibvXVNub8x3B5XhGFrP

    os-dev-report

    Draft PR #28235 at head 64d80fe0.

    {
      "issue": 27946,
      "status": "done",
      "branch": "claude/project-thread-nnpg3i",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/28235",
      "session": "session_01L9qibvXVNub8x3B5XhGFrP",
      "premise_still_valid": true,
      "summary": "ActionSchema declares one optional key, pressed: EvaluatedExpressionInputSchema.optional() after disabledReason: a CEL predicate over the visible scope (record + reader) that draws the button pressed (aria-pressed) while TRUE. Display only; no boolean arm (a constant is refused at parse); active / activeWhen / pressedWhen rename onto it. Lint checkAction judges it in the record scope and 'action pressed' joins SERVED_ROW_SITES, so an attachedOnRead block resolves there. Premise measured: the HotCRM vote is a per-user child row (crm_article_feedback), readable today only as an attachedOnRead block an app afterFind hook attaches per caller (shaping is legal), which the lint half now checks. Liveness row planned with authorWarn; ui inventory gets the one SPEC_ACTION_KEYS line (seat ruling A).",
      "tests": "spec vitest (lock) src/ui + src/kernel + src/data/object + evaluated-slot-population + metadata-form-zod-reconciliation: 235 files, 7705 passed, 1 failed pre-fix (form reconciliation asked for action.pressed) -> after the omit row, that file + action-pressed.test.ts 2/2 files 88/88. lint vitest validate-expressions* + lint-liveness-properties + runtime-gate.object-action-predicate-writes: 19/19 files 864/864. ui actionKeys.pin.test.ts (from ui/): 19/19. dogfood expression-conformance.test.ts: 8/8. spec + lint typecheck at 64d80fe0: lock VERDICT command-exit 0. Ablation via scripts/ablation-replace.mjs (lint tests read src): delete the checkAction pressed line, anchor 1 -> 0 on disk; -t pressed over both lint files -> 2 failed, 456 skipped; restored, blob == HEAD, git diff HEAD empty. lintLivenessPropertiesFromLedgerDir on an action setting pressed -> 1 liveness-planned-property finding.",
      "gates": "dispatch-gates --commands at 64d80fe0: 107 families, stdin from /dev/null, exit codes recorded before any pipe (record: /mnt/project-files/spec-seat-2/r2026-10-30/out/27946-gates-ran-64d80fe0.list). --ran: 107 derived, 107 run, 0 NOT-MEASURED, 0 UNRUN, all exit 0 (check-plugin-teardown-shape pinned control 621a4876 fetched first). spec check:generated 14/14 after each regen (before and after merging origin/main 3739d8d6). CI check-runs on #28235: in_progress at report time.",
      "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
      "line_budget": "+301 / -17 across 20 files vs origin/main",
      "files_changed": [
        ".changeset/27946-action-keys-pressed.md",
        ".changeset/27946-action-pressed.md",
        "content/docs/protocol/objectui/actions.mdx",
        "content/docs/references/data/object.mdx",
        "content/docs/references/kernel/metadata-plugin.mdx",
        "content/docs/references/ui/action.mdx",
        "packages/lint/src/validate-expressions.attached-on-read.test.ts",
        "packages/lint/src/validate-expressions.test.ts",
        "packages/lint/src/validate-expressions.ts",
        "packages/qa/dogfood/test/expression-conformance.ledger.ts",
        "packages/spec/authorable-surface/ui.json",
        "packages/spec/liveness/action.json",
        "packages/spec/liveness/object.json",
        "packages/spec/liveness/state-counts/action.md",
        "packages/spec/src/data/object.zod.ts",
        "packages/spec/src/shared/evaluated-slot-population.test.ts",
        "packages/spec/src/system/metadata-form-zod-reconciliation.test.ts",
        "packages/spec/src/ui/action-pressed.test.ts",
        "packages/spec/src/ui/action.zod.ts",
        "ui/packages/core/src/actions/actionKeys.ts"
      ],
      "deviations": [
        "ui/packages/core/src/actions/actionKeys.ts: one inventory line, under claim amendment 6528737693 (seat ruling A); every other ui/** path untouched.",
        "Ledger/test files outside the named surface but forced by a new evaluated key, all tests or docs-adjacent: packages/qa/dogfood/test/expression-conformance.ledger.ts (new row cel-action-pressed), packages/spec/src/system/metadata-form-zod-reconciliation.test.ts (planned omit row), packages/spec/src/shared/evaluated-slot-population.test.ts (census 31 -> 32), packages/spec/liveness/object.json + ObjectSchema.attachedOnRead TSDoc (served-row sites now name pressed).",
        "Second changeset .changeset/27946-action-keys-pressed.md (@object-ui/core patch, Clause-②: no, Metadata-protocol: none), the 26619 inventory precedent.",
        "Skills-docs: follow-up owed — skills/objectstack-ui/rules/actions.md; the seat files it with the console half."
      ],
      "decision_for_maintainer": "Chosen: pressed (aria-pressed word, says nothing about access), an EvaluatedExpressionInputSchema with no boolean arm, evaluated on the client by the action surface against the fetched row (visible's evaluator and scope), reading a per-reader fact from an attachedOnRead block attached per caller. Not taken: the name active (reads as a status / enabled flag; kept as a rename); ActionConditionInputSchema for symmetry (a literal true is a style, not reader state); a server-computed per-action pressed flag on the wire (a second contract for what the row block carries); a record field (a vote is per user, not per row); a CEL related-record lookup (new evaluator capability on every surface with its own fetch).",
      "mcp_calls": "3 — mcp__github__issue_read (get, get_comments x2) on #27946; 0 writes",
      "api_writes": "2 — relay pr_create (fleet-write run 41106812208, PR #28235); this os-dev-report comment via post-stamped",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: the seat · the ui half, a domain:ui follow-up card: page:header buttons and DeclaredActionsBar evaluate action.pressed and draw aria-pressed with a pressed face; ActionDef derives the key; liveness action.json pressed flips planned -> live with authorWarn/authorHint dropped; expression ledger cel-action-pressed re-stated enforced with its fail face; the actions.mdx callout goes · dedupe words: action pressed state, aria-pressed header action, toggle action active",
        "carrier: the seat · the HotCRM producer, a hotcrm card after the ui half: crm_knowledge_article declares attachedOnRead viewer.vote, an afterFind hook attaches the caller's crm_article_feedback verdict (one batched query per read: the hook runs on grid, search and lookup reads too), mark_article_helpful / mark_article_not_helpful set pressed · dedupe words: knowledge article vote pressed, reader vote attachedOnRead, helpful button state"
      ]
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions