Skip to content

Commit 3cf6449

Browse files
objectstack-fleet[bot]hotlongclaude
authored
chore(objectui): bump the console pin to dd3f7e1be356 (carries the injected-client boot fix) with the showcase div→box and trash-icon follow-through (#20436)
Clause-②: no Moves the console pin from objectui `f8a9d0fb0596` to objectui `main` `dd3f7e1be356`, which is 325 upstream changesets and 41 of them declared breaking on objectui's own surfaces. The PR also lands the three pieces this repo needs so the console built at that pin actually works. A local boot from a zero-user database, driven in a headless browser from the first-run registration onward, found each of them. ## Why this pin, and not an earlier `main` At any objectui `main` from `8cc0e2984` onward, a console built by `scripts/build-console.sh` dies at boot. The page stays on the splash screen with `TypeError: u is not a function`. The script always injects this tree's `@objectstack/client` through `OBJECTSTACK_CLIENT_DIST`. That client resolved outside `node_modules`, so it missed the `vendor-objectstack` group and landed in `framework`. `framework` imports `data-adapter`, and `data-adapter` `require`s the CJS client at its top level before `framework` has defined it. The Console Pin Gate only builds the bundle and never loads it, so the gate stays green on this breakage. The fix landed upstream as objectui#10921 (for objectui#10920), and `dd3f7e1be356` is its merge commit. At this pin `chunk-membership.json` reads `client: { "vendor-objectstack": 1 }`, and the console boots. ## What changes | Piece | Change | |---|---| | `.objectui-sha` + `.changeset/console-dd3f7e1be356.md` | `bump-objectui.sh` over the whole range `f8a9d0fb0596...dd3f7e1be356`: 325 releasing changesets, 41 annotated breaking, 0 declared major. ADR-0087 disposition: `not-required (no-migration-prescription)`, worded as in the last bump that carried breaking entries. | | `sdui.manifest.json` + record | Regenerated from the pin's built tree: 107 components, up from 59. The 48 additions are the html-tier intrinsic tags objectui now registers (`a`, `p`, `h1`–`h6`, `ul`/`ol`/`li`, `table`, `img`, …). `div` is not among them. `check-sdui-manifest --require-objectui` passes. | | `packages/sdui-parser/objectui-lockstep.json` | Re-recorded against `.cache/objectui-dd3f7e1be356`: 214 grammar lines, byte-identical, and all 26 codes agree. | | 47 pin citations in `packages/spec/src` | Re-measured, not sha-swapped, over both hops `f8a9d0fb0` → `733fd5ac6` → `dd3f7e1be`. Byte-identical files hold by identity. Moved spans are re-pointed. Changed spans are re-read, with the change stated where cited. No read point died. The corpus counts in the six semantic migration entries were calibrated at `f8a9d0fb0` first, then re-taken at the pin; all 96 dark tokens are still 0. The `view.zod.ts` map-config record's five quoted anchors are re-pointed; none changed what it reads. | | Showcase html pages | Capability Map (the showcase landing page), Start Here and the HTML Command Center rendered `HTML page failed to compile` at the pin. objectui now refuses `div` on `kind:'html'` pages and names `box` as the drop-in swap (objectui#10757). Every wrapper moves to `box`, and all three render with their styles intact. | | `packages/lint` JSX ratchet | With the regenerated manifest and the migrated pages, the wired gate emits zero findings over the shipped pages. Every ledger row went stale, so `sdui-jsx-baseline.json` is deleted, as its header prescribes, and the census assertion inverts to "the wired run is clean". | | `trash-2` → `trash` | lucide 1.43, which the pin ships, dropped `Trash2` from the runtime `icons` record the console resolves names through. `icon: 'trash-2'` draws nothing, while `trash` is the identical glyph (objectui measured it node for node). Changed: four `@objectstack/platform-objects` delete actions, the app-todo delete action, the flow builder's `delete_record` palette entry, and the `BulkAction.icon` describe example. The changeset is `patch` for `platform-objects` and `spec`. | | `FormField.span` describe | The describe names the pin it was re-read at; the claim still holds. Separate `spec` patch changeset; the reference docs are regenerated. | ## Verification Gates, run locally at the head: - `check:objectui-pin-citations`: 47 asserting citations match, and 7 anchor-content assertions are verified against objectui at `dd3f7e1be`. - `check:generated`: all 15 artifacts are up to date. - `check:sdui-lockstep` OK, `check-sdui-manifest --require-objectui` OK, `check:console-sha` OK, `check:cross-package-test-inputs` OK. - `packages/lint`: 4704 passed. - `examples/app-showcase`: 384 passed. - `packages/platform-objects`: 911 passed. - `packages/spec`: 17121 passed and 2 failed. Both failures reproduce here on Node 22 as well. They are script-harness tests, not tests of this diff: `build-schemas-check-mode` sees a stamp `mismatch`, and `check-liveness` sees a child's JSON cut at byte 65478. I left them for CI to arbitrate rather than claim either way. An earlier run under heavy machine load showed 18 failures; all 16 extra were load timeouts that pass on a quiet re-run. End to end, on a fresh zero-user database (`objectstack dev --no-seed-admin`), with the console built at the pin, driven in headless Chromium: - **First run.** The `/setup` wizard creates the owner. A CJK-only organization name renames the bootstrap org (slug kept) instead of creating a second one. The exit is a full-document navigation, and `bootstrap-status` flips to `hasOwner:true`. - **Signup gate.** An uninvited sign-up is refused `403 SELF_REGISTRATION_CLOSED`, both at the API and through `/register`. Anonymous `/setup` redirects to `/login?redirect=%2Fsetup`, and the owner's `/setup` opens Setup. - **Invitation.** The owner invites through the org record page; the role list is the closed four-name vocabulary. The invited email registers through `/register` and joins as `member`. - **Member gates.** Five forged admin calls as the member (invite admin, invite member, rename org, demote owner, create team) are each refused 403. The member sees no create affordance, and a direct create is refused 403. - **Owner CRUD.** Create through the form dialog (lookup and required-field validation work), edit and delete a project, each checked server-side. - **Self-service.** The member renames, uploads an avatar (now stored as `/api/v1/storage/files/<id>`, not a `data:` URL), changes the password (old one refused, new one accepted) and signs out from the user menu. - **Page sweep.** 95 surfaces as the owner (showcase and Setup) and 58 as the member (showcase and account) show no page errors and no 5xx. The only failures at `9f0c84a` were the three html pages this PR migrates. Findings outside this PR's scope, noted here and not filed: several strings are untranslated on the zh-CN console (`Delegated Admin`, the raw server message on a refused sign-up, the wizard's `Cancel/Next/Step 1 of 3`, `MEMBER COUNT`). A read-only member's wizard shows disabled inputs with no explanation. Record delete confirms through the native `window.confirm`. The console probes `GET /api/v1/usage/storage`, which answers 404 on this server. --- _Generated by [Claude Code](https://claude.ai/code/session_local_f929252f-7e71-4327-b98b-bd46424b5e05)_ --------- Co-authored-by: Jack Zhuang <50353452+hotlong@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com>
1 parent aeb0557 commit 3cf6449

36 files changed

Lines changed: 1730 additions & 558 deletions

‎.changeset/console-dd3f7e1be356.md‎

Lines changed: 158 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
One published `describe` sentence that dates itself to the `.objectui-sha` pin is re-pointed to the pin this release builds against, objectui `dd3f7e1be356`, after being re-read there.
6+
7+
Clause-②: no
8+
9+
- `FormField.span`: the `'auto'` clause says that at the pin this repo builds against, only textarea, markdown, html, richtext and repeater resolve to the full column count. It named `f8a9d0fb0596`. Re-read at `dd3f7e1be356`, the claim still holds. `plugin-form`'s `autoLayout.ts` `WIDE_FIELD_TYPES` (`:58-69`) and `resolveColSpan` (`:154`) are byte-identical; its only change is one docblock line. `form.tsx`'s `spanLadderFor` (`:204-231`) is byte-identical, so `'full'` is still the whole row at every multi-column tier. Only the pin the sentence names moves.
10+
11+
No key, default, enum member or export moves: the same authored metadata is accepted and refused as before, and `content/docs/references/ui/view.mdx` is regenerated from the sentence.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@objectstack/platform-objects': patch
3+
'@objectstack/spec': patch
4+
---
5+
6+
fix(platform-objects,spec): the delete actions and the flow builder's Delete Record node name the `trash` icon, which still renders after the console's lucide 1.43 upgrade
7+
8+
Clause-②: no
9+
10+
The console build at the new objectui pin ships `lucide-react` 1.43, whose runtime `icons` record dropped one key, `Trash2`. The console resolves an authored icon name through that record, so `icon: 'trash-2'` now resolves to nothing and the button draws no glyph. `trash` draws the identical glyph, which objectui measured node for node when it made the same repair in its own tree.
11+
12+
Four delete actions in `@objectstack/platform-objects` (OAuth application, organization, SSO provider, team) and the `delete_record` entry of the flow builder's default node palette in `@objectstack/spec` now say `trash`. The `BulkAction.icon` description's example names `trash` too. No key, default shape or export moves. An author's own `icon: 'trash-2'` keeps validating as before, but draws no glyph in the console; write `icon: 'trash'` to get the same glyph back.

‎.objectui-sha‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
f8a9d0fb0596f4521076628e2bbfe27e6ce67d52
1+
dd3f7e1be3561d63267d7162f3fc0ac52e72834d

‎content/docs/references/ui/bulk-action.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ const result = BulkActionDefSchema.parse(data);
4242
| :--- | :--- | :--- | :--- |
4343
| **name** | `string` | ✅ | Stable identifier — the audit-log action key, and (for an aggregate def) the name of the object action to dispatch. |
4444
| **label** | `string` | optional | Button + dialog-header text. Plain string: an authored def is not i18n-resolved (declare a real action and name it in `bulkActions` to get localization). |
45-
| **icon** | `string` | optional | Lucide icon name (e.g. "user-check", "trash-2"). |
45+
| **icon** | `string` | optional | Lucide icon name (e.g. "user-check", "trash"). |
4646
| **variant** | `Enum<'primary' \| 'secondary' \| 'danger' \| 'ghost' \| 'outline'>` | optional | Visual treatment of the button. |
4747
| **operation** | `Enum<'update' \| 'delete' \| 'custom'>` | ✅ | What the executor does: 'update'/'delete' are data-plane mass mutations; 'custom' dispatches an object action (see `execution`). |
4848
| **execution** | `Enum<'perRecord' \| 'aggregate'>` | optional | For `operation: 'custom'` — 'aggregate' dispatches the named action ONCE for the whole selection, carrying every id in `params._selectedIds`. Required on a custom def: the per-record form is declared as `bulkActions: ['<name>']` instead. |

‎content/docs/references/ui/view.mdx‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ Column footer summary configuration
200200
| **required** | `boolean` | optional | Required override |
201201
| **hidden** | `boolean` | optional | Hidden override |
202202
| **colSpan** | `integer` | optional | Absolute column span (1-4). The renderer clamps it to the form grid's current column count, so the cell starts at a real column boundary at every surface width and never overflows (`colSpan: 4` in a 3-column grid renders as 3); a `colSpan` within the column count renders as authored, and `colSpan: 1` emits no span class at all. |
203-
| **span** | `Enum<'auto' \| 'full'>` | optional (default: `"auto"`) | Relative field width. 'auto' (default — omit it): the renderer sizes the field from its widget type × the current column count — at the pin this repo builds against (`.objectui-sha` = `f8a9d0fb0596`), only textarea, markdown, html, richtext and repeater resolve to the full column count (repeater reaches it through the wide `field:grid` widget it maps to). 'full': resolves to the form grid's full column count. How far down the container-query tiers that span is emitted is the renderer's, not this key's: at that same pin the renderer emits one clamped col-span class per multi-column tier (`@md:col-span-2 @2xl:col-span-3` for a 3-column grid), so the field takes the whole row at every multi-column tier, not just the widest. |
203+
| **span** | `Enum<'auto' \| 'full'>` | optional (default: `"auto"`) | Relative field width. 'auto' (default — omit it): the renderer sizes the field from its widget type × the current column count — at the pin this repo builds against (`.objectui-sha` = `dd3f7e1be356`), only textarea, markdown, html, richtext and repeater resolve to the full column count (repeater reaches it through the wide `field:grid` widget it maps to). 'full': resolves to the form grid's full column count. How far down the container-query tiers that span is emitted is the renderer's, not this key's: at that same pin the renderer emits one clamped col-span class per multi-column tier (`@md:col-span-2 @2xl:col-span-3` for a 3-column grid), so the field takes the whole row at every multi-column tier, not just the widest. |
204204
| **widget** | `string` | optional | Custom widget/component name (overrides type-based inference) |
205205
| **language** | `string` | optional | Code editor language (for type=code) |
206206
| **keyField** | `{ field?: string; label?: string \| Record<string, string>; placeholder?: string \| Record<string, string>; helpText?: string \| Record<string, string>; … }` | optional | Key column config for record-typed fields |
@@ -365,7 +365,7 @@ View filter rule
365365
| **required** | `boolean` | optional | Required override |
366366
| **hidden** | `boolean` | optional | Hidden override |
367367
| **colSpan** | `integer` | optional | Absolute column span (1-4). The renderer clamps it to the form grid's current column count, so the cell starts at a real column boundary at every surface width and never overflows (`colSpan: 4` in a 3-column grid renders as 3); a `colSpan` within the column count renders as authored, and `colSpan: 1` emits no span class at all. |
368-
| **span** | `Enum<'auto' \| 'full'>` | optional (default: `"auto"`) | Relative field width. 'auto' (default — omit it): the renderer sizes the field from its widget type × the current column count — at the pin this repo builds against (`.objectui-sha` = `f8a9d0fb0596`), only textarea, markdown, html, richtext and repeater resolve to the full column count (repeater reaches it through the wide `field:grid` widget it maps to). 'full': resolves to the form grid's full column count. How far down the container-query tiers that span is emitted is the renderer's, not this key's: at that same pin the renderer emits one clamped col-span class per multi-column tier (`@md:col-span-2 @2xl:col-span-3` for a 3-column grid), so the field takes the whole row at every multi-column tier, not just the widest. |
368+
| **span** | `Enum<'auto' \| 'full'>` | optional (default: `"auto"`) | Relative field width. 'auto' (default — omit it): the renderer sizes the field from its widget type × the current column count — at the pin this repo builds against (`.objectui-sha` = `dd3f7e1be356`), only textarea, markdown, html, richtext and repeater resolve to the full column count (repeater reaches it through the wide `field:grid` widget it maps to). 'full': resolves to the form grid's full column count. How far down the container-query tiers that span is emitted is the renderer's, not this key's: at that same pin the renderer emits one clamped col-span class per multi-column tier (`@md:col-span-2 @2xl:col-span-3` for a 3-column grid), so the field takes the whole row at every multi-column tier, not just the widest. |
369369
| **widget** | `string` | optional | Custom widget/component name (overrides type-based inference) |
370370
| **language** | `string` | optional | Code editor language (for type=code) |
371371
| **keyField** | `{ field?: string; label?: string \| Record<string, string>; placeholder?: string \| Record<string, string>; helpText?: string \| Record<string, string>; … }` | optional | Key column config for record-typed fields |
@@ -1062,7 +1062,7 @@ View filter rule
10621062
| :--- | :--- | :--- | :--- |
10631063
| **name** | `string` | ✅ | Stable identifier — the audit-log action key, and (for an aggregate def) the name of the object action to dispatch. |
10641064
| **label** | `string` | optional | Button + dialog-header text. Plain string: an authored def is not i18n-resolved (declare a real action and name it in `bulkActions` to get localization). |
1065-
| **icon** | `string` | optional | Lucide icon name (e.g. "user-check", "trash-2"). |
1065+
| **icon** | `string` | optional | Lucide icon name (e.g. "user-check", "trash"). |
10661066
| **variant** | `Enum<'primary' \| 'secondary' \| 'danger' \| 'ghost' \| 'outline'>` | optional | Visual treatment of the button. |
10671067
| **operation** | `Enum<'update' \| 'delete' \| 'custom'>` | ✅ | What the executor does: 'update'/'delete' are data-plane mass mutations; 'custom' dispatches an object action (see `execution`). |
10681068
| **execution** | `Enum<'perRecord' \| 'aggregate'>` | optional | For `operation: 'custom'` — 'aggregate' dispatches the named action ONCE for the whole selection, carrying every id in `params._selectedIds`. Required on a custom def: the per-record form is declared as `bulkActions: ['<name>']` instead. |
@@ -1445,7 +1445,7 @@ View filter rule
14451445
| :--- | :--- | :--- | :--- |
14461446
| **name** | `string` | ✅ | Stable identifier — the audit-log action key, and (for an aggregate def) the name of the object action to dispatch. |
14471447
| **label** | `string` | optional | Button + dialog-header text. Plain string: an authored def is not i18n-resolved (declare a real action and name it in `bulkActions` to get localization). |
1448-
| **icon** | `string` | optional | Lucide icon name (e.g. "user-check", "trash-2"). |
1448+
| **icon** | `string` | optional | Lucide icon name (e.g. "user-check", "trash"). |
14491449
| **variant** | `Enum<'primary' \| 'secondary' \| 'danger' \| 'ghost' \| 'outline'>` | optional | Visual treatment of the button. |
14501450
| **operation** | `Enum<'update' \| 'delete' \| 'custom'>` | ✅ | What the executor does: 'update'/'delete' are data-plane mass mutations; 'custom' dispatches an object action (see `execution`). |
14511451
| **execution** | `Enum<'perRecord' \| 'aggregate'>` | optional | For `operation: 'custom'` — 'aggregate' dispatches the named action ONCE for the whole selection, carrying every id in `params._selectedIds`. Required on a custom def: the per-record form is declared as `bulkActions: ['<name>']` instead. |

‎examples/app-showcase/src/ui/pages/capability-map.page.ts‎

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ const card = (
2525
links: Array<[href: string, label: string]>,
2626
) => `
2727
<flex direction="col" gap={2} style={{"background":"hsl(var(--card))","border":"1px solid hsl(var(--border))","borderRadius":"var(--radius)","padding":"24px"}}>
28-
<div style={{"fontSize":"12px","fontWeight":"700","letterSpacing":"0.06em","textTransform":"uppercase","color":"hsl(var(--primary))"}}>${eyebrow}</div>
29-
<div style={{"fontSize":"18px","fontWeight":"600","color":"hsl(var(--foreground))"}}>${title}</div>
30-
<div style={{"fontSize":"14px","lineHeight":"1.6","color":"hsl(var(--muted-foreground))"}}>${body}</div>
28+
<box style={{"fontSize":"12px","fontWeight":"700","letterSpacing":"0.06em","textTransform":"uppercase","color":"hsl(var(--primary))"}}>${eyebrow}</box>
29+
<box style={{"fontSize":"18px","fontWeight":"600","color":"hsl(var(--foreground))"}}>${title}</box>
30+
<box style={{"fontSize":"14px","lineHeight":"1.6","color":"hsl(var(--muted-foreground))"}}>${body}</box>
3131
<flex direction="col" gap={1} style={{"marginTop":"4px"}}>
3232
${links
3333
.map(
@@ -48,12 +48,12 @@ export const CapabilityMapPage = definePage({
4848
<flex direction="col" gap={8} style={{"maxWidth":"1080px","margin":"0 auto","padding":"40px"}}>
4949
5050
<flex direction="col" gap={2}>
51-
<div style={{"fontSize":"12px","fontWeight":"600","letterSpacing":"0.12em","textTransform":"uppercase","color":"hsl(var(--primary))"}}>ObjectStack Showcase</div>
52-
<div style={{"fontSize":"32px","fontWeight":"700","letterSpacing":"-0.02em","color":"hsl(var(--foreground))"}}>The capability map</div>
53-
<div style={{"maxWidth":"720px","fontSize":"15px","lineHeight":"1.6","color":"hsl(var(--muted-foreground))"}}>Every metadata capability the platform delivers, demonstrated once and indexed here — one card per protocol domain, mirroring the registry and the src/ layout. The coverage test keeps this map honest: a new capability fails CI until it is demonstrated or explicitly waived.</div>
51+
<box style={{"fontSize":"12px","fontWeight":"600","letterSpacing":"0.12em","textTransform":"uppercase","color":"hsl(var(--primary))"}}>ObjectStack Showcase</box>
52+
<box style={{"fontSize":"32px","fontWeight":"700","letterSpacing":"-0.02em","color":"hsl(var(--foreground))"}}>The capability map</box>
53+
<box style={{"maxWidth":"720px","fontSize":"15px","lineHeight":"1.6","color":"hsl(var(--muted-foreground))"}}>Every metadata capability the platform delivers, demonstrated once and indexed here — one card per protocol domain, mirroring the registry and the src/ layout. The coverage test keeps this map honest: a new capability fails CI until it is demonstrated or explicitly waived.</box>
5454
</flex>
5555
56-
<div style={{"display":"grid","gridTemplateColumns":"repeat(2, 1fr)","gap":"20px"}}>
56+
<box style={{"display":"grid","gridTemplateColumns":"repeat(2, 1fr)","gap":"20px"}}>
5757
${card('data', 'Objects, fields & rules', 'The Account → Project → Task backbone plus the Field Zoo specimen (every field type), relationships, enforced validation rules, hooks, seed data, an object-extension overlay, and the analytics cube.', [
5858
['apps/com.example.showcase/showcase_field_zoo', 'Field Zoo'],
5959
['apps/com.example.showcase/showcase_project', 'Projects (backbone)'],
@@ -83,8 +83,8 @@ ${card('security', 'Roles, permissions & sharing', 'A role hierarchy, permission
8383
${card('ai · deferred', 'Agents, tools & skills', 'Deliberately not demonstrated here: agents are platform-owned (ADR-0063) and the open framework exposes AI via MCP only. The coverage manifest waives agent/tool/skill with a tracking issue instead of faking a demo.', [
8484
['https://github.com/objectstack-ai/objectstack/issues/2610', 'Tracking issue #2610'],
8585
])}
86-
</div>
86+
</box>
8787
88-
<div style={{"fontSize":"13px","lineHeight":"1.6","color":"hsl(var(--muted-foreground))"}}>Provenance: src/coverage.ts — every metadata kind in the registry is either demonstrated (with proof files) or waived (with a reason and an issue). Run pnpm verify to hold the map to it.</div>
88+
<box style={{"fontSize":"13px","lineHeight":"1.6","color":"hsl(var(--muted-foreground))"}}>Provenance: src/coverage.ts — every metadata kind in the registry is either demonstrated (with proof files) or waived (with a reason and an issue). Run pnpm verify to hold the map to it.</box>
8989
</flex>`,
9090
});

0 commit comments

Comments
 (0)