Repository navigation
Commit 041c8cf
fix(spec): describe
Fixes #19679
Clause-②: no
One published sentence described `FieldSchema.format` as `Format string
(e.g. email, phone)`: two example words, with no field type and no
reader named. It shipped verbatim into `packages/spec/json-schema/`,
into `dist`, into the published `src/**/*.zod.ts`, and to a customer in
`content/docs/references/data/field.mdx`. On an `autonumber` field the
same key is the record-number pattern, so following the documented
example there minted `email1`.
This round fixes the sentence and nothing else. The key is still
`z.string().optional()`: nothing is split, narrowed, retired or gated by
type, and no consumer is touched.
## What reads the key, measured and stated positively
Round 2 rewrote this section. Round 1 listed two readers and a `nothing
else` row. The at-tier review found a third reader at the pinned
objectui, so this table lists what each measured reader does and makes
no claim that the list is complete.
| reader | field types | what the value means there |
|:--|:--|:--|
| `resolveAutonumberFormat`
(`packages/spec/src/data/autonumber-format.ts:196`), minted through by
`packages/objectql/src/engine.ts` `applyAutonumbers` (`:5051`) and twice
by `packages/drivers/driver-sql/src/sql-driver.ts` (`:10639`, `:10744`)
| `autonumber` | the record-number **pattern**: canonical
`autonumberFormat` first, then this key, then the default `{0000}` |
| `lintAutonumberFormats`
(`packages/lint/src/lint-autonumber-formats.ts:60-62`), wired into `os
lint`, `os compile` and `os validate` through
`packages/lint/src/authoring-rules.ts` | `autonumber` | the same
pattern, linted at build time for unrecognised `{...}` tokens and bad
`{field}` references |
| objectui `DateCellRenderer` and `DateTimeCellRenderer`
(`packages/fields/src/index.tsx:1127`, `:1176`) at the pinned
`.objectui-sha` `87af769e9a3ee28ace099fdd653d3ebd79fe82e2` | `date`,
`datetime` | a display **style**, with its own words and a different
default per type |
| objectui `resolveCellRendererType`
(`packages/fields/src/index.tsx:2915-2947`), same pin; called from the
grid (`plugin-grid/src/cellRendererResolution.ts:114`), the detail views
(`DetailSection.tsx:411`, `DetailView.tsx:1298`,
`HeaderHighlight.tsx:137`, `RelatedList.tsx:1282`), and the kanban,
gallery, report and dashboard views | textual base types (`text`,
`textarea` and the rest of `TEXTUAL_BASE_TYPES`) | a cell-renderer
**hint**: a small word set promotes the cell to a richer renderer.
Pinned end to end by
`packages/plugin-grid/src/__tests__/formatHintedColumnRenderer-8920.test.tsx`,
where `{ type: 'text', format: 'phone' }` renders a `tel:` anchor |
| objectui `renderFieldValue`
(`packages/plugin-dashboard/src/recordFields.tsx:382-427`), same pin,
fed the object field's `format` at `:334` | any | a display **pattern**:
a leading currency symbol, a `%`, or any of the letters `Y`, `M`, `D`,
`H`, `m`, `s` selects a formatter, and anything else falls through to
`resolveCellRendererType` |
Run against this branch's build of `@objectstack/spec`
(`dist/data/index.mjs`), seq 1:
```text
format: 'INV-{0000}' -> INV-0001
format: 'email' -> email1
{ autonumberFormat: 'A-{000}', format: 'email' } -> A-001
{} -> 0001
```
So an author who followed the key's own documentation onto an
`autonumber` field got `email1` as a business identifier. It parsed, it
stored, and neither the runtime nor the build-time lint reported it:
`lintAutonumberFormats` returns no finding for `format: 'email'`, while
a `{nope}{000}` control draws `autonumber-references-unknown-field`.
**The negatives the description keeps, each with its radius:**
- *On any non-`autonumber` field the server does not act on the key.*
Radius: this repo's `packages/**` non-test sources at `a8c7f17751`.
Instrument: `git grep` over every non-call property read of `format`
(107 hits). Triaged, the only field-definition reads are
`autonumber-format.ts:199` and `lint-autonumber-formats.ts:62`, both on
the autonumber arm. A word census over `packages/drivers` and
`packages/objectql/src` finds every field-reading line gated on the
autonumber type (`sql-driver.ts:10638`, `:10743`; `engine.ts:5044`).
Firing control: the same instrument returns the two known readers. Named
blind spots: multi-line destructuring, computed keys, and whole-object
forwarding. Corroboration: `driver-sql`'s own `FIELD_KEY_STORAGE_CLASS`
classifies `format` as `presentation`
(`packages/drivers/driver-sql/src/builtin-column-collision.ts:97`).
- *The write-time record validator never reads it.* Radius:
`packages/objectql/src/validation/record-validator.ts`. `def.format` has
0 reads, against `def.type` 7 and `def.maxLength` 2 as lit controls.
`const t = def.type` is at `:628`, and the email, url and phone checks
at `:746`, `:749` and `:752` test `t`.
- *The spec checks nothing but that it is a string.* 49 `FieldType`
members times 6 values (`email`, `phone`, `url`, `relative`, an
arbitrary string, `INV-{0000}`) gives 294 cells, run through both
`FieldSchema` and `ObjectSchema`: 0 issues on a `format` path. Firing
control: `format: 123` draws 1 issue on each.
- *The two named objectui arms fall back silently.* Read at the pin: the
date cell hands the word to `formatDate`
(`packages/core/src/utils/date-display.ts:198`), which honours `short`
and `relative` and otherwise paints its default face. The datetime cell
handles `relative`, `short` and `compact` and hands any other word to
`formatDateTime`, which paints its default face (`index.tsx:1243-1282`).
`resolveCellRendererType` returns the base type's renderer for a word
outside its map (`:2943-2946`). None of the three warns or throws.
**Where a value check lives**, which the description now states: the
record validator's email, url and phone checks key on the field **type**
(`type: 'email'`), and the closed `email | url | phone | json`
vocabulary belongs to a **`format` validation rule**
(`FormatValidationSchema`,
`packages/spec/src/data/validation.zod.ts:204`, its `format` key at
`:214`). The description no longer says `email`, `url` and `phone` are
"not formats". On a plain-text field objectui reads them as renderer
hints, and whether that reading should become a declared vocabulary is
the decision this card leaves open.
## Write surface
`packages/spec/src/data/field.zod.ts` — the one `describe` string.
Everything else in the diff is the repo's own generators: `pnpm --filter
@objectstack/spec gen:docs` rewrote
`content/docs/references/data/field.mdx`, `data/object.mdx` and
`system/migration.mdx` (three projections of one string). Nothing was
hand-edited under `content/docs/references/`.
**The conditional fence did not trip.**
`packages/spec/authorable-surface/data.json` is held by open PR #19618.
`check:authorable-surface` ran green across the edit and the file did
not move — a description is not an authorable key — so no hunk under
another claim was touched.
## Changeset — measured, not assumed
`patch` on `@objectstack/spec`. `skip-changeset` would have been wrong,
and the measurement is the reason rather than a rule of thumb:
- `packages/spec/src/data/field.zod.ts` is itself a published file — it
matches `src/**/*.zod.ts` in this package's `files[]`.
- Greping a distinctive fragment of the new text (`record-number
PATTERN`) over each `files[]` entry: **22** `dist/` files and **13**
`json-schema/` files carry it.
- Positive control from the same source — `required`'s existing
published describe (`Write-time contract (ADR-0113)`) — returns **22**
and **13** over the same two trees. Same counts, so the route is
measuring what it claims to measure.
## Is a regression test owed? No, and here is the reasoning rather than
a silence
- **A negative pin is impossible here.** The obvious pin — assert the
description never says `email` or `phone` — goes red on the
*correction*, because the new sentence deliberately names both words in
order to redirect the author to the field `type` and the validation
rule. The defect was a false sentence, not the presence of two words.
- **A positive pin on this prose would rot by design.** The next honest
sharpening of the sentence breaks it, so the next author edits or
deletes the pin — a check nobody trusts is worse than none, and this
lane's rule prefers deleting the construct that permits the error over
adding a check.
- **The construct that permits the error is out of scope this round.**
It is `z.string()` with no declared vocabulary and no type gating —
narrowing it is exactly the contract-shape decision triage reserved.
- **Every behaviour claim the new sentence makes is pinned, but not all
of it in this repository.** The autonumber claims are pinned here by
`packages/spec/src/data/autonumber-format.test.ts`:
canonical-over-shorthand precedence, the `{0000}` default rendering as
`0001`, and the no-slot branch. The two objectui arms are pinned in
objectui at the pin:
`packages/fields/src/__tests__/datetimeCell.formatVocabulary-8853.test.tsx`
for the date and datetime styles, and
`packages/plugin-grid/src/__tests__/formatHintedColumnRenderer-8920.test.tsx`
for the plain-text hint. The description names those arms without
copying their words, so an objectui change to a word list cannot make
the spec sentence false. An objectui change that removes an arm could,
and nothing in this repository would go red. That residual risk is why
the arms are introduced as examples.
- What *would* earn its place is a gate asserting that every example
value a `describe` offers is honoured by some reader. Nothing like it
exists, building it is well outside a one-sentence repair, and it
belongs with the decision below.
## Verification
Final commit `a8c7f17751`. No merge of `origin/main` this round:
`origin/main` is one commit ahead (`ed4b655e5b`, `scripts/pm/**` only)
and touches none of this PR's five files, so `os-regen-merge.sh` was not
needed. The branch still carries round 1's merge of `dc1b98680b`.
| check | result at `a8c7f17751` |
|:--|:--|
| `pnpm --filter @objectstack/spec build` (under
`scripts/pm/os-verify-lock.sh`) | `VERDICT command-exit 0` |
| `pnpm --filter @objectstack/spec typecheck`, then `test`, in one
locked run | `VERDICT command-exit 0`; 516 test files, 15056 tests
passed, 1 todo |
| `gen:schema`, then `gen:docs` | rewrote exactly `data/field.mdx` (1
row), `data/object.mdx` (2 rows) and `system/migration.mdx` (2 rows) |
| `pnpm --filter @objectstack/spec check:docs` | exit 0: `225 generated
files in sync with packages/spec` |
| `pnpm --filter @objectstack/spec check:generated` | exit 0: `All 15
generated artifacts are up to date` |
| sentence census | the new sentence appears in `field.mdx` 1 time,
`object.mdx` 2 times and `migration.mdx` 2 times; the round-1 sentence
and the original sentence appear 0 times in each. `git diff dc1b986
HEAD -- content/docs/references` is the five `format` rows and nothing
else |
| every family `scripts/pm/dispatch-gates.mjs` derives for this path
set, run from its own `--commands` list with each exit recorded |
`--ran`: 101 derived, 98 run at exit 0, 3 NOT MEASURED, 0 unrun |
| eslint, narrowed | 1 file linted (`field.zod.ts`): 0 errors, 0
warnings, read from `--format json`. The `.mdx` pages and the
`.changeset/*.md` answer `File ignored because no matching configuration
was supplied`. `eslint.config.mjs:327-329` records that type-aware
linting is enabled for no file, so this diff cannot move the verdict on
an untouched file |
**NOT MEASURED, declared to CI:** `check:skill-examples`,
`check:dual-build-cjs-loads` and `check:lean-entry-closure` exited 3
(`PREREQUISITE NOT MET`). Each reads built output of packages this diff
does not touch: `client-react`, the whole workspace, and `objectql`. Not
a pass and not a finding.
**The red at `66cb68d947` was an intermediate head.** That push carried
the new `describe` before its regenerated pages. `875c198d2c` added
them, but its run was superseded by the next push before the docs-sync
step ran (`Type Check · source gates` cancelled, step 26 `Check
generated reference docs are in sync with the spec` skipped), so that
head is **not measured**. At the current head `a8c7f17751` the same step
reads `success`.
## Acceptance notes
**1. The key-vocabulary decision card triage said was owed does not
exist yet.** Triage graded this card "scoped to the `describe`" and said
the wider problem "belongs in the decision box as its own card" —
nothing has been filed, and this round deliberately does not file it.
The readings below are recorded so whoever files it starts from
measurements instead of from scratch.
The shape of the decision: **one `z.string()` key is read by at least
four readers with four different vocabularies (the autonumber pattern,
the date and datetime style, the textual renderer hint, and the
dashboard display pattern), with no declared value set and nothing that
checks the readers agree.**
- **Autonumber arm.** `resolveAutonumberFormat`
(`packages/spec/src/data/autonumber-format.ts:196`);
`AutonumberFormatSource` declares `format` as the shorthand predating
`autonumberFormat`; call sites `packages/objectql/src/engine.ts`
`applyAutonumbers` and `packages/drivers/driver-sql/src/sql-driver.ts`
(two). A pattern-less value renders as literal text plus the bare
counter — measured `email1` above — and nothing refuses it.
- **Display-style arm** (objectui, pinned `.objectui-sha`
`87af769e9a3ee28ace099fdd653d3ebd79fe82e2`):
`packages/fields/src/index.tsx:1127` `const style = dateField.format ||
'relative';` for the `date` cell, and `:1176` `const authoredFormat =
(field as DateTimeFieldMetadata | undefined)?.format || 'compact';` for
the `datetime` cell. Two different defaults for one key. The
vocabularies differ too: `formatDate`
(`packages/core/src/utils/date-display.ts`) honours `short` and
`relative` and silently paints its default locale face for anything
else, while the datetime cell maps `relative` and `short` onto its own
faces and falls through for everything else, `compact` and date patterns
such as `YYYY-MM-DD` included.
- **Renderer-hint and display-pattern arms** (objectui, same pin), found
by round 2's at-tier review and census. `resolveCellRendererType`
promotes a textual field by a word set
(`packages/fields/src/index.tsx:2915-2946`), and `plugin-dashboard`'s
`renderFieldValue` reads the same key as a display pattern
(`recordFields.tsx:382-427`). The two disagree on at least one word.
`format: 'email'` on a `text` field is a `mailto:` hint to the resolver,
but the dashboard's pattern test matches its `m` and hands the value to
`formatDate`, which answers an em dash. That defect is reported
separately in the round-2 report.
- **Blast radius today is documentation, not data.** No field anywhere
in this repository authors `format` — measured over
`packages/*/src/objects` and `examples/*/src/data`. format by the readers that exist, not by email/phone (#19763)1 parent 2005a55 commit 041c8cf
5 files changed
Lines changed: 28 additions & 6 deletions
File tree
- .changeset
- content/docs/references
- data
- system
- packages/spec/src/data
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
56 | 56 | | |
57 | 57 | | |
58 | 58 | | |
59 | | - | |
| 59 | + | |
60 | 60 | | |
61 | 61 | | |
62 | 62 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
218 | 218 | | |
219 | 219 | | |
220 | 220 | | |
221 | | - | |
| 221 | + | |
222 | 222 | | |
223 | 223 | | |
224 | 224 | | |
| |||
551 | 551 | | |
552 | 552 | | |
553 | 553 | | |
554 | | - | |
| 554 | + | |
555 | 555 | | |
556 | 556 | | |
557 | 557 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
56 | 56 | | |
57 | 57 | | |
58 | 58 | | |
59 | | - | |
| 59 | + | |
60 | 60 | | |
61 | 61 | | |
62 | 62 | | |
| |||
476 | 476 | | |
477 | 477 | | |
478 | 478 | | |
479 | | - | |
| 479 | + | |
480 | 480 | | |
481 | 481 | | |
482 | 482 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1087 | 1087 | | |
1088 | 1088 | | |
1089 | 1089 | | |
1090 | | - | |
| 1090 | + | |
| 1091 | + | |
| 1092 | + | |
| 1093 | + | |
| 1094 | + | |
1091 | 1095 | | |
1092 | 1096 | | |
1093 | 1097 | | |
| |||
0 commit comments