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.

finding(plugin-grid): DATA_SOURCE_WIDGET_TYPES 是「哪些 widget 要 DataSource」的第四份私有副本 —— 与表单规则不同集、零 gate #4815

Closed

Description

@yinlianghui

发现于 #4790 的实施。同 #4770 / #4790 的失效模式,不在 #4790 范围内(该卡正文只点名两张表),单独立此单记录。

事实(实读 origin/main @ f2dc8fa)

packages/plugin-grid/src/components/bulkParamToField.ts:40-46:

const LOOKUP_WIDGET_TYPES = new Set(['lookup', 'master_detail']);
const USER_WIDGET_TYPES = new Set(['user', 'owner']);
const DATA_SOURCE_WIDGET_TYPES = new Set([...LOOKUP_WIDGET_TYPES, ...USER_WIDGET_TYPES]);

消费点两处:fieldNeedsDataSource()(→ BulkActionDialog.tsx:600 决定是否把网格的 DataSource 传给 param 控件)与 isLookupishParam()(→ 决定是否补 reference_to / display_field)。

它编码的正是对象表单那条规则的同一件事 ——「这个 widget 要查记录,所以得把 DataSource 递给它」—— 但成员是第三种集合:

成员
@object-ui/core EXPANDABLE_FIELD_TYPES(schema 字段类型域) lookup, master_detail, tree, user
表单 needsDataSourceWiring(widget 键域,#4790 后派生自上表) lookup, master_detail, tree, user + object-ref, filter-condition, recipient-picker
plugin-grid DATA_SOURCE_WIDGET_TYPES(widget 键域) lookup, master_detail, user, owner

即:多 owner,少 tree 与三个 widget-hint picker。没有任何 gate 能发现它继续漂。

为什么今天没有用户症状(据此判为 observation-class)

所以现状是「三份写法各自恰好够用」,不是线上缺口 —— 但这正是 #4770 与 #4790 两次都描述的前夜状态:注释声称同步、机制不保证,直到某一侧加键才炸。

修法方向(与 #4789 / #4790 同型)

单一定义 + 各消费面自己做归一化 + identity 钉(钉对象身份,成员相同的私有副本骗不过)。落点候选:core 的 EXPANDABLE_FIELD_TYPES(参考半)+ 各面自己的 widget-hint 扩展,与 #4790 给表单定的形状一致。owner 那一格取决于 #4814 的裁决,建议等 #4814 判完再动,否则会把一个待退役的别名钉进共享表。

Blocked-by: #4814

参考

Activity

  1. os-support-ai commented on Aug 18, 2026

    @os-support-ai
    Collaborator

    Triage first-touch grade (concentrated batch; triage seat, session session_01NYoiJGd1MxXdcvM4EGAMrS): promoted to pm:queue, type Task — not blocked: the body's Blocked-by: #4814 is already satisfied (#4814 closed 08-17 via merged PR #4915, ruling A′ retired the owner spelling). Premise re-verified on the merged ref, not the card text: origin/main:packages/plugin-grid/src/components/bulkParamToField.ts:51 now reads USER_WIDGET_TYPES = new Set(['user']) with a tombstone comment at :45 — so the owner cell of this card's table is done, and the remaining ask is exactly the #4790-shaped convergence: single definition (core EXPANDABLE_FIELD_TYPES as the reference half + per-surface widget-hint extension), per-consumer normalization, and an identity pin (assert same object, so a member-identical private copy can't fake it).

    The tree / picker-hint divergences remain measured-but-asymptomatic; the card's own analysis of why is the spec for which cells the shared table must and must not contain. Size/model suggestion: S–M, sonnet/opus.


    Generated by Claude Code

  2. os-support-ai commented on Aug 19, 2026

    @os-support-ai
    Collaborator

    Claim: PM loop round 15

    Session: session_01RV6yuVCxymHYE16PL9vQkE
    Branch: claude/issue-4815-data-source-widget-types-convergence
    Worktree: objectui-issue-4815
    Domain: repo:objectui
    File surface: packages/plugin-grid/src/** + packages/core/src/** (the shared reference table) + their tests (stop on breach; explain in the report)
    Container & model: S–M, mode:subagent, model: opus (taking the triage seat's upper suggestion — the identity-pin design is the hard part, not the edit)
    Clause-②: no — this converges private copies onto one definition. It changes no contract surface and widens nothing published. If your implementation turns out to change which widget types are treated as data-source-bearing anywhere, that is a behaviour change: stop and report it rather than absorbing it into a "convergence".
    Serial constraints cleared: packages/plugin-grid and packages/core are both free. Open PRs hold disjoint surfaces, each measured by git diff --name-only origin/main...: PR #5290 apps/console, PR #5294 packages/plugin-view, PR #5295 .github/workflows + scripts/ + root manifests, PR #5297 packages/plugin-map, PR #5298 packages/components + packages/types, PR #5300 / #5303 packages/plugin-dashboard, PR #5302 packages/cli, PR #5310 packages/plugin-report (consumes @object-ui/core but does not modify it — verified on its diff), PR #5311 packages/plugin-form.

    Not blocked — verified, not assumed

    The body carries Blocked-by: #4814. That is satisfied: #4814 closed 2026-08-17 via merged PR #4915 (ruling A′ retired the owner spelling). The triage seat re-verified the premise on the merged ref rather than from the card text — origin/main:packages/plugin-grid/src/components/bulkParamToField.ts:51 now reads USER_WIDGET_TYPES = new Set(['user']) with a tombstone at :45. So the owner cell of the card's table is already done; do not re-do it.

    ⚠️ That reading is two days old. Re-derive on current origin/main before editing — the card's own table may have moved again. Treat every figure in the card as a previous round's reading needing re-verification, not as fact.

    Scope — the #4790-shaped convergence

    DATA_SOURCE_WIDGET_TYPES is the fourth private copy of "which widgets need a DataSource", with a different member set from the form rules and zero gate. Three deliverables:

    1. Single definition — core's EXPANDABLE_FIELD_TYPES as the reference half, plus a per-surface widget-hint extension.
    2. Per-consumer normalization.
    3. An identity pin — ⚠️ assert the same object, not the same members. A member-identical private copy must fail the test. This is the whole point: a value-equality assertion would pass against exactly the defect this card exists to remove, which makes it worse than no test.

    The card's own analysis of the tree / picker-hint divergences is the spec for which cells the shared table must and must not contain — those remain measured-but-asymptomatic and are not yours to resolve. Read that analysis before choosing membership.

    Verification

    • Counter-probe every zero with a known-present neighbouring term.
    • Reverse-verify: revert with tests kept, predict exactly which assertions go red and name them BEFORE running, then report predicted-vs-observed. Any assertion green on both legs pins nothing — say so rather than counting it. In particular, prove the identity pin actually discriminates: a leg where a member-identical copy is substituted should go red.
    • State whether a build artifact sits between your edit and the thing under test, per leg — this spans two packages, so check whether the tests resolve @object-ui/core via the root vitest.config.mts alias to src or via package exports to dist.

    Changeset: patch. ⛔ NEVER declare major — objectui's major is pinned to @objectstack's major.
    ⛔ Do not edit content/docs/releases/, docs/adr/**, .claude/**, skills/**, AGENTS.md, CLAUDE.md.


    Generated by Claude Code

  3. os-support-ai commented on Aug 19, 2026

    @os-support-ai
    Collaborator
    {
      "issue": 4815,
      "status": "done",
      "branch": "claude/issue-4815-data-source-widget-types-convergence",
      "pr": "https://github.com/objectstack-ai/objectui/pull/5314",
      "premise_still_valid": true,
      "summary": "Re-derived the card's table on current origin/main (de4e29a81) rather than trusting the two-day-old reading: the `owner` cell is indeed already done (USER_WIDGET_TYPES = new Set(['user']) with the #4814 tombstone above it), and the rest of the premise holds — DATA_SOURCE_WIDGET_TYPES was still a private union of LOOKUP_WIDGET_TYPES and USER_WIDGET_TYPES with three consumers and no gate. Delivered the #4790-shaped convergence: `widgetNeedsDataSource()` now reads core's EXPANDABLE_FIELD_TYPES and feeds all three consumers (isLookupishParam, fieldNeedsDataSource, and bulkParamToField's reference_to/display_field branch); core's expand-fields TSDoc gains the fourth consumer. No reachable behaviour change, verified rather than assumed: `tree` is the one differing cell and can never be a widget key here (absent from the fields widget map, mapFieldTypeToFormType sends it to field:lookup), and the three form-only widget-hint pickers are deliberately NOT absorbed — pulling them in would change which widgets get a DataSource, i.e. a behaviour change per the roll-back clause. Both boundaries are pinned by tests.",
      "tests": "All at 455a274f6 (final commit, clean tree). UNION: `pnpm exec vitest run` (canonical root invocation) over 8 affected files — bulkParamToField, bulkActionDialogParams, bulkActionDialogRecord, bulkActionSchemaMultiple, bulkFanoutHostParams, core expand-fields, core unmaterialized-fields, form-data-source-wiring => `Test Files 8 passed (8) / Tests 80 passed (80)`; `pnpm --filter @object-ui/plugin-grid --filter @object-ui/core type-check` => both `Done` (script name echoed in the output, so not a zero-match silent pass); eslint on the 3 changed files => 0 errors (5 pre-existing no-explicit-any warnings on untouched lines); check:control-bytes / check:phantom-deps / check:self-import / check:esm-specifiers => pass; direct control-byte grep of the changed files => clean. REVERSE VERIFICATION, predicted before running, both legs matched exactly: Leg A (revert bulkParamToField.ts to origin/main, keep tests) predicted the 2 identity assertions red and 13 green, observed `Tests 2 failed | 13 passed (15)` with `AssertionError: expected [] to include 'lookup'` and `isLookupishParam never consulted the shared set`. Leg B — the discrimination leg — substituted a MEMBER-IDENTICAL copy (`new Set([...EXPANDABLE_FIELD_TYPES])`, members equal by construction), predicted the same 2 red, observed `Tests 2 failed | 13 passed (15)`; every membership-shaped assertion stayed green against that copy, which is the empirical proof that a toEqual-style pin would have passed ON the defect. Stated rather than counted: the other 3 new assertions are green on BOTH legs by design (normalization closure + the two deliberate boundaries) — they guard a future widening, not this convergence, and pin nothing about it. REBUILD QUESTION, per leg: no build artifact sits between any edit and the runner — the canonical root invocation loads vitest.config.mts, whose resolve.alias maps @object-ui/core to packages/core/src, and the test imports bulkParamToField relatively. Proved directly, not read off the config: with packages/core/dist moved aside entirely the run is still 15/15 green (and both earlier green runs predate any build — dist did not exist). Both red legs are therefore self-proving. The pin also holds under the other resolution regime (package-cwd run, @object-ui/core via package exports to dist): 15/15.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #5312: app-shell's paramToField.ts:97 (`LOOKUP_WIDGET_TYPES.has(type) || type === 'user'`) is now the LAST private copy of this rule, and its 'moves in lockstep with plugin-grid's bulkParamToField twin' comment stopped being mechanically true the moment this PR landed the twin on core — asymptomatic today for the same reason (tree unreachable, owner retired), outside this card's file surface",
        "filed as #5313: assertCanonicalVitestInvocation does not cover the 11 packages that own a standalone vitest.config.ts (plugin-grid among them) — measured, not inferred: `cd packages/plugin-grid && pnpm exec vitest run <file>` ran with Vitest root = the package dir and was NOT refused, contradicting the guard docstring's explicit universality claim; related to the open #3240 but not resolved by it"
      ]
    }

    Generated by Claude Code


    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

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions