Compare commits

...

2 Commits

Author SHA1 Message Date
GitHub Action
70182c8ee4 [automated] Apply ESLint and Oxfmt fixes 2026-07-02 20:47:52 +00:00
huang47
092409ee93 docs: consolidate agent guidance into auto-loaded nested AGENTS.md files 2026-07-02 13:43:30 -07:00
14 changed files with 274 additions and 545 deletions

View File

@@ -10,8 +10,7 @@ You are reviewing Playwright E2E test code in `browser_tests/`. Focus on issues
Reference docs (read if you need full context):
- `browser_tests/README.md` — setup, patterns, screenshot workflow
- `browser_tests/AGENTS.md` — directory structure, fixture overview
- `docs/guidance/playwright.md` — type assertion rules, test tags, forbidden patterns
- `browser_tests/AGENTS.md` — directory structure, fixture overview, type assertion rules, test tags, forbidden patterns
- `.claude/skills/writing-playwright-tests/SKILL.md` — anti-patterns, retry patterns, Vue Nodes vs LiteGraph decision guide
## Checks
@@ -49,7 +48,7 @@ Reference docs (read if you need full context):
11. **Missing test tags** — Every `test.describe` should have `tag` with at least one of: `@smoke`, `@slow`, `@screenshot`, `@canvas`, `@node`, `@widget`, `@mobile`, `@2x`. See `.claude/skills/writing-playwright-tests/SKILL.md` for when to use each.
12. **`as any` type assertions** — Forbidden in E2E tests. Use specific type assertions or test-local type helpers. See `docs/guidance/playwright.md` for acceptable patterns.
12. **`as any` type assertions** — Forbidden in E2E tests. Use specific type assertions or test-local type helpers. See `browser_tests/AGENTS.md` for acceptable patterns.
13. **Screenshot tests without masking dynamic content** — Timestamps, version numbers, or other non-deterministic content in screenshots will cause flakes. Use `mask` option.

View File

@@ -143,7 +143,7 @@ A regression test that never proves red does not pin the bug.
| New store test or store mock | [`docs/testing/vitest-patterns.md`](../../../docs/testing/vitest-patterns.md) setup + [`docs/testing/store-testing.md`](../../../docs/testing/store-testing.md) |
| New component test | Top note in [`docs/testing/component-testing.md`](../../../docs/testing/component-testing.md) |
| `vue-i18n` in a component test | [`docs/testing/vitest-patterns.md`](../../../docs/testing/vitest-patterns.md) + [`src/components/searchbox/v2/__test__/testUtils.ts`](../../../src/components/searchbox/v2/__test__/testUtils.ts) |
| Cast around a mock | [`docs/guidance/typescript.md`](../../../docs/guidance/typescript.md) -> "Type Assertion Hierarchy" |
| Cast around a mock | [`src/AGENTS.md`](../../../src/AGENTS.md) -> "TypeScript" (type assertion hierarchy) |
## Key Files to Read

View File

@@ -1,37 +0,0 @@
Use the Vue 3 Composition API instead of the Options API when writing Vue components. An exception is when overriding or extending a PrimeVue component for compatibility, you may use the Options API.
Use setup() function for component logic
Utilize ref and reactive for reactive state
Implement computed properties with computed()
Use watch and watchEffect for side effects
Implement lifecycle hooks with onMounted, onUpdated, etc.
Utilize provide/inject for dependency injection
Use vue 3.5 style of default prop declaration.
Use Tailwind CSS for styling
Leverage VueUse functions for performance-enhancing styles
Use es-toolkit for utility functions
Use TypeScript for type safety
Implement proper props and emits definitions
Utilize Vue 3's Teleport component when needed
Use Suspense for async components
Implement proper error handling
Follow Vue 3 style guide and naming conventions
Use Vite for fast development and building
Use vue-i18n in composition API for any string literals. Place new translation entries in src/locales/en/main.json.

View File

@@ -1,6 +1,6 @@
# Storybook Guidelines
See `@docs/guidance/storybook.md` for story patterns (auto-loaded for `*.stories.ts`).
See the Storybook section of `src/AGENTS.md` for story structure and variant conventions.
## Available Context

View File

