From 922ef356c6ccde29c52a79996fee7649e93b0691 Mon Sep 17 00:00:00 2001 From: os-zhuang Date: Wed, 17 Jun 2026 13:35:00 +0800 Subject: [PATCH] chore(showcase): seed Field Zoo with all field types + guard budget hook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .changeset/showcase-field-zoo-seed.md | 4 +++ examples/app-showcase/src/data/index.ts | 45 +++++++++++++++++++++++- examples/app-showcase/src/hooks/index.ts | 6 +++- 3 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 .changeset/showcase-field-zoo-seed.md diff --git a/.changeset/showcase-field-zoo-seed.md b/.changeset/showcase-field-zoo-seed.md new file mode 100644 index 0000000000..1126f98ed5 --- /dev/null +++ b/.changeset/showcase-field-zoo-seed.md @@ -0,0 +1,4 @@ +--- +--- + +chore(showcase): seed the Field Zoo with two specimens exercising every input-able field type (arrays/objects/time/relational/computed all populated), and guard the `warn_over_budget` hook condition with `has()` so a partial rollup update no longer logs a "No such key" warning. The seed doubles as a runtime regression guard for the field-type persistence fixes — a field type that can't store now fails the boot seed instead of shipping silently. Example-app only; no package impact. diff --git a/examples/app-showcase/src/data/index.ts b/examples/app-showcase/src/data/index.ts index d448fc1a22..9bc94f852c 100644 --- a/examples/app-showcase/src/data/index.ts +++ b/examples/app-showcase/src/data/index.ts @@ -8,6 +8,7 @@ import { Task } from '../objects/task.object.js'; import { Category } from '../objects/category.object.js'; import { Team, ProjectMembership } from '../objects/team.object.js'; import { Product } from '../objects/invoice.object.js'; +import { FieldZoo } from '../objects/field-zoo.object.js'; /** * Seed data sized to "feed every view": every Kanban column is populated, @@ -100,4 +101,46 @@ const memberships = defineSeed(ProjectMembership, { ], }); -export const ShowcaseSeedData = [accounts, products, projects, tasks, categories, teams, memberships]; +// Field Zoo specimens — one record per "look" exercising EVERY input-able +// field type with a real value. This is both a demo (the gallery finally +// renders populated) and a regression guard: the seed inserts at boot, so a +// field type that can't persist (e.g. the array-serialization / `time` bugs +// found here) makes the app fail to start instead of silently shipping broken. +// Relational/computed fields (lookup/master_detail/tree/record-map, formula/ +// summary/autonumber) resolve or generate at runtime. `f_master_detail` is the +// owning Project; `f_lookup` the Account — referenced by their seed externalId. +const fieldZoo = defineSeed(FieldZoo, { + mode: 'upsert', + externalId: 'name', + records: [ + { + name: 'Specimen — Full', + f_textarea: 'Line one\nLine two', f_email: 'zoo@example.com', f_url: 'https://objectstack.ai', + // NOTE: `f_secret` (encryption-at-rest) is intentionally omitted — the + // seed path has no CryptoProvider, so a secret value is (correctly) + // refused fail-closed. The field type is still covered by the schema. + f_phone: '+1 555 010 2030', f_password: 'hunter2', + f_markdown: '# Heading\n\n- a\n- b', f_html: 'bold & italic', f_richtext: '

Rich text

', + f_number: 420, f_currency: 1299.95, f_percent: 75, + f_date: '2026-06-17', f_datetime: '2026-06-17T14:30:00Z', f_time: '14:30', + f_boolean: true, f_toggle: true, + f_select: 'high', f_multiselect: ['red', 'green'], f_radio: 'yes', f_checkboxes: ['email', 'push'], f_tags: ['alpha', 'beta'], + f_lookup: 'Northwind', f_master_detail: 'Website Relaunch', + f_location: { lat: 47.6062, lng: -122.3321 }, f_address: { street: '1 Main St', city: 'Seattle', state: 'WA', postal_code: '98101', country: 'US' }, + f_code: '{\n "ok": true\n}', f_json: { nested: { k: 'v' }, list: [1, 2, 3] }, f_color: '#2563EB', + f_rating: 4, f_slider: 60, f_progress: 80, + f_composite: { width: 10, height: 20 }, f_repeater: [{ label: 'one', qty: 1 }, { label: 'two', qty: 2 }], + f_record: { primary: { name: 'A', score: 9 }, backup: { name: 'B', score: 7 } }, + f_vector: [0.12, 0.34, 0.56, 0.78], + }, + { + name: 'Specimen — Minimal', + f_number: 7, f_select: 'low', f_radio: 'no', f_time: '09:05:30', + f_multiselect: ['blue'], f_checkboxes: ['sms'], f_tags: [], + f_boolean: false, f_rating: 2, f_slider: 0, f_progress: 0, + f_master_detail: 'Data Platform', + }, + ], +}); + +export const ShowcaseSeedData = [accounts, products, projects, tasks, categories, teams, memberships, fieldZoo]; diff --git a/examples/app-showcase/src/hooks/index.ts b/examples/app-showcase/src/hooks/index.ts index 4e00716266..35fbd448d1 100644 --- a/examples/app-showcase/src/hooks/index.ts +++ b/examples/app-showcase/src/hooks/index.ts @@ -63,7 +63,11 @@ export const WarnOverBudgetHook = { label: 'Warn On Over-Budget Project', object: 'showcase_project', events: ['afterUpdate'] as LifecycleEvent[], - condition: "record.spent > record.budget", + // Guard with has(): an afterUpdate fired by a partial write (e.g. the + // task-rollup that only touches task_count) carries a record WITHOUT + // spent/budget, and CEL throws "No such key" on a bare `record.spent`. + // has() is the missing-key-safe macro — the hook simply skips those. + condition: "has(record.spent) && has(record.budget) && record.spent > record.budget", body: { language: 'js' as const, source: "var r = ctx.result || ctx.input || {}; if (typeof ctx.log === 'function') ctx.log('project over budget: ' + (r.name || r.id || 'unknown') + ' (' + r.spent + ' / ' + r.budget + ')');",