Skip to content
Draft
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
22 changes: 22 additions & 0 deletions .changeset/7122-objectstack-family-17-3-0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
'@object-ui/data-objectstack': patch
---

Reconcile `@object-ui/data-objectstack` with the `@objectstack/*` family at
17.3.0 (objectui#7122).

`@objectstack/client`, `core`, `formula` and `lint` each pin `@objectstack/spec`
EXACTLY, so resolving the spec alone to 17.3.0 left the console bundling TWO
copies of it. Moving the family with it in `pnpm-lock.yaml` collapses the
duplicate; every declared range already admitted 17.3.0, so no manifest moved.

The one source change the family bump forces is a type reconciliation, not a
behaviour change. `client.analytics.query` resolved to `Promise<any>` at 17.2.0
and resolves to `Promise<AnalyticsResult>` at 17.3.0, so the pre-envelope
branches of `aggregate`'s row-shape fallback stopped type-checking. Those
branches are read through a widened alias rather than deleted: the client's own
docblock records the runtime change behind the narrower type ("BREAKING since
objectstack#13079 — read `result.rows`, not `result.data.rows`"), and deleting
them is a runtime compatibility decision about servers older than that, not a
type repair. The alias restores exactly the compile-time latitude 17.2.0 gave
the same expression and changes no runtime byte of it.
19 changes: 19 additions & 0 deletions .changeset/7122-postcss-dedupe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
---

Dev-time only: collapse the `postcss` duplicate this branch's first lockfile
bump introduced (objectui#7122).

`aabc527cb` resolved `@objectstack/spec` to 17.3.0 and, as a side effect of that
install, re-resolved `autoprefixer`'s auto-installed `postcss` peer to 8.5.28
while `@tailwindcss/postcss` kept 8.5.26. `@object-ui/cli` depends on both and
declares `postcss` itself, so its type-check program held TWO postcss copies
with two structurally distinct `Plugin` types — and comparing
`Plugin & ExportedAPI` against `Plugin` across them exceeded the compiler's
instantiation budget: `TS2321: Excessive stack depth`, in a file this branch
never touched. `origin/main` carries one copy for that pair and is green.

The split is resolved in the lockfile only. All eight manifests that declare
`postcss` already declare `^8.5.26`, which admits 8.5.28, so no declared range
moved and no published dependency declaration differs by a byte. Empty
frontmatter is the deliberate "no release" declaration.
25 changes: 25 additions & 0 deletions .changeset/7122-spec-17-3-0-reconciliation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
'@object-ui/app-shell': patch
'@object-ui/auth': patch
---

Reconcile `@object-ui/app-shell` with `@objectstack/spec` 17.3.0 (objectui#7122).

`SchemaDiffEntryKind` gained an `unreachable` member in 17.3.0, and the external
datasource validation panel labels those kinds through a map that is TOTAL over
the union on purpose — so an upstream addition fails the build rather than
rendering a blank cell. That mechanism fired: the package did not compile against
17.3.0 until the kind was labelled. It now reads "Not checked — remote
unreachable", following the spec's own ruling that this kind asserts nothing
about the remote schema and must never be surfaced as "schema changed": it means
introspection could not complete, which is often transient, and labelling it like
a mismatch would tell an operator to repair a schema nobody has read.

Two internal flow-inspector types were renamed (`FlowNodeLike` →
`InspectorFlowNode` / `ScopeFlowNode`) because 17.3.0 began exporting its own
`FlowNodeLike`; neither name is in this package's published entry, so no consumer
import changes.

`@object-ui/auth`: a README sentence claiming the preview-mode prop aligns with
the spec's `PreviewModeConfig` is corrected — 17.3.0 removed that symbol. The
`previewMode` prop itself is host-supplied, unchanged, and unaffected.
8 changes: 8 additions & 0 deletions .changeset/spec-17-3-0-lockfile-bump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
---

Dev-time only: move the `@objectstack/spec` resolution in `pnpm-lock.yaml` from
17.2.0 to 17.3.0. No published package changes — every manifest's declared range
(`^17.0.0` / `^17.1.0` / `^17.2.0`) already admitted 17.3.0, so no floor moved and
no package's dependency declaration differs by a byte. Empty frontmatter is the
deliberate "no release" declaration for a change that publishes nothing.
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
* INSTRUMENT against declaration 2. `[k: string]: unknown` makes every string
* a member, so `keyof` on that type is `string` and a key-set comparison
* cannot fail whatever the file does. That is the same trap
* `FlowNodeInspector.specKeys.test.tsx` records for `FlowNodeLike`.
* `FlowNodeInspector.specKeys.test.tsx` records for `InspectorFlowNode`.
*
* Here the index signature GOES AWAY in the convergence rather than being
* worked around, so the key-set half becomes live — but only against the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ type RunState = 'idle' | 'running' | 'done' | 'error' | 'unavailable';
* the union, and therefore silently unlabelled here (objectstack#4115).
* `default_mismatch` (spec 17.0.0-rc.2) is the mechanism working as intended —
* it arrived with the pin bump and failed this build until labelled.
* `unreachable` (spec 17.3.0) arrived the same way. It is the one kind that
* asserts NOTHING about the remote schema — introspection never completed, so
* the comparison never ran — and the spec's own ruling is that consumers must
* surface it as "cannot check", never as "schema changed": labelling it like a
* mismatch tells an operator to repair a schema nobody has read.
*/
const DIFF_LABEL: Record<SchemaDiffEntry['kind'], string> = {
missing_table: 'Missing table',
Expand All @@ -50,6 +55,7 @@ const DIFF_LABEL: Record<SchemaDiffEntry['kind'], string> = {
index_mismatch: 'Index mismatch',
unmapped_index: 'Unmapped index',
default_mismatch: 'Column default mismatch',
unreachable: 'Not checked — remote unreachable',
};

export function ValidationPanel({ datasource }: ValidationPanelProps) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
* Dropping `description?` from a local `interface` proves nothing on its own,
* for two independent reasons measured on this card:
*
* 1. The node the inspector edits is typed `FlowNodeLike` (the exported shape
* 1. The node the inspector edits is typed `InspectorFlowNode` (the exported shape
* `locateFlowNode` returns), NOT the inspector's own module-local
* declaration — so narrowing only the local copy changes no read.
* 2. Both shapes carry a deliberately load-bearing `[k: string]: unknown`
Expand All @@ -35,7 +35,7 @@
*
* The two assertions below survive both traps:
*
* - **Compile time**: the DECLARED members of `FlowNodeLike` — index signature
* - **Compile time**: the DECLARED members of `InspectorFlowNode` — index signature
* stripped — must be a subset of the spec's own `FlowNode` keys. That closes
* the whole class rather than the one key: any future member added to the
* read type that the contract refuses turns this red.
Expand All @@ -55,7 +55,7 @@ import { describe, it, expect, vi, afterEach } from 'vitest';
import { render, screen, cleanup, fireEvent } from '@testing-library/react';
import * as Automation from '@objectstack/spec/automation';
import type { FlowNode as SpecFlowNode } from '@objectstack/spec/automation';
import type { FlowNodeLike } from './flow-nested-selection';
import type { InspectorFlowNode } from './flow-nested-selection';

// Same stubs the sibling suite uses: the engine config-schema hook is empty so
// the hardcoded field groups render, and the field catalog resolves without a
Expand Down Expand Up @@ -129,7 +129,7 @@ type Equal<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ?
/**
* The DECLARED members of a type — its index signature removed.
*
* `keyof FlowNodeLike` is `string | number` while the index signature is there,
* `keyof InspectorFlowNode` is `string | number` while the index signature is there,
* which is why the naive key comparison cannot see this defect at all.
*/
type Declared<T> = {
Expand All @@ -138,14 +138,14 @@ type Declared<T> = {

describe('the node read type declares no key FlowNodeSchema refuses (#6287)', () => {
it('is pinned at compile time', () => {
type DeclaredNodeKeys = keyof Declared<FlowNodeLike>;
type DeclaredNodeKeys = keyof Declared<InspectorFlowNode>;
type SpecNodeKeys = keyof SpecFlowNode;

// Guard against a degenerate probe: were either side `any`, or the
// index-signature strip to leave nothing behind, every assertion below
// would pass while measuring nothing.
type _SpecNotAny = Assert<Equal<IsAny<SpecFlowNode>, false>>;
type _LocalNotAny = Assert<Equal<IsAny<FlowNodeLike>, false>>;
type _LocalNotAny = Assert<Equal<IsAny<InspectorFlowNode>, false>>;
type _StripLeftKeys = Assert<Equal<Equal<DeclaredNodeKeys, never>, false>>;
type _SpecHasKeys = Assert<Extends<'label', SpecNodeKeys>>;
// …and that the strip really removed the index signature: `description`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ import { useActionConfigSchemas } from '../previews/useFlowNodePalette.js';
import { FlowNodeConfigField } from './FlowNodeConfigField.js';
import { useFlowScope } from './useFlowScope.js';
import { nodeOutputRefs, type ScopeRef } from './flow-scope.js';
import { NESTED_NODE_KIND, parseNestedNodeId, locateFlowNode, type FlowNodeLike } from './flow-nested-selection.js';
import { NESTED_NODE_KIND, parseNestedNodeId, locateFlowNode, type InspectorFlowNode } from './flow-nested-selection.js';
import type { FlowDesignerEdge } from '../previews/flow-canvas-layout.js';
import { ScreenPreview } from '../previews/ScreenPreview.js';

Expand All @@ -69,7 +69,7 @@ import { ScreenPreview } from '../previews/ScreenPreview.js';
* - the node copy declared `description?: string`, a key `FlowNodeSchema`
* refuses by name (`.strict()`, objectstack#4001) — and the copy was not even
* the type the panel reads through, since `locateFlowNode` returns
* `FlowNodeLike`. Narrowing the copy alone would have changed nothing.
* `InspectorFlowNode`. Narrowing the copy alone would have changed nothing.
* - the edge copy still spelled `condition?: unknown`, months after
* `FlowEdgeInspector`'s twin was narrowed to the spec's `ExpressionInput`
* because the loose spelling described an envelope the server rejects — the
Expand All @@ -84,7 +84,7 @@ import { ScreenPreview } from '../previews/ScreenPreview.js';
* spec, so this panel now inherits that pin instead of needing its own copy of
* it.
*/
type FlowNode = FlowNodeLike;
type FlowNode = InspectorFlowNode;
type FlowEdge = FlowDesignerEdge;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,27 @@ export function regionLabelOf(regionKey: string, container?: { config?: unknown
* field for it (objectui#6287). `FlowNodeInspector.specKeys.test.tsx` pins the
* declared members (index signature stripped) as a subset of the spec's own
* node keys, so the next addition of that kind fails to compile.
*
* ## Why the name is `InspectorFlowNode` and not `FlowNodeLike`
*
* `@objectstack/spec/system` began exporting its OWN `FlowNodeLike` in spec
* 17.3.0 (the minimal node shape `translateFlow` consumes), so the local name
* became a shadow of a live spec export — read by the next agent as the spec's
* own definition, which is how objectstack#2901 was filed with a backwards
* premise. `check-spec-symbol-derivation.mjs` caught it at the pin bump
* (objectui#7122) and offers four remedies; the difference here is REAL, so
* this is remedy 3, a declared local dialect.
*
* The difference, measured rather than asserted: the spec's `FlowNodeLike`
* declares `id?: string` OPTIONAL, this one declares `id: string` REQUIRED.
* Assignability runs one way only — this type is usable where the spec's is
* expected, the spec's is NOT usable where this one is. Importing the spec
* export in place of this declaration would therefore be a silent WIDENING
* that drops the `id` guarantee `NodeLocation` and every `locateFlowNode`
* caller are built on. The tripwire lives in `@object-ui/types`'
* `page-nav-misc-spec-parity.test.ts`, with the other renamed dialects.
*/
export interface FlowNodeLike {
export interface InspectorFlowNode {
id: string;
type?: string;
label?: string;
Expand All @@ -136,7 +155,7 @@ export interface FlowNodeLike {
*/
export interface NodeLocation {
/** The resolved node (a member of draft.nodes, or of a region sub-graph). */
node: FlowNodeLike;
node: InspectorFlowNode;
/** True when the node lives inside a container region (not draft.nodes). */
nested: boolean;
/**
Expand All @@ -145,7 +164,7 @@ export interface NodeLocation {
*/
scopeAnchorId: string;
/** The enclosing container node — only when nested. */
container?: FlowNodeLike;
container?: InspectorFlowNode;
/** Human region label for the inspector breadcrumb — only when nested. */
regionLabel?: string;
/**
Expand All @@ -157,21 +176,21 @@ export interface NodeLocation {
write: (next: Record<string, unknown> | null) => Record<string, unknown> | null;
}

function asNodeArray(v: unknown): FlowNodeLike[] {
return Array.isArray(v) ? (v as FlowNodeLike[]) : [];
function asNodeArray(v: unknown): InspectorFlowNode[] {
return Array.isArray(v) ? (v as InspectorFlowNode[]) : [];
}

function configOf(node: FlowNodeLike): Record<string, unknown> {
function configOf(node: InspectorFlowNode): Record<string, unknown> {
const c = node.config;
return c && typeof c === 'object' && !Array.isArray(c) ? (c as Record<string, unknown>) : {};
}

/** A region object (`{ nodes, edges, name? }`) with a usable `nodes` array, or null. */
function asRegion(v: unknown): (Record<string, unknown> & { nodes: FlowNodeLike[] }) | null {
function asRegion(v: unknown): (Record<string, unknown> & { nodes: InspectorFlowNode[] }) | null {
if (!v || typeof v !== 'object' || Array.isArray(v)) return null;
const r = v as Record<string, unknown>;
if (!Array.isArray(r.nodes)) return null;
return r as Record<string, unknown> & { nodes: FlowNodeLike[] };
return r as Record<string, unknown> & { nodes: InspectorFlowNode[] };
}

/** Resolve a region object out of a container's config by its structured path. */
Expand All @@ -189,9 +208,9 @@ function regionFromConfig(cfg: Record<string, unknown>, rp: RegionConfigPath) {
* (`region.edges`, `branch.name`).
*/
function writeNestedNode(
nodes: FlowNodeLike[],
nodes: InspectorFlowNode[],
containerIdx: number,
container: FlowNodeLike,
container: InspectorFlowNode,
rp: RegionConfigPath,
nodeIdx: number,
next: Record<string, unknown> | null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,19 @@ export interface FlowScope {
trigger?: TriggerScope;
}

interface FlowNodeLike {
/**
* The scope walker's read shape for a node it has NOT yet validated — every
* member `unknown` on purpose, because this runs over raw stored metadata and
* narrows each value at its use site.
*
* Named `ScopeFlowNode` rather than `FlowNodeLike` because spec 17.3.0 began
* exporting a `FlowNodeLike` of its own (objectui#7122): a local declaration
* under a live spec export's name reads as the spec's definition to the next
* agent. This one is a genuine dialect, not a copy — the spec's members are
* typed (`id?: string`), these are deliberately untyped, which is the whole
* point of a pre-validation probe. Tripwire: `page-nav-misc-spec-parity.test.ts`.
*/
interface ScopeFlowNode {
id?: unknown;
type?: unknown;
label?: unknown;
Expand Down Expand Up @@ -154,7 +166,7 @@ export function flowAncestors(nodeId: string, edges: FlowEdgeLike[]): Set<string
* function path — framework#4278), so suggesting them in the data picker
* offered successors variables that never exist at run time.
*/
export function nodeOutputRefs(node: FlowNodeLike): ScopeRef[] {
export function nodeOutputRefs(node: ScopeFlowNode): ScopeRef[] {
const type = str(node.type);
const cfg = asRecord(node.config);
const nodeId = str(node.id) ?? '';
Expand Down Expand Up @@ -225,7 +237,7 @@ function dedupeByToken(refs: ScopeRef[]): ScopeRef[] {
* iterators, then trigger refs, de-duplicated by token.
*/
export function resolveFlowScope(draft: Record<string, unknown>, nodeId: string | undefined): FlowScope {
const nodes = asArray(draft.nodes).map(asRecord) as FlowNodeLike[];
const nodes = asArray(draft.nodes).map(asRecord) as ScopeFlowNode[];
const edges = asArray(draft.edges) as FlowEdgeLike[];
const refs: ScopeRef[] = [];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export interface FlowDesignerNode {
*
* ⛔ Making it required here catches nothing. Measured on `origin/main` in
* #6287: `tsc` exit 0, ZERO errors, because every node reaches the reader
* types through `as FlowNodeLike[]` / `as FlowDesignerNode[]` casts out of
* types through `as InspectorFlowNode[]` / `as FlowDesignerNode[]` casts out of
* `Record<string, unknown>`, and **a cast bypasses a required member**. It is
* also mildly harmful — `node.label ?? ''` and `node.label || node.id` are
* guards the OPTIONAL type forces, while a required `label` would let
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* ⛔ The reader-side type is NOT the enforcement point, and this is measured,
* not asserted. objectui#6287 made `label` required on the reader type and got
* `tsc` exit 0 with **zero errors**: every node reaches that type through
* `as FlowNodeLike[]` casts out of `Record<string, unknown>`, and **a cast
* `as InspectorFlowNode[]` casts out of `Record<string, unknown>`, and **a cast
* bypasses a required member**. It is also mildly harmful — `node.label ?? ''`
* and `node.label || node.id` are guards the OPTIONAL type currently forces,
* while a required `label` would let `node.label.trim()` compile against a
Expand Down Expand Up @@ -279,7 +279,7 @@ function collectSites(file: string, src: string): Site[] {
const sites: Site[] = [];

// (a) a literal declared AS a flow node
for (const m of src.matchAll(/:\s*(?:FlowDesignerNode|FlowNodeLike|FlowNode)\s*=\s*\{/g)) {
for (const m of src.matchAll(/:\s*(?:FlowDesignerNode|InspectorFlowNode|FlowNodeLike|FlowNode)\s*=\s*\{/g)) {
const open = src.indexOf('{', m.index!);
const close = matchBracket(src, open, '{', '}');
if (close < 0) continue;
Expand Down
2 changes: 1 addition & 1 deletion packages/auth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ not a gap.

Preview mode allows visitors (e.g. marketplace customers) to explore the platform without registering or logging in. The `AuthProvider` auto-authenticates with a simulated user identity and bypasses login/registration screens.

This feature aligns with the `PreviewModeConfig` from `@objectstack/spec/kernel` ([spec PR #676](https://github.com/objectstack-ai/spec/pull/676)).
This capability is host-supplied and has no `@objectstack/spec` anchor. It aligned with `PreviewModeConfig` from `@objectstack/spec/kernel` until that symbol was retired upstream (objectstack#11846), which removed it together with the `RuntimeMode` value `'preview'`; the spec this package resolves no longer exports it. The `previewMode` prop below is unaffected — it is the host's to supply, and always was.

### Usage

Expand Down
Loading
Loading