Community生产力与协作github.com

jimku233/workflow-visualizer

把业务流程渲染成 Mermaid 源码 + 自包含交互式 HTML 流程图(零依赖、纯离线,不请求任何 CDN)。用于把用户口述或文档里的流程、SOP、生产链路、复盘时间线画成能点的图:节点可点开看时间/负责人/说明,支持搜索筛选、明暗主题、导出 SVG/PNG,并同时产出可粘进飞书/语雀/GitHub 的 Mermaid 代码。当用户说"把流程画出来/画个流程图/可视化工作流/整理成流程"时使用。

workflow-visualizer 是什么?

workflow-visualizer is a Claude Code agent skill that 把业务流程渲染成 Mermaid 源码 + 自包含交互式 HTML 流程图(零依赖、纯离线,不请求任何 CDN)。用于把用户口述或文档里的流程、SOP、生产链路、复盘时间线画成能点的图:节点可点开看时间/负责人/说明,支持搜索筛选、明暗主题、导出 SVG/PNG,并同时产出可粘进飞书/语雀/GitHub 的 Mermaid 代码。当用户说"把流程画出来/画个流程图/可视化工作流/整理成流程"时使用。.

兼容平台✓Claude Code✓Codex CLI✓Cursor
npx skills add jimku233/workflow-visualizer

Installed? Explore more 生产力与协作 skills: steipete/gemini, steipete/gh-issues, steipete/skill-creator · View all 6 →

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

workflow-visualizer: Business Process → Interactive Flowchart

Render script: scripts/flow.mjs (relative to this skill directory) Absolute path: scripts\flow.mjs Full field and syntax reference: references/spec.md (Read before writing specifications)

1. Core Constraints (Why This Approach)

  1. Zero Dependencies: Uses only node: built-in modules. No npm install, no mermaid installation required.
  2. Fully Offline: Layout and rendering are handled by our own SVG implementation, does not reference any CDN. Works offline, on intranets, or without external network access (local web_fetch/docs.deepseek.com won't work; any solution depending on a CDN will fail).
  3. Self-Contained Output: The exported .svg includes <style> and displays correctly when sent independently; the .html is a single file, double-click to open.

2. Three-Step Usage

Step 1: Extract the Process (This step cannot be skipped) Extract from the user's actual materials, do not fabricate. Source priority:

  • Planning/review documents in the workspace (*.md, usually contain sections on "workflow/SOP/schedule")
  • User dictation (mark uncertain items as status: hold and note "pending confirmation" in the note)
  • Conversation history or logs

Always extract these five elements: Steps (who does what), Time windows, Dependencies, Decision branches, Risks/Manual review points.

Step 2: Write the Specification

  • Simple diagram (purely linear + one or two branches) → Write Mermaid directly and save as .mmd, most efficient.
  • Needs time/owner/status/description/grouping → Write JSON (much higher information density, allows the panel to display content).
  • Run sample first to get a finished spec as a template for speed:
node "scripts\flow.mjs" sample --out ".\out\my_process.json"

Step 3: Validate + Render

$sk = "scripts\flow.mjs"
node $sk check  ".\out\my_process.json"     # Structural check: orphaned nodes, unreachable, no endpoint, cycles
node $sk render ".\out\my_process.json" --all --out ".\out\my_process.html"

Output falls in the same directory as the input file.

One render produces two layouts (same HTML, auto-selected based on screen, manually switch at bottom):

LayoutArrangementSuitable for
PortraitSingle column (wrap: 0)Mobile: vertical scroll, full width
LandscapeLong processes auto-split into multiple columns (wrap: -1, target aspect ratio 1.6)Desktop: fits one screen

Common parameters: --out <file.html>, --all (additionally outputs <name>-portrait.svg, <name>-landscape.svg, .mmd), --wrap N (fixed N rows per column for landscape), --debug (prints column/row counts and members for both layouts).

Verify both layouts (recommended):

node $sk preview ".\out\my_process.html"   # Generates a "[For Desktop] Side-by-Side Preview.html" with two iframes for mobile and desktop

Open it in a debug browser to see at a glance if both layouts are correct. For precise real device dimensions, use CDP device simulation:

# Mobile + desktop screenshots, also prints actual canvas/aside/footer heights and auto-selected layout
node $sk\..\tools\devshot.mjs "http://127.0.0.1:8765/my_process.html" mobile.png desktop.png

# To run a JS snippet in the page to verify interactions (e.g., clicking nodes, entering search, opening drawer):
node $sk\..\tools\probe.mjs <url> probe.js --w 390 --h 844 --mobile --touch

Both tools create their own temporary tabs and close them when done, without touching the user's current browsing tabs (early versions directly grabbed the first page target, causing user's current page to be tested; lesson documented below). However, be aware that the new tab will appear in the user's browser window and steal focus—ask the user before using these tools if they are actively browsing, otherwise it will interrupt them.

