## Summary
Adds 7 new E2E tests for the Painter widget covering the remaining
untested items from Levels 1–5 of the widget test plan. All new tests
pass typecheck, lint, and the full pre-commit hook suite.
## Changes
**`browser_tests/tests/painter.spec.ts`**
- **Level 1.2** — assert node size ≥ 450×550 via the graph API to verify
the painter extension enforces its minimum dimensions
- **Level 1.3** — assert `width`, `height`, and `bg_color` widgets have
`options.hidden = true` so they don't appear as standard widget controls
- **Level 2.4** — cursor element becomes visible on pointer enter, its
CSS transform updates as the mouse moves across the canvas, and it hides
on pointer leave; uses `expect.poll` on transform to avoid the Vue
microtask race
- **Level 4.2** — set brush color to `#ff0000` via input event, draw a
stroke, sample a 40×40 region at canvas center and assert red pixels (R
> 200, G < 50, B < 50)
- **Level 4.3** — set opacity to 50%, draw a stroke, sample pixel alpha
values and assert semi-transparency (50 < α < 230)
- **Level 4.4** — focus the hardness slider, press ArrowLeft ×10, assert
the display value changes from `100%` to `90%`
- **Level 5.4** — set background color input to `#ff0000` via input
event, assert the canvas container div has `background-color: rgb(255,
0, 0)`
**`src/components/painter/WidgetPainter.vue`**
- Added `data-testid="painter-canvas-container"` to the inner canvas
wrapper div
- Added `data-testid="painter-cursor"` to the brush cursor div
- Added `data-testid="painter-bg-color-row"` to the background color
control row
- Added `data-testid="painter-hardness-value"` to the hardness display
span (mirrors the existing `painter-size-value` pattern)
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Low Risk**
> Low risk: changes are limited to Playwright E2E coverage plus a few
`data-testid` attributes to stabilize selectors, with no functional
logic changes to the painter behavior.
>
> **Overview**
> Adds **7 new Playwright E2E tests** for the Painter widget, covering
minimum node sizing/hidden standard widgets, cursor
visibility/positioning behavior, brush hardness display updates,
color/opacity effects on rendered strokes, and background color
application.
>
> Updates `WidgetPainter.vue` to add a handful of **`data-testid`
hooks** (canvas container, cursor, background color row, hardness value)
used by the new tests.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
a6da0c3e39. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11551-test-E2E-coverage-for-painter-widget-Levels-1-5-34a6d73d36508154a90fd24ffb3adb5b)
by [Unito](https://www.unito.io)
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: GitHub Action <action@github.com>
## Summary
Adds Playwright coverage for minimap **Level 1** gaps and **Level 4**
drag-to-pan behavior (`browser_tests/tests/minimap.spec.ts`).
## Changes
- [x] **Level 1.1** — After closing the minimap with the X button,
assert `Comfy.Minimap.Visible` is `false` (in addition to minimap hidden
and toolbar toggle still visible).
- [x] **Level 1.2** — New `@mobile` + `@canvas` suite: on a narrow
viewport (Pixel 5 project), assert default `Comfy.Minimap.Visible` is
`false`, minimap container is absent (`toHaveCount(0)`), and the toolbar
minimap toggle remains visible.
- [x] **Level 1.3** — New test: close via X, re-open via toolbar toggle;
assert minimap and viewport are visible and `Comfy.Minimap.Visible` is
`true`.
- [x] **Level 4.1** — Pointer drag on `minimap-interaction-overlay`
(`pointerdown` / `pointermove` / `pointerup`); use `expect.poll` on main
canvas offset so each move step updates pan; assert meaningful total pan
distance after drag.
- [x] **Level 4.2** — Same drag pattern; assert `.minimap-viewport`
`style.transform` updates mid-drag via `expect.poll` between move steps.
- [x] **Helpers** — `clientPointOnMinimapOverlay`,
`readMainCanvasOffset`, shared `MINIMAP_POINTER_OPTS` (no
`waitForTimeout`).
## Notes
- [x] Desktop minimap tests unchanged in intent; mobile-only cases live
in a separate `test.describe` so `beforeEach` does not force
`Comfy.Minimap.Visible` on small screens.
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Low Risk**
> Low risk: changes are limited to Playwright e2e tests and shared test
utilities, with no production logic modifications. Main risk is
increased test flakiness due to pointer-event simulation and
timing/polling on canvas state.
>
> **Overview**
> Expands minimap Playwright coverage by adding shared helpers
(`minimapUtils.ts`) for overlay-relative pointer coordinates, consistent
pointer event options, and reading the main canvas offset.
>
> Updates `minimap.spec.ts` to assert the `Comfy.Minimap.Visible`
setting toggles correctly when closing/reopening, adds
FitView+minimap-center regression coverage, and replaces mouse-drag with
explicit `pointerdown`/`pointermove`/`pointerup` overlay interactions
that assert progressive canvas panning and viewport `transform` updates.
Adds a new `@mobile` suite verifying the minimap is hidden by default on
small viewports while the toolbar toggle remains visible.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
7fad2a7dd3. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11192-test-extend-minimap-e2e-for-level-1-gaps-and-drag-panning-3416d73d36508194bc81f81a6d4536b8)
by [Unito](https://www.unito.io)
## Summary
Skip the main e2e test suite on PRs that only touch unrelated paths
(website, docs, storybook, markdown).
## Changes
- **What**: Replace the broad `paths-ignore: ['**/*.md']` on the
`pull_request` trigger with a more targeted `paths-ignore` list covering
`apps/**`, `docs/**`, `**/*.md`, and `.storybook/**`. The `push` (to
main), `merge_group`, and `workflow_dispatch` triggers remain
unconditional.
## Review Focus
- The `merge_group` trigger has no path filter, so the merge queue
always runs e2e as a safety net before merge.
- Using `paths-ignore` (denylist) rather than `paths` (allowlist) so new
top-level directories trigger e2e by default.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11568-ci-filter-e2e-workflow-on-PRs-to-skip-unrelated-changes-34b6d73d365081ea8603ef94bc86b6e6)
by [Unito](https://www.unito.io)
---------
Co-authored-by: Amp <amp@ampcode.com>
## Summary
Align `comfyManagerService` and Manager UI state with CSRF hardening in
[Comfy-Org/ComfyUI-Manager#2818](https://github.com/Comfy-Org/ComfyUI-Manager/pull/2818)
(4.2.0, Content-Type gate + GET→POST migration) and
[Comfy-Org/ComfyUI-Manager#2823](https://github.com/Comfy-Org/ComfyUI-Manager/pull/2823)
(4.2.1, `extension.manager.supports_csrf_post` feature flag).
## Changes
- **Service layer**: Convert 4 state-mutation endpoints (`START_QUEUE`,
`UPDATE_ALL`, `UPDATE_COMFYUI`, `REBOOT`) from GET to POST. `body=null`
+ axios default `Content-Type: application/json` is allowed by the
backend's `reject_simple_form_post` gate (only the three CORS
simple-form types are rejected).
- **UI/state layer**: Add `ManagerUIState.INCOMPATIBLE` triggered when
the backend advertises `supports_manager_v4` but not
`supports_csrf_post`. Manager UI is treated as "not installed" — buttons
hide via `shouldShowManagerButtons` with zero call-site changes across
`TopMenuSection`, `MissingNodeCard`, `MissingPackGroupRow`, `TabErrors`.
- **Graceful degraded mode**: One-shot upgrade toast (warn, 15s)
dispatched via `watch(immediate:true)` with a module-level guard that
survives multiple composable instances. `openManager()` re-emits on
explicit user action so stale shortcuts still surface guidance. i18n
(en/ko) covering Desktop / standalone pip / Manager UI self-update
paths.
- **Breaking**: None. Existing policies preserved (`--enable-manager`
absent → `DISABLED`; `--enable-manager-legacy-ui` → `LEGACY_UI`; feature
flags not yet loaded → `NEW_UI` transient fallback).
## Review Focus
- Decision-tree ordering in `useManagerState.ts`: `supports_csrf_post`
check evaluates before `NEW_UI`/`LEGACY_UI` branches so stale Manager
backends never reach the enabled paths.
- Toast guard: module-level `incompatibleToastShown` survives multiple
composable instances (tests verify 3× `useManagerState()` = 1 toast
call).
- `generatedManagerTypes.ts` still declares the 4 endpoints as GET;
regeneration follows once Manager 4.2.1 OpenAPI is published. Runtime is
unaffected since axios operates on the route string.
## References
-
[Comfy-Org/ComfyUI-Manager#2818](https://github.com/Comfy-Org/ComfyUI-Manager/pull/2818)
— CSRF Content-Type gate + GET→POST migration (4.2.0)
-
[Comfy-Org/ComfyUI-Manager#2823](https://github.com/Comfy-Org/ComfyUI-Manager/pull/2823)
— `supports_csrf_post` feature flag (4.2.1)
- [comfyui-manager 4.2.1 on
PyPI](https://pypi.org/project/comfyui-manager/4.2.1) — release package
## Summary
Adds a new Claude Code skill at `.claude/skills/bug-dump-ingest/` that
syncs the `#bug-dump` Slack channel into Linear as the system of record,
per discussion in [this
thread](https://comfy-organization.slack.com/archives/C075ANWQ8KS/p1776510375473579).
- Primary mode is bulk sync — every ingestable top-level message becomes
a Linear issue in the Frontend Engineering team's Triage state with
labels for area / env / severity / reporter.
- Marks handled messages via the team emoji scheme:
- `✅` — ticket created
- `:pr-open:` — fix PR open
- `❓` — needs more context
- `🔁` — duplicate
- Since the Slack reactions API isn't exposed to the skill, the
machine-readable marker is a thread reply carrying the Linear URL; the
human is prompted to add the visible parent reaction from a batch list
printed at session end.
- Secondary opt-in per-row mode delegates to `red-green-fix` to author a
failing unit + e2e test, then a minimal fix, then a PR.
## Files
- `SKILL.md` — entry point: workflow, classification, verification,
approval flow, Linear integration (MCP / GraphQL / draft fallback), fix
workflow
- `reference/linear-api.md` — GraphQL snippets for teams / states /
labels / issues
- `reference/schema.md` — field-by-field extraction rules
- `reference/examples.md` — seven worked examples from real recent
`#bug-dump` messages
- `reference/verify-commands.md` — cookbook of false-defect verification
commands
## Linear MCP setup
Two supported paths documented in `SKILL.md`:
- Option A: official hosted Linear MCP at `https://mcp.linear.app/sse`
via `claude mcp add`, OAuth-based, no API key.
- Option B: community self-hosted MCP with `LINEAR_API_KEY` in env.
## Test plan
- [ ] Restart Claude Code session after merging so the Linear MCP tools
register
- [ ] Authorize Linear OAuth on first tool call
- [ ] Dry-run the skill against a 48h window of `#bug-dump`, confirm the
approval table renders with all 9 columns
- [ ] File one real ticket end-to-end; verify labels, Triage state,
Slack thread reply, and permalink attachment
- [ ] Add a `✅` reaction on that parent; re-run and
confirm the message is skipped
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11460-feat-add-bug-dump-ingest-skill-3486d73d3650810094aee3e4ee79eb86)
by [Unito](https://www.unito.io)
---------
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: bymyself <cbyrne@comfy.org>
## Summary
Route the `progress_text` binary parser's feature-flag check through
`serverSupportsFeature()` so dev overrides via `localStorage` take
effect.
## Changes
- **What**: Replace
`this.getClientFeatureFlags()?.supports_progress_text_metadata` with
`this.serverSupportsFeature('supports_progress_text_metadata')` in the
`case 3` binary message handler, consistent with all other feature-flag
checks in the class.
## Review Focus
Minimal one-line change. The key consideration is that
`serverSupportsFeature()` routes through `getDevOverride()` first,
enabling `localStorage` overrides (`ff:supports_progress_text_metadata`)
for dev testing of the binary wire format.
Fixes#11187
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11384-fix-route-progress_text-feature-flag-check-through-getDevOverride-3476d73d36508161bca0d6c2ea7c3c55)
by [Unito](https://www.unito.io)
---------
Co-authored-by: GitHub Action <action@github.com>
## Summary
Add `ph-no-capture` class to TransformPane to block PostHog session
recording of canvas node DOM mutations, eliminating 226ms CPU overhead
in the viewport scenario.
## Changes
- **What**: Add `ph-no-capture` CSS class to the TransformPane root div
and a unit test guarding it
- **Why**: Profiling showed PostHog session recording (via rrweb
mutation observer) consuming **226ms CPU** in the viewport scenario —
**9× more** than the entire Vue reactivity system (25ms). The 150+
LGraphNode Vue components that mount/unmount during pan/zoom each
trigger rrweb to snapshot new DOM subtrees.
### How it works
PostHog uses rrweb under the hood. The `ph-no-capture` class maps to
rrweb's `blockClass`, which causes:
1. `processMutation` to **early-return** for `childList` mutations when
the parent is blocked
2. `genAdds` to **skip child traversal** (`dom.childNodes()`) for
blocked elements
3. The element to be replaced with a **same-size placeholder** in replay
This means all 150+ node components inside TransformPane produce **zero
mutation processing cost**.
### Scope
- **TransformPane**: blocked — wraps all Vue-rendered graph nodes
- **LinkOverlayCanvas**: evaluated but not blocked — contains only a
single `<canvas>` element with no DOM children, so no mutation overhead
- **All other UI** (sidebar, menus, dialogs, toolbar, bottom panel):
continues recording normally
### Trade-off
The graph canvas area appears as a blank placeholder in session replays.
This is acceptable because canvas interaction is better captured via
workflow JSON, and the performance gain far outweighs the replay
fidelity loss.
## Review Focus
- Correctness of the `ph-no-capture` placement on TransformPane as the
optimal DOM boundary
- Whether any other high-mutation DOM subtrees should also be blocked
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10494-perf-exclude-canvas-nodes-from-PostHog-session-recording-32e6d73d36508169ab07f1b193860fb0)
by [Unito](https://www.unito.io)
---------
Co-authored-by: Dante <bunggl@naver.com>
## Summary
Assorted website copy and content refinements — tidying up loose ends
across the site.
## Changes
- **What**: Remove placeholder doc links from custom nodes feature
description on pricing page
## Review Focus
Low-risk copy changes only; no logic or layout modifications.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11552-feat-website-website-mise-en-place-34b6d73d3650813b954afbc965e4dc74)
by [Unito](https://www.unito.io)
> **Note:** The `PR: Vercel Website Preview` workflow is
`workflow_run`-triggered, so it always runs the **main branch version**
of the workflow file. Until this PR is merged, the preview workflow will
continue posting standalone comments using the old `<!--
VERCEL_WEBSITE_PREVIEW -->` marker instead of writing to the
consolidated `<!-- WEBSITE_CI_REPORT -->` comment. This is expected and
resolves itself on merge.
---------
Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Yourz <crazilou@vip.qq.com>
## Summary
- harden cloud frontend runtime paths that were throwing on
`cloud.comfy.org`
- guard widget value propagation when the source widget is missing
- treat nullish executed outputs as empty output during flatten/parsing
- ignore stale autogrow disconnect callbacks after an autogrow group is
removed
## Root cause
This PR bundles three small runtime guard fixes from
`cloud-frontend-staging` issues that reproduce on
`https://cloud.comfy.org/`:
- `CLOUD-FRONTEND-STAGING-429`: widget propagation assumed
`this.widgets[0]` always existed and crashed during group-node/widget
lifecycle transitions
- `CLOUD-FRONTEND-STAGING-3QA` and sibling `3QB`: executed-event parsing
assumed `detail.output` was always an object and crashed on nullish
output payloads
- `CLOUD-FRONTEND-STAGING-42B`: `autogrowInputDisconnected()` could run
from a stale `requestAnimationFrame()` callback after its autogrow group
had already been removed
## User impact
- prevents unhandled frontend exceptions on `cloud.comfy.org`
- keeps node output rendering and linear-mode flattening resilient to
sparse executed payloads
- avoids autogrow disconnect crashes during graph/widget churn
## Changes
- extracted shared widget propagation logic into
`widgetValuePropagation.ts`
- added source-widget guards in custom widget / primitive widget
propagation paths
- added null guards in result parsing and linear-mode output flattening
- added an autogrow-group existence guard in `dynamicWidgets.ts`
- added focused regression tests for all three bug shapes
## Red / Green Verification
### Red
I ran the new targeted regression suite in a temporary pre-fix worktree
with the runtime guards reverted while keeping the new tests.
Failing tests in that state:
- `src/extensions/core/widgetValuePropagation.test.ts`
- `returns early when the source widget is missing`
- `src/stores/resultItemParsing.test.ts`
- `returns empty array for nullish node output`
- `ignores nullish node outputs`
- `src/renderer/extensions/linearMode/flattenNodeOutput.test.ts`
- `returns empty array for nullish node output`
Representative pre-fix errors:
- `TypeError: Cannot read properties of undefined (reading 'value')`
- `TypeError: Cannot convert undefined or null to object`
### Green
On the draft PR branch, the targeted regression suite passes:
- `pnpm exec vitest run src/core/graph/widgets/dynamicWidgets.test.ts
src/stores/resultItemParsing.test.ts
src/renderer/extensions/linearMode/flattenNodeOutput.test.ts
src/extensions/core/widgetValuePropagation.test.ts
src/extensions/core/customWidgets.test.ts`
Result:
- `5` test files passed
- `49` tests passed
## Validation
- `pnpm exec vitest run src/core/graph/widgets/dynamicWidgets.test.ts
src/stores/resultItemParsing.test.ts
src/renderer/extensions/linearMode/flattenNodeOutput.test.ts
src/extensions/core/widgetValuePropagation.test.ts
src/extensions/core/customWidgets.test.ts`
- `pnpm exec eslint --no-ignore src/core/graph/widgets/dynamicWidgets.ts
src/core/graph/widgets/dynamicWidgets.test.ts
src/stores/resultItemParsing.ts src/stores/resultItemParsing.test.ts
src/renderer/extensions/linearMode/flattenNodeOutput.ts
src/renderer/extensions/linearMode/flattenNodeOutput.test.ts
src/extensions/core/customWidgets.ts src/extensions/core/widgetInputs.ts
src/extensions/core/widgetValuePropagation.ts
src/extensions/core/widgetValuePropagation.test.ts`
- `pnpm exec oxfmt --check src/core/graph/widgets/dynamicWidgets.ts
src/core/graph/widgets/dynamicWidgets.test.ts
src/stores/resultItemParsing.ts src/stores/resultItemParsing.test.ts
src/renderer/extensions/linearMode/flattenNodeOutput.ts
src/renderer/extensions/linearMode/flattenNodeOutput.test.ts
src/extensions/core/customWidgets.ts src/extensions/core/widgetInputs.ts
src/extensions/core/widgetValuePropagation.ts
src/extensions/core/widgetValuePropagation.test.ts`
## Notes
- I explicitly skipped the `getCanvas: canvas is null` issue because it
is already covered by open PRs `#11173` / `#11174`.
- `pnpm typecheck` was not included in validation because the temporary
PR worktree used for publication hits local path-resolution issues
through the shared dependency install, which is unrelated to the changes
in this PR.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11180-codex-fix-cloud-frontend-runtime-guard-regressions-3416d73d365081e0af6ec612c9d0d8aa)
by [Unito](https://www.unito.io)
---------
Co-authored-by: GitHub Action <action@github.com>
## Summary
Adds 14 unit tests for \`FormDropdownMenuActions\` — isolated into its
own PR because the component is denser (three PrimeVue Popovers,
multiple filter models) than the sibling components in the form-dropdown
PR. Part of a widget-test-coverage sequence.
## Changes
- **What**: \`FormDropdownMenuActions.test.ts\` — search v-model, sort
popover options + selection, ownership popover visibility gated by
\`showOwnershipFilter\` + options present, base-model multi-select
toggle (add/remove/multiple), Clear Filters, list/grid layout-mode
v-model.
## Review Focus
- PrimeVue \`Popover\` stubbed as an always-slotted \`<div>\` with
\`toggle\`/\`hide\` methods on the Options-API \`methods\` (stub
\`expose\` did not satisfy template-ref access).
- Sort/ownership/base-model option discovery uses
\`within(popover-body)\` to disambiguate buttons across the three
popovers.
- Layout-switch locator uses an \`.icon-*\` class probe since the button
has no accessible name; covered by an \`eslint-disable-next-line
testing-library/no-node-access\`.
- No changes to the component source.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11443-test-add-unit-tests-for-FormDropdownMenuActions-3486d73d365081ed80dcfdf5d83655e1)
by [Unito](https://www.unito.io)
---------
Co-authored-by: GitHub Action <action@github.com>
## Summary
`CancelSubscriptionDialogContent` calls `new Date(dateStr)` directly on
both `cancelAt` and `subscription.value?.endDate`. Strict ISO 8601
parsers (Safari and some WebViews) reject fractional seconds whose
length is anything other than 3 digits, so a Go-style backend
timestamp such as `2026-04-18T10:04:55.6513Z` rendered as
`Your access continues until Invalid Date.` in a destructive billing
flow.
PR #11358 already added the tolerant `parseIsoDateSafe` helper and
applied it to the Secrets panel. This PR closes the same gap in the
cancellation dialog and adds regression coverage that exercises the
strict-parser code path (V8 alone is too lenient to fail without it).
## Changes
- `CancelSubscriptionDialogContent.vue` — pipe both date sources through
`parseIsoDateSafe`; collapse the two-step null check into one. When
the value is missing OR unparseable, fall back to the existing
`subscription.cancelDialog.endOfBillingPeriod` translation instead of
emitting `Invalid Date`.
- `CancelSubscriptionDialogContent.test.ts` (new) — wraps the assertions
in the same `withStrictMillisecondParser` shim used by
`dateTimeUtil.test.ts`, so 1-, 4-, and 9-digit fractional inputs
actually exercise the broken path. Also covers the missing/unparseable
fallbacks and the `cancelAt`-takes-precedence ordering.
## Red-green proof (local)
Confirmed locally before splitting the commits:
| Commit | `pnpm exec vitest run
…CancelSubscriptionDialogContent.test.ts` |
|---|---|
| `c8aecd07f test: regression cover …` (test-only) | 5 failed / 1 passed
— DOM rendered `"Your access continues until Invalid Date."` |
| `f24cb903f fix: parse cancel-subscription dialog ISO timestamps …` | 6
passed |
CI on this branch only runs on PR HEAD; happy to force-push a transient
red commit if you want a recorded red CI run alongside the green one.
## Test plan
- [x] `pnpm exec vitest run
src/components/dialog/content/subscription/CancelSubscriptionDialogContent.test.ts`
(6 passing on green)
- [x] `pnpm typecheck`
- [x] `pnpm exec eslint
src/components/dialog/content/subscription/CancelSubscriptionDialogContent.{vue,test.ts}`
- [x] `pnpm exec oxfmt --check` on changed files
- [ ] Manual repro on Safari with a Go-emitted cancel timestamp (out of
scope here; the unit test asserts the equivalent strict-parser behavior)
## Origin
Surfaced by `/codex:adversarial-review` as the gap left after PR #11358
(Secrets panel) — the same `new Date(...)` hazard survived in a
destructive billing flow with no regression coverage.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11539-fix-cancel-subscription-dialog-renders-Invalid-Date-for-ISO-fractional-seconds-34a6d73d365081e4bdd6c941afd8cef3)
by [Unito](https://www.unito.io)
## Summary
- run every `parseIsoDateSafe` case with more than 3 fractional-second
digits through `withStrictMillisecondParser`
- assert the normalized 3-digit timestamp string passed into `Date` for
each long-fraction variant
- keep the follow-up scoped to test coverage only
## Root cause
V8 already accepts and truncates ISO timestamps with more than 3
fractional-second digits, so the existing tests could stay green even if
`parseIsoDateSafe` failed to normalize those values before constructing
`Date`. Wrapping the long-fraction cases in the strict parser shim makes
CI exercise the Safari/WebView-sensitive path the feature is meant to
protect.
## Testing
- `pnpm exec vitest run src/utils/dateTimeUtil.test.ts`
- `pnpm exec eslint src/utils/dateTimeUtil.test.ts`
Fixes#11528
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11529-codex-test-harden-strict-parser-coverage-for-long-ISO-fractions-34a6d73d365081119577eb5fb6d4992c)
by [Unito](https://www.unito.io)
*PR Created by the Glary-Bot Agent*
---
## Summary
- Moves distribution-based template filtering from a CSS-level `v-show`
gate into the `useTemplateFiltering` composable's data pipeline,
guaranteeing that templates not meant for the current distribution never
reach the view layer
- Fixes "Showing 19 of 419" count mismatch when only 2 templates are
visible on Cloud with "Wan 2.2" filter active
- Derives `availableModels` and `availableUseCases` from
distribution-visible templates so filter dropdowns don't show options
that only exist on other distributions
- Always prunes `activeModels`/`activeUseCases` against available
options to prevent stale persisted selections from causing zero-result
filtering
## Root Cause
The template selector dialog used
`v-show="isTemplateVisibleOnDistribution(template)"` to hide templates
that don't match the current distribution (cloud/desktop/local). But
`filteredCount` and `totalCount` were computed upstream in the pipeline
before this visual filter, so the count text showed all matching
templates regardless of distribution visibility.
## Changes
- **`useTemplateFiltering.ts`**: Added `visibleTemplates` computed that
applies distribution filter at the top of the pipeline. All downstream
computeds (`fuse`, `availableModels`, `availableUseCases`,
`filteredBySearch`, counts) now operate on this distribution-filtered
set. `activeModels`/`activeUseCases` always prune against available
options.
- **`WorkflowTemplateSelectorDialog.vue`**: Passes `distributions` ref
to composable, removes `v-show` gate and
`isTemplateVisibleOnDistribution` function.
- **`useTemplateFiltering.test.ts`**: 10 new unit tests covering
distribution filtering, filter composition (search + model + use case +
runsOn), stale persisted selections, multi-distribution templates, and
Mac distribution.
- **`templateFilteringCount.spec.ts`**: 5 new `@cloud` e2e tests
verifying count/card consistency, DOM leak prevention, and filter reset
behavior with mocked template data.
## Verification
- 22 unit tests passing (12 existing + 10 new)
- `pnpm typecheck` clean
- `pnpm typecheck:browser` clean
- `oxlint` + `eslint` clean on all changed files
- E2E tests tagged `@cloud` — designed for CI cloud build execution
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11418-fix-move-template-distribution-filter-from-v-show-to-data-pipeline-3476d73d365081c3ba09fc8a42eb4c9b)
by [Unito](https://www.unito.io)
---------
Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
## Summary
Cover the remaining uncovered error-handling branch in
`useWorkflowThumbnail`, bringing unit test coverage from 93.9% to 100%.
## Changes
- **What**: Add 2 unit tests for `createMinimapPreview` error path and
`storeThumbnail` null-thumbnail branch
## Review Focus
Tests verify that when `createGraphThumbnail` throws,
`createMinimapPreview` returns `null` and `storeThumbnail` does not
persist anything.
## Coverage Delta
| File | Before | After | Delta | Missed |
|------|--------|-------|-------|--------|
| `useWorkflowThumbnail.ts` | 93.9% | 100.0% | 🟢 +6.1% | 0 |
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11404-test-cover-error-branch-in-useWorkflowThumbnail-6-1-3476d73d36508148a135ee29f7abf22e)
by [Unito](https://www.unito.io)
## Summary
Add 51 unit tests for `CanvasPathRenderer`, improving line coverage from
23.2% to 84.19% (100% function coverage).
## Changes
- **What**: New test file
`src/renderer/core/canvas/pathRenderer.test.ts` covering color
determination, border rendering, linear/straight/spline path modes,
`findPointOnBezier`, center point calculation, arrows, flow animation,
center markers, disabled patterns, and `drawDraggingLink`.
## Review Focus
Pure test addition — no production code changes. Tests mock `Path2D` via
`vi.stubGlobal` and `CanvasRenderingContext2D` via a plain object with
`vi.fn()` methods.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11387-test-add-unit-tests-for-CanvasPathRenderer-3476d73d365081bbb526d584cc41b723)
by [Unito](https://www.unito.io)
*PR Created by the Glary-Bot Agent*
---
## Summary
- Excludes `src/scripts/ui/**` (legacy DOM component library) from
Playwright e2e coverage reports — this code is kept solely for extension
backwards-compatibility and shouldn't count toward coverage metrics
- Extracts Monocart coverage config (`outputDir`, `sourceFilter`) into
`browser_tests/coverageConfig.ts` so coverage exclusions are
discoverable and centralized instead of buried in `globalTeardown.ts`
## Details
Monocart does support external config files (`mcr.config.ts`
auto-discovery), but since MCR is instantiated in two places with
different configs (per-worker collection in `ComfyPage.ts` vs final
report in `globalTeardown.ts`), auto-discovery would affect both
instances. A shared TypeScript constant is safer and more explicit.
## Changes
- **New**: `browser_tests/coverageConfig.ts` — shared
`COVERAGE_OUTPUT_DIR` and `coverageSourceFilter`
- **Modified**: `browser_tests/globalTeardown.ts` — imports from shared
config
- **Modified**: `browser_tests/fixtures/ComfyPage.ts` — imports
`COVERAGE_OUTPUT_DIR`
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11377-test-exclude-legacy-UI-component-library-from-e2e-coverage-3466d73d365081b78dc9e4e14d913295)
by [Unito](https://www.unito.io)
---------
Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>
Fixes#11009
## Summary
On Windows, Chromium may still hold file handles on the user-data
directory when global teardown runs `restorePath`. The `fs.moveSync(...,
{ overwrite: true })` call fails with EPERM because it can't remove the
target while handles are held.
## Changes
- Split `restorePath` into explicit remove-then-move
- Added `removeWithRetry` that retries up to 3× on EPERM/EBUSY with
500ms delay between attempts
- Downgraded the catch from `console.error` (which looks like a test
failure) to `console.warn` so teardown noise doesn't mask real failures
No E2E regression test added: this is a test-infrastructure fix for a
Windows-specific race condition in teardown that cannot be reliably
reproduced in CI.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11013-fix-handle-EPERM-EBUSY-in-global-teardown-restorePath-33e6d73d3650815ebe0cd42af23e6c0e)
by [Unito](https://www.unito.io)
*PR Created by the Glary-Bot Agent*
---
## Summary
Fixes auto-grow input slot connections breaking in Nodes 2.0 when a node
has multiple auto-grow groups (e.g., `Wan 2.7 Reference to Video` with
both image and video auto-grow inputs).
## Problem
When auto-grow adds inputs to one group, it splices new entries into
`node.inputs`, shifting the indices of all subsequent groups. The data
layer handles this correctly via `spliceInputs()`, but Nodes 2.0 Vue
components retained stale slot indices because:
1. **`NodeSlots.vue`** keyed `InputSlot`/`OutputSlot` by name only — Vue
reused components without remounting when indices shifted
2. **`useSlotElementTracking`** registered `data-slot-key` once at mount
and stopped its watcher — stale keys persisted in the DOM
3. **`useSlotLinkInteraction`** captured `index` in closures at mount —
stale closures targeted wrong slots
This caused connections to land on wrong inputs, incorrect hover
indicators, and some slot types becoming unreachable.
## Fix
Include the actual slot index in the component key for `InputSlot` (both
in `NodeSlots.vue` and `NodeWidgets.vue`) and `OutputSlot`. When
autogrow shifts a slot's position or an output is removed, the key
changes, forcing Vue to remount — which re-registers `data-slot-key` and
refreshes all interaction closures with the correct index.
## Testing
- **Remount verification**: Tests use setup() invocation counting to
prove components are actually remounted (not just prop-patched) when
indices shift — directly validating that `useSlotElementTracking` and
`useSlotLinkInteraction` are re-initialized
- **Multi-group autogrow**: Verifies data-layer index correctness when
first group growth shifts second group
- **Output removal**: Verifies OutputSlot remount when earlier output
removal shifts later output indices
- All existing tests pass, lint/typecheck/format clean
## Screenshots

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11423-fix-include-actual-slot-index-in-InputSlot-OutputSlot-keys-to-prevent-stale-indices-aft-3476d73d365081859da6c450a840a625)
by [Unito](https://www.unito.io)
---------
Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>
Adds tests for the vue audio preview widget and vue video previews
(which are not widgets).
Also
- Fixes a bug where muted audio previews would incorrectly display a
'low volume' indicator instead of a muted indicator.
- Add test helper for deleting uploaded files after a test completes
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11523-Add-audio-preview-tests-3496d73d365081be8630ede6dae1726a)
by [Unito](https://www.unito.io)
## Summary
<!-- One sentence describing what changed and why. -->
Polish and fix UI for new website
## Changes
- **What**: <!-- Core functionality added/modified -->
- [x] update about video
- [x] update Moment factory story content
- [x] update homepage visual
- [x] update customer story visual
- [x] put images and videos to bucket
## Review Focus
<!-- Critical design decisions or edge cases that need attention -->
<!-- If this PR fixes an issue, uncomment and update the line below -->
<!-- Fixes #ISSUE_NUMBER -->
## Screenshots (if applicable)
<!-- Add screenshots or video recording to help explain your changes -->
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11363-feat-website-Polish-and-fix-UI-3466d73d365081f895aff84b594450c9)
by [Unito](https://www.unito.io)
---------
Co-authored-by: DrJKL <DrJKL0424@gmail.com>
Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: github-actions <github-actions@github.com>
*PR Created by the Glary-Bot Agent*
---
## Summary
- Replace all `cn` / `ClassValue` imports from the
`@/utils/tailwindUtil` re-export shim with direct imports from
`@comfyorg/tailwind-utils` across 198 source files in `src/` and 3 in
`apps/desktop-ui/`
- Delete both shim files (`src/utils/tailwindUtil.ts` and
`apps/desktop-ui/src/utils/tailwindUtil.ts`)
- Add explicit `@comfyorg/tailwind-utils` dependency to
`apps/desktop-ui/package.json`
- Update documentation references in `AGENTS.md`,
`docs/guidance/design-standards.md`, and
`docs/guidance/vue-components.md`
Fixes#11288
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11453-refactor-migrate-cn-imports-from-utils-tailwindUtil-shim-to-comfyorg-tailwind-utils--3486d73d365081ec92cce91fbf88e6e4)
by [Unito](https://www.unito.io)
---------
Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
## Summary
Adds 20 unit tests across 2 files covering WidgetChart and
WidgetRecordAudio. Part of a widget-test-coverage sequence.
## Changes
- **What**:
- \`WidgetChart.test.ts\` (6) — default type 'line', honours
\`widget.options.type\`, passes model value through to the PrimeVue
Chart stub, empty-object fallback for labels/datasets, aria-label
includes widget name + type.
- \`WidgetRecordAudio.test.ts\` (14) — idle state renders Start
Recording button and disables it on \`readonly\`; recording state shows
"Listening..." and a stop button wired to \`recorder.stopRecording\`;
ready state shows Play button; playing state shows Stop-playback wired
to \`playback.stop\`.
## Review Focus
- \`WidgetRecordAudio\` mocks \`useAudioRecorder\` /
\`useAudioPlayback\` / \`useAudioWaveform\` at their module boundary
(follows "don't mock what you don't own" — MediaRecorder is behind those
composables).
- \`useAudioRecorder\` already has its own composable-level test; this
PR tests the orchestration only.
- \`WidgetLegacy\` is intentionally NOT covered here — 100+ LoC of
litegraph/canvas integration, already covered by e2e \`widget.spec.ts\`.
- No changes to any source component.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11444-test-add-unit-tests-for-media-widgets-chart-record-audio-3486d73d365081c5b438e104d0e3b0df)
by [Unito](https://www.unito.io)
---------
Co-authored-by: GitHub Action <action@github.com>
## Summary
Adds two Playwright specs extending
\`browser_tests/tests/vueNodes/widgets/\` to cover float and combo value
types, following the existing \`integerWidget.spec.ts\` /
\`multilineStringWidget.spec.ts\` pattern. Part of a
widget-test-coverage sequence.
## Changes
- **What**:
- \`browser_tests/tests/vueNodes/widgets/float/floatWidget.spec.ts\` (3)
— number-input value change, increment/decrement on \`denoise\`, and
persistence through litegraph widget state after user edit.
- \`browser_tests/tests/vueNodes/widgets/combo/comboWidget.spec.ts\` (3)
— dropdown lists known sampler options, combo value updates on select,
\`scheduler\` value persists.
Reuses the existing \`vueNodes/linked-int-widget.json\` fixture
(KSampler exposes \`cfg\` / \`denoise\` floats and \`sampler_name\` /
\`scheduler\` combos). No new fixture files.
## Review Focus
- Specs tagged \`@vue-nodes\`, consistent with the sibling suites.
- Persistence assertions read widget state via
\`window.graph._nodes_by_id[...].widgets\` (typed through
\`TestGraphAccess\` from \`@e2e/types/globals\`) rather than
JSON-serializing the whole graph — avoids \`unknown\` typing on
\`window.graph.serialize()\`.
- Boolean and color e2e specs are intentionally NOT in this PR — they'd
need new workflow fixtures, which I'd prefer to design with you before
writing.
- \`pnpm typecheck:browser\` is clean locally; CI run needed to validate
the Playwright behaviour since I couldn't run the full e2e suite
locally.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11447-test-add-e2e-specs-for-float-and-combo-Vue-widgets-3486d73d365081f79302edc87595130c)
by [Unito](https://www.unito.io)
## Summary
Phase 1 of this https://github.com/Comfy-Org/ComfyUI_frontend/pull/11388
## Changes
* **`src/composables/maskeditor/brushDrawingUtils.ts` (New)** —
Extracted `premultiplyData`, `formatRgba`, `drawShapeOnContext`,
`createBrushGradient`, `getCachedBrushTexture`, `drawRgbShape`,
`drawMaskShape`, `resetDirtyRect`, and `updateDirtyRect`; also exports
`DirtyRect` / `MaskColor` types.
* **`src/composables/maskeditor/brushDrawingUtils.test.ts` (New)** — 11
unit tests with zero module mocking.
* **`src/composables/maskeditor/useBrushDrawing.ts`** — Replaced logic
with imports; updated all `updateDirtyRect` call sites to use pure
function calls, eliminating redundant calculations in `drawShape`.
## Test locally
1. Draw a few strokes on the canvas — verify brush marks appear
correctly- ok
2. Switch to the eraser tool and erase part of the stroke — verify
erasure works - ok
3. Press Ctrl+Z to undo — verify the canvas state is restored - ok
4. Alt+drag to adjust brush size/hardness — verify the brush parameters
update correctly - ok
https://github.com/user-attachments/assets/ba4ca54d-e1a9-4985-bc46-b996bbf13eee
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Refactors core brush rendering and dirty-rect tracking used during
interactive drawing, so subtle regressions in brush
appearance/performance or cache behavior are possible. Adds new error
paths when brush texture canvas context/radius are invalid.
>
> **Overview**
> Extracts CPU brush rendering utilities into new
`brushDrawingUtils.ts`, including **shape drawing**, **soft brush
gradients/rect textures with an LRU cache**, **alpha
premultiplication**, and **dirty-rect reset/update** helpers.
>
> Updates `useBrushDrawing.ts` to import and use these helpers,
switching dirty-rect tracking to a pure-function style (`dirtyRect.value
= updateDirtyRect(...)`) and simplifying `drawShape` by computing
effective radius/hardness once.
>
> Adds `brushDrawingUtils.test.ts` with focused unit coverage for
premultiplication, dirty-rect bounds behavior, and RGB/mask drawing
paths (including cached soft-rect textures and error handling when a 2D
context can’t be created).
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
abbc6813a6. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11531-Refactor-brush-drawing-utils-34a6d73d365081e1b404c384e099d1a9)
by [Unito](https://www.unito.io)
## Summary
Adds 26 unit tests across 3 files covering BatchNavigation,
FormSearchInput, and WidgetLayoutField. Part of a widget-test-coverage
sequence.
## Changes
- **What**:
- \`BatchNavigation.test.ts\` (10) — hidden when count ≤ 1, counter
formatted as 1-based \`current / total\`, prev/next navigation, disabled
states at range boundaries.
- \`FormSearchInput.test.ts\` (8) — v-model binding as the user types,
clear-button visibility based on trimmed-query, debounced searcher
invocation with fake timers (250ms debounce, 1000ms maxWait).
- \`WidgetLayoutField.test.ts\` (8) — widget.name vs widget.label
preference, empty-name suppression, \`HideLayoutFieldKey\` injection
hides label but preserves slot, slot receives \`borderStyle\` scoped
prop.
## Review Focus
- Fake timers used in FormSearchInput tests for \`refDebounced\` — the
debounce assertion depends on the 250ms/1000ms window in the component
staying unchanged.
- \`HideLayoutFieldKey\` provided via \`global.provide\` using the
Symbol key.
- No changes to any source component.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11442-test-add-unit-tests-for-utility-widgets-3486d73d365081a891cafe21b09b91c0)
by [Unito](https://www.unito.io)
---------
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: bymyself <cbyrne@comfy.org>
## Summary
Follow-up to #10828. Addresses all deferred review nits from @DrJKL
tracked in #10932.
- Remove YAGNI `timeout` parameter from `waitForDraftPersisted` —
default 5s from `waitForFunction` is sufficient
- Extract `reloadAndWaitForApp()` into `WorkflowHelper` — preserves
localStorage (drafts) and URL hash (subgraph navigation), unlike
`ComfyPage.setup()` which clears storage and navigates to base URL
- Replace programmatic `canvas.setGraph()` with
`vueNodes.enterSubgraph()` for real UI-based subgraph entry
- Add `@vue-nodes` tag required for `enterSubgraph()` button rendering
- Extract `getSubgraphNodePositions` to deduplicate three identical
inline `page.evaluate` calls
- Fix vacuous pass: capture `positionsBefore` inside `expect.poll` to
ensure the array is non-empty before the verification loop
- Remove inline comments, relying on descriptive helper method names
## Test plan
- [x] `pnpm typecheck:browser` passes
- [x] `pnpm lint` passes
- [x] `pnpm test:browser:local --
browser_tests/tests/subgraph/subgraphDraftPositions.spec.ts` passes
locally
Fixes#10932
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11327-refactor-extract-test-helpers-and-use-UI-based-subgraph-entry-in-draft-position-test-3456d73d3650813cacc1e69398e3f80a)
by [Unito](https://www.unito.io)
---------
Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: GitHub Action <action@github.com>
*PR Created by the Glary-Bot Agent*
---
## Summary
Renames the first sort option in the `FormDropdown` widget (the inline
image picker shown on node inputs like `LoadImage`) from "Default" to
"Recent" for clarity. Fixes FE-238.
## Why "Recent" is accurate
The `'default'` sort preserves server order (see `assetSortUtils.ts`).
The cloud assets backend orders by `create_time DESC` — see
`cloud/common/assets/repository_impl.go` `applySortOrder()`:
```go
default: // "created_at" or default
return query.Order(asset.ByCreateTime(sql.OrderDesc()))
```
So the server already returns items newest-first and the user sees them
in recency order. "Recent" describes what's actually on screen.
## Scope
Minimal label-only change. The internal option id stays `'default'`
because `FormDropdown.vue` and `FormDropdownMenuActions.vue` use it as a
sentinel for "unmodified sort state" (e.g., the indicator dot that
appears when the user has changed the sort). A docstring on
`getDefaultSortOptions()` documents this intentional id/label asymmetry
so future maintainers don't silently rename the id and break the
sentinel checks.
The separate full-page asset browser (`AssetFilterBar.vue`) already uses
"Recent" as a distinct sort option that client-side-sorts by
`created_at`; it's untouched by this PR.
## Changes
- `shared.ts`: Swap i18n key `assetBrowser.sortDefault` →
`assetBrowser.sortRecent` (already translated in all 12 locales). Add a
docstring explaining the id/label relationship.
- `shared.test.ts`: Add an assertion that the first option is labeled
"Recent" so future label drift is caught.
## Verification
- `pnpm test:unit
src/renderer/extensions/vueNodes/widgets/components/form/dropdown/shared.test.ts`
— 18/18 pass
- `pnpm typecheck` — clean
- `pnpm format:check` — clean
- `pnpm lint` — no new issues (one pre-existing unrelated warning in
`useWorkspaceBilling.test.ts`)
- Manual verification in the Comfy Cloud local stack: opened
`gsc_starter_2` workflow, clicked the image widget on a `LoadImage`
node, opened the sort menu — confirmed it now shows "Recent" (selected)
and "A-Z" as expected. See screenshot.
## Screenshots

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11526-feat-rename-Default-sort-option-to-Recent-in-widget-image-dropdown-3496d73d365081278ce0d722f6060ccb)
by [Unito](https://www.unito.io)
---------
Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>
## Summary
Cloud Prod renders "Invalid Date" in Settings → Secrets on strict JS
Date parsers (older Safari, some WebViews) because the backend emits
timestamps with variable fractional-second precision (e.g.
`"2026-04-18T10:04:55.6513Z"` — 4 digits), which falls outside the
3-digit-only ECMA-262 grammar.
## Changes
- **What**:
- Add `parseIsoDateSafe()` in `src/utils/dateTimeUtil.ts` — trims the
fractional portion to millisecond precision before `new Date(...)` and
returns `null` for missing or unparseable input.
- `SecretListItem.vue` uses the helper and hides the Created / Last Used
line when the timestamp is invalid instead of rendering the literal
string "Invalid Date".
- Unit tests for the parser (8) and for the component (4-digit
fractional seconds, garbage input).
## Review Focus
- The backend (Go `time.RFC3339Nano`) strips trailing zeros from
fractional seconds, producing 0–9 digits depending on the value. Modern
V8 parses this leniently; older Safari does not. A durable fix is
server-side — emit exactly 3 fractional digits — and should be filed
separately. This PR is a defensive frontend guard that also protects ~10
other `toLocaleDateString` callsites if they migrate to the helper.
- Regex `(\.\d{3})\d+(?=Z|[+-]\d{2}:?\d{2}|$)` trims only when there are
**more than** 3 digits; shorter fractions and zero-fraction timestamps
are unchanged.
## Screenshots (if applicable)
Reported in Slack:
https://comfy-organization.slack.com/archives/C0A4XMHANP3/p1776443594202969
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11358-fix-render-dates-in-Secrets-panel-for-timestamps-with-3-fractional-second-digits-3466d73d3650813cb855cfbd50b3650b)
by [Unito](https://www.unito.io)
---------
Co-authored-by: Terry Jia <terryjia88@gmail.com>
## Summary
- Add Playwright E2E tests for `CancelSubscriptionDialogContent` and
`TopUpCreditsDialogContentLegacy`
- CancelSubscription tests: dialog display with date formatting, keep
subscription dismiss, confirm cancel with mocked API, error handling on
API failure
- TopUpCredits tests: dialog display with preset amounts, insufficient
credits variant, preset selection, close button dismiss, pricing link
visibility
Part of the FixIt Burndown test coverage initiative (Untested Dialogs).
## Test plan
- [ ] Verify tests pass in CI against OSS build
- [ ] `pnpm test:browser:local --
browser_tests/tests/dialogs/cancelSubscriptionDialog.spec.ts`
- [ ] `pnpm test:browser:local --
browser_tests/tests/dialogs/topUpCreditsDialog.spec.ts`
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10969-test-add-E2E-tests-for-billing-dialogs-CancelSubscription-TopUpCredits-33c6d73d36508164b268c08c99464ca1)
by [Unito](https://www.unito.io)
## Summary
Splits the WidgetImageCrop test coverage out of #11446 so this widget
can be reviewed independently.
## Changes
- **What**: Adds WidgetImageCrop unit tests covering
empty/loading/loaded states, ratio-control gating, bounding-box
delegation, and disabled upstream behavior.
## Review Focus
Focused test-only PR extracted from #11446.
Includes small test-only cleanups from the earlier review: shared crop
mock defaults, accessible image querying, and reactive upstream mock
setup.
Validated with `pnpm test:unit -- --run
src/components/imagecrop/WidgetImageCrop.test.ts`.
## Screenshots (if applicable)
N/A
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11470-test-add-WidgetImageCrop-unit-tests-3486d73d365081ff9a1eed159a8eb9a3)
by [Unito](https://www.unito.io)
---------
Co-authored-by: GitHub Action <action@github.com>
## Summary
Part of #11092 — Phase 3: remove @ts-expect-error suppressions from test
files.
This phase targets 22 suppressions across two test files:
- `src/utils/nodeDefUtil.test.ts` (18)
- `src/platform/workflow/validation/schemas/workflowSchema.test.ts` (4)
## Changes
`nodeDefUtil.test.ts`: Each test already constrains the inputs to a
known subtype (`IntInputSpec`, `FloatInputSpec`, `ComboInputSpecV2`), so
casting result to the expected subtype at the declaration site is both
correct and self-documenting. For the one test that uses the base
`InputSpec` type, the options object is extracted with an inline
structural cast.
`workflowSchema.test.ts`: validateComfyWorkflow returns
ComfyWorkflowJSON | null. The tests were accessing .nodes[0].pos without
narrowing, causing "object is possibly null" errors. Fixed with explicit
expect(validatedWorkflow).not.toBeNull() assertions before each property
access, which also improves failure messages — previously a null result
would throw a TypeError rather than a readable assertion failure.
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Low Risk**
> Test-only type-safety refactor with no runtime code changes; primary
risk is minor test assertion behavior changes if a helper unexpectedly
returns `null`.
>
> **Overview**
> Removes `@ts-expect-error` suppressions from two test suites by making
nullability and return-type expectations explicit.
>
> `workflowSchema.test.ts` now asserts `validateComfyWorkflow` results
are non-null before accessing `nodes[0]` fields, and
`nodeDefUtil.test.ts` casts `mergeInputSpec` results to the expected
spec subtype (or extracts typed options) so property assertions compile
cleanly under stricter TS settings.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
9f3829862b. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11337-refactor-remove-ts-expect-error-suppressions-in-test-files-3456d73d3650815aa2a2fca5a9332377)
by [Unito](https://www.unito.io)
Co-authored-by: Alexander Brown <drjkl@comfy.org>