Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ test-results/
playwright-report/
resources/vc_redist.x64.exe
resources/vc_redist.x64.exe.tmp
docs/screenshots/
80 changes: 80 additions & 0 deletions docs/view-flow-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# View/Modal Flow Documentation — Plan

Issue: [#226](https://github.com/Comfy-Org/ComfyUI-Desktop-2.0-Beta/issues/226)

## Goal

Generate a living, regenerable diagram of how all screens (views and modals) in Desktop 2.0 connect to each other, with visual previews. Ideally portable into Figma for designers.

## Current State

The app has **6 sidebar tab views** + **7 modal views** + **1 global ModalDialog**, all orchestrated via events in `App.vue`:

- **Tab views:** Dashboard, InstallationList, Running, Models, Media, Settings
- **Modal views:** DetailModal, ConsoleModal, ProgressModal, NewInstallModal, QuickInstallModal, TrackModal, LoadSnapshotModal
- **Global:** ModalDialog (alerts/confirms/prompts/selects)

There are 2 e2e test files using Playwright but no screenshot-capture infrastructure yet.

---

## Phase 1: Automated Screenshot Capture (Playwright)

Add a Playwright test file (`e2e/view-flow-screenshots.spec.ts`) that navigates to each view/modal state and captures a screenshot to `docs/screenshots/`.

- Mock the Electron IPC layer (already partially done in `e2e/support/`) to render each view with representative dummy data.
- Initial output: 10 PNG files covering 6 tab views + 4 modals (empty/welcome states).
- Future: add populated-state screenshots for DetailModal, ConsoleModal, ProgressModal once mock data seeding is implemented (~13–14 total).

**Effort:** 2–3 days

## Phase 2: Flow Graph Generator Script

Create a Node script (`scripts/generate-view-flow.ts`) that:

1. **Parses `App.vue`** and all `views/*.vue` files to extract `defineEmits` and `@event` bindings (the emit→handler wiring).
2. Builds a directed graph of view→view transitions.
3. Outputs a **Mermaid diagram** (`docs/view-flow.md`) with clickable links to source files.
4. Optionally outputs an **HTML page** (`docs/view-flow.html`) that renders the graph with screenshot thumbnails embedded at each node (using the Phase 1 PNGs).

This script can be re-run anytime the views change. Could be added to CI.

**Effort:** 1–2 days

## Phase 3: Figma Integration

### Feasibility

| Approach | Feasibility | Notes |
|----------|-------------|-------|
| **Figma REST API** | ❌ Cannot create/modify file content | REST API is read-only for nodes/images. No endpoint to create frames or upload image fills. |
| **Figma Plugin** | ✅ Best path | Plugin API has full write access: `createFrame()`, `createImage()`, `createConnector()`. |
| **Figma MCP / Make** | 🟡 Emerging | Not yet stable enough for programmatic pipelines. Worth revisiting. |
| **FigJam board** | ✅ Quick alternative | Connectors + image frames are a natural fit. Possibly better UX for designers. |

### Recommended Approach

Build a small **Figma/FigJam plugin** that:

1. Accepts a JSON file (generated by Phase 2) containing: node list (id, label, screenshot URL/base64), edge list (from, to, label).
2. Creates a frame per view with the screenshot as an image fill.
3. Draws connectors between frames labeled with the event name.
4. Designers run the plugin whenever they want the latest flow — it clears the old one and regenerates.

**Effort:** 2–3 days (depends on Phase 1 + 2 outputs)

## Phase 4: CI Integration (Optional)

- Add a GitHub Actions workflow that runs the Playwright screenshot capture + flow generator on every PR that touches `src/renderer/src/views/` or `src/renderer/src/components/`.
- Commit updated screenshots and Mermaid docs automatically, or post them as PR comments.

**Effort:** 1 day

## Summary

| Phase | Effort | Dependency |
|-------|--------|------------|
| Phase 1 (Screenshots) | 2–3 days | Requires expanding e2e IPC mocks |
| Phase 2 (Graph generator) | 1–2 days | None |
| Phase 3 (Figma plugin) | 2–3 days | Phase 1 + 2 outputs |
| Phase 4 (CI) | 1 day | Phase 1 + 2 |
99 changes: 99 additions & 0 deletions docs/view-flow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# View/Modal Flow — Desktop 2.0

> Auto-generated by `scripts/generate-view-flow.mjs` — do not edit manually.
>
> Run: `node scripts/generate-view-flow.mjs`

## Navigation Graph

```mermaid
flowchart TD

%% Tab Views
dashboard-view["📊 Dashboard"]
installation-list["📦 Installs"]
running-view["▶️ Running"]
models-view["📁 Models"]
media-view["🖼️ Media"]
settings-view["⚙️ Settings"]

%% Modals
quick-install-modal("⚡ Quick Install Modal")
detail-modal("🔍 Detail Modal")
console-modal("💻 Console Modal")
progress-modal("⏳ Progress Modal")
new-install-modal("➕ New Install Modal")
track-modal("📂 Track Existing Modal")
load-snapshot-modal("📸 Load Snapshot Modal")

sidebar{{"🧭 Sidebar"}}

%% Sidebar navigation
sidebar --> dashboard-view
sidebar --> installation-list
sidebar --> running-view
sidebar --> models-view
sidebar --> media-view
sidebar --> settings-view

%% View → Modal transitions
dashboard-view -->|show-quick-install| quick-install-modal
dashboard-view -->|show-settings| settings-view
dashboard-view -->|show-detail| detail-modal
dashboard-view -->|show-console| console-modal
dashboard-view -->|show-progress| progress-modal
installation-list -->|show-detail| detail-modal
installation-list -->|show-migrate| detail-modal
installation-list -->|show-console| console-modal
installation-list -->|show-progress| progress-modal
installation-list -->|show-new-install| new-install-modal
installation-list -->|show-track| track-modal
installation-list -->|show-load-snapshot| load-snapshot-modal
running-view -->|show-detail| detail-modal
running-view -->|show-console| console-modal
running-view -->|show-progress| progress-modal

%% Modal → View/Modal transitions
detail-modal -->|show-progress| progress-modal
detail-modal -->|navigate-list| installation-list
progress-modal -->|show-detail| detail-modal
progress-modal -->|show-console| console-modal
new-install-modal -->|show-progress| progress-modal
new-install-modal -->|navigate-list| installation-list
quick-install-modal -->|show-progress| progress-modal
track-modal -->|navigate-list| installation-list
load-snapshot-modal -->|show-progress| progress-modal

%% Styles
classDef tabView fill:#1a1a2e,stroke:#00d9ff,color:#e0e0e0,stroke-width:2px
classDef modal fill:#1a1a2e,stroke:#ff6b6b,color:#e0e0e0,stroke-width:2px
classDef nav fill:#1a1a2e,stroke:#ffd93d,color:#e0e0e0,stroke-width:2px
class dashboard-view,installation-list,running-view,models-view,media-view,settings-view tabView
class quick-install-modal,detail-modal,console-modal,progress-modal,new-install-modal,track-modal,load-snapshot-modal modal
class sidebar nav
```

## Legend

- **Blue border** = Tab view (sidebar navigation)
- **Red border** = Modal (overlay)
- **Yellow border** = Sidebar navigator
- Edge labels show the Vue event name that triggers the transition

## Source Files

| View/Modal | Source |
|------------|--------|
| 📊 Dashboard | [`src/renderer/src/views/DashboardView.vue`](../src/renderer/src/views/DashboardView.vue) |
| 📦 Installs | [`src/renderer/src/views/InstallationList.vue`](../src/renderer/src/views/InstallationList.vue) |
| ▶️ Running | [`src/renderer/src/views/RunningView.vue`](../src/renderer/src/views/RunningView.vue) |
| 📁 Models | [`src/renderer/src/views/ModelsView.vue`](../src/renderer/src/views/ModelsView.vue) |
| 🖼️ Media | [`src/renderer/src/views/MediaView.vue`](../src/renderer/src/views/MediaView.vue) |
| ⚙️ Settings | [`src/renderer/src/views/SettingsView.vue`](../src/renderer/src/views/SettingsView.vue) |
| ⚡ Quick Install Modal | [`src/renderer/src/views/QuickInstallModal.vue`](../src/renderer/src/views/QuickInstallModal.vue) |
| 🔍 Detail Modal | [`src/renderer/src/views/DetailModal.vue`](../src/renderer/src/views/DetailModal.vue) |
| 💻 Console Modal | [`src/renderer/src/views/ConsoleModal.vue`](../src/renderer/src/views/ConsoleModal.vue) |
| ⏳ Progress Modal | [`src/renderer/src/views/ProgressModal.vue`](../src/renderer/src/views/ProgressModal.vue) |
| ➕ New Install Modal | [`src/renderer/src/views/NewInstallModal.vue`](../src/renderer/src/views/NewInstallModal.vue) |
| 📂 Track Existing Modal | [`src/renderer/src/views/TrackModal.vue`](../src/renderer/src/views/TrackModal.vue) |
| 📸 Load Snapshot Modal | [`src/renderer/src/views/LoadSnapshotModal.vue`](../src/renderer/src/views/LoadSnapshotModal.vue) |
126 changes: 126 additions & 0 deletions e2e/view-flow-screenshots.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/**
* View Flow Screenshots — Issue #226
*
* Captures a screenshot of every major view and modal in the launcher UI.
* Screenshots are saved to docs/screenshots/ and can be used by the
* flow-graph generator (Phase 2) and Figma plugin (Phase 3).
*
* Run: pnpm exec playwright test e2e/view-flow-screenshots.spec.ts
*/
import { expect, test, type Page } from '@playwright/test'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { type LauncherAppHandle, launchLauncherApp } from './support/electronHarness'

const __dirname = path.dirname(fileURLToPath(import.meta.url))
const SCREENSHOT_DIR = path.resolve(__dirname, '..', 'docs', 'screenshots')

const UI_TIMEOUT = 12_000

let handle: LauncherAppHandle
let page: Page

async function screenshot(name: string): Promise<void> {
await page.screenshot({
path: path.join(SCREENSHOT_DIR, `${name}.png`),
type: 'png',
})
}

async function clickSidebar(text: string): Promise<void> {
const item = page.locator('.sidebar-item', { hasText: text })
await item.click()
await expect(item).toHaveClass(/active/, { timeout: UI_TIMEOUT })
}

async function openModal(locator: ReturnType<Page['locator']>, screenshotName: string): Promise<void> {
await expect(locator).toBeVisible({ timeout: UI_TIMEOUT })
await locator.click()
await expect(page.locator('.modal-overlay, .modal, [class*="modal"]')).toBeVisible({ timeout: UI_TIMEOUT })
await screenshot(screenshotName)
await page.keyboard.press('Escape')
await expect(page.locator('.modal-overlay, .modal, [class*="modal"]')).toBeHidden({ timeout: UI_TIMEOUT })
}

test.describe('View Flow Screenshots (#226)', () => {
test.beforeAll(async () => {
handle = await launchLauncherApp()
page = await handle.application.firstWindow()

await expect(page.locator('#app')).toBeAttached({ timeout: UI_TIMEOUT })
await expect(page.locator('.sidebar')).toBeVisible({ timeout: UI_TIMEOUT })
// Wait for Vue to finish mounting and i18n to load
await expect(page.locator('.sidebar-item')).toHaveCount(7, { timeout: UI_TIMEOUT })
})

test.afterAll(async () => {
await handle.cleanup()
})

// ── Tab Views ──────────────────────────────────────────────

test('01 — Dashboard @macos @windows @linux', async () => {
await screenshot('01-dashboard')
})

test('02 — Installation List @macos @windows @linux', async () => {
await clickSidebar('Installs')
await screenshot('02-installation-list')
})

test('03 — Running @macos @windows @linux', async () => {
await clickSidebar('Running')
await screenshot('03-running')
})

test('04 — Models @macos @windows @linux', async () => {
await clickSidebar('Models')
await screenshot('04-models')
})

test('05 — Media @macos @windows @linux', async () => {
await clickSidebar('Media')
await screenshot('05-media')
})

test('06 — Settings @macos @windows @linux', async () => {
await clickSidebar('Settings')
await screenshot('06-settings')
})

// ── Modals (opened from Installation List) ─────────────────

test('07 — New Install modal @macos @windows @linux', async () => {
await clickSidebar('Installs')
await openModal(
page.locator('.toolbar button', { hasText: 'New Install' }),
'07-new-install-modal',
)
})

test('08 — Track Existing modal @macos @windows @linux', async () => {
await clickSidebar('Installs')
await openModal(
page.locator('button', { hasText: 'Track Existing' }),
'08-track-modal',
)
})

test('09 — Load Snapshot modal @macos @windows @linux', async () => {
await clickSidebar('Installs')
await openModal(
page.locator('button', { hasText: 'Load Snapshot' }),
'09-load-snapshot-modal',
)
})

// ── Dashboard modal entry points ───────────────────────────

test('10 — Quick Install modal @macos @windows @linux', async () => {
await clickSidebar('Dashboard')
await openModal(
page.locator('button', { hasText: 'Install ComfyUI' }),
'10-quick-install-modal',
)
})
})
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"format:check": "prettier --check \"src/**/*.{ts,vue}\" \"*.{json,ts,yml}\"",
"test": "vitest run",
"test:e2e": "playwright test",
"generate:view-flow": "node scripts/generate-view-flow.mjs",
"test:e2e:macos": "playwright test --project=macos",
"test:e2e:windows": "playwright test --project=windows",
"test:e2e:linux": "playwright test --project=linux",
Expand Down
Loading
Loading