Commit fa7b565
fix(create-objectstack,cli): starter objects place their fields in a keyed field group (#21407)
Fixes #21370
Clause-②: no
## What changes
Each starter object now places the fields it declares in a keyed field
group (`fieldGroups` on the object, `group` on each field, ADR-0085 §5),
as triage ruled in comment 5948728426:
-
`packages/create-objectstack/src/templates/blank/src/objects/note.object.ts`
(`npm create objectstack`): `fieldGroups: [{ key: 'details', label:
'Details' }]`, with `group: 'details'` on `title` and `body`.
- `packages/cli/src/commands/init.ts`, `TEMPLATES.app` (`os init -t
app`): the same group, on `name`, `description` and `status`.
Each object carries a comment for the author. It says what the group
does, that it is what displays the field, that `objectstack validate` /
`objectstack lint` report a field nothing displays or reads once the
project holds anything that could read it, and that a new field should
name a group too. It links the public page on field groups.
`field-no-consumers` itself is unchanged: there is no starter carve-out,
no field was dropped, and nothing in `packages/lint` was edited. The
rule already credits a field the synthesized layout places in a keyed
section as displayed.
Clause-② is `no`, measured against the diff: the templates gain
placement keys the schema already accepts, and no accept set or public
member moves. The changeset (`.changeset/21370-starter-field-groups.md`)
is a `patch` for `create-objectstack` and `@objectstack/cli`, the two
packages whose shipped templates change.
## Measured before and after, through the real commands
Before, at `origin/main` 69a12a0: one fresh project per starter and
per waker, then `os validate --json` and `os lint --json`. Each cell is
the `field-no-consumers` count, identical under both commands, every
command exit 0.
| starter | bare | `os g dashboard probe` | `os g object gate_target` +
`os g view gate_target` | `os g object gate_target` + `os g flow
gate_probe --object gate_target` |
|---|---|---|---|---|
| `npm create objectstack` | 0 | 1: `body` | 1: `body` | 1: `body` |
| `os init -t app` | 0 | 2: `description`, `status` | 2 | 2 |
| `os init -t plugin` | 0 | 0 | 0 | 0 |
`os init -t plugin` is clean because its only field is the record's
title, which the rule exempts through the `nameField` ladder. `os init
-t empty` declares no object. Neither template changes.
After, at d94a0d6: the nightly pin below runs each starter, then all
three wakers in one project. It reads 0 `field-no-consumers` under both
commands for all three starters. With the starters' `group` lines
stripped, both commands report the finding again, on the starters' own
fields only.
## Pins
- `packages/cli/test/starter-field-consumers.test.ts`: per-PR, unit
tier, in-process. The roster is derived: every `*.object.ts` the blank
starter ships and every one an `os init` template renders. Each source
is loaded the way `os validate` loads it (`bundle-require` with
`BUNDLE_REQUIRE_EXTERNALS`). Each starter is combined with the real
`dashboard`, `view` and `flow` generator output, the latter two bound
through `stackBindingCandidates` to an object generated beside the
starter's. Each stack is run through `runAuthoringRules('validate')` and
`lintConfig`, with zero `field-no-consumers` asserted (9 legs). CONTROL:
every non-title starter field, with its `group` removed, is reported at
exactly its own path by both commands (3 legs: `body`, `description`,
`status`). The title is read through `resolveDisplayField`, not
restated. Every leg also requires its stack to pass `defineStack` and
parse, so a pipeline that stopped early cannot read as a zero.
- `packages/cli/test/starter-field-consumers.e2e.test.ts`: nightly, real
commands. Starters: the on-ramp's `bin/`, plus every `os init -t`
template that renders an object. Each runs `os g dashboard`, `os g
object`, `os g view` and `os g flow`, then `os validate --json` and `os
lint --json`, and must report zero `field-no-consumers` (6 legs).
CONTROL: every `group` line stripped from the scaffolded objects, then
the same two commands must report the finding, only on fields the
stripped files declare (4 legs). It runs only for starters whose sources
place a field, which is decided when the file is collected.
- `packages/cli/test/generate-scaffold-gates.e2e.test.ts`: the one
`STARTER_LATENT_FINDINGS` entry
(`field-no-consumers@objects[0].fields.body`) is deleted, so the ledger
is empty. The header now records why. The ledger's `describe` gains a
floor test pinning it empty. Without that test the block would hold no
test at all once both `it.each` tables are empty, and vitest 4 fails
such a block with "No test found in suite".
## Red before the fix, and the ablations
All ablations ran from the committed fix (d94a0d6) through
`scripts/ablation-replace.mjs`: the anchor hit once, the mutation was
proven on disk, and the restore was proven as blob == HEAD with `git
diff HEAD` empty. Dist-mediated legs were rebuilt and read with
`scripts/ablation-dist-preflight.mjs`.
- **Red before the fix.** The in-process pin ran against the 69a12a0
templates (`git restore --source=69a12a0952`, tree only). Result: 8
failed, 6 passed. The 3 blank and 3 app waker legs failed
(`objects[0].fields.body`; `description` + `status`), and so did the 2
app controls (both fields reported, not one). Plugin and the blank
control passed, as expected. Restored: blob == HEAD for both files.
- **A1**, blank `body` loses its group: 3 failed, 11 passed. Exactly the
3 blank waker legs.
- **A2**, app `description` loses its group: 4 failed, 10 passed. The 3
app waker legs, plus the `status` control.
- **A3**, the rule credits the untitled bucket (`if (section.key ===
undefined) continue;` deleted, `@objectstack/lint` rebuilt; preflight
`--absent` with `--source-marker` passed): 3 failed, 11 passed. Exactly
the 3 control legs. Restore leg: lint rebuilt, and the marker
`section.key === void 0` is present again in dist.
- **E** (nightly pin), blank `body` and app `description` lose their
groups. `create-objectstack` and `@objectstack/cli` were rebuilt, and
the dist read `group: 'details'` 2 → 1 and 3 → 2, with the plant marker
present 1 / 1. Result: 4 failed, 11 passed. Exactly the blank and app
zero legs under `os validate` and `os lint`. Restore leg: both rebuilt,
counts back to 2 / 3, markers 0 / 0.
- **EC** (nightly control), the same A3 guard deletion: 15 passed, so it
did **not** red the nightly control. That is expected on reading: the
control strips every `group`, title included, so
`deriveFieldGroupLayout` returns `null` and there is no untitled bucket
to credit. The in-process control, which strips one field at a time, is
the one that catches that class (A3).
- **EC2** (nightly control), the rule's findings stop carrying the
`field-no-consumers` id. This is plant `-ablated`, lint rebuilt,
preflight present in 4 built files. Result: 4 failed, 11 passed, exactly
the 4 control legs. Restore leg: lint rebuilt, marker absent, tree
clean.
## Verification (at d94a0d6 unless noted)
- `pnpm --filter @objectstack/cli exec vitest run --project unit
--maxWorkers=2`, in 3 shards: 82 + 82 + 83 files. One file,
`published-subpath-console.pin.test.ts`, first refused with
"packages/cli is not built" (prerequisite, not a red). After `pnpm
--filter @objectstack/cli build` it passed (14/14). All other files
passed: 1315 + 1079 + 1095 tests, 14 skipped.
- Integration tier, per-PR file the blank starter feeds:
`create-objectstack-stack-reach.test.ts` passed (4/4).
- Nightly, `OS_TEST_TIERS=nightly`:
`starter-field-consumers.e2e.test.ts` passed (15/15), and
`generate-scaffold-gates.e2e.test.ts` passed with the empty ledger
(37/37).
- `pnpm --filter @objectstack/cli typecheck`: exit 0.
`check:test-typecheck` holds the ledgered 28 errors in 3 files. `tsc -p
tsconfig.test.json --listFiles` includes all three touched test files,
with 0 errors in them. `pnpm --filter create-objectstack typecheck` and
`pnpm --filter create-objectstack test` (16 files, 249 tests) both exit
0. That suite includes the shipped-comment pins, which resolve the new
docs link.
- The gate union from `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands`: 68 commands, each run at
d94a0d6 with its exit code captured before any pipe, all exit 0.
`--ran` reconciliation: "68 derived, 68 run, 0 NOT-MEASURED, 0 UNRUN".
On an earlier pass, three of them first answered as follows.
`check:cross-package-test-inputs` was red: the pin's header named the
lint rule's repo path in prose, which the gate reads as a read. It was
reworded in d94a0d6. `check:dual-build-cjs-loads` and
`check:i18n-coverage` refused with PREREQUISITE NOT MET (exit 3). After
a workspace build both were green, and both are green in the final
union.
- Lint is a proven narrowing, not a `pnpm lint` run. ① The population
comes from `eslint.config.mjs`: `pnpm lint` is `eslint .
--no-inline-config`, minus the global `NEVER_LINTED` set (node_modules,
dist, build, .next, .turbo), and none of the 5 touched TS files falls in
it. ② `eslint --no-inline-config --format json` on those 5 files reports
5 file results, 0 errors and 0 warnings. No result is a "file ignored"
warning. ③ The config never enables type-aware linting (no
`parserOptions.project`, no `projectService`) and loads no cross-file
rule plugin. Its only reads are two baseline JSON files this diff does
not touch. So no untouched file's verdict can move.
## Acceptance notes
- `main` moved 7 commits past 69a12a0 while this ran (to 1d0600b).
None of them touches `init.ts`, the create-objectstack templates,
`generate.ts`, `generate-scaffold-gates.e2e.test.ts` or
`validate-field-consumers.ts`.
- The before readings were one project per waker. The nightly pin uses
one project per starter with all three wakers, to keep it at about 25
cold starts. The per-waker split is held per-PR by the in-process pin.
- Measured on the way, not filed (no defect class): `os validate` on a
bare `os init -t app` project prints one plain-string warning, "No apps
or plugins defined — this stack may not do much". It is not a rule
finding, and it is the same before and after.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 96a9719 commit fa7b565
6 files changed
Lines changed: 697 additions & 17 deletions
File tree
- .changeset
- packages
- cli
- src/commands
- test
- create-objectstack/src/templates/blank/src/objects
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
786 | 786 | | |
787 | 787 | | |
788 | 788 | | |
| 789 | + | |
| 790 | + | |
| 791 | + | |
| 792 | + | |
| 793 | + | |
| 794 | + | |
| 795 | + | |
| 796 | + | |
| 797 | + | |
| 798 | + | |
| 799 | + | |
789 | 800 | | |
790 | 801 | | |
791 | 802 | | |
792 | 803 | | |
793 | 804 | | |
| 805 | + | |
794 | 806 | | |
795 | 807 | | |
796 | 808 | | |
797 | 809 | | |
| 810 | + | |
798 | 811 | | |
799 | 812 | | |
800 | 813 | | |
| |||
805 | 818 | | |
806 | 819 | | |
807 | 820 | | |
| 821 | + | |
808 | 822 | | |
809 | 823 | | |
810 | 824 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
36 | 36 | | |
37 | 37 | | |
38 | 38 | | |
39 | | - | |
| 39 | + | |
40 | 40 | | |
41 | | - | |
42 | | - | |
43 | | - | |
44 | | - | |
45 | | - | |
46 | | - | |
47 | | - | |
48 | | - | |
49 | | - | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
50 | 58 | | |
51 | 59 | | |
52 | 60 | | |
| |||
90 | 98 | | |
91 | 99 | | |
92 | 100 | | |
93 | | - | |
| 101 | + | |
| 102 | + | |
94 | 103 | | |
95 | | - | |
96 | | - | |
97 | | - | |
98 | | - | |
99 | | - | |
100 | | - | |
| 104 | + | |
101 | 105 | | |
102 | 106 | | |
103 | 107 | | |
| |||
248 | 252 | | |
249 | 253 | | |
250 | 254 | | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
251 | 262 | | |
252 | 263 | | |
253 | 264 | | |
| |||
0 commit comments