From 7ac43a37d312c2d1265f4535e6c6a80c6235751a Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 24 Aug 2026 03:43:24 +0000 Subject: [PATCH] fix(showcase): Work Map declares its marker bindings via sourceView MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `TaskMapPage` (`showcase_task_map`) whitelisted `map` in `appearance.allowedVisualizations` but declared no field binding. `InterfacePageConfigSchema` is a CLOSED shape with no `map` key of its own, so the binding cannot be authored directly on `interfaceConfig` (confirmed: parse-rejected as an unrecognized key). objectui's auto-derivation then filled in only `{ locationField }`, and the map renderer's flat-form fallback defaults `titleField` to `'name'` — `showcase_task` has `title`, not `name`, so every marker rendered a placeholder title. `showcase_task` already declares the correct binding on its own `map` listView (task.view.ts, #9340): `map: { titleField: 'title', locationField: 'location' }`. The page now points at it via `sourceView: 'map'`, the one schema-legal channel that reaches it — objectui's `InterfaceListPage` resolves `sourceView` against the source object's named views and (since objectui#5908, merged) forwards that view's `map` block to the renderer. Adds a regression pin: the page carries the `sourceView` reference, the referenced view still carries the binding, and a `map` block declared directly on `interfaceConfig` stays rejected. --- .../src/ui/pages/task-visualizations.pages.ts | 12 ++++ .../test/task-map-marker-title.test.ts | 60 +++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 examples/app-showcase/test/task-map-marker-title.test.ts diff --git a/examples/app-showcase/src/ui/pages/task-visualizations.pages.ts b/examples/app-showcase/src/ui/pages/task-visualizations.pages.ts index 20cccdeeef..5755688e7f 100644 --- a/examples/app-showcase/src/ui/pages/task-visualizations.pages.ts +++ b/examples/app-showcase/src/ui/pages/task-visualizations.pages.ts @@ -96,6 +96,18 @@ export const TaskMapPage = definePage({ label: 'Work Map', interfaceConfig: { source: 'showcase_task', + // `InterfacePageConfigSchema` is a CLOSED shape with no `map` (or + // `kanban`/`calendar`/…) key of its own — an author cannot declare a + // marker-title binding directly here (confirmed: `map: {...}` on this + // object is rejected as an unrecognized key). The one schema-legal + // channel for a per-visualization field binding on an interface page is + // `sourceView`, which `InterfaceListPage`'s `resolveSourceView` resolves + // against the source object's OWN named view — and `showcase_task` + // already declares exactly this binding on its `map` listView + // (task.view.ts, `listViews.map.map: { titleField: 'title', + // locationField: 'location' }`, from #9340). Referencing it here is a + // pure forward, not a duplicate declaration. + sourceView: 'map', columns: [...cols, 'location'], appearance: { showDescription: true, allowedVisualizations: ['map'] }, userActions: { sort: false, search: true, filter: false, rowHeight: false, addRecordForm: false }, diff --git a/examples/app-showcase/test/task-map-marker-title.test.ts b/examples/app-showcase/test/task-map-marker-title.test.ts new file mode 100644 index 0000000000..e7c8dd21b1 --- /dev/null +++ b/examples/app-showcase/test/task-map-marker-title.test.ts @@ -0,0 +1,60 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +import { describe, it, expect } from 'vitest'; + +import { PageSchema } from '@objectstack/spec/ui'; + +import { TaskMapPage } from '../src/ui/pages/task-visualizations.pages.js'; +import { TaskViews } from '../src/ui/views/task.view.js'; + +/** + * Regression pin for the showcase Work Map's placeholder marker titles. + * + * `InterfacePageConfigSchema` is a CLOSED shape with no `map` (or `kanban` / + * `calendar` / …) key of its own — a per-visualization field binding cannot + * be declared directly on `interfaceConfig`. The one schema-legal channel is + * `sourceView`, which objectui's `InterfaceListPage` resolves against the + * source object's OWN named view and (since objectui#5908) forwards that + * view's `map` block verbatim to the renderer. `showcase_task` already + * declares the correct binding on its `map` listView (task.view.ts, #9340); + * this page has to REFERENCE it, or the renderer's `titleField || 'name'` + * fallback finds no `name` field on `showcase_task` and every marker title + * renders as a placeholder. + */ +describe('showcase Work Map — marker title binding (#11443)', () => { + it('references the object\'s `map` listView via sourceView', () => { + expect(TaskMapPage.interfaceConfig?.sourceView).toBe('map'); + }); + + it('the referenced `map` listView still carries the title/location binding', () => { + const mapView = (TaskViews.listViews as Record).map; + expect(mapView).toBeDefined(); + expect(mapView.type).toBe('map'); + expect(mapView.map).toEqual({ titleField: 'title', locationField: 'location' }); + }); + + it('a `map` block declared directly on interfaceConfig is rejected — documents the schema boundary this page works around', () => { + const attempt = () => + PageSchema.parse({ + type: 'list', + object: 'showcase_task', + kind: 'full', + template: 'default', + isDefault: false, + regions: [], + name: 'showcase_task_map_direct_probe', + label: 'Work Map (direct-block probe)', + interfaceConfig: { + source: 'showcase_task', + columns: ['title', 'location'], + appearance: { showDescription: true, allowedVisualizations: ['map'] }, + // `map` is not a key `InterfacePageConfigSchema` declares — the + // input TS type does not close over it (no excess-property error), + // but `.parse()` rejects it at runtime as an unrecognized key. That + // runtime rejection is exactly what this test pins. + map: { titleField: 'title', locationField: 'location' }, + }, + }); + expect(attempt).toThrow(/[Uu]nrecognized key/); + }); +});