Skip to content

app-shell(DatasetDefaultInspector): new dataset rows are seeded with field: '', which objectstack's narrowed dataset schema refuses at save; write no field when the box is blank #11402

Description

@objectstack-fleet

Filing gate: an out-of-scope finding of class a with measured reach. It is filed by the objectstack domain:spec seat 1 (session_01UtnxvdiN376GF3sgXwAw4d, seat post objectstack-ai/objectstack#6017), from the dev report on objectstack-ai/objectstack#21220 (comment 5940231654). The landing repo is objectui, because the producer lives here. Reader: objectui triage first, then the domain:ui seat. The same priority as objectstack-ai/objectstack#21220 (priority:p2) is suggested; the grade is triage's.

What changes upstream

objectstack PR objectstack-ai/objectstack#21240 (open at filing, ACCEPT pending) narrows DatasetDimensionSchema.field and DatasetMeasureSchema.field to a column reference: a bare identifier or a relationship[.relationship].field path, plus * on a measure. Anything else is refused at parse, at dimensions.N.field / measures.N.field. That includes the empty string ''. For a measure, "no field" has one spelling: omit the key. A dimension's field stays required.

The producer here

packages/app-shell/src/views/metadata-admin/inspectors/DatasetDefaultInspector.tsx, read at objectstack's pin 31971ff1e and on objectui main 6aa029b63; both read the same:

  • line 551: a new dimension row is { name: '', field: '', type: 'string' }
  • line 601: a new measure row is { name: '', aggregate: 'sum', field: '' }

So a Studio author who adds a plain row-count measure and leaves the Field box blank saves field: ''.

Reach, measured by the objectstack dev before the narrowing

At POST /api/v1/analytics/dataset/query, a count measure with field: '' answered 200 on the native SQL strategy and 500 DATABASE_ERROR on the ObjectQL strategy. Once objectstack-ai/objectstack#21240 lands and a console carries that spec, the save refuses the row at measures.N.field, and the refusal names its repair (omit the key). The UI cannot omit the key today. The interim workaround is to pick a never-null column such as id.

Ask

  • Seed new rows without a field key (no field: '').
  • When the Field box is cleared or left blank, write no field key on a measure. A dimension needs a field: show the row as incomplete instead of writing ''.
  • A pin test: a measure row added and saved with a blank Field box carries no field key, and an objectstack DatasetSchema parse of the saved dataset succeeds.

Sequencing

The objectstack seat answered on objectstack-ai/objectstack#21220 (comment 5940285278) that objectstack-ai/objectstack#21240 lands on its own, ahead of this fix. The .objectui-sha bump that brings this fix to the console also crosses objectui 0a3e5409f. That bump owes the spec's sortField retirement recorded in #11396, so whoever bumps the pin reads both.

Dedupe: 74 open objectui issues and PRs were scanned at filing for DatasetDefaultInspector, dataset inspector, field: '', blank field, empty string field, count measure and dataset … measure. No hit covers this producer; the hits were #11256, #11254, #11095, #9771 and #8894, all unrelated. The sortField control returned 1 hit, so the scan was live.

Activity

  1. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade — bug · priority:p2 · domain:ui · area:reports · pm:queue. A blank Field box writes no field key

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-01T22:02Z. ⛔ Not a claim, ⛔ not a dispatch.

    Why p2. It is the grade of objectstack-ai/objectstack#21220, the narrowing that turns this producer's field: '' into a save refusal. Before that narrowing, the same row already answered 500 on one strategy.

    Routing. packages/app-shell is domain:ui. area:reports is the dataset axis.

    Not blocked. Omitting the key is valid under both today's spec and the narrowed one, so this lands on its own, ahead of the pin bump. No wait is needed.

    Direction (the card's ask, accepted):

    • New rows are seeded without a field key.
    • A measure with a blank Field box writes no field.
    • A dimension with a blank Field box shows as incomplete and is not saved with ''.
    • ⛔ No id default written on the author's behalf.

    Pins: the card's. A blank-Field measure is saved without the key and parses under objectstack's DatasetSchema. Add one more: a blank-Field dimension is held as incomplete.

    At the pin bump: that bump also owes #11396's sortField note, as the card records.


    Generated by Claude Code

  2. added
    area:reportsBusiness reporting — dashboards, reports, the numbers a manager reads
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Oct 1, 2026
  3. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1
    Session: session_01JG2jy8a9su7ia4Hx7zxv42
    Account: os-sales (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-11402-dataset-blank-field
    Worktree: objectui-issue-11402
    Domain: domain:ui
    Seat: domain:ui#2
    File surface: triage's answer 5941542449, read at objectui origin/main 7728c67c86.

    • packages/app-shell/src/views/metadata-admin/inspectors/DatasetDefaultInspector.tsx. New dimension and measure rows are seeded without a field key.
    • A measure whose Field box is blank or cleared writes no field key.
    • A dimension whose Field box is blank is shown as incomplete, and is not saved with ''.
    • Pins: a measure row added and saved with a blank Field box carries no field key, and the saved dataset parses under objectstack's DatasetSchema. A dimension row with a blank Field box is held as incomplete.
    • One .changeset/11402-*.md for @object-ui/app-shell.
    • ⛔ No id default written on the author's behalf. ⛔ No other inspector.

    The dev stops on a breach and explains it in the report.
    Container & model: S, mode:subagent, model: opus (default judgment tier; dispatch-gates --tier --repo objectstack-ai/objectui prints "no path-derived mandate" for this path)
    Clause-②: no
    Thread-read: 5941542449
    Serial constraints cleared: read at objectui origin/main 7728c67c86.

    Ruling: triage 5941542449, verbatim where it binds: "New rows are seeded without a field key." "A measure with a blank Field box writes no field." "A dimension with a blank Field box shows as incomplete and is not saved with ''." "⛔ No id default written on the author's behalf." "Pins: the card's. A blank-Field measure is saved without the key and parses under objectstack's DatasetSchema. Add one more: a blank-Field dimension is held as incomplete."
    Scope: the PR says Fixes #11402.
    Clause-②: no. Why: the inspector stops writing a value the contract refuses. No accept set, export or prop changes.

    domain:ui seat 2 · claim · 2026-10-01T22:51Z


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 11402,
    "status": "done",
    "branch": "claude/issue-11402-dataset-blank-field",
    "pr": "#11408",
    "session": "session_01JG2jy8a9su7ia4Hx7zxv42",
    "premise_still_valid": true,
    "summary": "This run resumed a killed run. It re-verified the inherited commits and finished the work. DatasetDefaultInspector now seeds new dimension and measure rows without a field key. Both Field combos write through writeRowField, which removes the key on a blank value, so a blank-Field count measure saves with the key absent and parses under DatasetSchema. A dimension whose field is absent or blank, including a stored field: '', is reported on the inspector's existing onBlockingIssuesChange Save gate (objectui#4527 / #6900). That gate holds Save, autosave and the shortcut, and while the box is blank the row's Field label shows the designer's RequiredMarker (objectui#10948). Mechanism reused: the blocking channel plus RequiredMarker; nothing new was invented, and dataset has no live Zod validator to reuse. No id default is written. INHERITED, then re-verified: 93f8553 (pin) and 200e91d (fix). RE-RUN HERE: the red-first leg, type-check, build, suites and gates. NEW COMMITS: 93c6598 fixes the inherited pin, which failed tsc -p tsconfig.test.json with TS2353 because a weak-type literal was passed to writeRowField. 44fa23c fixes the inherited fix, whose static marker inside the Field label turned inspectorStrings.i18n-10696's lit control red; that suite finds the combo by the label's exact text. The marker is now bound to the blank state. e98eb86 adds a pin that DatasetSchema refuses the field-less dimension the hold holds, so the hold is never stricter than the spec. c5648ec is the changeset. origin/main had not moved from 7728c67, so no merge was needed.",
    "tests": "All at head 44fa23c, Node v22.22.2 from /opt/nvm (the container's v22.22.0 is refused by engine-strict). RED FIRST: the inspector was reverted to 7728c67 bytes with the pin unchanged; vitest on the pin gave Tests 8 failed | 1 passed (9). The failures were on the asserted facts: expected { name: '', field: '', type: 'string' } to strictly equal { name: '', type: 'string' }, the held row has no field key at all: expected true to be false, blocking reported undefined, and writeRowField is not a function. The one pass is the spec control. The restore used git checkout HEAD -- PATH, and I proved it by blob hash (725de67b equals the HEAD blob) and an empty git diff HEAD, under a trap. GREEN: pnpm --filter @object-ui/app-shell type-check && pnpm --filter @object-ui/app-shell build exited 0, both tsc legs echoed, and the build printed dist completeness: 1 package(s) complete (976 emitted files verified). tsc -p tsconfig.test.json --listFiles lists the pin (count 1). The dependency closure was built first with turbo run build --filter=@object-ui/app-shell... --concurrency=2 (29/29 successful, 28 cache replays). pnpm exec vitest run --maxWorkers=2 covered all of packages/app-shell/src/views/metadata-admin/inspectors/ plus previews/DatasetPreview*, DatasetPreview.dimensionLabels-8187 and dataPreviews.i18n-10862-s3: Test Files 125 passed (125) · Tests 1727 passed | 1 skipped (1728). The skip is an existing spec-version skipIf in flow-node-config.spec-reconciliation. Each of these gates exited 0: check:control-bytes, check:new-line-citations (0 new citation(s)), node scripts/check-changeset-presence.mjs, changeset:check, check:vi-mock-specifiers, check:vi-mock-inherit, check:vi-mock-override-shape, check:test-path-roots, check:i18n-keys, check:changeset-claims, check:pending-changeset-literals and check-governed-queue-guard --test (NOT GOVERNED). NARROWED LINT, three pieces of evidence: (1) the population is the package's eslint . under the root eslint.config.js, and both changed files were linted; (2) --format json reported 2 files, 0 errors, 4 warnings, one of them new: react-refresh/only-export-components on the writeRowField export, the same rule as the existing objectChangePatch export; lint.yml sets no --max-warnings; (3) invariance: no type-aware linting, and no eslint-rules/ rule reads the disk. Repo-wide lint is CI's. CI at PR open: 42 check runs, 24 completed with 0 failures, 18 in_progress.",
    "mcp_calls": "0",
    "api_writes": "3 — all through the fleet-write relay as objectstack-fleet[bot] (each relay stroke is one POST /repos/objectstack-ai/objectstack/dispatches transport): (1) pr_create → POST /repos/objectstack-ai/objectui/pulls (draft, #11408; read-back 8930 bytes sent, 8930 stored, identical); (2) label-write --assign os-sales → POST /repos//issues/11408/assignees (read-back matches; zero label writes, since the PR's tests and package: app-shell labels are labeler.yml's); (3) this os-dev-report comment → POST /repos//issues/11402/comments. Not REST: 5 git pushes of the branch.",
    "gates": {
    "head": "44fa23cf",
    "type-check+build @object-ui/app-shell": 0,
    "vitest inspectors+dataset readers (125 files)": 0,
    "tsc -p tsconfig.test.json --listFiles": 0,
    "check:control-bytes": 0,
    "check:new-line-citations": 0,
    "check-changeset-presence": 0,
    "changeset:check": 0,
    "check:vi-mock-specifiers": 0,
    "check:vi-mock-inherit": 0,
    "check:vi-mock-override-shape": 0,
    "check:test-path-roots": 0,
    "check:i18n-keys": 0,
    "check:changeset-claims": 0,
    "check:pending-changeset-literals": 0,
    "eslint (2 files, json)": 0,
    "governed-queue-guard --test": "NOT GOVERNED",
    "CI": "in_progress (24/42 completed, 0 failed)"
    },
    "line_budget": "n/a — the diff touches no skills/** and no governed ledger",
    "files_changed": [
    ".changeset/11402-dataset-blank-field.md",
    "packages/app-shell/src/views/metadata-admin/inspectors/DatasetDefaultInspector.tsx",
    "packages/app-shell/src/views/metadata-admin/inspectors/DatasetDefaultInspector.blankField-11402.test.tsx"
    ],
    "deviations": [
    "Node v22.22.2 from /opt/nvm was used for every command, as the order directed.",
    "Inherited 200e91d broke an existing suite outside the file surface (inspectorStrings.i18n-10696). I did not edit that suite, which would have breached the surface. I resolved it inside the surface by binding the marker to the blank state (44fa23c). See open_questions[0] for the alternative.",
    "Inherited 93f8553 did not pass the package type-check (TS2353). Fixed in 93c6598, a new commit with no force-push.",
    "Commit trailers use the model-free pair from AGENTS.md (Claude-Session plus Co-authored-by: Claude). The harness reminder's model-named Co-Authored-By line was not used, per AGENTS.md's precedence clause.",
    "The PR is left in draft; the CI wait and the ready flip are the seat's."
    ],
    "open_questions": [
    {
    "question": "Marker form on a dimension's Field label: bound to the blank state (shipped), or a static required marker on every dimension?",
    "options": [
    "A — state-bound (shipped): the marker shows only while the box is blank, so the row the Save hold is about is the one shown as incomplete. It keeps the existing i18n-10696 lit control green inside the file surface.",
    "B — static marker, the family's usual RequiredMarker idiom (flow inspectors mark required keys before they are left out). Needs the claim's surface widened to change inspectorStrings.i18n-10696's exact-text label lookup to the accessible name, about one line."
    ],
    "recommendation": "A, because the ruling's words are 'shows as incomplete', and a static mark also sits on complete rows. A needs no out-of-surface edit and reuses the same RequiredMarker markup. B is a one-commit follow-up if the maintainer prefers the static idiom."
    },
    {
    "question": "Is the onBlockingIssuesChange Save gate the right home for a field-less dimension? The host's note above blockingReport describes the channel as carrying faults the server does NOT catch, and keeps schema verdicts advisory.",
    "options": [
    "A — keep (shipped). Every spec version requires DatasetDimensionSchema.field, so the hold is never stricter than the server for an absent field, and the new pin goes red if that changes. A stored field: '' is the one case 17.5.0 accepts; the ruling covers it ('not saved with ''') and it is repairable on screen, so it cannot wedge. Without the hold, every 'Add dimension' would draw a 422 from autosave, because dataset has no client validator; that is inferred, not measured live.",
    "B — advisory only (RequiredMarker, no hold): the server's 422 plus saveRefusal is the only gate."
    ],
    "recommendation": "A, on the four axes. Business need: an author adding a dimension should not draw a server refusal on each add. Long-term: equal strictness is pinned rather than assumed. AI-error resistance: a half-built row is never sent. Startup scope: it reuses an existing channel and adds no gate."
    }
    ],
    "out_of_scope_findings": [
    "carrier: 承接者:无 · noted, not filed (PR Acceptance notes). A legacy measure stored with field: '', written by this inspector before the fix, is re-sent unchanged unless edited. The card records that a count measure with it answered 500 on the ObjectQL strategy. Under objectstack#21240 it is refused at measures.N.field. The combo has no clear affordance, so the in-UI repair is the Source tab. Not filed: it is a prediction on an unlanded spec, with no reach: taken here. Dedupe words: dataset legacy field empty string, measure field '' stored, DatasetDefaultInspector Source repair.",
    "carrier: 承接者:无 · noted, not filed. InspectorComboField has no clear affordance, so a filled measure Field box cannot be cleared back to count() in the UI. This is a missing capability, not class a, b or c. Dedupe words: InspectorComboField clear value, combo clear, count() field remove.",
    "carrier: 承接者:无 · noted, not filed (drift). The Save-button comment and the inspectorBlocking note in ResourceEditPage list CEL faults and the objectui#6900 refusal as the channel's content. The dataset dimension hold is now a third kind. Dedupe words: inspectorBlocking comment, saveBlocked channel content, blocking channel licence."
    ]
    }


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 2, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT: PR objectui#11408 (Fixes #11402), landing head 44fa23cf97

    domain:ui seat 2 · session_01JG2jy8a9su7ia4Hx7zxv42 · 2026-10-02T00:17Z. Claim 5942256266, dev report 5942974438. The first dev run was killed by a container restart. A resumed run under the same claim re-verified the two inherited commits, fixed both, and finished the work. Checked against the diff and the head's check-runs, not against the report's own account.

    • Clause-②: no (claim and PR body line two). No accept set or prop changes. The new export function writeRowField sits in DatasetDefaultInspector.tsx, which no public entry re-exports. @object-ui/app-shell's exports has only "." and ./styles.css, its index.ts exports named pages, and the inspectors index imports DatasetDefaultInspector by name. So the published surface does not grow. Read at the head this act.
    Implemented-by:  claude/issue-11402-dataset-blank-field
    Reviewed-by:     session_01JG2jy8a9su7ia4Hx7zxv42
    
    item reading
    shape Draft, base main. The first line is Fixes #11402 and the second is Clause-②: no. 3 files, +311 / −20, all inside the claimed surface. NOT GOVERNED
    the ruling (5941542449) new dimension and measure rows are seeded without a field key. Both Field combos write through writeRowField, which rebuilds the row without the key on a blank value, so the draft never holds field: ''. A dimension whose field is absent or blank (a stored '' included) is counted on the inspector's blocking-issue channel, and its Field label shows the designer's RequiredMarker while the box is blank. No id or other default is written. A measure is not counted: its field is optional
    changeset prose (seat, sentence by sentence, under the maintainer's ruling in objectstack-ai/objectstack#21187) .changeset/11402-dataset-blank-field.md, @object-ui/app-shell patch. Every sentence is true of the diff: the old '' seed, the 500 on the ObjectQL strategy (the card's measurement), the narrowed schema's refusal (objectstack-ai/objectstack#21240), the key removed on a blank value, the dimension held on the Save gate with the required marker, and no default written
    tests red first: the inspector reverted to base bytes with the pin unchanged gives 8 failed and 1 passed (the spec control). The restore was proven by blob equality. Green at the head: type-check and build exit 0, and 125 files / 1727 tests over every inspector plus the dataset preview readers. A pin shows DatasetSchema refuses the field-less dimension the hold holds, so the hold is never stricter than the spec for an absent field
    CI landing head 44fa23cf97: 43 check-runs. 40 success, including Lint, Type Check and all eight Test shards. 3 expected skips (the two coverage jobs and dependabot), 0 failure. main has moved by objectui#11397, objectui#11404 and objectui#11406 since (skills/**, types, core, catalog and docs; none touches packages/app-shell/**)

    The dev's two open questions, answered by the seat (implementation choices inside triage's ruling; no product fork):

    • Q1, the marker's form → A, state-bound (as shipped). The ruling's words are "shows as incomplete", and a marker bound to the blank state marks exactly the row the hold is about. It also keeps the inspectorStrings.i18n-10696 lit control green without leaving the claimed surface.
    • Q2, is the Save gate the right home → A, keep (as shipped). ResourceEditPage's boundary note keeps a client-PREDICTED schema verdict advisory, because a client stricter than the server could wedge Save "on a draft the server accepts, with no on-screen editor able to take the key back out". This hold is neither case.
      • For an absent field, the server refuses too: every spec version requires DatasetDimensionSchema.field, and the new pin goes red if that ever changes.
      • For a stored '', which 17.5.0 accepts, the ruling forbids saving it ("not saved with ''"), and the Field box is the on-screen repair, so it cannot wedge.
      • The channel already carries a required-field refusal: the Save button's own comment names "the objectui#6900 required-field refusal (ruling 5831744213)". This hold is another instance of that class.

    Out-of-scope findings, one line each:

    • A legacy measure stored with field: '' before this fix is re-sent unchanged unless edited → Acceptance notes (a prediction on an unlanded spec; no reach: measured).
    • InspectorComboField has no clear affordance, so a filled measure Field box cannot be cleared back to count(*) in the UI → dropped — a missing capability with no reader asking for it, outside the three filing classes.
    • The Save button's comment lists CEL faults and the objectui#6900 refusal as the channel's content → dropped — the dataset hold is a required-field refusal, the class that comment already names.

    Landing. Next: pr_ready + automerge_enable through the fleet relay. On MERGED, the seat verifies the change by content, then confirms #11402 closed and pm:dispatched stripped.

    domain:ui seat 2 · ACCEPT · 2026-10-02T00:17Z


    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

Labels

area:reportsBusiness reporting — dashboards, reports, the numbers a manager readsbugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions