Extract Variables tab JSX into WiredVariablesTabView component

Proposal #5 from docs/ARCHITECTURE.md, first slice: split one of the
three remaining inline tab bodies of WiredCreatorToolsView out into
its own file. Same approach the Settings tab has had for a while
(see WiredToolsSettingsTabView).

What moved
- 113 lines of inline JSX (the `{ activeTab === 'variables' && <div>
  ... </div> }` block) → src/components/wired-tools/WiredVariablesTabView.tsx
- The new component is a pure presentation function: 12 typed props,
  no useState, no useEffect, no event subscriptions. It receives:
    * state to render: variablesType, variablePickerDefinitions,
      selectedVariableDefinition, canVariableHighlight,
      isVariableHighlightActive, variableManageCanOpen,
      selectedVariableProperties, selectedVariableTextValues
    * actions to call: onVariablesTypeChange, onPickVariable,
      onToggleVariableHighlight, onOpenManagePanel
- The parent supplies all of them inline at the call site. The
  manage-panel open sequence (request fresh user vars + reset page +
  clear selection + show modal) is closed over into a single
  onOpenManagePanel callback, so the sub-component doesn't need to
  know about its three internal setters.

Impact
- WiredCreatorToolsView.tsx: 3901 → 3809 lines (−92 net). The file
  is still large, but one of the three big inline blocks is gone.
  Monitor (~176 lines) and Inspection (~138 lines) remain inline as
  follow-up PRs.
- The React Compiler now has a smaller file boundary for the
  Variables panel; once the other two blocks come out the parent
  module should stop being skipped for memoization.

Conscious non-goals
- No state was moved. The shared state (selectedVariableKeys,
  isVariableHighlightActive, variableManagePage, etc.) still lives
  in the parent's useState. Hoisting them to a Zustand slice would
  be a separate PR — premature here.
- No behavior change. Same renders, same handlers, same DOM.

Verification
- yarn eslint on the two touched files: 34 problems baseline,
  34 problems after the split (identical: same FC<{}>, same
  pre-existing set-state-in-effect, same react-compiler skip
  warnings on the parent module).
- yarn test: 49/49 passing.
- yarn tsc on the two files: clean.
This commit is contained in:
simoleo89
2026-05-11 16:46:48 +00:00
parent 388fb8ed34
commit 23fc302b24
2 changed files with 171 additions and 112 deletions
@@ -10,6 +10,7 @@ import { DIRECTION_NAMES, EDITABLE_FURNI_VARIABLES, EDITABLE_USER_VARIABLES, INS
import { createEmptyMonitorSnapshot, formatMonitorHistoryOccurrence, formatMonitorLatestOccurrence, formatMonitorSource, formatVariableTimestamp, getHotelDateTimeParts, getHotelTimeFormatter, normalizeMonitorReason } from './WiredCreatorTools.helpers';
import { HotelDateTimeParts, InspectionElementButton, InspectionElementType, InspectionFurniLiveState, InspectionFurniSelection, InspectionUserLiveState, InspectionUserSelection, InspectionUserTeamData, InspectionVariable, ManagedHolderVariableEntry, MonitorLog, MonitorLogDetails, MonitorSnapshot, MonitorStat, ParsedWallLocation, TeamEffectData, VariableDefinition, VariableHighlightOverlay, VariableHighlightTarget, VariableManageEntry, VariableTextValue, VariablesElementButton, VariablesElementType, WiredToolsTab } from './WiredCreatorTools.types';
import { WiredToolsSettingsTabView } from './WiredToolsSettingsTabView';
import { WiredVariablesTabView } from './WiredVariablesTabView';
export const WiredCreatorToolsView: FC<{}> = () =>
{
@@ -3387,118 +3388,26 @@ export const WiredCreatorToolsView: FC<{}> = () =>
</div>
</div> }
{ (activeTab === 'variables') &&
<div className="p-3 min-h-[360px] flex gap-4">
<div className="w-[205px] shrink-0 flex flex-col gap-3">
<div className="flex flex-col gap-1">
<Text bold>Variable type:</Text>
<div className="flex gap-1">
{ VARIABLES_ELEMENTS.map(element => (
<button
key={ element.key }
type="button"
className={ `w-[42px] h-[38px] rounded border flex items-center justify-center shadow-[inset_0_1px_0_rgba(255,255,255,.7)] ${ element.disabled ? 'border-[#b7b7b7] bg-[#e7e3da] opacity-60 cursor-not-allowed' : ((variablesType === element.key) ? 'border-[#222] bg-[#d9d6cf]' : 'border-[#7f7f7f] bg-[#ece9e1]') }` }
disabled={ element.disabled }
onClick={ () => !element.disabled && setVariablesType(element.key) }
title={ element.label }>
<img alt={ element.label } className="w-auto h-auto max-w-[22px] max-h-[22px] object-contain" src={ element.icon } />
</button>
)) }
</div>
</div>
<div className="flex flex-col gap-1 min-h-0 grow">
<Text bold>Variable picker:</Text>
<div className="grow rounded border border-[#bdb8ab] bg-white overflow-hidden">
<div className="max-h-[408px] overflow-y-auto">
<table className="w-full text-[12px]">
<tbody>
{ variablePickerDefinitions.map((variable, index) => (
<tr
key={ variable.key }
className={ `cursor-pointer ${ (selectedVariableDefinition?.key === variable.key) ? 'bg-[#d7dfea]' : ((index % 2 === 0) ? 'bg-white' : 'bg-[#f3f3f3]') } hover:bg-[#e8eefc]` }
onClick={ () => setSelectedVariableKeys(prev => ({ ...prev, [variablesType]: variable.key })) }>
<td className="px-3 py-1 text-[#444]">{ variable.key }</td>
</tr>
)) }
</tbody>
</table>
</div>
</div>
</div>
<div className="flex gap-2">
<Button
disabled={ !canVariableHighlight }
variant="secondary"
onClick={ () => setIsVariableHighlightActive(value => !value) }>
{ isVariableHighlightActive ? 'Undo' : 'Highlight' }
</Button>
<Button
disabled={ !variableManageCanOpen }
variant="secondary"
onClick={ () =>
{
requestUserVariables();
setVariableManagePage(1);
setSelectedManagedVariableEntry(null);
setIsVariableManageOpen(true);
} }>
Manage
</Button>
</div>
</div>
<div className="min-w-0 grow flex flex-col gap-3">
{ (variablesType === 'context') &&
<div className="rounded border border-[#c8b98f] bg-[#fff7df] px-3 py-2 text-[12px] text-[#6a5d33]">
Context variables live only during the current wired execution. This tab shows their definitions, text mappings and execution-scoped capabilities, but not live values from a running stack.
</div> }
<div className="flex flex-col gap-1">
<Text bold>Properties:</Text>
<div className="rounded border border-[#bdb8ab] bg-white overflow-hidden">
<div className="grid grid-cols-[1fr_120px] border-b border-[#d8d4c8] bg-[#f5f2ea] px-3 py-2 text-[12px] font-bold text-[#333]">
<span>Property</span>
<span>Value</span>
</div>
<div className="max-h-[210px] overflow-y-auto">
<table className="w-full text-[12px]">
<tbody>
{ selectedVariableProperties.map((property, index) => (
<tr key={ property.key } className={ (index % 2 === 0) ? 'bg-white' : 'bg-[#f3f3f3]' }>
<td className="px-3 py-1 text-[#444]">{ property.key }</td>
<td className="px-3 py-1 text-[#222]">{ property.value }</td>
</tr>
)) }
</tbody>
</table>
</div>
</div>
</div>
<div className="flex flex-col gap-1 min-h-0 grow">
<Text bold>Text values:</Text>
<div className="grow rounded border border-[#bdb8ab] bg-white overflow-hidden">
<div className="grid grid-cols-[120px_1fr] border-b border-[#d8d4c8] bg-[#f5f2ea] px-3 py-2 text-[12px] font-bold text-[#333]">
<span>Value</span>
<span>Text</span>
</div>
{ !selectedVariableTextValues.length &&
<div className="h-[calc(100%-37px)] flex items-center justify-center text-[#b1aca2] text-[20px]">
<Text>Nothing to display</Text>
</div> }
{ !!selectedVariableTextValues.length &&
<div className="max-h-[178px] overflow-y-auto">
<table className="w-full text-[12px]">
<tbody>
{ selectedVariableTextValues.map((entry, index) => (
<tr key={ `${ entry.value }-${ index }` } className={ (index % 2 === 0) ? 'bg-white' : 'bg-[#f3f3f3]' }>
<td className="px-3 py-1 text-[#444]">{ entry.value }</td>
<td className="px-3 py-1 text-[#222]">{ entry.text }</td>
</tr>
)) }
</tbody>
</table>
</div> }
</div>
</div>
</div>
</div> }
<WiredVariablesTabView
variablesType={ variablesType }
onVariablesTypeChange={ setVariablesType }
variablePickerDefinitions={ variablePickerDefinitions }
selectedVariableDefinition={ selectedVariableDefinition }
onPickVariable={ key => setSelectedVariableKeys(prev => ({ ...prev, [variablesType]: key })) }
canVariableHighlight={ canVariableHighlight }
isVariableHighlightActive={ isVariableHighlightActive }
onToggleVariableHighlight={ () => setIsVariableHighlightActive(value => !value) }
variableManageCanOpen={ variableManageCanOpen }
onOpenManagePanel={ () =>
{
requestUserVariables();
setVariableManagePage(1);
setSelectedManagedVariableEntry(null);
setIsVariableManageOpen(true);
} }
selectedVariableProperties={ selectedVariableProperties }
selectedVariableTextValues={ selectedVariableTextValues }
/> }
{ (activeTab === 'settings') && <WiredToolsSettingsTabView /> }
{ (activeTab !== 'monitor') &&
(activeTab !== 'inspection') &&
@@ -0,0 +1,150 @@
import { FC } from 'react';
import { Button, Text } from '../../common';
import { VARIABLES_ELEMENTS } from './WiredCreatorTools.constants';
import { VariableDefinition, VariablesElementType, VariableTextValue } from './WiredCreatorTools.types';
export interface WiredVariablesTabViewProps
{
variablesType: VariablesElementType;
onVariablesTypeChange: (next: VariablesElementType) => void;
variablePickerDefinitions: VariableDefinition[];
selectedVariableDefinition: VariableDefinition | null;
onPickVariable: (key: string) => void;
canVariableHighlight: boolean;
isVariableHighlightActive: boolean;
onToggleVariableHighlight: () => void;
variableManageCanOpen: boolean;
onOpenManagePanel: () => void;
selectedVariableProperties: { key: string; value: string; }[];
selectedVariableTextValues: VariableTextValue[];
}
/**
* The "Variables" tab body of WiredCreatorToolsView. Extracted so the
* parent module no longer carries 110 lines of inline JSX. Pure
* presentation: every piece of state and every callback is supplied as
* a prop, so this component is trivially memoizable and (eventually)
* testable in isolation.
*/
export const WiredVariablesTabView: FC<WiredVariablesTabViewProps> = ({
variablesType,
onVariablesTypeChange,
variablePickerDefinitions,
selectedVariableDefinition,
onPickVariable,
canVariableHighlight,
isVariableHighlightActive,
onToggleVariableHighlight,
variableManageCanOpen,
onOpenManagePanel,
selectedVariableProperties,
selectedVariableTextValues
}) =>
(
<div className="p-3 min-h-[360px] flex gap-4">
<div className="w-[205px] shrink-0 flex flex-col gap-3">
<div className="flex flex-col gap-1">
<Text bold>Variable type:</Text>
<div className="flex gap-1">
{ VARIABLES_ELEMENTS.map(element => (
<button
key={ element.key }
type="button"
className={ `w-[42px] h-[38px] rounded border flex items-center justify-center shadow-[inset_0_1px_0_rgba(255,255,255,.7)] ${ element.disabled ? 'border-[#b7b7b7] bg-[#e7e3da] opacity-60 cursor-not-allowed' : ((variablesType === element.key) ? 'border-[#222] bg-[#d9d6cf]' : 'border-[#7f7f7f] bg-[#ece9e1]') }` }
disabled={ element.disabled }
onClick={ () => !element.disabled && onVariablesTypeChange(element.key) }
title={ element.label }>
<img alt={ element.label } className="w-auto h-auto max-w-[22px] max-h-[22px] object-contain" src={ element.icon } />
</button>
)) }
</div>
</div>
<div className="flex flex-col gap-1 min-h-0 grow">
<Text bold>Variable picker:</Text>
<div className="grow rounded border border-[#bdb8ab] bg-white overflow-hidden">
<div className="max-h-[408px] overflow-y-auto">
<table className="w-full text-[12px]">
<tbody>
{ variablePickerDefinitions.map((variable, index) => (
<tr
key={ variable.key }
className={ `cursor-pointer ${ (selectedVariableDefinition?.key === variable.key) ? 'bg-[#d7dfea]' : ((index % 2 === 0) ? 'bg-white' : 'bg-[#f3f3f3]') } hover:bg-[#e8eefc]` }
onClick={ () => onPickVariable(variable.key) }>
<td className="px-3 py-1 text-[#444]">{ variable.key }</td>
</tr>
)) }
</tbody>
</table>
</div>
</div>
</div>
<div className="flex gap-2">
<Button
disabled={ !canVariableHighlight }
variant="secondary"
onClick={ onToggleVariableHighlight }>
{ isVariableHighlightActive ? 'Undo' : 'Highlight' }
</Button>
<Button
disabled={ !variableManageCanOpen }
variant="secondary"
onClick={ onOpenManagePanel }>
Manage
</Button>
</div>
</div>
<div className="min-w-0 grow flex flex-col gap-3">
{ (variablesType === 'context') &&
<div className="rounded border border-[#c8b98f] bg-[#fff7df] px-3 py-2 text-[12px] text-[#6a5d33]">
Context variables live only during the current wired execution. This tab shows their definitions, text mappings and execution-scoped capabilities, but not live values from a running stack.
</div> }
<div className="flex flex-col gap-1">
<Text bold>Properties:</Text>
<div className="rounded border border-[#bdb8ab] bg-white overflow-hidden">
<div className="grid grid-cols-[1fr_120px] border-b border-[#d8d4c8] bg-[#f5f2ea] px-3 py-2 text-[12px] font-bold text-[#333]">
<span>Property</span>
<span>Value</span>
</div>
<div className="max-h-[210px] overflow-y-auto">
<table className="w-full text-[12px]">
<tbody>
{ selectedVariableProperties.map((property, index) => (
<tr key={ property.key } className={ (index % 2 === 0) ? 'bg-white' : 'bg-[#f3f3f3]' }>
<td className="px-3 py-1 text-[#444]">{ property.key }</td>
<td className="px-3 py-1 text-[#222]">{ property.value }</td>
</tr>
)) }
</tbody>
</table>
</div>
</div>
</div>
<div className="flex flex-col gap-1 min-h-0 grow">
<Text bold>Text values:</Text>
<div className="grow rounded border border-[#bdb8ab] bg-white overflow-hidden">
<div className="grid grid-cols-[120px_1fr] border-b border-[#d8d4c8] bg-[#f5f2ea] px-3 py-2 text-[12px] font-bold text-[#333]">
<span>Value</span>
<span>Text</span>
</div>
{ !selectedVariableTextValues.length &&
<div className="h-[calc(100%-37px)] flex items-center justify-center text-[#b1aca2] text-[20px]">
<Text>Nothing to display</Text>
</div> }
{ !!selectedVariableTextValues.length &&
<div className="max-h-[178px] overflow-y-auto">
<table className="w-full text-[12px]">
<tbody>
{ selectedVariableTextValues.map((entry, index) => (
<tr key={ `${ entry.value }-${ index }` } className={ (index % 2 === 0) ? 'bg-white' : 'bg-[#f3f3f3]' }>
<td className="px-3 py-1 text-[#444]">{ entry.value }</td>
<td className="px-3 py-1 text-[#222]">{ entry.text }</td>
</tr>
)) }
</tbody>
</table>
</div> }
</div>
</div>
</div>
</div>
);