Repository navigation
Commit 6dbff8e
feat(spec,ui)!: retire the record page's slots.tabs; a page that needs its own strip is kind: 'full' (#27219)
<!-- claude-projects-attribution: {"github_login":"os-support-ai"} -->
_Requested by **Yibo Wang** · [project
thread](https://claude.ai/code/project/chan_01BVUyyPie8gHwoPM18iABwB?thread=cmsg_01BVUyyPie8gHwoPM18iABwBMVTpWL3WoqLD4tN69q1BPz)_
Fixes #27053
## What changes
The record page's `slots.tabs` is retired under the
`spec-property-retirement` kit. A slotted page now fills six slots
(`header`, `actions`, `alerts`, `highlights`, `details`, `discussion`);
a page that needs its own tab strip is `kind: 'full'`.
**Spec (`@objectstack/spec`)**
- `slots.tabs` is a `retiredKey()` tombstone carrying the card's
prescription: the record page composes its tabs and columns from the
object's roles and relationship flags; set `relatedList` / `relatedList:
'primary'` on the child's reference field to change which related lists
appear or their order; author `slots.details` to replace the Details
body; a page that needs its own strip is `kind: 'full'`. It ends with
the house `os migrate meta --from 17` sentence and carries no issue ids.
- The `related` / `relatedLists` aliases leave with the slot. They are
now `guidance` entries that refuse by name with the same remedy, instead
of being read as `tabs`.
- `checkPageSlotPair` (the `details` + `tabs` refusal) and
`page-slot-pair.test.ts` are removed, and so is its
`@objectstack/spec/ui` export. The check-exports pin now asserts that it
has zero declarations and zero attachments.
- ADR-0087 D2: the `page-slots-tabs-removed` conversion
(`retiredFromLoadPath`, `retiredAfter: '18.2.0'`, order 78) drops
`slots.tabs` from a stored page. When `tabs` was the only slot, the page
is left as an empty slotted page, which renders the synthesized default.
- ADR-0087 D3: the `page-slots-tabs-retired` entry covers the judgement
the conversion cannot make, which is whether such a page should become
`kind: 'full'`. It absorbs `page-slots-details-beside-tabs-refused`,
because the pair no longer exists. The `STEP18_RATIONALE` fragment moves
with it (order 112). The retired-key entry is `ui/Page:slots.tabs`.
- The JOIN row in `retired-key-removal-version.pin.test.ts`, the
liveness `slots` note, and the regenerated api-surface, export-origins
and references.
**The four in-repo producers, rebuilt as `kind: 'full'`.** Each rebuild
is faithful: the same nodes in the order the synthesizer drew them
(header, then the actions/alerts slot, highlights, the strip, then
discussion). Nothing is redesigned. Header actions are the object's
`record_header` / `record_more` actions in declaration order, which is
exactly what the synthesized header drew; the host still appends
Edit/Share/Delete.
| Page | Package | Body |
|:--|:--|:--|
| `sys_organization_detail` | `@objectstack/platform-objects` |
`page:header` (4 actions) · `record:highlights` (`slug`) · Members /
Invitations / Teams strip (unchanged) · `record:discussion` |
| `sys_user_detail` | `@objectstack/platform-objects` | `page:header`
(11 actions) · the `record:alert` from its `alerts` slot · highlights ·
its curated strip · no discussion (its `discussion: []`) |
| `sys_approval_request_detail` | `@objectstack/plugin-approvals` |
`page:header` · `record:approval_decision` · highlights · Details /
Timeline strip · no discussion (its `discussion: []`) |
| `showcase_project_detail` | `examples/app-showcase` (private) |
`page:header` · highlights · Details / Tasks grid / Related strip ·
`record:discussion` |
**Console (`@object-ui/*`)**
- `buildDefaultPageSchema` composes from the six slots only, and its
tabs branch is removed.
- `RecordDetailView`'s `pageColumnsEligible` drops the `slots.tabs` arm,
so the one gate line is now `!assignedPage && !namedPageBlocked`.
- `@object-ui/types` removes both `.superRefine(checkPageSlotPair)`
attachments and types `PageSlotMap.tabs` as `never`.
- The Studio slot canvas (`SLOT_ORDER`) lists six slots.
**Docs**
- `ui/content/docs/guide/slotted-pages.md`: the slot menu no longer has
`tabs`, and the guide says how to shape the strip instead.
- `content/docs/ui/pages.mdx` (merged from main during this branch): the
same change.
- The generated references.
- `skills/objectstack-ui/rules/pages.md`: one bullet. It is paid for by
a deletion in the same file (−23 tokens net).
**ADR-0133 and QA**
- ADR-0133's open-question correction gains the inline one-line
amendment: the mechanism moves from slotted to full, and the strip does
not.
- The `identity-auth.json` source pin moves with it (revision 6).
- `studio-authoring.json` follows the retired pair: the step now PUTs
`slots.tabs`, which is refused with `invalid_type`. The pin file, the
source anchor and revision 4 are updated to match.
## Why
An authored `tabs` slot replaced the whole synthesized strip. A page
that used it lost every related list, approval, attachment and history
entry the platform composes from the object, and stayed a tab strip at
every width instead of taking the record page's columns. The maintainer
ruled the slot out on epic #26676; the card carries that ruling.
## Acceptance notes
- **Correction to the card's premise.** The card assumed no producer
used `slots.tabs` ("HotCRM never uses tabs"). Re-measured on main at
`0f1045bc`, four in-repo pages authored it: the table above. Seat 1
confirmed moving all four to `kind: 'full'` with identical behaviour.
- HotCRM's own account page could not be measured from this session: the
hotcrm repo is outside this session's scope. If it authors no `tabs`, it
parses unchanged. If it does, its stored row is converted on read by the
D2 conversion, and its source is listed by `os migrate meta --from 17`.
- **The tombstone.** `safeParse` of `tabs` / `related` / `relatedLists`
fails with the prescription, and the six live slots parse.
`page-slots-tabs-retirement.test.ts` has 18 cases:
- tombstone refusal for one component, an array and empty;
- the alias guidance;
- the six live slots;
- a full page with `page:tabs` parses;
- no `#` and the migrate sentence;
- `@ts-expect-error` on `definePage`;
- the barrel no longer exports `checkPageSlotPair`.
- **Stored rows.** A stored slotted page with `tabs` replays through the
conversion: `applyConversionsToStoredItem('page', …)`, the tabs-only
page becomes `slots: {}`, the conversion is idempotent, and it is
retired from the load path. The registry fixture has three pages and
expects two notices.
- **Absence pin.** A tree-scoped absence pin (`packages`, `examples`,
`skills`, `content`, `scripts`) refuses a `slots` frame holding `tabs`,
`related` or `relatedLists` in `.ts`, `.json`, `.yaml` and `.md` fences.
It excludes the conversion fixtures, the json-schema output, release
notes and changelogs.
- **Fixtures and tests moved.** Each one now uses a live slot
(`details`) or reads the full page's region:
- **spec:** `i18n-resolver.test.ts` (two fixtures),
`page-component-filter-record-to-rule-array.test.ts`,
`object-refinement-check-exports.test.ts`,
`retired-key-removal-version.pin.test.ts` (new JOIN row).
`page-slot-pair.test.ts` is deleted.
- **lint:** `page-walk`, `validate-page-field-bindings`,
`validate-visibility-predicates` and `page-envelope-audit` tests.
- **cli:** `i18n-section-coverage` (its fixture is now a `kind: 'full'`
page whose region authors its own `page:tabs`, keeping the nesting under
test), `platform-page-i18n-parity` (the boundary pin now expects `kind:
'full'`, no `slots`, every component reached, and none addressed).
- **platform-objects:** `sys-user-details-tab`,
`sys-user-security-tab-own-record`, `sys-user-two-factor-display`,
`setup-nav-per-user-stores` and `canonical-expression-envelopes` (the
control's path is now `regions[0].components[N].visibleWhen`); new
`record-page-header-actions.test.ts` (round 2, review should-fix ④.1:
for `sys_organization_detail` and `sys_user_detail`, the `page:header`
action ids equal the object's `record_header` ∪ `record_more`
declarations in declaration order, with a non-vacuous guard); comments
in `invite-entry-toolbar.test.ts` and `sys-member.object.ts`.
- **plugin-approvals:** `sys-approval-request-page.test.ts`, which pins
the page body in order.
- **app-showcase:** comments in `task-detail.page.ts` and
`record-action-location-hosts.test.ts`.
- **ui:** `buildDefaultPageSchema.test.ts` (a stray `tabs` key is
ignored and `details` applies), `recordPageColumns.test.ts`,
`slots-canvas.test.ts`, `PagePreview.test.tsx`,
`spec-object-refinements-7715.test.ts` (the retired slot is refused at
both objectui doors), `imported-defaults-8317.test.ts`, and the ten
console served-approvals-page copies
(`approvalRequestsDataSource.servedPage*.test.tsx`,
`ApprovalsInboxPage.opensServedPage-12484.test.tsx`), which now copy the
full page whole.
## Verification
Head `ca5556e06`, with main merged at `b322b5da` (11 commits, no
conflicts). Runs below are local, under `os-verify-lock`.
- **spec:** `--project local` has 22219 passed. The 3 reds from the
first run were fixed and their files re-run green (166 passed): a
conversion fixture moved to `details`, the new JOIN row, and the
export-origins regeneration. `test:repo` has 82 files / 1343 passed,
including the tree-scoped absence pin, which is now listed in
`vitest.repo-tests.json`.
- **Package suites:**
- platform-objects: 96 files / 1377 passed.
- plugin-approvals: 105 files / 1331 passed.
- lint: 7522 passed.
- cli: the three touched files, 59 passed.
- plugin-auth: `organization-record-page-option`, 3 passed.
- **ui:**
- `plugin-detail`: 2941 passed.
- Every ui test that authors a slot map or the served approvals page (19
files): 319 passed.
- `RecordDetailView` record-page tiers and named-page tests: 13 passed.
- `PagePreview`: 8 passed.
- **Typecheck:** `turbo run typecheck` over spec, platform-objects,
plugin-approvals, lint, cli, example-showcase, `@object-ui/types`,
`plugin-detail`, `app-shell` and console: 104 of 104 tasks pass. This
includes the test-layer typecheck ledgers.
- **Derived gates:** `dispatch-gates --commands` lists 126 commands, and
121 exit 0. Of the other five:
- `check:role-word` and `check:cross-package-test-inputs` were real
findings. Both were fixed and now exit 0.
- `check-plugin-teardown-shape --self-test` exited 3 because the clone
is shallow. After fetching its pinned fixture commit it passes all 48
cases.
- `check:skill-examples` and `check:dual-build-cjs-loads` exit 3 because
they need a full repo build. That is a prerequisite miss, and nothing
was measured.
- **Spec artifacts:** `check:generated --fix` regenerated three
artifacts (api-surface, export-origins and references), and each
re-checks green. `check-platform-checklist`,
`check-skills-token-ratchet` (`pages.md` 5669/5692) and
`check-adr-0087-registration --base origin/main` are green.
Clause-②: no (narrowing)
Metadata-protocol: narrowed
Skills-docs: follow-up #27059
_Keep one value on each of the two lines above, each line starting with
its key.
[Human
Minimum](https://github.com/objectstack-ai/objectstack/blob/main/CONTRIBUTING.md#human-minimum)
item 5 says what they mean._
The changeset carries `Clause-②: no (narrowing)`, `Metadata-protocol:
narrowed`, the BREAKING banner, a **FROM → TO.** line and `<!--
adr-0087: registered page-slots-tabs-removed, page-slots-tabs-retired
-->`. The packages bump minor: spec, platform-objects, plugin-approvals,
`@object-ui/types`, `@object-ui/plugin-detail`, `@object-ui/app-shell`
and skills.
Governed surfaces (`docs/adr/**`, `skills/**`, Tier H): this PR is a
draft and is not queued or armed. It needs a governed approver's review
before the seat lands it.
## Human Minimum
Each item is one rule from
[CONTRIBUTING.md › Human
Minimum](https://github.com/objectstack-ai/objectstack/blob/main/CONTRIBUTING.md#human-minimum),
which links the AGENTS.md text that states it.
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes.
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
- [x] 6. `packages/spec` touched: its generated artifacts are
regenerated and committed.
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English.
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01WGufzff3QiP6kmtajVnKnq
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 5b5cff1 commit 6dbff8e
72 files changed
Lines changed: 2366 additions & 1717 deletions
File tree
- .changeset
- content/docs
- references/ui
- ui
- docs
- adr
- qa/platform-checklist/areas
- examples/app-showcase
- src/ui/pages
- test
- packages
- cli/test
- lint/src
- platform-objects/src
- apps
- identity
- pages
- plugins/plugin-approvals/src
- translations
- spec
- api-surface
- export-origins
- liveness
- src
- conversions
- migrations
- entries
- retired-keys
- semantic
- system
- ui
- skills/objectstack-ui/rules
- ui
- apps/console/src
- pages/system
- services
- content/docs/guide
- packages
- app-shell/src/views
- metadata-admin/previews
- __tests__
- plugin-detail
- src/synth
- __tests__
- types/src
- __tests__
- zod
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| 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 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
294 | 294 | | |
295 | 295 | | |
296 | 296 | | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
297 | 309 | | |
298 | 310 | | |
299 | 311 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
207 | 207 | | |
208 | 208 | | |
209 | 209 | | |
210 | | - | |
211 | 210 | | |
212 | 211 | | |
213 | 212 | | |
214 | | - | |
215 | | - | |
216 | | - | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
217 | 218 | | |
218 | 219 | | |
219 | 220 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
260 | 260 | | |
261 | 261 | | |
262 | 262 | | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
263 | 269 | | |
264 | 270 | | |
265 | 271 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
846 | 846 | | |
847 | 847 | | |
848 | 848 | | |
849 | | - | |
| 849 | + | |
850 | 850 | | |
851 | 851 | | |
852 | 852 | | |
| |||
921 | 921 | | |
922 | 922 | | |
923 | 923 | | |
924 | | - | |
| 924 | + | |
925 | 925 | | |
926 | 926 | | |
927 | 927 | | |
| |||
939 | 939 | | |
940 | 940 | | |
941 | 941 | | |
942 | | - | |
| 942 | + | |
| 943 | + | |
943 | 944 | | |
944 | 945 | | |
945 | 946 | | |
| |||
0 commit comments