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
12 changes: 7 additions & 5 deletions content/docs/guides/data-modeling.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -549,8 +549,8 @@ export const Account = ObjectSchema.create({

fieldGroups: [
{ key: 'contact_info', label: 'Contact Information', icon: 'user' },
{ key: 'billing', label: 'Billing', defaultExpanded: false },
{ key: 'system', label: 'System', visibleOn: '$user.isAdmin' },
{ key: 'billing', label: 'Billing', collapse: 'collapsed' },
{ key: 'system', label: 'System' },
],

fields: {
Expand All @@ -573,16 +573,18 @@ export const Account = ObjectSchema.create({
| `label` | `string` | Human-readable group header |
| `icon` | `string?` | Lucide/Material icon name for the group header |
| `description` | `string?` | Optional description under the header |
| `defaultExpanded` | `boolean` (default `true`) | Whether the group is expanded initially |
| `visibleOn` | `string?` | Expression controlling group-level visibility |
| `collapse` | `'none' \| 'expanded' \| 'collapsed'` (default `'none'`) | Collapse behaviour on every surface: `'none'` = always open (no toggle), `'expanded'` = collapsible and starts open, `'collapsed'` = collapsible and starts closed (ADR-0085; replaces the deprecated `defaultExpanded` flag, which is still accepted as an alias) |

### Supported Migrations (MVP)

✅ Add / rename / delete / reorder groups — edit the `fieldGroups` array.
✅ Assign an existing field to a group — set `Field.group`.

⏳ Deferred (future iterations): explicit per-field in-group ordering, nested
groups, permission-scoped group visibility beyond `visibleOn`.
groups, group-level visibility predicates. Groups render identically on forms,
modals, and detail pages; when a single page needs a bespoke layout beyond
groups, assign it a custom Page schema instead of adding per-surface hints
here (ADR-0085).

---

Expand Down
45 changes: 21 additions & 24 deletions content/docs/guides/public-forms.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Forms (Public + Internal)
description: Render any FormView either publicly (anonymous, /f/:slug) or internally (authed operators, /forms/:name). The same metadata drives both, with URL prefill, configurable post-submit behavior, defaultDetailForm record binding, and declarative open-form actions.
description: Render any FormView either publicly (anonymous, /f/:slug) or internally (authed operators, /forms/:name). The same metadata drives both, with URL prefill, configurable post-submit behavior, and declarative open-form actions.
---

# Forms
Expand Down Expand Up @@ -359,29 +359,26 @@ export default Action.create({

Compared to `{ type: 'url', target: '/console/forms/quick_create' }`, the `'form'` variant is portable across runtimes — Studio/console/native shells can route it through their own form renderer instead of a raw navigation.

## 10. `defaultDetailForm` — bind a FormView to record detail

Set `defaultDetailForm` on an object schema to pin the FormView used by the record-detail / edit screen. This is the Airtable Interface-form binding pattern.

```ts
// hotcrm/src/objects/lead.object.ts
import { ObjectSchema } from '@objectstack/spec/data';

export default ObjectSchema.create({
name: 'lead',
label: 'Lead',
defaultDetailForm: 'quick_create', // → views.formViews.quick_create
fields: { /* … */ },
});
```

Resolution order at runtime:

1. `views.formViews[defaultDetailForm]` if set
2. `views.form` (the unnamed default form view)
3. Auto-generated grid layout from object fields

The same FormView can therefore drive **three** experiences — public collection, internal quick-create, and record-detail editing — without duplicating layout metadata.
## 10. Record detail is driven by semantic roles, not a form binding

There is **no** object-level key that pins a FormView to the record-detail /
edit screen (the once-documented `defaultDetailForm` was never implemented and
has been removed from `ObjectSchema`). Record-detail rendering is derived from
the object's **cross-surface semantic roles** (ADR-0085):

- `nameField` — which field is the record's display name.
- `highlightFields` — the most important fields (detail highlight strip,
default list columns, cards).
- `stageField` — the linear-lifecycle field behind the detail progress
stepper (`false` suppresses detection).
- `fieldGroups` + `Field.group` — semantic grouping, rendered identically on
forms, modals, and detail pages.

When a record page needs a bespoke layout beyond what the roles derive, assign
the object a **custom Page schema** — that is the supported per-page
customization path. FormViews remain the metadata for the two form
experiences this guide covers: public collection (`/f/:slug`) and internal
entry (`/forms/:name`).

## 11. Security checklist (public mode)

Expand Down
10 changes: 8 additions & 2 deletions skills/objectstack-data/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ Organize fields into logical groups (e.g., "Contact Information", "Billing",
`ObjectFieldGroup.key`. In-group display order equals the traversal order
of `fields`.
- Group keys must be `snake_case`; group labels are human-readable.
- Optional per-group: `icon`, `description`, and `collapse`
(`'none'` always open · `'expanded'` collapsible, starts open ·
`'collapsed'` collapsible, starts closed — replaces the deprecated
`defaultExpanded` flag, ADR-0085). Groups render identically on forms,
modals, and detail pages; for a bespoke single-page layout assign a
custom Page instead.

```typescript
import { ObjectSchema } from '@objectstack/spec';
Expand All @@ -121,8 +127,8 @@ export default ObjectSchema.create({

fieldGroups: [
{ key: 'contact_info', label: 'Contact Information', icon: 'user' },
{ key: 'billing', label: 'Billing', defaultExpanded: false },
{ key: 'system', label: 'System', visibleOn: P`os.user.isAdmin == true` },
{ key: 'billing', label: 'Billing', collapse: 'collapsed' },
{ key: 'system', label: 'System' },
],

fields: {
Expand Down
1 change: 0 additions & 1 deletion skills/objectstack-formula/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,6 @@ to the envelope.
| `Field` | `visibleOn` | cel |
| `Field` | `defaultValue` (M9.9b) | cel |
| `ConditionalValidation` | `when` | cel |
| `ObjectFieldGroup` | `visibleOn` | cel |
| `View` | `visibleOn` | cel |
| `View.criteria` | filter expression | cel |
| `Action` | `disabled` | cel (or boolean) |
Expand Down
27 changes: 10 additions & 17 deletions skills/objectstack-ui/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,23 +151,16 @@ columns (see objectstack-data → Relationships → Detail-page related lists).
Authored record pages can still place an explicit `record:related_list` (or
inline-editable `record:line_items`) when they need bespoke placement.

**Related-list layout — `detail.relatedLayout` (`'stack'` | `'tabs'`).** By
default every related list stacks under a single **Related** tab on the record
detail page (`relatedLayout: 'stack'`). To promote each related table to its own
**peer tab** — sitting alongside *Details* / *Activity* instead of nested under
*Related* — set `relatedLayout: 'tabs'` in the object's `detail` block:

```typescript
detail: { relatedLayout: 'tabs' } // Details · Tasks · Risks · Milestones …
```

Pure config — no custom record page needed; the synthesized detail page emits
one peer tab per related entry (label falls back to the object name, the
relationship's icon carries through), with Activity/History ordered after them.
Omit it (or `'stack'`) for the default single-Related-tab behavior — zero
regression. Use `tabs` when a record has several substantial child tables that
each deserve first-class navigation; keep `stack` when related lists are
secondary to the main record body.
**Related-list layout.** Every related list stacks under a single **Related**
tab on the synthesized record detail page — that is the only built-in layout.
The old `detail.relatedLayout` (`'stack'` | `'tabs'`) toggle — like the whole
object-level `detail: {...}` block — was **removed** (ADR-0085): an object
declares no per-surface layout hints. When a record's child tables deserve
first-class navigation (their own tabs, bespoke placement), assign the object a
**custom record Page** and lay it out explicitly with `record:related_list`
(or inline-editable `record:line_items`) blocks. For lighter tweaks stay at the
relationship layer: `relatedList: false` to suppress a noisy child,
`relatedListTitle` / `relatedListColumns` to override title / columns.

### Field Conditional Rules in Forms

Expand Down