Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .changeset/field-inline-edit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
'@objectstack/spec': minor
---

feat(spec): `inlineEdit` on relationship fields for declarative master-detail

A `master_detail`/`lookup` field can now declare `inlineEdit: true` (plus
optional `inlineTitle` / `inlineColumns` / `inlineAmountField`) to mean "these
child records are entered/edited inline within the parent's form". The intent
lives in the data model: the parent's standard create/edit form then renders an
atomic master-detail form (object fields + an editable child grid) with no form
view config and no bespoke page. Use for line-item/composition children; leave
off for associations (comments, attachments). Renderer support is in objectui.
6 changes: 5 additions & 1 deletion examples/app-showcase/src/objects/task.object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ export const Task = ObjectSchema.create({

fields: {
title: Field.text({ label: 'Title', required: true, searchable: true, maxLength: 200 }),
project: Field.masterDetail('showcase_project', { label: 'Project', required: true }),
// `inlineEdit` declares (in the data model) that tasks are entered inline
// within their project's form — so the standard New/Edit Project form
// auto-renders an atomic Tasks subtable, with no form view config and no
// bespoke page.
project: Field.masterDetail('showcase_project', { label: 'Project', required: true, inlineEdit: true, inlineTitle: 'Tasks' }),
assignee: Field.text({ label: 'Assignee', maxLength: 200 }),
status: Field.select({
label: 'Status',
Expand Down
8 changes: 4 additions & 4 deletions examples/app-showcase/src/views/project.view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ export const ProjectViews = defineView({
{ label: 'Project', columns: 2, fields: ['name', 'account', 'status', 'health', 'owner'] },
{ label: 'Budget & Schedule', columns: 2, fields: ['budget', 'spent', 'start_date', 'end_date'] },
],
// Config-driven master-detail (Tier 0): the standard New/Edit Project form
// renders its Tasks inline (FK + columns derived from showcase_task),
// saved as one atomic transaction — no bespoke page.
subforms: [{ childObject: 'showcase_task', title: 'Tasks', addLabel: 'Add task' }],
// No subforms here: the Tasks subtable is derived from the data model —
// showcase_task.project declares `inlineEdit: true`, so every standard
// Project form auto-renders it. (A view could still add `subforms` to
// override the derived columns/order.)
},
},
});
17 changes: 17 additions & 0 deletions packages/spec/src/data/field.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,23 @@ export const FieldSchema = lazySchema(() => z.object({
referenceFilters: z.array(z.string()).optional().describe('Filters applied to lookup dialogs (e.g. "active = true")'),
writeRequiresMasterRead: z.boolean().optional().describe('If true, user needs read access to master record to edit this field'),
deleteBehavior: z.enum(['set_null', 'cascade', 'restrict']).optional().default('set_null').describe('What happens if referenced record is deleted'),
/**
* Master-detail INLINE EDITING. On a child's `master_detail`/`lookup` field
* (whose `reference` is the parent object), set `inlineEdit: true` to declare
* "this child is entered/edited inline within the parent's form". The
* parent's standard create/edit form then renders an editable grid for these
* children and saves parent + children in ONE atomic transaction — no form
* view config and no bespoke page. The intent lives here in the data model;
* forms derive the UI. Use for true line-item/composition children (invoice
* lines, order items); leave off for associations (comments, attachments).
*/
inlineEdit: z.boolean().optional().describe('Edit these child records inline within the parent object\'s form (atomic master-detail).'),
/** Optional section title for the inline grid (defaults to the child object label). */
inlineTitle: z.string().optional().describe('Title for the inline master-detail grid'),
/** Optional explicit grid columns for the inline editor (derived from the child object when omitted). */
inlineColumns: z.array(z.any()).optional().describe('Explicit columns for the inline grid (derived from the child object when omitted)'),
/** Optional numeric child field summed for the inline grid running total. */
inlineAmountField: z.string().optional().describe('Numeric child field summed for the inline grid total'),

/** Calculation — CEL formula. Plain string accepted for back-compat; build emits canonical envelope. */
expression: ExpressionInputSchema.optional().describe('Formula expression (CEL). e.g. F`record.amount * 0.1`'),
Expand Down
Loading