Skip to content

fix: array field types persist + Field.time accepts time-of-day#2004

Merged
os-zhuang merged 1 commit into
mainfrom
fix/field-type-persistence
Jun 17, 2026
Merged

fix: array field types persist + Field.time accepts time-of-day#2004
os-zhuang merged 1 commit into
mainfrom
fix/field-type-persistence

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Two field-type runtime gaps found driving app-showcase's field-zoo end-to-end. The showcase's coverage test is green because it asserts every field type is declared — but field-zoo has no seed data, so no field type was ever exercised with a real value. Both bugs are pure "passes build / silent at runtime."

1. Array/object field types broke every write (driver-sql)

multiselect / checkboxes / tags / repeater / vector were missing from the SQL driver's JSON-field classification. Their array values reached the better-sqlite3 binder un-serialized:

500 — SQLite3 can only bind numbers, strings, bigints, buffers, and null

This hit common field types on insert and update — even task.labels on an ordinary object (labels: ['red','blue'] → 500). Root cause: the DDL column-type switch and isJsonField had drifted into two separate type lists; the array types were in neither.

Fix: one shared JSON_COLUMN_TYPES source used by both the DDL switch and isJsonField, now covering the array/object types (so the columns are created as JSON and round-trip as arrays/objects). Plus a formatInput safety net that serializes any stray array/object value, so an unclassified field degrades to a stored string instead of a hard 500.

2. Field.time rejected every valid value (objectql)

The validator reused the date/datetime branch (Date.parse), which is NaN for any bare time string — so a time field could never accept 14:30 or 09:05:30.

Fix: time now validates a time-of-day (HH:MM / HH:MM:SS, optional fractional seconds + Z/offset) and still accepts a full ISO datetime; date/datetime unchanged. (Date.parse('14:60') returns a bogus number in Node, so the lenient fallback requires a real date component — malformed times like 14:60/25:00 are correctly rejected.)

Verification

  • Live app-showcase: the full field-zoo specimen (all input-able field types) now persists and round-trips — multiselect/tags/checkboxes/repeater/vector come back as arrays/objects, time as 14:30. task.labels array persists.
  • Tests: new sql-driver-array-fields.test.ts (round-trip + update + empty-array) → @objectstack/driver-sql 155 green; time-validator cases (accept HH:MM/HH:MM:SS/ISO, reject malformed, no date/datetime regression) → @objectstack/objectql 652 green.

Follow-ups (separate, showcase-side)

  • field-zoo + invoice ship no seed data — adding a seed specimen would demonstrate every field type and guard these fixes. (Next PR.)
  • showcase_warn_over_budget hook references a nonexistent record.spent field (fires a "no such key" warning). (Next PR.)

🤖 Generated with Claude Code

Two field-type runtime gaps found driving the showcase field-zoo (which had
no seed data, so neither was ever exercised at runtime).

1. Array/object fields broke every write (driver-sql). multiselect/checkboxes/
   tags/repeater/vector were missing from the SQL driver's JSON-field
   classification, so their arrays reached the better-sqlite3 binder
   un-serialized → "SQLite3 can only bind numbers, strings, bigints, buffers,
   and null" 500 on insert/update (incl. task.labels on a normal object). The
   DDL column-type switch and isJsonField had drifted into two lists; unified
   into one JSON_COLUMN_TYPES source covering the array/object types, plus a
   formatInput safety net that serializes any stray array/object so an
   unclassified field degrades to a stored string instead of crashing.

2. Field.time rejected every valid value (objectql). The validator reused the
   date/datetime Date.parse branch, which is NaN for any bare time string, so
   `time` could never accept 14:30 / 09:05:30. Now validates HH:MM / HH:MM:SS
   (optional fractional seconds + Z/offset), still accepts a full ISO datetime;
   date/datetime unchanged. (Date.parse('14:60') returns a bogus number, so the
   fallback requires a real date component — malformed times are rejected.)

Verified live on app-showcase: full field-zoo specimen now persists +
round-trips. Tests: driver-sql array round-trip (155 green) + time validator
(objectql 652 green).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jun 17, 2026 5:20am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/objectql, @objectstack/driver-sql.

