diff --git a/.changeset/28256-data-skill-percent-display-lookup-autofill.md b/.changeset/28256-data-skill-percent-display-lookup-autofill.md new file mode 100644 index 00000000000..109d265cd4a --- /dev/null +++ b/.changeset/28256-data-skill-percent-display-lookup-autofill.md @@ -0,0 +1,12 @@ +--- +'@objectstack/skills': patch +--- + +docs(skills): the objectstack-data field-types rule teaches `percentDisplay` and `lookupAutofill` + +Clause-②: no +Metadata-protocol: none + +- The `percent` row of the field-type table lists `percentDisplay`: unset or `'bar'` draws the progress bar, `'plain'` prints the number alone, for a rate or a probability. +- The Relational section teaches `lookupAutofill: [{ target, source, overwrite? }]` on a single-value `lookup` / `master_detail`: which targets a pick may fill, which sources it may copy, the overwrite rule, that the fill is client-side only, and how it relates to a line-item grid column's `autofill`. +- Paid inside the file's token ceiling: the `Select with Default`, `Email with Uniqueness` and `Lookup (Many-to-Many)` examples go (each restated a rule the skill carries elsewhere), and the `vectorConfig` trap folds into the invented-key list at the top of the file. diff --git a/content/docs/data-modeling/fields.mdx b/content/docs/data-modeling/fields.mdx index 9294d6e9554..b40a6f61a5d 100644 --- a/content/docs/data-modeling/fields.mdx +++ b/content/docs/data-modeling/fields.mdx @@ -212,6 +212,13 @@ primary_contact: Field.lookup('contact', { ], }), +// A pick fills sibling fields: the picked product's list_price lands in unit_price +product: Field.lookup('product', { + label: 'Product', + required: true, + lookupAutofill: [{ target: 'unit_price', source: 'list_price' }], +}), + // Master-detail (cascade delete) project: Field.masterDetail('project', { label: 'Project', @@ -236,6 +243,40 @@ order: Field.masterDetail('order', { | `inlineEdit` | `boolean \| 'grid' \| 'form'` | Render child records inline on the parent create/edit form (`true` = auto-pick, `'grid'`, or `'form'`) | | `inlineColumns` | `array` | Optional explicit columns for the inline grid | | `inlineAmountField` | `string` | Optional numeric child field for the inline running total | +| `lookupAutofill` | `{ target, source, overwrite? }[]` | Fields of this object a pick fills from the picked record (single-value `lookup` / `master_detail` only) — see below | + +#### Lookup-pick autofill: `lookupAutofill` + +A single-value `lookup` / `master_detail` can declare `lookupAutofill: [{ target, +source, overwrite? }]`. When a user picks a record in that field, each pair copies +the picked record's `source` field (a field of the `reference` object) into this +object's `target` field — in the record form, and in a line-item grid column over +the field. The example above fills a quote line's `unit_price` from the picked +product's `list_price`. + +- **`target`** is a field of this object that an author writes: not the lookup + itself, not `readonly`, and not computed (`formula`, `summary`, `autonumber`, or + a stored field with an `expression`). Each target is named once per field. The + object's parse refuses anything else. +- **`source`** is a field the referenced object declares and the record carries + (not `internal`), and its value must be one the target can hold: the same type + and multiplicity (a list only into a list), + a string-valued field into a text target, `number` and `currency` into each + other, a relationship only into a relationship over the same object. `os + validate` / `os build` judge the source against the referenced object. +- **Overwrite rule.** By default a pick never replaces a value somebody wrote: the + target is filled only while it is empty or still holds the value an earlier pick + filled in. A pair with `overwrite: true` replaces the target on every pick. +- **Client-side only.** The fill edits the form, and the filled value is saved as an + ordinary write. Nothing on the write path reads the key: an import or an API + write that sets the lookup fills nothing. +- **In a line-item grid**, the column key `autofill` still governs only the grid's + same-named copy; a declared pair applies under its own overwrite rule and takes + its target out of that copy. A master-detail collection whose `relationshipField` + is set and whose every column carries its `type` loads no child fields, so it + sees no pairs and keeps only the same-named copy. + +The key's full contract is in the [Field reference](/docs/references/data/field). #### Referential integrity diff --git a/skills/objectstack-data/rules/field-types.md b/skills/objectstack-data/rules/field-types.md index 2f353791bac..ee9aff8cec5 100644 --- a/skills/objectstack-data/rules/field-types.md +++ b/skills/objectstack-data/rules/field-types.md @@ -26,8 +26,8 @@ fields: { > **Config columns list only real `FieldSchema` keys.** Per-type display knobs > beyond these do **not** exist — an unknown field key is REFUSED at parse -> (`unrecognized_keys`), so don't invent `theme`, `rows`, or -> `fileAttachmentConfig`. Source of truth: +> (`unrecognized_keys`), so don't invent `theme`, `rows`, +> `vectorConfig` or `fileAttachmentConfig`. Source of truth: > `node_modules/@objectstack/spec/src/data/field.zod.ts`. ## Text & Content @@ -51,7 +51,7 @@ fields: { |:-----|:------------|:-------| | `number` | Generic numeric value | `min`, `max`, `precision`, `scale` | | `currency` | Monetary amounts | `currencyConfig` (currencyMode, defaultCurrency, currencyField, homeAmount) | -| `percent` | Percentage values (0-100) | `min`, `max`, `precision` | +| `percent` | Percentage values (0-100) | `min`, `max`, `precision`, `percentDisplay` — the read-only face: unset or `'bar'` draws a progress bar, `'plain'` the number alone, for a rate or a probability | ## Date & Time @@ -90,11 +90,20 @@ options: [ | Type | When to Use | Key Config | |:-----|:------------|:-----------| -| `lookup` | Reference another object (independent) | `reference`, `lookupFilters`, `multiple`, `deleteBehavior` | -| `master_detail` | Parent–child with lifecycle control | `reference`, `deleteBehavior` (`cascade`/`restrict` — `set_null` is refused) | +| `lookup` | Reference another object (independent) | `reference`, `lookupFilters`, `multiple`, `deleteBehavior`, `lookupAutofill` | +| `master_detail` | Parent–child with lifecycle control | `reference`, `deleteBehavior` (`cascade`/`restrict` — `set_null` is refused), `lookupAutofill` | | `tree` | Hierarchical self-reference | `reference` — optional; when written, this object's own name (any other object is refused at parse) | | `user` | Person picker — a lookup specialized to `sys_user` (assignee, watchers). Stored identically to `lookup` | `multiple` (collaborators), `defaultValue: 'current_user'` | +**`lookupAutofill: [{ target, source, overwrite? }]`** (single-value `lookup` / +`master_detail`): a pick copies the picked record's `source` into this object's +`target` (`[{ target: 'unit_price', source: 'list_price' }]` on a product lookup), +in the form and the line-item grid. `target`: a field of this object an author +writes (not the lookup, `readonly` or computed), named once. `source`: a declared, +non-`internal` field of `reference` the target can hold. A pick fills an empty +target or an earlier pick's fill; `overwrite: true` always replaces. Client-side +only; in a grid a declared target leaves the same-named `autofill` copy. + ## Media | Type | When to Use | Config | @@ -142,19 +151,6 @@ Stored as JSON on the parent row — no separate table / FK: ## Common Field Configurations -### Email with Uniqueness - -```typescript -{ - type: 'email', - required: true, - // State the scope (ADR-0120): 'organization' = one holder per organization - // (NULL-safe), 'global' = one across the whole installation. Bare `true` at - // field level still means 'organization' and stays valid. - unique: 'organization', -} -``` - ### Currency ```typescript @@ -182,20 +178,6 @@ home-currency amount in `field` (a `'dynamic'` `currency`) at the `rateDate` (`date`/`datetime`) rate, and that rate in `rateField` (`number`), both `readonly: true`. Cross-currency totals sum `field`. -### Select with Default - -```typescript -{ - type: 'select', - required: true, - options: [ - { label: 'Low', value: 'low' }, - { label: 'Medium', value: 'medium', default: true }, - { label: 'High', value: 'high', color: '#e74c3c' }, - ], -} -``` - ### Lookup (One-to-Many) ```typescript @@ -209,17 +191,6 @@ home-currency amount in `field` (a `'dynamic'` `currency`) at the `rateDate` } ``` -### Lookup (Many-to-Many) - -```typescript -{ - type: 'lookup', - reference: 'tag', - multiple: true, - max: 10, -} -``` - ### Formula ```typescript @@ -281,9 +252,6 @@ The `format` is literal text interleaved with `{...}` tokens: } ``` -There is **no** `vectorConfig` block — an authored `vectorConfig` is refused at -parse. `dimensions` is the flat field-level key. - ## Incorrect vs Correct ### ❌ Incorrect — Autonumber interpolating an optional / adjacent field