Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/6349-batch6-app-shell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@object-ui/app-shell': patch
---

The flow designer's structural-check finding type is declared as `FlowSimDiagnostic` instead of `Diagnostic` (objectui#6349, batch 6), because `@object-ui/sdui-parser` publishes `Diagnostic` for a parser finding, a different shape. The type is internal: this package's entry exports neither name, so no import changes. The doc comment above the chat page's `ChatbotEnhancedMessage` import now says what `@object-ui/plugin-chatbot` publishes since objectui#6349 batch 5.

No runtime behaviour changes.
7 changes: 7 additions & 0 deletions .changeset/6349-batch6-cli.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@object-ui/cli': patch
---

The `objectui doctor` check-result types are declared as `DoctorDiagnostic` and `DoctorDiagnosticLevel` instead of `Diagnostic` and `DiagnosticLevel` (objectui#6349, batch 6), because `@object-ui/sdui-parser` publishes `Diagnostic` for a parser finding, a different shape. Both types are internal to the command: this package's entry exports `serve` and `init` only, so no import changes.

No runtime behaviour changes; `objectui doctor` prints the same output.
7 changes: 7 additions & 0 deletions .changeset/6349-batch6-plugin-chatbot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@object-ui/plugin-chatbot': patch
---

Doc comments now name the runtime message type `ChatbotEnhancedMessage`, its name since objectui#6349 batch 5, instead of `ChatbotEnhanced.ChatMessage` (objectui#6349, batch 6).

No type or runtime behaviour changes.
9 changes: 9 additions & 0 deletions .changeset/6349-batch6-plugin-designer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@object-ui/plugin-designer': minor
---

The result type of `useConfirmDialog` is declared as `DesignerConfirmDialogState` instead of `ConfirmDialogState` (objectui#6349, batch 6). `@object-ui/app-shell` declares an unrelated `ConfirmDialogState` (the data its action-confirm dialog renders, with the promise's resolver), and one exported name has one declaration.

**Breaking-change note.** Nothing breaks for a consumer of this package: the package entry never exported `ConfirmDialogState`, so no import named it, and the hook's result keeps the same members (`isOpen`, `title`, `message`, `confirm`, `onConfirm`, `onCancel`). Only the type name shown in the published declaration of `useConfirmDialog` changes. `ReturnType<typeof useConfirmDialog>` resolves to the same shape as before.

No runtime behaviour changes.
20 changes: 10 additions & 10 deletions packages/app-shell/src/console/ai/AiChatPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,16 +91,16 @@ import {
// The ENHANCED message shape — the one `<ChatbotEnhanced>` renders and the
// one this file actually produces (`toolInvocations`, `buildProgress`).
//
// `@object-ui/plugin-chatbot` publishes ONE chat-message contract: its
// barrel's `ChatMessage` IS this type, and `ChatbotEnhancedMessage` is a
// deprecated alias of the same declaration, kept so this import (PR #4379)
// keeps compiling. The collision that made the alias necessary is gone —
// the barrel used to DECLARE a second, minimal `ChatMessage` of its own
// (id/role/content/timestamp/avatar only) and the natural name resolved to
// it, which is how this file once could not read `toolInvocations` off its
// own function's return (objectui#4040). Retired in objectui#4383 / PR
// #4400; pinned in the plugin's `chat-message-contract.test.ts`. New code
// here should spell `ChatMessage`.
// `ChatbotEnhancedMessage` is the plugin's one name for its RUNTIME
// chat-message contract, not an alias of anything. The plugin root publishes
// no `ChatMessage` at all, so the bare name means only the `@object-ui/types`
// AUTHORING contract (objectui#6349, batch 5). The `as ChatMessage` below is
// a local rename only; this file never imports the authoring type.
// History: the barrel once DECLARED a second, minimal `ChatMessage` of its
// own (id/role/content/timestamp/avatar only) and the natural name resolved
// to it, which is how this file once could not read `toolInvocations` off
// its own function's return (objectui#4040; retired in objectui#4383 / PR
// #4400). Pinned in the plugin's `chat-message-contract.test.ts`.
type ChatbotEnhancedMessage as ChatMessage,
} from '@object-ui/plugin-chatbot';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import { createContext } from 'react';
import { collectFlowGraphs } from '@objectstack/spec/automation';
import { missingNodeRefDiagnostics, validateFlowDraft } from './simulator/flow-sim-validate.js';
import type { Diagnostic, DiagnosticLevel, SimEdge, SimNode } from './simulator/flow-sim-types.js';
import type { FlowSimDiagnostic, DiagnosticLevel, SimEdge, SimNode } from './simulator/flow-sim-types.js';
import { conditionText, edgeKey, type FlowDesignerEdge, type FlowDesignerNode } from './flow-canvas-layout.js';
import { flowExpressionProblems } from './flow-expr-problems.js';
import { describeExprSite, flowNodeIds, nodeIdPositions, type ExprSite } from './flow-node-refs.js';
Expand Down Expand Up @@ -158,7 +158,7 @@ interface StructuralMapping {
* the author marks as a back-edge to resolve it — but flags EVERY hop (nodes +
* edges) for the red error highlight so the whole loop reads as the problem.
*/
function structuralMapping(diag: Diagnostic, edges: FlowDesignerEdge[]): StructuralMapping {
function structuralMapping(diag: FlowSimDiagnostic, edges: FlowDesignerEdge[]): StructuralMapping {
if (diag.edge) {
const { source, target } = diag.edge;
return { target: { kind: 'edge', source, target, edgeKey: resolveEdgeKey(edges, source, target) } };
Expand Down Expand Up @@ -233,7 +233,7 @@ export function buildFlowProblems({ nodes, edges, serverDiagnostics, variables,
const problems: FlowProblem[] = [];

const v = validateFlowDraft(nodes as unknown as SimNode[], edges as unknown as SimEdge[], locale);
const pushStructural = (level: DiagnosticLevel, list: Diagnostic[], tag = '') => {
const pushStructural = (level: DiagnosticLevel, list: FlowSimDiagnostic[], tag = '') => {
list.forEach((diag, i) => {
const { target, highlight } = structuralMapping(diag, edges);
problems.push({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,16 @@ export interface SimState {

export type DiagnosticLevel = 'error' | 'warning';

export interface Diagnostic {
/**
* One finding of the client-side structural check (`validateFlowDraft`,
* `missingNodeRefDiagnostics`), anchored on a node, an edge or a cycle.
*
* Named `FlowSimDiagnostic`, not `Diagnostic`: `@object-ui/sdui-parser`
* publishes `Diagnostic` for a parser finding (`severity` + `code`), a
* different thing, and one exported name has one authority (objectui#6349,
* batch 6).
*/
export interface FlowSimDiagnostic {
level: DiagnosticLevel;
/** The node this diagnostic points at (for an inline badge + click-to-reveal). */
nodeId?: string;
Expand All @@ -138,8 +147,8 @@ export interface Diagnostic {
}

export interface FlowValidation {
errors: Diagnostic[];
warnings: Diagnostic[];
errors: FlowSimDiagnostic[];
warnings: FlowSimDiagnostic[];
/** Resolved entry node id, when exactly one is determinable. */
startNodeId?: string;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
type AssignmentExpressionValue,
} from '@objectstack/spec/automation';
import { EVALUATED_EXPRESSION_SOURCE_REQUIRED, EvaluatedExpressionSchema } from '@objectstack/spec/shared';
import type { Diagnostic, FlowValidation, SimEdge, SimNode } from './flow-sim-types.js';
import type { FlowSimDiagnostic, FlowValidation, SimEdge, SimNode } from './flow-sim-types.js';
import { conditionText } from '../flow-canvas-layout.js';
import { missingNodePositions } from '../flow-node-refs.js';
import { valueEnvelopeRefusal } from '../../inspectors/flow-value-envelope.js';
Expand Down Expand Up @@ -315,8 +315,8 @@ export function findCycle(nodeIds: string[], edges: SimEdge[]): string[] | null

/** Static structural checks; `errors` block Run, `warnings` are advisory. */
export function validateFlowDraft(nodes: SimNode[], edges: SimEdge[], locale?: string): FlowValidation {
const errors: Diagnostic[] = [];
const warnings: Diagnostic[] = [];
const errors: FlowSimDiagnostic[] = [];
const warnings: FlowSimDiagnostic[] = [];

const ids = nodes.map((n) => n.id);
const idSet = new Set<string>();
Expand Down Expand Up @@ -443,11 +443,11 @@ export function validateFlowDraft(nodes: SimNode[], edges: SimEdge[], locale?: s
export function missingNodeRefDiagnostics(
flow: { nodes?: unknown; edges?: unknown; variables?: unknown },
locale?: string,
): Diagnostic[] {
const out: Diagnostic[] = [];
): FlowSimDiagnostic[] {
const out: FlowSimDiagnostic[] = [];
const seen = new Set<string>();
for (const p of missingNodePositions(flow)) {
let diag: Diagnostic;
let diag: FlowSimDiagnostic;
if (p.kind === 'boundary-host') {
diag = { level: 'error', nodeId: p.nodeId, message: tFormat('engine.flowValidate.boundaryHostMissing', locale, { id: p.nodeId, host: p.id }) };
} else if (p.kind === 'expression-root') {
Expand Down
6 changes: 3 additions & 3 deletions packages/cli/src/__tests__/doctor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { tmpdir } from 'node:os';

import { runDiagnostics, countIssues, type Diagnostic } from '../commands/doctor.js';
import { runDiagnostics, countIssues, type DoctorDiagnostic } from '../commands/doctor.js';

let cwd: string;

Expand All @@ -52,9 +52,9 @@ function writePkg(pkg: Record<string, unknown>): void {
write('package.json', JSON.stringify(pkg, null, 2));
}

const ids = (results: readonly Diagnostic[]): string[] => results.map((r) => r.id);
const ids = (results: readonly DoctorDiagnostic[]): string[] => results.map((r) => r.id);

function find(results: readonly Diagnostic[], id: string): Diagnostic | undefined {
function find(results: readonly DoctorDiagnostic[], id: string): DoctorDiagnostic | undefined {
return results.find((r) => r.id === id);
}

Expand Down
20 changes: 14 additions & 6 deletions packages/cli/src/commands/doctor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,18 @@ import { existsSync, readFileSync } from 'fs';
import { dirname, join, resolve as resolvePath } from 'path';

/** `ok` never counts toward the issue total; `warn` and `error` both do. */
export type DiagnosticLevel = 'ok' | 'warn' | 'error';
export type DoctorDiagnosticLevel = 'ok' | 'warn' | 'error';

export interface Diagnostic {
level: DiagnosticLevel;
/**
* One `objectui doctor` check result.
*
* `Doctor`-prefixed, like its level: `@object-ui/sdui-parser` publishes
* `Diagnostic` for a parser finding, and the flow designer's `DiagnosticLevel`
* is `'error' | 'warning'` with no `ok` verdict — different things, and one
* exported name has one authority (objectui#6349, batch 6).
*/
export interface DoctorDiagnostic {
level: DoctorDiagnosticLevel;
/** The single line rendered for this finding. */
message: string;
/**
Expand Down Expand Up @@ -198,8 +206,8 @@ function isInstalledFrom(cwd: string, specifier: string): boolean {
* Run every diagnostic against `cwd` and return the findings in render order.
* Pure with respect to stdout — see the module docblock.
*/
export function runDiagnostics(cwd: string): Diagnostic[] {
const results: Diagnostic[] = [];
export function runDiagnostics(cwd: string): DoctorDiagnostic[] {
const results: DoctorDiagnostic[] = [];
const ok = (id: string, message: string): void => {
results.push({ id, level: 'ok', message });
};
Expand Down Expand Up @@ -391,7 +399,7 @@ export function runDiagnostics(cwd: string): Diagnostic[] {
}

/** Number of findings that count as problems. `ok` findings never do. */
export function countIssues(results: readonly Diagnostic[]): number {
export function countIssues(results: readonly DoctorDiagnostic[]): number {
return results.filter((result) => result.level !== 'ok').length;
}

Expand Down
13 changes: 7 additions & 6 deletions packages/plugin-chatbot/src/chatMessageAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,17 @@
* The `@object-ui/types` <-> `@object-ui/plugin-chatbot` chat-message seam
* (objectui#4399).
*
* Two `ChatMessage` types meet in `renderer.tsx`, both on purpose:
* Two chat-message types meet in `renderer.tsx`, both on purpose:
*
* - `@object-ui/types`' `ChatMessage` is the **authoring** contract — the
* JSON an SDUI schema declares (`ChatMessageSchema` in
* `packages/types/src/zod/complex.zod.ts`), so it is deliberately wider:
* a `'tool'` role and a `Date` timestamp are authorable.
* - `./ChatbotEnhanced`'s `ChatMessage` is the **runtime** contract — what
* the React components actually render, so it is deliberately narrower
* (three roles, string timestamps) and carries render-only keys the
* authoring surface has no business declaring (`buildProgress`, `charts`).
* - `./ChatbotEnhanced`'s `ChatbotEnhancedMessage` is the **runtime**
* contract — what the React components actually render, so it is
* deliberately narrower (three roles, string timestamps) and carries
* render-only keys the authoring surface has no business declaring
* (`buildProgress`, `charts`).
*
* Until this module they met as three `messages as any` casts, which erased
* ALL of the drift rather than the parts that are intentional: a new authored
Expand Down Expand Up @@ -85,7 +86,7 @@ import type {
/**
* The render-only MESSAGE keys — declared by the runtime contract, never by the
* authoring one. Named here (rather than spelled out at each use) so that
* adding a render-only key to `ChatbotEnhanced.ChatMessage` and forgetting this
* adding a render-only key to `ChatbotEnhancedMessage` and forgetting this
* list is a one-line fix in one place. objectui#4424.
*/
type RuntimeOnlyMessageKeys = Pick<
Expand Down
10 changes: 5 additions & 5 deletions packages/plugin-chatbot/src/useObjectChat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -998,11 +998,11 @@ export function useObjectChat(options: UseObjectChatOptions = {}): UseObjectChat

const isLoading = status === 'submitted' || status === 'streaming';

// Vercel AI SDK v6 UIMessage → the runtime ChatMessage. The shared mapper
// handles parts (text, reasoning, tool-*, source-*), streaming-cursor
// flagging, and legacy `msg.toolInvocations` fallback. We splice `metadata`
// back in because `ChatbotEnhanced.ChatMessage` doesn't carry it but the
// authoring contract does.
// Vercel AI SDK v6 UIMessage → the runtime `ChatbotEnhancedMessage`. The
// shared mapper handles parts (text, reasoning, tool-*, source-*),
// streaming-cursor flagging, and legacy `msg.toolInvocations` fallback. We
// splice `metadata` back in because `ChatbotEnhancedMessage` doesn't carry it
// but the authoring contract does.
//
// objectui#4424: this used to end in `as OuiChatMessage[]`, and that cast was
// the card. It erased `buildProgress`, `blueprintProgress`, `charts` and
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-designer/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export { useDesignerHistory } from './useDesignerHistory';
export type { DesignerHistoryState, DesignerHistoryOptions } from './useDesignerHistory';

export { useConfirmDialog } from './useConfirmDialog';
export type { ConfirmDialogState } from './useConfirmDialog';
export type { DesignerConfirmDialogState } from './useConfirmDialog';

export { useClipboard } from './useClipboard';
export type { ClipboardState } from './useClipboard';
Expand Down
13 changes: 11 additions & 2 deletions packages/plugin-designer/src/hooks/useConfirmDialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,16 @@

import { useState, useCallback, useRef } from 'react';

export interface ConfirmDialogState {
/**
* What {@link useConfirmDialog} returns: the dialog's state plus the
* functions that drive it.
*
* `Designer`-prefixed, as `DesignerHistoryState` is: app-shell's
* `ConfirmDialogState` is a different thing (the data its action-confirm
* dialog renders, with the promise's resolver), and one exported name has one
* authority (objectui#6349, batch 6).
*/
export interface DesignerConfirmDialogState {
/** Whether the dialog is open */
isOpen: boolean;
/** Title for the dialog */
Expand All @@ -26,7 +35,7 @@ export interface ConfirmDialogState {
/**
* Hook for confirmation dialogs before destructive actions.
*/
export function useConfirmDialog(): ConfirmDialogState {
export function useConfirmDialog(): DesignerConfirmDialogState {
const [isOpen, setIsOpen] = useState(false);
const [title, setTitle] = useState('');
const [message, setMessage] = useState('');
Expand Down
43 changes: 32 additions & 11 deletions scripts/__tests__/one-authority-per-exported-name-6273.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -423,20 +423,41 @@ const KNOWN_COLLISIONS: ReadonlyMap<string, readonly string[]> = new Map([
// `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']],
['ConfirmDialogState', ['packages/app-shell/src/views/ActionConfirmDialog.tsx', 'packages/plugin-designer/src/hooks/useConfirmDialog.ts']],
// `ConfirmDialogState` sat here, colliding between
// `packages/app-shell/src/views/ActionConfirmDialog.tsx` and
// `packages/plugin-designer/src/hooks/useConfirmDialog.ts`. Two meanings that
// share only `message`: app-shell's is the DATA its action-confirm dialog
// renders (`open`, `options`, the promise's `resolve`), the designer's is a
// hook RESULT (`isOpen`, `title` and the `confirm` / `onConfirm` / `onCancel`
// functions). Neither package's built entry exports the name. RENAME branch:
// the designer's declaration now spells `DesignerConfirmDialogState`
// (objectui#6349, batch 6).
['ConnectionState', ['packages/collaboration/src/useRealtimeSubscription.ts', 'packages/data-objectstack/src/index.ts']],
['DataSource', ['packages/app-shell/src/types.ts', 'packages/types/src/data.ts']],
['Diagnostic', ['packages/app-shell/src/views/metadata-admin/previews/simulator/flow-sim-types.ts', 'packages/cli/src/commands/doctor.ts', 'packages/sdui-parser/src/types.ts']],
['DiagnosticLevel', ['packages/app-shell/src/views/metadata-admin/previews/simulator/flow-sim-types.ts', 'packages/cli/src/commands/doctor.ts']],
// `Diagnostic` sat here with THREE sites and three meanings:
// `packages/sdui-parser/src/types.ts` (a parser finding: `severity`, `code`,
// `start`, `tag` — published at that package's root and read through it),
// `packages/app-shell/src/views/metadata-admin/previews/simulator/flow-sim-types.ts`
// (a flow structural-check finding anchored on a node, edge or cycle) and
// `packages/cli/src/commands/doctor.ts` (a doctor check result with a stable
// `id`). Only the first was reachable from its package's built entry, so it
// keeps the name; RENAME branch for the other two, which now spell
// `FlowSimDiagnostic` and `DoctorDiagnostic` (objectui#6349, batch 6).
// `DiagnosticLevel` sat here too, colliding between the same flow-sim and
// doctor files: `'error' | 'warning'` there, `'ok' | 'warn' | 'error'` here.
// The flow designer's is the one general level (its structural, server and
// expression problems all carry it), so it keeps the name and the doctor's
// now spells `DoctorDiagnosticLevel` (objectui#6349, batch 6).
['DomProps', ['packages/core/src/utils/dom-props.ts', 'packages/fields/src/widgets/toDomProps.ts']],
// ⚠️ `FilterBuilderCondition` / `FilterGroup` are NOT one shape declared twice:
// the component's condition carries a required `id`, a required narrow `value`
// and `operator: string` (the dropdown's camelCase ids); the types copy has no
// `id`, `value?: any` and `operator: FilterBuilderOperator` (the snake_case
// union). Both headers claim ONE concept, so the remedy is a re-point, and the
// only dependency-legal direction (components -> types) retypes `operator` —
// the vocabulary objectui#7561 is asking a maintainer to rule on. Both rows
// wait for that ruling (objectui#6349, batch 3, stop-and-report).
// ⚠️ `FilterBuilderCondition` / `FilterGroup` still carry two declarations,
// but no longer two unrelated shapes. objectui#7561 and objectui#9306 are
// closed: the maintainer's ruling on objectui#9306 (letter A, comment
// `5857342228`) landed as PR objectui#10823, and `@object-ui/components` now
// DERIVES both from `@object-ui/types`, restating only `operator` and `value`
// as named extensions on the row (and the row-typed `conditions` on the
// group). A derived declaration is still an authority to this gate, so both
// rows stay until a batch measures a rename against that ruling
// (objectui#6349, batch 3 parked them; batch 6 recorded the ruling).
['FilterBuilderCondition', ['packages/components/src/custom/filter-builder.tsx', 'packages/types/src/complex.ts']],
['FilterBuilderOperator', ['packages/components/src/custom/filter-builder.tsx', 'packages/types/src/complex.ts']],
['FilterGroup', ['packages/components/src/custom/filter-builder.tsx', 'packages/types/src/complex.ts']],
Expand Down
Loading