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
15 changes: 15 additions & 0 deletions .changeset/19630-view-binding-rows-gantt-timeline-map.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
"@objectstack/spec": patch
---

`view/layout-without-binding` — the `gantt`, `timeline` and `map` warnings no longer tell an author the renderer falls back to literal default field names, because objectui deleted those floors; each now names the refusal screen the renderer shows instead, and the keys that clear it (#19630).

Re-derived at the objectui pin this repo builds against (`87af769e9`), not at objectui's head, both halves of each path a list view takes:

- **`gantt`** — `ListView.tsx`'s `case 'gantt'` restates only declared bindings (objectui#7070 deleted the `start_date` / `end_date` floors, objectui#7499 the `progress` / `dependencies` ones); `ObjectGantt`'s `getGanttConfig` returns `null` without both dates and the component renders "Gantt configuration required". The body now names `gantt.startDateField`, `gantt.endDateField` and `gantt.titleField`, the three keys `GanttConfigSchema` requires.
- **`timeline`** — the `startDateField || 'created_at'` floor is gone (objectui#7070 step three) and `ObjectTimeline` renders "Timeline date axis required"; the `titleField || 'name'` default still stands, and the body says so. It names `timeline.startDateField` and `timeline.titleField`, the two keys `TimelineConfigSchema` requires.
- **`map`** — `locationField || 'location'` is gone on both faces (objectui#8169): `ObjectMap` no longer guesses coordinate field names and its `hasCoordinateBinding` gate renders "Map configuration required", for an absent `map` block and for a declared block that names neither coordinate form alike. Both `map` messages — the absent-block body and the block-present one, which had quoted `locationField || 'location'` as the renderer's read — now describe that refusal and name `map.locationField` or the `map.latitudeField` + `map.longitudeField` pair.

`kanban` and `tree` were re-read at the same pin and still floor (or infer) a binding, so they keep the generic body. The `VIEW_BINDING_BLOCKS` docblock rows carry asserting pin citations, so the next `.objectui-sha` bump reds on them instead of leaving them to go stale.

No severity moves and no finding appears or disappears: every route stays `warning`, consistent with #16577's ruling B for the `calendar` route (both doors loud — `os validate` and a named refusal at render), which these rows now measure too without extending or reopening it. No schema, export or accept set moved: this corrects prose and three warning strings. `Clause-②: no`
66 changes: 51 additions & 15 deletions packages/spec/src/kernel/functional-completeness.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,10 @@ describe('checkViewCompleteness — layout bindings', () => {
// ⛔ What it is NOT is one shared mechanism. This comment used to say the
// renderer's fallback for every one is a literal field name; objectui has
// been deleting those floors one view type at a time, and for `calendar`
// the renderer now REFUSES by name instead (#17445). The rule fires on all
// six either way — that is what this pin holds — and the per-type
// mechanism belongs to the table's docblock, which states each row's own
// measurement and which of them have gone stale.
// (#17445), `gantt`, `timeline` and `map` (#19630) the renderer now REFUSES
// by name instead. The rule fires on all six either way — that is what this
// pin holds — and the per-type mechanism belongs to the table's docblock,
// which states each row's own measurement.
it.each(['kanban', 'calendar', 'gantt', 'timeline', 'map', 'tree'])('flags a %s view missing its block as a WARNING', (type) => {
const f = only(checkViewCompleteness({ type }) as never);
expect(f.rule).toBe(VIEW_LAYOUT_WITHOUT_BINDING);
Expand Down Expand Up @@ -186,15 +186,43 @@ describe('checkViewCompleteness — layout bindings', () => {
expect(f.severity).toBe('warning');
});

it('the five types with no per-type body keep the generic one — an override, not a rewrite', () => {
// ⚠️ An honest pin: it records WHICH body each type receives, NOT that
// the body is true of each. The table's re-measurement note says `gantt`,
// `timeline` and `map` inherit this sentence pending corrections of their
// own; `kanban` and `tree` were re-read at the same ref and still floor a
// literal. Correcting one of the three means adding an entry beside
// `calendar`'s and moving that type out of this list — a deliberate edit,
// which is the point.
for (const type of ['kanban', 'gantt', 'timeline', 'map', 'tree']) {
it.each([
// [#19630] Re-derived at the pin this repo builds against, both halves of
// each path: `ListView.tsx` restates only DECLARED bindings (objectui#7070
// for the gantt / timeline date axes, objectui#7499 for gantt progress /
// dependencies, objectui#8169 for map), and the component then renders a
// named refusal screen. The generic body asserted literal floors as the
// reason for the warning; this pin stops that from coming back.
{ type: 'gantt', component: 'ObjectGantt.tsx', screen: 'Gantt configuration required',
keys: ['gantt.startDateField', 'gantt.endDateField', 'gantt.titleField'] },
{ type: 'timeline', component: 'ObjectTimeline.tsx', screen: 'Timeline date axis required',
keys: ['timeline.startDateField', 'timeline.titleField'] },
{ type: 'map', component: 'ObjectMap.tsx', screen: 'Map configuration required',
keys: ['map.locationField', 'map.latitudeField', 'map.longitudeField'] },
])('the $type body describes the REFUSAL, not a deleted literal fallback (#19630)', ({ type, component, screen, keys }) => {
const f = only(checkViewCompleteness({ type }) as never);
expect(f.rule).toBe(VIEW_LAYOUT_WITHOUT_BINDING);
expect(f.message).not.toContain('falls back to literal default field names');
expect(f.message).toContain(screen);
expect(f.message).toContain(component);
// ⛔ The half the correction had to PRESERVE: the prescription. Each key
// the renderer's refusal screen names is the key the body tells the
// author to declare.
for (const key of keys) expect(f.message).toContain(key);
// Severity is untouched and CONSISTENT with #16577's ruling B (comment
// `5634033966`): a route that refuses BY NAME stays warning-class under
// ADR-0078 §1 because both doors are loud. These rows now measure that
// shape; they do not extend or reopen the ruling.
expect(f.severity).toBe('warning');
});

it('the two types with no per-type body keep the generic one — an override, not a rewrite', () => {
// ⚠️ An honest pin: it records WHICH body each type receives. `kanban`
// and `tree` are the two rows re-read at the pin that still floor (or
// infer) a binding, so the generic sentence is still true of them. A type
// that stops flooring gets an entry beside `calendar`'s and moves out of
// this list — a deliberate edit, which is the point.
for (const type of ['kanban', 'tree']) {
const f = only(checkViewCompleteness({ type }) as never);
expect(f.rule).toBe(VIEW_LAYOUT_WITHOUT_BINDING);
expect(f.message).toContain('falls back to literal default field names');
Expand All @@ -212,13 +240,21 @@ describe('checkViewCompleteness — layout bindings', () => {

it('flags a `map` block that declares neither coordinate form — `map: {}` is the unbound view with braces', () => {
// `ListMapConfigSchema` requires no key, so block presence alone would
// bless `map: { titleField }` on its way to `locationField || 'location'`.
// bless `map: { titleField }` — which objectui's `ObjectMap` refuses by
// its `hasCoordinateBinding` gate exactly as it refuses an absent block.
for (const map of [{}, { titleField: 'title' }, { latitudeField: 'lat' }, { longitudeField: 'lng' }]) {
const f = only(checkViewCompleteness({ type: 'map', map }) as never);
expect(f.rule).toBe(VIEW_LAYOUT_WITHOUT_BINDING);
expect(f.severity).toBe('warning');
expect(f.path).toBe('map.locationField');
expect(f.message).toContain("locationField || 'location'");
// [#19630] The literal `locationField || 'location'` floor this message
// used to quote is gone on both faces (objectui#8169); the body names
// the refusal the renderer shows instead, and the keys that clear it.
expect(f.message).not.toContain("|| 'location'");
expect(f.message).toContain('Map configuration required');
expect(f.message).toContain('hasCoordinateBinding');
expect(f.message).toContain('map.locationField');
expect(f.message).toContain('map.latitudeField');
expect(f.fix).toContain('locationField');
expect(f.fix).toContain('latitudeField');
}
Expand Down
Loading
Loading