diff --git a/.changeset/12829-flow-runs-flat-items.md b/.changeset/12829-flow-runs-flat-items.md
new file mode 100644
index 0000000000..061486a385
--- /dev/null
+++ b/.changeset/12829-flow-runs-flat-items.md
@@ -0,0 +1,4 @@
+---
+---
+
+Console Flow Runs page: the flow list reads the served flat `meta.getItems('flow')` item only (`name` and `label` on the item itself), and the page's test fixtures feed that shape. The removed `spec`-wrapped arm had no producer: no server serves that shape, so no behaviour a user can reach changes and nothing is released (objectui#12829).
diff --git a/apps/console/src/pages/consoleFaces.displayLocale-9909.test.tsx b/apps/console/src/pages/consoleFaces.displayLocale-9909.test.tsx
index faf8f3959e..0b2a183969 100644
--- a/apps/console/src/pages/consoleFaces.displayLocale-9909.test.tsx
+++ b/apps/console/src/pages/consoleFaces.displayLocale-9909.test.tsx
@@ -70,7 +70,7 @@ const { STORED, approvalsApiStub, ADAPTER, AUTH, I18N } = vi.hoisted(() => {
// its flow and run loads on it, so a fresh one per call re-ran them on every
// render and its runs table never settled (objectui#11865 surfaced it).
const client = {
- meta: { getItems: async (type: string) => (type === 'flow' ? [{ spec: { name: 'sync_flow', label: 'Sync' } }] : []) },
+ meta: { getItems: async (type: string) => (type === 'flow' ? [{ name: 'sync_flow', label: 'Sync' }] : []) },
automation: { execute: async () => ({ success: true }), listRuns, getRun: async () => ({ run: null }) },
};
const ADAPTER = {
diff --git a/apps/console/src/pages/developer/FlowRunsPage.flattenedLabel-12783.test.tsx b/apps/console/src/pages/developer/FlowRunsPage.flattenedLabel-12783.test.tsx
index 9485bd23f9..a913416ad9 100644
--- a/apps/console/src/pages/developer/FlowRunsPage.flattenedLabel-12783.test.tsx
+++ b/apps/console/src/pages/developer/FlowRunsPage.flattenedLabel-12783.test.tsx
@@ -10,18 +10,19 @@
* the flat item but the label from `it.spec.label` alone, so on a real server
* every flow was listed by its API name only, `showcase_batch_reminders` where
* "Batch Task Reminders (Loop) (showcase_batch_reminders)" belongs. The page's
- * other suites feed `{ spec: {...} }` items, which is why none of them saw it.
+ * other suites fed `{ spec: {...} }` items, which is why none of them saw it.
*
* What is pinned:
* - a flattened answer, shaped as the served list is: the option text is
* `label (name)`;
- * - a `spec`-wrapped answer: the same text (the page still reads that form,
- * `spec` first, as it reads the name);
+ * - a `spec`-wrapped item is NOT read (objectui#12829): no server serves that
+ * shape, so the page keeps no arm for it and such an item lists no flow;
* - an unlabelled flow: its bare name, not the name standing in for a label.
*
- * DIRECTION, observed on unchanged `main`: the flattened case is red there (the
- * option reads the bare name); the other two are green there by design, as
- * no-regression guards for the forms the fix must not disturb.
+ * DIRECTION, observed on unchanged `main` at objectui#12783: the flattened case
+ * was red there (the option read the bare name); the unlabelled case is green
+ * by design, as a no-regression guard. The `spec`-wrapped case is red on any
+ * page that still reads a `spec` arm (objectui#12829).
*/
import '@testing-library/jest-dom/vitest';
@@ -100,19 +101,16 @@ describe('the flow picker names a labelled flow by its label (objectui#12783)',
]);
});
- it('a spec-wrapped answer: the same text', async () => {
+ it('a spec-wrapped item is not read: it lists no flow (objectui#12829)', async () => {
catalog.answer = {
type: 'flow',
items: [
{ spec: { name: 'showcase_batch_reminders', label: 'Batch Task Reminders (Loop)', variables: [] } },
- { spec: { name: 'nightly_sync', variables: [] } },
],
};
render();
- expect(await optionTexts()).toEqual([
- 'Batch Task Reminders (Loop) (showcase_batch_reminders)',
- 'nightly_sync',
- ]);
+ expect(await screen.findByText('No flow definitions found.')).toBeInTheDocument();
+ expect(screen.queryByRole('combobox')).not.toBeInTheDocument();
});
it('an unlabelled flow: its bare name, never the name doubled as a label', async () => {
diff --git a/apps/console/src/pages/developer/FlowRunsPage.sharedSelect-11865.test.tsx b/apps/console/src/pages/developer/FlowRunsPage.sharedSelect-11865.test.tsx
index 8104e7a02c..dcae3bd013 100644
--- a/apps/console/src/pages/developer/FlowRunsPage.sharedSelect-11865.test.tsx
+++ b/apps/console/src/pages/developer/FlowRunsPage.sharedSelect-11865.test.tsx
@@ -36,7 +36,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { cleanup, fireEvent, render, screen, waitFor, within } from '@testing-library/react';
const { execute, listRuns, catalog, ADAPTER } = vi.hoisted(() => {
- const catalog = { flows: [] as Array<{ spec: Record }> };
+ const catalog = { flows: [] as Array> };
const execute = vi.fn(async () => ({ success: true, status: 'completed', durationMs: 1 }));
const listRuns = vi.fn(async () => ({ runs: [] }));
// A STABLE singleton: a fresh object per render loops the page's fetch effects.
@@ -56,8 +56,9 @@ vi.mock('@object-ui/app-shell', async (importOriginal) => ({
// Imported AFTER the mocks so the page picks them up.
import { FlowRunsPage } from './FlowRunsPage';
-const REASSIGN = { spec: { name: 'reassign_wizard', label: 'Reassign', variables: [] } };
-const NIGHTLY = { spec: { name: 'nightly_sync', variables: [] } };
+// Flows as the server lists them: flattened, `name` and `label` on the item (objectui#12829).
+const REASSIGN = { name: 'reassign_wizard', label: 'Reassign', variables: [] };
+const NIGHTLY = { name: 'nightly_sync', variables: [] };
beforeEach(() => {
catalog.flows = [REASSIGN, NIGHTLY];
diff --git a/apps/console/src/pages/developer/FlowRunsPage.test.tsx b/apps/console/src/pages/developer/FlowRunsPage.test.tsx
index 9295d58460..89a6355db8 100644
--- a/apps/console/src/pages/developer/FlowRunsPage.test.tsx
+++ b/apps/console/src/pages/developer/FlowRunsPage.test.tsx
@@ -40,7 +40,8 @@ const { execute, ADAPTER, authFetch } = vi.hoisted(() => {
}));
const ADAPTER = {
getClient: () => ({
- meta: { getItems: async (type: string) => (type === 'flow' ? [{ spec: flow }] : []) },
+ // The served list item IS the flow definition (objectui#12829).
+ meta: { getItems: async (type: string) => (type === 'flow' ? [flow] : []) },
automation: { execute, listRuns: async () => ({ runs: [] }) },
}),
};
diff --git a/apps/console/src/pages/developer/FlowRunsPage.tsx b/apps/console/src/pages/developer/FlowRunsPage.tsx
index 5147dc4b43..0b2baeeb1a 100644
--- a/apps/console/src/pages/developer/FlowRunsPage.tsx
+++ b/apps/console/src/pages/developer/FlowRunsPage.tsx
@@ -66,7 +66,6 @@ interface FlowItem {
name: string;
label?: string;
spec?: any;
- updatedAt?: string;
}
interface RunSummary {
@@ -218,15 +217,14 @@ export function FlowRunsPage() {
: Array.isArray(result?.items)
? result.items
: [];
- // The served list is flattened: `name` and `label` sit on the item
- // itself, with no `spec` member. Both are read the same way, `spec`
- // first, so the picker names a labelled flow `label (name)` on the list
- // the server answers (objectui#12783).
+ // The served list is flattened: each item IS the flow definition, its
+ // `name` and `label` on the item itself, so the picker names a labelled
+ // flow `label (name)` (objectui#12783). No server wraps an item in a
+ // `spec` member, so none is read (objectui#12829).
const normalized: FlowItem[] = items.map(it => ({
- name: it?.spec?.name ?? it?.name,
- label: it?.spec?.label ?? it?.label,
- spec: it?.spec ?? it,
- updatedAt: it?.updatedAt ?? it?.updated_at,
+ name: it?.name,
+ label: it?.label,
+ spec: it,
})).filter(f => f.name);
setFlows(normalized);
if (!selectedFlowName && normalized.length > 0) {
diff --git a/apps/console/src/pages/developer/developerMetadataEnvelope.contractEnvelope-6917.test.tsx b/apps/console/src/pages/developer/developerMetadataEnvelope.contractEnvelope-6917.test.tsx
index 2658f4cdee..4c2c077804 100644
--- a/apps/console/src/pages/developer/developerMetadataEnvelope.contractEnvelope-6917.test.tsx
+++ b/apps/console/src/pages/developer/developerMetadataEnvelope.contractEnvelope-6917.test.tsx
@@ -77,13 +77,12 @@ vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn() } }));
import { FlowRunsPage } from './FlowRunsPage';
import { PublicFormsPage } from './PublicFormsPage';
-/** A flow definition, as `meta.getItems('flow')` rows carry it. */
+/** A flow definition, as `meta.getItems('flow')` rows carry it: the item IS
+ * the definition, flattened, with no `spec` member (objectui#12829). */
const FLOW = {
- spec: {
- name: 'reassign_wizard',
- label: 'Reassign',
- variables: [{ name: 'recordId', type: 'text', isInput: true }],
- },
+ name: 'reassign_wizard',
+ label: 'Reassign',
+ variables: [{ name: 'recordId', type: 'text', isInput: true }],
};
/** A published public form, on the `ViewItem` shape `GET /meta/view` serves: