From a391cb0f316cbc39113ef1b80a70fde6f40581c9 Mon Sep 17 00:00:00 2001 From: Aymeric Rabot Date: Mon, 12 Oct 2026 16:04:23 -0400 Subject: [PATCH 1/8] feat(editor): the host owns the interface theme The editor forced its chrome dark: a mount effect put `dark` on and the v1, v2, mobile, sidebar, mobile-sheet and riser roots carried it too, so a host's light theme never reached the editor or its portals. The editor now sets no theme; it resolves against the host's `.dark` ancestor. Every dark-only chrome literal gets a light base with the original kept under `dark:` (bg-[#2C2C2E] -> bg-black/4 dark:bg-[#2C2C2E], white tints -> black tints, 300/400 inks -> 600/700), so dark renders as before. Where a class is merged with a caller's override, the pair is arranged so the override still wins: ActionButton's fill runs through custom properties, ghost-button callers leave the dark hover to the variant, and the mobile delete hover and disabled segments carry their own dark twin. The rail's selection uses sidebar-accent, which equals accent in dark and reads on the light sidebar. Presentation HUDs keep `dark` (Preview stage, walkthrough, viewer controls and now their dropdown menus), as do the scene-graph viewer and the self-dark chips. The open-source app puts `dark` on . Co-Authored-By: Claude Opus 5.5 --- apps/editor/app/layout.tsx | 2 +- .../editor/capture-level-picker.tsx | 4 +- .../editor/editor-layout-mobile.tsx | 2 +- .../components/editor/editor-layout-v2.tsx | 2 +- .../editor/floorplan-mode-coordinator.tsx | 4 +- .../src/components/editor/floorplan-panel.tsx | 6 +- .../editor/src/components/editor/index.tsx | 9 +-- .../components/editor/riser-diagram-panel.tsx | 4 +- .../src/components/editor/views/view-bar.tsx | 7 +- .../ui/action-menu/camera-actions.tsx | 6 +- .../ui/action-menu/control-modes.tsx | 12 +-- .../ui/action-menu/measurement-control.tsx | 16 ++-- .../ui/action-menu/view-toggles.tsx | 76 +++++++++---------- .../components/ui/controls/action-button.tsx | 5 +- .../components/ui/controls/metric-control.tsx | 2 +- .../ui/controls/segmented-control.tsx | 9 ++- .../components/ui/controls/select-control.tsx | 2 +- .../components/ui/controls/slider-control.tsx | 2 +- .../components/ui/controls/toggle-control.tsx | 6 +- .../components/ui/floating-level-selector.tsx | 22 +++--- .../ui/helpers/contextual-helper-panel.tsx | 8 +- .../collections/collections-popover.tsx | 16 ++-- .../ui/panels/mobile-panel-sheet.tsx | 4 +- .../ui/panels/mobile-selection-bar.tsx | 9 ++- .../ui/panels/multi-selection-panel.tsx | 4 +- .../components/ui/panels/panel-wrapper.tsx | 12 +-- .../ui/panels/parametric-field-control.tsx | 4 +- .../ui/panels/parametric-inspector.tsx | 6 +- .../src/components/ui/sidebar/app-sidebar.tsx | 2 +- .../src/components/ui/sidebar/icon-rail.tsx | 6 +- .../ui/sidebar/panels/items-panel/index.tsx | 12 +-- .../sidebar/panels/settings-panel/index.tsx | 2 +- .../ui/sidebar/panels/site-panel/index.tsx | 4 +- .../ui/sidebar/panels/zone-panel/index.tsx | 4 +- .../src/components/ui/sidebar/tab-bar.tsx | 8 +- .../components/viewer/viewer-controls-bar.tsx | 10 +-- packages/nodes/src/block/panel.tsx | 12 +-- packages/nodes/src/box-vent/panel.tsx | 2 +- .../nodes/src/cabinet/compartment-card.tsx | 20 ++--- packages/nodes/src/cabinet/panel.tsx | 12 +-- packages/nodes/src/cabinet/run-panel.tsx | 8 +- packages/nodes/src/ceiling/panel.tsx | 10 +-- packages/nodes/src/chimney/panel.tsx | 4 +- packages/nodes/src/column/panel.tsx | 12 +-- .../src/construction-dimension/panel.tsx | 2 +- packages/nodes/src/cupola/panel.tsx | 2 +- packages/nodes/src/door/panel.tsx | 14 ++-- .../src/dormer/panel-actions-section.tsx | 2 +- .../src/dormer/panel-windows-section.tsx | 8 +- packages/nodes/src/dormer/panel.tsx | 6 +- packages/nodes/src/elevator/panel.tsx | 24 +++--- packages/nodes/src/eyebrow-vent/panel.tsx | 2 +- .../nodes/src/fence-feature/inspector.tsx | 4 +- .../nodes/src/gutter/downspouts-panel.tsx | 6 +- packages/nodes/src/item/panel.tsx | 10 +-- packages/nodes/src/ridge-vent/panel.tsx | 2 +- packages/nodes/src/roof-segment/panel.tsx | 2 +- packages/nodes/src/roof/panel.tsx | 16 ++-- packages/nodes/src/shared/panel-fields.tsx | 6 +- packages/nodes/src/skylight/panel.tsx | 4 +- packages/nodes/src/slab/panel.tsx | 12 +-- packages/nodes/src/solar-panel/panel.tsx | 8 +- packages/nodes/src/spawn/panel.tsx | 2 +- packages/nodes/src/stair-segment/panel.tsx | 2 +- packages/nodes/src/stair/panel.tsx | 8 +- packages/nodes/src/turbine-vent/panel.tsx | 2 +- packages/nodes/src/unit/unit-panel.tsx | 8 +- packages/nodes/src/window/panel.tsx | 6 +- wiki/architecture/materials-and-themes.md | 2 +- 69 files changed, 279 insertions(+), 270 deletions(-) diff --git a/apps/editor/app/layout.tsx b/apps/editor/app/layout.tsx index 01965f4ba3..fb48d7f0b6 100644 --- a/apps/editor/app/layout.tsx +++ b/apps/editor/app/layout.tsx @@ -31,7 +31,7 @@ export default function RootLayout({ return ( diff --git a/packages/editor/src/components/editor/capture-level-picker.tsx b/packages/editor/src/components/editor/capture-level-picker.tsx index 9a54de3e2e..630d8a2f07 100644 --- a/packages/editor/src/components/editor/capture-level-picker.tsx +++ b/packages/editor/src/components/editor/capture-level-picker.tsx @@ -10,8 +10,8 @@ const rowClass = (selected: boolean) => cn( 'flex w-full min-w-0 items-center rounded-lg px-2.5 py-1.5 text-left font-medium text-xs transition-colors', selected - ? 'bg-white/10 text-foreground' - : 'text-muted-foreground/70 hover:bg-white/5 hover:text-muted-foreground', + ? 'bg-black/6 dark:bg-white/10 text-foreground' + : 'text-muted-foreground/70 hover:bg-black/4 dark:hover:bg-white/5 hover:text-muted-foreground', ) /** diff --git a/packages/editor/src/components/editor/editor-layout-mobile.tsx b/packages/editor/src/components/editor/editor-layout-mobile.tsx index f803363b62..9e952dd24f 100644 --- a/packages/editor/src/components/editor/editor-layout-mobile.tsx +++ b/packages/editor/src/components/editor/editor-layout-mobile.tsx @@ -214,7 +214,7 @@ export function EditorLayoutMobile({ }, [panelSheetHeight, committedSheetH]) return ( -
+
{navbarSlot}
+
{/* Top navbar */} {navbarSlot} diff --git a/packages/editor/src/components/editor/floorplan-mode-coordinator.tsx b/packages/editor/src/components/editor/floorplan-mode-coordinator.tsx index bb8a213e10..1915bd5ace 100644 --- a/packages/editor/src/components/editor/floorplan-mode-coordinator.tsx +++ b/packages/editor/src/components/editor/floorplan-mode-coordinator.tsx @@ -59,7 +59,7 @@ export function FloorplanModeCoordinator() { {notice.message} {notice.kind === 'switch-to-expert' ? (
@@ -236,11 +236,11 @@ function GuidesControl() { 'flex h-11 w-6 items-center justify-center rounded-r-lg transition-colors', showGuides ? isOpen - ? 'bg-white/10' - : 'bg-white/5 hover:bg-white/8' + ? 'bg-black/6 dark:bg-white/10' + : 'bg-black/4 dark:bg-white/5 hover:bg-black/5 dark:hover:bg-white/8' : isOpen - ? 'bg-white/8' - : 'opacity-60 hover:bg-white/5 hover:opacity-100', + ? 'bg-black/5 dark:bg-white/8' + : 'opacity-60 hover:bg-black/4 dark:hover:bg-white/5 hover:opacity-100', )} type="button" > @@ -284,7 +284,7 @@ function GuidesControl() { className={cn( 'group/item space-y-2 rounded-xl border bg-background/75 p-2.5 transition-colors', selectedReferenceId === guide.id - ? 'border-foreground/35 bg-white/10' + ? 'border-foreground/35 bg-black/6 dark:bg-white/10' : 'border-border/45', )} key={guide.id} @@ -386,8 +386,8 @@ function ScansControl() { className={cn( 'rounded-r-none p-0', showScans - ? 'bg-white/15' - : 'opacity-60 grayscale hover:bg-white/5 hover:opacity-100 hover:grayscale-0', + ? 'bg-black/9 hover:bg-black/9 dark:bg-white/15' + : 'opacity-60 grayscale hover:bg-black/4 hover:opacity-100 hover:grayscale-0', )} label={`Scans: ${showScans ? 'Visible' : 'Hidden'}`} onClick={() => setShowScans(!showScans)} @@ -396,7 +396,7 @@ function ScansControl() { >
Scans - + {scans.length}
@@ -411,11 +411,11 @@ function ScansControl() { 'flex h-11 w-6 items-center justify-center rounded-r-lg transition-colors', showScans ? isOpen - ? 'bg-white/10' - : 'bg-white/5 hover:bg-white/8' + ? 'bg-black/6 dark:bg-white/10' + : 'bg-black/4 dark:bg-white/5 hover:bg-black/5 dark:hover:bg-white/8' : isOpen - ? 'bg-white/8' - : 'opacity-60 hover:bg-white/5 hover:opacity-100', + ? 'bg-black/5 dark:bg-white/8' + : 'opacity-60 hover:bg-black/4 dark:hover:bg-white/5 hover:opacity-100', )} type="button" > @@ -459,7 +459,7 @@ function ScansControl() { className={cn( 'group/item space-y-2 rounded-xl border bg-background/75 p-2.5 transition-colors', selectedReferenceId === scan.id - ? 'border-foreground/35 bg-white/10' + ? 'border-foreground/35 bg-black/6 dark:bg-white/10' : 'border-border/45', )} key={scan.id} @@ -578,7 +578,7 @@ function ReferenceListSection({
@@ -710,11 +710,11 @@ function ReferencesControl() { 'flex h-11 w-6 items-center justify-center rounded-r-lg transition-colors', anyVisible ? isOpen - ? 'bg-white/10' - : 'bg-white/5 hover:bg-white/8' + ? 'bg-black/6 dark:bg-white/10' + : 'bg-black/4 dark:bg-white/5 hover:bg-black/5 dark:hover:bg-white/8' : isOpen - ? 'bg-white/8' - : 'opacity-60 hover:bg-white/5 hover:opacity-100', + ? 'bg-black/5 dark:bg-white/8' + : 'opacity-60 hover:bg-black/4 dark:hover:bg-white/5 hover:opacity-100', )} type="button" > @@ -784,8 +784,8 @@ function ReferenceFloorControl() { className={cn( 'rounded-r-none p-0', showReferenceFloor && selectedLevel - ? 'bg-white/15' - : 'opacity-60 grayscale hover:bg-white/5 hover:opacity-100 hover:grayscale-0', + ? 'bg-black/9 hover:bg-black/9 dark:bg-white/15' + : 'opacity-60 grayscale hover:bg-black/4 hover:opacity-100 hover:grayscale-0', )} disabled={!hasLowerLevels} label={ @@ -801,7 +801,7 @@ function ReferenceFloorControl() { >
- + {lowerLevels.length}
@@ -815,11 +815,11 @@ function ReferenceFloorControl() { 'flex h-11 w-6 items-center justify-center rounded-r-lg transition-colors', showReferenceFloor && selectedLevel ? isOpen - ? 'bg-white/10' - : 'bg-white/5 hover:bg-white/8' + ? 'bg-black/6 dark:bg-white/10' + : 'bg-black/4 dark:bg-white/5 hover:bg-black/5 dark:hover:bg-white/8' : isOpen - ? 'bg-white/8' - : 'opacity-60 hover:bg-white/5 hover:opacity-100', + ? 'bg-black/5 dark:bg-white/8' + : 'opacity-60 hover:bg-black/4 dark:hover:bg-white/5 hover:opacity-100', )} disabled={!hasLowerLevels} type="button" @@ -848,7 +848,7 @@ function ReferenceFloorControl() {
{onPaste && ( )} {onClose && ( - + Scene Graph
3 - ? 'text-muted-foreground hover:bg-red-500/20 hover:text-red-400' + ? 'text-muted-foreground hover:bg-red-500/20 hover:text-red-600 dark:hover:text-red-400' : 'cursor-not-allowed text-muted-foreground/30', )} disabled={points.length <= 3} diff --git a/packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx b/packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx index 914bb85dac..71f81b0589 100644 --- a/packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx +++ b/packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx @@ -195,14 +195,14 @@ export function ZonePanel() { /> } + icon={} label="Delete" onClick={() => deleteSelectedZone(false)} type="button" /> } + icon={} label="Delete with contents" onClick={() => deleteSelectedZone(true)} type="button" diff --git a/packages/editor/src/components/ui/sidebar/tab-bar.tsx b/packages/editor/src/components/ui/sidebar/tab-bar.tsx index e65caa5ddc..89f7c2352d 100644 --- a/packages/editor/src/components/ui/sidebar/tab-bar.tsx +++ b/packages/editor/src/components/ui/sidebar/tab-bar.tsx @@ -47,8 +47,8 @@ export function TabBar({ tabs, activeTab, onTabChange }: TabBarProps) { className={cn( 'relative h-7 rounded-md px-3 font-medium text-sm transition-colors', isActive - ? 'bg-accent text-foreground' - : 'text-muted-foreground hover:bg-accent/50 hover:text-foreground', + ? 'bg-sidebar-accent text-foreground' + : 'text-muted-foreground hover:bg-sidebar-accent/50 hover:text-foreground', )} key={tab.id} onClick={() => { @@ -101,8 +101,8 @@ export function IconRail({ tabs, activeTab, collapsed, onIconClick }: IconRailPr className={cn( 'group flex h-11 w-11 items-center justify-center rounded-xl transition-all duration-200 [&_img]:transition-[opacity,filter] [&_img]:duration-200', showActive - ? 'bg-accent text-foreground shadow-sm [&_img]:opacity-100 [&_img]:grayscale-0' - : 'text-muted-foreground hover:bg-accent/50 hover:text-foreground [&_img]:opacity-60 [&_img]:grayscale hover:[&_img]:opacity-100 hover:[&_img]:grayscale-0', + ? 'bg-sidebar-accent text-foreground shadow-sm [&_img]:opacity-100 [&_img]:grayscale-0' + : 'text-muted-foreground hover:bg-sidebar-accent/50 hover:text-foreground [&_img]:opacity-60 [&_img]:grayscale hover:[&_img]:opacity-100 hover:[&_img]:grayscale-0', )} onClick={() => { triggerSFX('sfx:menu-click') diff --git a/packages/editor/src/components/viewer/viewer-controls-bar.tsx b/packages/editor/src/components/viewer/viewer-controls-bar.tsx index 732aa6d718..fff4a12fcb 100644 --- a/packages/editor/src/components/viewer/viewer-controls-bar.tsx +++ b/packages/editor/src/components/viewer/viewer-controls-bar.tsx @@ -110,7 +110,7 @@ function VisibilityMenu({ - + {canShowScans && ( keepOpen(e, () => useViewer.getState().setShowScans(!showScans))} @@ -167,7 +167,7 @@ function DisplayMenu() { - + keepOpen(e, () => useViewer.getState().setShadows(!shadows))} > @@ -208,7 +208,7 @@ function DisplayMenu() { Render {activeShading.name} - + {SHADING_OPTIONS.map((option) => { const OptionIcon = option.icon return ( @@ -238,7 +238,7 @@ function DisplayMenu() { {activeTheme.name} - + {SCENE_THEMES.map((t) => { const swatches = (['wall', 'roof', 'floor', 'glazing'] as const).map( (role) => t.clayTints?.[role] ?? CLAY_PALETTE[role], @@ -271,7 +271,7 @@ function DisplayMenu() { Edges {activeEdges.name} - + {EDGE_OPTIONS.map((option) => ( ) : null} -
+
{slotDeclarations.map((slot, index) => { const ref = node.slots?.[slot.slotId] const active = @@ -334,7 +334,7 @@ export default function BlockPanel() {
0 ? 'border-t' : '' - } ${active ? 'bg-primary/15' : 'hover:bg-white/[0.035]'}`} + } ${active ? 'bg-primary/15' : 'hover:bg-black/4 dark:hover:bg-white/[0.035]'}`} key={slot.slotId} >
handleServiceBoundaryChange('toLevelId', event.target.value)} value={toLevelId} > @@ -600,7 +600,7 @@ export default function ElevatorPanel() { Default Floor
handleUpdate({ shaftStyle: event.target.value as ElevatorNode['shaftStyle'] }) } @@ -715,7 +715,7 @@ export default function ElevatorPanel() { Opening Style
handleUpdate({ doorPanelStyle: event.target.value as ElevatorNode['doorPanelStyle'], @@ -782,7 +782,7 @@ export default function ElevatorPanel() { return (
{getLevelDisplayName(level)} @@ -791,7 +791,7 @@ export default function ElevatorPanel() { className={`rounded-md border px-2 py-1 text-[11px] transition-colors ${ isServiceOnly ? 'border-sky-300/45 bg-sky-400/15 text-sky-100' - : 'border-border/50 bg-black/15 text-muted-foreground hover:text-foreground' + : 'border-border/50 bg-black/4 dark:bg-black/15 text-muted-foreground hover:text-foreground' } ${isDisabled ? 'cursor-not-allowed opacity-45' : ''}`} disabled={isDisabled} onClick={() => toggleLevelAccess('serviceOnlyLevelIds', level.id)} @@ -803,7 +803,7 @@ export default function ElevatorPanel() { className={`rounded-md border px-2 py-1 text-[11px] transition-colors ${ isDisabled ? 'border-red-300/45 bg-red-400/15 text-red-100' - : 'border-border/50 bg-black/15 text-muted-foreground hover:text-foreground' + : 'border-border/50 bg-black/4 dark:bg-black/15 text-muted-foreground hover:text-foreground' }`} onClick={() => toggleLevelAccess('disabledLevelIds', level.id)} type="button" @@ -828,10 +828,10 @@ export default function ElevatorPanel() {