Repository navigation
feat(plugin-form): derive the inline grid default columns through the spec rule deriveInlineGridColumns (objectui#11345) - #11623
Merged
objectstack-fleet[bot] merged 4 commits intoOct 4, 2026
Conversation
…he spec's deriveInlineGridColumns (objectui#11345) `deriveColumns` now takes which columns an inline master-detail grid draws, their order and their `defaultHidden` flag from `@objectstack/spec`'s `deriveInlineGridColumns`, and builds each column from its child field with the per-column builder it already had (extracted as `deriveColumn`). The local budget (`curateColumns`, `NAME_LIKE_FIELDS`, `TYPE_FILL_PRIORITY`), the local column filter (`NON_EDITABLE_TYPES`) and the module's own `DEFAULT_MAX_INLINE_COLUMNS = 6` are deleted; the budget is the spec's `DEFAULT_MAX_INLINE_GRID_COLUMNS`. Signature and output of the public `deriveColumns` are unchanged. A new pin holds a corpus to both answers: the spec's names / order / defaultHidden, and the full output the replaced implementation returned. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL
`@object-ui/plugin-form`'s published `dist/index.js` now imports `deriveInlineGridColumns` and `DEFAULT_MAX_INLINE_GRID_COLUMNS` from `@objectstack/spec/data`, which the spec first exports in 17.6.0. The declared `^17.0.0` admitted 17.0.0 to 17.5.x, none of which carries either name. `pnpm-lock.yaml` follows the specifier; the resolved version (17.6.0) does not move. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL
…columns (objectui#11345) Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
objectstack-fleet
Bot
deleted the
claude/issue-11345-derive-inline-grid-columns
branch
October 4, 2026 18:39
akarma-synetal
pushed a commit
to akarma-synetal/objectui
that referenced
this pull request
Oct 7, 2026
…ere the audience posture admits it (objectui#11691) (objectstack-ai#11703) Fixes objectstack-ai#11691 Clause-②: yes ## What this changes `/api/v1/auth/config` states the sign-up rule as two keys: `emailPassword.disableSignUp` (the hard off switch) and `features.audiencePosture` (who may self-register). The server deliberately does not force the first from the second: under `invite_only` its sign-up route still admits a pending invitee. The console read only the first, so under the default `invite_only` posture `/login` offered "Sign up" and `/register` refused the finished form with `403 SELF_REGISTRATION_CLOSED`. Both console pages now decide through one shared function, `decideSignUpOffer` in the new `apps/console/src/pages/auth/signUpOffer.ts`: | config | `/login` | `/register` | |---|---|---| | `disableSignUp: true` | no "Sign up", as objectui#11634 does | bounces to `/login` as before, invitation redirects included | | posture `open` or `email_domain` | unchanged | unchanged | | posture `invite_only`, reached from an invitation (`?redirect=/accept-invitation/ID`) | "Sign up", carrying the redirect | the form | | posture `invite_only`, deployment with no owner yet (`bootstrap-status` answers `hasOwner: false`) | "Sign up" | the form | | posture `invite_only`, otherwise | no "Sign up" | says "Self-registration is not open on this environment. Ask an administrator for an invitation." before any field, with a Sign in link | | no `audiencePosture` key (older server) | `disableSignUp` alone decides, as before | same | | a posture value outside the spec vocabulary | read as closed | read as closed | - `@object-ui/auth`: `AuthPublicConfig.features` declares `audiencePosture?: AudiencePosture` (the type from `@objectstack/spec/system`), beside `tenancyPosture`. The README's server-feature-flags section says how to read it beside `disableSignUp`. - `@object-ui/auth`'s `@objectstack/spec` floor moves from `^17.0.0` to `^17.3.0`. The published `types.d.ts` now references `AudiencePosture`, which 17.0.0 to 17.2.0 do not export, and `check:spec-floors` names that exact finding when the old floor is restored (see Evidence). The matching `pnpm-lock.yaml` change is one specifier line, the shape PR objectstack-ai#11623 used for plugin-form. - The posture predicate is restated locally (`audienceAdmitsUninvitedSignUp`) rather than imported at runtime. This follows the `postureHasOrgWall` precedent in app-shell's `useTenancyPosture.ts`, because the login and register pages are in the console's eager closure. A parity test imports the spec's `audiencePermitsSelfRegistration` and `AUDIENCE_POSTURES` and asserts agreement for every declared posture. - No new i18n key. The register page's explanation reuses `auth.register.errors.selfRegistrationClosed`, which is the refusal's own copy, plus the existing title and "Already have an account? Sign in" keys. ### One addition beyond the triage direction: the first-owner window Triage's direction (comment 6010280240) names two cases that keep the generic sign-up under `invite_only`: an `open` or `email_domain` posture, and an invitation redirect. Measured, a third case is load-bearing: - The server's `decideAudienceAdmission` admits a bootstrap creation under every posture, with the comment "a fresh install must never lock its operator out". - The objectstack self-hosting guide's first-run step is "Open the deployment's root URL and sign up". - The console has no automatic route to `/setup`: nothing in `apps/console/src` or app-shell navigates there. Without the window, an unseeded fresh deployment on the default posture would land its operator on a login page with no way to create the first account. So under a closed posture the pages ask `GET /api/v1/auth/bootstrap-status` through `useBootstrapStatus` from `components/setupEntry.ts` (reused, not edited), and keep "Sign up" while `hasOwner` is false. The probe runs only when the posture is closed and the visitor is not an invitee. `open`, `email_domain` and older servers make no extra request, which is pinned. There is no server change and no new key. If the seat prefers the literal direction, removing the `context.bootstrap === 'fresh'` line from `decideSignUpOffer`, together with its two pins, reverts it. ## Premises measured 1. **Key path and vocabulary.** `features.audiencePosture` is emitted by `getPublicConfig` in objectstack's `auth-manager.ts` (origin/main `01e0f71a`) and by the published `@objectstack/plugin-auth@17.6.0` tarball (`audiencePosture: audience.posture`). The spec's closed vocabulary is `AUDIENCE_POSTURES` = `invite_only`, `email_domain`, `open` (`@objectstack/spec/system`, installed 17.6.0). Its `audiencePermitsSelfRegistration` is true for `email_domain` and `open` only. `getAuthConfig` unwraps the `{ success, data }` envelope, so the pages read `cfg.features.audiencePosture`. 2. **Older server.** For a config without the key, `decideSignUpOffer` returns the pre-change answer and makes no bootstrap probe. This is pinned for both pages. 3. **Invitation redirect.** No marker is needed. `DefaultAcceptInvitationPage` bounces a signed-out visitor to `/login?redirect=` plus the router path `/accept-invitation/ID`, and `/login` already forwards `redirect` to `/register`. The pages recognise the `/accept-invitation/` prefix followed by a non-empty id, and `AcceptInvitationPage.tsx` is not edited. Recognition is an affordance only: a non-invitee who types the URL by hand still meets the server's refusal, which the form renders localized. 4. **Clause-②.** The published surface that moved is `@object-ui/auth`'s `dist/types.d.ts`, with a new optional `features.audiencePosture` member and a new type import from `@objectstack/spec/system`, plus the package's spec floor. No locale key and no export was added or removed. ## Evidence All results are at branch head `efcf1ea` unless another commit is named. - **New pins.** `apps/console/src/pages/auth/__tests__/signUpFollowsPosture-11691.test.tsx`: 16 passed. A real `AuthProvider` runs over a real `createAuthClient` against a stub server. The end-to-end case clicks "Sign up" from `/login` with an invitation redirect, fills the form, and reads the `/sign-up/email` request body. - **Ablation.** Run on committed `387c36b` in WRAP mode through objectstack's `scripts/ablation-replace.mjs`; each restore was proven as blob equal to HEAD with an empty `git diff HEAD`. - With the decision ignoring the posture (the pre-fix behaviour): 3 failed, 13 passed. The failures are the decision table, "/login under invite_only offers no generic Sign up", and "/register explains instead of rendering the form". - With the invitation exception deleted: 3 failed, 13 passed. The failures are the decision table, "/login offers Sign up to an invitation redirect", and "an invitation redirect reaches a working registration". - **Type reverse check.** A probe file assigning `audiencePosture: 'invite-only'` turns console `tsc` red with TS2820 on that value only; the `'invite_only'` line beside it compiles. With the probe removed, console `type-check` is green. - **Floor reverse check.** Restoring `"@objectstack/spec": "^17.0.0"` makes `check:spec-floors` report `@object-ui/auth [floor-too-low] packages/auth/dist/types.d.ts references AudiencePosture from @objectstack/spec/system, which @objectstack/spec@17.0.0 does not export`. The floor was then restored. - **Union at `efcf1ea`.** `pnpm exec vitest run apps/console/src/pages/auth/ packages/auth/` plus the four `App.*` and `internalFormShell` tests that mock these pages: 43 files and 367 tests passed. - **Type-check and lint.** `pnpm --filter @object-ui/auth run type-check` exits 0. `pnpm --filter @object-ui/console run type-check` exits 0, after building the `@object-ui/console^...` closure (34 tasks). `lint` for both packages reports 0 errors; its warnings are on pre-existing lines only. Type-check and lint ran at `8e12c74`, whose sources are byte-identical to `efcf1ea`: that later commit adds only the changeset. - **Root gates at `efcf1ea`, each exit 0:** `check:new-line-citations` (0 new), `check:control-bytes`, `check:changeset-claims`, `check:pending-changeset-literals`, `check:i18n-keys`, `check:i18n-dead-keys`, `check:test-path-roots`, `check:readme-exports`, `check:spec-symbols`, `check:phantom-deps`, `check:lockfile-integrity`, `check:lockfile-dedupe`, `check:unreferenced-sources`, `check:installed-pin-claims`, `check:vi-mock-specifiers`, `check:spec-floors`, `check:eager-closure`, `node scripts/check-changeset-no-major.mjs` and `node scripts/check-changeset-presence.mjs`. - `check:readme-exports` needs `@object-ui/cli` and `@object-ui/plugin-ai` built, so they were built first. - `check:eager-closure` ran on a fresh console `vite build`. - `check-governed-queue-guard.mjs --test` answers NOT GOVERNED for all 9 paths. - **Left to CI:** the repo-wide `pnpm lint`, the full `pnpm test`, and `check:i18n-drift` (no `en` value changed). ## Acceptance notes - **The same defect, outside this claim.** app-shell's published `DefaultLoginPage` and `DefaultRegisterPage` read only `disableSignUp`. A throwaway probe, not committed, measured it with `audiencePosture: 'invite_only'` and `disableSignUp: false`: `DefaultLoginPage` offers "Sign up" to `/register`, and `DefaultRegisterPage` renders the full form. Its control leg (`disableSignUp: true`) hides the link. `examples/console-starter/src/App.tsx` routes both pages. This goes to the seat for its own card and is not edited here, because it is a different package with its own verification surface. - **Doc drift, noted only.** objectstack's `content/docs/permissions/authentication.mdx` says "The Console's root route uses it to choose between `/login` (normal) and `/setup` (first-run owner creation)", but no console code navigates to `/setup`. Carrier: none. - **Files outside the claim's listed surface, declared here.** `signUpOffer.ts` is a new file beside the two pages. `packages/auth/package.json` and the one-line `pnpm-lock.yaml` change are there for floor honesty, under objectui#5793's ruling. `packages/auth/README.md` is there because AGENTS.md rule 2 asks for docs. - **Untouched, as triage directed:** the settings follow-up (`signup_enabled` versus `audience_posture` on the objectstack settings page). --- _Generated by [Claude Code](https://claude.ai/code/session_01FngvPpdrnhHMdHHq6vwwju)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #11345
Clause-②: no
What this PR is
deriveColumns, the default columns of a master-detail inline grid whose author listed none, now takes which columns it draws, their order and theirdefaultHiddenflag from@objectstack/spec'sderiveInlineGridColumns, and its visible budget from the spec'sDEFAULT_MAX_INLINE_GRID_COLUMNS. objectui's own per-column builder stays over the returned names (extracted asderiveColumn), so each column's label, cell type, options, lookup target, conditional rules and computed expression are built exactly as before, including the plain text column for a falsy field definition.Deleted from
packages/plugin-form/src/deriveMasterDetail.ts:curateColumns,NAME_LIKE_FIELDS,TYPE_FILL_PRIORITY,fillPriority,NON_EDITABLE_TYPES, and the module-levelDEFAULT_MAX_INLINE_COLUMNS = 6, which the package entry never re-exported.SYSTEM_FIELDSandSORT_FIELD_NAMESstay, becausederiveFormFieldsand the sort-field pick inderiveDetailstill read them.The direction is triage's first grade (comment 5926478449): names and
defaultHiddenfrom the spec, objectui's builder over the names, the budget imported asDEFAULT_MAX_INLINE_GRID_COLUMNS, no second6.The fence
deriveColumnskeeps its signature, and its output is unchanged on every input the evidence below reached.@object-ui/*package. The one declaration-file change is the module-levelDEFAULT_MAX_INLINE_COLUMNSleavingdist/deriveMasterDetail.d.ts. The package entrysrc/index.tsxnever re-exported it, and the packageexportsmap exposes only..Landing site, and one addition to the claimed file surface
The landing site is the dispatched one:
packages/plugin-form/src/deriveMasterDetail.tsand a test beside it.packages/plugin-form/package.json(@objectstack/specgoes from^17.0.0to^17.6.0) and the one matching specifier line inpnpm-lock.yaml. The resolved version stays 17.6.0. They are in commit399fc03, kept separate so it can be judged on its own. The reason: the publisheddist/index.jsnow importsderiveInlineGridColumnsandDEFAULT_MAX_INLINE_GRID_COLUMNSfrom@objectstack/spec/data. The old floor admitted 17.0.0 to 17.5.x, which carry neither name, so a consumer that resolves one of those versions could not link the entry. PR #11552 and PR #11555 raised floors the same way, with the lockfile specifier.Evidence (head
fe06e31, after merging mainc096f03)The pin.
deriveMasterDetail.inlineGridColumns-11345.test.tsholds one 14-entry corpus to two answers:defaultHiddenequal (toStrictEqual) the spec'sderiveInlineGridColumnsanswer;toStrictEqual) the arrays the replaced implementation returned. These were generated fromderiveColumnsat6e9090c.The corpus covers both sides of the budget edge,
maxColumns3, 0 and -1,excludewith and without a relationship field, the filters, more required columns than the budget, a computed required column, falsy definitions, and four shapes with no field map. One more test checks that an omittedmaxColumnsleaves exactlyDEFAULT_MAX_INLINE_GRID_COLUMNScolumns visible.Ablations on committed code (
b879c7c). Each ran through objectstack'sscripts/ablation-replace.mjs. In each, the anchor went from 1 hit to 0, the blob changed, and the restore was proven (blob equals HEAD,git diff HEADempty).defaultHiddencarry. Predicted 13 red. ObservedTests 13 failed | 16 passed (29).{ name }, the hydrate route the card warns about. Predicted 1 red (pin 2, falsy entry). ObservedTests 1 failed | 28 passed (29).One-off differential. Not committed; a historical reading at
fe06e31. It comparesderiveColumnsat BASE (6e9090c) with this branch over 80,014 cases: the 14 corpus entries, 50,000 random cases and 30,000 budget-heavy ones.relationshipField,excludeandmaxColumns, including NaN and Infinity.util.inspectrendering (so key order and undefined-valued keys count) plus deep equality.hydrateColumns, gives 68,522 mismatches on the same cases, so the instrument can fire.Floor. This is objectui's
scripts/check-spec-range-floors.mjsverdict (itsanalyze()), scoped to@object-ui/plugin-formover its built dist:^17.0.0: red,floor-too-lowon exactlyDEFAULT_MAX_INLINE_GRID_COLUMNSandderiveInlineGridColumns;^17.5.0(in memory): red, on the same two names;^17.6.0: green.pnpm exec vitest run packages/plugin-form/Test Files 164 passed (164),Tests 1918 passed / 1 skipped (1919)pnpm --filter @object-ui/plugin-form type-check, afterturbo run build --filter=@object-ui/plugin-form. Bothtsclegs ran, and--listFilesshows the test program reads the new pinpnpm --filter @object-ui/plugin-form lintcheck:spec-symbols,check:phantom-deps,check:unused-deps,check:lockfile-integrity,check:lockfile-dedupe,check:new-line-citations,check:control-bytes,check:test-path-roots,check:unreferenced-sources,check:installed-pin-claims,check:pending-changeset-literals,check:changeset-claims,check:esm-specifiers,check:self-import,check:vi-mock-specifiersscripts/check-changeset-presence.mjs,scripts/check-changeset-no-major.mjsscripts/check-governed-queue-guard.mjs --testover the five changed pathsNOT MEASURED locally. These need every published package built, and CI or the release path runs them:
check:spec-floors(the scoped verdict above stands in);check:readme-exports(refused here with 24 packages unbuilt; this diff touches no README and no export);check:dist-completeness,check:node-esm-loadandcheck:published-dist;pnpm lintandpnpm test.Measured answers to the dispatch's open points
@objectstack/spec17.6.0dist/datadeclaresDEFAULT_MAX_INLINE_GRID_COLUMNS = 6and exportsderiveInlineGridColumns. 17.5.0 lacks both, as the scoped floor verdict shows.be5211522412. The differential above was taken on this base, after PR feat(types,plugin-form,plugin-charts,app-shell)!: dashboard widgets bind a dataset in the catalog and docs; FormulaFieldMetadata takes the spec's expression; three display-pointer readers read displayField alone (objectui#11070, round 6) #11333 and PR feat(types)!: a form view'ssubforms[].columnsentry is the spec'sInlineGridColumnSchema, by reference (objectui#11266) #11618.deriveMasterDetail.tsreadDEFAULT_MAX_INLINE_COLUMNS.6exists: themaxColumns = 6default ofRelatedListinpackages/plugin-detail. That is a different budget: a related-list card that slices columns away, not an inline grid that collapses them into a chooser. It is left as is.GridField's doc comment namesderiveColumnsand gives no number. No docs page states the budget.Acceptance notes
SYSTEM_FIELDSandSORT_FIELD_NAMES(used by the row form and the sort-field pick) hold the same names as the spec's module-privateINLINE_GRID_SYSTEM_FIELDSandINLINE_GRID_SORT_FIELDS, which@objectstack/spec/datadoes not export. This is an observation only: the row-form rule is not spec-owned, and nothing here judges it. Carrier: none.Changeset
.changeset/11345-inline-grid-columns-spec-rule.mddeclares@object-ui/plugin-formpatchand includes the floor sentence.Session:
https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL(os-dev, dispatched by thedomain:uiseat 1).Generated by Claude Code