3. Post-Rendering Self-Check (Don't just assume "command succeeded")

Command success ≠ a readable diagram. Perform at least a visual check using browser-control:

# 1) Create a copy with an ASCII filename to avoid Chinese encoding issues with file://
Copy-Item ".\out\my_process.html" ".\out\preview.html" -Force
# 2) Use the system URI generator to get the correct file:// address (manually encoding percent is error-prone)
$u = [System.Uri]::new((Resolve-Path ".\out\preview.html").Path).AbsoluteUri
node browser-control\webctl.mjs shot $u "out\shot.png" 5000

Then inspect the image to confirm: text is not clipped, branches don't tangle, group bands don't cover nodes. Interactions can also be script-verified (webctl.mjs evalfile, must be a (function(){...})() expression; bare return causes "Illegal return statement").

4. For Human Viewing: Desktop / Mobile Two Ways to Open

Desktop: Double-click the .html (single file, works offline); drag the .svg into documents.

Mobile (HTML on desktop, mobile browser can't open local files)—use LAN access, no file transfer needed:

node "scripts\serve.mjs" ".\out" 8765

The script prints usable mobile addresses directly (VPN/virtual adapters/169.254 are automatically marked "ignore"), like: Mobile → http://<your_LAN_IP>:8765/ ✔ Use this (WLAN) Connect the mobile device to the same Wi-Fi, open the address in a browser, and tap the file name. Key points:

  • Only expose the output directory, not the workspace root.
  • On Windows, a firewall prompt appears the first time; check "Private networks" to allow inbound traffic. If it doesn't appear and you can't connect, the firewall is blocking it.
  • The directory page is a file list; on mobile, you can tap any .html / .svg / .png to view.
  • Once done, I can stop the server (it's a background task), or you can close the terminal window.

Not on the same network / need to send to others: Send the HTML file directly—it's self-contained and works offline; can be sent via WeChat/QQ. If only the image is needed, use "Export PNG" from the page or Ctrl+P to save as PDF in the browser.

Mobile Interactions: Single-finger drag to pan, pinch to zoom, tap a node for details, bottom "Fit" button to reset to full view. Portrait/Landscape buttons are at the bottom left (icons only on mobile).

AreaCapability
Top barBrand point + title, search box, "All Flowcharts" button (top right), status legend capsules (click to filter)
Right panelProcess overview when nothing is selected; lists matches first when searching (see below); after selecting a node: detail card + "Back to overview"
Bottom barPortrait/Landscape toggle, Overview/Detail (mobile), zoom, fit, export SVG/PNG, light/dark theme, Mermaid source code

Search Hit Levels (Important: Don't treat this as a bug)

Search checks title / id / time / owner / description / notes simultaneously, but only titles are visible on the canvas. Thus hits fall into two categories:

  • Visible Hits (title/id/time/owner) → Node outlined in blue, counts as one hit.
  • Hits Only in Description/Notes → Node outlined in orange dashed line, displayed as 2 matches (1 only in description/notes), with a match list on the right (click to navigate).

Hit colors use the dedicated variable --hit (dark #4a9dff, beige theme #2b7fe0), do not borrow --accent—the beige theme's accent is terracotta #bf6b3a, borrowing it would turn hits to "red outlines," contradicting the "blue outline" explanation (a past mistake).

Example: Searching for cup in the sample process yields 2 hits—Open Second Cup Station (title) and Afternoon Restock (description mentions "cup"); searching for f matches node id f and any nodes with the letter f in description/notes. A single node is counted only once; this is not duplicate counting.

Light/Dark Theme: Global Setting / Single Diagram / Lock (Later takes precedence)

All three keys are stored in the browser's localStorage (all in JSON {mode, ts}, comparison by timestamp, the most recent change wins):

KeyWho WritesMeaning
flowviz-themeHomepage "Display Settings"Global setting: Follow system / Always dark / Always light
flowviz-theme:<filename>Flowchart page's light/dark togglePer-diagram setting (values: dark/light)
flowviz-lock:<filename>Flowchart page's "Lock Color"Locks the diagram's colors ({locked:true, mode})

Activation rules (the bootstrap script in <head> and page JS are fully consistent to avoid flash):

  1. If locked → Use the color frozen at lock time; both global and per-diagram settings have no effect.
  2. Otherwise, compare timestamps of global and per-diagram settings, the newer one wins.
  3. If neither exists → Follow system (prefers-color-scheme), live updates on system switch.

Thus: Change per-diagram first, then global setting → all follow global; change global first, then per-diagram → only that diagram changes. Must unlock before changing colors (clicking the light/dark toggle while locked is blocked with a prompt).

Test matrix (all 8 steps pass, 0 anomalies): Clear → Global=Light → Diagram=Light; Toggle per-diagram light/dark → Diagram=Dark (writes per-diagram key); Change Global to Light → Diagram=Light (global update overrides per-diagram); Toggle per-diagram again → Only that diagram turns dark; Lock → After changing global setting, still keeps the locked dark; Click light/dark toggle while locked → Blocked with prompt "Unlock first"; Unlock → Returns to global light setting; Homepage card shows "🔒 Locked Light" badge.

Other notes:

  • Repeated setItem with the same key is an overwrite, not accumulation (tested: tapping the light/dark toggle 12 times, Object.keys(localStorage) still shows 1 item, total 18 bytes). No need to "delete the previous entry."
  • Preferences are stored in the device's browser, server restart unaffected; only changing IP/port (different origin), clearing browser data, or switching browsers resets them.
  • Light theme is beige-millennium style, not pure white: background #f2ebdd, panel #fdfaf3, body #33302a, accent terracotta #bf6b3a, diagram internal background #e6dcc8 (dark theme retains its original set).

Top Buttons

🏠 Home goes back to the file list page (href="./") | All Flowcharts opens the right panel | Search box | Status legend | Light/Dark toggles this diagram | Lock Color freezes this diagram's colors. Current page is rendered as a non-link in the drawer (clicking it won't produce a response, avoiding the "I thought it was broken" confusion). The list is always sorted by filename, so fetching doesn't reorder.

The top-right button opens the right drawer listing all flowcharts generated by this skill in the same directory (sorted by filename, current page marked as "Current" and rendered as a non-clickable non-link). Clicking one switches to it. Detection is via the data-flow-viz="1" attribute in the page, so other HTML files are not included.

  • The manifest file is flow-index.json in the directory, rebuilt automatically on each render (or manually via node flow.mjs index <directory>).
  • When opened via HTTP, the drawer fetches and refreshes the list in real-time. When directly double-clicking a local file, it uses the embedded snapshot (every page includes one, so local switching works).

Search Interaction (Three Patches for "Hits Not Obvious")

  1. Auto-expand result panel: When a search yields hits and no node is currently selected, setPanel(true) is called—on mobile, results are hidden in a collapsed bottom drawer; without expansion, it looks like "search returned nothing" (root cause of "search feels useless").
  2. Previous / Next: Result area title bar has ↑ / ↓ and current/total count. In the search box, Enter = Next, Shift+Enter = Previous; Esc clears.
  3. Auto-focus (optional): The result area has an "Auto-focus hits" toggle. When enabled:
    • If the bounding box of all hits fits and required zoom ≥ current zoom × 0.6 → Frame all hits (multiple hits visible on single screen).
    • Otherwise (hits too far apart) → Do not zoom out, keep current zoom and center on the "current hit," allowing navigation via ↑/↓. This prevents text from shrinking to nothing when trying to fit two distant hits.
  4. During search, add .searching class to the SVG: non-hits fade to 14% (lines 10%, group bands 35%), hits get 3px stroke.
  5. Programmatic moves (focus/navigation) add .vp-anim to #vp (0.28s transition); manual dragging is unaffected.

Duplicate Flowchart Names: First Distinguish "Same Content" vs. "Just Same Name"

When multiple files appear under the same title (typical cause: after deleting and restoring a file, the system creates xxx - Copy.html), the top of the homepage shows a prompt box with two different actions:

SituationCriterionAction Provided
Same Content (two renders of the same process)Process semantic fingerprint identical"Keep only the latest, move the rest to recycle folder"
Different Content (same name but different versions)Fingerprints differDo not delete, only offer "Rename..." (default suggestion: original name (old version YYYY-MM-DD).html)
  • Fingerprint = process semantics, only takes title/subtitle/orientation/groups/nodes/edges, ignoring selfFile, flows, slug and other page-level fields. So "two files rendered from the same process at different times" have identical fingerprints, while changes only to page metadata are not misjudged as different content.
  • Deletion is always "Move to recycle folder": Create .trash-YYYY-MM-DD/ in the same directory, and rename the HTML along with its SVG/MMD (matched by normalized name) into it. Files can be restored at any time.
  • The action is handled by the backend endpoint POST /__flow/act ({op:'trash', remove:[...]} / {op:'rename', file, to}):
    • Only allows calls from 127.0.0.1 (mobile, LAN are always 403)—this prevents exposing "delete your files" capability over Wi-Fi.
    • Filename whitelist: no path separators, length ≤200, must end with .html; conflict returns 409.
    • In mobile / local file:// mode, the button is not shown; only the prompt is displayed (those scenarios have no writable backend).
  • The card still retains the filename (when titles are duplicated) for manual identification.

Two Ways to Open (Both Must Access the Homepage)

MethodHow to Access HomepageWhere Homepage Comes From
Local double-click (file://)Flowchart top right 🏠 → ./index.htmlrender writes a static index.html (browser's built-in directory listing only shows filenames, so this is necessary)
LAN / HTTP🏠 → ./serve.mjs dynamically generates it
  • render rebuilds index.html every time. It shares the same rendering code as the HTTP homepage (buildHome() exported by serve.mjs; serve.mjs only starts a server when run directly, providing functions when imported).
  • Differences between static and HTTP versions: breadcrumb shows 📁 Directory Name (Local Folder), badge reads "Flowchart Page", footer adds "Local file mode: mobile users need to start the server to access".
  • The 🏠 button is protocol-aware: when location.protocol==='file:', it changes the href to ./index.html.
  • index.html itself does not appear in the card list (early versions did, showing a card named "index"). HTML files that are neither in the manifest nor parseable for flow-stats are categorized as "Other files".

Homepage (Same for Desktop/Mobile)

  • Breadcrumb: 🏠 Flowcharts / Subdirectory (Current), each level clickable.
  • Disk path block: Shows . out directly, with a "Copy Path" button—no need to guess when sending raw files or locating source files.
  • Search + Sort + Summary: Filter by title/filename. Sort options: "Recent First / By Name / By Modified Time". Summary: 3 diagrams · 36 steps / 47 connections · 4 items need attention (in 2 diagrams).
  • Recent opens: Clicking a card records it in flowviz-recent (max 8 items), pinned to top by default with a "Recently Opened" badge.
  • Cards also show 🔒 Locked X / Set to X badges (read from local localStorage).
  • Text adapts by device: The same HTML contains two sets of hints, switched via @media (hover:none),(pointer:coarse) (more accurate than width-based detection; works for landscape mobile as well)—
    • Desktop (.only-pc): 🖱️ Click card to open. In diagram: scroll to zoom, drag to pan. Use "Display Settings" at top right for dark/light.
    • Mobile (.only-touch): 📱 Tap card to open. In diagram: single-finger drag, pinch zoom, tap steps for details...
    • Card badges like "Tap Here on Mobile" have .touchonly, hidden on desktop. The preview page badge is replaced with neutral "Side-by-Side Preview · For Desktop" (understood by both).
    • Flowchart page footer/sidebar hints: desktop sees Scroll to zoom · Drag to pan · Esc, mobile sees Single finger drag · Pinch zoom.
  • Duplicate flowchart names (e.g., xxx - Copy.html and xxx.html) can't be distinguished by title alone → these cards show the filename below the title.
  • Thumbnails matched by normalized name: Spaces, -portrait/-landscape suffixes are stripped before matching, so example-flow - Copy.html can match example-flow-portrait - Copy.svg (even if the suffix position differs).
  • Directory page response must include cache-control: no-store: It's dynamically generated; once cached, the browser won't see new versions (a previous issue where a user repeatedly saw an old, plain list and thought "the homepage is too bare, no thumbnails").

Mobile Interactions

Single-finger drag to pan, pinch to zoom, tap a node for details, bottom "Fit" button to reset to full view. On mobile, Overview/Detail is a covering bottom drawer (position:fixed): slides up when a node is tapped, collapses when tapping empty space, manually opened via the "Overview" button in the bottom bar. It's an overlay, not a flex sibling—so opening/closing the panel doesn't change the canvas height (measured: 607→607), avoiding the "canvas shrinks → re-fit → diagram gets cropped" cascade.

Two implementation details related to "zoom looks wrong":

  • Dot grid background is inside #vp, covering 3× the canvas area, centered on the content (x=-W, y=-H, w=3W, h=3H). This ensures the background follows zoom and pan, and the viewer never sees a hard edge of the background rectangle (in early versions, the background was only the canvas size, so zooming to 150% and panning revealed an edge).
  • The HTML version does not render an SVG title block; only the exported .svg includes it (the page already has a title in the top bar). Otherwise, zooming would cause the title to overlap nodes.
  • On narrow screens, switching to landscape always fits to full width: the criterion is canvas width / content width > fit × 1.8 and content aspect ratio ≤ 1.2 for "fit by width" mode. Landscape is a flat 1.58 aspect ratio; on a 390px screen, fitting by width would zoom to 150% (showing only a corner). Now it's stable at 100%.

5. Experience Rules for Business Process Diagrams

  1. Node granularity: One node = one deliverable action ("Write script" is a node; "Think carefully" is not). 8–16 nodes per diagram is most comfortable; above 20, split into main diagram + sub-processes.
  2. Use statuses effectively: done/doing/todo/risk/hold are not decoration. In real processes, blocks like "Awaiting confirmation / waiting for assets" should be marked hold, and "Must be manually reviewed" marked risk—this is what makes a flowchart better than a document.
  3. Decision nodes only for true branches: {...} is for decisions that take different paths. For "check something then continue," use milestone.
  4. Time windows should come from materials as much as possible, not fabricated. If materials lack it, leave blank; don't guess.
  5. Copyright/compliance/red-line nodes must be explicitly drawn and noted in the note; otherwise, the diagram seems fine but may lead to issues.
  6. Avoid horizontal LR for long chains: A 13-step chain as LR creates a 4:1 rectangle (1980×489); on a 1100px wide canvas, fitting results in 0.55× zoom, making text illegible. However, column-based layout (landscape) can compress the same diagram to about 1.6:1, fitting a screen without shrinking text. Experience: For chain flows that need horizontal layout, use columns, not LR.
  7. How to choose between the two layouts: Mobile → portrait (single column scroll, intuitive for vertical orientation); Desktop → landscape (columns, fits one screen). autoVariant() threshold is width < 760px or aspect ratio < 1.08 → portrait.
  8. Column reading direction remains downward: No snake pattern (alternating up/down), as reverse columns confuse arrow direction. Cross-column connections run through inter-column gaps (when a row has only one node) or bottom corridor (when a row has multiple nodes), never crossing through nodes.
  9. When a group spans columns, it is automatically split into segments labeled with "(cont.)" to avoid a single rectangle enclosing unrelated nodes.

6. Verified Records (2024-10-04)

  • Input formats: JSON / Mermaid subset / ```mermaid code blocks in .md—all three paths tested and passed.
  • Mermaid round-trip: Parsing → re-export .mmd, correctly preserves decision nodes {}, dashed lines -.->, edge labels -->|with|, subgraph groupings.
  • Two layouts (13 nodes / 13 edges / 3 groups sample flow): portrait 533×1936 (single column, 12 rows); landscape 1119×710 (auto 3 columns × 4 rows, aspect ratio 1.58, fits one screen). 7-node sample flow: portrait 533×890, landscape 806×459 (auto 2 columns).
  • Auto-layout selection (CDP device simulation): 390×844 → show=portrait; 1440×820 → show=landscape.
  • Mobile layout (390×844 measured heights): canvas 364 / overview panel 253 / footer 81, overview content fully rendered (panelLen 1533).
  • Touch gestures (CDP synthesized TouchEvent test): single-finger drag pan ✓, two-finger pinch zoom ✓, tap to select node with detail popup ← Back to Overview | S | Day T 12:00 Start | Start | Completed ✓. Mouse path also passed all three.
  • Interactions: Click node → detail card + upstream/downstream highlights + rest dim; Legend filter writes data-hide-status; Search hit count; +/0 keys zoom and fit to 100%; Light/dark theme switch; Both layouts maintain selection/search/filter state.
  • Loading period exception check (Runtime.exceptionThrown): Both sample pages 0 exceptions.
  • Enhanced styling: Card-style nodes (status color vertical bar + soft shadow), type badges, time pills, rounded orthogonal bypasses, capsule edge labels, group label capsules, right panel process overview (count + status filter + needs-attention list), dot grid background, icon toolbar.

Past Mistakes (Fixed in Code; Documented to Save Time Later)

  1. Mermaid decision node not rendered: touch() wrote to shape field, but normalize() read kind—field name mismatch, diamonds rendered as rectangles.
  2. Label quotes eaten: Label "Real Exam Adaptation" became Label "Real Exam Adaptation, due to unconditionally stripping leading/trailing quotes; fixed to strip only when both match.
  3. Zoom percentage mismatch: SVG viewBox applied its own meet scaling, making the transform cumulative; "100%" showed as 26%. Changed to compute in element pixel space view{K,TX,TY} then reverse-solve.
  4. PowerShell Set-Content adds UTF-8 BOM, breaking #!/usr/bin/env node with a SyntaxError. Don't use shell to write files for this script.
  5. b.members undefined: Group band object missing members, diagram with group labels crashed directly.
  6. Fit-to-screen shrinks long flowcharts to 26%: Changed to "if fit and width-fill differ by less than 1.8×, use fit; otherwise, width-fill + top-aligned."
  7. Diamond text overflow: Decision nodes recalculated size based on inscribed rectangle condition, then Is it syntax or discourse? fits in one line.
  8. ID conflict with both layouts: renderSvg's <g id="vp"> lacked version suffix; client getElementById('vp-landscape') returned null → paint() threw an exception, entire initialization aborted, overview panel blank (symptom: aside only 25px). All ids (defs/filter/pattern/marker/vp) must include -${vid}.
  9. wrap parameter semantics reversed: It means "rows per column"; no wrap should be R = L (= number of rows), not R = 1 (R=1 made every layer a single-column horizontal strip, turning portrait into 3476×101).
  10. CDP exception playback is misleading: After Runtime.enable, exceptions from the previous document are also delivered. Before determining "whether the page really errored," first Page.navigate to about:blank then to the target page; otherwise, old page errors are counted against the new page.
  11. Tap-to-select verification requires fitting first: After gesture manipulation, the target node may be outside the viewport; elementFromPoint naturally misses. This was a test script problem, not a page issue.
  12. CDP verification stole user's tab: webctl shot / directly grabbing the first page target would navigate the user's active tab; while the user was also browsing, both parties interrupted each other (tested: I navigated, user searched and navigated back, location changed to an internship site every tens of seconds). Changed to tools/cdp.mjs's withOwnTab()—create a temporary tab via Target.createTarget, poll for it in /json/list, close via Target.closeTarget when done. Even so, the new tab still steals focus; ask the user if they are actively browsing before using.
  13. "Extra" search matches are not bugs: Searching for mouth yields 2 hits because search also checks description/notes ("calibration") and id (node id j). Fix is not to change the number, but to mark invisible hits (orange dashed circle + tiered counting + match list).
  14. Background tabs cannot be screenshotted: Target.createTarget({background:true}) doesn't steal focus, but background tabs don't produce frames; Page.captureScreenshot will hang forever (tested: 120s timeout). Screenshots require a foreground tab (steals focus). Background tabs are only suitable for pure geometry measurements (getBoundingClientRect / getComputedStyle work normally). Also, CSS transitions don't advance in background tabs—inject *{transition:none!important} before measuring geometry, otherwise you'll measure animation start state (I misjudged the drawer's rect.x=398 as "not opened," when it was just the start of a throttled transition).
  15. Output intended "for desktop" was also labeled "Tap here on mobile": File list badges only looked at the extension. Now files starting with 【 or _ are separately labeled "For desktop; do not tap on mobile" and grayed out—the condition must be updated together with the display text.
  16. Verification artifacts should not be placed in the served directory: shot-*.png and probe scripts appeared in the mobile file list. Unified into a non-served directory (e.g., out/_shots/), not in the output directory served by serve.mjs.
  17. Drawer list reordering under user's finger = clicking the wrong item: The embedded snapshot originally placed "current page first," while the fetch('flow-index.json') response was sorted by mtime—about 100ms after opening the drawer, the list jumped; the user's tap hit the wrong entry, resulting in "clicked nothing / can't go back." Two fixes: List now always sorts by filename (snapshot and list order are always consistent), current page is rendered as a non-link. Lesson: Any list that "fetches asynchronously after opening" must guarantee stable order.
  18. Changes to serve.mjs require a server restart: The page template is in the process memory; without restart, it remains the old version (I spent a round debugging "homepage missing h1" because of this).
  19. Missing cache-control on the directory page caused the user to always see an old homepage: The user said twice "the desktop homepage seems too bare / no thumbnails," but I had already added cards and thumbnails; the browser was using a cached old response. Dynamically generated HTML must have no-store.
  20. Start-Process with detached start still kills the service when the command ends: The sandbox's process tree cleanup kills the service silently (manifested as "cannot connect to remote server" on the next request). Use a harness background task, or let the user run the command in the terminal directly.
  21. Don't use similarly named JSON in the directory as input: A directory contained a-flow.json; the name looked like flow A, but the content was flow B's spec. Running render on it directly overwrote the existing a-flow.html with different content. Check the JSON's title/node count before rendering, and prefer input that matches the output filename.
  22. Chinese comments with straight double quotes can truncate JS strings: " // saying "pure filename" list" ends prematurely. Fell into this same trap three times: in Chinese comments, either avoid quotes or use 「」.
  23. No "directory homepage" under file://: href="./" locally falls back to the browser's plain filename list; users see "only filenames." The dynamically generated directory page must have a separate static index.html, and the homepage button must switch targets based on protocol.
  24. Screenshot method when no debug browser is available: In the sandbox, Edge fails to start (mojo named pipe denied), but msedge --headless=new --screenshot=... --window-size=W,H <url> for a one-shot render-and-write works (requires unrestricted permission). Can capture file:// local pages, and simulate mobile/desktop sizes. This was key to debugging "mobile homepage doesn't show cards" and "summary shows 0."
  25. Regular expressions in template strings need double backslashes: \d/\s in template strings degenerate to d/s, breaking the regex on the page (the summary once showed "0 steps total"). Use \\d, \\s for regex in page scripts.
  26. Text should not be hardcoded for a single device: A desktop homepage saying "Tap here on mobile" or a mobile flowchart teaching "scroll to zoom/double-click/Esc" is "device mismatch." Solution: write two sets in the same place (.only-pc / .only-touch) + @media (hover:none),(pointer:coarse) switch—using input method is more accurate than screen width (landscape phones, narrow-window desktops are handled correctly).
  27. When duplicate-named flowcharts appear in the same directory, cards look identical: xxx - Copy.html and xxx.html have the same title; users can't tell them apart. When titles are duplicated, add a filename line. Thumbnails should also be matched by normalized names (strip spaces and -portrait/-landscape).
  28. \n in template strings becomes a real newline: In page JS written inside templates like homeScript(), '...\n\n' + x becomes a string literal containing actual newlines → page script syntax error (node --check on serve.mjs won't catch it). Use \\n. Verification: Extract the two <scripts> generated by buildHome() and run node --check on each separately (I added this regression step).
  29. Determining "if content is identical" should be semantic, not by file hash: The full page hash will inevitably differ (rendering time...). Use a semantic process fingerprint as described earlier.

相关技能