@@ -1,6 +1,6 @@
# Repository Guidelines
See @docs/guidance/\*.md for file-type-specific conventions (auto-loaded by glob).
Directory-specific conventions live in nested `AGENTS.md` files — `src/AGENTS.md` (TypeScript, unit tests, Vue, Storybook, design standards) and `browser_tests/AGENTS.md` (Playwright E2E) — which agents load automatically when working in those directories.
## Project Structure & Module Organization
@@ -185,13 +185,13 @@ This project uses **pnpm**. Always prefer scripts defined in `package.json` (e.g
## Design Standards
Before implementing any user-facing feature, consult the [Comfy Design Standards](https://www.figma.com/design/QreIv5htUaSICNuO2VBHw0/Comfy-Design-Standards) Figma file. Use the Figma MCP to fetch it live — the file is the single source of truth and may be updated by designers at any time.
When adding new UI or changing visual design, consult the [Comfy Design Standards](https://www.figma.com/design/QreIv5htUaSICNuO2VBHw0/Comfy-Design-Standards) Figma file. Use the Figma MCP to fetch it live — the file is the single source of truth and may be updated by designers at any time. Skip this for refactors, bugfixes, and logic-only changes; if the Figma MCP is unavailable, say so in the PR description instead of skipping silently.
See `docs/guidance/design-standards.md` for Figma file keys, section node IDs, and component references.
## Testing Guidelines
See @docs/testing/\*.md for detailed patterns.
Read `docs/testing/<topic>.md` (unit-testing, component-testing, store-testing, vitest-patterns) for detailed patterns when writing the corresponding kind of test.
- Frameworks:
- Vitest (unit/component, happy-dom)
@@ -218,7 +218,7 @@ See @docs/testing/\*.md for detailed patterns.
3. Keep your module mocks contained
Do not use global mutable state within the test file
Use `vi.hoisted()` if necessary to allow for per-test Arrange phase manipulation of deeper mock state
4. For Component testing, prefer [@testing-library/vue](https://testing-library.com/docs/vue-testing-library/intro/) with `@testing-library/user-event` for user-centric, behavioral tests. [Vue Test Utils](https://test-utils.vuejs.org/) is also accepted, especially for tests that need direct access to the component wrapper (e.g., `findComponent`, `emitted()`). Follow the advice [about making components easy to test](https://test-utils.vuejs.org/guide/essentials/easy-to-test.html)
4. For Component testing, use [@testing-library/vue](https://testing-library.com/docs/vue-testing-library/intro/) with `@testing-library/user-event` for user-centric, behavioral tests — an ESLint rule bans `@vue/test-utils` imports in new tests. Follow the advice [about making components easy to test](https://test-utils.vuejs.org/guide/essentials/easy-to-test.html)
5. Aim for behavioral coverage of critical and new features
### Playwright / Browser / E2E Tests
@@ -226,7 +226,7 @@ See @docs/testing/\*.md for detailed patterns.
1. Follow the Best Practices described [in the Playwright documentation](https://playwright.dev/docs/best-practices)
2. Do not use waitForTimeout, use Locator actions and [retrying assertions](https://playwright.dev/docs/test-assertions#auto-retrying-assertions)
3. Tags like `@mobile`, `@2x` are respected by config and should be used for relevant tests
4. Type all API mock responses in `route.fulfill()` using generated types or schemas from `packages/ingest-types`, `packages/registry-types`, `src/workbench/extensions/manager/types/generatedManagerTypes.ts`, or `src/schemas/` — see `docs/guidance/playwright.md` for the full source-of-truth table
4. Type all API mock responses in `route.fulfill()` using generated types or schemas from `packages/ingest-types`, `packages/registry-types`, `src/workbench/extensions/manager/types/generatedManagerTypes.ts`, or `src/schemas/` — see `browser_tests/AGENTS.md` for the full source-of-truth table
## External Resources

View File

@@ -1,9 +1,19 @@
# E2E Testing Guidelines
See `@docs/guidance/playwright.md` for Playwright best practices (auto-loaded for `*.spec.ts`).
See `@browser_tests/FLAKE_PREVENTION_RULES.md` when triaging or editing
See `browser_tests/FLAKE_PREVENTION_RULES.md` when triaging or editing
flaky browser tests.
## Core Rules
- Follow [Playwright Best Practices](https://playwright.dev/docs/best-practices)
- Never use `waitForTimeout` — use retrying assertions (`toBeVisible`,
`toHaveText`), `expect.poll()`, or `toPass()`
- Prefer specific selectors (role, label, test-id)
- Prefer helpers from `fixtures/helpers/` that wrap real user interactions
(`comfyPage.settings.setSetting`, `comfyPage.workflow.loadWorkflow`,
`comfyPage.nodeOps`) over hand-rolled equivalents
- Tag viewport-specific tests: `@mobile` (mobile viewport), `@2x` (high DPI)
## Directory Structure
```text
@@ -62,6 +72,46 @@ flowchart TD
E -- No, coordinates actions<br/>across the app --> H[fixtures/helpers/]
```
## Test Structure: Arrange/Act/Assert
1. All mock setup, state resets, and fixture arrangement belongs in `test.beforeEach()` or Playwright fixtures
2. Inside `test()`, only act (user actions) and assert
3. Never call `clearAllMocks` or reset mock state mid-test
```typescript
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.workflow.loadWorkflow('test.json')
})
test('should do something', async ({ comfyPage }) => {
await comfyPage.menu.topbar.click()
await expect(comfyPage.menu.nodeLibraryTab.root).toBeVisible()
})
```
## Creating New Test Helpers
New domain-specific test helpers (e.g., `AssetHelper`, `JobHelper`) must be
registered as Playwright fixtures via `base.extend()` — do **not** add them as
properties on `ComfyPage`. Extend `@playwright/test` directly (not
`comfyPageFixture`) so fixtures stay composable via `mergeTests`; the callback
after `use()` gives automatic teardown.
```typescript
// browser_tests/fixtures/assetFixture.ts
import { test as base } from '@playwright/test'
export const test = base.extend<{
assetHelper: AssetHelper
}>({
assetHelper: async ({ page }, use) => {
const helper = new AssetHelper(page)
await helper.setup()
await use(helper)
await helper.cleanup() // automatic teardown
}
})
```
## Page Object Locator Style
Define UI element locators as `public readonly` properties assigned in the constructor — not as getter methods. Getters that simply return a locator add unnecessary indirection and hide the object shape from IDE auto-complete.
@@ -95,7 +145,7 @@ export class MyDialog extends BaseDialog {
When a class has cached locator properties, prefer reusing them in methods rather than rebuilding locators from scratch.
## Polling Assertions
## Assertions
Prefer `expect.poll()` over `expect(async () => { ... }).toPass()` when the block contains a single async call with a single assertion. `expect.poll()` is more readable and gives better error messages (shows actual vs expected on failure).
@@ -113,6 +163,98 @@ await expect(async () => {
Reserve `toPass()` for blocks with multiple assertions or complex async logic that can't be expressed as a single polled value.
Assert preconditions explicitly with a custom message so failures point to the
broken assumption; use `expect.soft()` to verify multiple invariants without
aborting on the first failure:
```typescript
expect(node.widgets, 'Widget count changed — update test fixture').toHaveLength(
4
)
expect.soft(menuItem1).toBeVisible()
expect.soft(menuItem2).toBeVisible()
```
Add assertion methods directly on the page object or helper class
(`await node.expectPinned()`) — do not add custom matchers to `comfyExpect`.
Page object methods are discoverable via IntelliSense without special imports.
## Window Globals & Type Assertions
`window.app`, `window.graph`, and `window.LiteGraph` are optional in the main
app types. Non-null assertions (`!`) are allowed in E2E tests only:
`window.app!.graph!.nodes`. Specific assertions like
`id: 'TestSetting' as TestSettingId` are acceptable; `as any` and
`as unknown as SomeType` are forbidden. Access internal state via
`page.evaluate` and stores directly — don't change public API types to expose
internals.
## When to Use `page.evaluate`
Acceptable (use sparingly): reading internal state that has no UI
representation, or setting up test fixtures (registering extensions, mock
error handlers). Never for actions that have a UI equivalent.
```typescript
// ✅ Reading internal state
const nodeCount = await page.evaluate(() => window.app!.graph!.nodes.length)
// ❌ Bad: setting a widget value programmatically
await page.evaluate(() => {
node.widgets![0].value = 512
})
// ✅ Good: interact like a user
await widgetLocator.click()
await widgetLocator.fill('512')
```
## Test Data
- Check `browser_tests/assets/` for fixtures; use realistic ComfyUI workflows
- When multiple nodes share the same title, use
`vueNodes.getNodeByTitle(name).nth(n)` — Playwright strict mode will fail on
ambiguous locators
- When creating fixture data, import existing Zod schemas and types from
`src/` instead of inventing ad-hoc shapes: `src/schemas/apiSchema.ts` (API
responses, WebSocket messages), `src/schemas/nodeDefSchema.ts` and
`src/schemas/nodeDef/nodeDefSchemaV2.ts` (node definitions),
`src/platform/remote/comfyui/jobs/jobTypes.ts` (Jobs API),
`src/platform/workflow/validation/schemas/workflowSchema.ts` (workflows),
`src/types/metadataTypes.ts` (asset metadata)
## Typed API Mocks
Type every `route.fulfill()` response body using existing schemas or generated
types — never untyped inline JSON. This catches shape mismatches at compile
time instead of through flaky runtime failures.
| Endpoint category | Type source |
| --------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| Cloud-only (hub, billing, workflows) | `@comfyorg/ingest-types` (`packages/ingest-types`, auto-generated from OpenAPI) |
| Registry (releases, nodes, publishers) | `@comfyorg/registry-types` (`packages/registry-types`, auto-generated from OpenAPI) |
| Manager (queue tasks, packages) | `generatedManagerTypes.ts` (`src/workbench/extensions/manager/types/`, auto-generated from OpenAPI) |
| Python backend (queue, history, settings, features) | Manual Zod schemas in `src/schemas/apiSchema.ts` |
| Node definitions | `src/schemas/nodeDefSchema.ts` |
| Templates | `src/platform/workflow/templates/types/template.ts` |
```typescript
// ✅ Import the type and annotate mock data
import type { ReleaseNote } from '@/platform/updates/common/releaseService'
const mockRelease: ReleaseNote = {
id: 1,
project: 'comfyui',
version: 'v0.3.44',
attention: 'medium',
content: '## New Features',
published_at: new Date().toISOString()
}
body: JSON.stringify([mockRelease])
// ❌ Untyped inline JSON — schema drift goes unnoticed
body: JSON.stringify([{ id: 1, project: 'comfyui', version: 'v0.3.44', ... }])
```
## Gotchas
| Symptom | Cause | Fix |
@@ -121,6 +263,13 @@ Reserve `toPass()` for blocks with multiple assertions or complex async logic th
| Context menu empty or wrong items | Node not selected | Select node first: `vueNodes.selectNode()` or `nodeRef.click('title')` |
| `navigateIntoSubgraph` timeout | Node too small in test asset JSON | Use node size `[400, 200]` minimum |
## Running Tests
```bash
pnpm test:browser:local # Run all E2E tests
pnpm test:browser:local --ui # Interactive UI mode
```
## After Making Changes
- Run `pnpm typecheck:browser` after modifying TypeScript files in this directory

View File

@@ -31,4 +31,4 @@ test.beforeEach(async ({ comfyPage }) => {
- Tests requiring VueNodes rendering enable it in `beforeEach` via `comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)` and call `comfyPage.vueNodes.waitForNodes()`.
- Verify node state changes via user-facing indicators (text labels like "Bypassed"/"Muted", pin indicator test IDs) rather than internal properties.
- Color changes are verified via `page.evaluate` accessing node properties, per the guidance in `docs/guidance/playwright.md`.
- Color changes are verified via `page.evaluate` accessing node properties, per the guidance in `browser_tests/AGENTS.md`.

View File

@@ -1,23 +1,23 @@
---
globs:
- 'src/components/**/*.vue'
- 'src/views/**/*.vue'
---
# Comfy Design Standards
Applies when implementing or modifying user-facing components and views.
Reference for implementing user-facing components and views anywhere in the
codebase (`src/components/`, `src/views/`, `src/platform/`, `src/workbench/`,
`apps/`).
## Before Implementing UI Changes
## When to Consult
Consult the **Comfy Design Standards** Figma file to ensure your changes follow the agreed-upon design principles. Use the Figma MCP tool to fetch the current standards:
When adding new UI or changing visual design, fetch the relevant section of
the **Comfy Design Standards** Figma file before implementing. Skip this for
refactors, bugfixes, and logic-only changes. If the Figma MCP is unavailable,
say so in the PR description instead of skipping silently.
The Figma file is the single source of truth. Always fetch it live — do not
rely on cached assumptions:
```javascript
get_figma_data({ fileKey: 'QreIv5htUaSICNuO2VBHw0', nodeId: '0:1' })
```
The Figma file is the single source of truth. Always fetch it live — do not rely on cached assumptions.
> **Note:** The Figma MCP is read-only. It cannot detect changes or diffs between versions. Always fetch the latest state before implementing.
### Key Sections

View File

@@ -1,254 +0,0 @@
---
globs:
- '**/*.spec.ts'
---
# Playwright E2E Test Conventions
See `docs/testing/*.md` for detailed patterns.
## Best Practices
- Follow [Playwright Best Practices](https://playwright.dev/docs/best-practices)
- Do NOT use `waitForTimeout` — use Locator actions and retrying assertions
- Prefer specific selectors (role, label, test-id)
- Test across viewports
## Window Globals
Browser tests access `window.app`, `window.graph`, and `window.LiteGraph` which are
optional in the main app types. Use non-null assertions (`!`) in E2E tests only:
```typescript
window.app!.graph!.nodes
window.LiteGraph!.registered_node_types
```
TODO: Consolidate into a central utility (e.g., `getApp()`) with runtime type checking.
## Type Assertions
Use specific type assertions when needed, never `as any`.
Acceptable:
```typescript
window.app!.extensionManager
id: 'TestSetting' as TestSettingId
type TestSettingId = keyof Settings
```
Forbidden:
```typescript
settings: testData as any
data as unknown as SomeType
```
Access internal state via `page.evaluate` and stores directly — don't change public API types to expose internals.
## Assertion Best Practices
Assert preconditions explicitly with a custom message so failures point to the broken assumption:
```typescript
expect(node.widgets, 'Widget count changed — update test fixture').toHaveLength(
4
)
await node.move(100, 200)
expect.soft(menuItem1).toBeVisible()
expect.soft(menuItem2).toBeVisible()
// Bad — bare expect on a precondition gives no context when it fails
expect(node.widgets).toHaveLength(4)
```
- `expect(x, 'reason')` for precondition checks unrelated to the test's purpose
- `expect.soft()` to verify multiple invariants without aborting on the first failure
## Test Structure: Arrange/Act/Assert
1. All mock setup, state resets, and fixture arrangement belongs in `test.beforeEach()` or Playwright fixtures
2. Inside `test()`, only act (user actions) and assert
3. Never call `clearAllMocks` or reset mock state mid-test
```typescript
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.workflow.loadWorkflow('test.json')
})
test('should do something', async ({ comfyPage }) => {
await comfyPage.menu.topbar.click()
await expect(comfyPage.menu.nodeLibraryTab.root).toBeVisible()
})
```
## Creating New Test Helpers
New domain-specific test helpers (e.g., `AssetHelper`, `JobHelper`) should be
registered as Playwright fixtures via `base.extend()` rather than attached as
properties on `ComfyPage`. This enables automatic setup/teardown.
### Extend `base` from Playwright
Keep each fixture self-contained by extending `@playwright/test` directly.
Compose fixtures together with `mergeTests` when a test needs multiple helpers.
```typescript
// browser_tests/fixtures/assetFixture.ts
import { test as base } from '@playwright/test'
export const test = base.extend<{
assetHelper: AssetHelper
}>({
assetHelper: async ({ page }, use) => {
const helper = new AssetHelper(page)
await helper.setup()
await use(helper)
await helper.cleanup() // automatic teardown
}
})
```
### Rules
- **Do NOT** add new helpers as properties on `ComfyPage`
- Each fixture gets automatic cleanup via the callback after `use()`
- Keep fixtures modular — extend `@playwright/test` base, not
`comfyPageFixture`, so they can be composed via `mergeTests`
## Custom Assertions
Add assertion methods directly on the page object or helper class instead of extending `comfyExpect`. Page object methods are discoverable via IntelliSense without special imports.
```typescript
// ✅ Page object assertions
await node.expectPinned()
await node.expectBypassed()
// ❌ Do not add custom matchers to comfyExpect
```
## Test Tags
- `@mobile` — Mobile viewport tests
- `@2x` — High DPI tests
## Test Data
- Check `browser_tests/assets/` for fixtures
- Use realistic ComfyUI workflows
- When multiple nodes share the same title, use `vueNodes.getNodeByTitle(name).nth(n)` — Playwright strict mode will fail on ambiguous locators
## Fixture Data & Schemas
When creating test fixture data, import or reference existing Zod schemas and TypeScript
types from `src/` instead of inventing ad-hoc shapes. This keeps test data in sync with
production types.
Key schema locations:
- `src/schemas/apiSchema.ts` — API response types (`PromptResponse`, `SystemStats`, `User`, `UserDataFullInfo`, WebSocket messages)
- `src/schemas/nodeDefSchema.ts` — Node definition schema (`ComfyNodeDef`, `InputSpec`, `ComboInputSpec`)
- `src/schemas/nodeDef/nodeDefSchemaV2.ts` — V2 node definition schema
- `src/platform/remote/comfyui/jobs/jobTypes.ts` — Jobs API Zod schemas (`zJobDetail`, `zJobsListResponse`, `zRawJobListItem`)
- `src/platform/workflow/validation/schemas/workflowSchema.ts` — Workflow validation (`ComfyWorkflowJSON`, `ComfyApiWorkflow`)
- `src/types/metadataTypes.ts` — Asset metadata types
## Typed API Mocks
When mocking API responses with `route.fulfill()`, **always** type the response body
using existing schemas or generated types — never use untyped inline JSON objects.
This catches shape mismatches at compile time instead of through flaky runtime failures.
All three generated-type packages (`ingest-types`, `registry-types`, `generatedManagerTypes`)
are auto-generated from their respective OpenAPI specs. Prefer these as the single
source of truth for any mock that targets their endpoints.
### Sources of truth
| Endpoint category | Type source |
| --------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| Cloud-only (hub, billing, workflows) | `@comfyorg/ingest-types` (`packages/ingest-types`, auto-generated from OpenAPI) |
| Registry (releases, nodes, publishers) | `@comfyorg/registry-types` (`packages/registry-types`, auto-generated from OpenAPI) |
| Manager (queue tasks, packages) | `generatedManagerTypes.ts` (`src/workbench/extensions/manager/types/`, auto-generated from OpenAPI) |
| Python backend (queue, history, settings, features) | Manual Zod schemas in `src/schemas/apiSchema.ts` |
| Node definitions | `src/schemas/nodeDefSchema.ts` |
| Templates | `src/platform/workflow/templates/types/template.ts` |
### Patterns
```typescript
// ✅ Import the type and annotate mock data
import type { ReleaseNote } from '@/platform/updates/common/releaseService'
const mockRelease: ReleaseNote = {
id: 1,
project: 'comfyui',
version: 'v0.3.44',
attention: 'medium',
content: '## New Features',
published_at: new Date().toISOString()
}
body: JSON.stringify([mockRelease])
// ❌ Untyped inline JSON — schema drift goes unnoticed
body: JSON.stringify([{ id: 1, project: 'comfyui', version: 'v0.3.44', ... }])
```
## When to Use `page.evaluate`
### Acceptable (use sparingly)
Reading internal state that has no UI representation (prefer locator
assertions whenever possible):
```typescript
// Reading graph/store values
const nodeCount = await page.evaluate(() => window.app!.graph!.nodes.length)
const linkSlot = await page.evaluate(() => window.app!.graph!.links.get(1)?.target_slot)
// Reading computed properties or store state
await page.evaluate(() => useWorkflowStore().activeWorkflow)
// Setting up test fixtures (registering extensions, mock error handlers)
await page.evaluate(() => {
window.app!.registerExtension({ name: 'TestExt', settings: [...] })
})
```
### Avoid
Performing actions that have a UI equivalent — use Playwright locators and user
interactions instead:
```typescript
// Bad: setting a widget value programmatically
await page.evaluate(() => { node.widgets![0].value = 512 })
// Good: click the widget and type the value
await widgetLocator.click()
await widgetLocator.fill('512')
// Bad: dispatching synthetic DOM events
await page.evaluate(() => { btn.dispatchEvent(new MouseEvent('click', ...)) })
// Good: use Playwright's click
await page.getByTestId('more-options-button').click()
// Bad: calling store actions that correspond to user interactions
await page.evaluate(() => { app.queuePrompt(0) })
// Good: click the Queue button
await page.getByRole('button', { name: 'Queue' }).click()
```
### Preferred
Use helper methods from `browser_tests/fixtures/helpers/` that wrap real user
interactions (e.g., `comfyPage.settings.setSetting`, `comfyPage.nodeOps`,
`comfyPage.workflow.loadWorkflow`).
## Running Tests
```bash
pnpm test:browser:local # Run all E2E tests
pnpm test:browser:local --ui # Interactive UI mode
```

View File

@@ -1,59 +0,0 @@
---
globs:
- '**/*.stories.ts'
---
# Storybook Conventions
## File Placement
Place `*.stories.ts` files alongside their components:
```
src/components/MyComponent/
├── MyComponent.vue
└── MyComponent.stories.ts
```
## Story Structure
```typescript
import type { Meta, StoryObj } from '@storybook/vue3'
import ComponentName from './ComponentName.vue'
const meta: Meta<typeof ComponentName> = {
title: 'Category/ComponentName',
component: ComponentName,
parameters: { layout: 'centered' }
}
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {
/* props */
}
}
```
## Required Story Variants
Include when applicable:
- **Default** - Minimal props
- **WithData** - Realistic data
- **Loading** - Loading state
- **Error** - Error state
- **Empty** - No data
## Mock Data
Use realistic ComfyUI schemas for mocks (node definitions, components).
## Running Storybook
```bash
pnpm storybook # Development server
pnpm build-storybook # Production build
```

View File

@@ -1,78 +0,0 @@
---
globs:
- '**/*.ts'
- '**/*.tsx'
- '**/*.vue'
---
# TypeScript Conventions
## Type Safety
- Never use `any` type - use proper TypeScript types
- Never use `as any` type assertions - fix the underlying type issue
- Type assertions are a last resort; they lead to brittle code
- Avoid `@ts-expect-error` - fix the underlying issue instead
### Type Assertion Hierarchy
When you must handle uncertain types, prefer these approaches in order:
1.**No assertion** — Properly typed from the start
2.**Type narrowing**`if ('prop' in obj)` or type guards
3. ⚠️ **Specific assertion**`as SpecificType` when you truly know the type
4. ⚠️ **`unknown` with narrowing** — For genuinely unknown data
5.**`as any`** — FORBIDDEN
### Zod Schema Rules
- Never use `z.any()` — it disables validation and propagates `any` into types
- Use `z.unknown()` if the type is genuinely unknown, then narrow it
- Never add test-only settings/types to production schemas
### Public API Contracts
- Keep public API types stable (e.g., `ExtensionManager` interface)
- Don't expose internal implementation types (e.g., Pinia store internals)
- Reactive refs (`ComputedRef<T>`) should be unwrapped before exposing
## Avoiding Circular Dependencies
Extract type guards and their associated interfaces into **leaf modules** — files with only `import type` statements. This keeps them safe to import from anywhere without pulling in heavy transitive dependencies.
```typescript
// ✅ myTypes.ts — leaf module (only type imports)
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
export interface MyView extends IBaseWidget {
/* ... */
}
export function isMyView(w: IBaseWidget): w is MyView {
return 'myProp' in w
}
// ❌ myView.ts — heavy module (runtime imports from stores, utils, etc.)
// Importing the type guard from here drags in the entire dependency tree.
```
## Utility Libraries
- Use `es-toolkit` for utility functions (not lodash)
## API Utilities
When making API calls in `src/`:
```typescript
// ✅ Correct - use api helpers
const response = await api.get(api.apiURL('/prompt'))
const template = await fetch(api.fileURL('/templates/default.json'))
// ❌ Wrong - direct URL construction
const response = await fetch('/api/prompt')
```
## Security
- Sanitize HTML with `DOMPurify.sanitize()`
- Never log secrets or sensitive data

View File

@@ -1,38 +0,0 @@
---
globs:
- '**/*.test.ts'
---
# Vitest Unit Test Conventions
See `docs/testing/*.md` for detailed patterns.
## Test Quality
- Do not write change detector tests (tests that just assert defaults)
- Do not write tests dependent on non-behavioral features (styles, classes)
- Do not write tests that just test mocks - ensure real code is exercised
- Be parsimonious; avoid redundant tests
## Mocking
- Use Vitest's mocking utilities (`vi.mock`, `vi.spyOn`)
- Keep module mocks contained - no global mutable state
- Use `vi.hoisted()` for per-test mock manipulation
- Don't mock what you don't own
## Component Testing
- Use `@testing-library/vue` with `@testing-library/user-event` for component tests (an ESLint rule bans `@vue/test-utils` in new tests)
- Follow advice about making components easy to test
- Wait for reactivity with `await nextTick()` after state changes
## Running Tests
```bash
pnpm test:unit # Run all unit tests
pnpm test:unit path/to/file # Filter by substring of test file path
pnpm test:unit foo.test.ts -t "name" # Filter by test name (regex; it()/test() only, not describe())
```
Do not use the `--` separator before vitest args; pnpm forwards extra args automatically, and `--` mangles quoted args (e.g. `-t "two words"`) on Windows PowerShell.

View File

@@ -1,50 +0,0 @@
---
globs:
- '**/*.vue'
---
# Vue Component Conventions
Applies to all `.vue` files anywhere in the codebase.
## Vue 3 Composition API
- Use `<script setup lang="ts">` for component logic
- Destructure props (Vue 3.5 style with defaults) like `const { color = 'blue' } = defineProps<...>()`
- Use `ref`/`reactive` for state
- Use `computed()` for derived state
- Use lifecycle hooks: `onMounted`, `onUpdated`, etc.
## Component Communication
- Prefer `emit/@event-name` for state changes (promotes loose coupling)
- Use `defineExpose` only for imperative operations (`form.validate()`, `modal.open()`)
- Proper props and emits definitions
## VueUse Composables
Prefer VueUse composables over manual event handling:
- `useElementHover` instead of manual mouseover/mouseout listeners
- `useIntersectionObserver` for visibility detection instead of scroll handlers
- `useFocusTrap` for modal/dialog focus management
- `useEventListener` for auto-cleanup event listeners
Prefer Vue native options when available:
- `defineModel` instead of `useVModel` for two-way binding with props
## Styling
- Use inline Tailwind CSS only (no `<style>` blocks)
- Use `cn()` from `@comfyorg/tailwind-utils` for conditional classes
- Refer to packages/design-system/src/css/style.css for design tokens and tailwind configuration
- Exception: when third-party libraries render runtime DOM outside Vue templates
(for example xterm internals inside PrimeVue terminal wrappers), scoped
`:deep()` selectors are allowed. Add a brief inline comment explaining why the
exception is required.
## Best Practices
- Extract complex conditionals to `computed`
- In unmounted hooks, implement cleanup for async operations

View File

@@ -1,5 +1,28 @@
# Source Code Guidelines
## TypeScript
- Never use `any` or `as any` (enforced by oxlint) — fix the underlying type issue
- Avoid `@ts-expect-error` — fix the underlying issue instead
- When handling uncertain types, prefer in order:
1. Properly typed from the start
2. Type narrowing — `if ('prop' in obj)` or type guards
3. Specific assertion (`as SpecificType`) when you truly know the type
4. `unknown` with narrowing for genuinely unknown data
- Zod: never `z.any()` — it disables validation and propagates `any` into
types. Use `z.unknown()` and narrow. Never add test-only settings/types to
production schemas
- Extract type guards and their interfaces into leaf modules (files with only
`import type` statements) so they are safe to import from anywhere without
pulling in heavy transitive dependencies or creating cycles
- Keep public API types stable (e.g. `ExtensionManager`); don't expose
internal implementation types (e.g. Pinia store internals); unwrap reactive
refs (`ComputedRef<T>`) before exposing
- Use `es-toolkit` for utility functions
- API calls: use the api helpers — `api.get(api.apiURL('/prompt'))`,
`fetch(api.fileURL('/templates/default.json'))` — never construct
`/api/...` URLs by hand
## Error Handling
- User-friendly and actionable messages
@@ -19,9 +42,83 @@
- Clean up subscriptions
- Only expose state/actions that are used externally; keep internal state private
## General Guidelines
## i18n
- Use `es-toolkit` for utility functions
- Use TypeScript for type safety
- Avoid `@ts-expect-error` - fix the underlying issue
- Use `vue-i18n` for ALL user-facing strings (`src/locales/en/main.json`)
## Unit Tests (`*.test.ts`)
- Use `@testing-library/vue` with `@testing-library/user-event` (an ESLint
rule bans `@vue/test-utils` imports in new tests)
- No change-detector tests (asserting defaults), no tests of non-behavioral
features (styles, classes), no tests that only exercise mocks — ensure real
code is exercised. Be parsimonious; avoid redundant tests
- Mocking: use `vi.mock`/`vi.spyOn`; keep module mocks contained (no global
mutable state — use `vi.hoisted()` for per-test arrangement); don't mock
what you don't own
- Wait for reactivity with `await nextTick()` after state changes
- Read `docs/testing/<topic>.md` (unit-testing, component-testing,
store-testing, vitest-patterns) when writing the corresponding kind of test
```bash
pnpm test:unit # Run all unit tests
pnpm test:unit path/to/file # Filter by substring of test file path
pnpm test:unit foo.test.ts -t "name" # Filter by test name (it()/test() only)
```
Do not use a `--` separator before vitest args; pnpm forwards extra args
automatically, and `--` mangles quoted args (e.g. `-t "two words"`) on Windows
PowerShell.
## Vue Components (`*.vue`)
The root `AGENTS.md` covers Composition API conventions. Additionally:
- Prefer `emit`/`@event-name` for state changes; use `defineExpose` only for
imperative operations (`form.validate()`, `modal.open()`)
- Prefer VueUse composables over manual event handling: `useElementHover`,
`useIntersectionObserver`, `useFocusTrap`, `useEventListener`
(auto-cleanup). Prefer Vue-native `defineModel` over `useVModel`
- Inline Tailwind only — no `<style>` blocks. Exception: third-party libraries
that render runtime DOM outside Vue templates (e.g. xterm internals) may use
scoped `:deep()` selectors with a brief comment explaining why
- Design tokens and Tailwind configuration:
`packages/design-system/src/css/style.css`
- In unmount hooks, clean up async operations
## Design Standards
When adding new UI or changing visual design, fetch the relevant section of
the Comfy Design Standards Figma file first — file keys and section node IDs
are in `docs/guidance/design-standards.md`. Skip this for refactors, bugfixes,
and logic-only changes. If the Figma MCP is unavailable, say so in the PR
description instead of skipping silently.
## Storybook (`*.stories.ts`)
Place stories alongside their components. Use realistic ComfyUI schemas for
mock data (node definitions, components).
```typescript
import type { Meta, StoryObj } from '@storybook/vue3'
import ComponentName from './ComponentName.vue'
const meta: Meta<typeof ComponentName> = {
title: 'Category/ComponentName',
component: ComponentName,
parameters: { layout: 'centered' }
}
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {}
}
```
Variants: always include `Default`; add `Loading` and `Error` if the component
fetches data, `Empty` if it renders a collection, `WithData` for realistic
content.
Run with `pnpm storybook` (dev server) or `pnpm build-storybook`.