fix(export): prevent left-side crop of exported images on Windows#73
fix(export): prevent left-side crop of exported images on Windows#73111wukong wants to merge 3 commits into
Conversation
Users who route Claude Code through a custom model endpoint (e.g. via `cc switch`) need to select `deepseek-v4-pro` or `deepseek-v4-flash` from the model picker. Without these entries, the only way to switch models is to leave html-anything, change the CLI config, and reload. Closes nexu-io#65
The off-screen iframe used to capture each slide for PPTX / PNG-ZIP export could report a 0px scrollHeight when fonts or Tailwind CDN styles had not yet been applied, even after the `load` event fired. This caused `iframeToBlob` to throw "preview has no content yet" for decks that rendered correctly in the live preview. Changes: - Increased the srcdoc load timeout from 4 s → 8 s (Tailwind CDN can be slow) - Added a double-rAF flush before the first capture to let the browser finish layout after the load event - Added a single retry with 1.2 s backoff when the first capture fails Fixes nexu-io#62
On platforms with always-visible scrollbars (Windows), setting overflow to "visible" during iframe screenshot capture added a vertical scrollbar that reduced documentElement.clientWidth by ~15px. The screenshot was taken at this narrower width, cropping the left edge of the exported image. Switching overflow from "visible" to "hidden" during capture suppresses the scrollbar while preserving the full content layout — the iframe height is already pinned to scrollHeight on the previous line, so no content is hidden. Fixes nexu-io#70
PerishCode
left a comment
There was a problem hiding this comment.
Review summary
Thanks @111wukong — three small, well-reasoned bugfixes, each with a clear root-cause comment. The overflow: visible → overflow: hidden switch in iframeToBlob (next/src/lib/export/image.ts) is a sound, standard way to keep documentElement.clientWidth independent of platform scrollbars; since iframe.style.height is already pinned to fullScrollHeight and captureHeight is re-measured after the overflow change, nothing is clipped vertically. The PPTX path in next/src/lib/export/deck.ts (double-rAF flush + single 1.2 s-backoff retry + 4 s→8 s load timeout) is a reasonable defense against the 0 px-scrollHeight race.
No blocking issues. One non-blocking process note here, plus one nit inline.
PR scope vs. description — This branch (fix/image-export-left-crop) actually contains three independent commits, not one: 227baf7 adds DeepSeek models to the Claude Code picker (Closes #65, touches next/src/lib/agents/detect.ts), f194370 fixes the PPTX export race (Fixes #62, touches next/src/lib/export/deck.ts), and b8d9f17 is the left-crop fix (Fixes #70, touches next/src/lib/export/image.ts). The PR title and body describe only #70 — they don't mention the DeepSeek picker change or the PPTX retry logic at all. Why it matters: a maintainer reviewing or release-noting this PR from its title/body would not realize that merging it also lands a model-picker change and new export-retry behavior, and the Closes #65 / Fixes #62 lines in the commit messages will auto-close those issues on merge to the default branch. Suggested fix: either update the PR body to enumerate all three fixes (and their issues), or split the unrelated #65 detect.ts change into its own PR — #62 and #70 are at least both export fixes, but #65 is unrelated to image export.
| { id: "deepseek-v4-pro", label: "deepseek-v4-pro" }, | ||
| { id: "deepseek-v4-flash", label: "deepseek-v4-flash" }, |
There was a problem hiding this comment.
These two DeepSeek entries are added to the Claude Code adapter's fallbackModels, but unlike the Claude/alias models above them they only resolve when the user has routed Claude Code through a custom model endpoint (e.g. via cc switch). For a user who has not done that, picking deepseek-v4-pro here makes invokeAgent run claude --model deepseek-v4-pro, which the stock Claude CLI/API rejects with an opaque "unknown model" failure surfaced as a failed run.
Why it matters: the new code comment documents this precondition for developers, but the user-facing label ("deepseek-v4-pro") gives the end user no hint that these require a routed setup. The picker therefore advertises two models that fail by default, which works against the "Curated, evidence-based model list" intent documented on AgentDef.fallbackModels (lines 37-40) and on DEFAULT_MODEL.
Suggested change: surface the precondition in the user-visible label, e.g. { id: "deepseek-v4-pro", label: "deepseek-v4-pro (routed endpoint)" } and likewise for deepseek-v4-flash; or gate these two entries behind a detected routed config so they only appear when usable. This is a nit, not merge-blocking.
|
Hey @111wukong — carrying over fresh verification from #70 so it is visible on the PR too. @LuckyFishGeek tested this branch on Windows 10 and the left-edge crop still reproduces. The new evidence points beyond the scrollbar case: when a centered template card is wider than the preview iframe, part of the layout can sit at negative X coordinates, and Could you refine |
|
Hey @111wukong — just a gentle nudge on this one. @LuckyFishGeek mapped out the remaining fix in #70: temporarily widen the iframe to the full scroll-width before calling |
|
Hey @111wukong, the PR has been quiet for a couple of days since @LuckyFishGeek's centered-layout feedback — still on this one? Any comment, commit, or progress note keeps the claim active; happy to help if you get stuck on the width-expansion approach. |
|
@lefarcen The image cropping issue can be closed now. Thanks. |
|
Hey @LuckyFishGeek — thanks for the update! Just to confirm: are you saying the left-crop issue is resolved for you now (e.g., you found a workaround or it no longer affects your workflow), or are you saying the PR as-is (with the I want to make sure we're on the same page before closing #70 — your earlier detailed feedback on 2026-05-21 showed the |
|
Hi there, @lefarcen Sorry for the confusion! To clarify: the PR as-is ( When I said it was resolved, I meant that it is resolved locally for me ONLY because I manually applied the "Ultimate Fix" (forcing the iframe width) that I posted in my previous detailed feedback. As a quick recap of why To officially fix #70, the export logic in So please do not close #70 until the width-forcing logic (from my previous code snippet) is merged. Let me know if you need me to re-paste that code snippet! Here is the code adjustment that completely solved the issue for me in next/src/lib/export/image.ts (iframeToBlob function): export async function iframeToBlob( const fullHeight = fullScrollHeight(doc); // <--- 2. Calculate true width (ensure it fits the 1080px card) iframe.style.height = doc.documentElement.style.overflow = "hidden"; await NEXT_FRAME(); try { } finally { |
|
Thanks for clarifying, @LuckyFishGeek — that resolves the ambiguity. We will not close #70 based on the current @111wukong, this confirms the current head still needs the width-expansion step before it can be considered a fix for #70: temporarily widen the iframe/capture viewport to the full content width before |
Fixes #70 (image export left-side crop)
On platforms with always-visible scrollbars (Windows),
overflow: visibleduring iframe screenshot capture added a ~15px scrollbar that reduceddocumentElement.clientWidth, causing the exported image to be cropped on the left edge.Switching to
overflow: hiddensuppresses the scrollbar while preserving the full layout — the iframe height is already pinned toscrollHeight, so no content is hidden.The Gemini CLI deprecation part of #70 is not addressed here — that requires a separate adapter for the Antigravity CLI.