From ceb7b297f8394167fb6801e4714901d18c056595 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 9 Oct 2026 20:48:43 +0000 Subject: [PATCH] refactor(plugins): one authority each for AggregationConfig, ConditionalFormattingRule and ViewType (objectui#6349, batch 7) Three plugin <-> @object-ui/types rows of the one-authority ledger, each two meanings behind one name. @object-ui/types keeps the general name in all three. - AggregationConfig: the grid's per-group header aggregation (field, type) is renamed GroupAggregationConfig; types' is the query-AST aggregation (function, alias, distinct, separator). The plugin-grid root publishes the new name in place of the old one. - ConditionalFormattingRule: KanbanImpl's module-local alias of KanbanConditionalFormattingRule is deleted; the board spells the kanban rule by its own name. No package entry reached the alias. - ViewType: plugin-list's alias of core's ListViewVisualization (nine arms, without list and detail) is deleted; the switcher and ListView spell ListViewVisualization, and the plugin-list root no longer publishes ViewType. KNOWN_COLLISIONS goes from 19 to 16 rows. Types only: emitted JS is byte-identical. Changesets: plugin-grid and plugin-list minor with breaking notes, plugin-kanban patch. Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8 Co-authored-by: Claude --- .changeset/6349-batch7-plugin-grid.md | 9 ++++ .changeset/6349-batch7-plugin-kanban.md | 7 +++ .changeset/6349-batch7-plugin-list.md | 9 ++++ packages/plugin-grid/README.md | 2 +- packages/plugin-grid/src/index.tsx | 2 +- packages/plugin-grid/src/useGroupedData.ts | 21 +++++--- packages/plugin-grid/src/useServerGrouping.ts | 6 +-- packages/plugin-kanban/src/KanbanImpl.tsx | 15 +++--- packages/plugin-list/src/ListView.tsx | 26 ++++----- packages/plugin-list/src/ViewSwitcher.tsx | 53 +++++++++---------- packages/plugin-list/src/index.tsx | 5 +- ...e-authority-per-exported-name-6273.test.ts | 36 ++++++++++--- 12 files changed, 126 insertions(+), 65 deletions(-) create mode 100644 .changeset/6349-batch7-plugin-grid.md create mode 100644 .changeset/6349-batch7-plugin-kanban.md create mode 100644 .changeset/6349-batch7-plugin-list.md diff --git a/.changeset/6349-batch7-plugin-grid.md b/.changeset/6349-batch7-plugin-grid.md new file mode 100644 index 0000000000..91bf8252de --- /dev/null +++ b/.changeset/6349-batch7-plugin-grid.md @@ -0,0 +1,9 @@ +--- +'@object-ui/plugin-grid': minor +--- + +The per-group aggregation type that `useGroupedData` takes is declared and exported as `GroupAggregationConfig` instead of `AggregationConfig` (objectui#6349, batch 7). `@object-ui/types` publishes a different `AggregationConfig`: the query-AST aggregation `DriverQueryConfig.aggregations` carries (`function`, `alias`, `distinct`, `separator`). The two shared only `field`, so one exported name stood for two contracts across the two packages. + +**Type change, breaking for some consumers.** `AggregationConfig` is no longer exported from `@object-ui/plugin-grid`. Replace `import type { AggregationConfig } from '@object-ui/plugin-grid'` with `GroupAggregationConfig`; the members (`field`, `type`) are unchanged. The compiler names the replacement (TS2724, "Did you mean 'GroupAggregationConfig'?"). `AggregationType` and `AggregationResult` keep their names. The README's export list now names `GroupAggregationConfig`. + +No runtime behaviour changes. diff --git a/.changeset/6349-batch7-plugin-kanban.md b/.changeset/6349-batch7-plugin-kanban.md new file mode 100644 index 0000000000..bba43460a7 --- /dev/null +++ b/.changeset/6349-batch7-plugin-kanban.md @@ -0,0 +1,7 @@ +--- +'@object-ui/plugin-kanban': patch +--- + +The board implementation types its `conditionalFormatting` rules as `@object-ui/types`' `KanbanConditionalFormattingRule` by that name, instead of through a module-local alias named `ConditionalFormattingRule` (objectui#6349, batch 7). `@object-ui/types` publishes `ConditionalFormattingRule` for the grid's and list view's rule, so the alias put a second meaning behind that name. The alias was internal: this package's entry never exported it, so no import changes, and the rule type is the same. + +No runtime behaviour changes. diff --git a/.changeset/6349-batch7-plugin-list.md b/.changeset/6349-batch7-plugin-list.md new file mode 100644 index 0000000000..cb369a18fc --- /dev/null +++ b/.changeset/6349-batch7-plugin-list.md @@ -0,0 +1,9 @@ +--- +'@object-ui/plugin-list': minor +--- + +The view switcher's views are typed as `@object-ui/core`'s `ListViewVisualization`, and this package no longer re-exports that union as `ViewType` (objectui#6349, batch 7). `@object-ui/types` publishes a different `ViewType`: the whole view-type vocabulary, which adds the `list` and `detail` categories to the nine visualizations the switcher draws. One exported name stood for two unions across the two packages. + +**Type change, breaking for some consumers.** `ViewType` is no longer exported from `@object-ui/plugin-list` (TS2305 on import). Import `ListViewVisualization` from `@object-ui/core` instead; it is the same nine-member union. `ViewSwitcherProps` keeps its members, and `ViewSwitcherProps['currentView']` names the same union. + +No runtime behaviour changes. diff --git a/packages/plugin-grid/README.md b/packages/plugin-grid/README.md index 74add6a675..7b3f1a6f90 100644 --- a/packages/plugin-grid/README.md +++ b/packages/plugin-grid/README.md @@ -142,7 +142,7 @@ import type { GroupEntry, UseGroupedDataResult, AggregationType, - AggregationConfig, + GroupAggregationConfig, AggregationResult, CellRange, UseCellClipboardOptions, diff --git a/packages/plugin-grid/src/index.tsx b/packages/plugin-grid/src/index.tsx index 532bbd9a4e..147fd11103 100644 --- a/packages/plugin-grid/src/index.tsx +++ b/packages/plugin-grid/src/index.tsx @@ -50,7 +50,7 @@ export type { ObjectGridComponentProps as ObjectGridProps } from './ObjectGrid'; export type { VirtualGridProps, VirtualGridColumn } from './VirtualGrid'; export type { InlineEditingProps } from './InlineEditing'; export type { ImportWizardProps, ImportResult } from './ImportWizard'; -export type { GroupEntry, UseGroupedDataResult, AggregationType, AggregationConfig, AggregationResult } from './useGroupedData'; +export type { GroupEntry, UseGroupedDataResult, AggregationType, GroupAggregationConfig, AggregationResult } from './useGroupedData'; export type { GroupRowProps } from './GroupRow'; export type { RowActionMenuProps } from './components/RowActionMenu'; export type { BulkActionBarProps } from './components/BulkActionBar'; diff --git a/packages/plugin-grid/src/useGroupedData.ts b/packages/plugin-grid/src/useGroupedData.ts index 4090a29e84..5884ecd54d 100644 --- a/packages/plugin-grid/src/useGroupedData.ts +++ b/packages/plugin-grid/src/useGroupedData.ts @@ -13,8 +13,17 @@ import { deriveColumnSummary, type ListViewGroupHeaderRow } from '@objectstack/s /** Supported aggregation function types. */ export type AggregationType = 'sum' | 'count' | 'avg' | 'min' | 'max' | 'count_distinct'; -/** Describes a single aggregation to compute per group. */ -export interface AggregationConfig { +/** + * Describes a single aggregation to compute per group — one entry of + * `ObjectGridSchema.aggregations`, drawn in that group's header. + * + * Not named `AggregationConfig`: `@object-ui/types` publishes that name for + * the query-AST aggregation `DriverQueryConfig.aggregations` carries + * (`function`, `alias`, `distinct`, `separator`), which `@object-ui/core`'s + * `QueryASTBuilder` compiles. The two share only `field`, so this one carries + * the narrower name (objectui#6349, batch 7). + */ +export interface GroupAggregationConfig { /** The field to aggregate. */ field: string; /** The aggregation function. */ @@ -180,7 +189,7 @@ function buildSegmentLabel( */ function computeAggregations( rows: any[], - configs: AggregationConfig[], + configs: GroupAggregationConfig[], ): AggregationResult[] { return configs.map(({ field, type }) => { if (type === 'count_distinct') { @@ -405,7 +414,7 @@ export function serverSummaryOf(type: AggregationType): (typeof SERVER_SUMMARY)[ */ function readServerAggregations( row: ListViewGroupHeaderRow, - configs: AggregationConfig[] | undefined, + configs: GroupAggregationConfig[] | undefined, ): AggregationResult[] { if (!configs || configs.length === 0) return []; const out: AggregationResult[] = []; @@ -463,7 +472,7 @@ function sameGroupKey(a: unknown, b: unknown): boolean { export function useGroupedData( config: GroupingConfig | undefined, data: any[], - aggregations?: AggregationConfig[], + aggregations?: GroupAggregationConfig[], formatValue?: GroupValueFormatter, server?: ServerGroupSource, ): UseGroupedDataResult { @@ -481,7 +490,7 @@ export function useGroupedData( export function useGroupedDataInOptionOrder( config: GroupingConfig | undefined, data: any[], - aggregations: AggregationConfig[] | undefined, + aggregations: GroupAggregationConfig[] | undefined, formatValue: GroupValueFormatter | undefined, server: ServerGroupSource | undefined, optionRanks: GroupOptionRanks | undefined, diff --git a/packages/plugin-grid/src/useServerGrouping.ts b/packages/plugin-grid/src/useServerGrouping.ts index 42a6886160..76abd54917 100644 --- a/packages/plugin-grid/src/useServerGrouping.ts +++ b/packages/plugin-grid/src/useServerGrouping.ts @@ -87,14 +87,14 @@ import { type ListViewGroupQuerySource, } from '@objectstack/spec/ui'; import { isExpandableFieldType } from '@object-ui/core'; -import { serverSummaryOf, type AggregationConfig, type UsableGroupingField } from './useGroupedData'; +import { serverSummaryOf, type GroupAggregationConfig, type UsableGroupingField } from './useGroupedData'; /** * `aggregations` as the summary columns the header query compiles from. An * entry naming no field or an unknown type contributes no node, so it renders * no number rather than a fabricated one. */ -function summaryColumnsOf(aggregations: AggregationConfig[] | undefined): NonNullable { +function summaryColumnsOf(aggregations: GroupAggregationConfig[] | undefined): NonNullable { const out: Array<{ field: string; summary: NonNullable> }> = []; for (const agg of aggregations ?? []) { if (!agg || typeof agg.field !== 'string' || agg.field === '') continue; @@ -126,7 +126,7 @@ export interface ServerGroupHeadersInput { search?: string; searchFields?: readonly string[]; /** `object-grid.aggregations` — the per-group numbers besides the count. */ - aggregations?: AggregationConfig[]; + aggregations?: GroupAggregationConfig[]; /** The object's field catalogue, for reference-typed grouping keys. */ objectFields?: Record; /** Any change re-asks the server (refresh, data invalidation). */ diff --git a/packages/plugin-kanban/src/KanbanImpl.tsx b/packages/plugin-kanban/src/KanbanImpl.tsx index c16184f074..879cd62d75 100644 --- a/packages/plugin-kanban/src/KanbanImpl.tsx +++ b/packages/plugin-kanban/src/KanbanImpl.tsx @@ -129,8 +129,11 @@ const SWIMLANE_AXIS_X_PADDING = 'px-2 pl-36 sm:pl-44' export type { KanbanCard, KanbanColumn } from './types' // Card formatting is the spec `{ condition, style }` CEL rule — the one dialect -// `object-kanban` declares since objectui#11522 — see @object-ui/types. -export type ConditionalFormattingRule = KanbanConditionalFormattingRule +// `object-kanban` declares since objectui#11522 — spelled by its own name, +// `KanbanConditionalFormattingRule` from @object-ui/types. This module used to +// alias it as `ConditionalFormattingRule`, a second meaning behind the name +// `@object-ui/types` gives the grid's and list view's rule (objectui#6349, +// batch 7); no package entry reached the alias. export interface KanbanBoardProps { columns: KanbanColumn[] @@ -140,7 +143,7 @@ export interface KanbanBoardProps { quickAdd?: boolean onQuickAdd?: (columnId: string, title: string) => void coverImageField?: string - conditionalFormatting?: ConditionalFormattingRule[] + conditionalFormatting?: KanbanConditionalFormattingRule[] /** Object field definitions — see `getCardStyles` (objectui#3501). */ objectFields?: unknown /** Field name for swimlane rows (2D grouping) */ @@ -169,7 +172,7 @@ export interface KanbanBoardProps { // `current_user.*` conditions resolve here exactly as they do on grid rows. function getCardStyles( card: KanbanCard, - rules?: ConditionalFormattingRule[], + rules?: KanbanConditionalFormattingRule[], scope?: Record, objectFields?: unknown, ): React.CSSProperties { @@ -183,7 +186,7 @@ function getCardStyles( return resolveConditionalFormatting(card as Record, rules as any, scope, objectFields as never) as React.CSSProperties } -function SortableCard({ card, onCardClick, conditionalFormatting, objectFields }: { card: KanbanCard; onCardClick?: (card: KanbanCard, event?: React.MouseEvent) => void; conditionalFormatting?: ConditionalFormattingRule[]; objectFields?: unknown }) { +function SortableCard({ card, onCardClick, conditionalFormatting, objectFields }: { card: KanbanCard; onCardClick?: (card: KanbanCard, event?: React.MouseEvent) => void; conditionalFormatting?: KanbanConditionalFormattingRule[]; objectFields?: unknown }) { const { attributes, listeners, @@ -547,7 +550,7 @@ function KanbanColumnView({ onCardClick?: (card: KanbanCard, event?: React.MouseEvent) => void quickAdd?: boolean onQuickAdd?: (columnId: string, title: string) => void - conditionalFormatting?: ConditionalFormattingRule[] + conditionalFormatting?: KanbanConditionalFormattingRule[] /** Object field definitions — see `getCardStyles` (objectui#3501). */ objectFields?: unknown /** Container-aware width override from useResizeObserver in KanbanBoardInner. */ diff --git a/packages/plugin-list/src/ListView.tsx b/packages/plugin-list/src/ListView.tsx index 91349c9524..b1f7cb3955 100644 --- a/packages/plugin-list/src/ListView.tsx +++ b/packages/plugin-list/src/ListView.tsx @@ -12,7 +12,7 @@ import type { SortItem } from '@object-ui/components'; import { Search, SlidersHorizontal, ArrowUpDown, X, EyeOff, Pencil, Group, Paintbrush, Inbox, Download, Rows4, Rows3, Rows2, Share2, Printer, Plus, Trash2, CheckSquare, AlertTriangle, ShieldAlert, RotateCw, Loader2, type LucideIcon } from 'lucide-react'; import type { FilterGroup } from '@object-ui/components'; import { VALUELESS_FILTER_BUILDER_OPERATORS, isFilterValueComplete } from '@object-ui/components'; -import { ViewSwitcherDropdown, ViewType } from './ViewSwitcher'; +import { ViewSwitcherDropdown } from './ViewSwitcher'; import { ViewSettingsPopover } from './components/ViewSettingsPopover'; import { UserFilters } from './UserFilters'; import { SchemaRenderer, useNavigationOverlay, classifyLoadError, usePredicateScope, useDataInvalidation, useFilterScope, useResolvedFilter } from '@object-ui/react'; @@ -21,7 +21,7 @@ import { useDensityMode, resolveInlineAriaProps } from '@object-ui/react'; import type { ListViewSchema, ObjectMapConfig } from '@object-ui/types'; import { detectStatusField, isSystemManagedField } from '@object-ui/types'; import { usePullToRefresh } from '@object-ui/mobile'; -import { resolveConditionalFormatting, buildExpandFields, buildExportFileName, resolveEffectiveCrudAffordances, isObjectInlineEditable, partitionRowsByPredicate, normalizeListViewSchema, isListViewVisualization, rowHeightToDensityMode, mergeFilterNodes, FilterOperatorError, columnIdentity, collectPredicateFieldRefs, collectGroupingFieldRefs, listViewPredicates, PLATFORM_RECORD_COLUMNS, EXPANDABLE_FIELD_TYPES, UNMATERIALIZED_FIELD_TYPES, readObjectSortability, isPlatformSortableField, filterPlatformSortableSort } from '@object-ui/core'; +import { type ListViewVisualization, resolveConditionalFormatting, buildExpandFields, buildExportFileName, resolveEffectiveCrudAffordances, isObjectInlineEditable, partitionRowsByPredicate, normalizeListViewSchema, isListViewVisualization, rowHeightToDensityMode, mergeFilterNodes, FilterOperatorError, columnIdentity, collectPredicateFieldRefs, collectGroupingFieldRefs, listViewPredicates, PLATFORM_RECORD_COLUMNS, EXPANDABLE_FIELD_TYPES, UNMATERIALIZED_FIELD_TYPES, readObjectSortability, isPlatformSortableField, filterPlatformSortableSort } from '@object-ui/core'; import { useObjectLabel, useSafeFieldLabel, createSafeTranslation, useDisplayLocale, pickLocalized } from '@object-ui/i18n'; // Two resolvers, two vocabularies — the repo spells the distinction into the // NAMES (objectui#4167). `resolveInlineI18nLabel` is the spec's own @@ -308,7 +308,7 @@ export interface ListViewProps { * is a separate change with its own consumer sweep. */ dataSource?: any; - onViewChange?: (view: ViewType) => void; + onViewChange?: (view: ListViewVisualization) => void; /** * Fires with the advanced-filter group the toolbar's `FilterBuilder` emitted. * @@ -1350,8 +1350,8 @@ export const ListView = React.forwardRef(({ // Declared ahead of `toolbarFlags`, which reads it: which view is ON SCREEN // decides whether a filter control has anything to reach (objectui#10327). - const [currentView, setCurrentView] = React.useState( - (schema.viewType as ViewType) + const [currentView, setCurrentView] = React.useState( + (schema.viewType as ListViewVisualization) ); /** @@ -3104,7 +3104,7 @@ export const ListView = React.forwardRef(({ // Capability-resolvable types: a visualization is only offered when its // required field bindings resolve (ADR-0047) — kanban needs a groupBy, // calendar a start date, etc. `grid` always renders. - const resolvable: ViewType[] = ['grid']; + const resolvable: ListViewVisualization[] = ['grid']; // Check for Kanban capabilities (spec config takes precedence) // @@ -3187,7 +3187,7 @@ export const ListView = React.forwardRef(({ // undrawable kind (`page`) must NOT be pushed here: offering a switch into // a branch that falls through to `grid` is the silent downgrade wearing a // button. - if (schema.viewType && !resolvable.includes(schema.viewType as ViewType) && + if (schema.viewType && !resolvable.includes(schema.viewType as ListViewVisualization) && isListViewVisualization(schema.viewType)) { resolvable.push(schema.viewType); } @@ -3198,8 +3198,8 @@ export const ListView = React.forwardRef(({ // field renders garbage, so it must not be offered. const whitelist = schema.appearance?.allowedVisualizations; if (Array.isArray(whitelist) && whitelist.length > 0) { - const filtered = whitelist.filter((v: any) => resolvable.includes(v)) as ViewType[]; - return filtered.length > 0 ? filtered : (['grid'] as ViewType[]); + const filtered = whitelist.filter((v: any) => resolvable.includes(v)) as ListViewVisualization[]; + return filtered.length > 0 ? filtered : (['grid'] as ListViewVisualization[]); } return resolvable; @@ -3231,7 +3231,7 @@ export const ListView = React.forwardRef(({ // Sync view from props React.useEffect(() => { if (schema.viewType) { - setCurrentView(schema.viewType as ViewType); + setCurrentView(schema.viewType as ListViewVisualization); } }, [schema.viewType]); @@ -3240,8 +3240,8 @@ export const ListView = React.forwardRef(({ React.useEffect(() => { try { const savedView = localStorage.getItem(storageKey); - if (savedView && ['grid', 'kanban', 'calendar', 'timeline', 'gantt', 'map', 'gallery'].includes(savedView) && availableViews.includes(savedView as ViewType)) { - setCurrentView(savedView as ViewType); + if (savedView && ['grid', 'kanban', 'calendar', 'timeline', 'gantt', 'map', 'gallery'].includes(savedView) && availableViews.includes(savedView as ListViewVisualization)) { + setCurrentView(savedView as ListViewVisualization); } } catch (error) { console.warn('Failed to load view preference from localStorage:', error); @@ -3249,7 +3249,7 @@ export const ListView = React.forwardRef(({ }, [storageKey, availableViews]); */ - const handleViewChange = React.useCallback((view: ViewType) => { + const handleViewChange = React.useCallback((view: ListViewVisualization) => { setCurrentView(view); try { localStorage.setItem(storageKey, view); diff --git a/packages/plugin-list/src/ViewSwitcher.tsx b/packages/plugin-list/src/ViewSwitcher.tsx index 173a178496..7952fa6fa6 100644 --- a/packages/plugin-list/src/ViewSwitcher.tsx +++ b/packages/plugin-list/src/ViewSwitcher.tsx @@ -25,32 +25,31 @@ import { } from 'lucide-react'; /** - * The visualizations this switcher offers. + * The visualizations this switcher offers are `@object-ui/core`'s + * {@link ListViewVisualization} (`ca3942729`), which derives from + * `@objectstack/spec/ui` through `@object-ui/types`' `ViewType`. * - * DERIVED from `@object-ui/core`'s {@link ListViewVisualization} (`ca3942729`), - * which derives from `@objectstack/spec/ui` through `@object-ui/types`' `ViewType`. - * - * This nine-arm union was written out by hand here, and that is a THIRD + * A nine-arm union was once written out by hand here, and that was a THIRD * re-declaration of one vocabulary — the card behind `ca3942729` was filed against the two in * `@object-ui/types`, and the maps below were described there as total over the * shared union when they were in fact total over this private copy. A map that * is total over a copy is exactly as incomplete as the copy, so the two - * `Record` below only became honest guards once this alias - * replaced the copy: the next visualization the spec adds now fails the build - * in both of them. + * `Record` below are honest guards only because they + * key on the derived union: the next visualization the spec adds fails the + * build in both of them. * - * Exported unchanged in membership — this is the same nine names it always - * carried. `page` is deliberately NOT among them: the spec models it as a list - * view that mounts a published page rather than a visualization a user switches - * into, and `AppearanceConfig.allowedVisualizations` excludes it for the same - * reason. + * Spelled by that name, not re-exported as `ViewType`. This module used to + * publish an alias `ViewType = ListViewVisualization`, a second meaning behind + * the name `@object-ui/types` gives the whole view-type vocabulary — the alias + * lacked its `list` and `detail` arms (objectui#6349, batch 7). `page` is + * deliberately NOT a visualization: the spec models it as a list view that + * mounts a published page rather than one a user switches into, and + * `AppearanceConfig.allowedVisualizations` excludes it for the same reason. */ -export type ViewType = ListViewVisualization; - export interface ViewSwitcherProps { - currentView: ViewType; - availableViews?: ViewType[]; - onViewChange: (view: ViewType) => void; + currentView: ListViewVisualization; + availableViews?: ListViewVisualization[]; + onViewChange: (view: ListViewVisualization) => void; className?: string; /** Enable animated transitions between views (default: true) */ animated?: boolean; @@ -75,7 +74,7 @@ export interface ViewSwitcherProps { // the same glyph for the same `ViewType` — #5586 landed `ChartGantt` for // `gantt` in `plugin-view`, and one view type showing two different icons // depending on which switcher is on screen is the drift worth avoiding. -const VIEW_ICONS: Record = { +const VIEW_ICONS: Record = { grid: , kanban: , gallery: , @@ -102,7 +101,7 @@ const VIEW_ICONS: Record = { * this very namespace: `packages/plugin-view/src/ObjectView.tsx:83` resolves * `console.objectView.new`. */ -const VIEW_LABEL_KEYS: Record = { +const VIEW_LABEL_KEYS: Record = { grid: 'console.objectView.viewTypeGrid', kanban: 'console.objectView.viewTypeKanban', gallery: 'console.objectView.viewTypeGallery', @@ -144,16 +143,16 @@ const useViewSwitcherTranslation = createSafeTranslation( /** * Resolve every visualization's label once per render. * - * Returns a total `Record` so the three call sites per button + * Returns a total `Record` so the three call sites per button * (visible span, `aria-label`, `title`) stay a plain map lookup and cannot - * drift apart — and so a `ViewType` added to the union is a compile error + * drift apart — and so a visualization added to the union is a compile error * naming the missing key rather than a button labelled `undefined`. */ -function useViewLabels(): Record { +function useViewLabels(): Record { const { t } = useViewSwitcherTranslation(); return React.useMemo(() => { - const out = {} as Record; - for (const [view, key] of Object.entries(VIEW_LABEL_KEYS) as [ViewType, string][]) { + const out = {} as Record; + for (const [view, key] of Object.entries(VIEW_LABEL_KEYS) as [ListViewVisualization, string][]) { out[view] = t(key); } return out; @@ -177,7 +176,7 @@ export const ViewSwitcherDropdown: React.FC = ({ const VIEW_LABELS = useViewLabels(); const handleViewChange = React.useCallback( - (view: ViewType) => { + (view: ListViewVisualization) => { setOpen(false); if (view === currentView) return; if (animated && typeof document !== 'undefined' && 'startViewTransition' in document) { @@ -284,7 +283,7 @@ export const ViewSwitcher: React.FC = ({ const VIEW_LABELS = useViewLabels(); const handleViewChange = React.useCallback( - (view: ViewType) => { + (view: ListViewVisualization) => { if (!animated || view === currentView) { onViewChange(view); return; diff --git a/packages/plugin-list/src/index.tsx b/packages/plugin-list/src/index.tsx index 480b18ff0f..99827225cf 100644 --- a/packages/plugin-list/src/index.tsx +++ b/packages/plugin-list/src/index.tsx @@ -25,7 +25,10 @@ export { evaluateConditionalFormatting, normalizeFilterCondition, normalizeFilte export { LIST_DEFAULT_TRANSLATIONS } from './ListView'; export type { ListViewProps, ListViewHandle } from './ListView'; export type { ObjectGalleryProps } from './ObjectGallery'; -export type { ViewSwitcherProps, ViewType } from './ViewSwitcher'; +// The switcher's views are `@object-ui/core`'s `ListViewVisualization`. This +// barrel no longer re-exports them as `ViewType`, the name `@object-ui/types` +// gives the wider view-type vocabulary (objectui#6349, batch 7). +export type { ViewSwitcherProps } from './ViewSwitcher'; /** * Registry entry point for ``. Both bridges it carries — the diff --git a/scripts/__tests__/one-authority-per-exported-name-6273.test.ts b/scripts/__tests__/one-authority-per-exported-name-6273.test.ts index a0666a7bd2..15b6d39267 100644 --- a/scripts/__tests__/one-authority-per-exported-name-6273.test.ts +++ b/scripts/__tests__/one-authority-per-exported-name-6273.test.ts @@ -56,7 +56,7 @@ import { blank, scanSource } from '../js-comment-mask.mjs'; * ⛔ **Near-spellings and derived names are NOT collisions.** The matcher * anchors the name on both ends, so `ConditionalFormattingRule` does not match * `KanbanConditionalFormattingRule` (declared in `packages/types/src/objectql.ts`, - * next to the contested name) or `KanbanNativeConditionalFormattingRule` — a + * next to the bare name) or `KanbanNativeConditionalFormattingRule` — a * derived name this repository declared there until objectui#11522 retired it, * kept below as a synthetic near-spelling. * @@ -367,7 +367,15 @@ const KNOWN_COLLISIONS: ReadonlyMap = new Map([ // members, `refresh` was retired, and `UndoableOperation` (which types `undo`) // moved down beside it, so `ActionRunner.ts` and `UndoManager.ts` both // RE-EXPORT (objectui#6349, batch 4). - ['AggregationConfig', ['packages/plugin-grid/src/useGroupedData.ts', 'packages/types/src/data-protocol.ts']], + // `AggregationConfig` sat here, colliding between + // `packages/plugin-grid/src/useGroupedData.ts` and + // `packages/types/src/data-protocol.ts`. Two meanings that share only `field`: + // `@object-ui/types`' is the query-AST aggregation `DriverQueryConfig` carries + // (`function`, `alias`, `distinct`, `separator`) and `@object-ui/core`'s + // `QueryASTBuilder` compiles; the grid's is one per-group header aggregation + // (`field`, `type`). RENAME branch: the general name stays on + // `@object-ui/types`, and the grid's declaration now spells + // `GroupAggregationConfig` (objectui#6349, batch 7). ['AppShellProps', ['packages/app-shell/src/types.ts', 'packages/layout/src/AppShell.tsx']], // `ActionSchema` sat here, colliding between `packages/types/src/crud.ts` and // `packages/types/src/ui-action.ts`. Structurally unrelated types — 28 members @@ -420,9 +428,15 @@ const KNOWN_COLLISIONS: ReadonlyMap = new Map([ // the general metadata plus five registry-only keys), so the remedy was the // RENAME branch: `Registry.ts` declares `RegistryComponentMeta` and // re-exports `@object-ui/types`' `ComponentMeta` (objectui#6349, batch 4). - // `ConditionalFormattingRule` had a THIRD site, `packages/plugin-kanban/src/KanbanEnhanced.tsx`, - // which left the tree when objectui#8932 deleted that module; the two below still collide. - ['ConditionalFormattingRule', ['packages/plugin-kanban/src/KanbanImpl.tsx', 'packages/types/src/objectql.ts']], + // `ConditionalFormattingRule` sat here. Its THIRD site, + // `packages/plugin-kanban/src/KanbanEnhanced.tsx`, left the tree when + // objectui#8932 deleted that module; the other two were + // `packages/plugin-kanban/src/KanbanImpl.tsx` and `packages/types/src/objectql.ts`. + // The board's was an ALIAS of `@object-ui/types`' `KanbanConditionalFormattingRule`, + // the object-kanban rule, behind the name the grid's and list view's rule carries + // — the two differ by the retired `expression` key. No package entry reached the + // alias, so the remedy was the DELETE branch: the board spells the kanban rule + // by its own name (objectui#6349, batch 7). // `ConfirmDialogState` sat here, colliding between // `packages/app-shell/src/views/ActionConfirmDialog.tsx` and // `packages/plugin-designer/src/hooks/useConfirmDialog.ts`. Two meanings that @@ -551,7 +565,15 @@ const KNOWN_COLLISIONS: ReadonlyMap = new Map([ // (objectui#6349). ['VersionEntry', ['packages/collaboration/src/useConflictResolution.ts', 'packages/plugin-designer/src/components/VersionHistory.tsx']], ['ViewSwitcherProps', ['packages/plugin-list/src/ViewSwitcher.tsx', 'packages/plugin-view/src/ViewSwitcher.tsx']], - ['ViewType', ['packages/plugin-list/src/ViewSwitcher.tsx', 'packages/types/src/views.ts']], + // `ViewType` sat here, colliding between + // `packages/plugin-list/src/ViewSwitcher.tsx` and `packages/types/src/views.ts`. + // Two meanings, not drift: `@object-ui/types`' is the whole view-type + // vocabulary (the spec's list-view types plus the `list` and `detail` + // categories), and plugin-list's was an ALIAS of `@object-ui/core`'s + // `ListViewVisualization` — the drawable subset, without those two arms. + // The narrower meaning already had its own name, so the alias was deleted: + // the switcher spells `ListViewVisualization`, and the plugin-list root no + // longer publishes a `ViewType` (objectui#6349, batch 7). ]); /** @@ -701,7 +723,7 @@ describe('objectui#6273 — the matcher discriminates', () => { { rel: 'packages/types/src/app.ts', source: 'export interface MenuItem { id: string }' }, { rel: 'packages/types/src/index.ts', source: "export type { MenuItem as AppMenuItem } from './app';" }, ], - // Derived names next to the contested one. `KanbanConditionalFormattingRule` + // Derived names next to the bare one. `KanbanConditionalFormattingRule` // is real (packages/types/src/objectql.ts); the `KanbanNative…` spelling // was, until objectui#11522 retired it, and stays as a synthetic one. [