Repository navigation
Commit 4d1f52c
domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC
<!-- claude-projects-attribution: {"github_login":"os-bill"} -->
_Requested by **Bill** · [project
thread](https://claude.ai/code/project/chan_016iMXWSZDzqWRWvWzdNo8wg?thread=cmsg_016iMXWSZDzqWRWvWzdNo8wgP6XkfpjCfyzzG2KwQPUZfy)_
Fixes #27567
Clause-②: yes (widening)
Metadata-protocol: none
## 维护者速读
- 改了什么:导入向导「Match
on」只列出服务端接受的匹配字段:多值查找、多选、标签、复选组、JSON/地址等结构化字段不再出现;向导字段形状新增可选
`multiple`,由控制台的两处字段构建(列表导入、AI Excel 导入)从对象定义带过来。
- 为什么改:服务端已拒绝把这类字段当匹配键(#27465 / PR #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
<details><summary>Ablation (filter removed, then restored)</summary>
`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.
</details>
<details><summary>Tests and type-check</summary>
- 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).
</details>
<details><summary>Derived gates (dispatch-gates --commands, merged
head)</summary>
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`.
```text
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
```
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_01Utp6CXKADE9Bt6J32Lacv5)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent b761968 commit 4d1f52c
6 files changed
Lines changed: 135 additions & 6 deletions
File tree
- .changeset
- ui/packages
- app-shell/src
- console/ai
- views
- plugin-grid/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
42 | 42 | | |
43 | 43 | | |
44 | 44 | | |
| 45 | + | |
45 | 46 | | |
46 | 47 | | |
47 | 48 | | |
| |||
71 | 72 | | |
72 | 73 | | |
73 | 74 | | |
| 75 | + | |
| 76 | + | |
74 | 77 | | |
75 | 78 | | |
76 | 79 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
77 | 77 | | |
78 | 78 | | |
79 | 79 | | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
80 | 92 | | |
81 | 93 | | |
82 | 94 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
32 | 32 | | |
33 | 33 | | |
34 | 34 | | |
| 35 | + | |
35 | 36 | | |
36 | 37 | | |
37 | 38 | | |
| |||
67 | 68 | | |
68 | 69 | | |
69 | 70 | | |
| 71 | + | |
| 72 | + | |
70 | 73 | | |
71 | 74 | | |
72 | 75 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
19 | | - | |
| 19 | + | |
| 20 | + | |
20 | 21 | | |
21 | 22 | | |
22 | 23 | | |
| |||
362 | 363 | | |
363 | 364 | | |
364 | 365 | | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
365 | 371 | | |
366 | 372 | | |
367 | 373 | | |
| |||
605 | 611 | | |
606 | 612 | | |
607 | 613 | | |
| 614 | + | |
| 615 | + | |
| 616 | + | |
| 617 | + | |
| 618 | + | |
| 619 | + | |
| 620 | + | |
| 621 | + | |
| 622 | + | |
| 623 | + | |
| 624 | + | |
608 | 625 | | |
609 | 626 | | |
610 | 627 | | |
| |||
671 | 688 | | |
672 | 689 | | |
673 | 690 | | |
674 | | - | |
675 | | - | |
676 | | - | |
| 691 | + | |
| 692 | + | |
| 693 | + | |
677 | 694 | | |
678 | 695 | | |
679 | 696 | | |
| |||
1962 | 1979 | | |
1963 | 1980 | | |
1964 | 1981 | | |
1965 | | - | |
| 1982 | + | |
| 1983 | + | |
1966 | 1984 | | |
1967 | | - | |
| 1985 | + | |
| 1986 | + | |
| 1987 | + | |
| 1988 | + | |
1968 | 1989 | | |
1969 | 1990 | | |
1970 | 1991 | | |
| |||
0 commit comments