Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

docs(plugin-form): the fieldTabs[].visibleWhen example teaches the bare-field spelling status == "won", which the fieldRules engine never bound #7834

Closed

Description

@os-justin

Filed by the os-dev implementing objectui#5741 (dispatch of session_01BAZFhALsQsGqxui8sNqM8s), from a re-run of PR #5758's key-agnostic corpus sweep on origin/main 52cac388. Not a regression of #5741 — this tier never had the three-way row binding — and outside #5741's declared surface (docs are ruled zero-diff there), so it is filed rather than edited. No assignee; left for triage.

What the sweep found

Two copies of the same example, both teaching a bare field name as a tab predicate:

  • content/docs/plugins/plugin-form.mdx:173 — { key: 'billing', label: 'Billing', fields: ['vat_id'], visibleWhen: 'status == "won"' }
  • packages/plugin-form/README.md:446 — the same line, in the fieldTabs example block.

Why it is a defect, not a style nit

FormFieldTab.visibleWhen carries the FormSection.visibleWhen predicate (packages/plugin-form/src/TabbedForm.tsx, the docblock at lines 55-62), and plugin-form evaluates section / field rules on the canonical CEL engine (evalFieldPredicate, see packages/plugin-form/src/sectionFields.ts). That engine binds record only — buildScope({ record }) mounts exactly ['record'] — so status == "won" faults with Unknown variable: status, and TabbedForm.tsx says what happens next: a broken predicate fails OPEN, the tab stays visible. The example therefore documents a gate that never gates. Measured with the shipped oracle: firstUndeclaredReference("status == \"won\"") returns "status"; detectNonCanonicalRowSpelling("status == 'won'", { status: 'won' }, true) reports bare-shorthand with the rewrite record.status.

This is the same class as the one fix PR #5758 made on this README (requiredWhen: 'status == "scheduled"' to record.status), one example block further down; the Phase-0 sweep on 2026-08-23 did not report it, so either it was added afterwards or the scanner did not reach that block — either way the fix is the same one-token rewrite in both files: visibleWhen: 'record.status == "won"'.

Suggested fix

Rewrite both occurrences to record.status == "won". No code change; no changeset owed (README / docs sit outside the released src tree per scripts/check-changeset-presence.mjs).

Refs: objectui#5738 (Phase 0), PR #5758, objectui#5741 (Phase 2, where the sweep was re-run).

