You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
import preview: the client check flags a bad number but not an unknown picklist value, and the button still reads "Import 2 Rows" with a known-bad row #11814
Filing gate ① — product defect with a named location and a reproduction. reach: Tasks → Import a 2-row CSV where row 2 has Priority Bogus and Estimate abc.
Who acts on it: objectui triage → the console / records owner. ⛔ Not a claim. Found in a manual browser QA pass of the end-user console on 2026-10-07; filed one card per finding on the maintainer's word: 「你发现的问题全部提交 issue,然后继续测试前端」, and on the one-card-per-finding question 「覆盖规则,逐条立卡」.
What happens
The preview marks abc (number column) red, but not Bogus, although the client has the picklist's options.
Validate data (server) reports only Row 2: Priority: "Bogus" is not one of the allowed options — the two checks each see half of the row's problems.
With "1 row with errors" shown, the button still reads Import 2 Rows; the result is "1 created · 1 skipped" with the reason and Download failed rows (that part works well).
Expected
The client marks unknown option values like other type errors; the button states what will happen ("Import 1 row, skip 1").
Suggested direction (triage to rule)
Validate select/multiselect cells against the field options client-side (respecting Keep unknown option values), merge client and server findings into one per-row list, and count only importable rows on the button.
Environment
objectstack 879bd38c · examples/app-showcase booted with objectstack dev --ui --seed-admin on an isolated port and SQLite file · objectui 179f6fe9 (HEAD; the framework pin .objectui-sha is a58626c8) served by the console's Vite dev server, perf numbers from a vite build of the same commit · Chromium 141 at 1440×900 · signed in as the seeded platform admin admin@objectos.ai unless stated.
Duplicate check
Semantic issue search on objectstack-ai/objectui (open and closed), import preview validation picklist value unknown option import rows count: 1 hits (0 open, 1 closed).
Semantic issue search on objectstack-ai/objectui (open and closed), import dialog does not flag invalid select value before submit, import button counts invalid rows: 3 hits (0 open, 3 closed).
Path: ② the capabilities an end user meets in the app — importing records | 缺项 | P3
Triage: first grade, bug · priority:p3 · domain:ui · area:records · pm:queue. Direction as the body proposes
Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-07T17:37Z. ⛔ Not a claim, ⛔ not a dispatch.
Triage: lands in packages/plugin-grid/src/ImportWizard.tsx (the preview check and the import button) ⇒ domain:ui; rationale: a Studio or console surface in objectui. Filed from the browser QA pass of 2026-10-07 (objectstack 879bd38c, objectui 179f6fe9); paths re-read on objectui main9990f9e122.
Why p3: the server refuses the bad row and reports it, so nothing wrong is stored. The preview and the button tell half the story.
Direction:
check select and multiselect cells against the field options on the client (respecting Keep unknown option values)
merge client and server findings into one per-row list
validateValue (about :521) and validateRow (about :601): select and multiselect cells are checked against the field's options, honouring Keep unknown option values.
The preview's per-row findings list: client and server findings merged into one list per row.
The import button's label: it counts only importable rows.
The wizard's own grid.import.* defaults table (about :138–:171), only for a new label.
The tests beside these, and .changeset/11814-*.md.
⛔ Not on it:
packages/plugin-grid/src/importParsers.ts: seat 1's objectui#11813 (isDateLike, type inference) is assigned and in flight.
The server import route and its refusal text.
packages/i18n/** and packages/components/src/ui/**.
Any file outside this list: the dev reports it before opening the PR (stop on breach; explain in the report).
Container & model: M, mode:subagent, model: opus (dispatch-gates --tier --repo objectstack-ai/objectui over these paths: no path-derived mandate; default tier)
Clause-②: no
Responsibility: objectui plugin-grid ImportWizard: the preview's client check covers number cells but not select/multiselect values the field's options refuse, and the import button counts every row, known-bad ones included | the server's Validate data / import already refuses the bad row and reports it, so nothing wrong is stored | every user importing a CSV whose option column holds an unknown value, measured by the filer on Tasks (Priority "Bogus": no client mark; "Import 2 Rows" with 1 known-bad row)
Thread-read: 6043373967
Serial constraints cleared: none blocking.
Open objectui PRs, read 2026-10-08T03:24Z: none touches ImportWizard.tsx.
Why Clause-②: no: this is client-side preview UX. The server's accepted values do not change, and no export, prop or packages/i18n key is added. If a new string must live in packages/i18n rather than the wizard's own defaults table, the dev reports it before opening the PR, and the seat re-claims with Clause-②: yes.
Note for this card's claimant (seat 2): a measured finding from objectui#11813, same Date.parse family
domain:ui seat 1 · session_01DrKzdPdyLLBW3qpZ4vtk7z · 2026-10-08T03:56Z. ⛔ Not a claim, and not a request to widen this card. It is information for whoever is working ImportWizard.tsx.
The finding: objectui#11813's dev found that validateValue in packages/plugin-grid/src/ImportWizard.tsx accepts any value Date.parse reads for a date / datetime field. So "Phase 2" mapped onto a date field is not marked red in the preview, because V8 reads it as 2001-02-01. Its reach was not measured.
PR objectui#11881 (objectui#11813) gates importParsers.ts's module-private isDateLike on explicit date shapes. It does not touch ImportWizard.tsx.
What to do with it: whether the preview check should share that shape rule is this card's claimant's call. If it is out of this card's scope, it can be filed separately.
Dedupe words: import preview date validation Date.parse lenient · validateValue date datetime.
os-dev-report
{
"issue": 11814,
"status": "done",
"branch": "claude/issue-11814-import-preview-options",
"pr": "#11886",
"session": "session_01MgfduSkFrfM3eorB3UGfAU",
"premise_still_valid": true,
"summary": "Reproduced the premise on base cef0eee: the preview check had no select/multiselect case, so Bogus stayed unmarked, and the button showed every row (rows.length). The Import Wizard preview now checks select/radio/multiselect/checkboxes/tags cells against the field options the way the server import matches them: exact value, case-insensitive trimmed label, multi-value cells split on , ; 、 and newline, and a field with no options accepts anything. Client findings and Validate-data findings are merged into one list with one entry per row (a duplicate on the same field is dropped), and the button counts rows with no finding ("Import 1 Row" for the reported file). Because the count now trusts the client check, four existing checks that marked values the server accepts were aligned, all in the same file: the number grammar now mirrors parseNumberCell; a whitespace-only cell counts as blank; a blank required cell is marked only in insert mode; and columns a named mapping transforms are not checked. H2 was falsified by measurement (published @objectstack core/objectql 17.7.0): with Keep unknown option values on, coercion keeps the value but the engine validateRecord still refuses it on every writable field, so the client still marks it there and skips only match-only (readonly) fields. This departs from the literal triage wording; see open_questions[0].",
"tests": "All at head 25c0417. (1) pnpm exec vitest run packages/plugin-grid/src/ImportWizard.previewFindings-11814.test.tsx packages/plugin-grid/src/ImportWizard.cellCheck-11814.test.ts -> Test Files 2 passed (2), Tests 28 passed (28). (2) Reverse validation, one-off with a trap: wrote the base cef0eee ImportWizard.tsx to disk (marker checkImportCell grep -c 4 -> 0; base-only validateValue signature 1), ran the same command -> Test Files 2 failed (2), Tests 27 failed | 1 passed (28). Sample failures: aria-invalid Expected "true" Received "false"; button Expected "Import 1 Row" Received "Import 2 Rows". The 1 passing test is the inline-correction test, which by design does not pin the defect. Restored with git checkout HEAD: hash 047c9b3d matches the HEAD blob and git diff HEAD is empty. (3) pnpm exec vitest run --maxWorkers=2 packages/plugin-grid/ -> Test Files 190 passed (190), Tests 1764 passed (1764); this includes ImportWizard.i18n-parity.test.ts. All runs went through os-verify-lock, slot issue-11814.",
"gates": [
"head 25c0417 for every row",
"pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-grid^...' build -> BUILD_EXIT=0",
"pnpm --filter @object-ui/plugin-grid type-check (tsc --noEmit && tsc -p tsconfig.test.json) -> TC_EXIT=0; tsc -p tsconfig.test.json --listFilesOnly lists both new test files",
"pnpm exec eslint (3 touched source files) -> exit 0, 0 errors, 10 warnings; the base file gets the same 10 (5 set-state-in-effect, 3 only-export-components, 2 no-explicit-any)",
"node scripts/check-changeset-presence.mjs -> exit 0: 3 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)",
"pnpm check:i18n-keys -> exit 0: every in-scope call-site key resolves against the en pack",
"pnpm check:new-line-citations -> VERDICT new-cross-file-line-citations: 0 new citation(s) -> exit 0",
"pnpm check:control-bytes -> check-control-bytes: OK",
"pnpm check:spec-symbols -> exit 0",
"NOT MEASURED: check:spec-floors, reason: it reads every package dist and this checkout builds only the plugin-grid dependency closure. Hand measurement: the @objectstack/spec@17.5.0 tarball (plugin-grid floor ^17.5.0) exports SINGLE_OPTION_TYPES and MULTI_OPTION_TYPES from dist/data",
"CI on the PR: in_progress at report time (19 success, 3 skipped, 20 in progress of 42 check runs); convergence belongs to the PM"
],
"files_changed": [
"packages/plugin-grid/src/ImportWizard.tsx",
"packages/plugin-grid/src/ImportWizard.cellCheck-11814.test.ts",
"packages/plugin-grid/src/ImportWizard.previewFindings-11814.test.tsx",
".changeset/11814-import-preview-options.md"
],
"deviations": [
"H2 inverted by measurement: with Keep unknown option values on, an unknown value is still marked on writable fields (the engine refuses it) and is not marked only on match-only fields. The PM pin "with Keep unknown option values on, the unknown value is not marked" became "still marks the value the server refuses" plus a match-only case where it is not marked.",
"H3 wording: the button reads "Import 1 Row" (existing key importNRows, count = importable rows), not "Import 1 row, skip 1". The skip wording needs a new key, and ImportWizard.i18n-parity.test.ts pins the wizard defaults table to packages/i18n en, which would make this Clause-② yes. So the stop condition was not triggered, and no packages/i18n change was made.",
"Same-class in-place fixes in the claimed file (validateValue / validateRow): number grammar = server parseNumberCell; whitespace-only cell = blank; required blank marked only in insert mode; columns transformed by a named mapping are not checked. Without these, the new button count would undercount rows the server imports (for example 1,234 or $12).",
"The separate dry-run list inside the Validate panel was removed; its entries now sit in the merged findings list. The panel keeps its summary line.",
"grid.import.required no longer has a call site (client findings reuse grid.import.requiredValue). It stays in place, because removing it touches packages/i18n.",
"File surface held: only the claimed file, its tests and the changeset. Nothing in importParsers.ts, the server, packages/i18n or packages/components/src/ui."
],
"mcp_calls": "0",
"api_writes": "3 REST writes, all through the fleet-write relay as repository_dispatch POST /repos/objectstack-ai/objectstack/dispatches: (1) pr_create, which became POST /repos/objectstack-ai/objectui/pulls (draft) and created #11886; (2) label-write --assign os-bill, which became POST /repos//issues/11886/assignees; (3) this os-dev-report comment, via post-stamped, which became POST /repos//issues/11814/comments. Two git pushes, which are not REST: the empty-branch write probe, and commit 25c0417.",
"open_questions": [
{
"question": "Keep unknown option values: the triage direction says the client should respect this setting, but measurement shows the server still refuses an unknown value on every writable field when it is on. Coercion keeps the value; the engine validateRecord select and multi-value arms refuse it ("Priority must be one of: high, low"). Which should the client follow?",
"options": [
"A (shipped): follow the server. Mark on writable fields; spare only match-only (readonly) fields, whose value the engine checks for shape only. The client verdict equals the server verdict.",
"B: literal wording. Never mark an unknown value while the setting is on. The button then counts rows the import will fail, which is the defect this card fixes."
],
"recommendation": "A. The card exists so that the client verdict equals the server verdict. That the setting does not keep values on writable fields is a server-side gap, reported in out_of_scope_findings. If the server is changed to keep the value, or the setting is retired, the client follows in one line (refusedOptionValue)."
},
{
"question": "Three residuals each need Clause-② yes (public-surface change), so none is in this PR: (1) select/radio with multiple:true — the wizard field shape has no multiple and the producers (app-shell importTargetFields, ExcelImportBar) do not send it, so the client marks a single-option cell only when it fails both as one value and as a split list; (2) a required field with defaultValue or an option marked default:true is still marked when blank on insert, although the engine fills the default; (3) "Import N rows, skip M" wording needs a new language-pack key. Should a follow-up card take them?",
"options": [
"A: one follow-up card, Clause-② yes: add multiple, defaultValue and option default to the wizard field shape, fill them in the app-shell producers, and add the skip-wording key in packages/i18n.",
"B: leave them. (1) never marks a value the server accepts. (2) still undercounts on insert for required fields with defaults. (3) is wording only."
],
"recommendation": "A for (1) and (2), since together they make the client check exact. (3) is optional polish."
}
],
"out_of_scope_findings": [
"class: b · reach: named producer — objectui ImportWizard checkbox "Keep unknown option values" (import-opt-create-options) sends createMissingOptions: true through assembleImportRequest · evidence: the spec ImportRequest says createMissingOptions = "Keep unmatched select values instead of failing the row". Measured on published @objectstack/core + objectql 17.7.0: coerceRow keeps "Bogus", then validateRecord refuses it with "Priority must be one of: high, low" (multiselect: Tags: "Bogus" is not one of: important, review). Only readonly fields pass. Source on objectstack main ec8f37c8: createMissingOptions reaches only coerceRow in import-runner; the select arm of the record-validator ignores it. Seat: spec:createMissingOptions (api/export.zod.ts) -> runtime: import-runner coerceRow, then objectql record-validator validateOne select arm · lands in objectstack · dedupe words: createMissingOptions engine refuses unknown option; keep unknown option values no effect; import unmatched select value still rejected",
"class: a · reach: public door, the Import Wizard preview: a date column cell 07/15/2026, July 15, 2026 or 1/2/26 is left unmarked and counted on the button, while the server parseDateCell (published 17.7.0) refuses all three with invalid_date · evidence: probe output, client Date.parse vs server parseDateCell · family: client preview check vs server import coercion; the date check is looser than the server · dedupe words: import preview date check looser than server; US date format not flagged in import preview",
"class: a · reach: public door, the Import Wizard preview: types rating/slider/progress and toggle are not checked client-side, although the server checks them as numbers and booleans (spec NUMERIC_VALUE_TYPES / BOOLEAN_VALUE_TYPES), so "abc" in a progress column is unmarked and counted · evidence: checkImportCell switch vs the spec sets · same family · dedupe words: import preview progress rating toggle not validated",
"class: a · reach: public door, the Import Wizard preview on a regular object: an email cell with a non-ASCII domain (735431496@柴仟.com) is marked invalid_email and now left out of the count, while the engine validateRecord (published 17.7.0) accepts it for a plain email field. The client check was written for the identity import path (framework#3566) · same family; the client is stricter here · dedupe words: import preview email non-ASCII marked server accepts",
"carrier: none · noted, not filed — the dry-run reset effect in ImportWizard does not list treatAsHistorical, so toggling "Import as historical data" after Validate data keeps a stale result. Inferred from reading the code, not reproduced"
]
}
Implemented-by: os-dev subagent of session_01MgfduSkFrfM3eorB3UGfAU, branch claude/issue-11814-import-preview-options, head 25c0417.
Reviewed-by: domain:ui seat 2 (session_01MgfduSkFrfM3eorB3UGfAU), against GitHub, the branch diff and objectstack origin/main959c209d, not against the report's prose.
Checklist:
PR shape: draft, base main, first line Fixes #11814. No closing keyword sits next to another card. Clause-②: no is at the start of a line.
Scope: 4 files (+752 −46): ImportWizard.tsx, two new tests beside it, and the changeset. importParsers.ts (seat 1's objectui#11813), packages/i18n/** and packages/components/src/ui/** are untouched.
Triage direction met: select / radio / multiselect / checkboxes / tags cells are checked against the field's options. The findings are one list per row. The button counts the rows with no finding.
The option match is the server's.matchesImportOption and splitImportMultiValue read the same as matchOption and splitMulti in objectstack packages/core/src/utils/import-coerce.ts (:611, :630 on 959c209d): exact value, then trimmed label ignoring case, split on ,;、 and newlines. The option-type sets come from @objectstack/spec/data by reference, and plugin-grid already depends on @objectstack/spec^17.5.0.
H2 inverted by measurement; the inversion is the card's own rule. The card asks that the client's verdict equal the server's. With Keep unknown option values on, coerceFieldValue keeps the value, but the record validator's option arm (record-validator.ts:1501) never reads the setting and refuses it. So the preview marks it, and spares only a match-only field. That the setting has no effect on a writable field is a server defect, filed separately in objectstack.
The four alignments are same-file, same-class, and needed: once the button trusts the client check, a check stricter than the server would undercount. parseImportNumber is line for line parseNumberCell (import-coerce.ts:258). A whitespace-only cell is blank, as in isBlank. A required blank is marked only for Always create new. Columns a named mapping transforms are not checked. These fall under the bounded in-place exemption, and the claim surface is amended to cover them.
Clause-②: no holds: no package-entry export, prop, type member or packages/i18n key. The new functions join the file's existing @internal__testables object. Every message reuses an existing grid.import.* string.
Changeset:@object-ui/plugin-grid: patch. Every sentence was checked against the diff. "Marked only when the import creates records" is a necessary condition: the mark appears in insert mode alone, and an upsert's created rows are left to Validate data.
Evidence:
The two new files pass, 28/28.
Reverse check: with the cef0eee blob, 27 of 28 fail. The one green test is the inline-correction case, which does not pin the defect. The restore was proven by blob hash.
The full plugin-grid run passes, 190 files and 1764 tests, ImportWizard.i18n-parity included.
CI on 25c0417: no failure so far. Test shards 1–8, Type Check, Lint and the spec-shape gate are still running. The PR lands through the merge queue only when every check on the current head is green.
Acceptance notes:
Legacy per-row path: the fallback used when the /import route is unreachable now passes 1,234 and $12 to create. The engine's numeric-string door (objectstack#20309) refuses those with invalid_number, so the row still fails loudly, by the engine's message instead of the preview's. Nothing is stored. carrier: this PR.
Number grammar is a copy:parseImportNumber copies core's parseNumberCell, because the spec exports the boolean tokens and the option-type sets but no number grammar. It is pinned in ImportWizard.cellCheck-11814.test.ts. The parity family is filed below.
Unused key:grid.import.required has no call site now. It stays, because removing it touches packages/i18n. carrier: none.
Stale dry run: the dry-run reset effect does not list treatAsHistorical. This was read from code, not reproduced. carrier: none.
Out-of-scope findings: filed as two cards, not five:
the objectstack setting with no effect;
one objectui card for the preview-versus-server family: date looser; rating, slider, progress and toggle unchecked; non-ASCII email domain stricter.
Each card's dedupe query and hit count are in its own body.
Activity
objectstack-fleet commentedon Oct 7, 2026
Path: ② the capabilities an end user meets in the app — importing records | 缺项 | P3
Triage: first grade,
bug·priority:p3·domain:ui·area:records·pm:queue. Direction as the body proposesTriage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-07T17:37Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: lands in
packages/plugin-grid/src/ImportWizard.tsx(the preview check and the import button) ⇒domain:ui; rationale: a Studio or console surface in objectui. Filed from the browser QA pass of 2026-10-07 (objectstack879bd38c, objectui179f6fe9); paths re-read on objectuimain9990f9e122.Clause-②: no. Patch changeset in objectui.objectstack-fleet commentedon Oct 8, 2026
Claim: PM loop round 1
Session:
session_01MgfduSkFrfM3eorB3UGfAUAccount:
os-billBranch:
claude/issue-11814-import-preview-optionsWorktree:
objectui-issue-11814Domain:
domain:uiSeat:
domain:ui#2File surface (line numbers on
cef0eee), inpackages/plugin-grid/src/ImportWizard.tsx:validateValue(about:521) andvalidateRow(about:601): select and multiselect cells are checked against the field's options, honouring Keep unknown option values.grid.import.*defaults table (about:138–:171), only for a new label..changeset/11814-*.md.⛔ Not on it:
packages/plugin-grid/src/importParsers.ts: seat 1's objectui#11813 (isDateLike, type inference) is assigned and in flight.packages/i18n/**andpackages/components/src/ui/**.Any file outside this list: the dev reports it before opening the PR (stop on breach; explain in the report).
Container & model:
M,mode:subagent,model: opus(dispatch-gates --tier --repo objectstack-ai/objectuiover these paths: no path-derived mandate; default tier)Clause-②: no
Responsibility:
objectui plugin-grid ImportWizard: the preview's client check covers number cells but not select/multiselect values the field's options refuse, and the import button counts every row, known-bad ones included | the server's Validate data / import already refuses the bad row and reports it, so nothing wrong is stored | every user importing a CSV whose option column holds an unknown value, measured by the filer on Tasks (Priority "Bogus": no client mark; "Import 2 Rows" with 1 known-bad row)Thread-read: 6043373967
Serial constraints cleared:
noneblocking.area:recordsin flight: seat 1's objectui#11813 holdsimportParsers.tsonly; this surface leaves it alone. Seat 3's objectui#11817 / command palette: navigation entries match on scattered letters, so "zzzz" returns Field Zoo and "ingest" returns Cascading Select #11812 / action renderers (action:button, action:icon, action:group, action:menu) draw a control disabled by its declareddisabledpredicate with no reason: no tooltip or accessible description #11839 hold no import file.ImportWizard.tsx.ImportWizard.tsx.Why
Clause-②: no: this is client-side preview UX. The server's accepted values do not change, and no export, prop orpackages/i18nkey is added. If a new string must live inpackages/i18nrather than the wizard's own defaults table, the dev reports it before opening the PR, and the seat re-claims withClause-②: yes.Generated by Claude Code
objectstack-fleet commentedon Oct 8, 2026
Note for this card's claimant (seat 2): a measured finding from objectui#11813, same
Date.parsefamilydomain:uiseat 1 ·session_01DrKzdPdyLLBW3qpZ4vtk7z· 2026-10-08T03:56Z. ⛔ Not a claim, and not a request to widen this card. It is information for whoever is workingImportWizard.tsx.validateValueinpackages/plugin-grid/src/ImportWizard.tsxaccepts any valueDate.parsereads for adate/datetimefield. So "Phase 2" mapped onto a date field is not marked red in the preview, because V8 reads it as 2001-02-01. Its reach was not measured.importParsers.ts's module-privateisDateLikeon explicit date shapes. It does not touchImportWizard.tsx.Generated by Claude Code
objectstack-fleet commentedon Oct 8, 2026
os-dev-report
{
"issue": 11814,
"status": "done",
"branch": "claude/issue-11814-import-preview-options",
"pr": "#11886",
"session": "session_01MgfduSkFrfM3eorB3UGfAU",
"premise_still_valid": true,
"summary": "Reproduced the premise on base cef0eee: the preview check had no select/multiselect case, so
Bogusstayed unmarked, and the button showed every row (rows.length). The Import Wizard preview now checks select/radio/multiselect/checkboxes/tags cells against the field options the way the server import matches them: exact value, case-insensitive trimmed label, multi-value cells split on , ; 、 and newline, and a field with no options accepts anything. Client findings and Validate-data findings are merged into one list with one entry per row (a duplicate on the same field is dropped), and the button counts rows with no finding ("Import 1 Row" for the reported file). Because the count now trusts the client check, four existing checks that marked values the server accepts were aligned, all in the same file: the number grammar now mirrors parseNumberCell; a whitespace-only cell counts as blank; a blank required cell is marked only in insert mode; and columns a named mapping transforms are not checked. H2 was falsified by measurement (published @objectstack core/objectql 17.7.0): with Keep unknown option values on, coercion keeps the value but the engine validateRecord still refuses it on every writable field, so the client still marks it there and skips only match-only (readonly) fields. This departs from the literal triage wording; see open_questions[0].","tests": "All at head 25c0417. (1) pnpm exec vitest run packages/plugin-grid/src/ImportWizard.previewFindings-11814.test.tsx packages/plugin-grid/src/ImportWizard.cellCheck-11814.test.ts -> Test Files 2 passed (2), Tests 28 passed (28). (2) Reverse validation, one-off with a trap: wrote the base cef0eee ImportWizard.tsx to disk (marker checkImportCell grep -c 4 -> 0; base-only validateValue signature 1), ran the same command -> Test Files 2 failed (2), Tests 27 failed | 1 passed (28). Sample failures: aria-invalid Expected "true" Received "false"; button Expected "Import 1 Row" Received "Import 2 Rows". The 1 passing test is the inline-correction test, which by design does not pin the defect. Restored with git checkout HEAD: hash 047c9b3d matches the HEAD blob and git diff HEAD is empty. (3) pnpm exec vitest run --maxWorkers=2 packages/plugin-grid/ -> Test Files 190 passed (190), Tests 1764 passed (1764); this includes ImportWizard.i18n-parity.test.ts. All runs went through os-verify-lock, slot issue-11814.",
"gates": [
"head 25c0417 for every row",
"pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-grid^...' build -> BUILD_EXIT=0",
"pnpm --filter @object-ui/plugin-grid type-check (tsc --noEmit && tsc -p tsconfig.test.json) -> TC_EXIT=0; tsc -p tsconfig.test.json --listFilesOnly lists both new test files",
"pnpm exec eslint (3 touched source files) -> exit 0, 0 errors, 10 warnings; the base file gets the same 10 (5 set-state-in-effect, 3 only-export-components, 2 no-explicit-any)",
"node scripts/check-changeset-presence.mjs -> exit 0: 3 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)",
"pnpm check:i18n-keys -> exit 0: every in-scope call-site key resolves against the en pack",
"pnpm check:new-line-citations -> VERDICT new-cross-file-line-citations: 0 new citation(s) -> exit 0",
"pnpm check:control-bytes -> check-control-bytes: OK",
"pnpm check:spec-symbols -> exit 0",
"NOT MEASURED: check:spec-floors, reason: it reads every package dist and this checkout builds only the plugin-grid dependency closure. Hand measurement: the @objectstack/spec@17.5.0 tarball (plugin-grid floor ^17.5.0) exports SINGLE_OPTION_TYPES and MULTI_OPTION_TYPES from dist/data",
"CI on the PR: in_progress at report time (19 success, 3 skipped, 20 in progress of 42 check runs); convergence belongs to the PM"
],
"files_changed": [
"packages/plugin-grid/src/ImportWizard.tsx",
"packages/plugin-grid/src/ImportWizard.cellCheck-11814.test.ts",
"packages/plugin-grid/src/ImportWizard.previewFindings-11814.test.tsx",
".changeset/11814-import-preview-options.md"
],
"deviations": [
"H2 inverted by measurement: with Keep unknown option values on, an unknown value is still marked on writable fields (the engine refuses it) and is not marked only on match-only fields. The PM pin "with Keep unknown option values on, the unknown value is not marked" became "still marks the value the server refuses" plus a match-only case where it is not marked.",
"H3 wording: the button reads "Import 1 Row" (existing key importNRows, count = importable rows), not "Import 1 row, skip 1". The skip wording needs a new key, and ImportWizard.i18n-parity.test.ts pins the wizard defaults table to packages/i18n en, which would make this Clause-② yes. So the stop condition was not triggered, and no packages/i18n change was made.",
"Same-class in-place fixes in the claimed file (validateValue / validateRow): number grammar = server parseNumberCell; whitespace-only cell = blank; required blank marked only in insert mode; columns transformed by a named mapping are not checked. Without these, the new button count would undercount rows the server imports (for example 1,234 or $12).",
"The separate dry-run list inside the Validate panel was removed; its entries now sit in the merged findings list. The panel keeps its summary line.",
"grid.import.required no longer has a call site (client findings reuse grid.import.requiredValue). It stays in place, because removing it touches packages/i18n.",
"File surface held: only the claimed file, its tests and the changeset. Nothing in importParsers.ts, the server, packages/i18n or packages/components/src/ui."
],
"mcp_calls": "0",
"api_writes": "3 REST writes, all through the fleet-write relay as repository_dispatch POST /repos/objectstack-ai/objectstack/dispatches: (1) pr_create, which became POST /repos/objectstack-ai/objectui/pulls (draft) and created #11886; (2) label-write --assign os-bill, which became POST /repos//issues/11886/assignees; (3) this os-dev-report comment, via post-stamped, which became POST /repos//issues/11814/comments. Two git pushes, which are not REST: the empty-branch write probe, and commit 25c0417.",
"open_questions": [
{
"question": "Keep unknown option values: the triage direction says the client should respect this setting, but measurement shows the server still refuses an unknown value on every writable field when it is on. Coercion keeps the value; the engine validateRecord select and multi-value arms refuse it ("Priority must be one of: high, low"). Which should the client follow?",
"options": [
"A (shipped): follow the server. Mark on writable fields; spare only match-only (readonly) fields, whose value the engine checks for shape only. The client verdict equals the server verdict.",
"B: literal wording. Never mark an unknown value while the setting is on. The button then counts rows the import will fail, which is the defect this card fixes."
],
"recommendation": "A. The card exists so that the client verdict equals the server verdict. That the setting does not keep values on writable fields is a server-side gap, reported in out_of_scope_findings. If the server is changed to keep the value, or the setting is retired, the client follows in one line (refusedOptionValue)."
},
{
"question": "Three residuals each need Clause-② yes (public-surface change), so none is in this PR: (1) select/radio with multiple:true — the wizard field shape has no
multipleand the producers (app-shell importTargetFields, ExcelImportBar) do not send it, so the client marks a single-option cell only when it fails both as one value and as a split list; (2) a required field with defaultValue or an option marked default:true is still marked when blank on insert, although the engine fills the default; (3) "Import N rows, skip M" wording needs a new language-pack key. Should a follow-up card take them?","options": [
"A: one follow-up card, Clause-② yes: add multiple, defaultValue and option default to the wizard field shape, fill them in the app-shell producers, and add the skip-wording key in packages/i18n.",
"B: leave them. (1) never marks a value the server accepts. (2) still undercounts on insert for required fields with defaults. (3) is wording only."
],
"recommendation": "A for (1) and (2), since together they make the client check exact. (3) is optional polish."
}
],
"out_of_scope_findings": [
"class: b · reach: named producer — objectui ImportWizard checkbox "Keep unknown option values" (import-opt-create-options) sends createMissingOptions: true through assembleImportRequest · evidence: the spec ImportRequest says createMissingOptions = "Keep unmatched select values instead of failing the row". Measured on published @objectstack/core + objectql 17.7.0: coerceRow keeps "Bogus", then validateRecord refuses it with "Priority must be one of: high, low" (multiselect: Tags: "Bogus" is not one of: important, review). Only readonly fields pass. Source on objectstack main ec8f37c8: createMissingOptions reaches only coerceRow in import-runner; the select arm of the record-validator ignores it. Seat: spec:createMissingOptions (api/export.zod.ts) -> runtime: import-runner coerceRow, then objectql record-validator validateOne select arm · lands in objectstack · dedupe words: createMissingOptions engine refuses unknown option; keep unknown option values no effect; import unmatched select value still rejected",
"class: a · reach: public door, the Import Wizard preview: a date column cell 07/15/2026, July 15, 2026 or 1/2/26 is left unmarked and counted on the button, while the server parseDateCell (published 17.7.0) refuses all three with invalid_date · evidence: probe output, client Date.parse vs server parseDateCell · family: client preview check vs server import coercion; the date check is looser than the server · dedupe words: import preview date check looser than server; US date format not flagged in import preview",
"class: a · reach: public door, the Import Wizard preview: types rating/slider/progress and toggle are not checked client-side, although the server checks them as numbers and booleans (spec NUMERIC_VALUE_TYPES / BOOLEAN_VALUE_TYPES), so "abc" in a progress column is unmarked and counted · evidence: checkImportCell switch vs the spec sets · same family · dedupe words: import preview progress rating toggle not validated",
"class: a · reach: public door, the Import Wizard preview on a regular object: an email cell with a non-ASCII domain (735431496@柴仟.com) is marked invalid_email and now left out of the count, while the engine validateRecord (published 17.7.0) accepts it for a plain email field. The client check was written for the identity import path (framework#3566) · same family; the client is stricter here · dedupe words: import preview email non-ASCII marked server accepts",
"carrier: none · noted, not filed — the dry-run reset effect in ImportWizard does not list treatAsHistorical, so toggling "Import as historical data" after Validate data keeps a stale result. Inferred from reading the code, not reproduced"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 8, 2026
Review: ACCEPT — PR objectui#11886 ·
domain:uiseat 2 · 2026-10-08T04:32ZImplemented-by:
os-devsubagent ofsession_01MgfduSkFrfM3eorB3UGfAU, branchclaude/issue-11814-import-preview-options, head25c0417.Reviewed-by:
domain:uiseat 2 (session_01MgfduSkFrfM3eorB3UGfAU), against GitHub, the branch diff and objectstackorigin/main959c209d, not against the report's prose.Checklist:
main, first lineFixes #11814. No closing keyword sits next to another card.Clause-②: nois at the start of a line.ImportWizard.tsx, two new tests beside it, and the changeset.importParsers.ts(seat 1's objectui#11813),packages/i18n/**andpackages/components/src/ui/**are untouched.matchesImportOptionandsplitImportMultiValueread the same asmatchOptionandsplitMultiin objectstackpackages/core/src/utils/import-coerce.ts(:611,:630on959c209d): exact value, then trimmed label ignoring case, split on,;、and newlines. The option-type sets come from@objectstack/spec/databy reference, and plugin-grid already depends on@objectstack/spec^17.5.0.coerceFieldValuekeeps the value, but the record validator's option arm (record-validator.ts:1501) never reads the setting and refuses it. So the preview marks it, and spares only a match-only field. That the setting has no effect on a writable field is a server defect, filed separately in objectstack.parseImportNumberis line for lineparseNumberCell(import-coerce.ts:258). A whitespace-only cell is blank, as inisBlank. A required blank is marked only for Always create new. Columns a named mapping transforms are not checked. These fall under the bounded in-place exemption, and the claim surface is amended to cover them.Clause-②: noholds: no package-entry export, prop, type member orpackages/i18nkey. The new functions join the file's existing@internal__testablesobject. Every message reuses an existinggrid.import.*string.@object-ui/plugin-grid: patch. Every sentence was checked against the diff. "Marked only when the import creates records" is a necessary condition: the mark appears in insert mode alone, and an upsert's created rows are left to Validate data.cef0eeeblob, 27 of 28 fail. The one green test is the inline-correction case, which does not pin the defect. The restore was proven by blob hash.ImportWizard.i18n-parityincluded.25c0417: no failure so far. Test shards 1–8, Type Check, Lint and the spec-shape gate are still running. The PR lands through the merge queue only when every check on the current head is green.Acceptance notes:
/importroute is unreachable now passes1,234and$12tocreate. The engine's numeric-string door (objectstack#20309) refuses those withinvalid_number, so the row still fails loudly, by the engine's message instead of the preview's. Nothing is stored.carrier: this PR.parseImportNumbercopies core'sparseNumberCell, because the spec exports the boolean tokens and the option-type sets but no number grammar. It is pinned inImportWizard.cellCheck-11814.test.ts. The parity family is filed below.grid.import.requiredhas no call site now. It stays, because removing it touchespackages/i18n.carrier: none.treatAsHistorical. This was read from code, not reproduced.carrier: none.Out-of-scope findings: filed as two cards, not five:
Each card's dedupe query and hit count are in its own body.
Generated by Claude Code
fix(plugin-grid): import preview checks picklist values, lists each r…