19 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/concepts/core/plugins.mdx (via @objectstack/driver-sql)
  • content/docs/concepts/core/services.mdx (via @objectstack/objectql)
  • content/docs/concepts/implementation-status.mdx (via @objectstack/objectql, @objectstack/driver-sql)
  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/objectql)
  • content/docs/concepts/packages.mdx (via @objectstack/objectql)
  • content/docs/concepts/terminology.mdx (via @objectstack/driver-sql)
  • content/docs/getting-started/glossary.mdx (via @objectstack/driver-sql)
  • content/docs/guides/authentication.mdx (via @objectstack/objectql)
  • content/docs/guides/deployment-vercel.mdx (via @objectstack/objectql)
  • content/docs/guides/driver-configuration.mdx (via @objectstack/driver-sql)
  • content/docs/guides/formula.mdx (via packages/objectql)
  • content/docs/guides/kernel-services.mdx (via @objectstack/objectql)
  • content/docs/guides/objectql-migration.mdx (via @objectstack/objectql)
  • content/docs/guides/packages.mdx (via @objectstack/objectql, @objectstack/driver-sql)
  • content/docs/guides/plugins.mdx (via @objectstack/objectql)
  • content/docs/protocol/objectos/index.mdx (via @objectstack/objectql, @objectstack/driver-sql)
  • content/docs/protocol/objectos/lifecycle.mdx (via @objectstack/driver-sql)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/objectql)
  • content/docs/releases/v9.mdx (via @objectstack/objectql)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Jun 17, 2026
@os-zhuang
os-zhuang merged commit bfa3102 into main Jun 17, 2026
14 checks passed
@os-zhuang
os-zhuang deleted the fix/field-type-persistence branch June 17, 2026 05:26
os-zhuang added a commit that referenced this pull request Jun 17, 2026
…ook (#2005)

The Field Zoo declared all 48 field types but shipped NO seed data, so no
field type was ever exercised with a real value — which is exactly why the
array-serialization and Field.time bugs (fixed in #2004) went unnoticed.

- Seed two field-zoo specimens ("Full" + "Minimal") covering every input-able
  field type with real values: arrays (multiselect/checkboxes/tags/repeater/
  vector), objects (composite/address/location/json/record), time (14:30 /
  09:05:30), relational (lookup→Account, master_detail→Project by externalId),
  and computed (formula=315, autonumber). `f_secret` is intentionally omitted —
  the seed path has no CryptoProvider so a secret value is refused fail-closed.
  This doubles as a runtime regression guard: a field type that can't persist
  now fails the boot seed instead of shipping silently.
- Guard the showcase_warn_over_budget hook condition with has() — a partial
  rollup update (task_count recompute) fires afterUpdate with a record lacking
  spent/budget, and CEL throws "No such key" on a bare record.spent. has() is
  the missing-key-safe macro; the noisy warning is gone.

Verified live: both specimens seed (37 populated f_* fields on Full); coverage
test 20 green; warn_over_budget "No such key" warnings = 0 after a task update.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
os-zhuang added a commit that referenced this pull request Jun 18, 2026
…thoring) (#2022)

Extends the dogfood gate from "pin one example app" toward the platform's real
risk: third parties have an AI author arbitrary metadata, so every authorable
primitive needs a runtime proof — not a static shape check.

`showcase_field_zoo` carries one field of (almost) every protocol FieldType but
was only static-checked (the bundle registers it). This adds the first
capability-matrix block: write one record covering many field types over the
real REST data API, read it back, assert each round-trips with type fidelity.

- Headliner coverage = the #2004 regressions: array-typed fields
  (multiselect/checkboxes/tags) and Field.time (time-of-day) — all proven to
  round-trip end-to-end on the in-memory (WASM) driver path `objectstack dev`
  uses for :memory:.
- On first run it surfaced three real type-fidelity gaps — rating/slider/toggle
  persist but read back as strings ('4'/'25'/'1') while number/currency/percent/
  boolean round-trip correctly. They are quarantined with `it.fails` (passes
  while broken, turns RED when fixed) rather than hidden by coercion, and filed
  as a separate task.

Private package — no changeset. 21 passed + 3 expected-fail.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
os-zhuang added a commit that referenced this pull request Jun 25, 2026
fix(console): route org-less users into guided org creation on multi-org (#2004)

objectui@9130b33ec35ac79bcd0da2618be4806e49ac2475
os-zhuang added a commit that referenced this pull request Jun 26, 2026
…ad (SQLite) (#2347)

Field.time is a tz-naive time-of-day, not an instant (#2004). A
defaultValue:'NOW()' time column historically took the full SQLite
CURRENT_TIMESTAMP default, so a defaulted/legacy row read back a full
'YYYY-MM-DD HH:MM:SS' timestamp instead of a time-of-day.

formatOutput now repairs a Field.time value to just its time portion (toTimeOnly):
a legacy full timestamp (or a full ISO value that leaked into the column) is
sliced to HH:MM[:SS[.fff]], while a value already stored as a bare time-of-day is
left untouched. Deliberately NARROW + read-only (no write/filter counterpart) so
it introduces no write/read asymmetry and preserves exact round-trips for bare
time-of-day values (the field-zoo f_time guard). Runs for every dialect (a native
TIME column already returns a time-of-day → no-op). Adds a timeFields registry +
registration mirroring dateFields/datetimeFields.

Completes the temporal read normalization alongside #2346: datetime -> ISO-8601-Z,
date -> YYYY-MM-DD, time -> wall-clock time-of-day.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant