From 227baf709d87ce4ac957b797f8e0d65f8c0a0e33 Mon Sep 17 00:00:00 2001 From: 111wukong <96066236+111wukong@users.noreply.github.com> Date: Thu, 21 May 2026 02:26:00 +0800 Subject: [PATCH 1/3] fix(claude): add deepseek models to Claude Code model picker 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 #65 --- next/src/lib/agents/detect.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/next/src/lib/agents/detect.ts b/next/src/lib/agents/detect.ts index c72c8db..8f4520c 100644 --- a/next/src/lib/agents/detect.ts +++ b/next/src/lib/agents/detect.ts @@ -60,6 +60,11 @@ export const AGENTS: AgentDef[] = [ { id: "claude-opus-4-7", label: "claude-opus-4-7" }, { id: "claude-sonnet-4-6", label: "claude-sonnet-4-6" }, { id: "claude-haiku-4-5", label: "claude-haiku-4-5" }, + // DeepSeek models (via `cc switch` or direct config) — kept in the + // Claude Code picker so users who route Claude through a custom model + // endpoint can switch models without leaving html-anything's UI. + { id: "deepseek-v4-pro", label: "deepseek-v4-pro" }, + { id: "deepseek-v4-flash", label: "deepseek-v4-flash" }, ], }, { From f194370a3deff1c3b3cdce51dfe5aebe3c4f69b7 Mon Sep 17 00:00:00 2001 From: 111wukong <96066236+111wukong@users.noreply.github.com> Date: Thu, 21 May 2026 02:30:26 +0800 Subject: [PATCH 2/3] fix(export): resolve PPTX export race condition with empty slides MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 #62 --- next/src/lib/export/deck.ts | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/next/src/lib/export/deck.ts b/next/src/lib/export/deck.ts index 7e2898f..fa3dbe9 100644 --- a/next/src/lib/export/deck.ts +++ b/next/src/lib/export/deck.ts @@ -48,9 +48,31 @@ async function renderSlideToBlob(slide: DeckSlide, scale = 2): Promise { const done = () => res(); if (iframe.contentDocument?.readyState === "complete") return done(); iframe.addEventListener("load", done, { once: true }); - setTimeout(done, 4000); + setTimeout(done, 8000); }); - return await iframeToBlob(iframe, { scale }); + + // Give the browser an extra frame to finish layout — without this, + // Tailwind CDN / fonts / images injected via srcdoc can report a 0px + // scrollHeight on the first paint, causing iframeToBlob to throw + // "preview has no content yet" even though the live preview is fine. + await new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(() => r()))); + + // Retry once if the first capture returns an empty document. + const maxAttempts = 2; + let lastError: unknown; + for (let attempt = 1; attempt <= maxAttempts; attempt++) { + try { + return await iframeToBlob(iframe, { scale }); + } catch (err) { + lastError = err; + if (attempt < maxAttempts) { + // Wait longer before retry — fonts / Tailwind CDN may still be + // inflight even after the load event fired. + await new Promise((r) => setTimeout(r, 1200)); + } + } + } + throw lastError; } finally { wrap.remove(); } From b8d9f1728e2acd097627eb9730b2ca7e98b2049e Mon Sep 17 00:00:00 2001 From: 111wukong <96066236+111wukong@users.noreply.github.com> Date: Thu, 21 May 2026 02:33:38 +0800 Subject: [PATCH 3/3] fix(export): prevent left-side crop of exported images on Windows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 #70 --- next/src/lib/export/image.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/next/src/lib/export/image.ts b/next/src/lib/export/image.ts index 93c1c77..0c9fea5 100644 --- a/next/src/lib/export/image.ts +++ b/next/src/lib/export/image.ts @@ -149,6 +149,10 @@ export async function nodeToBlob(node: HTMLElement, opts: ImageOpts = {}): Promi * the exact viewport the browser used when measuring text. Using * `scrollWidth` here causes a 1–2px drift that wraps Chinese titles to * a new line and shoves them under the body text. + * To keep clientWidth reliable across platforms, overflow is set to + * "hidden" during capture — without this, Windows' always-visible + * scrollbar adds ~15px of chrome that narrows clientWidth and crops + * the left edge of the exported image. * 4. Pass explicit width/height to modern-screenshot so the foreignObject * SVG matches the laid-out size 1:1. */ @@ -172,8 +176,13 @@ export async function iframeToBlob( const fullHeight = fullScrollHeight(doc); if (!fullHeight) throw new Error("preview has no content yet"); iframe.style.height = `${fullHeight}px`; - doc.documentElement.style.overflow = "visible"; - doc.body.style.overflow = "visible"; + // Use overflow:hidden — not visible — to suppress platform scrollbars. + // On Windows where scrollbars are always visible, "visible" adds a ~15px + // vertical scrollbar that reduces clientWidth, causing the exported image + // to be cropped on the left side (the screenshot is taken at the narrowed + // viewport width and the rightmost pixels are lost). + doc.documentElement.style.overflow = "hidden"; + doc.body.style.overflow = "hidden"; // Wait a couple of frames for the browser to re-flow at the new size. await NEXT_FRAME();