Skip to content

fix(plugin-grid): "Match on" offers only fields the server takes as a match key (#27567) - #27645

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-w0so7g
Oct 29, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-w0so7g

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC

Requested by Bill · project thread

Fixes #27567

Clause-②: yes (widening)
Metadata-protocol: none

维护者速读

  • 改了什么:导入向导「Match on」只列出服务端接受的匹配字段:多值查找、多选、标签、复选组、JSON/地址等结构化字段不再出现;向导字段形状新增可选 multiple,由控制台的两处字段构建(列表导入、AI Excel 导入)从对象定义带过来。
  • 为什么改:服务端已拒绝把这类字段当匹配键(/import upsert: a multiple: true lookup ticked as a match field fails every row with Operator "=" … received an array #27465 / PR fix(rest, core)!: refuse a multi-value or structured field as an import match key (#27465) #27562,400 INVALID_REQUEST),但向导照样让用户勾选,要到「Validate」才被整单拒绝。
  • 风险与回滚:只动前端:一个过滤条件、一个可选 prop 键、两处字段构建各一行;不改 spec、不改服务端、不加元数据键。回滚即 revert 本 PR。
  • 先看:ui/packages/plugin-grid/src/ImportWizard.tsx(isMatchKeyField 与 matchable 过滤)→ ui/packages/app-shell/src/views/importTargetFields.ts(带 multiple)→ 新测试 ImportWizard.matchKeyFields-27567.test.tsx。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/plugin-grid/src/ImportWizard.matchKeyFields-27567.test.tsx,预期 1/1 通过(只剩 code、email、owner、stage 四个可选)。
  • 需要你做的:无,席位落地。

What changes

In "Update existing" and "Update if matched, else create", the import wizard's "Match on" listed every mapped field. Since #27465 the server refuses a match key whose value is a list or a structured payload, so ticking such a field got the whole import refused at Validate. "Match on" now offers only the mapped fields the server takes as a key.

  • One rule, read from the spec. isMatchKeyField in ImportWizard.tsx is !STRUCTURED_JSON_TYPES.has(type) && !isMultiValueField({ type, multiple }), the same two @objectstack/spec/data definitions the server's multiValueMatchKeyRefusal (@objectstack/core) composes. plugin-grid already imported from @objectstack/spec/data, so no dependency edge and no package.json change. matchable is the existing list with that filter added: no new widget, dialog or hook.
  • multiple on the field shape. ImportWizardProps['fields'][number] takes an optional multiple?: boolean (the declared widening: ImportWizardProps is re-exported by name from the package entry). The wizard does not build its field shape; its hosts do, so the flag is carried at both:
    • importTargetFields (app-shell/src/views/importTargetFields.ts, the console list's Import), only when the schema says multiple: true;
    • normalizeFields in ExcelImportBar.tsx (the AI build panel's spreadsheet import, which offers all three write modes), the same way. Cleared for this PR by the seat that holds that path.
      identityImportFields (text/email only) is unchanged.
  • Fields left out. A multiple: true lookup, user, select, radio, file or image; any multiselect, checkboxes or tags; any json, composite, repeater, record, location, address or vector. A single-value lookup or select and every scalar field stay offered. A host that leaves multiple out still has the inherently multi and structured types filtered by type alone.
  • Docblock. refusedOptionValue's note said the field shape does not carry multiple; it now says the cell check does not read it (a host may leave it out), so its behaviour is unchanged.

Tests

  • New ui/packages/plugin-grid/src/ImportWizard.matchKeyFields-27567.test.tsx: on an upsert import with ten mapped fields, "Match on" lists exactly code (text), email, owner (single lookup), stage (single select); owners (lookup, multiple: true), stages (select, multiple: true), labels (tags), regions (multiselect), meta (json), site (address) are absent.
  • importTargetFields.test.ts: a new case pins that multiple: true is carried and multiple: false / absent is not.

Acceptance notes

  • The card's three acceptance lines: the field shape carries multiple from the object schema (both hosts that build it); "Match on" lists only mapped fields the rule admits; the change is a filter on matchable.
  • ExcelImportBar's one carried line has no pin of its own: its existing tests (ExcelImportBar.sharedSelect-11865) compare the wizard's fields as JSON text, and they stay byte-identical because the key is written only when true.
  • A saved server mapping's upsertKey that names such a field is applied as the mapping says (setMatchFields(m.upsertKey…)), and the server's refusal names the mapping; the wizard does not edit an authored mapping. Not changed here.
  • importTargetFields.test.ts is outside both of app-shell's tsc programs (tsc --noEmit and tsconfig.test.json list neither it nor the other views/*.test.ts beside it, measured with --listFiles); vitest runs it. Pre-existing, not widened by this PR.

Verification

Ablation (filter removed, then restored)

node scripts/ablation-replace.mjs --file ui/packages/plugin-grid/src/ImportWizard.tsx --anchor 'mappedFieldNames.has(f.name) && isMatchKeyField(f)' --replacement 'mappedFieldNames.has(f.name) /* ABLATED_27567 */' -- pnpm --dir ui exec vitest run packages/plugin-grid/src/ImportWizard.matchKeyFields-27567.test.tsx

  • mutation landed: anchor 1 → 0, blob bff5282cf050 → cd3b19e941f3
  • red: Tests 1 failed (1), received the six refused fields in addition (owners, stages, labels, regions, meta, site)
  • restored: blob == HEAD (bff5282cf050), git diff HEAD empty; the pin passes again (Tests 1 passed (1))

@object-ui/* resolves to source in ui vitest, so no rebuild between legs.

Tests and type-check
  • Pre-merge head 06d76839: pnpm --dir ui exec vitest run --maxWorkers=2 packages/plugin-grid/ packages/app-shell/src/views/importTargetFields.test.ts packages/app-shell/src/console/ai/__tests__/ → Test Files 305 passed (305), Tests 2530 passed (2530) (lock VERDICT command-exit 0).
  • Merged head 0c73a9da: the new pin, importWriteModes-12697, importTargetFields.test.ts, both ExcelImportBar test files → Test Files 5 passed (5), Tests 31 passed (31). The origin/main merge moved no plugin-grid file.
  • pnpm --filter @object-ui/app-shell --filter @object-ui/plugin-grid run type-check on the merged head → VERDICT command-exit 0.
  • eslint on the five touched source files: 0 errors (warnings are pre-existing lines; the new test adds none).
Derived gates (dispatch-gates --commands, merged head)

Head 0c73a9da, node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran FILE → 34 derived famil(ies) accounted for — 34 run, 0 NOT-MEASURED.

node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-sdui-manifest.mjs :: exit 0
node scripts/check-sdui-manifest.mjs --self-test :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --dir ui exec vitest run scripts/ :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:dispatcher-error-vocabulary :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:engine-double-contract :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:pm-changeset-deadline-census :: exit 0
pnpm check:published-files :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:skill-identifier-liveness :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0

Generated by Claude Code

… match key (#27567)

The import wizard's options step listed every mapped field under "Match on".
The server refuses a list or structured field as a match key, so the list now
filters on the same spec definitions (isMultiValueField, STRUCTURED_JSON_TYPES),
and the console's field-shape builders carry the schema's `multiple` flag.

Claude-Session: https://claude.ai/code/session_01Utp6CXKADE9Bt6J32Lacv5
Co-authored-by: Claude <noreply@anthropic.com>
…w optional `multiple` prop key

Claude-Session: https://claude.ai/code/session_01Utp6CXKADE9Bt6J32Lacv5
Co-authored-by: Claude <noreply@anthropic.com>
…, as the console list does (#27567)

Claude-Session: https://claude.ai/code/session_01Utp6CXKADE9Bt6J32Lacv5
Co-authored-by: Claude <noreply@anthropic.com>
…hout `as any`

Claude-Session: https://claude.ai/code/session_01Utp6CXKADE9Bt6J32Lacv5
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Oct 29, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC

Contract review

Served-tier: TIER_DEFAULT
Head-sha: 0c73a9da146961d123b47705f3d7d7cabfa6efbe
Local-runs: none

Face: the Clause-②: yes (widening) declaration alone. It appears in the PR body, the changeset and the amended claim 6499806710. The PR's six files include no packages/spec/** path and no governed path, so no CONTRACT_REVIEW_TIER face applies.

① Derived judgments

  • Widened, correctly declared: @object-ui/plugin-grid re-exports ImportWizardProps (export type { ImportWizardProps, ImportResult } from './ImportWizard', index.tsx). Its fields[number] shape gains one optional key, multiple?: boolean. The public surface grows, so yes (widening) is the honest line. Nothing becomes required, so a host that omits the key still compiles. For such a host, the inherently multi-value and structured types are still filtered by type.
  • Not widened: ImportTargetField (app-shell/src/views/importTargetFields.ts) and the WizardField in ExcelImportBar.tsx gain the same optional key. Neither is reachable from @object-ui/app-shell's entry (no export of importTargetFields / ImportTargetField in src/index.ts), so both stay package-internal.
  • One rule, not a second list: isMatchKeyField composes STRUCTURED_JSON_TYPES and isMultiValueField from @objectstack/spec/data. These are the two definitions core's multiValueMatchKeyRefusal reads (PR fix(rest, core)!: refuse a multi-value or structured field as an import match key (#27465) #27562), which meets triage 6498869186 ("by reading the same match-key rule, not a second list"). plugin-grid already imports from @objectstack/spec/data, so there is no new dependency edge and package.json is untouched.
  • Metadata-protocol: none holds. Nothing an author writes changes, and multiple is the object schema's existing field key, read rather than introduced.

② Semver level

patch on @object-ui/plugin-grid, following the repo's rule that the level follows Metadata-protocol: (kind none ⇒ patch). The changeset body names the new optional key. app-shell's two edits are internal and carry no changeset entry. That is acceptable: no exported surface of @object-ui/app-shell changes, and the behaviour they feed is announced under plugin-grid.

③ Boundary flags

  • No open_questions. The dev reported premise valid and no deviations. The ExcelImportBar.tsx and importTargetFields.ts lines were declared on the amended claim before the work.
  • The PR author is the relay (objectstack-fleet[bot]).

Implemented-by: claude/project-thread-w0so7g
Reviewed-by: session_01Utp6CY2n1JmZZKrHgtp9jC

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC

席位核对

ACCEPT · head 0c73a9da146961d123b47705f3d7d7cabfa6efbe · 2026-10-29T16:24Z

  1. Shape: draft → main, Fixes #27567 only, attribution block on top. Six files: plugin-grid ImportWizard.tsx + its new test, app-shell importTargetFields.ts + its test and console/ai/ExcelImportBar.tsx, and one changeset. No assignee, no reviewer.
  2. Premise measured: the seat read the diff. matchable is now mapped && isMatchKeyField(f), where isMatchKeyField is !STRUCTURED_JSON_TYPES.has(type) && !isMultiValueField({type, multiple}) from @objectstack/spec/data. That is the composition core's multiValueMatchKeyRefusal reads (PR fix(rest, core)!: refuse a multi-value or structured field as an import match key (#27465) #27562), so it is not a second list. Both console builders of the wizard's fields carry the schema's multiple: true. No package.json change and no packages/spec/** path. This matches triage 6498869186 and the card's three Acceptance lines.
  3. Gates (check-runs on this head): 44 success, 11 skipped, 0 red. These include Lint & Repo Gates 123572913139, TypeScript Type Check 123571423240, Test UI 123576682112 and Lint · PM dispatch-gates 123566508360. The dev's derived gates were 34/34 exit 0 on this head.
  4. Contract review: declaration face, TIER_DEFAULT, PASS on this head (comment 6502273608). Clause-②: yes (widening) comes from the optional multiple key on the exported ImportWizardProps, declared on the amended claim 6499806710, in the body and in the changeset.
  5. Accepted deviations / out of scope: none. The app-shell edits are internal (no exported surface) and fall within the amended claim's surface. I read the changeset sentence by sentence against the diff and it matches: FROM → TO, the refused families, the optional flag, and the type-only fallback for hosts that omit it.
  6. Landing: not governed (check-governed-merges --pr 27645: NOT governed, 0 of 6 paths), 141 lines < 3000. The seat lands it: ready + auto-merge now.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 29, 2026 16:25
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 29, 2026
Merged via the queue into main with commit 4d1f52c Oct 29, 2026
56 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-w0so7g branch October 29, 2026 16:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Import wizard: "Match on" offers a multi-value or structured field the server refuses as a match key

1 participant