Skip to content

Commit 04cbdf7

Browse files
fix(app-shell): on a read-only item, the Properties panel shown with nothing selected says why its controls are disabled (#29958)
<!-- claude-projects-attribution: {"github_login":"os-support-ai"} --> _Requested by **Yibo Wang** · [project thread](https://claude.ai/code/project/chan_01BVUyyPie8gHwoPM18iABwB?thread=cmsg_01BVUyyPie8gHwoPM18iABwB4xjiquevJqfBXiUoqfQwa1)_ Fixes #29897 ## 维护者速读 - **改了什么**:元数据编辑页(Studio「所有元数据」条目页)在锁定、只读包等只读状态下,未选中任何元素时显示的「属性」面板,以及无画布页面的通用表单,现在会说明控件为何被禁用:悬停禁用控件或用 Tab 键停到面板时弹出原因提示,读屏器也能读到同一句原因。 - **为什么改**:选中节点时的检查器早已给出原因,但默认属性面板一直静默置灰,键盘与读屏用户完全得不到原因,违反「禁用控件须说明原因」约定(QA 实测 3/3、2/2 复现)。 - **风险与回滚**:低。只在页面层用已有的 `InspectorReadOnlyReason` 包一层,不改任何注册检查器的契约;可写条目渲染与之前完全一致。回滚即还原 `ResourceEditPage.tsx` 一个文件。 - **先看**:`ResourceEditPage.tsx` 的 `withReadOnlyReason` 及三处包裹点;`ResourceEditPage.inspectorReadOnlyReason-29079.test.tsx` 新增的 objectstack#29897 describe 块。 - **两分钟验证**:在 showcase 中打开 `/_console/studio/~all/metadata/object/showcase_task`,不选中任何字段,悬停「Label」输入框,应弹出以「This item is locked and cannot be edited or deleted.」开头的原因提示;按 Tab 能停到属性面板并弹出同一句;换一个可写包的条目,面板可编辑且无提示。 - **需要你做的**:无,席位落地。 Clause-②: no Metadata-protocol: none ## What changes - **`@object-ui/app-shell`, `ResourceEditPage.tsx`.** The page wraps the panels it draws itself in `InspectorReadOnlyReason` (the wrapper the flow node and edge inspectors already use), handing them the same `inspectorReadOnlyReason` the scoped inspector gets. Three sites: the registered default inspector shown with nothing selected (object, view, dashboard, page, report, dataset, action, hook), the generic `SchemaForm` beside a canvas when the type registers no default inspector (flow), and the `SchemaForm` of a page with no canvas. The wrapper draws nothing unless the form is read-only and the reason is non-blank, so a writable item and view mode render exactly as before. - **No contract change.** `MetadataDefaultInspectorProps` is untouched and no registered default inspector was edited: the host draws the reason around whatever is registered. The scoped-inspector branch is unchanged (the inspector wraps itself, as since #29203). - The group's accessible name is the panel's own label (`engine.edit.inspector.properties`, "Properties"), an existing key in every locale. ## Tests New top-level `describe` in the existing page harness `ResourceEditPage.inspectorReadOnlyReason-29079.test.tsx` (the real page, the real `ObjectDefaultInspector` and the real `SchemaForm`; only the canvases and data reads are stubbed): - Locked object and locked flow, nothing selected: the Properties panel is a `tabindex=0` group named "Properties" whose accessible description is the lock sentence plus the served lock reason, and every form control in it is disabled (or, for the generic form's text inputs, read-only). - Locked object and locked flow: hovering the first disabled control opens a tooltip with the reason. - Locked object and locked flow: tabbing from the top of the page lands on the panel, which opens the tooltip with the reason. - CONTROL: a writable object renders the panel live (the Label input enabled) with no wrapper and no reason. <details><summary>Ablation (drop the prop)</summary> `node scripts/ablation-replace.mjs` replaced the wrapper's ` reason={inspectorReadOnlyReason}` with ` reason={undefined /* ablated-29897 */}` (anchor 1 → 0, blob `a324e465` → `520890e8`), then ran the file: **6 failed | 7 passed (13)**. The six failures are exactly the six new locked-item cases; the control and the six existing #29079 cases stayed green. Restored: blob == HEAD (`a324e465`), `git diff HEAD` empty. </details> ## Acceptance notes - The generic form's text inputs (`mdf-name`, `mdf-label` on a flow) are rendered `readonly`, not `disabled`, so they still take focus on their own; the reason reaches them through the described panel group around them. The disabled controls (the flow's Type select, the object panel's inputs) are the ones the tooltip and the group speak for. - A page without a canvas in view mode (writable, not editing) still draws no reason: the page offers an Edit button there and `inspectorReadOnlyReason` returns none for view mode, as before. The comment on `inspectorReadOnlyReason` now says so. - The Source tab (`JsonSourceEditor`) is unchanged; it is out of this card's scope. - No README or docs page change: this is an accessibility fix to an existing panel, not a new feature or prop. ## Verification All readings at head `d9aae973` on `objectstack-ai/objectstack`, merge base `21510bba`; heavy runs under `scripts/pm/os-verify-lock.sh` on a shared box. <details><summary>Local package checks</summary> - `pnpm turbo run build --filter='@object-ui/app-shell^...' --concurrency=2`: 64/64 tasks, exit 0. - `pnpm --filter @object-ui/app-shell build`: exit 0. - `pnpm --filter @object-ui/app-shell run type-check` (`tsc --noEmit` and `tsc -p tsconfig.test.json`): exit 0. - `pnpm --dir ui exec eslint` on the two touched ui files: 0 errors, 51 warnings, none on a line this PR adds or changes. - `pnpm --dir ui exec vitest run packages/app-shell/src/views/metadata-admin/ResourceEditPage.inspectorReadOnlyReason-29079.test.tsx`: 13 passed (13). - `pnpm --dir ui exec vitest run --maxWorkers=3 packages/app-shell/`: Test Files 2166 passed, 1 skipped (2167); Tests 19243 passed, 9 skipped; exit 0. </details> <details><summary>Derived gates (dispatch-gates --commands, 35 families)</summary> `node scripts/pm/dispatch-gates.mjs --ran`: 35 derived, 35 run, 0 NOT-MEASURED, 0 UNRUN, exit 0. ```text node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0 node scripts/check-adr-0087-registration.mjs --self-test :: exit 0 node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0 node scripts/check-closing-keyword-parity.mjs :: exit 0 node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0 node scripts/check-comment-mask-corpus.mjs :: exit 0 node scripts/check-empty-changeset.mjs --base origin/main :: exit 0 node scripts/check-empty-changeset.mjs --self-test :: exit 0 node scripts/check-sdui-manifest.mjs :: exit 0 node scripts/check-sdui-manifest.mjs --self-test :: exit 0 node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0 node scripts/release-pending-publish.mjs --self-test :: exit 0 pnpm --dir ui exec vitest run scripts/ :: exit 0 pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0 pnpm check:changeset-gate-self-tests :: exit 0 pnpm check:closed-ceremony-text :: exit 0 pnpm check:cross-package-test-inputs :: exit 0 pnpm check:dispatcher-error-vocabulary :: exit 0 pnpm check:driver-memory-census :: exit 0 pnpm check:engine-double-contract :: exit 0 pnpm check:gitlink-declared :: exit 0 pnpm check:nul-bytes :: exit 0 pnpm check:published-files :: exit 0 pnpm check:query-options-erasure :: exit 0 pnpm check:refd-timer-probe :: exit 0 pnpm check:registry-log-declared :: exit 0 pnpm check:rest-log-spy-declared :: exit 0 pnpm check:skill-identifier-liveness :: exit 0 pnpm check:test-source-alias :: exit 0 pnpm check:tier-file-adoption :: exit 0 pnpm check:type-check-coverage :: exit 0 pnpm check:type-check-debt :: exit 0 pnpm check:undeclared-dep-imports :: exit 0 pnpm check:watch-hint-literal :: exit 0 pnpm check:where-matcher :: exit 0 ``` </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01WGufziRkHRHwEC3eQwnrwx --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 6e68d8f commit 04cbdf7

3 files changed

Lines changed: 194 additions & 54 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
---
4+
5+
fix(app-shell): on a read-only item, the Properties panel shown with nothing selected says why its controls are disabled
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
On the metadata editor page (Studio's All metadata item page, Setup's metadata pages), a locked item, a read-only package, a caller without `manage_metadata`, a code-shipped artifact or a walled catalog type disables every control in the Properties panel. The scoped inspector (a selected flow node or edge) already said why; the panel shown with nothing selected (an object's or view's default inspector, or the generic form a flow shows) greyed its controls silently, and Tab never reached them. The page now wraps that panel, and the generic form on a page without a canvas, in the same reason wrapper the scoped inspectors use: hovering a disabled control or tabbing to the panel opens a tooltip with the reason, and the panel's accessible description is that reason. A writable item renders the panel exactly as before. No registered default inspector changes: the page draws the reason around it.

‎ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.inspectorReadOnlyReason-29079.test.tsx‎

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import '@testing-library/jest-dom/vitest';
2222
import * as React from 'react';
2323
import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest';
2424
import { render, screen, cleanup, fireEvent, waitFor } from '@testing-library/react';
25+
import userEvent from '@testing-library/user-event';
2526
import { MemoryRouter } from 'react-router-dom';
2627
import { MetadataAreaHostContext, type MetadataAreaHost } from './metadataAreaHost';
2728

@@ -42,6 +43,8 @@ const FLOW = {
4243

4344
const POSITION = { name: 'sales_rep', label: 'Sales rep' };
4445

46+
const OBJECT = { name: 'task', label: 'Task', fields: { title: { type: 'text', label: 'Title' } } };
47+
4548
/** The layered envelope each case serves; reset per test. */
4649
let layered: Record<string, unknown> = {};
4750
let canAuthor = true;
@@ -68,6 +71,7 @@ vi.mock('./useMetadata', async (importOriginal) => ({
6871
entries: [
6972
{ type: 'flow', label: 'Flow', domain: 'automation', allowEnvironmentOverlay: false, allowRuntimeCreate: true },
7073
{ type: 'position', label: 'Position', domain: 'security', allowEnvironmentOverlay: false, allowRuntimeCreate: true },
74+
{ type: 'object', label: 'Object', domain: 'data', allowEnvironmentOverlay: false, allowRuntimeCreate: true },
7175
],
7276
}),
7377
}));
@@ -132,6 +136,7 @@ function HostPositionInspector({ readOnly, readOnlyReason }: MetadataInspectorPr
132136
const realFlowPreview = getMetadataPreview('flow');
133137
const realPositionPreview = getMetadataPreview('position');
134138
const realPositionInspector = getMetadataInspector('position');
139+
const realObjectPreview = getMetadataPreview('object');
135140

136141
beforeEach(() => {
137142
layered = { effective: FLOW, code: null, overlay: null, editable: true, deletable: true };
@@ -140,6 +145,7 @@ beforeEach(() => {
140145
registerMetadataPreview('flow', stubCanvas({ kind: 'node', id: 'set_status', label: 'Set status' }) as never);
141146
registerMetadataPreview('position', stubCanvas({ kind: 'node', id: 'sales_rep', label: 'Sales rep' }) as never);
142147
registerMetadataInspector('position', HostPositionInspector);
148+
registerMetadataPreview('object', stubCanvas({ kind: 'field', id: 'title', label: 'Title' }) as never);
143149
});
144150

145151
afterEach(() => {
@@ -148,6 +154,7 @@ afterEach(() => {
148154
if (realFlowPreview) registerMetadataPreview('flow', realFlowPreview);
149155
if (realPositionPreview) registerMetadataPreview('position', realPositionPreview);
150156
if (realPositionInspector) registerMetadataInspector('position', realPositionInspector);
157+
if (realObjectPreview) registerMetadataPreview('object', realObjectPreview);
151158
});
152159

153160
const READ_ONLY_HOST: MetadataAreaHost = {
@@ -241,3 +248,103 @@ describe('the metadata editor page names the read-only cause to the scoped inspe
241248
);
242249
});
243250
});
251+
252+
/** A full lock with a served reason, over `effective`. */
253+
function lockedEnvelope(effective: Record<string, unknown>) {
254+
return {
255+
effective,
256+
code: null,
257+
overlay: null,
258+
editable: false,
259+
deletable: false,
260+
lock: 'full',
261+
lockReason: 'Managed by the release pipeline.',
262+
};
263+
}
264+
265+
const LOCK_REASON = () => `${t('engine.edit.lockFull', 'en-US')} Managed by the release pipeline.`;
266+
267+
/** Open the item and leave the canvas unselected: the page's own panel shows. */
268+
async function openUnselected(type: string, name: string) {
269+
render(
270+
<MemoryRouter initialEntries={[`/apps/setup/metadata/${type}/${name}`]}>
271+
<MetadataResourceEditPage type={type} name={name} />
272+
</MemoryRouter>,
273+
);
274+
await screen.findByRole('button', { name: 'select on canvas' });
275+
}
276+
277+
/** The Properties panel's form controls: the ones the reason is about. */
278+
const panelControls = (root: HTMLElement) =>
279+
[...root.querySelectorAll<HTMLElement>('input, select, textarea, button[role="combobox"], button[role="switch"]')];
280+
281+
/** The controls a pointer and Tab cannot reach: those the panel speaks for. */
282+
const disabledControls = (root: HTMLElement) =>
283+
panelControls(root).filter((el) => (el as HTMLInputElement).disabled);
284+
285+
/** Tab from the top of the page until `target` has focus, or the page runs out. */
286+
async function tabTo(user: ReturnType<typeof userEvent.setup>, target: HTMLElement): Promise<boolean> {
287+
for (let i = 0; i < 80 && document.activeElement !== target; i++) await user.tab();
288+
return document.activeElement === target;
289+
}
290+
291+
const LOCKED_ITEMS = [
292+
['object', 'task', OBJECT],
293+
['flow', 'close_case', FLOW],
294+
] as const;
295+
296+
describe('the panel shown with nothing selected names the read-only cause too (objectstack#29897)', () => {
297+
it.each(LOCKED_ITEMS)(
298+
'a locked %s: the Properties panel is a described tab stop holding its disabled controls',
299+
async (type, name, effective) => {
300+
layered = lockedEnvelope(effective);
301+
await openUnselected(type, name);
302+
const panel = await screen.findByTestId('metadata-properties-readonly-reason');
303+
expect(panel).toHaveAttribute('tabindex', '0');
304+
expect(panel).toHaveAccessibleName(t('engine.edit.inspector.properties', 'en-US'));
305+
expect(panel).toHaveAccessibleDescription(LOCK_REASON());
306+
307+
// A vacuous pass over an empty panel is not one. A text input the generic
308+
// form renders read-only (not disabled) still takes focus on its own.
309+
expect(disabledControls(panel).length).toBeGreaterThanOrEqual(1);
310+
for (const el of panelControls(panel)) {
311+
expect((el as HTMLInputElement).disabled || el.hasAttribute('readonly')).toBe(true);
312+
}
313+
},
314+
);
315+
316+
it.each(LOCKED_ITEMS)(
317+
'a locked %s: hovering a disabled control opens a tooltip with the reason',
318+
async (type, name, effective) => {
319+
const user = userEvent.setup();
320+
layered = lockedEnvelope(effective);
321+
await openUnselected(type, name);
322+
const panel = await screen.findByTestId('metadata-properties-readonly-reason');
323+
const [control] = disabledControls(panel);
324+
expect(control).toBeDisabled();
325+
await user.hover(control);
326+
expect(await screen.findByRole('tooltip')).toHaveTextContent(LOCK_REASON());
327+
},
328+
);
329+
330+
it.each(LOCKED_ITEMS)(
331+
'a locked %s: a keyboard user reaches the reason, Tab lands on the panel and opens it',
332+
async (type, name, effective) => {
333+
const user = userEvent.setup();
334+
layered = lockedEnvelope(effective);
335+
await openUnselected(type, name);
336+
const panel = await screen.findByTestId('metadata-properties-readonly-reason');
337+
expect(await tabTo(user, panel)).toBe(true);
338+
expect(await screen.findByRole('tooltip')).toHaveTextContent(LOCK_REASON());
339+
},
340+
);
341+
342+
it('a writable object: the Properties panel is live and carries no reason', async () => {
343+
layered = { effective: OBJECT, code: null, overlay: null, editable: true, deletable: true };
344+
await openUnselected('object', 'task');
345+
const label = await screen.findByDisplayValue('Task');
346+
await waitFor(() => expect(label).toBeEnabled());
347+
expect(screen.queryByTestId('metadata-properties-readonly-reason')).toBeNull();
348+
expect(screen.queryByText(LOCK_REASON())).toBeNull();
349+
});
350+
});

‎ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx‎

Lines changed: 77 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ import { readFields } from './previews/object-fields-io.js';
146146
import { useRegisterAssistantEditor, type AssistantEditorContext } from '../../assistant/assistantBus.js';
147147
import { useRegisteredMetadataInspector } from './inspector-registry.js';
148148
import { useRegisteredMetadataDefaultInspector } from './default-inspector-registry.js';
149+
import { InspectorReadOnlyReason } from './inspectors/_shared.js';
149150
import { useMetadataLocale, t, tFormat, translateMetadataType, translateSchemaIssue, translateValidationMessage } from './i18n.js';
150151
import { JsonSourceEditor } from '../item-tools/JsonSourceEditor.js';
151152
import { validateMetadataDraft, hasClientValidator, type DraftMode } from './clientValidation.js';
@@ -2621,7 +2622,8 @@ function MetadataResourceEditPageImpl({
26212622
// names the cause (`MetadataInspectorProps.readOnlyReason`). View mode owes
26222623
// no sentence: the inspector exists only beside a designer canvas, and a
26232624
// designer type enters editing whenever it can be written (the auto-on
2624-
// effect above) and offers no Edit button to point at.
2625+
// effect above) and offers no Edit button to point at. A form without a
2626+
// canvas does offer one, so its view mode stays unexplained here too.
26252627
const inspectorReadOnlyReason = ((): string | undefined => {
26262628
if (!formReadOnly) return undefined;
26272629
if (walledDeciding) {
@@ -2638,6 +2640,21 @@ function MetadataResourceEditPageImpl({
26382640
return tFormat('engine.edit.readOnlyTypeBanner', locale, { flag: 'OS_METADATA_WRITABLE', type });
26392641
})();
26402642

2643+
// objectstack#29897 — the panels the page draws itself (the default
2644+
// inspector shown with nothing selected, and the generic form) carry the
2645+
// same reason, through the wrapper the scoped inspectors use. The page wraps
2646+
// them, so no registered default inspector has to honour a new prop.
2647+
const withReadOnlyReason = (panel: React.ReactNode) => (
2648+
<InspectorReadOnlyReason
2649+
readOnly={formReadOnly}
2650+
reason={inspectorReadOnlyReason}
2651+
label={t('engine.edit.inspector.properties', locale)}
2652+
testId="metadata-properties-readonly-reason"
2653+
>
2654+
{panel}
2655+
</InspectorReadOnlyReason>
2656+
);
2657+
26412658
// Note: URL `?tab=` deep-links were repurposed to open side-panel
26422659
// sheets (Layers / References / Related). Anything else is ignored —
26432660
// the main work area is always the form+preview.
@@ -3562,43 +3579,47 @@ function MetadataResourceEditPageImpl({
35623579
locale={locale}
35633580
/>
35643581
) : !selection && DefaultInspectorComponent ? (
3565-
// eslint-disable-next-line react-hooks/static-components -- useRegisteredMetadataDefaultInspector returns a registered component (stable), not one created during render
3566-
<DefaultInspectorComponent
3567-
type={type}
3568-
name={name}
3569-
draft={draft}
3570-
onPatch={(patch) =>
3571-
handleCreateAwareChange((d) => ({
3572-
...(d as Record<string, unknown>),
3573-
...patch,
3574-
}))
3575-
}
3576-
onSelectionChange={setSelection}
3577-
onBlockingIssuesChange={(count) =>
3578-
setBlockingReport({ key: selectionKey, count })
3579-
}
3580-
readOnly={formReadOnly}
3581-
locale={locale}
3582-
serverSchema={entry?.schema as Record<string, unknown> | undefined}
3583-
/>
3582+
withReadOnlyReason(
3583+
// eslint-disable-next-line react-hooks/static-components -- useRegisteredMetadataDefaultInspector returns a registered component (stable), not one created during render
3584+
<DefaultInspectorComponent
3585+
type={type}
3586+
name={name}
3587+
draft={draft}
3588+
onPatch={(patch) =>
3589+
handleCreateAwareChange((d) => ({
3590+
...(d as Record<string, unknown>),
3591+
...patch,
3592+
}))
3593+
}
3594+
onSelectionChange={setSelection}
3595+
onBlockingIssuesChange={(count) =>
3596+
setBlockingReport({ key: selectionKey, count })
3597+
}
3598+
readOnly={formReadOnly}
3599+
locale={locale}
3600+
serverSchema={entry?.schema as Record<string, unknown> | undefined}
3601+
/>
3602+
)
35843603
) : (
3585-
<SchemaForm
3586-
schema={schema}
3587-
idPath={formIdPath}
3588-
form={createMode && config.createSchema ? undefined : entry?.form}
3589-
value={draft}
3590-
onChange={handleCreateAwareChange}
3591-
issues={displayIssues}
3592-
hiddenFields={effectiveHiddenFields}
3593-
fieldOrder={effectiveFieldOrder}
3594-
readOnly={formReadOnly}
3595-
createMode={createMode}
3596-
widgetContext={widgetContext}
3597-
revealIssues={revealIssues}
3598-
onInlineIssueFieldsChange={setInlineIssueFields}
3599-
onRequiredRowFieldsChange={setRequiredRowFields}
3600-
fieldNotes={createFieldNotes}
3601-
/>
3604+
withReadOnlyReason(
3605+
<SchemaForm
3606+
schema={schema}
3607+
idPath={formIdPath}
3608+
form={createMode && config.createSchema ? undefined : entry?.form}
3609+
value={draft}
3610+
onChange={handleCreateAwareChange}
3611+
issues={displayIssues}
3612+
hiddenFields={effectiveHiddenFields}
3613+
fieldOrder={effectiveFieldOrder}
3614+
readOnly={formReadOnly}
3615+
createMode={createMode}
3616+
widgetContext={widgetContext}
3617+
revealIssues={revealIssues}
3618+
onInlineIssueFieldsChange={setInlineIssueFields}
3619+
onRequiredRowFieldsChange={setRequiredRowFields}
3620+
fieldNotes={createFieldNotes}
3621+
/>
3622+
)
36023623
)}
36033624
</div>
36043625
</div>
@@ -3610,23 +3631,25 @@ function MetadataResourceEditPageImpl({
36103631
button, so this duplicate affordance was just noise. */}
36113632
</div>
36123633
) : (
3613-
<SchemaForm
3614-
schema={schema}
3615-
idPath={formIdPath}
3616-
form={createMode && config.createSchema ? undefined : entry?.form}
3617-
value={draft}
3618-
onChange={handleCreateAwareChange}
3619-
issues={displayIssues}
3620-
hiddenFields={effectiveHiddenFields}
3621-
fieldOrder={effectiveFieldOrder}
3622-
readOnly={formReadOnly}
3623-
createMode={createMode}
3624-
widgetContext={widgetContext}
3625-
revealIssues={revealIssues}
3626-
onInlineIssueFieldsChange={setInlineIssueFields}
3627-
onRequiredRowFieldsChange={setRequiredRowFields}
3628-
fieldNotes={createFieldNotes}
3629-
/>
3634+
withReadOnlyReason(
3635+
<SchemaForm
3636+
schema={schema}
3637+
idPath={formIdPath}
3638+
form={createMode && config.createSchema ? undefined : entry?.form}
3639+
value={draft}
3640+
onChange={handleCreateAwareChange}
3641+
issues={displayIssues}
3642+
hiddenFields={effectiveHiddenFields}
3643+
fieldOrder={effectiveFieldOrder}
3644+
readOnly={formReadOnly}
3645+
createMode={createMode}
3646+
widgetContext={widgetContext}
3647+
revealIssues={revealIssues}
3648+
onInlineIssueFieldsChange={setInlineIssueFields}
3649+
onRequiredRowFieldsChange={setRequiredRowFields}
3650+
fieldNotes={createFieldNotes}
3651+
/>
3652+
)
36303653
)}
36313654
{/* objectui#7611 — under the designer, too: `position` renders
36323655
through its preview, so the section takes a bounded strip

0 commit comments

Comments
 (0)