From d30f8380517ac5da45eea5ce84322ec714376ea7 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 04:39:31 +0000 Subject: [PATCH 1/2] fix(studio): a flow node's key / value map stacks each field over its value Claude-Session: https://claude.ai/code/session_01JQwxbWuU6sVfBYiy4dY4PA Co-authored-by: Claude --- .../27356-studio-flow-map-rows-stack.md | 12 +++ ...owKeyValueField.stackedRows-27356.test.tsx | 82 +++++++++++++++++++ .../inspectors/FlowKeyValueField.tsx | 38 +++++---- 3 files changed, 117 insertions(+), 15 deletions(-) create mode 100644 .changeset/27356-studio-flow-map-rows-stack.md create mode 100644 ui/packages/app-shell/src/views/metadata-admin/inspectors/FlowKeyValueField.stackedRows-27356.test.tsx diff --git a/.changeset/27356-studio-flow-map-rows-stack.md b/.changeset/27356-studio-flow-map-rows-stack.md new file mode 100644 index 00000000000..f536c97e38e --- /dev/null +++ b/.changeset/27356-studio-flow-map-rows-stack.md @@ -0,0 +1,12 @@ +--- +'@object-ui/app-shell': patch +'@object-ui/console': patch +--- + +fix(studio): a flow node's key / value map puts each field name on its own line, so the name is readable + +Clause-②: no +Metadata-protocol: none + +- **Before.** In the flow inspector, each row of a key / value map (a Create Record or Update Record node's field values, an HTTP node's headers, an assignment's variables) put the key box, the value box and up to two buttons on one line. In the 300 px inspector the key box showed about nine characters, so a field name read "subj" or "dueD". +- **Now.** A row stacks: the key on its own line beside the remove button, the value on the line under it beside the text / expression toggle where the map has one. The same field name shows about 24 characters. Nothing about what the map stores changes. diff --git a/ui/packages/app-shell/src/views/metadata-admin/inspectors/FlowKeyValueField.stackedRows-27356.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/inspectors/FlowKeyValueField.stackedRows-27356.test.tsx new file mode 100644 index 00000000000..63979621063 --- /dev/null +++ b/ui/packages/app-shell/src/views/metadata-admin/inspectors/FlowKeyValueField.stackedRows-27356.test.tsx @@ -0,0 +1,82 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * objectstack#27356 (hotcrm#2320 Studio S5) — a Create Record node's field + * names were cut to four or five characters ("subj", "dueD"): each map row put + * the field box, the value box and two buttons on one line, so the narrow + * inspector split its width between them. A row now stacks: the field on its + * own line beside the remove button, the value on the line under it beside the + * text / expression toggle. + * + * jsdom draws no layout, so the pin is the row's structure, read through + * `FlowNodeConfigField` (the editor's only caller) on a `create_record` node: + * the field box never shares a line with the value box. The same editor draws + * every key / value map in the flow inspector; the HTTP `headers` map is the + * second case, a map with no value toggle. + */ + +import { describe, it, expect, afterEach, vi } from 'vitest'; +import { render, screen, cleanup } from '@testing-library/react'; + +vi.mock('../previews/useObjectFields', () => ({ + useObjectFields: () => ({ fields: [], loading: false, error: null }), +})); + +import { FlowNodeConfigField } from './FlowNodeConfigField'; +import type { FlowConfigField } from './flow-node-config'; + +afterEach(cleanup); + +const TOGGLE = 'Write as a CEL expression'; +const REMOVE = /remove/i; +const LONG_FIELD = 'customer_satisfaction_score'; + +function renderMap(field: FlowConfigField, nodeType: string, value: Record) { + render( + , + ); +} + +/** The line a box sits on: the row's key line or value line. */ +const lineOf = (el: HTMLElement) => el.closest('[data-testid^="flow-kv-"]') as HTMLElement; + +describe('a key / value map row stacks the field over its value (objectstack#27356)', () => { + it('Create Record: the long field name has its own line, beside the remove button only', () => { + renderMap( + { id: 'fields', path: ['config', 'fields'], label: 'Field values', kind: 'keyValue' }, + 'create_record', + { [LONG_FIELD]: '5' }, + ); + const key = screen.getByDisplayValue(LONG_FIELD); + const value = screen.getByDisplayValue('5'); + const keyLine = lineOf(key); + + expect(keyLine.contains(value)).toBe(false); + expect(keyLine.querySelectorAll('input')).toHaveLength(1); + const buttons = [...keyLine.querySelectorAll('button')]; + expect(buttons).toHaveLength(1); + expect(buttons[0]!.getAttribute('aria-label')).toMatch(REMOVE); + + // The value's line carries the text / expression toggle, under the field. + const valueLine = lineOf(value); + expect(valueLine).not.toBe(keyLine); + expect(valueLine.contains(screen.getByRole('button', { name: TOGGLE }))).toBe(true); + expect(keyLine.compareDocumentPosition(valueLine) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy(); + }); + + it('a map with no value toggle (HTTP headers) stacks the same way', () => { + renderMap( + { id: 'headers', path: ['config', 'headers'], label: 'Headers', kind: 'keyValue' }, + 'http', + { 'X-Request-Correlation-Id': 'abc' }, + ); + const key = screen.getByDisplayValue('X-Request-Correlation-Id'); + expect(lineOf(key).contains(screen.getByDisplayValue('abc'))).toBe(false); + expect(screen.queryByRole('button', { name: TOGGLE })).toBeNull(); + }); +}); diff --git a/ui/packages/app-shell/src/views/metadata-admin/inspectors/FlowKeyValueField.tsx b/ui/packages/app-shell/src/views/metadata-admin/inspectors/FlowKeyValueField.tsx index ca2e8438286..9f71951b764 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/inspectors/FlowKeyValueField.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/inspectors/FlowKeyValueField.tsx @@ -359,7 +359,7 @@ export function FlowKeyValueField({ {ASSIGNMENT_ARRAY_FORM_PRESCRIPTION}

)} -
+
{rows.length === 0 && (

{emptyLabel}

)} @@ -373,7 +373,13 @@ export function FlowKeyValueField({ const refusal = envelopeSlot && writesMap ? valueRefusal(rowValue(row)) : null; return (
-
+ {/* + * objectstack#27356 — the key sits on its own line, over its + * value. Side by side, the inspector's width split three ways + * (key, value, buttons) and a field name read "subj" or "dueD"; + * on its own line it keeps the full width beside one button. + */} +
+ +
+
)} -
{refusal ? (
From 5ebc5576c99b8e46ea8fcf708b6a814d7bb41581 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 05:16:23 +0000 Subject: [PATCH 2/2] docs(changeset): name the Assignment node's map by its key Claude-Session: https://claude.ai/code/session_01JQwxbWuU6sVfBYiy4dY4PA Co-authored-by: Claude --- .changeset/27356-studio-flow-map-rows-stack.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/27356-studio-flow-map-rows-stack.md b/.changeset/27356-studio-flow-map-rows-stack.md index f536c97e38e..a7eb2808c43 100644 --- a/.changeset/27356-studio-flow-map-rows-stack.md +++ b/.changeset/27356-studio-flow-map-rows-stack.md @@ -8,5 +8,5 @@ fix(studio): a flow node's key / value map puts each field name on its own line, Clause-②: no Metadata-protocol: none -- **Before.** In the flow inspector, each row of a key / value map (a Create Record or Update Record node's field values, an HTTP node's headers, an assignment's variables) put the key box, the value box and up to two buttons on one line. In the 300 px inspector the key box showed about nine characters, so a field name read "subj" or "dueD". +- **Before.** In the flow inspector, each row of a key / value map (a Create Record or Update Record node's field values, an HTTP node's headers, an Assignment node's assignments) put the key box, the value box and up to two buttons on one line. In the 300 px inspector the key box showed about nine characters, so a field name read "subj" or "dueD". - **Now.** A row stacks: the key on its own line beside the remove button, the value on the line under it beside the text / expression toggle where the map has one. The same field name shows about 24 characters. Nothing about what the map stores changes.