From 1c871aa5c057c737c05544d7b291c4840b6eded2 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 2 Oct 2026 22:45:53 +0000 Subject: [PATCH 1/5] feat(types): TS twins of the zod-only node arms detail-section, app-schema-renderer and cloud:plan-status, parity-paired (objectui#11515) `@object-ui/types` declared no TypeScript type for three arms its zod face already publishes. Each gains a twin beside its arm's module (`DetailSectionNodeSchema` in views.ts, `AppSchemaRendererNodeSchema` in app.ts, `CloudPlanStatusSchema` in a new cloud.ts), joins `AnySchema`, and is registered as a `zod-mirror-parity` pair instead of an exclusion. `cloud:plan-status` measures clean. The other two are born ledgered with the reading their by-reference member already carries one level up: `fields` (the DetailViewField `options` divergence) and `schema` (the app document's SCHEMA-NODE wider reading). Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC Co-authored-by: Claude --- .../src/__tests__/zod-mirror-parity.test.ts | 79 +++++++++++++------ packages/types/src/app.ts | 53 +++++++++++++ packages/types/src/cloud.ts | 70 ++++++++++++++++ packages/types/src/index.ts | 18 ++++- packages/types/src/views.ts | 69 +++++++++++++++- 5 files changed, 261 insertions(+), 28 deletions(-) create mode 100644 packages/types/src/cloud.ts diff --git a/packages/types/src/__tests__/zod-mirror-parity.test.ts b/packages/types/src/__tests__/zod-mirror-parity.test.ts index 6e207b6145..bb7014065e 100644 --- a/packages/types/src/__tests__/zod-mirror-parity.test.ts +++ b/packages/types/src/__tests__/zod-mirror-parity.test.ts @@ -154,7 +154,13 @@ * a delta to this number; count the registry. Nothing asserts it against a written * one, so this line is prose and can rot; the pin that cannot is the one * comparing the two halves to each other. - * - **49 entries** in `KnownDrift`, **87 keys** across them — 49 / 86 until + * - **50 entries** in `KnownDrift`, **88 keys** across them — 49 / 87 until + * objectui#11515 registered the TS twins of three zod-only node arms. ONE + * entry is NEW with one key: `views.zod.ts#DetailSectionNodeSchema`'s + * `fields`, the EXPECTED DIVERGENCE on `views.zod.ts#DetailViewFieldSchema`'s + * `options` measured one level up, as on `views.zod.ts#DetailViewSectionSchema`. + * A pair born ledgered, not growth on an existing entry; the other two pairs + * carry no `KnownDrift` entry. It was 49 / 86 until * objectui#11483 took the component type `metric-card` out of the mirror's * widget vocabulary (`DashboardWidgetTypeSchema`), so a card in the slot is read * by its component arm alone and its required `value` governs. The TS @@ -617,8 +623,13 @@ * spelled "six" rots exactly as fast as one spelled `6`, it is just harder to * point a regex at. ⛔ Do not spell a live figure out again, and ⛔ do not * restate one without checking that the pin's spelling still reaches it. - * - **3 entries** in `WiderThanDeclared`, **3 keys** across them, and **3 arms** - * under those keys — split **2** SCHEMA-NODE, **1** CONCRETE, **0** MIXED, **0** unions. + * - **4 entries** in `WiderThanDeclared`, **4 keys** across them, and **4 arms** + * under those keys — split **3** SCHEMA-NODE, **1** CONCRETE, **0** MIXED, **0** unions. + * It read 3 / 3 / 3 — 2 / 1 / 0 / 0 — until objectui#11515 registered + * `app.zod.ts#AppSchemaRendererNodeSchema`, whose `schema` member IS + * `app.zod.ts#AppComponentSchema`: that pair's own SCHEMA-NODE reading is measured + * one level up, so the entry, its one key and its one SCHEMA-NODE arm ENTERED + * together, a pair born ledgered. * It read 4 / 4 / 4 — 2 / 2 / 0 / 0 — until objectui#11168 slice 3 (objectui#7759 * group C) narrowed `form.zod.ts#FormSchema`'s `layout` to the `vertical` / * `horizontal` its declaration states: `@objectstack/spec` 17.5.0 retired `grid`, and @@ -848,7 +859,7 @@ * * ## KNOWN_DRIFT is a ratchet, not a waiver * - * 49 of the registered pairs carry TYPE drift TODAY (measured, not assumed). Each is + * 50 of the registered pairs carry TYPE drift TODAY (measured, not assumed). Each is * pinned to its EXACT drifted key set, so the entry fails when new drift appears on * that mirror AND when the recorded drift is fixed — a stale entry cannot rot * quietly. Correcting them is not one change: the pairs below split into DISJOINT @@ -895,8 +906,9 @@ import ts from 'typescript'; import type { z } from 'zod'; import { AIConfigSchema, AIFieldSuggestionSchema, AIFormAssistSchema, AIRecommendationItemSchema, AIRecommendationsSchema, NLQueryResultSchema, NLQuerySchema } from '../zod/ai.zod.js'; -import { AppComponentSchema, MenuItemSchema as AppMenuItemSchema, NavigationAreaSchema, NavigationItemSchema } from '../zod/app.zod.js'; +import { AppComponentSchema, AppSchemaRendererNodeSchema, MenuItemSchema as AppMenuItemSchema, NavigationAreaSchema, NavigationItemSchema } from '../zod/app.zod.js'; import { BaseSchema, ComponentConfigSchema, ComponentInputSchema, ComponentMetaSchema, KeyedI18nLabelSchema, SchemaNodeSchema } from '../zod/base.zod.js'; +import { CloudPlanStatusSchema } from '../zod/cloud.zod.js'; import { CalendarEventSchema, CalendarViewSchema, CarouselItemSchema, CarouselSchema, ChatbotSchema, ChatbotEnhancedSchema, ChatbotFloatingSchema, ChatMessageSchema, ChatMessageSourceSchema, ChatToolInvocationSchema, DashboardComponentSchema, DashboardConfigSchema, DashboardWidgetConfigSchema, DashboardWidgetLayoutSchema, DashboardWidgetSchema, FilterBuilderSchema, FilterFieldSchema, KanbanCardSchema, KanbanColumnSchema, CardTemplateSchema, FilterGroupSchema } from '../zod/complex.zod.js'; import { ActionSchema, CRUDDialogSchema, DetailSchema } from '../zod/crud.zod.js'; import { AlertSchema, AvatarSchema, BadgeSchema, BarChartSchema, ChartDataSeriesSchema, ChartSchema, DataTableSchema, DrillDownConfigSchema, HtmlSchema, KbdSchema, ListItemSchema, ListSchema, MarkdownSchema, PivotTableSchema, StaticTableColumnSchema, StatisticSchema, TableColumnSchema, TableSchema, TimelineEventSchema, TimelineFeedItemSchema, TimelineGanttItemBarSchema, TimelineGanttItemSchema, TimelineSchema, TreeNodeSchema, TreeViewSchema } from '../zod/data-display.zod.js'; @@ -909,11 +921,12 @@ import { BreadcrumbItemSchema, BreadcrumbSchema, ButtonGroupButtonSchema, Button import { ObjectCalendarSchema, ObjectChartSchema, ObjectDataTableSchema, ObjectFormSchema, ObjectGallerySchema, ObjectGanttSchema, ObjectGridSchema, ObjectKanbanSchema, ObjectMapConfigSchema, ObjectMapSchema, ObjectTreeSchema, ObjectViewSchema, SortConfigSchema } from '../zod/objectql.zod.js'; import { AlertDialogSchema, ContextMenuSchema, DialogSchema, DrawerSchema, DropdownMenuSchema, HoverCardSchema, MenubarMenuSchema, MenubarSchema, MenuItemSchema as OverlayMenuItemSchema, PopoverSchema, SheetSchema, TooltipSchema } from '../zod/overlay.zod.js'; import { ReportBuilderSchema, ReportComponentSchema, ReportExportConfigSchema, ReportFieldSchema, ReportFilterSchema, ReportGroupBySchema, ReportSectionSchema, ReportViewerSchema } from '../zod/reports.zod.js'; -import { DetailViewFieldSchema, DetailViewSchema, DetailViewSectionSchema, DetailViewTabSchema, FilterUISchema, SortUISchema, ViewSwitcherSchema } from '../zod/views.zod.js'; +import { DetailSectionNodeSchema, DetailViewFieldSchema, DetailViewSchema, DetailViewSectionSchema, DetailViewTabSchema, FilterUISchema, SortUISchema, ViewSwitcherSchema } from '../zod/views.zod.js'; import type { AIConfig as Ts_AIConfig, AIFieldSuggestion as Ts_AIFieldSuggestion, AIFormAssistSchema as Ts_AIFormAssistSchema, AIRecommendationItem as Ts_AIRecommendationItem, AIRecommendationsSchema as Ts_AIRecommendationsSchema, NLQueryResult as Ts_NLQueryResult, NLQuerySchema as Ts_NLQuerySchema } from '../ai'; -import type { AppComponentSchema as Ts_AppComponentSchema, NavigationArea as Ts_NavigationArea } from '../app'; +import type { AppComponentSchema as Ts_AppComponentSchema, AppSchemaRendererNodeSchema as Ts_AppSchemaRendererNodeSchema, NavigationArea as Ts_NavigationArea } from '../app'; import type { BaseSchema as Ts_BaseSchema, ComponentConfig as Ts_ComponentConfig, ComponentInput as Ts_ComponentInput, ComponentMeta as Ts_ComponentMeta, KeyedI18nLabel as Ts_KeyedI18nLabel } from '../base'; +import type { CloudPlanStatusSchema as Ts_CloudPlanStatusSchema } from '../cloud'; import type { CalendarEvent as Ts_CalendarEvent, CalendarViewSchema as Ts_CalendarViewSchema, CarouselItem as Ts_CarouselItem, CarouselSchema as Ts_CarouselSchema, ChatbotSchema as Ts_ChatbotSchema, ChatbotEnhancedSchema as Ts_ChatbotEnhancedSchema, ChatbotFloatingSchema as Ts_ChatbotFloatingSchema, ChatMessage as Ts_ChatMessage, ChatMessageSource as Ts_ChatMessageSource, ChatToolInvocation as Ts_ChatToolInvocation, DashboardComponentSchema as Ts_DashboardComponentSchema, DashboardWidgetLayout as Ts_DashboardWidgetLayout, DashboardWidgetSchema as Ts_DashboardWidgetSchema, FilterBuilderSchema as Ts_FilterBuilderSchema, FilterField as Ts_FilterField, KanbanCard as Ts_KanbanCard, KanbanColumn as Ts_KanbanColumn, CardTemplate as Ts_CardTemplate } from '../complex'; import type { BPMNEdge as Ts_BPMNEdge, BPMNLane as Ts_BPMNLane, BPMNNode as Ts_BPMNNode, DashboardConfig as Ts_DashboardConfig, DashboardWidgetConfig as Ts_DashboardWidgetConfig, DataModelDesignerSchema as Ts_DataModelDesignerSchema, DataModelEntity as Ts_DataModelEntity, DataModelField as Ts_DataModelField, DataModelRelationship as Ts_DataModelRelationship, DesignerCanvasConfig as Ts_DesignerCanvasConfig, DesignerFieldDefinition as Ts_DesignerFieldDefinition, DesignerFieldOption as Ts_DesignerFieldOption, DesignerPaletteCategory as Ts_DesignerPaletteCategory, DesignerPaletteItem as Ts_DesignerPaletteItem, DesignerPosition as Ts_DesignerPosition, FieldDesignerSchema as Ts_FieldDesignerSchema, ObjectDefinition as Ts_ObjectDefinition, ObjectManagerSchema as Ts_ObjectManagerSchema, PageDesignerSchema as Ts_PageDesignerSchema, ProcessDesignerSchema as Ts_ProcessDesignerSchema, ReportDesignerElement as Ts_ReportDesignerElement, ReportDesignerSchema as Ts_ReportDesignerSchema, ReportDesignerSection as Ts_ReportDesignerSection } from '../designer'; import type { CRUDDialogSchema as Ts_CRUDDialogSchema, DetailSchema as Ts_DetailSchema } from '../crud'; @@ -926,7 +939,7 @@ import type { ButtonGroupButton as Ts_ButtonGroupButton, ButtonGroupSchema as Ts import type { ObjectCalendarSchema as Ts_ObjectCalendarSchema, ObjectChartSchema as Ts_ObjectChartSchema, ObjectDataTableSchema as Ts_ObjectDataTableSchema, ObjectFormSchema as Ts_ObjectFormSchema, ObjectGallerySchema as Ts_ObjectGallerySchema, ObjectGanttSchema as Ts_ObjectGanttSchema, ObjectGridSchema as Ts_ObjectGridSchema, ObjectKanbanSchema as Ts_ObjectKanbanSchema, ObjectMapConfig as Ts_ObjectMapConfig, ObjectMapSchema as Ts_ObjectMapSchema, ObjectTreeSchema as Ts_ObjectTreeSchema, ObjectViewSchema as Ts_ObjectViewSchema, SortConfig as Ts_SortConfig } from '../objectql'; import type { AlertDialogSchema as Ts_AlertDialogSchema, ContextMenuSchema as Ts_ContextMenuSchema, DialogSchema as Ts_DialogSchema, DrawerSchema as Ts_DrawerSchema, DropdownMenuSchema as Ts_DropdownMenuSchema, HoverCardSchema as Ts_HoverCardSchema, MenubarMenu as Ts_MenubarMenu, MenubarSchema as Ts_MenubarSchema, PopoverSchema as Ts_PopoverSchema, SheetSchema as Ts_SheetSchema, TooltipSchema as Ts_TooltipSchema } from '../overlay'; import type { ReportBuilderSchema as Ts_ReportBuilderSchema, ReportComponentSchema as Ts_ReportComponentSchema, ReportExportConfig as Ts_ReportExportConfig, ReportField as Ts_ReportField, ReportFilter as Ts_ReportFilter, ReportGroupBy as Ts_ReportGroupBy, ReportSection as Ts_ReportSection, ReportViewerSchema as Ts_ReportViewerSchema } from '../reports'; -import type { DetailViewField as Ts_DetailViewField, DetailViewSchema as Ts_DetailViewSchema, DetailViewSection as Ts_DetailViewSection, DetailViewTab as Ts_DetailViewTab, FilterUISchema as Ts_FilterUISchema, SortUISchema as Ts_SortUISchema, ViewSwitcherSchema as Ts_ViewSwitcherSchema } from '../views'; +import type { DetailSectionNodeSchema as Ts_DetailSectionNodeSchema, DetailViewField as Ts_DetailViewField, DetailViewSchema as Ts_DetailViewSchema, DetailViewSection as Ts_DetailViewSection, DetailViewTab as Ts_DetailViewTab, FilterUISchema as Ts_FilterUISchema, SortUISchema as Ts_SortUISchema, ViewSwitcherSchema as Ts_ViewSwitcherSchema } from '../views'; /* ── Type-level helpers (objectui#5680) ─────────────────────────────────────── */ @@ -1739,12 +1752,16 @@ const MIRRORS = { 'ai.zod.ts#NLQueryResultSchema': NLQueryResultSchema, 'ai.zod.ts#NLQuerySchema': NLQuerySchema, 'app.zod.ts#AppComponentSchema': AppComponentSchema, + // objectui#11515 — the zod-only arm's TS twin. + 'app.zod.ts#AppSchemaRendererNodeSchema': AppSchemaRendererNodeSchema, 'app.zod.ts#NavigationAreaSchema': NavigationAreaSchema, 'base.zod.ts#BaseSchema': BaseSchema, 'base.zod.ts#ComponentConfigSchema': ComponentConfigSchema, 'base.zod.ts#ComponentInputSchema': ComponentInputSchema, 'base.zod.ts#ComponentMetaSchema': ComponentMetaSchema, 'base.zod.ts#KeyedI18nLabelSchema': KeyedI18nLabelSchema, + // objectui#11515 — the zod-only arm's TS twin. + 'cloud.zod.ts#CloudPlanStatusSchema': CloudPlanStatusSchema, 'complex.zod.ts#CalendarEventSchema': CalendarEventSchema, 'complex.zod.ts#CalendarViewSchema': CalendarViewSchema, 'complex.zod.ts#CarouselItemSchema': CarouselItemSchema, @@ -1916,6 +1933,8 @@ const MIRRORS = { 'reports.zod.ts#ReportGroupBySchema': ReportGroupBySchema, 'reports.zod.ts#ReportSectionSchema': ReportSectionSchema, 'reports.zod.ts#ReportViewerSchema': ReportViewerSchema, + // objectui#11515 — the zod-only arm's TS twin. + 'views.zod.ts#DetailSectionNodeSchema': DetailSectionNodeSchema, 'views.zod.ts#DetailViewFieldSchema': DetailViewFieldSchema, 'views.zod.ts#DetailViewSchema': DetailViewSchema, 'views.zod.ts#DetailViewSectionSchema': DetailViewSectionSchema, @@ -1935,12 +1954,14 @@ interface Declared { 'ai.zod.ts#NLQueryResultSchema': Ts_NLQueryResult; 'ai.zod.ts#NLQuerySchema': Ts_NLQuerySchema; 'app.zod.ts#AppComponentSchema': Ts_AppComponentSchema; + 'app.zod.ts#AppSchemaRendererNodeSchema': Ts_AppSchemaRendererNodeSchema; 'app.zod.ts#NavigationAreaSchema': Ts_NavigationArea; 'base.zod.ts#BaseSchema': Ts_BaseSchema; 'base.zod.ts#ComponentConfigSchema': Ts_ComponentConfig; 'base.zod.ts#ComponentInputSchema': Ts_ComponentInput; 'base.zod.ts#ComponentMetaSchema': Ts_ComponentMeta; 'base.zod.ts#KeyedI18nLabelSchema': Ts_KeyedI18nLabel; + 'cloud.zod.ts#CloudPlanStatusSchema': Ts_CloudPlanStatusSchema; 'complex.zod.ts#CalendarEventSchema': Ts_CalendarEvent; 'complex.zod.ts#CalendarViewSchema': Ts_CalendarViewSchema; 'complex.zod.ts#CarouselItemSchema': Ts_CarouselItem; @@ -2111,6 +2132,7 @@ interface Declared { 'reports.zod.ts#ReportGroupBySchema': Ts_ReportGroupBy; 'reports.zod.ts#ReportSectionSchema': Ts_ReportSection; 'reports.zod.ts#ReportViewerSchema': Ts_ReportViewerSchema; + 'views.zod.ts#DetailSectionNodeSchema': Ts_DetailSectionNodeSchema; 'views.zod.ts#DetailViewFieldSchema': Ts_DetailViewField; 'views.zod.ts#DetailViewSchema': Ts_DetailViewSchema; 'views.zod.ts#DetailViewSectionSchema': Ts_DetailViewSection; @@ -2681,6 +2703,16 @@ interface KnownDrift { * `views.zod.ts#DetailViewSchema`. Same cause, not a separate finding. */ 'views.zod.ts#DetailViewSectionSchema': 'fields'; + /** + * objectui#11515: the pair was REGISTERED here, born ledgered. The TS twin + * takes each member from `DetailViewSection` by reference, as the arm + * `.pick`s each from `DetailViewSectionSchema`, so its `fields` carries the + * same `DetailViewField` elements and the EXPECTED DIVERGENCE on + * `views.zod.ts#DetailViewFieldSchema`'s `options` is measured here one level + * up, the reading the entry above records for the section. Same cause, not a + * separate finding. + */ + 'views.zod.ts#DetailSectionNodeSchema': 'fields'; } /* ── The measured unmirrored-declared ledger (objectui#6058) ────────────────── */ @@ -3903,6 +3935,16 @@ interface WiderThanDeclared { * pair measures clean and this entry would be STALE if it stayed. */ 'app.zod.ts#AppComponentSchema': 'areas'; + /** + * SCHEMA-NODE `schema` (objectui#11515: the pair was REGISTERED here, born + * ledgered). The mirror's member IS `app.zod.ts#AppComponentSchema`, the same + * schema object, and the TS twin's is that pair's declaration, so the + * document's own WIDER reading is measured here one level up: the + * `app.zod.ts#AppComponentSchema` entry above, through the + * `z.ZodType< any >` annotation `NavigationItemSchema` carries. Same cause, + * not a separate finding; it leaves when that entry does. + */ + 'app.zod.ts#AppSchemaRendererNodeSchema': 'schema'; // `complex.zod.ts#ChatbotSchema` recorded `body` here (CONCRETE, and DISJOINT rather // than strictly wider — the pair carried a `KnownDrift` entry for the same key, one of // the measured cases where each face refuses something the other admits: the mirror @@ -4130,6 +4172,9 @@ const WIDER_ARM_ROW_SEPARATOR = '::'; const WIDER_ARMS: Readonly< Record< string, readonly WiderArmClass[] > > = { 'app.zod.ts#AppComponentSchema::areas': ['SCHEMA-NODE'], + // objectui#11515: the one arm is the app document, read wider through the + // annotation the `areas` row above reaches (the ledger entry says so). + 'app.zod.ts#AppSchemaRendererNodeSchema::schema': ['SCHEMA-NODE'], 'layout.zod.ts#PageNodeSchema::slots': ['SCHEMA-NODE'], 'objectql.zod.ts#ObjectGridSchema::objectName': ['CONCRETE'], }; @@ -4743,12 +4788,6 @@ const EXCLUSIONS: Readonly> = { // `./flat-arm-responsive-styles-10872.test.ts`. 'public-blocks.zod.ts#NODE_ENVELOPE': "a shape FRAGMENT, not a schema and not a mirror — `{ responsiveStyles }`, the spec's `ResponsiveStylesSchema` by reference (the schema `PageComponentSchema.responsiveStyles` declares), spread into every public-block arm and into the `flex`, `object-grid` and `object-chart` mirrors so the one declaration is shared; no TS declaration restates the fragment itself, and each of those three pairs' TS twins declares the member as the spec's `ResponsiveStyles` type", - // objectui#10919 — the `cloud:plan-status` widget's arm, declared locally from - // the widget's read points: `@objectstack/spec` has no row for a `cloud:` - // widget, and the widget's props type lives beside it in `@object-ui/app-shell`, - // not in this package. Pinned by `./cloud-plan-status-arm-10919.test.ts`. - 'cloud.zod.ts#CloudPlanStatusSchema': - "no TS declaration in this package restates it — `BaseSchema` + the `cloud:plan-status` literal + a strict `properties` bag holding the one key the widget reads (`plan`) + the two content-channel refusals; the widget's own props type is in `@object-ui/app-shell`", // Renamed from `StylePropsSchema` by objectui#5928. Under the old name the // like-named `StyleProps` (../base.ts) — the Tailwind-scale vocabulary, sharing // ZERO keys with this `{ className, style }` object — read as its declaration, and @@ -4762,12 +4801,6 @@ const EXCLUSIONS: Readonly> = { "recursive; declared `z.ZodType`, which exposes no `.shape` to read — and accepts `any`, so it cannot be narrower than any declaration. One of the three consts that REFUSED the objectui#7760 type argument (the mirror accepts more than the declaration states), so this reason is still literally true here", 'app.zod.ts#MenuItemSchema': "recursive; a `z.lazy` exposes no `.shape` to read, so there is no key set for the per-key comparison. Since objectui#7760 it carries its TS declaration as BOTH type arguments, so the pair IS compared — as a whole type, by `tsc`, at the annotation itself", - // objectui#11440 — the `app-schema-renderer` whole-shell node, declared from - // its registration `inputs`. Pinned by `./passing-keys-arms-11440.test.ts`; - // its `schema` member (objectui#11494) by - // `./app-schema-renderer-schema-input-11494.test.ts`. - 'app.zod.ts#AppSchemaRendererNodeSchema': - "registration-owned — `BaseSchema` + the `app-schema-renderer` literal + the registration's three `inputs`: `schema`, which IS `app.zod.ts#AppComponentSchema` (the same schema object, optional; `@object-ui/layout`'s registration adapter hands it to the component, objectui#11494), `basePath` and `mobileNavMode` + the objectui#9256 `body` / `children` refusals; no TS declaration in this package restates the node (`@object-ui/layout`'s `AppSchemaRendererProps` is the component's prop type)", 'app.zod.ts#AppContextSelectorSchema': "spec-owned BY REFERENCE — the local `.extend(…)` adds renderer props that no TS declaration in this package restates", 'base.zod.ts#SchemaNodeSchema': @@ -4926,10 +4959,6 @@ const EXCLUSIONS: Readonly> = { "a bare vocabulary with no `.shape`; it is checked where a mirrored KEY declares it", 'views.zod.ts#ViewComponentSchema': "a union OVER the mirrors, not an object of its own — its members are checked individually above", - // objectui#11440 — one field section as a node. Pinned by - // `./passing-keys-arms-11440.test.ts`. - 'views.zod.ts#DetailSectionNodeSchema': - "mirror-owned BY REFERENCE — `BaseSchema` + the `detail-section` literal + the ten members of `views.zod.ts#DetailViewSectionSchema` the registration publishes as `inputs` (the same schema objects, `.pick`ed), flat on the node, + the objectui#9256 `body` / `children` refusals; no TS declaration in this package restates the node (`@object-ui/plugin-detail`'s `DetailSectionNodeProps` is the component's prop type)", 'index.zod.ts#AnyComponentSchema': "the barrel union OVER the mirrors, not an object of its own — its members are checked individually above", 'expression.zod.ts#ExpressionWireSchema': @@ -5098,7 +5127,7 @@ const ZOD_DIR = join(dirname(fileURLToPath(import.meta.url)), '..', 'zod'); * MINUEND under it had moved. Nothing failed on any of those days, because nothing * compared the registry to a number. objectui#7433 is that absence, not the digits. */ -const EXPECTED_MIRROR_PAIRS = 191; +const EXPECTED_MIRROR_PAIRS = 194; /** * A ledger this file can size from its own AST. `WiderThanDeclared` joined at diff --git a/packages/types/src/app.ts b/packages/types/src/app.ts index f2e49bb75c..7e26865d00 100644 --- a/packages/types/src/app.ts +++ b/packages/types/src/app.ts @@ -734,6 +734,59 @@ export interface AppComponentSchema extends BaseSchema, Omit Date: Fri, 2 Oct 2026 22:48:20 +0000 Subject: [PATCH 2/5] docs(plugin-detail): the README's Activity tab authors record:activity's declared properties, not the host feed slot `items` (objectui#11515) `items` is the host's feed slot, which the `record:activity` node refuses by name. The tab now authors `properties: { limit, showCompleted }`, as the plugin-detail guide does, and the note below the block says where the feed comes from: a record host, or the empty state in a bare `DetailView`. Rendered through `DetailTabs` and the real registry, the tab draws the block's empty state. The zod docblock that named this README as a host composition passing `items` gets a dated parenthesis. Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC Co-authored-by: Claude --- packages/plugin-detail/README.md | 20 +++++++++++--------- packages/types/src/zod/public-blocks.zod.ts | 6 ++++-- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/packages/plugin-detail/README.md b/packages/plugin-detail/README.md index 5cd1312084..4352963dba 100644 --- a/packages/plugin-detail/README.md +++ b/packages/plugin-detail/README.md @@ -134,9 +134,7 @@ const accountDetail = void; declare const deleteAccount: (id: string) => void; @@ -169,9 +167,10 @@ const accountDetail = ` - like the one above mounts neither, so a caller that already owns the feed - passes it in as `items` (the convention `record:history` uses for `entries`). +- **The node declares the feed's filters, not the feed.** Its inputs (`limit`, + `showCompleted`, `types`, `filterMode`, …) go in its `properties` bag, and + the block finds its own feed: a mounted discussion context, or a self-fetch + from `sys_activity` scoped to the record `useRecordContext` binds. Both need + a record host; a bare `` like the one above mounts neither, so + there the tab draws the block's empty state ("No activity recorded"). Under + a record host, such as the console's record page, the same node draws that + record's activity. `items` (with its `loading` flag) is the host's feed + slot, not a key of this node: `@object-ui/types` refuses it by name, and `data` is not a key this block reads. See **The `record:activity` block** in the plugin-detail guide for its declared diff --git a/packages/types/src/zod/public-blocks.zod.ts b/packages/types/src/zod/public-blocks.zod.ts index f9c5ce353d..676bc8189e 100644 --- a/packages/types/src/zod/public-blocks.zod.ts +++ b/packages/types/src/zod/public-blocks.zod.ts @@ -731,9 +731,11 @@ export const RecordPathBlockSchema = BaseSchema.extend({ * `items` (`hostItems` in its renderer) and `record:history` reads `entries` * (`hostEntries`), each with a `loading` flag beside it, on the node or in * `properties`. A host supplies them in code, on a node it composes: a TSX - * composition (the plugin-detail README hands `record:activity` its `items` - * inside a `DetailView` tab), or the record page's synthesizer + * composition, or the record page's synthesizer * (`buildDefaultPageSchema({ history })` writes `record:history`'s `entries`). + * (The plugin-detail README handed `record:activity` its `items` inside a + * `DetailView` tab until objectui#11515 re-authored that tab to the node's + * declared `properties`.) * Such a node is rendered by `SchemaRenderer` and never passes through this * validator. None of the four keys is authorable metadata: a feed written into * a JSON document is a snapshot that never updates, and an authored `loading: From 8e3e1e749bb5acf7120e404186287adf772eff22 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 2 Oct 2026 23:04:21 +0000 Subject: [PATCH 3/5] fix(vscode-extension): Export to React types its schema constant as SchemaRendererProps['schema'], and the doc sample authors declared types (objectui#11515) Unannotated, every `type` in the emitted constant widens to `string`, which a prop that discriminates on the literal `type` refuses (objectui#11466). `SchemaNode`, the annotation the card named, was measured and does not compile today or under that change: it admits `number` and `boolean`, which the prop leaves out, and the initializer's narrowing does not reach the component body. `SchemaRendererProps['schema']`, from the package the file already imports, compiles in both. The compile pin gains a leg against the REAL prop type: `@object-ui/types` resolves to its source entry and the `@object-ui/react` declaration carries the `SchemaRendererProps` body read out of SchemaRenderer.tsx, with a positive control refused on the constant's own line. The two documented mirrors follow the new preamble, and the mdx sample writes `text` with its `h1` variant. Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC Co-authored-by: Claude --- content/docs/utilities/vscode-extension.mdx | 15 +- packages/vscode-extension/DESIGN.md | 6 +- .../export-to-react-compiles.test.ts | 154 +++++++++++++++++- .../export-to-react-preamble.test.ts | 5 +- packages/vscode-extension/src/extension.ts | 13 +- 5 files changed, 176 insertions(+), 17 deletions(-) diff --git a/content/docs/utilities/vscode-extension.mdx b/content/docs/utilities/vscode-extension.mdx index e43c3ca049..354c6e80bb 100644 --- a/content/docs/utilities/vscode-extension.mdx +++ b/content/docs/utilities/vscode-extension.mdx @@ -86,24 +86,31 @@ This is the file the command produces — your exported schema comes back verbat as the `schema` constant, and both imports are part of the output. `import '@object-ui/components'` is load-bearing: importing the package is what registers the default renderers, so a file that drops it renders nothing. +The constant is typed as `SchemaRendererProps['schema']`, the schema +`SchemaRenderer` takes, so the compiler checks your schema where it is written: +each node's `type` stays a literal and is checked against the node types the +prop accepts, and a schema it does not accept is refused on that line. ```typescript // No React import: under the automatic JSX runtime ("jsx": "react-jsx", // what a new Vite or Next project is configured with) nothing here reads that // identifier, so it compiles as an unused local. Add the import back only if // this file is built with the classic "jsx": "react" transform. -import { SchemaRenderer } from '@object-ui/react'; +import { SchemaRenderer, type SchemaRendererProps } from '@object-ui/react'; // Importing the package registers every default renderer as a side effect — // there is no separate registration call. import '@object-ui/components'; -const schema = { +// Typed as the schema SchemaRenderer takes, so each "type" below stays a literal +// and the compiler checks the schema against the node types it accepts. +const schema: SchemaRendererProps['schema'] = { "type": "div", "className": "p-4", "children": [ { - "type": "h1", - "children": "Hello World" + "type": "text", + "variant": "h1", + "content": "Hello World" } ] }; diff --git a/packages/vscode-extension/DESIGN.md b/packages/vscode-extension/DESIGN.md index 6c2109ca9f..dcfa8a3d58 100644 --- a/packages/vscode-extension/DESIGN.md +++ b/packages/vscode-extension/DESIGN.md @@ -271,12 +271,14 @@ if (currentType === 'input') { // what a new Vite or Next project is configured with) nothing here reads that // identifier, so it compiles as an unused local. Add the import back only if // this file is built with the classic "jsx": "react" transform. -import { SchemaRenderer } from '@object-ui/react'; +import { SchemaRenderer, type SchemaRendererProps } from '@object-ui/react'; // Importing the package registers every default renderer as a side effect — // there is no separate registration call. import '@object-ui/components'; -const schema = { /* 用户的schema */ }; +// Typed as the schema SchemaRenderer takes, so each "type" below stays a literal +// and the compiler checks the schema against the node types it accepts. +const schema: SchemaRendererProps['schema'] = { /* 用户的schema */ }; export default function GeneratedComponent() { return ; diff --git a/packages/vscode-extension/src/__tests__/export-to-react-compiles.test.ts b/packages/vscode-extension/src/__tests__/export-to-react-compiles.test.ts index 103a1b6383..f9c6cdbf5a 100644 --- a/packages/vscode-extension/src/__tests__/export-to-react-compiles.test.ts +++ b/packages/vscode-extension/src/__tests__/export-to-react-compiles.test.ts @@ -144,7 +144,8 @@ declare module 'react' { } declare module '@object-ui/react' { import type { JSX } from 'react/jsx-runtime'; - export function SchemaRenderer(props: { schema: unknown }): JSX.Element; + export interface SchemaRendererProps { schema: unknown } + export function SchemaRenderer(props: SchemaRendererProps): JSX.Element; } declare module '@object-ui/components' {} `; @@ -152,8 +153,20 @@ declare module '@object-ui/components' {} const GENERATED_PATH = '/objectui-7862/Generated.tsx'; const STUB_PATH = '/objectui-7862/stubs.d.ts'; -/** Compile one candidate file and return its diagnostics, formatted. */ -function compile(source: string, overrides: ts.CompilerOptions = {}): string[] { +/** + * Compile one candidate file and return its diagnostics, formatted. + * + * `stubs` replaces the module declarations above (objectui#11515's real-type + * leg below passes its own). `onlyGenerated` reports the generated file's + * diagnostics alone: that leg reads real source trees, whose own diagnostics + * under these options are not this pin's question. + */ +function compile( + source: string, + overrides: ts.CompilerOptions = {}, + stubs: string = STUB_DECLARATIONS, + onlyGenerated = false +): string[] { const options: ts.CompilerOptions = { target: ts.ScriptTarget.ES2020, module: ts.ModuleKind.ESNext, @@ -174,7 +187,7 @@ function compile(source: string, overrides: ts.CompilerOptions = {}): string[] { const virtual = new Map([ [GENERATED_PATH, source], - [STUB_PATH, STUB_DECLARATIONS], + [STUB_PATH, stubs], ]); const read = (fileName: string): string | undefined => virtual.get(fileName) ?? @@ -200,7 +213,16 @@ function compile(source: string, overrides: ts.CompilerOptions = {}): string[] { }; const program = ts.createProgram([STUB_PATH, GENERATED_PATH], options, host); - return ts.getPreEmitDiagnostics(program).map((diagnostic) => { + const generated = program.getSourceFile(GENERATED_PATH); + const diagnostics = + onlyGenerated && generated + ? [ + ...program.getOptionsDiagnostics(), + ...program.getSyntacticDiagnostics(generated), + ...program.getSemanticDiagnostics(generated), + ] + : ts.getPreEmitDiagnostics(program); + return diagnostics.map((diagnostic) => { const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, ' '); if (diagnostic.file && diagnostic.start !== undefined) { const { line, character } = diagnostic.file.getLineAndCharacterOfPosition( @@ -313,13 +335,18 @@ const DOCUMENTED_MIRRORS = [ }, ] as const; -/** The part shape B binds: everything above the schema assignment. */ +/** + * The part shape B binds: everything above the schema assignment. The line is + * found by its binding, `const schema`, annotated or not: objectui#11515 typed + * it, so it reads `const schema: SchemaRendererProps['schema'] =` in the + * product, while a document may still print the binding bare. + */ function preambleOf(source: string, what: string): string { const lines = source.split('\n'); - const end = lines.findIndex((line) => line.startsWith('const schema =')); + const end = lines.findIndex((line) => /^const schema\b/.test(line)); if (end < 0) { throw new Error( - `no \`const schema =\` line in ${what} — this pin needs rewriting, not deleting.` + `no \`const schema\` line in ${what} — this pin needs rewriting, not deleting.` ); } return lines.slice(0, end).join('\n').trimEnd(); @@ -412,3 +439,114 @@ describe('Export to React — the documented mirrors of the preamble (objectui#7 expect(documentedPreambleFrom(asDocument(product), mirror)).toBe(product); }); }); + +/** + * objectui#11515 — the emitted `schema` constant compiles against the REAL + * `SchemaRendererProps['schema']`, not against a stub of it. + * + * WHY THE PIN ABOVE CANNOT SEE THIS. Its stub types the prop as `unknown`, + * which accepts every schema, so an annotation, a missing annotation or a wrong + * one all compile there. objectui#11466 types the prop as the declared-node + * union, which discriminates on the literal `type`: an unannotated constant + * widens every `type` to `string` and is refused, and `SchemaNode`, the first + * candidate annotation, is refused today and under that change alike, because + * it admits `number` and `boolean`, which the prop leaves out. + * + * HOW THE REAL TYPE IS REACHED WITHOUT A BUILD (the header's CI constraint + * stands). `@object-ui/types` resolves to its SOURCE entry, which is in the tree + * when no `dist/` is. The `@object-ui/react` module is still declared here, but + * its `SchemaRendererProps` is read out of `packages/react/src/SchemaRenderer.tsx` + * at test time: the interface body and the `@object-ui/types` names it imports, + * verbatim. So when the prop's type moves, this program moves with it, and a + * restated copy of the prop never exists to drift. + */ +const REACT_SCHEMA_RENDERER = join(REPO_ROOT, 'packages/react/src/SchemaRenderer.tsx'); +const TYPES_SOURCE_ENTRY = join(REPO_ROOT, 'packages/types/src/index.ts'); + +/** The documented sample (`content/docs/utilities/vscode-extension.mdx`), in declared types. */ +const DOC_SAMPLE_SCHEMA = JSON.stringify( + { + type: 'div', + className: 'p-4', + children: [{ type: 'text', variant: 'h1', content: 'Hello World' }], + }, + null, + 2 +); + +/** The real `SchemaRendererProps` declaration, as module text for the stub. */ +function realSchemaRendererProps(): { imports: string; body: string } { + if (!existsSync(REACT_SCHEMA_RENDERER)) { + throw new Error('packages/react/src/SchemaRenderer.tsx is gone — this pin needs rewriting, not deleting.'); + } + const source = readFileSync(REACT_SCHEMA_RENDERER, 'utf8'); + const imports = source.match(/^import type \{([^}]*)\} from '@object-ui\/types';$/m); + const body = source.match(/^export interface SchemaRendererProps \{\n([^}]*)\n\}$/m); + if (!imports || !body) { + throw new Error( + 'SchemaRenderer.tsx no longer spells its `@object-ui/types` type import or `SchemaRendererProps` the way this extractor reads them — this pin needs rewriting, not deleting.' + ); + } + if (!/^\s*schema:/m.test(body[1])) { + throw new Error('`SchemaRendererProps` has no `schema` member — this pin needs rewriting, not deleting.'); + } + return { imports: imports[1].trim(), body: body[1] }; +} + +/** The stubs above, with `@object-ui/react` carrying the real prop type. */ +function realPropStubs(): string { + const { imports, body } = realSchemaRendererProps(); + const react = `declare module '@object-ui/react' { + import type { JSX } from 'react/jsx-runtime'; + import type { ${imports} } from '@object-ui/types'; + export interface SchemaRendererProps { +${body} + } + export function SchemaRenderer(props: SchemaRendererProps): JSX.Element; +}`; + const stubbed = STUB_DECLARATIONS.replace( + /declare module '@object-ui\/react' \{[\s\S]*?\n\}/, + react + ); + if (stubbed === STUB_DECLARATIONS) { + throw new Error('the `@object-ui/react` stub was not replaced — this pin needs rewriting, not deleting.'); + } + return stubbed; +} + +function compileAgainstRealProp(schemaJson: string): string[] { + return compile( + generatedFile().replace(SAMPLE_SCHEMA, schemaJson), + { + lib: ['lib.es2022.d.ts', 'lib.dom.d.ts'], + paths: { '@object-ui/types': [TYPES_SOURCE_ENTRY] }, + }, + realPropStubs(), + true + ); +} + +describe('Export to React — the schema constant against the real prop type (objectui#11515)', () => { + it('compiles the documented sample, annotated, against SchemaRendererProps[\'schema\']', () => { + expect(compileAgainstRealProp(DOC_SAMPLE_SCHEMA)).toEqual([]); + }); + + it('is a harness that can fail — a schema that is no node is refused AT the constant', () => { + // The positive control: if `@object-ui/types` stopped resolving, the prop + // would read as an error type and this would compile. The refusal lands on + // the annotated line, which is what the annotation buys: the error is + // reported where the schema is written, not at the JSX element. + const product = generatedFile().replace(SAMPLE_SCHEMA, JSON.stringify({ className: 'p-4' }, null, 2)); + const constLine = product.split('\n').findIndex((line) => /^const schema\b/.test(line)) + 1; + const diagnostics = compileAgainstRealProp(JSON.stringify({ className: 'p-4' }, null, 2)); + expect(diagnostics).toHaveLength(1); + expect(diagnostics[0]).toContain(`${GENERATED_PATH}(${constLine},`); + expect(diagnostics[0]).toContain('error TS2322'); + }); + + it('reads the prop from the source it names, not from a copy', () => { + const stubs = realPropStubs(); + expect(stubs).toContain(realSchemaRendererProps().body); + expect(stubs).not.toContain('export interface SchemaRendererProps { schema: unknown }'); + }); +}); diff --git a/packages/vscode-extension/src/__tests__/export-to-react-preamble.test.ts b/packages/vscode-extension/src/__tests__/export-to-react-preamble.test.ts index b45c4be295..3d4f55b054 100644 --- a/packages/vscode-extension/src/__tests__/export-to-react-preamble.test.ts +++ b/packages/vscode-extension/src/__tests__/export-to-react-preamble.test.ts @@ -96,7 +96,10 @@ describe('Export to React — the generated file preamble (objectui#7837)', () = it('still imports SchemaRenderer from @object-ui/react — the renderer it calls', () => { const template = generatedFileTemplate(); - expect(template).toContain("import { SchemaRenderer } from '@object-ui/react';"); + // objectui#11515: the same import also names the prop type the schema + // constant is annotated with, type-only, so it adds no runtime binding. + expect(template).toContain("import { SchemaRenderer, type SchemaRendererProps } from '@object-ui/react';"); + expect(template).toContain("const schema: SchemaRendererProps['schema'] = "); expect(template).toContain(''); }); }); diff --git a/packages/vscode-extension/src/extension.ts b/packages/vscode-extension/src/extension.ts index e9f001d546..5a4ba55228 100644 --- a/packages/vscode-extension/src/extension.ts +++ b/packages/vscode-extension/src/extension.ts @@ -221,6 +221,13 @@ async function exportToReact(editor: vscode.TextEditor) { /** * Generate React component from schema + * + * The emitted `schema` constant is annotated with `SchemaRendererProps['schema']` + * (objectui#11515). Unannotated, every `type` in it widens to `string`, which a + * prop that discriminates on the literal `type` refuses. `SchemaNode` was the + * first candidate and does not fit: it also admits `number` and `boolean`, which + * the prop leaves out, and the narrowing at the initializer does not reach the + * component's body, so the one JSX line was refused for every schema. */ function generateReactComponent(schema: any): string { const schemaJson = JSON.stringify(schema, null, 2); @@ -229,12 +236,14 @@ function generateReactComponent(schema: any): string { // what a new Vite or Next project is configured with) nothing here reads that // identifier, so it compiles as an unused local. Add the import back only if // this file is built with the classic "jsx": "react" transform. -import { SchemaRenderer } from '@object-ui/react'; +import { SchemaRenderer, type SchemaRendererProps } from '@object-ui/react'; // Importing the package registers every default renderer as a side effect — // there is no separate registration call. import '@object-ui/components'; -const schema = ${schemaJson}; +// Typed as the schema SchemaRenderer takes, so each "type" below stays a literal +// and the compiler checks the schema against the node types it accepts. +const schema: SchemaRendererProps['schema'] = ${schemaJson}; export default function GeneratedComponent() { return ; From 71f9f2acae2a038021d8d866f64014f4b2f88cf0 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 2 Oct 2026 23:19:17 +0000 Subject: [PATCH 4/5] chore(changeset): objectui#11515's three entries, dated notes on three pending entries it makes false, and the cli pin on the README's full document - `@object-ui/types` minor (the three TS twins), `object-ui` patch (the Export to React annotation), `@object-ui/plugin-detail` patch (the README's Activity tab). - Dated notes on 8114 and 11321 (the README no longer hands `record:activity` its `items`) and 11478 (`AnySchema` gains three members). - `validate-passing-keys-11440.test.ts` described the README's second tab as the host feed; it now validates the full document as the page writes it and keeps the host-feed row as the refusal's control. Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC Co-authored-by: Claude --- .changeset/11321-record-feed-host-slots.md | 7 ++++ .../11478-anyschema-declared-node-types.md | 7 ++++ ...lugin-detail-readme-activity-properties.md | 7 ++++ .../11515-types-ts-twins-zod-only-arms.md | 16 ++++++++ .../11515-vscode-export-react-annotation.md | 11 ++++++ .../8114-detail-tab-activity-timeline.md | 9 +++++ .../validate-passing-keys-11440.test.ts | 37 +++++++++++++++---- 7 files changed, 86 insertions(+), 8 deletions(-) create mode 100644 .changeset/11515-plugin-detail-readme-activity-properties.md create mode 100644 .changeset/11515-types-ts-twins-zod-only-arms.md create mode 100644 .changeset/11515-vscode-export-react-annotation.md diff --git a/.changeset/11321-record-feed-host-slots.md b/.changeset/11321-record-feed-host-slots.md index a03362ad13..f2afed4bcb 100644 --- a/.changeset/11321-record-feed-host-slots.md +++ b/.changeset/11321-record-feed-host-slots.md @@ -17,3 +17,10 @@ A host feed slot written on a `record:activity` or `record:history` node is refu **Across the release.** The `record:activity` and `record:history` arms have not shipped yet: `@object-ui/types` 17.6.0 has no public-block arm, and its `safeValidateSchema` refuses both types at `type`. So this narrows what earlier entries of this same release accept, and nothing a published consumer could validate before. **Docs.** The `record:related_list` example in the schema reference's retired-`related` note now writes its props in the `properties` bag, the last of the flat public-block examples objectui#11321 named. The plugin-detail page says `items` is passed in code and refused when a document writes it. + +⚠️ **Dated note, 2026-10-02 — the plugin-detail README no longer passes `items` — objectui#11515.** +At this change the plugin-detail README handed `record:activity` its `items` +inside a `DetailView` tab, the TSX composition the paragraph above names; now +that tab authors the block's declared `properties`, and no example in this +repository composes a `record:activity` node with `items`. The refusal and +what a host may still do in code are unchanged. The rest of this entry is kept as the reading of this change. diff --git a/.changeset/11478-anyschema-declared-node-types.md b/.changeset/11478-anyschema-declared-node-types.md index f83d9ddc10..fe3cf2ac0f 100644 --- a/.changeset/11478-anyschema-declared-node-types.md +++ b/.changeset/11478-anyschema-declared-node-types.md @@ -24,3 +24,10 @@ admitted every one of these nodes. enumerates every exported object type that extends `BaseSchema` with a literal `type` and fails on any that is not a member. `ActionBarSchema` does not extend `BaseSchema`, so it is held by name. + +⚠️ **Dated note, 2026-10-02 — three more members — objectui#11515.** +At this change `ViewComponentSchema` was the four schemas named above; now it +also holds `DetailSectionNodeSchema` (`detail-section`), and `AnySchema` also +holds `AppSchemaRendererNodeSchema` (`app-schema-renderer`) and +`CloudPlanStatusSchema` (`cloud:plan-status`), the TypeScript twins of three +zod arms that had none. The rest of this entry is kept as the reading of this change. diff --git a/.changeset/11515-plugin-detail-readme-activity-properties.md b/.changeset/11515-plugin-detail-readme-activity-properties.md new file mode 100644 index 0000000000..410e898862 --- /dev/null +++ b/.changeset/11515-plugin-detail-readme-activity-properties.md @@ -0,0 +1,7 @@ +--- +'@object-ui/plugin-detail': patch +--- + +The README's Activity tab authors `record:activity`'s declared inputs in `properties` (`{ limit: 20, showCompleted: false }`) instead of a host feed in `items` (objectui#11515). + +`items` is the host's feed slot, which `@object-ui/types` refuses by name on the node (objectui#11321), so the example taught a key the node's declaration refuses. The note under the example now says where the block's feed comes from: a record host, such as the console's record page, or, in a bare `` like the example, the block's empty state ("No activity recorded"), which is what the tab draws there. No package source changes. diff --git a/.changeset/11515-types-ts-twins-zod-only-arms.md b/.changeset/11515-types-ts-twins-zod-only-arms.md new file mode 100644 index 0000000000..217e98854a --- /dev/null +++ b/.changeset/11515-types-ts-twins-zod-only-arms.md @@ -0,0 +1,16 @@ +--- +'@object-ui/types': minor +--- + +`@object-ui/types` declares TypeScript types for three node types its zod face already validates: `DetailSectionNodeSchema` (`detail-section`), `AppSchemaRendererNodeSchema` (`app-schema-renderer`) and `CloudPlanStatusSchema` (`cloud:plan-status`), each the twin of the zod arm of the same name (objectui#11515). + +**Clause-②: yes (widening)** — the TypeScript face's declared surface widens to match what the zod face already publishes: three new exported types, each a member of `AnySchema` (`DetailSectionNodeSchema` through `ViewComponentSchema`). No zod schema, validator verdict or runtime behaviour changes. + +**What changed, in observable terms.** + +- `SchemaByType<'detail-section'>`, `SchemaByType<'app-schema-renderer'>` and `SchemaByType<'cloud:plan-status'>` resolve to the new types; each was `never`. +- `DetailSectionNodeSchema` declares the ten section members the arm picks from `DetailViewSectionSchema` (`title`, `description`, `icon`, `fields`, `collapsible`, `defaultCollapsed`, `columns`, `showBorder`, `headerColor`, `hideEmpty`), each `DetailViewSection`'s own member by reference, `fields` required. +- `AppSchemaRendererNodeSchema` declares `schema` (the app document, `AppComponentSchema`), `basePath` and `mobileNavMode` (`'drawer'` | `'bottom_nav'`). +- `CloudPlanStatusSchema` declares a required `properties` bag holding `plan: string` and no other key. The arm also refuses an empty `plan`, which the type cannot state. +- On all three, `body` and `children` are `?: never`, the twins of the arms' by-name refusals. +- Each type is a `zod-mirror-parity` pair with its arm. `cloud:plan-status` measures clean. The other two carry the reading their by-reference member already carries one level up: `fields` (the `DetailViewField` `options` divergence) and `schema` (the app document's wider reading through `NavigationItemSchema`'s `z.ZodType`). diff --git a/.changeset/11515-vscode-export-react-annotation.md b/.changeset/11515-vscode-export-react-annotation.md new file mode 100644 index 0000000000..69d32025c5 --- /dev/null +++ b/.changeset/11515-vscode-export-react-annotation.md @@ -0,0 +1,11 @@ +--- +'object-ui': patch +--- + +The VS Code extension's **Export to React** command types the `schema` constant it emits as `SchemaRendererProps['schema']`, imported type-only from `@object-ui/react` beside `SchemaRenderer` (objectui#11515). + +Unannotated, every `type` in the constant widened to `string`. A `SchemaRenderer` prop that discriminates on the literal `type` refuses such a value, so the generated file stopped compiling as soon as the prop is narrowed to the declared node types (objectui#11466). Annotated, each `type` stays a literal and the schema is checked where it is written: a schema the prop does not accept is refused on the constant's own line. Measured with `tsc` against the built packages, the emitted file compiles today and with objectui#11466 applied. + +`SchemaNode` was the annotation first proposed, and it does not fit: it also admits `number` and `boolean`, which the prop leaves out, so every generated file was refused at its one JSX line. + +The compile pin gains a leg against the real prop type, read from `SchemaRenderer.tsx`, with a positive control. The two documented copies of the preamble (`DESIGN.md` and the docs page) follow it, and the docs page's sample authors a `text` node with its `h1` variant. diff --git a/.changeset/8114-detail-tab-activity-timeline.md b/.changeset/8114-detail-tab-activity-timeline.md index 7b059643bf..86804b560f 100644 --- a/.changeset/8114-detail-tab-activity-timeline.md +++ b/.changeset/8114-detail-tab-activity-timeline.md @@ -52,3 +52,12 @@ does not walk `packages/NAME/README.md` — that widening is objectui#7896's, an objectui#7896 is blocked by this card. Measured rather than assumed: with an unregistered type substituted back into this very block, `check:doc-snippets` and `check:doc-types` both still exit 0. + +⚠️ **Dated note, 2026-10-02 — the Activity tab authors `properties`, not `items` — objectui#11515.** +At this change the README's Activity tab handed `record:activity` a feed it +already owned as `items: activityData`, typed `FeedItem[]`; now the tab authors +the block's declared inputs in `properties` (`{ limit: 20, showCompleted: false }`), +`activityData` and its `FeedItem` import are gone, and in a bare `` +the tab draws the block's empty state, because `items` is the host's feed slot +the node refuses by name (objectui#11321). The `items, not data` bullet above +and the sentence on `activityData` no longer describe the README. The rest of this entry is kept as the reading of this change. diff --git a/packages/cli/src/__tests__/validate-passing-keys-11440.test.ts b/packages/cli/src/__tests__/validate-passing-keys-11440.test.ts index 0e0f8d39e7..69b87d9f13 100644 --- a/packages/cli/src/__tests__/validate-passing-keys-11440.test.ts +++ b/packages/cli/src/__tests__/validate-passing-keys-11440.test.ts @@ -17,13 +17,15 @@ * - the plugin-detail package's own `detail-view` example, whose first tab's * content is a `detail-section` node (its "With Tabs" section). * - * That example is TSX, and its second tab hands `record:activity` a - * host feed (`items: activityData`), which objectui#11321 refuses by name in a - * JSON document — that refusal names this example as the TSX composition it - * stays legal in. So the row below validates the example's first tab as - * written (restated here, not read from the page, so this file reads no - * markdown), and the second row holds the host-feed refusal as the ONLY issue - * left on the full document. + * That example is TSX. Its second tab handed `record:activity` a host feed + * (`items: activityData`), which objectui#11321 refuses by name in a JSON + * document, so the rows below validated the first tab alone and held the + * host-feed refusal as the ONLY issue left on the full document. + * objectui#11515 re-authored that tab to the block's declared `properties`, so + * the full document as the page now writes it validates (the third row). The + * host-feed row stays, as the refusal's control on the old spelling. Each + * document is restated here, not read from the page, so this file reads no + * markdown. */ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; @@ -130,7 +132,26 @@ describe('objectui validate — the documents objectui#11440 arms', () => { expect(exitCodes).toEqual([0]); }); - it('on the example\'s full document, the only issue left is the host feed on `record:activity` (objectui#11321)', async () => { + it('validates the example\'s full document as the page writes it since objectui#11515: the Activity tab authors `properties`', async () => { + const text = await run('detail-view-full-declared', { + type: 'detail-view', + title: 'Account: Acme Corp', + objectName: 'accounts', + resourceId: '12345', + fields: [{ name: 'name', label: 'Account Name' }, { name: 'industry', label: 'Industry' }], + tabs: [ + { key: 'details', label: 'Details', icon: '📄', content: EXAMPLE_DETAIL_SECTION }, + { key: 'activity', label: 'Activity', badge: '12', content: { type: 'record:activity', properties: { limit: 20, showCompleted: false } } }, + ], + showEdit: true, + showDelete: true, + }); + expect(text).not.toContain('Schema validation failed'); + expect(text).toContain('Schema is valid'); + expect(exitCodes).toEqual([0]); + }); + + it('on the example\'s old full document, the only issue left is the host feed on `record:activity` (objectui#11321)', async () => { const text = await run('detail-view-full', { type: 'detail-view', title: 'Account: Acme Corp', From afb660b754e8ff644bcd74dcd5b606e13dc8e853 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 2 Oct 2026 23:24:56 +0000 Subject: [PATCH 5/5] docs(vscode-extension): the Export to React sample keeps its declared `h1` node (objectui#11515) `h1` is a declared type (`HtmlElementSchema`, objectui#8499), and `check-doc-component-types.test.ts` pins it in this very sample as the registered replacement for `heading`. With the constant annotated, the `h1` sample compiles against the built packages today and with objectui#11466 applied (measured with tsc and with check:doc-snippets on both trees), so the `text` / `variant: 'h1'` rewrite the card proposed is not needed and is reverted. The compile pin's documented sample follows. Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC Co-authored-by: Claude --- .changeset/11515-vscode-export-react-annotation.md | 2 +- content/docs/utilities/vscode-extension.mdx | 5 ++--- .../src/__tests__/export-to-react-compiles.test.ts | 8 ++++++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.changeset/11515-vscode-export-react-annotation.md b/.changeset/11515-vscode-export-react-annotation.md index 69d32025c5..631a6e2617 100644 --- a/.changeset/11515-vscode-export-react-annotation.md +++ b/.changeset/11515-vscode-export-react-annotation.md @@ -8,4 +8,4 @@ Unannotated, every `type` in the constant widened to `string`. A `SchemaRenderer `SchemaNode` was the annotation first proposed, and it does not fit: it also admits `number` and `boolean`, which the prop leaves out, so every generated file was refused at its one JSX line. -The compile pin gains a leg against the real prop type, read from `SchemaRenderer.tsx`, with a positive control. The two documented copies of the preamble (`DESIGN.md` and the docs page) follow it, and the docs page's sample authors a `text` node with its `h1` variant. +The compile pin gains a leg against the real prop type, read from `SchemaRenderer.tsx`, with a positive control. The two documented copies of the preamble (`DESIGN.md` and the docs page) follow it. The docs page's sample keeps its `h1` node: `h1` is a declared type (`HtmlElementSchema`), and the annotated sample compiles both today and with objectui#11466 applied. diff --git a/content/docs/utilities/vscode-extension.mdx b/content/docs/utilities/vscode-extension.mdx index 354c6e80bb..543ca458e0 100644 --- a/content/docs/utilities/vscode-extension.mdx +++ b/content/docs/utilities/vscode-extension.mdx @@ -108,9 +108,8 @@ const schema: SchemaRendererProps['schema'] = { "className": "p-4", "children": [ { - "type": "text", - "variant": "h1", - "content": "Hello World" + "type": "h1", + "children": "Hello World" } ] }; diff --git a/packages/vscode-extension/src/__tests__/export-to-react-compiles.test.ts b/packages/vscode-extension/src/__tests__/export-to-react-compiles.test.ts index f9c6cdbf5a..2c03c61d89 100644 --- a/packages/vscode-extension/src/__tests__/export-to-react-compiles.test.ts +++ b/packages/vscode-extension/src/__tests__/export-to-react-compiles.test.ts @@ -463,12 +463,16 @@ describe('Export to React — the documented mirrors of the preamble (objectui#7 const REACT_SCHEMA_RENDERER = join(REPO_ROOT, 'packages/react/src/SchemaRenderer.tsx'); const TYPES_SOURCE_ENTRY = join(REPO_ROOT, 'packages/types/src/index.ts'); -/** The documented sample (`content/docs/utilities/vscode-extension.mdx`), in declared types. */ +/** + * The documented sample (`content/docs/utilities/vscode-extension.mdx`). Both + * of its types are declared: `div`, and `h1`, one of the HTML passthrough tags + * `HtmlElementSchema` declares (objectui#8499). + */ const DOC_SAMPLE_SCHEMA = JSON.stringify( { type: 'div', className: 'p-4', - children: [{ type: 'text', variant: 'h1', content: 'Hello World' }], + children: [{ type: 'h1', children: 'Hello World' }], }, null, 2