-
f9bbddb: feat: gate detail/form edit & delete on the server's effective operation set (#3546)
PR-4 (#3391) wired the list/toolbar surface (ObjectView Import, ListView / ObjectGrid Export) to the server-resolved effective API operation set (
/me/permissionsapiOperations, intersected viaresolveCrudAffordances(obj, effectiveApiOperations?)). The detail / form surfaces still gated edit/delete on the bucket +userActionsalone. This extends the same intersection to them, so the record page and its forms never offer an operation the server would 405.- core
isObjectInlineEditable(obj, effectiveApiOperations?)gains the same optional second argument asresolveCrudAffordances— inline-edit is now additionally ANDed with the server allowingupdate. - app-shell
RecordDetailViewthreads the object's effective operations into the synthesized Edit/Delete header actions and the record-body inline-edit gate (canEdit);RelatedRecordActionsBridgeintersects each child object's Create/Edit/Delete handlers with that child's own effective set. - plugin-detail
record:detailsANDs its inline-edit affordance with the object's effectiveupdate. - plugin-form
ObjectForm's blanket managed-object field lock also engages when the server deniesupdate(edit mode) /create(create mode).
Backward-compatible: a missing effective set (unrestricted object, older backend, or no
PermissionProvider) leaves the resolved affordance untouched — the bucket/userActionsdecision wins, exactly as today. Layers on top of the existing per-objectcheck('edit')/check('delete')permission gates (intersection, never union). - core
-
6dee2cb: feat(form): consume spec-aligned FormView buttons/defaults in ObjectForm
The authored
@objectstack/specFormViewSchema carries structuredbuttons.{submit,cancel,reset}.{show,label}anddefaults, but the form renderer only read the flat renderer-inventedshowSubmit/submitText/showCancel/cancelText/showReset/initialValues. That left the two spec keys parsed-but-inert (ADR-0078) and stuck atexperimentalin the spec liveness ledger.ObjectFormnow folds the structured shape down onto those flat props inside its existing normalization pass, so every entry path (ObjectView drawer/modal/page, RecordFormPage) honors it. An explicitly-set flat key still wins, so metadata authored against the deprecated flat keys is unchanged.ObjectViewandRecordFormPageforwardbuttons/defaultsfrom the spec form view.ObjectFormSchemagains the optionalbuttons/defaultsfields.Refs objectstack-ai/objectstack#1894, objectstack-ai/objectstack#2998.
-
2f947e4: fix(page,field): consume the spec's
type/label/maxLengthkeys (framework#1878 §3 naming-drift recheck)Three forward-drifts where objectui read a different key than the spec declares, so authoring the documented key silently no-oped:
- page
type→pageType(app-shell + components):PageSchemadeclares the page KIND astype, butPageRendererreadsschema.pageTypeand fell back to'record'— and nothing mapped between them. Every non-record page (home/app/list/utility) rendered with the record max-width, a wrongdata-page-typeattribute, and a suppressed header.PageViewnow passespageTypealongside the SchemaNode discriminatortype. - page
label→title(components):PageSchema.labelis required but the region renderer read onlytitle. Now dual-readstitle ?? label, mirroring the fallbackDashboardRendereralready uses. Coupled with the above — the header is gated onpageType !== 'record', so both were needed for a title to appear. - field
maxLength/minLength(plugin-form + fields): validation already dual-read these, butObjectForm's HTML-attribute pass andTextAreaFieldreadmax_lengthonly, so a spec-authoredmaxLengthgave no browser cap and no character counter. Both now dual-read, matchingbuildValidationRules.
Verified in the browser against the showcase:
capability_map(type: 'home') now rendersdata-page-type="home", thehomemax-width and its page title; record pages are unchanged. - page
-
662bdf9: fix(fls): wire the real per-caller FLS channel into import targets and grid columns; remove the never-populated
field.permissionsshape (objectstack#3661)The
permissions?: { read?, write?, edit? }key on@object-ui/typesfield definitions (Phase 3.2.6) was declared-but-never-enforced: no producer in the stack ever populated it, so every guard reading it short-circuited to "allow". Per ADR-0049 enforce-or-remove, the shape is deleted and the three consumers now use the server-resolved/auth/me/permissionschannel (usePermissions().checkField) — the same channel ObjectForm/ModalForm/ListView already enforce:- ImportWizard target fields (app-shell
ObjectView): the importable field set (and thus the downloadable CSV template's columns) now drops fields the caller cannot edit, instead of offering columns the server's FLS write gate would 403. - ObjectGrid auto-derived columns: columns the caller cannot read are dropped (same gate ListView applies), instead of a dead schema-shape check.
- ObjectForm: the redundant dead guard in field generation is removed;
the existing
applyFieldPermsgate remains the real enforcement point.
BREAKING CHANGE:
@object-ui/typesfield definitions no longer accept apermissionskey. It never carried data at runtime; consumers needing per-caller field-level permissions must use@object-ui/permissions(MePermissionsProvider+useFieldPermissions/checkField). - ImportWizard target fields (app-shell
-
dc7a798: fix(plugin-grid,plugin-form,plugin-designer,cli,vscode-extension): type-check the last five unchecked packages, and fix the two runtime bugs that hid there (#2919)
Closes the remaining
DEBTentries from the #2911 sweep. Each package gains"type-check": "tsc --noEmit"and loses its entry inscripts/check-type-check-coverage.mjs; coverage goes 36 -> 41 of 45 and outstanding errors 25 -> 5 (only #2916plugin-viewand #2918layoutremain).Two of these were real bugs, not just type noise.
@object-ui/cli—objectui validatecould never report a validation failure.ZodError.errorswas removed in Zod 4 (the repo is on 4.4.3), so.errorsreadundefinedand.forEachthrew aTypeErrorthat the enclosingcatchreported as✗ Error reading or parsing schema file: Cannot read properties of undefined— swallowing the very errors the command exists to print. Now reads.issues. Verified against the built CLI: an invalid schema now prints1. Invalid input / Code: invalid_unionand exits 1.@object-ui/plugin-grid— grouping a grid by a boolean column showed the raw i18n key.t('grid.booleanTrue', 'Yes')asked for a key present in neitherGRID_DEFAULT_TRANSLATIONSnor any locale bundle, and passed the English fallback as a bare second argument — whichcreateSafeTranslation's no-provider translator reads as an options object, so the fallback never applied and the header rendered the literalgrid.booleanTrue. Switched to thegrid.yes/grid.nokeys the boolean cell renderer (ObjectGrid.tsx) andBulkActionDialogalready use, with the fallback passed asdefaultValue. Covered by a new regression test, confirmed to fail against the old code.The rest are type-only corrections that preserve runtime behaviour exactly:
- plugin-grid
importParsers.ts—scorePair'sscore/reasonmoved into onebestrecord. They were capturedlets mutated only inside thebumpclosure, which TypeScript's control-flow analysis does not track, so it still believedreasonwas'none'at the type gate and flagged the comparisons as non-overlapping (TS2367). The gate — which stops a text column being mapped onto a number field — is unchanged; its two dedicated tests still pass. - plugin-form —
SectionFieldsContext.fieldLabelnow requiresfallback, matching theuseSafeFieldLabelproducer in@object-ui/i18n(an omitted fallback could not satisfy the=> stringreturn, and all four call sites already pass one). This one signature cleared six errors.MasterDetailFormSchema.recordIdwidens tostring | number, matchingObjectFormSchemaand the five envelopes that forward straight into it; it is narrowed withString()only at the batch-transaction boundary, whoseBatchTransactionOperation.idis a string by protocol (theisEditguard already proves it non-null there).deriveMasterDetail's column sort gets an explicitfillPriorityhelper —GridColumn.typeis optional, and a column without one keeps sorting at priority 5 exactly as the oldTYPE_FILL_PRIORITY[undefined] ?? 5lookup put it. - plugin-designer — unused
indexparameter prefixed_, matching the_entrybeside it. - cli — a stale
@ts-expect-errorremoved;viteConfigis typedany, so the line it guarded had stopped erroring. - vscode-extension (
object-ui) — migrated offmoduleResolution: "node", which is deprecated and stops working in TypeScript 7, tonode16paired withmodule: "node16"(the package has no"type": "module", so node16 resolves it as the CommonJS that tsup emits, and it gains theexports-map awareness node10 lacks). Its error count was under-reported as 1: that TS5107 config error masked four more. The package usesconsole/Bufferbut setslib: ["ES2020"]with no DOM and never declared@types/node— added, with an explicittypes: ["node", "vscode"].
Also:
plugin-grid,plugin-formandplugin-designergain thebaseUrl+pathsoverride their type-checked plugin peers already carry, andclian emptypaths. Without it the inherited rootpathspoint@object-ui/*at siblingsrc/, which is outside each project'srootDirand produces the ~104 spurious TS6059 errors noted in #2915; workspace deps instead resolve through node_modules to built.d.ts, whichtype-check'sdependsOn: ["^build"]guarantees exist.Verified the gate genuinely covers all five rather than trusting the green: injecting a type error into each package makes
pnpm type-check --filter <pkg>fail, which was impossible before this change. - plugin-grid
-
Updated dependencies [7b21891]
-
Updated dependencies [0b3be01]
-
Updated dependencies [3c4d935]
-
Updated dependencies [4b1ed7d]
-
Updated dependencies [4b60d2d]
-
Updated dependencies [952b978]
-
Updated dependencies [de5e40c]
-
Updated dependencies [1a03af6]
-
Updated dependencies [3e886eb]
-
Updated dependencies [cfc675e]
-
Updated dependencies [20df08c]
-
Updated dependencies [1767124]
-
Updated dependencies [8ecf5a6]
-
Updated dependencies [af705b9]
-
Updated dependencies [0502a7c]
-
Updated dependencies [7b35e4b]
-
Updated dependencies [8fb1295]
-
Updated dependencies [e16ed2d]
-
Updated dependencies [c6fd752]
-
Updated dependencies [f9bbddb]
-
Updated dependencies [dfd3705]
-
Updated dependencies [c77108c]
-
Updated dependencies [2735de6]
-
Updated dependencies [697cda4]
-
Updated dependencies [c19ac11]
-
Updated dependencies [6dee2cb]
-
Updated dependencies [e05f052]
-
Updated dependencies [0502a7c]
-
Updated dependencies [faad45e]
-
Updated dependencies [09c6a17]
-
Updated dependencies [c7cff19]
-
Updated dependencies [ba73a02]
-
Updated dependencies [cd09a7b]
-
Updated dependencies [f1abf0e]
-
Updated dependencies [f05b84e]
-
Updated dependencies [9b4b952]
-
Updated dependencies [341bfb5]
-
Updated dependencies [2f947e4]
-
Updated dependencies [7d46648]
-
Updated dependencies [9b53d72]
-
Updated dependencies [bb4aa25]
-
Updated dependencies [75f1cdf]
-
Updated dependencies [662bdf9]
-
Updated dependencies [059a052]
-
Updated dependencies [53642d4]
-
Updated dependencies [8aae006]
-
Updated dependencies [c6cfdf1]
-
Updated dependencies [d147a13]
-
Updated dependencies [c6aaed8]
-
Updated dependencies [263f885]
-
Updated dependencies [dc334da]
- @object-ui/components@17.0.0
- @object-ui/i18n@17.0.0
- @object-ui/fields@17.0.0
- @object-ui/react@17.0.0
- @object-ui/types@17.0.0
- @object-ui/core@17.0.0
- @object-ui/permissions@17.0.0
-
62b9ab5: feat(data): unify master-detail saves behind
DataSource.batchTransaction, isolate the non-atomic fallback in the adapter (#2679)Master-detail saves (
MasterDetailForm,LineItemsPanel) now always persist throughdataSource.batchTransaction(operations)— one ordered cross-object operation list, with{ $ref: <op index> }linking a child to a parent created in the same batch. The form no longer contains any client-side orchestration or best-effort compensation-delete; that atomicity anti-pattern is gone from the UI layer (framework #1604 / framework ADR-0034 item 4).@object-ui/types—batchTransaction?is now a first-class (optional) method on theDataSourcecontract, typed viaBatchTransactionOperation/BatchRef. Replaces the previous(dataSource as any).batchTransactionmethod-sniffing.@object-ui/core— newemulateBatchTransaction(dataSource, operations)(sequential writes,$refresolution, best-effort reverse-order compensation) andrunBatchTransaction(dataSource, operations)(prefers the adapter's method, emulates otherwise).ApiDataSource/ValueDataSourceimplementbatchTransactionvia the emulation.@object-ui/data-objectstack—ObjectStackAdapter.batchTransactionuses the server's atomicPOST /api/v1/batch, prefers the typedclient.data.batchTransactionSDK method when the installed client exposes it, and degrades to the client-side emulation ONLY when the endpoint is missing (404/405) or the runtime can't do transactions (501). Real errors (400/401/403/ 409/500) still surface. This is the isolated, tested home of the non-atomic fallback.@object-ui/plugin-form— removedapplyDetail/createMany/ApplyDetailResultfrommasterDetailTx.ts;MasterDetailFormandLineItemsPanelbuild ops and callrunBatchTransaction.LineItemsPanelsaves are now atomic on a capable backend, with the rollup folded into the same batch.
No behavior change on a current ObjectStack backend (it has
/api/v1/batch); older/limited backends keep a working — now clearly non-atomic — save path.
-
7cf4051: chore(deps): align every
@objectstack/*dependency to^16.0.0-rc.0Bumps
@objectstack/spec/client/formula/lintfrom^15.1.1to the16.0.0-rc.0pre-release across the workspace (root +apps/console+apps/site+ all consuming packages). ObjectUI's own packages are already on major 16, so this closes the 15↔16 skew between ObjectUI and the@objectstackcontract libraries (which publish in lockstep withspec).This is a dependency alignment, not a behavioral migration: the full workspace build (43/43) and the
@objectstack-consuming package test suites (core/app-shell/data-objectstack/plugin-form/types) are green against16.0.0-rc.0with no source changes required.Practical effect:
@objectstack/client@16.0.0-rc.0now shipsdata.batchTransaction(framework #3271), soObjectStackAdapter's feature detect (typeof client.data.batchTransaction === 'function') routes master-detail cross-object saves through the typed SDK method instead of the rawfetch('/api/v1/batch')fallback — realizing the "verify SDK path" half of #2694. The raw-fetch branch stays as a defensive fallback (removal tracked in #2694). -
0a3710b: Finish the
managedBy/userActionsde-dup — one parser for the override shape (completes objectui#2712, framework#3343). #2712 consolidated the bucket union + affordance set mirrors but left four surfaces still parsing theuserActions.{create,edit,delete}override shape by hand. They now all route through the shared@object-ui/corepolicy, so no package re-implements the boolean / #2614-object-form parse locally.@object-ui/corepromotes the internalnormalizeOverrideto the exportednormalizeUserAction(v, base)(the one parser) and addsuserActionPredicates(v)for per-record CEL predicate extraction.app-shell/utils/managedByEmptyState.ts— the writable-systemcreate check and its localEmptyStateUserActionsinterface are replaced byresolveCrudAffordances({ managedBy, userActions }).create.plugin-grid/rowCrudAffordances.ts— the localisOptedOut/predicatesOfhelpers (and duplicatedRowCrudUserAction/RowCrudPredicatestypes) fold intonormalizeUserAction; the historical type names stay re-exported for compat.plugin-detail/RelatedList.tsx— its inlinepredicatesOffold intouserActionPredicates.plugin-form/ObjectForm.tsx— the hand-rolledmanagedBy !== 'platform'blanket lock +userActionsunlock is replaced by the resolved affordance for the current mode (edit/create), the sameresolveCrudAffordancescontract the detail (isObjectInlineEditable) and grid surfaces use.
Behavior-preserving for
platform/system/append-only/better-auth, with one deliberate alignment: an admin-editableconfig-bucket object (e.g.sys_webhook,sys_permission_set) is now editable inObjectForm— it was previously over-locked as "non-platform", while detail/grid already treated it as editable (configresolvesedit: true). New unit coverage for the shared parser and the config / create-mode form gate; all existing affordance/edit-gate tests stay green. -
Updated dependencies [0318118]
-
Updated dependencies [1c8935a]
-
Updated dependencies [af1b0db]
-
Updated dependencies [8b8b744]
-
Updated dependencies [7cf4051]
-
Updated dependencies [803558e]
-
Updated dependencies [aefcf39]
-
Updated dependencies [2e7d7f0]
-
Updated dependencies [ef14f69]
-
Updated dependencies [94d4876]
-
Updated dependencies [1100a8b]
-
Updated dependencies [7abe4cd]
-
Updated dependencies [69fa5d1]
-
Updated dependencies [549c67d]
-
Updated dependencies [ebe6494]
-
Updated dependencies [2b17339]
-
Updated dependencies [31b77d4]
-
Updated dependencies [6d4fbe6]
-
Updated dependencies [0a3710b]
-
Updated dependencies [f80aaf2]
-
Updated dependencies [62b9ab5]
-
Updated dependencies [14cb729]
-
Updated dependencies [1629313]
-
Updated dependencies [29c6040]
-
Updated dependencies [faebac3]
-
Updated dependencies [2331ac9]
-
Updated dependencies [199fa83]
-
Updated dependencies [eee4ded]
-
Updated dependencies [3b2e4d9]
- @object-ui/fields@16.1.0
- @object-ui/i18n@16.1.0
- @object-ui/core@16.1.0
- @object-ui/types@16.1.0
- @object-ui/react@16.1.0
- @object-ui/components@16.1.0
- @object-ui/permissions@16.1.0
-
9d4a429: fix(form+detail): keep single-file children as inline grids; drop non-spec
attachmenthandlingTwo follow-ups to the upload-in-grid work (objectui#2360):
-
#2654 — Now that
file/image/avatarfields render a compact upload cell in the line-item grid, a child object with a single such field no longer flips the smartinlineEditdefault to a per-row form.resolveInlineModesplits the oldFORM_ONLY_TYPES: truly form-only types (textarea / richtext / html / markdown / json / location / address) still tip toformon their own, while file-family types only tip when several rich fields pile up (RICH_FIELD_FORM_THRESHOLD, default 2). An explicitinlineEditalways wins. -
#2655 —
attachmentis not a@objectstack/specfield type (the spec media types are file/image/avatar/video/audio), so the renderer no longer models it: removed fromfieldTypeToColumnType, the inline-mode heuristic, andRelatedList's auto-columnSKIP_TYPES. Contract-first cleanup — the renderer stops fossilizing a phantom type (AGENTS.md #0.1).
-
-
5534535: feat(grid): built-in row Edit/Delete honor per-record CEL predicates (#2614)
The object's
userActions.edit/userActions.deletenow also accept an object form{ enabled?, visibleWhen?, disabledWhen? }. The predicates are evaluated per row on the canonical CEL engine (useRowPredicate, the same machinery custom row actions use):visibleWhenfalse → the built-in Edit/Delete item is not rendered for that row (fail-closed);disabledWhentrue → rendered disabled (fail-soft). Wired through ObjectGrid's RowActionMenu and the data-table's row overflow menu (the related-list path), with the app-shellcrudAffordancesmirror kept in lockstep. Omitting the predicates (or using plain booleans) keeps today's behavior bit-for-bit; declared predicates evaluate only when a row's menu opens, so grid rendering cost is unchanged. -
Updated dependencies [d3e19ed]
-
Updated dependencies [59d4fa9]
-
Updated dependencies [4c7c47f]
-
Updated dependencies [210806a]
-
Updated dependencies [b4ef588]
-
Updated dependencies [ca0f5f0]
-
Updated dependencies [5534535]
-
Updated dependencies [9b8f978]
-
Updated dependencies [195a651]
-
Updated dependencies [33b4995]
- @object-ui/react@16.0.0
- @object-ui/components@16.0.0
- @object-ui/types@16.0.0
- @object-ui/i18n@16.0.0
- @object-ui/fields@16.0.0
- @object-ui/core@16.0.0
- @object-ui/permissions@16.0.0
- @object-ui/types@15.0.0
- @object-ui/core@15.0.0
- @object-ui/i18n@15.0.0
- @object-ui/react@15.0.0
- @object-ui/components@15.0.0
- @object-ui/fields@15.0.0
- @object-ui/permissions@15.0.0
-
579b24d: feat(fields+form+detail): file/image uploads in inline line-item grids (#2360)
Field.filein a master-detail inline grid previously degraded to a plain text input (noinput[type=file]on the page → no way to upload from the grid), and auto-derived subform / related-list columns silently dropped file fields.- fields: new
FileCell— a compact upload control for grid cells (upload button + removable chips, image thumbnails), sharing theUploadProviderpipeline with the full-sizeFileFieldvia an extracteduseFileUploadshook.GridFieldsupportstype: 'file'columns (withaccept/multiple), renders file names in list/readonly modes, and no longer falls back to a text<Input>for file columns. - plugin-form:
deriveColumns/hydrateColumnsno longer excludefile/image/avatarfields — they map tofilecolumns and carry the field'smultiple+accept(image fields default to['image/*']). - plugin-detail: auto-derived related-list columns no longer skip
file/imagefields — they render through the existing FileCellRenderer / ImageCellRenderer (file-name chip / thumbnail).
- fields: new
- Updated dependencies [82441e4]
- Updated dependencies [2efa9fd]
- Updated dependencies [0890fa7]
- Updated dependencies [2ded18c]
- Updated dependencies [e628d1f]
- Updated dependencies [5523fc4]
- Updated dependencies [887062c]
- Updated dependencies [579b24d]
- Updated dependencies [2b30583]
- Updated dependencies [2b30583]
- Updated dependencies [23d65c3]
- Updated dependencies [055e1d2]
- Updated dependencies [9e2d58f]
- Updated dependencies [dea65f7]
- Updated dependencies [f30ff68]
- Updated dependencies [073e7aa]
- Updated dependencies [3e8bf07]
- Updated dependencies [6c0135c]
- Updated dependencies [5b52624]
- Updated dependencies [4afb251]
- Updated dependencies [d5b1bc0]
- Updated dependencies [f94905d]
- Updated dependencies [2712fc1]
- Updated dependencies [f0f10f5]
- @object-ui/i18n@14.1.0
- @object-ui/fields@14.1.0
- @object-ui/core@14.1.0
- @object-ui/types@14.1.0
- @object-ui/react@14.1.0
- @object-ui/permissions@14.1.0
- @object-ui/components@14.1.0
- Updated dependencies [443360a]
- Updated dependencies [c70bca7]
- Updated dependencies [86c69c3]
- Updated dependencies [05e56ca]
- Updated dependencies [a44e7b6]
- Updated dependencies [5971cc4]
- Updated dependencies [6a74160]
- @object-ui/core@14.0.0
- @object-ui/i18n@14.0.0
- @object-ui/react@14.0.0
- @object-ui/types@14.0.0
- @object-ui/components@14.0.0
- @object-ui/fields@14.0.0
- @object-ui/permissions@14.0.0
-
e492b9d: Permission sets — pure separation of design (Studio) and assignment (Setup), per ADR-0056 / epic #2398. A
sys_permission_setused to render its six authorization facets in Setup as raw[Object]/ JSON textareas, and only objects+fields were editable in Studio; this reworks both surfaces.Setup (assign + read-only):
- The six facets (
object_permissions,field_permissions,system_permissions,row_level_security,tab_permissions,admin_scope) now render read-only on thesys_permission_setrecord page as a compact summary (counts, or capability chips) plus a “Design in Studio →” deep-link into the structured editor (/apps/:appName/metadata/permission/:setName, env scope). No[Object], no JSON — in the record view, inline edit, and the create/edit form. Implemented as apermission-facet-linkfield widget stamped onto the six fields via the singleObjectStackAdapter.getObjectSchemachoke point and honored by DetailSection + the record form. - User assignment (add/remove via
sys_user_permission_set) is surfaced directly on the Setup record page.
Studio (design every facet): the permission matrix editor gains structured editors for the facets that were JSON-only —
- System Capabilities: a multi-select over the live
sys_capabilityregistry (scope-grouped, labelled chips). - Row-Level Security: per-policy rows (object · operation · enabled) with CEL USING/CHECK.
- Tab Visibility: per-tab
visible | hidden | default_on | default_off. - Delegated Admin Scope: business-unit + subtree, manage-assignments / -bindings / author-env-sets toggles, and an assignable-permission-sets allowlist. Assignment was moved out of the editor (it is now a Setup act) — the editor is purely a design surface.
Storage/types are unchanged; editors read/write the draft’s existing parsed fields and tolerate legacy JSON strings on load. Note: env-scope metadata saves of these facets do not yet project onto the queryable
sys_permission_setdata record the Setup summary reads, so a fresh Studio edit isn’t reflected in Setup’s read-only view until the projection refreshes — tracked as a framework follow-up (enforcement reads the authoritative metadata). - The six facets (
-
5da9905: fix(plugin-form): honor
userActions.editon managed objects instead of blanket-disabling every field (ADR-0092 D4)ObjectFormdisabled every field on any non-platformlifecycle bucket (config / system / append-only / better-auth) — a defensive default from when those objects had no generic edit affordance at all. Now that an object can OPEN per-record editing viauserActions.{edit,create}(framework ADR-0092 D4 — e.g.sys_userexposing itsname/imageprofile fields), the blanket lock lifts for the current mode when its affordance istrue, and each field's ownreadonlyflag decides. Managed buckets still default the affordance off, so an object that doesn't opt in is unchanged. The server-side identity write guard remains the real boundary; this is UX only. -
Updated dependencies [80901aa]
-
Updated dependencies [53c40c2]
-
Updated dependencies [e492b9d]
- @object-ui/components@13.2.0
- @object-ui/i18n@13.2.0
- @object-ui/fields@13.2.0
- @object-ui/react@13.2.0
- @object-ui/types@13.2.0
- @object-ui/core@13.2.0
- @object-ui/permissions@13.2.0
- @object-ui/types@13.1.0
- @object-ui/core@13.1.0
- @object-ui/i18n@13.1.0
- @object-ui/react@13.1.0
- @object-ui/components@13.1.0
- @object-ui/fields@13.1.0
- @object-ui/permissions@13.1.0
- Updated dependencies [9e38270]
- Updated dependencies [ac04b76]
- Updated dependencies [619097e]
- @object-ui/i18n@13.0.0
- @object-ui/components@13.0.0
- @object-ui/types@13.0.0
- @object-ui/fields@13.0.0
- @object-ui/react@13.0.0
- @object-ui/core@13.0.0
- @object-ui/permissions@13.0.0
-
195121a: Studio form designer + preview now match the runtime form's column density.
The Data pillar's Form → Layout designer laid every section out in a fixed 2-column grid capped at
max-w-3xl, and Form → Preview capped the realObjectFormatmax-w-2xl. So on a wide screen the studio showed at most 2 columns while the record the end user actually edits spreads to up to 4 — the design surface misrepresented the real layout.ObjectFormDesignernow derives its column count the same way the runtime form does (inferColumnsover the object's editable field count, objectui#2578) and lays each section out with the shared container-query grid classes (containerGridColsFor) inside a per-section@container, so a field-heavy object reaches 4 fields per row on wide screens and collapses to one column when the panel is narrow. Wide widgets (textarea/markdown/html/…) span the full row, mirroring the form. Both the layout and preview canvases were widened tomax-w-6xlso the container queries can actually reach 4 columns.containerGridColsForis now exported from@object-ui/plugin-formas the single source of truth for these grid classes. -
Updated dependencies [6cbccf3]
-
Updated dependencies [e1840bf]
-
Updated dependencies [c31874d]
- @object-ui/components@12.1.0
- @object-ui/fields@12.1.0
- @object-ui/i18n@12.1.0
- @object-ui/types@12.1.0
- @object-ui/react@12.1.0
- @object-ui/core@12.1.0
- @object-ui/permissions@12.1.0
-
e4de456: Fix form section grouping inconsistencies found in a UX review of grouped forms:
- Unified section visual language.
FormSection's Card-wrapped path (used by Modal/Split/Tabbed/Wizard forms) previously rendered as a nearly-invisible white-on-white card (samebg-cardas the page background, distinguished only by a barely-visible shadow) with a duplicated, inconsistent header (different title size, and a collapse chevron positioned differently) versus the flatSectionDividerpath used by simple/drawer forms. Both now share the same header treatment (text-sm font-semibold, inline-left chevron, bottom border), and the Card path gets a softbg-muted/40tint so grouped sections are visually distinguishable without relying on shadow alone. readonlyno longer renders asdisabled. A field markedreadonly(statically or viareadonlyWhen) was being folded into thedisabledprop before reaching field widgets, so widgets with a dedicated readonly display (e.g.EmailField's mailto link,TextField's plain-text view) never received it — every readonly field just looked permanently disabled.readonlyis now forwarded as its own prop; genericinput/textareafields get a distinct readonly style (bg-muted/40, nocursor-not-allowed) instead of the disabled look.- Section
className/gridClassNamenow flow through JSON schemas.ObjectFormSectionand the per-form-variant section configs (ModalFormSectionConfig,SplitFormSectionConfig,FormSectionConfig,DrawerFormSectionConfig) acceptclassName(andgridClassNamewhere applicable), wired throughObjectForm's form-type dispatch intoFormSection/SectionDivider— closing a gap where section wrappers couldn't be customized from schema despiteFormSectionitself already supporting it.
- Unified section visual language.
- Updated dependencies [226fde9]
- Updated dependencies [e36a9c7]
- Updated dependencies [e4de456]
- Updated dependencies [68e2d1c]
- @object-ui/types@12.0.0
- @object-ui/core@12.0.0
- @object-ui/components@12.0.0
- @object-ui/fields@12.0.0
- @object-ui/permissions@12.0.0
- @object-ui/react@12.0.0
- @object-ui/i18n@12.0.0
-
fae75e2: Fix two bugs verified still-present after #2254 claimed to resolve them (framework#2620 / framework#2616 Showcase UX pass, tracked in #2268):
-
Wizard/form
submitBehavior: 'thank-you'allowed duplicate resubmission. #2254 fixed the spec-bridge droppingsubmitBehaviorbefore it reached the renderer, so the configured toast message started appearing — butWizardForm's last step andObjectForm's submit handler only ever calledtoast.success(...)forthank-you/next-record; the form stayed mounted and fully filled with its submit button re-enabled once the request settled, so a second click created a second record. Both components now track a terminalsubmittedstate and, when set, replace the form with a confirmation panel (using the behavior'stitle/message, which were also never read before) — mirroring the patternapps/console/src/components/FormPage.tsxalready used for its own standalone forms. -
Command Center-style 3-up chart bands stayed collapsed to ~100-130px, and a dataset-bound chart's measure leaked its raw field name.
responsiveStyles(andstyle) were declared on the page-specPageComponentbridge input type but never copied onto theSchemaNodeinspec-bridge/bridges/page.ts::mapComponent()— so a page author's ADR-0065 layout override (e.g. forcingdisplay: 'grid'on atype: 'flex'band) never reachedSchemaRenderer, and the node silently fell back to its default flex layout. Both fields are now mapped through.ObjectChart's dataset-bound fetch path (schema.dataset+ds.queryDataset(...)) discarded the response'sfieldsarray (which carries each measure'slabel, e.g.{ name: 'task_count', label: 'Tasks' }) before it ever reachedbuildChartSeries()— whosefieldsparam already resolves this correctly (seechart-series.test.ts) — so the legend/tooltip always fell back to the raw field name. The fetchedfieldsare now captured and threaded through.
-
-
ec9c8ee: Fix master-detail record create: stop double success toast + localize the Cancel button.
Objects with inline subforms (master-detail, e.g. a Lead with product line items) render
MasterDetailForminsideModalForm/DrawerForminstead of the plain footer, which exposed two mismatches with the host contract:-
Double success toast. Flat
ObjectFormdelegates confirmation to the host when anonSuccessis supplied (skips its own default toast), butMasterDetailForm.handleSavedALWAYS toastedCreated/SavedAND ranonSuccess. In the console the host'sonSuccesschains into thecrud_successhandler, which toasts a localized message — so create fired bothCreatedand e.g.线索创建成功.handleSavednow only toasts as a fallback when no hostonSuccessis provided, matching theObjectFormcontract; saves without a host handler stay non-silent. -
Hardcoded English
Cancel. The master-detail action bar wroteCancelas a literal and accepted nocancelText, so the button stayed English while the submit button was localized (submitTextwas already forwarded).MasterDetailFormnow takescancelText, andModalForm/DrawerForm/ObjectFormforward the host's localized label down the subforms branch.
Adds regression tests: create with a host
onSuccessfires no built-in toast (no double-confirm), and the Cancel button renders the host-suppliedcancelText. -
-
6c1ad9e: Record task flows open as derived overlays with lossless return (framework#2604, extends framework#2578).
- Create/Edit never route — the global record form is URL-driven (
?form=new/?form=<id>): browser Back closes the overlay with the origin (list scroll/filters, detail state) intact; field-heavy objects derive a full-screen modal (modalSize:'full') via the newderiveRecordFlowSurfacemirror in plugin-view, light ones keep the auto-sized modal.editMode:'page'opt-in unchanged. - Save invariant — edit never moves you (origin refetches in place); create lands on the new record's detail on its derived surface (drawer over the still-intact list for light objects, detail route for heavy), with
replace:trueso Back skips the transient form entry. - Subtable child create/edit = overlay over the parent detail, never a route — related-list New/Edit push
?form=…&formObject=<child>&formLink=<fk>:<parentId>; the one global overlay pre-links the parent (refresh-safe), sizes to the CHILD object, and on save stays on the parent while only the child's related lists refetch. ModalForm now forwardsinitialValuesinto its master-detail (subforms) branch so pre-links survive for children with inline line items.
- Create/Edit never route — the global record form is URL-driven (
-
Updated dependencies [544d8eb]
-
Updated dependencies [6fffd3d]
-
Updated dependencies [9255686]
-
Updated dependencies [fae75e2]
-
Updated dependencies [1072701]
- @object-ui/i18n@11.5.0
- @object-ui/react@11.5.0
- @object-ui/components@11.5.0
- @object-ui/types@11.5.0
- @object-ui/fields@11.5.0
- @object-ui/core@11.5.0
- @object-ui/permissions@11.5.0
-
8bf6295: feat: adaptive record surface + semantic field span + responsive columns (framework#2578)
Field-heavy objects (all metadata is AI-authored) now present themselves without any authored presentation config:
- Adaptive surface — a record's create/edit/detail opens as a full page when
the object is field-heavy, or a drawer when it is light. Derived from field
count (
deriveRecordSurface), not authored; mobile always pages. Wired into the app-shell ObjectView detail navigation (an authored view/objectnavigationstill wins). - Semantic field span —
FormField.span(auto/full) is a width primitive decoupled from the (per-surface derived) column count; legacycolSpanis clamped so it never overflows.ObjectFormnow honours per-sectioncolumnsand carriesspan/colSpanfrom section defs — fixes the bug wheretype:'simple'ignoredsection.columnsand grouped fields rendered single column. - Responsive columns —
inferColumnsscales the column CAP with field count (≤3→1, ≤8→2, ≤15→3, 16+→4); the ACTUAL column count follows the form's real width via CSS container queries, so the same form goes 1→2→3→4 columns as a drawer widens or becomes a page. - Runtime overlay width —
NavigationConfig.sizebucket is resolved to a viewport-clamped width at runtime (overlayWidthFor); a pixel width is never authored (the author cannot know the client viewport).
- Adaptive surface — a record's create/edit/detail opens as a full page when
the object is field-heavy, or a drawer when it is light. Derived from field
count (
-
144ab55: Consume the ADR-0085 object semantic roles from
@objectstack/spec@11.7.0, retiring the per-surface hint dialects:- Single-source fieldGroups derivation:
plugin-form'sderiveFieldGroupSectionsandplugin-detail'sderiveFieldGroupDetailSectionsare now thin adapters over the spec'sderiveFieldGroupLayout(ADR-0085 §5) — forms, modals and detail pages render the SAME grouping from one implementation. The canonicalcollapse: 'none' | 'expanded' | 'collapsed'enum is honoured everywhere (deprecatedcollapsible/collapsedanddefaultExpandedspellings still read for pre-11.7 metadata). stageFieldsemantic role: the detail stepper reads the top-levelstageField;stageField: falsenow actually suppresses stage detection (previously thefalsehandling was wired to the removeddetail.stageFieldkey, so spec-authoredfalsefell through to the name heuristic).highlightFieldsrename: default grid columns, card compact views, the detail highlight strip, child-record preview fields and interface-page default columns read the object'shighlightFields(deprecatedcompactLayoutspelling read as fallback for pre-11.7 metadata).- Removed dead reads: the never-spec-writable
objectDef.views.*UI hints and the ADR-0085-removeddetail.*block (sections,sectionGroups,highlightFields,stageField,useFieldGroups,showReferenceRail,hideReferenceRail,hideRelatedTab,relatedLayout) are no longer consulted. Per-page customization goes through an assigned Page schema (record:reference_railremains available there as a renderer capability).detail.renderViaSchemasurvives only as the legacy-renderer kill-switch and is removed together with that path.
- Single-source fieldGroups derivation:
-
c38d107: Fix view-level
FormField.visibleOn(CEL) never taking effect (#2212).The spec ships
visibleOnas an Expression object{ dialect: 'cel', source }(what thePtemplate emits) or a bare string, but the whole chain dropped it:sectionFields.ts/ObjectForm.tsxonly accepted the bare-string shape and attached a deadvisible()closure no renderer ever called — the Expression object shape was silently discarded.- The form renderer destructured
visibleOnout of the field config and never evaluated it. RecordFormPagedropped asimpleform view'ssectionsentirely, so page-mode create/edit fell back to the raw schema (every field, no authored selection/grouping) while the modal path honored the same view.ObjectForm's grouped-sections path matched section fields by name only, dropping per-fieldvisibleOnoverrides.
visibleOnnow flows through normalization verbatim (both wire shapes) and is evaluated reactively by the form renderer with the canonical expression engine (evalFieldPredicate— same engine, record scope, and fail-open semantics as field-levelvisibleWhen; both predicates must allow a field for it to show). Sectioned/flat normalization also copies field-levelvisibleWhen/readonlyWhen/requiredWhenrules it previously lost. -
1e9145d: Hydrate widget types on hand-authored master-detail subform columns. A view can list a child grid's columns as bare
{ field, label }(the common authoring form); previously such untyped columns were passed straight to the grid, so aselect/lookup/date/numberfield silently rendered as a plain text cell.MasterDetailForm(andderiveDetail) now resolve each untyped column'stype(plusoptions/reference/ computedexpr) from the child object's schema via the newhydrateColumnshelper — a picklist becomes a dropdown, a lookup a record picker, a date a date input — while preserving the author's exact column set, order and labels. Columns that already declare atypeare left untouched (the author's explicit choice still wins). -
Updated dependencies [8bf6295]
-
Updated dependencies [1948c5b]
-
Updated dependencies [bce581a]
-
Updated dependencies [9cd9be1]
-
Updated dependencies [5160832]
-
Updated dependencies [69d6b94]
-
Updated dependencies [c38d107]
-
Updated dependencies [243a9ba]
-
Updated dependencies [289be5b]
-
Updated dependencies [7782698]
-
Updated dependencies [19f2533]
-
Updated dependencies [790558b]
-
Updated dependencies [09e1b26]
-
Updated dependencies [e84d64d]
- @object-ui/types@11.4.0
- @object-ui/components@11.4.0
- @object-ui/fields@11.4.0
- @object-ui/i18n@11.4.0
- @object-ui/core@11.4.0
- @object-ui/permissions@11.4.0
- @object-ui/react@11.4.0
- Updated dependencies [d88c8ec]
- Updated dependencies [b7237bb]
- Updated dependencies [d23d6eb]
- @object-ui/components@11.3.0
- @object-ui/i18n@11.3.0
- @object-ui/core@11.3.0
- @object-ui/fields@11.3.0
- @object-ui/react@11.3.0
- @object-ui/types@11.3.0
- @object-ui/permissions@11.3.0
- Updated dependencies [9e7a986]
- Updated dependencies [1311749]
- @object-ui/components@11.2.0
- @object-ui/core@11.2.0
- @object-ui/fields@11.2.0
- @object-ui/react@11.2.0
- @object-ui/types@11.2.0
- @object-ui/i18n@11.2.0
- @object-ui/permissions@11.2.0
- Updated dependencies [6726a2b]
- @object-ui/i18n@11.1.0
- @object-ui/components@11.1.0
- @object-ui/fields@11.1.0
- @object-ui/react@11.1.0
- @object-ui/types@11.1.0
- @object-ui/core@11.1.0
- @object-ui/permissions@11.1.0
- Updated dependencies [788dbf9]
- @object-ui/fields@7.3.0
- @object-ui/types@7.3.0
- @object-ui/core@7.3.0
- @object-ui/react@7.3.0
- @object-ui/components@7.3.0
- @object-ui/permissions@7.3.0
-
4aa8b84: fix(plugin-form): call
useRecordContextunconditionally; drop impure render-timeDate.now()LineItemsPanelwrappeduseRecordContext()in atry/catch, which ESLint flagged asreact-hooks/rules-of-hooks("React Hook is called conditionally") — a genuine hook-order hazard if thecatchever fired part-way through render.useRecordContextreturnsnulloutside a<RecordContextProvider>and never throws, so the guard was dead code; it's now called unconditionally at the top level and thenullcase is handled by the existing optional chaining.Also clears a second pre-existing lint error:
EmbeddableFormnow seedsmountedAtReffrom0instead of calling the impureDate.now()during render (the mount effect already overwrites it before any submit, so the anti-bot min-fill check is unchanged), fixing the react-compiler "Cannot call impure function during render" error. No behavior change. -
Updated dependencies [d23db5c]
- @object-ui/types@7.2.0
- @object-ui/components@7.2.0
- @object-ui/fields@7.2.0
- @object-ui/react@7.2.0
- @object-ui/core@7.2.0
- @object-ui/permissions@7.2.0
-
aae8791: Flow Screen preview: render inline master-detail subforms (follow-up to #1944)
The object-form mode of the Screen-node preview now renders inline master-detail child grids, matching runtime.
ScreenPreviewfeeds the SAME enriched object list the runtimeFlowRunneruses (useMetadata().objects, which derivesform.subformsfrominlineEditrelationships viaattachInlineSubforms), so e.g. ashowcase_invoiceobject-form step previews its Line Items grid (with live Subtotal/Tax/Total) — only fetched in object-form mode.To keep the preview non-persisting — consistent with the flat-field preview (disabled Submit) and the simple object-form preview (no Save) —
MasterDetailFormnow honours ashowSubmitflag (default shown; backward-compatible) thatObjectFormforwards, so the preview hides the master-detail Save bar. Also drops a deade = formDataassignment inObjectForm(lintno-useless-assignment). -
Updated dependencies [677f7ed]
-
Updated dependencies [08c47da]
-
Updated dependencies [a71be60]
-
Updated dependencies [cb03bc3]
- @object-ui/types@7.1.0
- @object-ui/core@7.1.0
- @object-ui/react@7.1.0
- @object-ui/components@7.1.0
- @object-ui/fields@7.1.0
- @object-ui/permissions@7.1.0
-
5332639: feat(app-shell): render full object forms (incl. master-detail) in screen-flow wizard steps
FlowRunnernow renders anobject-formscreen step: when the paused screen carrieskind: 'object-form', it mounts the real<ObjectForm>for the named object (auto-routing toMasterDetailFormfor inline child collections), prefilled from the step'sdefaults. The form persists itself (atomic master-detail batch), then resumes the run with the saved record id bound to the step'sidVariable.dataSource/objectsare threaded through all threeFlowRunnermount points.Also fixes three pre-existing bugs this surfaced (each affects normal forms too):
- plugin-form:
ObjectFormnow forwardsinitialValues/initialDatawhen routing toMasterDetailForm, so prefilled header values are no longer dropped on master-detail create forms. - fields:
PercentFieldtreated values as0–1fractions (value × 100), so a0–100field (e.g.probabilitydefault50) rendered as5000%— exceedingmax=100, which makes HTML5 constraint validation mark the field:invalidand silently block the whole form's submit. It now treats a field declaringmax > 1as the0–100whole-number convention, matching the read-side formatter. - data-objectstack:
ObjectStackAdapter.batchTransactionnow sendscredentials: 'include', so master-detail batch saves authenticate under the console's cookie session (previously every batch save 401'd).
- plugin-form:
-
80c133c: Spreadsheet-style line-item grid editor.
GridField's editable grid mode is reworked into an enterprise line-item editor (the QuickBooks / Stripe / NetSuite pattern), generalised across every inline grid:- Computed read-only columns — a child field with an arithmetic
expression(e.g.amount = quantity * unit_price) renders read-only, recomputes live as its inputs change, and writes the result back into the row so it persists and the running total reflects it. A small safe arithmetic evaluator (+ - * / %, parens,record.<field>refs; noeval) powers it. - Trailing "ghost" row — start-with-one + auto-append: typing in the ghost materialises a real row (index-stable, so focus/caret survive), so you keep entering lines without clicking "Add".
- Borderless click-to-focus cells + role-based column widths (description flexes; qty/price/amount stay narrow).
- Keyboard navigation — Enter / ArrowUp / ArrowDown move between rows in the same column.
- Per-row "expand to full form" is gated to grids that omit fields (no redundant expand on thin lines).
deriveColumnssurfaces a fieldexpressionas a computed column; the running-total column prefers the computed/last-currency column. Blank/ghost rows are filtered from the persisted batch (isBlankRow).
- Computed read-only columns — a child field with an arithmetic
-
d16566f: Atomic master-detail create via the cross-object transactional batch endpoint (ObjectStack #1604).
When the server exposes the transactional batch endpoint, a NEW parent record and its child line items are now persisted in ONE server transaction — commit all or roll back all — instead of the previous client-orchestrated "create parent → create children → best-effort cleanup on failure" sequence.
@object-ui/data-objectstack—ObjectStackAdapter.batchTransaction(operations)- New method posting
{ operations }toPOST /api/v1/batch. Operations run in one server transaction. A field value of{ $ref: <earlier op index> }resolves to that op's generated id, so a child can reference its parent created earlier in the same batch (master-detail FK). ThrowsObjectStackError('BATCH_ERROR')on a non-2xx response.
@object-ui/plugin-formMasterDetailFormnow detectsdataSource.batchTransactionand, on a NEW parent, builds one atomic batch (parent at index 0, each child FK set to{ $ref: 0 }) via the new pure helperbuildMasterDetailBatch. Client-side total rollups are merged into the parent payload before the batch. Edit mode and adapters withoutbatchTransactionkeep the existing client-orchestrated path.ObjectFormgained asubmitHandlerhook: when supplied, the form validates and hands the collected values to the host instead of callingdataSource.create/dataSource.update.MasterDetailFormuses it to own the atomic parent+children write while the parent fields are still rendered byObjectForm.
@object-ui/typesObjectFormSchema.submitHandler?: (values) => any | Promise<any>— typed override for host-owned persistence.
Pairs with the framework-side ambient-transaction fix (ObjectQL
AsyncLocalStoragetransaction propagation) and the/api/v1/batchendpoint added in@objectstack/rest. - New method posting
-
69510df: feat(master-detail): derive child columns + relationship FK from metadata
A master-detail child collection can now be configured with just the child object name — the relationship FK and the editable grid columns are derived from the child object's schema (via
DataSource.getObjectSchema), instead of a hand-authored columns block.// before: ~40 lines of columns + relationshipField details: [{ childObject: 'task', relationshipField: 'project', columns: [ ...12 lines... ] }] // after: details: [{ childObject: 'task' }]
relationshipFieldis auto-detected from the child'smaster_detail/lookupfield that references the parent (master_detail preferred).columnsare derived from the child's fields, skipping system/audit fields, the back-reference FK, and non-editable types (formula/summary/autonumber/ file/json/…); select options and lookup references carry through.amountField(running-total source) defaults to the first numeric/currency column.- Any of these can still be set explicitly to override the derived defaults.
- Save is gated until derivation resolves; new pure helpers
(
deriveDetail/deriveColumns/findRelationshipField) are unit-tested.
-
b148daf: feat(master-detail): atomic EDIT via the cross-object batch endpoint
Edit mode now persists the parent update together with its child line-item create/update/delete diffs in ONE server transaction (commit all or roll back all), matching what create already did. Previously only create used the atomic
/api/v1/batchpath; edit fell back to client-orchestrated writes with best-effort cleanup.- New pure helper
buildMasterDetailEditBatch(parentObject, parentId, parentData, details)— emits a parentupdateop (index 0) then diffs each child collection against its loaded snapshot intocreate/update/deleteops (children reference the known parent id directly, no$ref). MasterDetailFormnow treatscanBatchas available whenever the data source exposesbatchTransaction(create AND edit).submitViaBatchbuilds create-ops or edit-ops by mode;onSuccess→handleSaved("saved" toast, no form reset in edit).
The server
/api/v1/batchhandler already supportsupdate/deleteactions, and the adapter already forwardsaction/id, so this is a front-end change. Unit-tested (parent update + child create/update/delete diff); the create path remains verified by the live e2e. - New pure helper
-
90acb7f: Master-detail subform + lightweight list primitives (SDUI).
MasterDetailForm(object-master-detail-form): enter a parent record and its child line items together; client-orchestrated transactional create (parent → FK → bulk children → rollup → cleanup). Enterprise-convention layout (header on top, line grid, single Save bar at the bottom).LineItemsFieldeditable child grid (line numbers, right-aligned numerics, running total) andLineItemsPanel(record:line_items) for detail-page inline edit.element:definition-listandelement:repeater— lightweight, low-chrome list primitives for simple data.
-
00f8d2d: Master-detail form: live Subtotal / Tax / Total stack.
MasterDetailFormnow renders a right-aligned document totals stack under the line items when the parent form has a tax-rate field (taxRateField, defaulttax_rate): Subtotal (Σ line amounts) → Tax (header rate %) → Total, recomputed live as lines and the rate change. The header rate is read via scoped event delegation on the form host (no coupling intoObjectForminternals). When the stack is shown, the per-grid footer total is subsumed. -
300d755: feat(form): inline master-detail in a plain ObjectForm via
subformsObjectFormSchemagains asubformsarray. When set, a regularobject-formrenders as a master-detail form — the object's own fields on top, an editable grid per child collection below, persisted together in one atomic transaction — without a bespokeobject-master-detail-formpage.{ type: 'object-form', objectName: 'expense_claim', subforms: [{ childObject: 'expense_line' }] } // FK + columns auto-derived
Each subform needs only
childObject(relationship FK and columns are derived from the child object's metadata; override withrelationshipField/columns). This is the config-driven, page-less way to express master-detail entry — a form view can declare its child collections directly. -
18728c1: Master-detail entry: lighter layout, compact lookup cells, persisted line order.
- De-framed line-item section — the subform no longer double-frames the grid in a
Card(border +p-6); it renders as a light label + the grid's own bordered table, reclaiming the width the line table needs. - Compact lookup cells —
LookupFieldgains acompactmode (used by grid cells): the selected value shows inline in a borderless single-line trigger instead of a chip stacked above a separate "Select…" button. - Persisted drag-reorder —
deriveMasterDetaildetects a sort field (position/sort_order/…), excludes it from the editable columns/row-form, and threads it as the grid'ssort_fieldso reordering stampsrow[position] = indexand survives a reload.
- De-framed line-item section — the subform no longer double-frames the grid in a
-
8426db7: feat(form): standard New/Edit modal renders form-view subforms (Tier 0)
The console's standard create/edit record modal now renders inline child collections when the object's form view declares
subforms— master-detail entry with no bespoke page, persisted as one atomic transaction.ModalForm(and the create/edit modal in app-shellAppContent) detectssubformsand rendersMasterDetailForminside the dialog (it owns its Save bar; the modal footer is suppressed); on success the modal closes + refreshes.AppContentsourcessubformsfrom the object's default form view (form.subforms/formViews.default.subforms).ModalFormSchemagainssubforms.
With this, declaring
formViews.default.subforms: [{ childObject }]is enough to make an object's standard New/Edit screen a master-detail form — completing the config-driven master-detail story (Tier 0 → derive everything from the relationship + child metadata).
-
ddbe4a2: B2 step 3: client-side field-level conditional rules (
visibleWhen/readonlyWhen/requiredWhen). The form renderer now evaluates these CEL predicates reactively against the live record and gates each field's visibility, read-only state, and required-ness accordingly. Evaluation delegates to the canonical@objectstack/formulaExpressionEngine— the same dialect the server enforces (requiredWhenin the rule-validator,readonlyWheninstripReadonlyWhenFields) — so the UX and the persisted verdict always agree. New core helpersevalFieldPredicate/resolveFieldRuleState(zero-React, fail-open).FormFieldgainsvisibleWhen/readonlyWhen/requiredWhen(+ deprecatedconditionalRequiredalias), andObjectFormcarries them through from object metadata. -
2d47e94: B2 follow-ups (A): field conditional rules in inline grids + submit-time enforcement.
- Grids: a line-item column's
readonlyWhen/requiredWhenCEL rule is now honored per row —deriveMasterDetailcarries the props onto theGridColumnandGridFieldevaluates them against each row viaresolveFieldRuleState(areadonlyWhen-TRUE cell locks; arequiredWhen-TRUE empty cell flags inline-invalid). Rules are row-scoped (record.*); the core helpers gained an optionalscope(andGridFieldacontextRecordprop) so a future header-driven lock can bindparent.*— that wiring is deferred (it needs the master-detail header's re-renders isolated). - Submit enforcement:
requiredWhenalready drove react-hook-form'srequiredrule, so submit is blocked with a field error when the predicate is TRUE and the value is empty. Added a reactive cleanup so a stale required error clears when the predicate flips FALSE (and all errors clear when a field is hidden byvisibleWhen).
- Grids: a line-item column's
-
f6044fa: feat(form): subforms in DrawerForm + full-page record form (Tier 0 everywhere)
Completes config-driven master-detail across all standard create/edit entry points (after the modal in the previous change):
DrawerFormnow hostsMasterDetailForminside the drawer when the schema declaressubforms(its own Save bar; closes + refreshes on success).RecordFormPage(full-page New/Edit) sourcessubformsfrom the object's form view, so the full-page form renders inline child collections too.ObjectForm's subforms shortcut now defers to the drawer/modal variants for those formTypes (so they keep their envelope), and only renders the master-detail form directly for inline/simple forms.
Declaring
formViews.default.subforms: [{ childObject }]now yields a master-detail experience in the modal, drawer, AND full-page form — no bespoke page anywhere. -
ad8ade6: feat(components): metadata-derived field locators on generated forms (ADR-0054 Phase 4)
The form renderer now emits a stable
data-testid="field:{objectName}.{field}"(plusdata-field) on every field wrapper, derived from the form'sobjectNameand each field's name — closing the locator gap at the source so every generated form (ObjectForm/ModalForm/DrawerForm/SplitForm/WizardForm) inherits testable fields with zero per-app work (ADR-0054 C4).FormSchemagains an optionalobjectName; the object prefix is omitted (field:{field}) when a form has none.FormItemnow acceptsdata-*attributes. -
3870c20: feat(forms): declarative
navigateOnSuccess+resetOnSuccesson object-formRounds out declarative success behavior for metadata-only forms (which can't pass an
onSuccessfunction), complementingsuccessMessage:navigateOnSuccess— after a successful create/update, navigate here. Supports{id}/{recordId}interpolation from the saved record and is same-origin-guarded; takes precedence over the toast (landing on the record is the confirmation).resetOnSuccess— after a successful create, reset the form for another entry (the wizard returns to a cleared step 1). Ignored when navigating.
Wired in both ObjectForm and WizardForm via a small shared
successBehaviorhelper (kept dependency-free to avoid an EmbeddableForm import cycle). -
b88c560: feat(forms): declarative
successMessageon object-formMetadata-only forms (a wizard/object-form authored as JSON) cannot pass an
onSuccessfunction, so the post-create/update feedback was a fixed "Created"/"Saved" toast.ObjectFormSchemanow acceptssuccessMessage, which ObjectForm and WizardForm use for the default success toast when noonSuccesshandler is supplied. Falls back to "Created"/"Saved". -
7913390: fix(master-detail): never silent on save — feedback, reset, and a duplicate-submit guard
MasterDetailForm's "Create" submitted successfully but gave no feedback: no toast, no form reset, no navigation. A successful create looked broken, and re-clicking created duplicate records.- On success: a
toast.success, and on create the form clears (line items reset + parent<ObjectForm>remounts) ready for the next entry. A page-suppliedonSuccessstill runs afterwards (e.g. to navigate). - On failure (validation / network / atomic rollback): a
toast.errorsurfaces the message instead of failing silently. - In-flight guard: the Create button shows "Saving…" and is disabled while a submit is running, preventing duplicate submissions, with a safety release if client-side validation blocks the submit.
@object-ui/componentsnow re-exportstoast(alongsideToaster) from its sonner wrapper.
Tests: two new
MasterDetailFormtests assert success → toast + form clear, and failure → error toast. - On success: a
-
514f426: fix(master-detail): reliable submit + stable e2e hooks
Fixes the "click Create, nothing happens" report, surfaced by a new live browser e2e harness that drives the form with real input.
- MasterDetailForm
handleSavenow triggers the button-less parent form's submit from a deferred macrotask and re-queries the live<form>inside it. CallingrequestSubmit()synchronously inside the click handler (right after thesetSavingstate update) intermittently dropped the nested submit event, so react-hook-form'sonSubmitnever ran and the click appeared to do nothing — only the occasional click got through. Deferring makes it fire every time. - Stable
data-testids so automation/e2e can drive the widgets deterministically (Radix Select + react-hook-form cannot be driven by synthetic DOM events):select-trigger-{field}/select-option-{value}(SelectField),lookup-trigger-{field}(LookupField),line-items-add(GridField),md-form-submit/md-form-cancel(MasterDetailForm).
- MasterDetailForm
-
586a027: B2 follow-up (#1581): parent-scoped conditional rules in inline grids — "paid invoice → lock lines".
MasterDetailFormnow binds the live header record to every line-item grid asparent, so a column'sreadonlyWhen/requiredWhenCEL rule can react to the header (e.g.parent.status == 'paid'locks quantity / unit price / product when the invoice is paid). The line grids + document totals moved into a dedicated<MasterDetailLines>child that owns the scraped header record, so a header edit re-renders only the lines and never resets the headerObjectForm's react-hook-form state mid-edit; the scrape is deduped by value to avoid needless churn. (@object-ui/fields'GridField.contextRecordand column-rule derivation already existed — this wires the last link.) -
9aac2b8: feat(form): modal forms can host a tabbed layout (modal + tabbed composes)
ModalFormrendered sections as a flat vertical stack — a modal create/edit form could never be tabbed, becauseformType(one field) couldn't be bothmodal(container) andtabbed(layout). Per ADR-0050 (additive first), the modal container now accepts acontentLayout('simple' | 'tabbed'): whentabbed, sections render as tabs inside the dialog. The console record New/Edit modal (AppContent) forwards the default form view's layout, so atype:'tabbed'form view now renders tabbed in the modal too — not just on the full-page route (#1762). Non-breaking;FormView.typeenum unchanged.Refs objectstack-ai/objectstack#1890, ADR-0050
-
650bd1f: fix(forms/dashboard/related-list): four business-facing rendering fixes found while QA-ing a showcase workspace
- plugin-form / WizardForm: a multi-step
object-formwithformType: 'wizard'posted an empty/partial body on submit, so the server rejected every required field. Two causes: (1) the footer Next/Create buttons bypassed the inner form and submitted the wizard's own (never-collected)formData; (2) the create-mode data-seeding effect re-ran ondataSource/objectSchemaidentity churn and resetformDatato{}mid-wizard. Now the buttons submit the inner form natively (<form id>+type="submit", which validates each step and collects values viagetValues()), and the create seed is made idempotent. - plugin-dashboard / DashboardRenderer: chart widgets rendered as empty cards (recharts logged
width(-1) height(-1)) because the positioned grid usedauto-rows-min, collapsing any widget with no intrinsic height. The explicit-columns grid now usesgridAutoRows: minmax(5rem, auto)so spanned chart rows get a real height while tables can still grow. - plugin-detail / RelatedList: auto-derived related-list columns led with system audit fields (
created_at,updated_at, …) for child objects without a name/title field, pushing business columns past the column cap. System audit fields are now sorted last. - plugin-form / ObjectForm + WizardForm: a successful create/update gave no feedback for metadata-only pages (which can't pass an
onSuccessfunction). They now show a defaulttoast.success('Created'/'Saved')when noonSuccesshandler is supplied (guarded so asubmitHandlerhost like MasterDetailForm never double-toasts).
- plugin-form / WizardForm: a multi-step
-
Updated dependencies [5976ba3]
-
Updated dependencies [a00e16d]
-
Updated dependencies [eaccefd]
-
Updated dependencies [f7f325d]
-
Updated dependencies [c12986e]
-
Updated dependencies [71d7ce0]
-
Updated dependencies [053c948]
-
Updated dependencies [ddbe4a2]
-
Updated dependencies [2d47e94]
-
Updated dependencies [9049bbe]
-
Updated dependencies [6c0c92c]
-
Updated dependencies [cb2fdb1]
-
Updated dependencies [c3749eb]
-
Updated dependencies [6cfa330]
-
Updated dependencies [ad8ade6]
-
Updated dependencies [d54346c]
-
Updated dependencies [5332639]
-
Updated dependencies [3870c20]
-
Updated dependencies [2eb3096]
-
Updated dependencies [b88c560]
-
Updated dependencies [bd398df]
-
Updated dependencies [66ed3ad]
-
Updated dependencies [c6445b6]
-
Updated dependencies [80c133c]
-
Updated dependencies [5e1b838]
-
Updated dependencies [d16566f]
-
Updated dependencies [90acb7f]
-
Updated dependencies [7913390]
-
Updated dependencies [514f426]
-
Updated dependencies [1394e34]
-
Updated dependencies [e95cc25]
-
Updated dependencies [abe8ebc]
-
Updated dependencies [300d755]
-
Updated dependencies [bd8b054]
-
Updated dependencies [4eb9cb6]
-
Updated dependencies [7c239fd]
-
Updated dependencies [858ad94]
-
Updated dependencies [2270239]
-
Updated dependencies [db8cd00]
-
Updated dependencies [18728c1]
-
Updated dependencies [8d1195d]
- @object-ui/core@7.0.0
- @object-ui/components@7.0.0
- @object-ui/react@7.0.0
- @object-ui/types@7.0.0
- @object-ui/fields@7.0.0
- @object-ui/permissions@7.0.0
- @object-ui/types@6.2.3
- @object-ui/core@6.2.3
- @object-ui/react@6.2.3
- @object-ui/components@6.2.3
- @object-ui/fields@6.2.3
- @object-ui/permissions@6.2.3
- Updated dependencies [a66f788]
- @object-ui/react@6.2.2
- @object-ui/components@6.2.2
- @object-ui/fields@6.2.2
- @object-ui/types@6.2.2
- @object-ui/core@6.2.2
- @object-ui/permissions@6.2.2
- @object-ui/types@6.2.1
- @object-ui/core@6.2.1
- @object-ui/react@6.2.1
- @object-ui/components@6.2.1
- @object-ui/fields@6.2.1
- @object-ui/permissions@6.2.1
- @object-ui/react@6.2.0
- @object-ui/components@6.2.0
- @object-ui/fields@6.2.0
- @object-ui/types@6.2.0
- @object-ui/core@6.2.0
- @object-ui/permissions@6.2.0
- Updated dependencies [991b62d]
- @object-ui/core@6.1.0
- @object-ui/types@6.1.0
- @object-ui/components@6.1.0
- @object-ui/fields@6.1.0
- @object-ui/react@6.1.0
- @object-ui/permissions@6.1.0
- @object-ui/types@6.0.4
- @object-ui/core@6.0.4
- @object-ui/react@6.0.4
- @object-ui/components@6.0.4
- @object-ui/fields@6.0.4
- @object-ui/permissions@6.0.4
- @object-ui/types@6.0.3
- @object-ui/core@6.0.3
- @object-ui/react@6.0.3
- @object-ui/components@6.0.3
- @object-ui/fields@6.0.3
- @object-ui/permissions@6.0.3
- @object-ui/types@6.0.2
- @object-ui/core@6.0.2
- @object-ui/react@6.0.2
- @object-ui/components@6.0.2
- @object-ui/fields@6.0.2
- @object-ui/permissions@6.0.2
- @object-ui/types@6.0.1
- @object-ui/core@6.0.1
- @object-ui/react@6.0.1
- @object-ui/components@6.0.1
- @object-ui/fields@6.0.1
- @object-ui/permissions@6.0.1
- @object-ui/types@6.0.0
- @object-ui/core@6.0.0
- @object-ui/react@6.0.0
- @object-ui/components@6.0.0
- @object-ui/fields@6.0.0
- @object-ui/permissions@6.0.0
- @object-ui/types@5.4.2
- @object-ui/core@5.4.2
- @object-ui/react@5.4.2
- @object-ui/components@5.4.2
- @object-ui/fields@5.4.2
- @object-ui/permissions@5.4.2
- @object-ui/types@5.4.1
- @object-ui/core@5.4.1
- @object-ui/react@5.4.1
- @object-ui/components@5.4.1
- @object-ui/fields@5.4.1
- @object-ui/permissions@5.4.1
- Updated dependencies [3a8c754]
- @object-ui/types@5.4.0
- @object-ui/components@5.4.0
- @object-ui/core@5.4.0
- @object-ui/fields@5.4.0
- @object-ui/permissions@5.4.0
- @object-ui/react@5.4.0
- @object-ui/types@5.3.2
- @object-ui/core@5.3.2
- @object-ui/react@5.3.2
- @object-ui/components@5.3.2
- @object-ui/fields@5.3.2
- @object-ui/permissions@5.3.2
- @object-ui/types@5.3.1
- @object-ui/core@5.3.1
- @object-ui/react@5.3.1
- @object-ui/components@5.3.1
- @object-ui/fields@5.3.1
- @object-ui/permissions@5.3.1
- @object-ui/types@5.3.0
- @object-ui/core@5.3.0
- @object-ui/react@5.3.0
- @object-ui/components@5.3.0
- @object-ui/fields@5.3.0
- @object-ui/permissions@5.3.0
- @object-ui/types@5.2.1
- @object-ui/core@5.2.1
- @object-ui/react@5.2.1
- @object-ui/components@5.2.1
- @object-ui/fields@5.2.1
- @object-ui/permissions@5.2.1
- Updated dependencies [de0c5e6]
- Updated dependencies [9997cae]
- Updated dependencies [b2d1704]
- Updated dependencies [6c3f018]
- Updated dependencies [d912a60]
- Updated dependencies [87bc8ff]
- Updated dependencies [3ebba63]
- Updated dependencies [e919433]
- Updated dependencies [a8d12ec]
- Updated dependencies [70b5570]
- Updated dependencies [aa063db]
- Updated dependencies [d9c3bae]
- Updated dependencies [d1442e3]
- Updated dependencies [7c7400a]
- @object-ui/types@5.2.0
- @object-ui/core@5.2.0
- @object-ui/react@5.2.0
- @object-ui/fields@5.2.0
- @object-ui/components@5.2.0
- @object-ui/permissions@5.2.0
- Updated dependencies [8955b9c]
- @object-ui/components@5.1.1
- @object-ui/fields@5.1.1
- @object-ui/types@5.1.1
- @object-ui/core@5.1.1
- @object-ui/react@5.1.1
- @object-ui/permissions@5.1.1
- c0b236f: Platform detail/form polish:
- Auto-section grouping: When an object has no authored
views.form.sections, the detail page now splits fields into a primary section and a collapsible "More details" section based on a field-type/name heuristic (textarea / markdown / description / notes / remarks). Eliminates the wall-of-fields layout on objects without explicit detail metadata. - FormSection card chrome:
FormSectionnow acceptsshowBorder. Defaults totruefor titled sections (Card wrapper) andfalsefor untitled sections (flat). Same auto-default already applied toDetailSection. - Origin breadcrumb: Navigating from a list/kanban into a record now records the source view; the detail page shows a
← <view label>back-link above the page header. - New i18n key
detail.sectionMoreDetails(en + zh-CN).
- Auto-section grouping: When an object has no authored
- Updated dependencies [bd8447d]
- Updated dependencies [fbd5052]
- Updated dependencies [d51a577]
- Updated dependencies [d1ec6a2]
- Updated dependencies [cf30cc2]
- Updated dependencies [5b80cfd]
- Updated dependencies [d548d6b]
- @object-ui/components@5.1.0
- @object-ui/react@5.1.0
- @object-ui/types@5.1.0
- @object-ui/core@5.1.0
- @object-ui/fields@5.1.0
- @object-ui/permissions@5.1.0
- a311e22: Fix EmbeddableForm rendering no inputs on the public-form path. When the
caller passes a
fields: string[]list (e.g. the response fromGET /api/v1/forms/:slug) the innerObjectFormnow receives a read-only wrapper of the data source — preservinggetObjectSchema()so it can materialise widgets, while neutralising mutating ops so all backend writes still go throughEmbeddableForm.handleSubmit(and its consent / honeypot / min-fill / redirect / payload-sanitisation gates).- @object-ui/components@5.0.2
- @object-ui/fields@5.0.2
- @object-ui/react@5.0.2
- @object-ui/types@5.0.2
- @object-ui/core@5.0.2
- @object-ui/permissions@5.0.2
- @object-ui/types@5.0.1
- @object-ui/core@5.0.1
- @object-ui/react@5.0.1
- @object-ui/components@5.0.1
- @object-ui/fields@5.0.1
- @object-ui/permissions@5.0.1
- Updated dependencies [8930b15]
- Updated dependencies [95b6b21]
- Updated dependencies [ddb08a7]
- Updated dependencies [765d50f]
- Updated dependencies [927187a]
- Updated dependencies [bae8ba8]
- Updated dependencies [8435860]
- Updated dependencies [bb2ea48]
- Updated dependencies [b14fe09]
- Updated dependencies [a7bef6e]
- Updated dependencies [74962b0]
- Updated dependencies [3154334]
- Updated dependencies [fa4c2cb]
- Updated dependencies [7213027]
- @object-ui/components@5.0.0
- @object-ui/react@5.0.0
- @object-ui/types@5.0.0
- @object-ui/fields@5.0.0
- @object-ui/core@5.0.0
- @object-ui/permissions@5.0.0
- @object-ui/types@4.8.0
- @object-ui/core@4.8.0
- @object-ui/react@4.8.0
- @object-ui/components@4.8.0
- @object-ui/fields@4.8.0
- @object-ui/permissions@4.8.0
- @object-ui/types@4.7.0
- @object-ui/core@4.7.0
- @object-ui/react@4.7.0
- @object-ui/components@4.7.0
- @object-ui/fields@4.7.0
- @object-ui/permissions@4.7.0
- Updated dependencies [3ee436d]
- @object-ui/components@4.6.0
- @object-ui/fields@4.6.0
- @object-ui/types@4.6.0
- @object-ui/core@4.6.0
- @object-ui/react@4.6.0
- @object-ui/permissions@4.6.0
- 6b6afd1: ModalForm / SplitForm / WizardForm now honor field-level
visibleOn(CEL expression on inline fields) andvisible_on(object schema mirror) inside their section-mode rendering. Previously only flat-field forms via ObjectForm respected the expression; section-mode dropped it so conditional fields always rendered. - Updated dependencies [ab5e281]
- Updated dependencies [d714e85]
- Updated dependencies [6b6afd1]
- Updated dependencies [aa7855f]
- Updated dependencies [170d89f]
- @object-ui/types@4.5.0
- @object-ui/fields@4.5.0
- @object-ui/components@4.5.0
- @object-ui/core@4.5.0
- @object-ui/permissions@4.5.0
- @object-ui/react@4.5.0
- Updated dependencies [63eb66d]
- Updated dependencies [2bd45af]
- @object-ui/fields@4.4.0
- @object-ui/components@4.4.0
- @object-ui/types@4.4.0
- @object-ui/core@4.4.0
- @object-ui/react@4.4.0
- @object-ui/permissions@4.4.0
- Updated dependencies [6b683c8]
- @object-ui/components@4.3.1
- @object-ui/fields@4.3.1
- @object-ui/react@4.3.1
- @object-ui/types@4.3.1
- @object-ui/core@4.3.1
- @object-ui/permissions@4.3.1
- Updated dependencies [4e7bc1b]
- Updated dependencies [8442c05]
- @object-ui/components@4.3.0
- @object-ui/fields@4.3.0
- @object-ui/react@4.3.0
- @object-ui/types@4.3.0
- @object-ui/core@4.3.0
- @object-ui/types@4.2.1
- @object-ui/core@4.2.1
- @object-ui/react@4.2.1
- @object-ui/components@4.2.1
- @object-ui/fields@4.2.1
- @object-ui/components@4.2.0
- @object-ui/fields@4.2.0
- @object-ui/react@4.2.0
- @object-ui/types@4.2.0
- @object-ui/core@4.2.0
- @object-ui/types@4.1.0
- @object-ui/core@4.1.0
- @object-ui/react@4.1.0
- @object-ui/components@4.1.0
- @object-ui/fields@4.1.0
- @object-ui/types@4.0.12
- @object-ui/core@4.0.12
- @object-ui/react@4.0.12
- @object-ui/components@4.0.12
- @object-ui/fields@4.0.12
- @object-ui/components@4.0.11
- @object-ui/fields@4.0.11
- @object-ui/react@4.0.11
- @object-ui/types@4.0.11
- @object-ui/core@4.0.11
- @object-ui/types@4.0.10
- @object-ui/core@4.0.10
- @object-ui/react@4.0.10
- @object-ui/components@4.0.10
- @object-ui/fields@4.0.10
- @object-ui/types@4.0.9
- @object-ui/core@4.0.9
- @object-ui/react@4.0.9
- @object-ui/components@4.0.9
- @object-ui/fields@4.0.9
- @object-ui/components@4.0.8
- @object-ui/fields@4.0.8
- @object-ui/react@4.0.8
- @object-ui/types@4.0.8
- @object-ui/core@4.0.8
- Updated dependencies [7c9b85c]
- @object-ui/core@4.0.7
- @object-ui/react@4.0.7
- @object-ui/components@4.0.7
- @object-ui/fields@4.0.7
- @object-ui/types@4.0.7
-
89ae109: Fix click navigation and required-FK form rendering
- plugin-grid: ObjectGrid's
getSelectFields()now always includesidin the SELECT projection. Previously, when a view configuredcolumnswithoutid, the SQL driver stripped it from results, and row-click handlers silently no-oped becauserecord.idwas undefined. - plugin-form / fields: Master-detail fields now render as a single-value
lookup picker (
LookupField) in create/edit forms instead of a one-to-many related-list widget. From the child-side, master-detail is the FK to the parent record and is typically NOT NULL — it must appear in forms. Prior behavior dropped it via the auto-layout exclusion list, which caused server errors like "NOT NULL constraint failed: contact.account" when users tried to create child records.
- plugin-grid: ObjectGrid's
-
Updated dependencies [89ae109]
-
Updated dependencies [925051d]
-
Updated dependencies [1b6dc64]
- @object-ui/fields@4.0.6
- @object-ui/components@4.0.6
- @object-ui/types@4.0.6
- @object-ui/core@4.0.6
- @object-ui/react@4.0.6
-
1dc6061: fix(build): inline dynamic imports in library outputs
Library
vite build --liboutputs were emitting separate code-split chunks (rolldown-runtime-*.js,LookupField-*.js, etc.) when source files usedReact.lazy()/ dynamicimport(). When consumer apps re-bundled these multi-file dists, the library's per-chunk rolldown-runtime collided with the consumer's own runtime, causing "TypeError: i is not a function" at runtime when lazy components tried to register themselves (e.g. TextField in@object-ui/fieldsafter 4.0.4).Adding
output.inlineDynamicImports: trueto all@object-ui/*library vite configs forces a singledist/index.jsper package, which lets consumer bundlers handle the library as an opaque ESM module without identifier mismatches across chunks.Affected packages: components, fields, layout, plugin-aggrid, plugin-ai, plugin-calendar, plugin-charts, plugin-chatbot, plugin-dashboard, plugin-designer, plugin-detail, plugin-editor, plugin-form, plugin-gantt, plugin-grid, plugin-kanban, plugin-list, plugin-map, plugin-markdown, plugin-report, plugin-timeline, plugin-view, plugin-workflow.
-
Updated dependencies [1dc6061]
- @object-ui/components@4.0.5
- @object-ui/fields@4.0.5
- @object-ui/types@4.0.5
- @object-ui/core@4.0.5
- @object-ui/react@4.0.5
-
d2b6ece: fix: externalize all bare imports in library builds
Library builds (vite lib mode) now externalize every non-relative import instead of bundling third-party CJS dependencies into the published dist. This avoids inlined
require("react")/require("react-dom")calls that causeCalling \require` for "react" in an environment that doesn't expose the `require` function` runtime errors when consumer apps re-bundle the published dist.Specifically fixes:
@object-ui/plugin-dashboardno longer inlinesreact-grid-layout(and its transitivereact-draggable/react-resizableCJS bundles).react-grid-layoutis now declared as a peer dependency so consumers install a single ESM-friendly copy.@object-ui/components,@object-ui/plugin-calendar,@object-ui/plugin-charts,@object-ui/plugin-designerno longer inlinereact-i18next/i18next/use-sync-external-storeCJS shims.- All plugin packages now use a unified
external: (id) => !/^[./]/.test(id) && !id.startsWith(__dirname)rule, ensuring future additions of CJS deps are automatically externalized.
-
Updated dependencies [d2b6ece]
- @object-ui/components@4.0.4
- @object-ui/fields@4.0.4
- @object-ui/types@4.0.4
- @object-ui/core@4.0.4
- @object-ui/react@4.0.4
-
4be43e2: Page-mode record forms (
editMode: 'page'). New per-object metadata flag that opts a record's create/edit form into a dedicated full-screen route (/apps/:appName/:objectName/new,/apps/:appName/:objectName/record/:recordId/edit). Two new declarative actionsnavigate_createandnavigate_editopen these routes from JSON action buttons. Default modal behavior is preserved for objects that do not seteditMode.@object-ui/plugin-list&@object-ui/plugin-detail:ComponentRegistrysingleton fix. Both plugins' Vite configs now mark all@object-ui/*packages as external so each plugin no longer bundles its own private copy of@object-ui/core. Cross-plugin component lookups now resolve correctly from the same singleton registry.plugin-listdist shrank from multi-MB to 67 kB (gzip 16 kB);plugin-detailto 124 kB (gzip 28 kB).@object-ui/app-shellCreateViewDialogchurn fix.existingSetis now memoised on the joined string key ofexistingLabelsrather than the raw array reference, preventing the name-suggestuseEffectfrom re-firing on every parent render.CI fixes.
ReportViewerconditional-formatting test now accepts bothrgb(...)and hex color representations.ObjectViewi18n mocks rewritten to mirror the real hook shapes (useObjectTranslation,useObjectLabel). -
Updated dependencies [4be43e2]
- @object-ui/types@4.0.3
- @object-ui/core@4.0.3
- @object-ui/react@4.0.3
- @object-ui/components@4.0.3
- @object-ui/fields@4.0.3
- @object-ui/types@4.0.1
- @object-ui/core@4.0.1
- @object-ui/react@4.0.1
- @object-ui/components@4.0.1
- @object-ui/fields@4.0.1
- Updated dependencies
- @object-ui/types@4.0.0
- @object-ui/components@4.0.0
- @object-ui/core@4.0.0
- @object-ui/fields@4.0.0
- @object-ui/react@4.0.0
-
de881ef: Mobile UX round 3 — Form: sticky save bar, fullscreen long-text editor, and auto-stepper for long forms on small viewports.
@object-ui/types—ObjectFormSchema.mobile(new) lets a single form opt into all three behaviours:{ type: 'object-form', objectName: 'leads', mode: 'create', mobile: { stickyActions: true, // pin Submit/Cancel to bottom on phones stepper: 'auto', // long forms render one field per step stepperMinFields: 8, // …but only past this many fields stepperFieldsPerStep: 1, // … (default 1) fullscreenLongText: true, // textarea fields get an "expand" affordance }, }
FormSchema.mobileStickyActions(new) is the lower-level escape hatch — applied automatically whenmobile.stickyActionsis set onObjectFormSchema.@object-ui/plugin-form—ObjectFormnow:- propagates
mobile.fullscreenLongTextto every textarea/markdown/html field asmobile_fullscreen: true, - sets
mobileStickyActionson the inner form schema and addspb-20padding so content isn't covered by the fixed bar, - when
mobile.stepper === true(or'auto'+useIsMobile()+ >stepperMinFieldsfields), routes the flat field list through the existingWizardFormwith synthetic single-field "steps" — keeping per-step validation and the existingNext/Back/Submitflow.
@object-ui/components— the registeredformrenderer adds:- a
mobileStickyActionsopt-in that turns the action row into aposition: sticky; bottom: 0bar on small viewports, and - an inline
FullscreenTextareawrapper used when no field-package widget is registered, providing the same expand-button + edit-dialog UX so the feature works even in lighter setups.
@object-ui/fields—TextAreaFieldships the actual fullscreen UX: a top-rightMaximize2button opens a near-fullscreenDialogcontaining a full-heightTextareawith a draft-then-commit save model (Cancel reverts).All three behaviours are off by default — existing forms render unchanged.
- propagates
-
Updated dependencies [a2d7023]
-
Updated dependencies [f1ca238]
-
Updated dependencies [de881ef]
- @object-ui/components@3.4.0
- @object-ui/fields@3.4.0
- @object-ui/types@3.4.0
- @object-ui/core@3.4.0
- @object-ui/react@3.4.0
- @object-ui/types@3.3.2
- @object-ui/core@3.3.2
- @object-ui/react@3.3.2
- @object-ui/components@3.3.2
- @object-ui/fields@3.3.2
- Updated dependencies [b429568]
- @object-ui/components@3.3.1
- @object-ui/fields@3.3.1
- @object-ui/types@3.3.1
- @object-ui/core@3.3.1
- @object-ui/react@3.3.1
- @object-ui/types@3.3.0
- @object-ui/core@3.3.0
- @object-ui/react@3.3.0
- @object-ui/components@3.3.0
- @object-ui/fields@3.3.0
- @object-ui/types@3.2.0
- @object-ui/core@3.2.0
- @object-ui/react@3.2.0
- @object-ui/components@3.2.0
- @object-ui/fields@3.2.0
- @object-ui/react@3.1.5
- @object-ui/components@3.1.5
- @object-ui/fields@3.1.5
- @object-ui/types@3.1.5
- @object-ui/core@3.1.5
- @object-ui/types@3.1.4
- @object-ui/core@3.1.4
- @object-ui/react@3.1.4
- @object-ui/components@3.1.4
- @object-ui/fields@3.1.4
- @object-ui/types@3.1.3
- @object-ui/core@3.1.3
- @object-ui/react@3.1.3
- @object-ui/components@3.1.3
- @object-ui/fields@3.1.3
- @object-ui/types@3.1.2
- @object-ui/core@3.1.2
- @object-ui/react@3.1.2
- @object-ui/components@3.1.2
- @object-ui/fields@3.1.2
- Updated dependencies
- @object-ui/types@3.1.1
- @object-ui/components@3.1.1
- @object-ui/core@3.1.1
- @object-ui/fields@3.1.1
- @object-ui/react@3.1.1
- @object-ui/types@3.0.3
- @object-ui/core@3.0.3
- @object-ui/react@3.0.3
- @object-ui/components@3.0.3
- @object-ui/fields@3.0.3
- @object-ui/types@3.0.2
- @object-ui/core@3.0.2
- @object-ui/react@3.0.2
- @object-ui/components@3.0.2
- @object-ui/fields@3.0.2
- Updated dependencies [adf2cc0]
- @object-ui/react@3.0.1
- @object-ui/components@3.0.1
- @object-ui/fields@3.0.1
- @object-ui/types@3.0.1
- @object-ui/core@3.0.1
- 87979c3: Upgrade to @objectstack v3.0.0 and console bundle optimization
- Upgraded all @objectstack/* packages from ^2.0.7 to ^3.0.0
- Breaking change migrations: Hub → Cloud namespace, definePlugin removed, PaginatedResult.value → .records, PaginatedResult.count → .total, client.meta.getObject() → client.meta.getItem()
- Console bundle optimization: split monolithic 3.7 MB chunk into 17 granular cacheable chunks (95% main entry reduction)
- Added gzip + brotli pre-compression via vite-plugin-compression2
- Lazy MSW loading for build:server (~150 KB gzip saved)
- Added bundle analysis with rollup-plugin-visualizer
- Updated dependencies [87979c3]
- @object-ui/types@3.0.0
- @object-ui/core@3.0.0
- @object-ui/react@3.0.0
- @object-ui/components@3.0.0
- @object-ui/fields@3.0.0
- b859617: Release v1.0.0 — unify all package versions to 1.0.0
- Updated dependencies [b859617]
- @object-ui/types@2.0.0
- @object-ui/core@2.0.0
- @object-ui/react@2.0.0
- @object-ui/components@2.0.0
- @object-ui/fields@2.0.0
- Maintenance release - Documentation and build improvements
- Updated dependencies
- @object-ui/types@0.3.1
- @object-ui/core@0.3.1
- @object-ui/react@0.3.1
- @object-ui/components@0.3.1
- @object-ui/fields@0.3.1