From c4add1d11e8c09f8106ef3271f072615c4296b4e Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 5 Aug 2026 16:12:47 +0000 Subject: [PATCH 1/2] fix(components): read the fullscreen long-text flag on one spelling (#3303) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The built-in `textarea` branch resolved the flag as `mobile_fullscreen || fullscreen`, and both prop strips carried a matching entry discarding a `fullscreen` key. That alias had zero producers: neither this repo nor `@objectstack/spec` publishes a form-field `fullscreen` property (the only `fullscreen` keys that exist belong to the unrelated feedback/loading overlay), so the second term was undefined from the day it was written. Its cost was not a wrong value, it was a second spelling: the renderer advertised a flag that quietly does nothing, which is the lenient consumer fallback AGENTS.md #0.1 forbids and the same mechanism as #3245 / #3301. `ObjectForm` is the sole producer and stamps `mobile_fullscreen` (#3245/#3300) — also the single spelling `TextAreaField` and `RichTextField` read, so the built-in branch was the last place where a producer-less spelling still "worked". Removing the strip entries puts `fullscreen` in the ordinary unknown-key class rather than giving a key nobody produces a dedicated discard. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt --- ...m-fullscreen-flag-single-spelling.test.tsx | 169 ++++++++++++++++++ .../components/src/renderers/form/form.tsx | 15 +- 2 files changed, 180 insertions(+), 4 deletions(-) create mode 100644 packages/components/src/renderers/form/__tests__/form-fullscreen-flag-single-spelling.test.tsx diff --git a/packages/components/src/renderers/form/__tests__/form-fullscreen-flag-single-spelling.test.tsx b/packages/components/src/renderers/form/__tests__/form-fullscreen-flag-single-spelling.test.tsx new file mode 100644 index 0000000000..b661ab0fcc --- /dev/null +++ b/packages/components/src/renderers/form/__tests__/form-fullscreen-flag-single-spelling.test.tsx @@ -0,0 +1,169 @@ +/** + * ObjectUI + * Copyright (c) 2024-present ObjectStack Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * The fullscreen long-text flag has exactly ONE spelling — objectui#3303. + * + * The built-in (unregistered) `textarea` branch used to read the flag as + * `mobile_fullscreen || fullscreen`, and `stripRendererOnlyProps` / + * `stripRegisteredFieldProps` each carried a matching entry that discarded a + * `fullscreen` key. That alias had **zero producers**: a repo-wide grep (plus + * `objectstack`'s `packages/spec`) turned up only the unrelated + * feedback/loading overlay property of the same name. `ObjectForm` — the one + * and only producer — stamps `mobile_fullscreen` (#3245/#3300), so the second + * term of the `||` was undefined from the day it was written. + * + * A no-producer alias is not "one more layer of safety", it is the lenient + * consumer fallback AGENTS.md #0.1 forbids: the next author (very much + * including an AI writing form metadata) reads `fullscreen` off the renderer, + * spells it that way, and gets silence — no dialog, no error, nowhere to look. + * That is the same mechanism as #3245 and #3301. + * + * What these tests pin, in the two places the alias lived: + * + * 1. the built-in branch honours `mobile_fullscreen` and ONLY that. The + * canonical case is asserted alongside the alias case on purpose — a + * lone "the alias renders no expand button" assertion would also pass if + * the fullscreen affordance stopped rendering altogether, i.e. it would + * be green for an empty reason. + * 2. the prop strips own `mobile_fullscreen` and no longer name + * `fullscreen`, so a misspelled flag is now handled exactly like any + * other unrecognised authored key rather than being quietly swallowed by + * a dedicated discard entry. + */ + +import { describe, it, expect, beforeAll, beforeEach, afterEach, vi } from 'vitest'; +import { render, screen, fireEvent, cleanup } from '@testing-library/react'; +import { ComponentRegistry } from '@object-ui/core'; +// Module scope, not `beforeAll` — the cold transform must not be billed to +// `hookTimeout`. See object-ui/no-dynamic-import-in-test-hook (objectui#3010). +import '../../../renderers'; + +function renderForm(fields: any[]) { + const Form = ComponentRegistry.get('form')!; + return render( +
, + ); +} + +afterEach(() => { + cleanup(); + vi.restoreAllMocks(); +}); + +describe('form renderer — built-in textarea reads one fullscreen spelling (objectui#3303)', () => { + it('renders the expand affordance for the canonical `mobile_fullscreen`', () => { + // The positive half. Without it the alias assertion below would be + // satisfied by a branch that renders no expand button for ANY input. + renderForm([{ name: 'notes', label: 'Notes', type: 'textarea', mobile_fullscreen: true }]); + + expect(screen.getByTestId('form-textarea-fullscreen-toggle')).toBeInTheDocument(); + }); + + it('still opens and commits through the dialog on the canonical spelling', () => { + // Narrowing the read must not disturb the surviving path: the draft model + // (edit → Done → value lands in form state) is what the flag is FOR. + renderForm([{ name: 'notes', label: 'Notes', type: 'textarea', mobile_fullscreen: true }]); + + fireEvent.click(screen.getByTestId('form-textarea-fullscreen-toggle')); + fireEvent.change(screen.getByTestId('form-textarea-fullscreen-input'), { + target: { value: 'committed from the dialog' }, + }); + fireEvent.click(screen.getByTestId('form-textarea-fullscreen-save')); + + expect(screen.queryByTestId('form-textarea-fullscreen-dialog')).not.toBeInTheDocument(); + expect(screen.getByLabelText('Notes')).toHaveValue('committed from the dialog'); + }); + + it('does NOT honour the producer-less `fullscreen` alias', () => { + // The nail. Before #3303 this field DID render the expand button and the + // dialog, through the `|| fullscreen` limb — which is precisely the + // problem: a spelling nothing in the repo produces nevertheless "worked" + // here, and nowhere else (`TextAreaField` / `RichTextField` are both + // single-read), so the same metadata behaved differently depending on + // whether the field type happened to resolve to a registered widget. + // + // React logs its usual "Received `true` for a non-boolean attribute" + // warning here now, because the key survives the strip and reaches the + // `