From 52674dfd86903b5b43f0e6fbdcb048cea6c74b29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Sat, 9 May 2026 18:41:58 +0000 Subject: [PATCH] fix(studio): stale selection, rotation control, design panel polish MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes and improvements based on power-user testing feedback: 1. Fix stale selection after style edits — handleDomStyleCommit now calls refreshDomEditSelectionFromPreview after persisting, matching every other commit handler. Without this, the PropertyPanel showed frozen computedStyles after color/radius/shadow edits, making it look like editing "didn't work." Also adds error handling around the persist call. 2. Add rotation field to the Design panel Layout section — reads the current rotation angle from the manual edit manifest and commits via the existing handleDomRotationCommit handler. 3. Enable motion panel by default — STUDIO_MOTION_PANEL_ENABLED now defaults to true so the Motion tab is discoverable without env vars. 4. Color controls only when element has color — fill color section now only shows when the element has an explicit non-transparent background-color. Text color shows only when the element has a color style. Prevents showing color pickers on elements where color edits have no visible effect. 5. Exclude canvas from selection — added "canvas" to DOM_LAYER_IGNORED_TAGS so canvas elements are not selectable in the preview or listed in the layer panel. 6. Multi-selection feedback — shows "N elements selected" with guidance instead of the generic empty state when multiple elements are selected. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/studio/src/App.tsx | 29 +++++++--- .../src/components/editor/PropertyPanel.tsx | 57 ++++++++++++++++--- .../src/components/editor/domEditing.ts | 1 + .../editor/manualEditingAvailability.test.ts | 4 +- .../editor/manualEditingAvailability.ts | 2 +- 5 files changed, 73 insertions(+), 20 deletions(-) diff --git a/packages/studio/src/App.tsx b/packages/studio/src/App.tsx index 6a825bbe94..6e455cb4b3 100644 --- a/packages/studio/src/App.tsx +++ b/packages/studio/src/App.tsx @@ -2933,15 +2933,26 @@ export function StudioApp() { buildDomEditStylePatchOperation("background-size", "contain"), ); } - await persistDomEditOperations(domEditSelection, operations, { - label: "Edit layer style", - skipRefresh: true, - prepareContent: importedFont - ? (html, sourceFile) => ensureImportedFontFace(html, importedFont, sourceFile) - : undefined, - }); + try { + await persistDomEditOperations(domEditSelection, operations, { + label: "Edit layer style", + skipRefresh: true, + prepareContent: importedFont + ? (html, sourceFile) => ensureImportedFontFace(html, importedFont, sourceFile) + : undefined, + }); + } catch (err) { + console.warn("[Studio] Style persist failed:", err instanceof Error ? err.message : err); + } + refreshDomEditSelectionFromPreview(domEditSelection); }, - [activeCompPath, domEditSelection, persistDomEditOperations, resolveImportedFontAsset], + [ + activeCompPath, + domEditSelection, + persistDomEditOperations, + refreshDomEditSelectionFromPreview, + resolveImportedFontAsset, + ], ); const handleDomTextCommit = useCallback( @@ -4197,11 +4208,13 @@ export function StudioApp() { {designPanelActive ? ( 1 ? null : domEditSelection} + multiSelectCount={domEditGroupSelections.length} copiedAgentPrompt={copiedAgentPrompt} onClearSelection={clearDomSelection} onSetStyle={handleDomStyleCommit} onSetManualOffset={handleDomPathOffsetCommit} onSetManualSize={handleDomBoxSizeCommit} + onSetRotation={handleDomRotationCommit} onSetText={handleDomTextCommit} onSetTextFieldStyle={handleDomTextFieldStyleCommit} onResetManualEdits={handleDomManualEditsReset} diff --git a/packages/studio/src/components/editor/PropertyPanel.tsx b/packages/studio/src/components/editor/PropertyPanel.tsx index 581a45d324..d153c27ed8 100644 --- a/packages/studio/src/components/editor/PropertyPanel.tsx +++ b/packages/studio/src/components/editor/PropertyPanel.tsx @@ -11,6 +11,7 @@ import { import { createPortal } from "react-dom"; import { Eye, + Layers, MessageSquare, Move, Palette, @@ -28,7 +29,7 @@ import { type ParsedColor, } from "./colorValue"; import { isTextEditableSelection, type DomEditSelection } from "./domEditing"; -import { readStudioBoxSize, readStudioPathOffset } from "./manualEdits"; +import { readStudioBoxSize, readStudioPathOffset, readStudioRotation } from "./manualEdits"; import { COMMON_LOCAL_FONT_FAMILIES, googleFontStylesheetUrl, @@ -39,11 +40,13 @@ import { resolveFloatingPanelPosition, type FloatingPosition } from "./floatingP interface PropertyPanelProps { element: DomEditSelection | null; + multiSelectCount?: number; copiedAgentPrompt: boolean; onClearSelection: () => void; onSetStyle: (prop: string, value: string) => void | Promise; onSetManualOffset: (element: DomEditSelection, next: { x: number; y: number }) => void; onSetManualSize: (element: DomEditSelection, next: { width: number; height: number }) => void; + onSetRotation: (element: DomEditSelection, next: { angle: number }) => void; onSetText: (value: string, fieldKey?: string) => void; onSetTextFieldStyle: (fieldKey: string, property: string, value: string) => void; onResetManualEdits: (element: DomEditSelection) => void; @@ -1568,11 +1571,13 @@ export function isPropertyPanelMediaLikeSelection(input: { export const PropertyPanel = memo(function PropertyPanel({ element, + multiSelectCount = 0, copiedAgentPrompt, onClearSelection, onSetStyle, onSetManualOffset, onSetManualSize, + onSetRotation, onSetText, onSetTextFieldStyle, onResetManualEdits, @@ -1597,12 +1602,28 @@ export const PropertyPanel = memo(function PropertyPanel({ if (!element) { return (
- -

Select an element in the preview.

-

- The inspector is tuned for element edits with safer geometry controls, color picking, and - cleaner grouped layer controls. -

+ {multiSelectCount > 1 ? ( + <> + +

+ {multiSelectCount} elements selected +

+

+ Select a single element to edit its properties. Click an element in the preview or use + the timeline layer panel. +

+ + ) : ( + <> + +

+ Select an element in the preview. +

+

+ Click any element to inspect and edit it. Click deeper to select child elements. +

+ + )}
); } @@ -1618,8 +1639,14 @@ export const PropertyPanel = memo(function PropertyPanel({ tagName: element.tagName, styles, }); - const canShowFillColor = !mediaLikeSelection; - const canShowTextColor = !hasTextControls && !mediaLikeSelection; + const bgColor = styles["background-color"]?.trim(); + const hasExplicitFillColor = + !mediaLikeSelection && + Boolean(bgColor) && + bgColor !== "rgba(0, 0, 0, 0)" && + bgColor !== "transparent"; + const canShowFillColor = hasExplicitFillColor; + const canShowTextColor = !mediaLikeSelection && Boolean(styles.color?.trim()); const visibleSections = getPropertyPanelVisibleSections({ hasSelection: true, canEditStyles: showEditableSections, @@ -1628,6 +1655,7 @@ export const PropertyPanel = memo(function PropertyPanel({ }); const manualOffset = readStudioPathOffset(element.element); const manualSize = readStudioBoxSize(element.element); + const manualRotation = readStudioRotation(element.element); const resolvedWidth = manualSize.width > 0 ? manualSize.width @@ -1837,6 +1865,17 @@ export const PropertyPanel = memo(function PropertyPanel({ disabled={manualSizeEditingDisabled} onCommit={(next) => commitManualSize("height", next)} /> + { + const parsed = parseFloat(next.replace("°", "").trim()); + if (Number.isFinite(parsed)) { + onSetRotation(element, { angle: parsed % 360 }); + } + }} + /> )} diff --git a/packages/studio/src/components/editor/domEditing.ts b/packages/studio/src/components/editor/domEditing.ts index f05adef722..6d40006dbd 100644 --- a/packages/studio/src/components/editor/domEditing.ts +++ b/packages/studio/src/components/editor/domEditing.ts @@ -398,6 +398,7 @@ function buildElementLabel(el: HTMLElement): string { const DOM_LAYER_IGNORED_TAGS = new Set([ "base", "br", + "canvas", "link", "meta", "script", diff --git a/packages/studio/src/components/editor/manualEditingAvailability.test.ts b/packages/studio/src/components/editor/manualEditingAvailability.test.ts index 3d30fcbcaa..e2cd9902a4 100644 --- a/packages/studio/src/components/editor/manualEditingAvailability.test.ts +++ b/packages/studio/src/components/editor/manualEditingAvailability.test.ts @@ -16,13 +16,13 @@ describe("manual editing availability", () => { vi.resetModules(); }); - it("enables inspector selection by default while motion and manual dragging stay opt-in", async () => { + it("enables inspector selection, motion panel, and manual editing by default", async () => { const availability = await loadAvailabilityWithEnv({}); expect(availability.STUDIO_PREVIEW_MANUAL_EDITING_ENABLED).toBe(false); expect(availability.STUDIO_PREVIEW_SELECTION_ENABLED).toBe(true); expect(availability.STUDIO_INSPECTOR_PANELS_ENABLED).toBe(true); - expect(availability.STUDIO_MOTION_PANEL_ENABLED).toBe(false); + expect(availability.STUDIO_MOTION_PANEL_ENABLED).toBe(true); expect(availability.STUDIO_TIMELINE_LAYER_INSPECTOR_ENABLED).toBe(true); }); diff --git a/packages/studio/src/components/editor/manualEditingAvailability.ts b/packages/studio/src/components/editor/manualEditingAvailability.ts index f31e151d97..dc3add3860 100644 --- a/packages/studio/src/components/editor/manualEditingAvailability.ts +++ b/packages/studio/src/components/editor/manualEditingAvailability.ts @@ -44,7 +44,7 @@ export const STUDIO_INSPECTOR_PANELS_ENABLED = resolveStudioBooleanEnvFlag( export const STUDIO_MOTION_PANEL_ENABLED = resolveStudioBooleanEnvFlag( env, [STUDIO_MOTION_PANEL_ENV, "VITE_STUDIO_MOTION_PANEL_ENABLED"], - false, + true, ); export const STUDIO_TIMELINE_LAYER_INSPECTOR_ENABLED =