Files
EvoScientist-WebUI/docs/superpowers/plans/2026-08-08-html-preview-fullscreen.md

5.7 KiB
Raw Permalink Blame History

HTML Preview Fullscreen Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Workspace 对话框的 HTML 预览面板支持浏览器全屏(Fullscreen API),工具栏在全屏下可用,Esc 退出不误关对话框。

Architecture: 仅改 WorkspaceFileDialog.tsx:header+内容区包入挂 ref 的 wrapper,Fullscreen API 切换 + fullscreenchange 同步状态,DialogContent 加 Esc 拦截。

Tech Stack: React, Radix Dialog, lucide-react, Tailwind。

Global Constraints

  • 仅修改 src/app/components/WorkspaceFileDialog.tsx;服务端与 iframe sandbox 不动。
  • 全屏按钮显示条件复用既有 showHtmlToggle。
  • 全屏态 wrapper 类:flex h-full w-full flex-col bg-background p-4;非全屏:flex min-h-0 flex-1 flex-col。
  • 设计文档:docs/superpowers/specs/2026-08-08-html-preview-fullscreen-design.md。
  • 回归命令:npm test 与 npx tsc --noEmit 全绿。

Task 1: 预览面板全屏

Files:

  • Modify: src/app/components/WorkspaceFileDialog.tsx

Interfaces:

  • Consumes: 既有 showHtmlToggle(约 404 行)、header div(约 497-642 行)、内容区 div(约 653 行 min-h-0 flex-1 overflow-hidden)。

  • Produces: 无导出变化。

  • Step 1: State、ref 与 fullscreenchange 监听

组件 state 区(const [savedTick, setSavedTick] = useState(0); 之后)追加:

  // Browser-level fullscreen for the preview panel (toolbar stays usable).
  const previewAreaRef = useRef<HTMLDivElement>(null);
  const [isFullscreen, setIsFullscreen] = useState(false);

  useEffect(() => {
    const onChange = () =>
      setIsFullscreen(document.fullscreenElement === previewAreaRef.current);
    document.addEventListener("fullscreenchange", onChange);
    return () => document.removeEventListener("fullscreenchange", onChange);
  }, []);

  const toggleFullscreen = () => {
    if (document.fullscreenElement) void document.exitFullscreen();
    else void previewAreaRef.current?.requestFullscreen();
  };

确认 useRef 已在 React import 中(文件已用 useState/useEffect;若无 useRef 则加入)。

  • Step 2: lucide 图标 import

现有 lucide-react import 块加入 Maximize2 与 Minimize2(按字母序插入现有列表)。

  • Step 3: DialogContent 加 Esc 拦截

现有:

        <DialogContent
          aria-describedby={undefined}
          className="flex h-[80vh] max-h-[80vh] min-w-[60vw] flex-col p-6"
        >

改为:

        <DialogContent
          aria-describedby={undefined}
          className="flex h-[80vh] max-h-[80vh] min-w-[60vw] flex-col p-6"
          onEscapeKeyDown={(e) => {
            // While the preview panel is fullscreen, Esc must only exit
            // fullscreen — not close the whole dialog.
            if (document.fullscreenElement) e.preventDefault();
          }}
        >
  • Step 4: wrapper 包裹 header + 内容区

现有结构(约 496-875 行):<DialogTitle .../> 之后依次是 header div(<div className="mb-4 flex items-center justify-between gap-3 border-b border-border pb-4">…)、{actionError && …}、内容区 <div className="min-h-0 flex-1 overflow-hidden">…</div>,然后 </DialogContent>。

在 <DialogTitle className="sr-only">{path}</DialogTitle> 之后插入 wrapper 开标签,把 header div、actionError 块、内容区 div 三者包进去,内容区 div 结束后、</DialogContent> 之前闭合:

          <DialogTitle className="sr-only">{path}</DialogTitle>
          <div
            ref={previewAreaRef}
            className={
              isFullscreen
                ? "flex h-full w-full flex-col bg-background p-4"
                : "flex min-h-0 flex-1 flex-col"
            }
          >
            <div className="mb-4 flex items-center justify-between gap-3 border-b border-border pb-4">
            …(header / actionError / 内容区原样保留,仅整体缩进)
            </div>
          </div>
        </DialogContent>

注意:wrapper 是 DialogContent 的唯一 flex 子项(DialogTitle 为 sr-only 不参与布局),非全屏类必须保持 flex min-h-0 flex-1 flex-col 以维持现状布局。

  • Step 5: 工具栏全屏按钮

在 Preview/Source 分段组({showHtmlToggle && (<div role="group" …>…</div>)},约 550-581 行)之后、{editable && (…Edit…)} 之前插入:

                  {showHtmlToggle && (
                    <Button
                      variant="ghost"
                      size="sm"
                      className="h-8 px-2"
                      onClick={toggleFullscreen}
                      aria-label="Toggle fullscreen"
                      aria-pressed={isFullscreen}
                      title="Fullscreen"
                    >
                      {isFullscreen ? (
                        <Minimize2 size={16} aria-hidden="true" />
                      ) : (
                        <Maximize2 size={16} aria-hidden="true" />
                      )}
                    </Button>
                  )}
  • Step 6: Typecheck + 全量回归

Run: npx tsc --noEmit && npm test Expected: no errors; 全部通过

  • Step 7: Commit
git add src/app/components/WorkspaceFileDialog.tsx
git commit -m "feat(webui): fullscreen toggle for html preview panel"

Task 2: 手动验证(controller 处理)

  • 浏览器验证 spec「测试」节 4 项:全屏进入/按钮退出/Esc 退出不关对话框/非 html 无按钮