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. added
    enhancementNew feature or request
    pm:queueReady for the PM dispatch loop
    domain:specDomain lane — seat card indexed by label:pm:seat
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Oct 30, 2026
  2. 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.

  3. 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.

  4. 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

Labels

domain:specDomain lane — seat card indexed by label:pm:seatenhancementNew feature or requestpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions