feat(apollo-react): add LockableValueField HITL prototype#893
feat(apollo-react): add LockableValueField HITL prototype#893dbacomputer wants to merge 12 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Dependency License Review
License distribution
Excluded packages
|
There was a problem hiding this comment.
Pull request overview
Adds a HITL-oriented LockableValueField prototype to the NodePropertyPanel Storybook surface, demonstrating quick-form editing patterns (lock/read-only, fixed vs expression, AI assist affordance) and a draggable list of fields.
Changes:
- Introduces
LockableValueField(lock toggle + value-type switcher + AI/insert affordances) built on@uipath/apollo-wind’sInputGroup. - Adds a new “Lockable Field” Storybook story that renders a Quick Approve-style panel, including a draggable/reorderable list of lockable fields.
- Exports
LockableValueFieldand its types from the NodePropertyPanel barrel.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| packages/apollo-react/src/canvas/components/NodePropertyPanel/NodePropertyPanel.stories.tsx | Adds the “Lockable Field” prototype story, including DnD-based field reordering and panel UI. |
| packages/apollo-react/src/canvas/components/NodePropertyPanel/LockableValueField.tsx | Adds the new LockableValueField component (lock + mode switch + AI/insert affordances) using apollo-wind primitives. |
| packages/apollo-react/src/canvas/components/NodePropertyPanel/index.ts | Re-exports LockableValueField and related types from the NodePropertyPanel module. |
| <InputGroupInput | ||
| id={id} | ||
| readOnly={locked} | ||
| value={value} | ||
| onChange={(e) => onValueChange?.(e.target.value)} | ||
| placeholder={FIELD_PLACEHOLDER[mode]} | ||
| className={mode === 'expression' ? 'font-mono' : undefined} | ||
| /> |
| export function LockableValueField({ | ||
| value = '', | ||
| onValueChange, | ||
| locked = true, | ||
| onLockedChange, |
| > | ||
| <Plus size={12} /> | ||
| Add case | ||
| </button> |
📊 Coverage + size by packagePer-package coverage and bundle size on this PR. New-line coverage = of the source lines this PR adds or changes, the % hit by tests.
"Coverage" is each package's own |
| /* Storybook (apps/storybook/.storybook/preview.tsx) imports this file | ||
| * directly, making it the Tailwind entry point for the whole app — including | ||
| * apollo-react's canvas stories. Without this, utility classes unique to | ||
| * apollo-react (not already used somewhere in apollo-wind) silently produce | ||
| * no CSS, since they'd never be scanned otherwise. */ |
| * apollo-react's canvas stories. Without this, utility classes unique to | ||
| * apollo-react (not already used somewhere in apollo-wind) silently produce | ||
| * no CSS, since they'd never be scanned otherwise. */ | ||
| @source "../../../apollo-react/src/**/*.{js,jsx,ts,tsx}"; No newline at end of file |
| {label ?? ( | ||
| <Label htmlFor={id} className="text-xs font-medium text-foreground-muted"> | ||
| {FIELD_LABEL[mode]} | ||
| {required && <span className="ml-0.5 text-destructive">*</span>} | ||
| </Label> |
| {value ? ( | ||
| new Date(value).toLocaleDateString(undefined, { | ||
| year: 'numeric', | ||
| month: 'long', | ||
| day: 'numeric', | ||
| }) | ||
| ) : ( | ||
| <span className="text-muted-foreground">Pick a date</span> | ||
| )} |
| id={id} | ||
| checked={value === 'true'} | ||
| onCheckedChange={(checked) => onValueChange?.(String(checked))} | ||
| disabled={locked} | ||
| /> |
| const handleJsonChange = (value: string) => { | ||
| setJsonDraft(value); | ||
| try { | ||
| const parsed = JSON.parse(value); | ||
| if (!Array.isArray(parsed)) { | ||
| setJsonError('Expected a JSON array of fields.'); | ||
| return; | ||
| } | ||
| setCases(parsed); | ||
| setJsonError(null); | ||
| } catch { | ||
| setJsonError('Invalid JSON.'); | ||
| } | ||
| }; |
| export { | ||
| DEMO_SELECT_OPTIONS, | ||
| FIELD_TYPE_META, | ||
| LockableValueField, | ||
| parseListValue, | ||
| } from './LockableValueField'; |
| * the direction a full code-editing surface would take. Select/multiselect | ||
| * options are demo placeholders; file uploads aren't persisted anywhere. | ||
| */ | ||
| export function LockableValueField({ |
| id?: string; | ||
| className?: string; | ||
| } |
2604a3c to
fe5294c
Compare
| * directly, making it the Tailwind entry point for the whole app — including | ||
| * apollo-react's canvas stories. Without this, utility classes unique to | ||
| * apollo-react (not already used somewhere in apollo-wind) silently produce | ||
| * no CSS, since they'd never be scanned otherwise. */ | ||
| @source "../../../apollo-react/src/**/*.{js,jsx,ts,tsx}"; No newline at end of file |
| const FIELD_LABEL: Record<LockableValueFieldMode, string> = { | ||
| fixed: 'String value', | ||
| expression: 'Write a string expression', | ||
| }; |
| <Label htmlFor={id} className="text-xs font-medium text-foreground-muted"> | ||
| {FIELD_LABEL[mode]} | ||
| {required && <span className="ml-0.5 text-destructive">*</span>} | ||
| </Label> |
| <InputGroupButton | ||
| icon | ||
| size="3xs" | ||
| aria-label={locked ? 'Locked. Click to unlock.' : 'Unlocked. Click to lock.'} | ||
| > |
| <InputGroupButton | ||
| icon | ||
| size="3xs" | ||
| aria-label={locked ? 'Locked. Click to unlock.' : 'Unlocked. Click to lock.'} | ||
| > |
| const parsed = JSON.parse(value); | ||
| if (!Array.isArray(parsed)) { | ||
| setJsonError('Expected a JSON array of fields.'); | ||
| return; | ||
| } | ||
| setCases(parsed); | ||
| setJsonError(null); |
| export { | ||
| DEMO_SELECT_OPTIONS, | ||
| FIELD_TYPE_META, | ||
| LockableValueField, | ||
| parseListValue, | ||
| } from './LockableValueField'; |
| export function LockableValueField({ | ||
| value = '', | ||
| onValueChange, | ||
| locked = true, | ||
| onLockedChange, |
| {label ?? ( | ||
| <Label htmlFor={id} className="text-xs font-medium text-foreground-muted"> | ||
| {FIELD_LABEL[mode]} | ||
| {required && <span className="ml-0.5 text-destructive">*</span>} | ||
| </Label> | ||
| )} |
| <InputGroupButton | ||
| icon | ||
| size="3xs" | ||
| aria-label={locked ? 'Locked. Click to unlock.' : 'Unlocked. Click to lock.'} | ||
| > | ||
| {locked ? <Lock /> : <LockOpen />} | ||
| </InputGroupButton> |
| <DropdownMenuTrigger asChild> | ||
| <InputGroupButton icon size="3xs" aria-label="Choose value type"> | ||
| <typeMeta.icon /> | ||
| </InputGroupButton> | ||
| </DropdownMenuTrigger> |
| export { | ||
| DEMO_SELECT_OPTIONS, | ||
| FIELD_TYPE_META, | ||
| LockableValueField, | ||
| parseListValue, | ||
| } from './LockableValueField'; |
| <button | ||
| type="button" | ||
| className="flex h-8 items-center gap-2 rounded-lg bg-brand px-4 text-sm font-semibold text-foreground-on-accent transition hover:bg-brand-hover" | ||
| > | ||
| <CircleCheck size={14} /> | ||
| Approve | ||
| </button> |
| const handleJsonChange = (value: string) => { | ||
| setJsonDraft(value); | ||
| try { | ||
| const parsed = JSON.parse(value); | ||
| if (!Array.isArray(parsed)) { | ||
| setJsonError('Expected a JSON array of fields.'); | ||
| return; | ||
| } | ||
| setCases(parsed); | ||
| setJsonError(null); | ||
| } catch { | ||
| setJsonError('Invalid JSON.'); | ||
| } | ||
| }; |
| /* Storybook (apps/storybook/.storybook/preview.tsx) imports this file | ||
| * directly, making it the Tailwind entry point for the whole app — including | ||
| * apollo-react's canvas stories. Without this, utility classes unique to | ||
| * apollo-react (not already used somewhere in apollo-wind) silently produce | ||
| * no CSS, since they'd never be scanned otherwise. */ | ||
| @source "../../../apollo-react/src/**/*.{js,jsx,ts,tsx}"; No newline at end of file |
| type="button" | ||
| onClick={() => { | ||
| navigator.clipboard.writeText( | ||
| `${window.location.origin}/forms/quick-approve` | ||
| ); | ||
| setMoreMenuOpen(false); | ||
| }} |
| export type { ExpressionFieldProps, ExpressionMode } from './ExpressionField'; | ||
| export { ExpressionField } from './ExpressionField'; | ||
| export type { | ||
| LockableFieldType, | ||
| LockableValueFieldMode, | ||
| LockableValueFieldProps, | ||
| } from './LockableValueField'; | ||
| export { | ||
| DEMO_SELECT_OPTIONS, | ||
| FIELD_TYPE_META, | ||
| LockableValueField, | ||
| parseListValue, | ||
| } from './LockableValueField'; | ||
| export { NodePropertyPanel } from './NodePropertyPanel'; | ||
| export type { NodePropertyPanelProps } from './NodePropertyPanel.types'; |
| {label ?? ( | ||
| <Label htmlFor={id} className="text-xs font-medium text-foreground-muted"> | ||
| {FIELD_LABEL[mode]} | ||
| {required && <span className="ml-0.5 text-destructive">*</span>} | ||
| </Label> | ||
| )} |
| {label ?? ( | ||
| <Label htmlFor={id} className="text-xs font-medium text-foreground-muted"> | ||
| {FIELD_LABEL[mode]} |
| /** | ||
| * LockableValueField — a field that can be locked to read-only, typed as one | ||
| * of several data types, and (for scalar types) switched between a literal | ||
| * value and a JS expression. | ||
| * | ||
| * Prototype: the expression mode is styled as code (monospace) but does not | ||
| * yet carry real syntax highlighting or evaluation. See ExpressionField for | ||
| * the direction a full code-editing surface would take. Select/multiselect | ||
| * options are demo placeholders; file uploads aren't persisted anywhere. | ||
| */ | ||
| export function LockableValueField({ | ||
| value = '', | ||
| onValueChange, | ||
| locked = true, | ||
| onLockedChange, | ||
| mode = 'fixed', | ||
| onModeChange, | ||
| fieldType = 'string', |
| * directly, making it the Tailwind entry point for the whole app — including | ||
| * apollo-react's canvas stories. Without this, utility classes unique to | ||
| * apollo-react (not already used somewhere in apollo-wind) silently produce | ||
| * no CSS, since they'd never be scanned otherwise. */ | ||
| @source "../../../apollo-react/src/**/*.{js,jsx,ts,tsx}"; No newline at end of file |
| const lockedDisplayValue = | ||
| fieldType === 'boolean' | ||
| ? value === 'true' | ||
| ? 'True' | ||
| : 'False' | ||
| : fieldType === 'date' | ||
| ? value |
| {label ?? ( | ||
| <Label htmlFor={id} className="text-xs font-medium text-foreground-muted"> | ||
| {FIELD_LABEL[mode]} | ||
| {required && <span className="ml-0.5 text-destructive">*</span>} | ||
| </Label> | ||
| )} |
| <Switch | ||
| id={id} | ||
| checked={value === 'true'} | ||
| onCheckedChange={(checked) => onValueChange?.(String(checked))} | ||
| /> |
Prototype for a Quick Approve panel demonstrating quick-form editing inside the Property Panel experience. - LockableValueField: a string field built on apollo-wind's InputGroup, with a lock/unlock popover (locked = read-only, not disabled) and a Fixed value / Expression type-switcher popover, plus an AI-assist popover (describe + generate) and an Insert-variable affordance. - NodePropertyPanel "Lockable Field" story: a Quick Approve panel with a Delivery channel multi-select, an Assignment criteria search + dropdown, and a Quick form section (UI/JSON toggle) containing a draggable, deletable, re-orderable list of LockableValueField rows seeded with Invoice Number / Submission Date / Approved Amount. Built entirely on existing apollo-wind primitives (InputGroup, DropdownMenu, Popover, MultiSelect, Select, Card, ToggleGroup) and the existing @dnd-kit dependency already used by StageNode, following this file's established prototype conventions (PanelFrame, CasePanel-style row chrome). No production wiring yet — this is a review prototype.
…build Storybook imports apollo-wind's tailwind.css directly as its Tailwind entry point, including for apollo-react's canvas stories, but the @source glob only covered apollo-wind's own files. Utility classes unique to apollo-react (e.g. container-query variants) silently produced no CSS since they were never scanned. Also pins @tailwindcss/vite to an exact version already cleared by the pnpm minimumReleaseAge policy, keeping it aligned with the tailwindcss core version resolved elsewhere in the workspace.
Iterates the HITL "Quick Approve" panel prototype for coworker feedback: - Field-type switching (string, integer, date, boolean, single/multi select, file) with per-type value controls and Fixed/Expression copy. - Responsive header: field-type/AI-assist/insert-variable controls collapse to icon-only in narrow containers, plus a `compact` prop to force that layout for side-by-side comparison. - Real tooltips on the icon-only header controls instead of native title attributes. - Edit/Preview/JSON toggle for the Quick Form card, restyled to borderless square icon buttons matching the compact header controls. - Showcase panel documenting the component with live Full view / Compact view examples.
… HITL prototype Adds a Required/Not-required toggle (full-view switch, compact icon+popover variant), an upload-triggering icon on the demo file card, a type-selector dropdown behind "+ Add field" (including a new Button field type), and Approve/Cancel form buttons editable inline. Replaces the field-reorder insertion-line indicator with a DragOverlay + synced placeholder, mirroring StageNode's existing dnd-kit pattern, since the prior indicator was visually inconsistent during drag. Also fixes apollo-wind's Switch "sm" size to match shadcn/ui's actual Small spec (track 14x24, thumb 12px) and updates its tests accordingly, and fixes a hover-visibility bug where a field's controls would fade out while their own popover/dropdown was still open (Tooltip/Popover asChild data-state collision fixed by keying off aria-expanded instead).
…ew switcher Moves the form-button edit affordance (Type/Delete popover) inside the button pill and makes it expand on hover instead of floating a separate icon; wires the buttons into the Preview view so they actually render there. Reworks the Quick form view row into a standalone "more options" menu (Preview, Describe the form you want) plus a connected UI/JSON segmented toggle, replacing the earlier three-icon row for a cleaner layout. Also drops the header icon on Form HITL Review to free up title space next to the Reject/Approve actions.
… menu Moves the "String value"/"Write a string expression" text to the field's inside placeholder (it was incorrectly placed as the label above the field) so it now shows consistently across all locked field types, matching the LockableValueField showcase used as the source of truth. Simplifies the Add field/Add button popover down to two flat actions (dropping the field-type submenu, since choosing a type after adding wasn't needed), reorders it above the buttons row, and resizes form buttons to match input height. Also renames the builder tabs to Parameters / Branching / Error handling.
Empty commit to refresh the PR's Storybook preview deployment with today's Form HITL changes for coworker review.
- Controls default to Show instead of Hide across the showcase and both
Form HITL / Form HITL Review panels.
- Fixed/Expression trigger icon no longer varies by field type (was
swapping to the per-type icon; now strictly Type vs Code2).
- Simplified the "more" menu to a single AI-generate action (dropped
Preview and Get link) and removed the now-unused Preview/JSON views
and the UI/JSON toggle from the Quick form builder.
- Added a header-synced close (X) action to LockableValueField via
headerActions, wired to controlsVisibility, and fixed the same
affordance on the left-panel field rows (previously hardcoded to
row-hover regardless of the Show/Hide toggle).
- Swapped the Insert-variable glyph from a "{x}" text glyph to a Braces
icon so it reads distinctly from the new close X.
- Added a showFieldActions prop to suppress the AI-assist/Insert-variable
actions for read-only reviewer contexts, and used it on the Form HITL
Review left panel so reviewers can't reconfigure fields they're only
meant to approve.
- Renamed the lock toggle's Locked/Unlocked language to Read-only/Editable
everywhere it appears (aria-labels, dropdown items, showcase copy).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…opy action Coworker feedback (Dushyant/Prabhat) flagged that removing the JSON toggle overfit on the Flow Canvas builder experience and cut against QuickForms' schema-first design: JSON is what Case Management/Coded Apps users and generation agents actually work from, and it's expected to stay available at parity with the low-code builder (FormIO parity). - Reintroduce the UI/JSON ToggleGroup in the Quick form header, wired to a formView state (edit/json only — Preview stays removed). - JSON view shows the live field schema in a synced textarea; editing it round-trips back into the field list, and building fields in the UI updates the JSON live. - Add a one-click copy icon (Copy/CircleCheck swap, matching the existing copyExpr pattern) for grabbing the schema. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
0bd6d11 to
f6df0cc
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 8 changed files in this pull request and generated 6 comments.
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
Comments suppressed due to low confidence (5)
packages/apollo-wind/src/styles/tailwind.css:20
- Adding
@sourcepointing atapollo-react/srcmakes apollo-wind’s internal build entry point depend on a sibling package and will pull apollo-react-only utilities into@uipath/apollo-wind/styles.css, increasing bundle size and coupling builds across packages. Prefer a Storybook-only Tailwind entry/config that adds the extra scan paths without changing apollo-wind’s library build input.
/* Storybook (apps/storybook/.storybook/preview.tsx) imports this file
* directly, making it the Tailwind entry point for the whole app — including
* apollo-react's canvas stories. Without this, utility classes unique to
* apollo-react (not already used somewhere in apollo-wind) silently produce
* no CSS, since they'd never be scanned otherwise. */
@source "../../../apollo-react/src/**/*.{js,jsx,ts,tsx}";
packages/apollo-react/src/canvas/components/NodePropertyPanel/LockableValueField.tsx:236
- When
fieldType === 'boolean'andvalueis empty,lockedDisplayValuecurrently becomes "False", which prevents the placeholder from showing and misrepresents an unset value. Treat unknown/empty values as empty string so the placeholder can appear.
fieldType === 'boolean'
? value === 'true'
? 'True'
: 'False'
: fieldType === 'date'
packages/apollo-react/src/canvas/components/NodePropertyPanel/LockableValueField.tsx:259
- The default label uses
FIELD_LABEL[mode], buteffectiveModeforces'fixed'for types that don’t support expressions. If a consumer passesmode="expression"with a non-expression type, the label becomes misleading. UseeffectiveModefor the default label.
{label ?? (
<Label htmlFor={id} className="text-xs font-medium text-foreground-muted">
{FIELD_LABEL[mode]}
{required && <span className="ml-0.5 text-destructive">*</span>}
</Label>
packages/apollo-react/src/canvas/components/NodePropertyPanel/index.ts:12
DEMO_SELECT_OPTIONSis explicitly a demo placeholder (per the component doc comment) but is re-exported from the public NodePropertyPanel barrel, which effectively makes it part of the public API surface. Consider keeping it internal (or story-only) and only exporting the component/types (and optionally stable metadata likeFIELD_TYPE_META).
export {
DEMO_SELECT_OPTIONS,
FIELD_TYPE_META,
LockableValueField,
parseListValue,
packages/apollo-react/src/canvas/components/NodePropertyPanel/LockableValueField.tsx:207
LockableValueFieldis a new exported canvas component, but there are no unit tests covering its core behaviors (locked vs unlocked rendering, mode switching, boolean/date rendering, etc.). Since this package already has Vitest coverage forNodePropertyPanelin the same directory, adding a focused test suite for this component would help prevent regressions.
export function LockableValueField({
value = '',
onValueChange,
locked = true,
onLockedChange,
| size: { | ||
| default: 'h-5 w-5 data-[state=checked]:translate-x-5', | ||
| sm: 'h-4 w-4 data-[state=checked]:translate-x-4', | ||
| sm: 'size-3 data-[state=checked]:translate-x-[calc(100%-2px)]', | ||
| }, |
| <TabsTrigger value="error-handling" className={TAB_TRIGGER_CLASS}> | ||
| Branching | ||
| </TabsTrigger> | ||
| <TabsTrigger value="advanced" className={TAB_TRIGGER_CLASS}> | ||
| Error handling | ||
| </TabsTrigger> |
| <TabsContent value="error-handling" className="mt-0" /> | ||
| <TabsContent value="advanced" className="mt-0" /> |
| <TabsTrigger value="error-handling" className={TAB_TRIGGER_CLASS}> | ||
| Branching | ||
| </TabsTrigger> | ||
| <TabsTrigger value="advanced" className={TAB_TRIGGER_CLASS}> | ||
| Error handling | ||
| </TabsTrigger> |
| <TabsContent value="error-handling" className="mt-0" /> | ||
| <TabsContent value="advanced" className="mt-0" /> |
| "@storybook/react": "^10.4.1", | ||
| "@storybook/react-vite": "^10.4.1", | ||
| "@tailwindcss/vite": "^4.2.2", | ||
| "@tailwindcss/vite": "4.3.0", |
… type icons Coworker feedback round: - Add field now adds a string field directly on click instead of opening a popover menu. - Add button moved out of that menu into its own ghost + icon button, placed to the right of the Approve/Cancel row. - New buttons default to the label "Button" instead of "New button". - The Editable/Read-only lock toggle no longer opens a dropdown -- clicking the icon flips the state directly, with a tooltip naming the current state instead of the old submenu subtext. - String's field-type icon switched to ALargeSmall (Aa) so it no longer collides with the Fixed/Expression toggle's Type (T) icon when a field is a string in Fixed mode. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 8 changed files in this pull request and generated no new comments.
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
Comments suppressed due to low confidence (7)
packages/apollo-react/src/canvas/components/NodePropertyPanel/LockableValueField.tsx:258
- The default label uses
modeeven when the selectedfieldTypedoes not support expressions. This contradicts the prop contract (“Ignored for types that don't support expressions”) and can display “Write a string expression” while the field is forced into fixed mode.
{FIELD_LABEL[mode]}
packages/apollo-react/src/canvas/components/NodePropertyPanel/LockableValueField.tsx:236
- Locked boolean fields currently render "False" when the value is empty/unset. That makes an unset value indistinguishable from an explicit false and also conflicts with the placeholder behavior used by the other types.
fieldType === 'boolean'
? value === 'true'
? 'True'
: 'False'
packages/apollo-react/src/canvas/components/NodePropertyPanel/LockableValueField.tsx:474
- The boolean control inside
InputGroupdoesn't setdata-slot="input-group-control", soInputGroupcan't apply its focus ring/disabled styling (it keys off[data-slot=input-group-control]in apollo-wind'sInputGroup).
<Switch
id={id}
checked={value === 'true'}
onCheckedChange={(checked) => onValueChange?.(String(checked))}
/>
packages/apollo-react/src/canvas/components/NodePropertyPanel/index.ts:13
DEMO_SELECT_OPTIONSis exported from the public barrel, which effectively makes a demo placeholder part of the package API surface. This is hard to support long-term and makes it easy for consumers to depend on non-production data.
export {
DEMO_SELECT_OPTIONS,
FIELD_TYPE_META,
LockableValueField,
parseListValue,
} from './LockableValueField';
packages/apollo-wind/src/styles/tailwind.css:20
- Adding
@sourceforapollo-react/srcin apollo-wind’s internal Tailwind entry point will causepackages/apollo-wind’s build (build:csscompilesdist/styles.cssfrom this file) to include utility classes used only by apollo-react stories. That couples apollo-wind’s shipped CSS size/content to apollo-react changes and can significantly bloat the distributedstyles.css. Prefer keeping this file scoped to apollo-wind and instead add a Storybook-only Tailwind entry (or additional@sourcein Storybook’s own CSS) for scanning apollo-react.
/* Storybook (apps/storybook/.storybook/preview.tsx) imports this file
* directly, making it the Tailwind entry point for the whole app — including
* apollo-react's canvas stories. Without this, utility classes unique to
* apollo-react (not already used somewhere in apollo-wind) silently produce
* no CSS, since they'd never be scanned otherwise. */
@source "../../../apollo-react/src/**/*.{js,jsx,ts,tsx}";
packages/apollo-react/src/canvas/components/NodePropertyPanel/NodePropertyPanel.stories.tsx:3096
- In the JSON editor, any JSON array is accepted and assigned directly to
cases. If an element is missing required fields (e.g.id,title,fieldType) or has invalid values, the story can crash later when rendering (e.g. indexingFIELD_TYPE_METAwith an invalidfieldType).
}
setCases(parsed);
setJsonError(null);
packages/apollo-react/src/canvas/components/NodePropertyPanel/LockableValueField.tsx:204
LockableValueFieldintroduces non-trivial behavior (mode/type switching, locked read-only rendering, list parsing) but currently has no dedicated unit tests. Since this folder already has Vitest coverage forNodePropertyPanel, adding a small test suite here would help prevent regressions (e.g. effectiveMode behavior for non-expression field types, locked display values).
export function LockableValueField({
…rison Swapped lucide's Lock/LockOpen for raw Material Symbols Outlined glyphs (FILL 0, wght 400, GRAD 0, opsz 24) on the lock toggle, unmodified, so we can judge the actual Material look side-by-side against the rest of the lucide-based icon set before deciding whether to keep it, redraw it in lucide's stroke style, or revert. Not a final decision either way -- waiting on feedback. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 8 changed files in this pull request and generated 5 comments.
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
Comments suppressed due to low confidence (8)
packages/apollo-react/src/canvas/components/NodePropertyPanel/LockableValueField.tsx:302
- The default label is driven by
mode, but for field types that don't support expressionseffectiveModeforces'fixed'. Usingmodehere can show an expression label even though the control is fixed-only.
{FIELD_LABEL[mode]}
packages/apollo-react/src/canvas/components/NodePropertyPanel/LockableValueField.tsx:280
- When locked and
fieldType === 'boolean', an empty value currently renders as "False" (because anything other than the string "true" maps to False). This prevents the placeholder from ever showing for unset boolean values.
const lockedDisplayValue =
fieldType === 'boolean'
? value === 'true'
? 'True'
: 'False'
packages/apollo-react/src/canvas/components/NodePropertyPanel/LockableValueField.tsx:288
- For locked date fields, invalid date strings will render "Invalid Date" via
toLocaleDateString. This can happen ifvalueis user-edited (e.g. JSON view in the story) or comes from an unexpected source; treat invalid dates as empty so the placeholder can render.
: fieldType === 'date'
? value
? new Date(value).toLocaleDateString(undefined, {
year: 'numeric',
month: 'long',
day: 'numeric',
})
: ''
packages/apollo-react/src/canvas/components/NodePropertyPanel/LockableValueField.tsx:533
- The date trigger formats
new Date(value)directly. Ifvalueis not a valid ISO date, this will display "Invalid Date" instead of the placeholder.
{value ? (
new Date(value).toLocaleDateString(undefined, {
year: 'numeric',
month: 'long',
day: 'numeric',
packages/apollo-react/src/canvas/components/NodePropertyPanel/index.ts:12
- The NodePropertyPanel barrel export currently re-exports
DEMO_SELECT_OPTIONS, which further exposes demo-only data as part of the public canvas components API.
export {
DEMO_SELECT_OPTIONS,
FIELD_TYPE_META,
LockableValueField,
parseListValue,
packages/apollo-react/src/canvas/components/NodePropertyPanel/NodePropertyPanel.stories.tsx:3091
- In the JSON view,
setCases(parsed)accepts any array shape. If a user edits JSON to omitid(or uses a non-number), drag-and-drop and rendering can break becauseuseSortableexpects stable ids.
const handleJsonChange = (value: string) => {
setJsonDraft(value);
try {
const parsed = JSON.parse(value);
if (!Array.isArray(parsed)) {
packages/apollo-react/src/canvas/components/NodePropertyPanel/NodePropertyPanel.stories.tsx:175
- The custom "Approve"
<button>lacks the focus-visible styling thatButtonprovides, so keyboard users won't get a visible focus indicator.
<button
type="button"
className="flex h-8 items-center gap-2 rounded-lg bg-brand px-4 text-sm font-semibold text-foreground-on-accent transition hover:bg-brand-hover"
>
<CircleCheck size={14} />
packages/apollo-react/src/canvas/components/NodePropertyPanel/LockableValueField.tsx:248
LockableValueFieldintroduces several non-trivial behaviors (mode coercion viaeffectiveMode, locked display formatting for boolean/date, multi-select JSON encoding, etc.) but currently has no unit tests alongside the existingNodePropertyPanel.test.tsx. Adding targeted tests would help prevent regressions as this prototype evolves.
export function LockableValueField({
| <PopoverContent align="start" className="w-auto p-0"> | ||
| <Calendar | ||
| mode="single" | ||
| selected={value ? new Date(value) : undefined} |
| type="button" | ||
| id={id} | ||
| data-slot="input-group-control" | ||
| className="flex h-full flex-1 items-center text-left text-sm text-foreground outline-none" | ||
| > |
| export const DEMO_SELECT_OPTIONS = [ | ||
| { label: 'Option 1', value: 'option-1' }, | ||
| { label: 'Option 2', value: 'option-2' }, | ||
| { label: 'Option 3', value: 'option-3' }, | ||
| ]; |
| <input | ||
| ref={fileInputRef} | ||
| type="file" | ||
| className="hidden" | ||
| onChange={() => {}} | ||
| /> |
| <InputGroupButton | ||
| icon | ||
| size="3xs" | ||
| onClick={() => onLockedChange?.(!locked)} | ||
| aria-label={ |
…opy action Swap the Form HITL JSON view's plain Textarea for the same Monaco editor treatment used on the Input / Output panels (syntax highlighting, same theme wiring, same rounded-card chrome), so the schema view is visually consistent across both surfaces. Stays editable (unlike Input/Output's read-only JSON tab) via a dedicated JSON_EDITOR_OPTIONS variant. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 8 changed files in this pull request and generated no new comments.
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
Comments suppressed due to low confidence (5)
packages/apollo-react/src/canvas/components/NodePropertyPanel/LockableValueField.tsx:305
- For field types that don’t support expressions,
modeis documented as ignored, but the default label still usesmode. This can show an “expression” label even though the control is forced to fixed. UseeffectiveModefor the label to keep UI consistent with the actual rendered mode.
<div className="flex items-center gap-1">
{label ?? (
<Label htmlFor={id} className="text-xs font-medium text-foreground-muted">
{FIELD_LABEL[mode]}
{required && <span className="ml-0.5 text-destructive">*</span>}
</Label>
)}
packages/apollo-react/src/canvas/components/NodePropertyPanel/LockableValueField.tsx:281
- When the field is boolean and
valueis empty, the locked display currently renders “False”, which is misleading (it reads like a real value). Consider rendering an empty string when no value is set.
fieldType === 'boolean'
? value === 'true'
? 'True'
: 'False'
: fieldType === 'date'
packages/apollo-react/src/canvas/components/NodePropertyPanel/NodePropertyPanel.stories.tsx:3280
- The logo upload hover-card says it accepts PNG/JPG/SVG, but the hidden file input doesn’t constrain file types. Add an
acceptattribute so the file picker matches the UI guidance.
<input
ref={fileInputRef}
type="file"
className="hidden"
onChange={() => {}}
packages/apollo-wind/src/styles/tailwind.css:20
src/styles/tailwind.cssis used to build the publisheddist/styles.css(seebuild:css), so adding@source ../../../apollo-react/...will pull apollo-react’s utility usage into apollo-wind’s precompiled CSS. That increases CSS size and couples apollo-wind’s build output to a sibling workspace. Prefer keeping library@sourcelimited to apollo-wind, and add a Storybook-only Tailwind entry (or move this extra@sourceinto the Storybook app’s CSS entry) so it doesn’t affect published artifacts.
* directly, making it the Tailwind entry point for the whole app — including
* apollo-react's canvas stories. Without this, utility classes unique to
* apollo-react (not already used somewhere in apollo-wind) silently produce
* no CSS, since they'd never be scanned otherwise. */
@source "../../../apollo-react/src/**/*.{js,jsx,ts,tsx}";
packages/apollo-react/src/canvas/components/NodePropertyPanel/LockableValueField.tsx:252
LockableValueFieldis a new exported component undercanvas/components/NodePropertyPanel, but there are no unit tests covering its locked/unlocked behavior, mode switching, and per-field-type rendering (boolean/date/select/multi-select/file). Since this package already has Vitest coverage for NodePropertyPanel, adding a focused test suite would help prevent regressions as the prototype evolves.
/**
* LockableValueField — a field that can be locked to read-only, typed as one
* of several data types, and (for scalar types) switched between a literal
* value and a JS expression.
*
* Prototype: the expression mode is styled as code (monospace) but does not
* yet carry real syntax highlighting or evaluation. See ExpressionField for
* the direction a full code-editing surface would take. Select/multiselect
* options are demo placeholders; file uploads aren't persisted anywhere.
*/
export function LockableValueField({
value = '',
onValueChange,
locked = true,
onLockedChange,
|
Superseded by #960, which promotes this prototype's LockableValueField into a real, tested component in apollo-wind and wires the apollo-react Form HITL panel to consume it. Closing to avoid confusion. |
Summary
Prototype for a Quick Approve HITL panel demonstrating quick-form building and review inside the Property Panel experience, built on the
InputGroupprimitive from #890.LockableValueField— a reusable field that can be locked to read-only and typed as one of several field types (String, Integer, Date, Boolean, Single select, Multiselect, File):"Form HITL" story — the quick-form builder page:
@dnd-kit, same pattern asStageNode) with aDragOverlay+ synced insertion-line indicator."Form HITL Review" story — a read-only reviewer view of the same form (Reject/Approve actions in the header), sharing the same field-rendering logic as the builder's Preview mode.
apollo-windSwitch — added a propersmsize matching shadcn/ui's actual "Small" switch spec (14×24 track, 12px thumb);defaultsize is unchanged.Notes for reviewers
user-round-arrow-lefticon isn't in the installedlucide-react@0.577.0(added in1.20.0); usedUserRoundCheckinstead rather than bumping a shared dependency across a major version for one icon.@dnd-kitdependency already used byStageNode, following the same sensor + overlay convention.Test plan
pnpm exec tsc --noEmit— cleanpnpm biome check— cleanpnpm vitest run(NodePropertyPanel, Switch) — all existing tests pass, no regressions🤖 Generated with Claude Code