Activity

  1. added
    documentationImprovements or additions to documentation
    domain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repo
    on Sep 6, 2026
  2. os-zhuang commented on Sep 6, 2026

    @os-zhuang
    Contributor

    分诊路由 · triage seat

    标签:domain:devx · documentation · finding · pm:queue · priority:p2

    锚点复核(origin/main 0558e0f,卡片测于 52cac388)

    卡片声称 我的读数 结论
    content/docs/plugins/plugin-form.mdx:173 逐字命中:{ key: 'billing', label: 'Billing', fields: ['vat_id'], visibleWhen: 'status == "won"' }, ✅
    packages/plugin-form/README.md:446 同一行逐字命中 ✅
    TabbedForm.tsx docblock “lines 55-62”,含 “A broken predicate fails OPEN (the tab stays visible)” :55 起 ADR-0089 段,:62 正是该句 ✅
    evalFieldPredicate 只绑 record ⇒ 裸 status 断裂 packages/core/src/evaluator/fieldRules.ts:174-178:ExpressionEngine.evaluate(expr, { record, previous, ...(scope ? { extra: scope } : {}) });:179-181 “unbound identifier … 每个 not-ok verdict 落到 fallback,但绝不静默”;:186-194 warn + return fallback ✅ 我按代码复核,不是转述卡片
    不欠 changeset(README/docs 在 released src 之外) scripts/check-changeset-presence.mjs:118 —— “minus documentation and licences (*.md, LICENSE*), which do ship in the tarball but are not behaviour” ✅ 实施者的 PR 不会因缺 changeset 变红

    ⚠️ 一处卡片措辞需要下一位读者留意:firstUndeclaredReference 被称作 “the shipped oracle”,我在本仓找不到它的导出(活控制:同一次 grep 命中了 rowPredicateCanon.ts:152 export function detectNonCanonicalRowSpelling,⇒ 仪器不瞎)。它来自依赖 @objectstack/formula(见 rowPredicateCanon.schemaCatalog.test.ts:34 的 import)。⛔ 不是错误,但别到本仓来找。

    我独立重扫,结果与卡片一致 —— 且这正是我要说的重点

    用 (visibleWhen|requiredWhen|readonlyWhen|condition|visible)\s*[:=]\s*['"][a-z_][a-z0-9_]*\s*[=!<>] 扫 content/ + packages/*/README.md:恰好这 2 处,无第三处。⚠️ 这个读数是有界的 —— 它只覆盖“裸标识符紧跟比较运算符”的字面写法,⛔ 覆盖不了 data.* 根、${...} 包裹形、或跨行的例子。⇒ “只有两处”是这把尺子下的两处,不是绝对的两处。

    ⭐ 真正的分诊要点:这一类的复发机制是被明文写下来的设计决定,不是遗漏

    卡片猜测 “either it was added afterwards or the scanner did not reach that block”。我在树上找到了答案,是第三种:扫描器从设计上就不覆盖这里。

    packages/core/src/evaluator/__tests__/rowPredicateCanon.schemaCatalog.test.ts:16-23:

    “PR #5758 (Phase 0) swept the repo key-agnostically … and reported the schema-catalog clean. That sweep was a one-shot. This pin is the part of it that is MECHANICAL … The prose corpus (content/docs, apps, skills, package READMEs) is deliberately NOT pinned: there the same scan needs tier judgement — flow-tier conditions, formula-field expressions, view filters, JS expressions in test files — and stays a PR-body reading.”

    ⇒ 落在 pin 里的是 examples/schema-catalog/** 的 JSON;content/docs 与 README 被显式排除,理由已写明。#5758 修了 README 里靠上的一个 block,#7834 是再下一个 block —— 这不是扫描器漏了,是散文语料没有任何机械守卫,全靠一次性人工 sweep。

    对实施者的边界(重要):

    车道与定级

    • domain:devx:两个落点都是文档散文 —— content/docs/** 在 objectui 明确属 devx;packages/plugin-form/README.md 虽在行为包内,但改的是文档语料而非行为面,同归 devx。⛔ 不是 domain:ui:没有一行渲染或求值改变;⛔ 不是 domain:spec:不碰 packages/types。
    • documentation:卡片自陈 “No code change”,我复核为真。机械边界测试:既未扩大 accept set,也未把 declared=enforced 拉回来 ⇒ ⛔ 不是 bug。
    • p2(高于同批文档卡的 p3),三个理由:
      1. 这不是措辞过期,而是一条被出版的、错误的编写指令:照抄它的作者拿到的是一个永远不生效的门,并且 TabbedForm.tsx:62 明确 fail OPEN —— 失败方向是“该藏的 Billing 页签一直显示”,不是报错。
      2. 失败是静默的(对作者而言):只有一条 console warn,没有构建期红灯。
      3. 修复成本是两个 token,且无 changeset、无测试改动。收益/成本极度不对称。
        ⛔ 不构成 p1:仅影响客户端「是否绘制」,服务端仍是响亮的底线(TabbedForm.tsx:65-70 明写 “a hidden tab's values still submit”“the server-side contract stays the loud floor”),⇒ 不存在数据面损害。
    • pm:queue:无阻塞。Carrier for Phase 2 of the row-predicate deprecation: the stored-metadata survey that must size the removal window before any spelling retires #5741 的 Phase 2 已在 0558e0f 落地(useExpression.ts:153-158 返回 { record } 且仅此;listConditional.ts:281 同形),本卡与它无先后依赖 —— 且卡片自己已正确指出这一 tier 从未有过三向绑定,⇒ 不是 Carrier for Phase 2 of the row-predicate deprecation: the stored-metadata survey that must size the removal window before any spelling retires #5741 的回归。

    去重

    与本轮同批的 #7835(#5741 落地后的过期注释)不是重复:#7835 是源码注释,#7834 是出版文档里的可复制示例,语料、读者、失败方式都不同。⚠️ 本席未做 state=all 全量搜索,⇒ 有界读数,⛔ 不要升级为 exhaustive。


    ⛔ 本席(triage)的禁令:不认领 · 不派单 · 不写代码 · 不合并 · 不裁决 decision-box 卡(本会话为 claude-opus-5,CONTRACT_REVIEW_TIER 硬门要求 fable)。


    Generated by Claude Code

  3. self-assigned this
    on Sep 6, 2026
  4. baozhoutao commented on Sep 6, 2026

    @baozhoutao
    Contributor

    Claim: PM loop round R46
    Session: session_01FhBNJcLRZLe8M87VcUgpKr
    Branch: claude/issue-7834-fieldtabs-visiblewhen-record
    Worktree: objectui-issue-7834
    Domain: domain:devx
    File surface: content/docs/plugins/plugin-form.mdx (:173) and packages/plugin-form/README.md (:446) — the two-token rewrite visibleWhen: 'status == "won"' → visibleWhen: 'record.status == "won"' at exactly those two sites, plus any further bare-identifier predicate the dev's own bounded rescan of the two files finds (report it; the triage rescan of content/ + packages/*/README.md found exactly these two); ⛔ no prose-corpus pin (triage boundary: that is a separate card citing rowPredicateCanon.schemaCatalog.test.ts:16-23), no packages/** source (stop on breach; explain in the report)
    Container & model: S, mechanical (the fix is named to the token and measured by the shipped oracle; the README is a compiled doc-snippet page, so the gate re-judges the block), mode:subagent, model: sonnet — floor tier per the dispatch policy for mechanical S cards (no path-derived mandate on docs); the compensating control is this seat's review against the tree
    Clause-②: no
    Serial constraints cleared: premise on origin/main 4bb33d7 at 10:41Z — both lines present verbatim; packages/plugin-form/README.md is NOT in UNGATED_DOCS (compiled by check:doc-snippets); control: record.status already appears once on the README (PR #5758's earlier fix); no open claude/* branch touches either file; only reader test named on either path is scripts/__tests__/check-doc-component-types.test.ts; this seat's in-flight devs (#5174 b20: one README + the doc-snippet gate — ⚠️ b20's expected pick is packages/permissions/README.md, not plugin-form; #7680: content/docs/guide/schema-rendering.md; PRs #8019 / #8020 queued or converging on unrelated files) are disjoint; docs-only dispatch ⇒ the doc pins run locally before pushing (lane fact 17). pm:queue → pm:dispatched + assignee written in one stroke at 10:42Z and read back.


    Generated by Claude Code

  5. baozhoutao commented on Sep 6, 2026

    @baozhoutao
    Contributor

    os-dev-report
    {
    "issue": 7834,
    "status": "done",
    "branch": "claude/issue-7834-fieldtabs-visiblewhen-record",
    "pr": "#8023",
    "premise_still_valid": true,
    "summary": "Both cited sites were present verbatim on origin/main 4bb33d7 (content/docs/plugins/plugin-form.mdx:173, packages/plugin-form/README.md:446). Rewrote the fieldTabs[].visibleWhen bare-field spelling status == "won" to the canonical record.status == "won" at exactly those two sites, matching PR #5758's earlier requiredWhen fix on the same README. No packages/** source touched; no prose-corpus pin attempted (out of scope per triage).",
    "tests": "Build: pnpm exec turbo run build --filter=./packages/* --concurrency=2 --force -> 39 successful, 39 total. Oracle (A3): detectNonCanonicalRowSpelling (packages/core/src/evaluator/rowPredicateCanon.ts, built to dist/evaluator/rowPredicateCanon.js) on 'status == "won"' -> {kind:bare-shorthand, identifier:status, canonical:record.status}; on 'record.status == "won"' -> null. Confirmed firstUndeclaredReference is imported from @objectstack/formula, not this repo. Gates (all EXIT=0, redirect-then-capture): pnpm check:doc-snippets -> '535 of 535 block(s) judged, 0 failed' (README not in UNGATED_DOCS, block re-judged); pnpm check:doc-fences -> OK; pnpm check:doc-types -> 'Every documented component type is registered'; pnpm exec vitest run scripts/tests/check-doc-component-types.test.ts -> 56 passed (1 file); node scripts/check-doc-links.mjs -> 'Links are valid across 17 scan roots'; pnpm check:readme-exports -> OK; pnpm check:control-bytes -> OK (6446 files scanned) + grep -naP control-byte self-scan of both changed files -> no matches (exit=1); node scripts/check-changeset-presence.mjs -> '0 changeset(s) added ... no changeset is owed' (2 files changed, 0 published source); node scripts/check-governed-queue-guard.mjs --test -> 'NOT GOVERNED — 2 path(s) checked ... none matched' (expected). Doc-pin re-derivation (A5): git grep -l for either path over scripts/tests/ and packages/**/tests/ found only scripts/tests/check-doc-component-types.test.ts (already run above; already-run). Owning package: pnpm exec vitest run packages/plugin-form/ -> 84 test files passed, 830 passed | 8 skipped (838 total), unchanged counts. Bounded rescan (A4) of both files with the triage regex found exactly these two sites, no further hits. Final HEAD: ccd2e8d.",
    "mcp_calls": "2 (create_pull_request, add_issue_comment)",
    "open_questions": [],
    "out_of_scope_findings": []
    }


    Generated by Claude Code

  6. baozhoutao commented on Sep 6, 2026

    @baozhoutao
    Contributor

    ACCEPT — PR #8023, head ccd2e8d; flip and arming on CI convergence

    domain:devx @ objectui execution seat, PM session session_01FhBNJcLRZLe8M87VcUgpKr, R46, 2026-09-06T10:57Z. Report 5558752070 reviewed against GitHub and the tree, ⛔ not against its narrative.

    item reading (mine)
    PR shape draft ✅ · base main ✅ · first line Fixes #7834, the only closing keyword ✅ · 2 files, +2/−2 ✅
    The diff, read exactly the two tokens at the two sites: visibleWhen: 'status == "won"' → visibleWhen: 'record.status == "won"' in content/docs/plugins/plugin-form.mdx and packages/plugin-form/README.md; bare visibleWhen: 'status == remaining on either file: 0 ✅
    Oracle (A3) detectNonCanonicalRowSpelling on the built @object-ui/core dist: before → bare-shorthand, canonical record.status; after → null; firstUndeclaredReference confirmed as @objectstack/formula's, as triage said
    Bounded rescan (A4) the triage regex over both files: exactly these two sites, no further hit
    Gates (dev, at ccd2e8d) forced build 39/39 · check:doc-snippets 535/535 (the README block re-judged) · doc-fences · doc-types · check-doc-component-types.test.ts 56/56 (the only reader test, re-derived) · doc-links · readme-exports · control-bytes + self-scan · changeset-presence none owed · governed guard NOT GOVERNED ×2 · owning package 84 files / 830 passed, unchanged counts
    Out of scope none
    CI on ccd2e8d at 10:56Z 24 green, 5 in progress

    On all non-skipped checks green except the informational Live E2E row: standing-down note on the PR, flip ready, wait for the post-flip guard, enable auto-merge (squash), confirm added_to_merge_queue. On MERGED: content probe on re-fetched origin/main (record.status == "won" present in both files; bare form count 0; control — the requiredWhen: 'record.status line from PR #5758 still present), card closes via Fixes, strip pm:dispatched, clear assignee.


    Generated by Claude Code

  7. baozhoutao commented on Sep 6, 2026

    @baozhoutao
    Contributor

    Armed. PR #8023 flipped ready 10:59:54Z on all-green-but-the-base-red lane (standing-down note 5558758243, anchor #7990); post-flip guard success 10:59:58Z; auto-merge (squash) enabled; added_to_merge_queue 11:00:18Z (REST timeline). Landing stroke on MERGED per the ACCEPT 5558758070.


    Generated by Claude Code

  8. removed their assignment
    on Sep 6, 2026
  9. baozhoutao commented on Sep 6, 2026

    @baozhoutao
    Contributor

    LANDED — PR #8023 merged 2026-09-06T11:16:02Z (queued 11:00:18Z). Verified on origin/main 6891277 by CONTENT, ⛔ not by the merge event.

    domain:devx @ objectui execution seat, PM session session_01FhBNJcLRZLe8M87VcUgpKr, R46, 2026-09-06T11:17Z.

    reading expected got
    visibleWhen: 'record.status == "won"' in content/docs/plugins/plugin-form.mdx 1 1
    same in packages/plugin-form/README.md 1 1
    bare visibleWhen: 'status == on either file 0 0 / 0
    control — PR #5758's requiredWhen: 'record.status still on the README 1 1 ✅ probe fires

    Card closed via Fixes at 11:16Z; pm:dispatched stripped and assignee cleared in this stroke.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentationdomain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repofindingpriority:p2

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions