Skip to content

Commit 0a70bdb

Browse files
committed
fix(spec): stageField describe states only what the parse refuses; changeset names platform-objects and metadata-core; FLS contract fixture declares kind
Claude-Session: https://claude.ai/code/session_017qmjVjz61scwQFLfspdBX6 Co-authored-by: Claude <noreply@anthropic.com>
1 parent 4274c65 commit 0a70bdb

4 files changed

Lines changed: 27 additions & 7 deletions

File tree

‎.changeset/23809-select-option-kind.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
---
22
'@objectstack/spec': minor
3+
'@objectstack/platform-objects': patch
4+
'@objectstack/metadata-core': patch
35
---
46

57
feat(spec)!: a select option declares its lifecycle `kind` (`open` / `won` / `lost`), and every option of an object's `stageField` must declare it
@@ -24,4 +26,9 @@ No conversion makes this edit, because which option ends a record's life success
2426

2527
The Studio object designer's options repeater offers `kind`; the field form's options repeater lists it.
2628

29+
The other packages follow the new key:
30+
31+
- **`@objectstack/platform-objects`.** The metadata-form translation bundles (`./metadata-translations`) carry the options repeater's new `kind` row, with its label and help text in zh-CN, ja-JP and es-ES.
32+
- **`@objectstack/metadata-core`.** The published contract fixture `FLS_CONTRACT_OBJECT` (`@objectstack/metadata-core/testing`) names `salary_grade` as its `stageField`, so its two options now declare `kind: 'open'` and the fixture still parses through `ObjectSchema`.
33+
2734
<!-- adr-0087: registered stage-field-option-kind-required -->

‎packages/metadata-core/src/object-schema-fls-contract.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,10 @@ export const FLS_CONTRACT_OBJECT = {
132132
salary_grade: {
133133
type: 'select',
134134
label: 'Salary Grade',
135-
options: [{ value: 'band_a', label: 'Band A' }, { value: 'band_b', label: 'Band B' }],
135+
// `kind` on both: the object names this field its `stageField`, and
136+
// `ObjectSchema` requires every stage option to declare one. A salary
137+
// band is a step on no road to an end, so both are `open`.
138+
options: [{ value: 'band_a', label: 'Band A', kind: 'open' }, { value: 'band_b', label: 'Band B', kind: 'open' }],
136139
requiredPermissions: ['view_compensation'],
137140
},
138141
bonus_formula: {

‎packages/metadata-core/src/object-schema-fls-references.test.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,3 +707,10 @@ describe('[ADR-0106] the shared contract table, driven through the bare projecti
707707
});
708708
}
709709
});
710+
711+
describe('the published contract fixture is an object the spec accepts', () => {
712+
it('`FLS_CONTRACT_OBJECT` parses through `ObjectSchema` (its stage field declares every option\'s `kind`, #23809)', () => {
713+
const parsed = ObjectSchema.safeParse(FLS_CONTRACT_OBJECT);
714+
expect(parsed.success, JSON.stringify(parsed.error?.issues)).toBe(true);
715+
});
716+
});

‎packages/spec/src/data/object.zod.ts‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2885,13 +2885,16 @@ const ObjectSchemaBase = strictObject(
28852885
* path/stepper today; kanban default grouping, list badges and report
28862886
* bucketing are natural future consumers.
28872887
*
2888-
* [#23809] The named field is a single-value `select` / `radio` whose inline
2889-
* options EACH declare their lifecycle `kind` (`open` / `won` / `lost`), so
2890-
* those consumers tell the steps from the two ends by declaration, not by
2891-
* the value's spelling. Refused at parse otherwise — see
2892-
* `refuseStageFieldWithoutKinds`.
2888+
* [#23809] When the named field carries options, it must be a single-value
2889+
* `select` / `radio` whose inline options EACH declare their lifecycle `kind`
2890+
* (`open` / `won` / `lost`), so those consumers tell the steps from the two
2891+
* ends by declaration, not by the value's spelling. The parse refuses such a
2892+
* field with an option lacking `kind`, a `picklist`-backed one, and a
2893+
* multi-value one (see `refuseStageFieldWithoutKinds`). Not judged here:
2894+
* `false`, a name that resolves to no declared field (the lint
2895+
* `semantic-roles` rule reports it), and a field with no options at all.
28932896
*/
2894-
stageField: z.union([z.string(), z.literal(false)]).optional().describe("[ADR-0085] Lifecycle stage field (linear/ordered), or false to declare the status field non-linear and suppress stage heuristics. Absent = heuristic detection allowed. The named field is a single-value `select` / `radio` with inline `options`, and every option declares its `kind` ('open' / 'won' / 'lost'); refused otherwise."),
2897+
stageField: z.union([z.string(), z.literal(false)]).optional().describe("[ADR-0085] Lifecycle stage field (linear/ordered), or false to declare the status field non-linear and suppress stage heuristics. Absent = heuristic detection allowed. When the named field carries options, it must be a single-value `select` / `radio` with inline `options`, each declaring its `kind` ('open' / 'won' / 'lost'); a picklist-backed or multi-value stage field is refused. A name that resolves to no declared field, or a field with no options, is not judged at parse."),
28952898

28962899
/**
28972900
* Cross-renderer EDIT-INTERACTION intent: whether a record of this object is

0 commit comments

Comments
 (0)