You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 3cf6449
Browse filesBrowse the repository at this point in the historyBrowse files
authored
chore(objectui): bump the console pin to dd3f7e1 (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...dd3f7e1`:
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>
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.
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.
Copy file name to clipboardExpand all lines: content/docs/references/ui/bulk-action.mdx
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -42,7 +42,7 @@ const result = BulkActionDefSchema.parse(data);
42
42
| :--- | :--- | :--- | :--- |
43
43
|**name**|`string`| ✅ | Stable identifier — the audit-log action key, and (for an aggregate def) the name of the object action to dispatch. |
44
44
|**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"). |
46
46
|**variant**|`Enum<'primary' \| 'secondary' \| 'danger' \| 'ghost' \| 'outline'>`| optional | Visual treatment of the button. |
47
47
|**operation**|`Enum<'update' \| 'delete' \| 'custom'>`| ✅ | What the executor does: 'update'/'delete' are data-plane mass mutations; 'custom' dispatches an object action (see `execution`). |
48
48
|**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. |
|**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. |
204
204
|**widget**|`string`| optional | Custom widget/component name (overrides type-based inference) |
205
205
|**language**|`string`| optional | Code editor language (for type=code) |
|**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. |
369
369
|**widget**|`string`| optional | Custom widget/component name (overrides type-based inference) |
370
370
|**language**|`string`| optional | Code editor language (for type=code) |
|**name**|`string`| ✅ | Stable identifier — the audit-log action key, and (for an aggregate def) the name of the object action to dispatch. |
1064
1064
|**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"). |
1066
1066
|**variant**|`Enum<'primary' \| 'secondary' \| 'danger' \| 'ghost' \| 'outline'>`| optional | Visual treatment of the button. |
1067
1067
|**operation**|`Enum<'update' \| 'delete' \| 'custom'>`| ✅ | What the executor does: 'update'/'delete' are data-plane mass mutations; 'custom' dispatches an object action (see `execution`). |
1068
1068
|**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
1445
1445
| :--- | :--- | :--- | :--- |
1446
1446
|**name**|`string`| ✅ | Stable identifier — the audit-log action key, and (for an aggregate def) the name of the object action to dispatch. |
1447
1447
|**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"). |
1449
1449
|**variant**|`Enum<'primary' \| 'secondary' \| 'danger' \| 'ghost' \| 'outline'>`| optional | Visual treatment of the button. |
1450
1450
|**operation**|`Enum<'update' \| 'delete' \| 'custom'>`| ✅ | What the executor does: 'update'/'delete' are data-plane mass mutations; 'custom' dispatches an object action (see `execution`). |
1451
1451
|**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. |
<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>
<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>
${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.',[
@@ -83,8 +83,8 @@ ${card('security', 'Roles, permissions & sharing', 'A role hierarchy, permission
83
83
${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.',[
<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>
0 commit comments