Repository navigation
Commit 41b1333
fix(spec): a view container's
Fixes #21500
Clause-②: no
## What this changes
`ViewSchema` declares `form` a view container's **default form** and
`formViews` **additional named form views**.
`expandViewContainerWithDiagnostics`
(`packages/spec/src/ui/view.zod.ts`), the one expansion every view
registrar shares (ObjectQL boot loop, metadata artifact/HMR loader,
runtime expansion in `metadata-protocol`), now serves exactly that. This
executes triage's ruling of record on the card (Q1 A, Q2 A):
- **Form family.** The `{ type, label, columns }` signature
de-duplication (`formSigSeen`) is gone. The container's `form` is always
served as its own item, `OBJECT.form`, flagged `isDefault`, and it is
the only form item flagged. A named form whose body equals `form` stays
its own named item.
- **No `form`, no default form.** The first-named promotion (`isDefault`
stamped on `out[formStart]`) is gone. A container with no `form` serves
every named form by name, with no `isDefault` key, the same spelling the
list family uses for "not default".
- **List family, folded in (same function, same signature helper).** The
default `list` used to collapse into any named list sharing its `{ type,
label, columns }`, so a named list with a different body (a filter, a
sort) took the default's place and the default list's own body was not
served. It now collapses only into a named list that restates its whole
body, the view's own `name` aside, which is the "default ==
`listViews.all`" pattern the collapse exists for. A named list that
differs in anything else is its own view, and `list` is served beside it
as the default.
The example containers whose create/edit form lived in `formViews` move
it into `form`. The public Web-to-Lead and contact-us forms stay named.
⛔ No serve-path, `metadata-protocol`, `objectql` or console change.
## In-repo containers whose served form family changed
Measured by running the pure function over all 11 in-repo `defineView`
containers (`examples/**`, `packages/qa/downstream-contract`;
`packages/apps/**` holds none), before (spec `dist` built at
`88fb5e85a0`) and after (this branch). `*` marks the `isDefault` item.
The list family is unchanged in all 11.
| Container | Before | After | Intent, read from the container's own
text |
|---|---|---|---|
| `crm_activity` (app-crm) | `default*` | `form*` | `formViews.default`,
the only form, keyed `default`. Moved into `form`. |
| `crm_lead` (app-crm) | `web_to_lead*`, `default` | `web_to_lead`,
`form*` | `default` is the full lead form. `web_to_lead`'s docblock:
"PUBLIC / ANONYMOUS — Web-to-Lead", hosted at `/forms/contact-us`.
`default` moved into `form`; `web_to_lead` stays named. |
| `crm_opportunity` (app-crm) | `default*` | `form*` | As
`crm_activity`. |
| `showcase_business_unit` | `edit*` | `form*` | Its comment: "`edit`,
not `default`: the main `list` implicitly claims OBJECT.default ... so a
`default` form key collides". Intended as the default; moved. |
| `showcase_project` | `edit*` | `form*` | Same comment; moved. |
| `showcase_task` | `edit*`, `tabbed`, `wizard`, `split`, `quick` |
`tabbed`, `wizard`, `split`, `quick`, `form*` | Same comment ("Keyed
`edit`, NOT `default`"); moved. `showcase_log_time`, `showcase_new_task`
and the Overview page's Create Task button now target
`showcase_task.form`. |
| `showcase_inquiry` | `contact*` | `contact` | `contact` is the PUBLIC
anonymous web-to-lead form (docblock, `sharing.allowAnonymous`,
`publicLink`). Not a create/edit form, so it stays named and the
container has no default form. No source change. |
| `showcase_contact` | `create*` | `create`, `form*` | Already authors
`form` (the documented create-vs-edit pattern in
`content/docs/ui/create-vs-edit-form.mdx`). No source change; now served
as documented. |
For every migrated container the default form's body is the body that
was `isDefault` before, so create/edit render the same form; `crm_lead`
and `showcase_contact` change to the form their authors declared.
Companion edits the move needs:
`examples/app-crm/test/i18n-sections.test.ts` walks the default `form`
as well as `formViews` (otherwise moving the forms silently dropped 8 of
its 9 sections from coverage, and its non-vacuity guard would have read
1); the showcase coverage collector counts the default `form`'s type,
symmetric with the list collector; the app-crm translation comments and
two platform-checklist strings name the new identities.
`content/docs/ui/views.mdx` taught a container whose only form was
`formViews.edit`, which after this change declares no default form; it
now teaches `form` as the default and `formViews` as named forms served
by name. `create-vs-edit-form.mdx` already describes what is served.
## The list family and the `default_2` reading
- **Falsifying case, before:** `list: { type: grid, label: 'All Leads',
columns: [name, company] }` beside `listViews.mine` with the same three
keys plus a `filter`. The default list was collapsed into `mine`: one
item, `crm_lead.mine`, flagged default, and the default list (no filter)
was not served. **After:** `crm_lead.mine` (named, not default) and
`crm_lead.default` (default, no filter). Pinned.
- **Identical restatement:** app-crm's `list` restates `listViews.all`
key for key in a different key order; it still collapses into `all` in
all three app-crm containers (scan above). Pinned, including the case
where only the list's own `name` differs.
- **`default` renamed `default_2` on app-crm:** not reproduced at
`88fb5e85a0`. With the pure function and with the composed app-crm
stack, `crm_lead`'s `formViews.default` was served as `crm_lead.default`
with zero collisions, because the default `list` collapses into
`listViews.all` and leaves `crm_lead.default` free. The rename mechanism
itself (a nameless default `list` claims `OBJECT.default`, a later
`formViews.default` is renamed `OBJECT.default_2` and reported as a
collision) is a different rule: the shared name space, not the signature
or the default promotion. It survives this change and is already loud
(rename warning, `collisions`, `os lint`). After the migration no
app-crm container has a `formViews.default`.
## Measurements
- **Door probe**
(`packages/qa/dogfood/test/view-container-default-form.dogfood.test.ts`,
the `domain:engine` seat's commits brought in unchanged): green at HEAD
(3 passed): the HotCRM shape, the two-form shape and the `crm_account`
control. Its expectations are untouched.
- **Real HotCRM container** (hotcrm `0b2a9d4`,
`src/sales/views/lead.view.ts`, 7 named forms), through this branch's
spec `dist`: `crm_lead.form` is served with its six sections and is the
only `isDefault`; the seven named forms are served by name, none
default.
- **Overlay re-measure (unscoped kernel, `@objectstack/verify`
`bootStack`, real HTTP):** packaged `crm_contact` = `form` +
`formViews.detail_form`. `PUT /meta/view/crm_contact` (200) with the
HotCRM shape (`form` + `detail_form` + `web_to_lead`) now serves
`crm_contact.detail_form` (order 0, not default),
`crm_contact.web_to_lead` (1, not default), `crm_contact.form` (2,
**default**, the overlay's own sections). One default form; before this
change the same overlay served two. A second overlay, of `crm_account`,
authoring named forms and no `form`, serves `crm_account.detail_form`,
the packaged `crm_account.form` (still the one default) and
`crm_account.web_to_lead`. One default. NOT MEASURED on `env_local`: the
session's permission policy refused setting up the CLI standalone-stack
driver.
- **What the console does with a container that has no `form`:**
objectui's merge rule at the `.objectui-sha` pin (`applyViewItem`: `if
(view.isDefault || !bucket.form) bucket.form = entry;`), run verbatim on
this branch's rows, still makes `showcase_inquiry.contact` the console's
`.form`: the first form item, with no `isDefault`. `crm_lead` and
`showcase_contact` resolve to their `form`. Recorded in Acceptance
notes.
## Ablation
The `formSigSeen` de-duplication alone re-planted
(`scripts/ablation-replace.mjs`, wrap mode; anchor 1 → 0, blob
`5053b26a8f` → `8cc32810e7`), spec rebuilt,
`scripts/ablation-dist-preflight.mjs` marker present in 6 built files:
- the spec pins: 2 failed, 3 passed (the HotCRM collision shape and the
named-form-equals-`form` case);
- the door probe: 1 failed, 2 passed (the HotCRM shape, `crm_lead.form`
missing; the two-form and control cases stay green).
Restored by blob (`5053b26a8f` == HEAD, `git diff HEAD` empty, tree
clean), spec rebuilt, preflight `--absent` over 228 built files: pins
5/5 and probe 3/3 green. Run at `78f36c0ebb`; the form family is
byte-identical at HEAD.
## Tests updated, and why
- `packages/lint/src/validate-translation-references.test.ts`,
`packages/cli/src/utils/i18n-extract.ts`,
`packages/objectql/src/engine-nested-plugin-view-expansion.test.ts`,
`scripts/codemod/view-to-viewitem.mjs`: comments only. They stated the
old `{ type, label, columns }` collapse rule; their fixtures and
assertions are unchanged and green.
- `examples/app-showcase/test/actions.test.ts`: the two `target`
assertions follow the task form's move to `showcase_task.form`.
- No test was found pinning the defect itself. The lint fixture whose
default list differs from `listViews.all` only by the list's own `name`
is what decided that `name` is identity, not body.
## Gates
All readings below are at HEAD `3fa9e55aed`.
- **Derived set:** `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` derived 132 commands; all 132 run
with the exit code recorded before any pipe; `--ran`: "132 derived, 132
run, 0 NOT-MEASURED, 0 UNRUN". Two first answered exit 3, PREREQUISITE
NOT MET (`check:skill-examples` reads `@objectstack/client-react`'s
declarations; `check:dual-build-cjs-loads` reads 8 packages outside this
diff's build closure). Those packages were built and both re-ran to exit
0.
- **Release level:** `node scripts/check-changeset-no-major.mjs --base
origin/main`, with this body as the `--event` payload: "This diff
introduces no `major` bump." and "LEVEL AXIS: this PR declares clause-②
`no`". `check-adr-0087-registration`: "this PR adds no declared-breaking
changeset (1 non-breaking changeset(s) seen)". `check-empty-changeset`:
"1 declaring changeset(s) added".
- **`@objectstack/spec`:** `build`; `check:generated` ("All 15 generated
artifacts are up to date"); `typecheck` (tsc, scripts, test layer); the
`local` suite, 604 files / 17844 tests passed; the three view-related
`repo`-project files, 64 tests passed. No generated artifact changed.
- **Consumers of the expansion** (each package's test files that read
it): `metadata` 52, `metadata-protocol` 217, `objectql` 46, `rest` 76,
`lint` 686, `metadata-core` 27, `cli` unit 81. All passed.
- **Example apps:** `@objectstack/example-crm` typecheck, plus 45 tests
(the i18n sweep now lists the 8 moved `form` sections);
`@objectstack/example-showcase` typecheck, plus 394 tests. **Dogfood:**
the door probe 3/3; the two showcase-booting view and public-form tests
6/6.
- **Declared, not run here:** the full lint-workflow farm, `pnpm lint`,
the CLI integration tier and the full dogfood suite are CI's.
- **Stale tree, declared:** at HEAD the branch is 6 commits behind
`origin/main` (`550f4cc2fd`). Two gate-config files changed there: the
doc-authoring prose-id baseline (shrunk by the spec strings stage) and
the engine-double pin list. This diff feeds neither family: no runtime
string here carries a tracker id, and no engine double is added. CI
judges the merge ref.
## Acceptance notes
- **objectui still promotes the first named form for a container with no
`form`** (measured above, `showcase_inquiry`). The spec no longer flags
it; the console's `!bucket.form` fallback does. Filed as
objectstack-ai/objectui#11539; no console change here.
- **The published `objectstack-ui` skill**
(`skills/objectstack-ui/SKILL.md` subforms example,
`rules/list-views.md` container example and its eval) teaches the
create/edit form under `formViews.default` / `formViews.edit`. After
this change those forms are named, not default. `skills/**` is a Tier H
surface, so it is left out of this PR. It is carded as #21537 (seat
answer `5965506932`, option A), to ship in the same release.
- **A dead read, noted:** the expansion's `form.name` branch (`typeof
defaultForm.name === 'string' ? … : 'form'`) reads a key the strict
`FormViewSchema` refuses (`unrecognized_keys`), so it is unreachable for
schema-valid metadata. It is left as it is.
- **Release:** `@objectstack/spec` `patch` (the expansion is corrected
to the declared contract; no accept set changes). The example apps are
private.
> Seat edit, 2026-10-03T04:25Z: the acceptance notes name
objectstack-ai/objectui#11539 and #21537, the cards filed for the two
reported items, and add the `form.name` dead-read note from the dev
report.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM)_
---------
Co-authored-by: Claude <noreply@anthropic.com>form is its default form — never collapsed into a named form, and no named form is promoted (#21535)1 parent 1ac7308 commit 41b1333
22 files changed
Lines changed: 585 additions & 292 deletions
File tree
- .changeset
- content/docs/ui
- docs/qa/platform-checklist/areas
- examples
- app-crm
- src
- translations
- views
- test
- app-showcase
- src
- ui
- actions
- pages
- views
- test
- packages
- cli/src/utils
- lint/src
- objectql/src
- qa/dogfood/test
- spec/src/ui
- scripts/codemod
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
11 | | - | |
| 11 | + | |
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
| |||
43 | 43 | | |
44 | 44 | | |
45 | 45 | | |
46 | | - | |
47 | | - | |
48 | | - | |
49 | | - | |
50 | | - | |
51 | | - | |
52 | | - | |
53 | | - | |
54 | | - | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
55 | 53 | | |
56 | 54 | | |
57 | 55 | | |
| |||
360 | 358 | | |
361 | 359 | | |
362 | 360 | | |
363 | | - | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
364 | 365 | | |
365 | 366 | | |
366 | 367 | | |
367 | 368 | | |
368 | | - | |
369 | | - | |
370 | | - | |
371 | | - | |
372 | | - | |
373 | | - | |
374 | | - | |
375 | | - | |
376 | | - | |
377 | | - | |
378 | | - | |
379 | | - | |
380 | | - | |
381 | | - | |
382 | | - | |
383 | | - | |
384 | | - | |
385 | | - | |
386 | | - | |
387 | | - | |
388 | | - | |
389 | | - | |
390 | | - | |
391 | | - | |
392 | | - | |
393 | | - | |
394 | | - | |
395 | | - | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
396 | 395 | | |
397 | 396 | | |
398 | 397 | | |
| |||
467 | 466 | | |
468 | 467 | | |
469 | 468 | | |
470 | | - | |
| 469 | + | |
471 | 470 | | |
472 | 471 | | |
473 | 472 | | |
| |||
502 | 501 | | |
503 | 502 | | |
504 | 503 | | |
505 | | - | |
506 | | - | |
507 | | - | |
508 | | - | |
509 | | - | |
510 | | - | |
511 | | - | |
512 | | - | |
513 | | - | |
514 | | - | |
515 | | - | |
516 | | - | |
517 | | - | |
518 | | - | |
519 | | - | |
520 | | - | |
521 | | - | |
522 | | - | |
523 | | - | |
524 | | - | |
525 | | - | |
526 | | - | |
527 | | - | |
528 | | - | |
529 | | - | |
530 | | - | |
531 | | - | |
532 | | - | |
533 | | - | |
534 | | - | |
535 | | - | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
536 | 533 | | |
537 | 534 | | |
538 | 535 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1534 | 1534 | | |
1535 | 1535 | | |
1536 | 1536 | | |
1537 | | - | |
| 1537 | + | |
1538 | 1538 | | |
1539 | 1539 | | |
1540 | 1540 | | |
| |||
1574 | 1574 | | |
1575 | 1575 | | |
1576 | 1576 | | |
1577 | | - | |
| 1577 | + | |
1578 | 1578 | | |
1579 | 1579 | | |
1580 | 1580 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
136 | 136 | | |
137 | 137 | | |
138 | 138 | | |
139 | | - | |
| 139 | + | |
140 | 140 | | |
141 | 141 | | |
142 | 142 | | |
| |||
162 | 162 | | |
163 | 163 | | |
164 | 164 | | |
165 | | - | |
| 165 | + | |
166 | 166 | | |
167 | 167 | | |
168 | 168 | | |
| |||
190 | 190 | | |
191 | 191 | | |
192 | 192 | | |
193 | | - | |
| 193 | + | |
194 | 194 | | |
195 | 195 | | |
196 | 196 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
48 | 48 | | |
49 | 49 | | |
50 | 50 | | |
51 | | - | |
52 | | - | |
53 | | - | |
54 | | - | |
55 | | - | |
56 | | - | |
57 | | - | |
58 | | - | |
59 | | - | |
60 | | - | |
61 | | - | |
62 | | - | |
63 | | - | |
64 | | - | |
65 | | - | |
66 | | - | |
67 | | - | |
68 | | - | |
69 | | - | |
70 | | - | |
71 | | - | |
72 | | - | |
73 | | - | |
74 | | - | |
75 | | - | |
76 | | - | |
77 | | - | |
78 | | - | |
79 | | - | |
80 | | - | |
81 | | - | |
82 | | - | |
83 | | - | |
84 | | - | |
85 | | - | |
86 | | - | |
87 | | - | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
88 | 87 | | |
89 | 88 | | |
0 commit comments