From fe6fb0534c1ad50c82bfd2ffecd7f115847f8625 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 17 Feb 2026 07:23:09 +0100 Subject: [PATCH] :bug: Fix focus mode for simple component --- .../playwright/ui/pages/WasmWorkspacePage.js | 13 +++++++ .../ui/render-wasm-specs/shapes.spec.js | 36 ++++++++++++++++++ ...ible-when-focusing-after-creating-it-1.png | Bin 0 -> 11153 bytes render-wasm/src/render.rs | 8 ++-- 4 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 frontend/playwright/ui/render-wasm-specs/shapes.spec.js-snapshots/Keeps-component-visible-when-focusing-after-creating-it-1.png diff --git a/frontend/playwright/ui/pages/WasmWorkspacePage.js b/frontend/playwright/ui/pages/WasmWorkspacePage.js index a29f741612..405f3eb55e 100644 --- a/frontend/playwright/ui/pages/WasmWorkspacePage.js +++ b/frontend/playwright/ui/pages/WasmWorkspacePage.js @@ -54,6 +54,19 @@ export class WasmWorkspacePage extends WorkspacePage { await this.hideUI(); } + async getRenderCount() { + return this.page.evaluate(() => window.wasmRenderCount || 0); + } + + async waitForNextRender(previousCount = null) { + const baseCount = + previousCount === null ? await this.getRenderCount() : previousCount; + await this.page.waitForFunction( + (count) => (window.wasmRenderCount || 0) > count, + baseCount, + ); + } + async hideUI() { await this.page.keyboard.press("\\"); await expect(this.pageName).not.toBeVisible(); diff --git a/frontend/playwright/ui/render-wasm-specs/shapes.spec.js b/frontend/playwright/ui/render-wasm-specs/shapes.spec.js index 67eccff5b9..11253425c6 100644 --- a/frontend/playwright/ui/render-wasm-specs/shapes.spec.js +++ b/frontend/playwright/ui/render-wasm-specs/shapes.spec.js @@ -356,3 +356,39 @@ test("Renders shapes with multiple fills and blur", async ({ await expect(workspace.canvas).toHaveScreenshot(); }); + +test("Keeps component visible when focusing after creating it", async ({ + page, +}) => { + const workspace = new WasmWorkspacePage(page); + await workspace.setupEmptyFile(); + await workspace.mockRPC(/get\-file\?/, "workspace/get-file-not-empty.json"); + await workspace.mockRPC( + "update-file?id=*", + "workspace/update-file-create-rect.json", + ); + + await workspace.goToWorkspace({ + fileId: "6191cd35-bb1f-81f7-8004-7cc63d087374", + pageId: "6191cd35-bb1f-81f7-8004-7cc63d087375", + }); + await workspace.waitForFirstRender(); + + await workspace.clickLayers(); + await workspace.clickLeafLayer("Rectangle"); + await page.keyboard.press("ControlOrMeta+k"); + + const componentLayer = workspace.layers + .getByTestId("layer-row") + .filter({ has: page.getByTestId("icon-component") }) + .first(); + await expect(componentLayer).toBeVisible(); + await componentLayer.click(); + + const previousRenderCount = await workspace.getRenderCount(); + await page.keyboard.press("f"); + await workspace.waitForNextRender(previousRenderCount); + + await workspace.hideUI(); + await expect(workspace.canvas).toHaveScreenshot(); +}); diff --git a/frontend/playwright/ui/render-wasm-specs/shapes.spec.js-snapshots/Keeps-component-visible-when-focusing-after-creating-it-1.png b/frontend/playwright/ui/render-wasm-specs/shapes.spec.js-snapshots/Keeps-component-visible-when-focusing-after-creating-it-1.png new file mode 100644 index 0000000000000000000000000000000000000000..ebdf07ab640dfdd86dfd98f80eed366106422d57 GIT binary patch literal 11153 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 literal 0 HcmV?d00001 diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index b0de9c6211..b8ef18fda1 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -1896,10 +1896,12 @@ impl RenderState { } } + let can_flatten = element.can_flatten() && !self.focus_mode.should_focus(&element.id); + // Skip render_shape_enter/exit for flattened containers // If a container was flattened, it doesn't affect children visually, so we skip // the expensive enter/exit operations and process children directly - if !element.can_flatten() { + if !can_flatten { // Enter focus early so shadow_before_layer can run (it needs focus_mode.is_active()) self.focus_mode.enter(&element.id); @@ -1978,7 +1980,7 @@ impl RenderState { // Skip nested state updates for flattened containers // Flattened containers don't affect children, so we don't need to track their state - if !element.can_flatten() { + if !can_flatten { match element.shape_type { Type::Frame(_) if Self::frame_clip_layer_blur(element).is_some() => { self.nested_blurs.push(None); @@ -2003,7 +2005,7 @@ impl RenderState { let children_clip_bounds = node_render_state.get_children_clip_bounds(element, None); - let children_ids: Vec<_> = if element.can_flatten() { + let children_ids: Vec<_> = if can_flatten { // Container was flattened: get simplified children (which skip this level) get_simplified_children(tree, element) } else {