diff --git a/CHANGELOG.md b/CHANGELOG.md index 12599b6..1b161e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,14 @@ The version in `plugins/vstack/.claude-plugin/plugin.json` is what your host compares against to decide an update is available. See the release checklist in [`CONTRIBUTING.md`](CONTRIBUTING.md). +## 6.9.1 — 2026-11-06 + +**Fixed** + +- **The canvas stays where you were when the page changes height.** If you had + scrolled down a long page and it then grew or shrank, the canvas jumped back to + the top. It now keeps your place. + ## 6.9.0 — 2026-11-06 **Added** diff --git a/e2e/features/framing.feature b/e2e/features/framing.feature index 80efe4d..24c81f1 100644 --- a/e2e/features/framing.feature +++ b/e2e/features/framing.feature @@ -32,3 +32,12 @@ Feature: Long pages and overlays on the canvas Then the dialog is where the reviewer is looking When the framed page closes its confirmation dialog Then the canvas fits the whole page again + + @browser + Scenario: F4 — the canvas stays where the reviewer was when the page changes height + Given a long page is under review + And the reviewer opens the workspace + When the reviewer scrolls the canvas to the bottom + And the framed page grows + Then the canvas fits the whole page again + And the canvas has not moved diff --git a/e2e/steps/browser.steps.mjs b/e2e/steps/browser.steps.mjs index 7396e26..2ffa6bc 100644 --- a/e2e/steps/browser.steps.mjs +++ b/e2e/steps/browser.steps.mjs @@ -371,6 +371,18 @@ Then('the canvas fits the whole page again', async function () { }, 'the frame goes back to the height of the page') }) +When('the framed page grows', async function () { + this.canvasScrollWas = await this.browserPage.locator('#viewportBox').evaluate(element => element.scrollTop) + const framed = await framedWindow(this) + await framed.evaluate(() => { document.querySelector('main').style.minHeight = '2800px' }) +}) + +Then('the canvas has not moved', async function () { + const now = await this.browserPage.locator('#viewportBox').evaluate(element => element.scrollTop) + assert.ok(Math.abs(now - this.canvasScrollWas) <= 1, + `the canvas was at ${this.canvasScrollWas} and is now at ${now}`) +}) + When('the reviewer opens Clear all', async function () { await this.browserPage.locator('#btnClear').click() await this.browserPage.locator('#clearDialog[open]').waitFor() diff --git a/plugins/vstack/.claude-plugin/plugin.json b/plugins/vstack/.claude-plugin/plugin.json index 5f1056c..17fffb0 100644 --- a/plugins/vstack/.claude-plugin/plugin.json +++ b/plugins/vstack/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "vstack", "displayName": "Visual Stack", - "version": "6.9.0", + "version": "6.9.1", "description": "Stop prompting. Start pointing. Visual Stack adds a Figma-like feedback layer to Claude Code. Create a new screen from a prompt, screenshot, reference site, or design system, or open an app you already have running. Click any element and leave feedback exactly where the problem is hiding, and Claude publishes the next revision into the same workspace. Compare revisions on a timeline, preview desktop, tablet, and mobile layouts, and keep every comment attached to the element, route, and version it refers to. Wireframes are self-contained HTML and review state stays in your project.", "author": { "name": "Cavalry Collective", diff --git a/plugins/vstack/.codex-plugin/plugin.json b/plugins/vstack/.codex-plugin/plugin.json index f3bedb7..01a1898 100644 --- a/plugins/vstack/.codex-plugin/plugin.json +++ b/plugins/vstack/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "vstack", - "version": "6.9.0", + "version": "6.9.1", "description": "Stop prompting. Start pointing. Visual Stack adds a Figma-like feedback layer to Codex. Create a new screen from a prompt, screenshot, reference site, or design system, or open an app you already have running. Click any element and leave feedback exactly where the problem is hiding, and Codex publishes the next revision into the same workspace. Compare revisions on a timeline, preview desktop, tablet, and mobile layouts, and keep every comment attached to the element, route, and version it refers to. Wireframes are self-contained HTML and review state stays in your project.", "author": { "name": "Cavalry Collective", diff --git a/plugins/vstack/skills/review/assets/workspace.html b/plugins/vstack/skills/review/assets/workspace.html index e677fb2..59dfba3 100644 --- a/plugins/vstack/skills/review/assets/workspace.html +++ b/plugins/vstack/skills/review/assets/workspace.html @@ -3037,6 +3037,8 @@
return vh; } const inner = modalViewport ? (fwin()?.scrollY || 0) : 0; + // Read the outer scroll before apply(vh) shrinks the frame and clamps it to 0. + const outer = modalViewport ? 0 : vpBox.scrollTop; modalViewport = false; apply(vh); const natural = measure(); @@ -3044,7 +3046,7 @@ apply(natural); if (measure() > natural + 2) apply(vh); } - if (inner) vpBox.scrollTop = inner; + if (inner || outer) vpBox.scrollTop = inner || outer; requestAnimationFrame(() => { sizingFrame = false }); return parseFloat(frame.style.height) || 0; }