Files
ComfyUI_frontend/scripts/coverage-slack-notify.ts
Alexander Brown 3b29e89abf [backport cloud/1.43] consolidated bug fixes from main / v1.44.16 (#11926)
*PR Created by the Glary-Bot Agent*

---

## Summary

Consolidated backport of bug fixes from `origin/main` (which feeds
`v1.44.16`) onto `cloud/1.43`. Cherry-picks every applicable `fix:`
commit identified in the prior backport-gap audit so `cloud/1.43`
doesn't keep falling further behind.

- **35 fixes cherry-picked** (out of 49 audited; 14 dropped — see
"Dropped commits" below).
- Verification (run on this branch): `pnpm typecheck` clean, `pnpm knip`
clean, `pnpm lint` 0 errors (warning count 2976 vs cloud/1.43 baseline
2877 — delta is from new test files brought in by the picks, not new
lint errors), `pnpm test:unit` 7842 passed / 8 skipped.

This is a single consolidated PR per request — not a per-fix backport
stream. Each commit has the original SHA recorded via `cherry picked
from commit ...` so individual reverts remain possible.

## Picked fixes (35)

App / runtime stability
- `#10995` auto fit-to-view on first subgraph entry
- `#11240` re-sync collapsed node slot positions after subgraph fitView
- `#10849` store promoted widget values per SubgraphNode instance
- `#10361` place cloned node above original in Vue renderer
- `#9935` trigger Vue reactivity on output slot type changes in
matchType
- `#11541` stop duplicate node creation when dropping image on Vue nodes
- `#11779` ensure escape key/graph navigation cancels ghost node
placement
- `#11063` disable pointer events on non-visible DOM widget overlays
- `#11295` include focusMode in splitter refresh key to prevent panel
resize
- `#11487` render edit pencil icon correctly in properties panel header
- `#11296` remove hover dimming overlay on image nodes
- `#11542` keep finished badge fully opaque in ProgressToastItem
- `#11713` search bar layout and autocomplete clipping on Desktop at
small sizes
- `#11570` dedupe keybinding modifier display
- `#11144` fix(vite): hide git rev-parse window on Windows

Cloud / subscription / workspace
- `#11130` track workspace subscription success on immediate subscribe
- `#11622` dedupe pending checkout attempt construction
- `#11463` show credits in legacy user popover on non-cloud
distributions
- `#11636` enable Chrome password autofill on signup form
- `#11520` fix(manager): migrate 4 endpoints GET→POST for CSRF hardening
(security)
- `#11425` remove deleted workflow from search results in sidebar

Assets / blueprints
- `#11700` route context menu Download through downloadMultipleAssets
- `#11502` render asset fixtures in AssetBrowserModal stories
- `#11759` hide blueprint node id in search
- `#11573` translate blueprint label

Telemetry / progress / feature flags
- `#11174` guard progress_text before canvas init
- `#11384` route progress_text feature flag check through getDevOverride
- `#11417` reset file input value after selection to allow same-file
reupload

Load3D
- `#11265` fix(load3d): restore missed hover state when viewer init is
async
- `#11546` load3d used wrong i18n key, add test
- `#11359` chain Load3D node lifecycle callbacks to preserve widget
cleanup

GLSL preview
- `#11010` resolve incorrect GLSL live preview for non-primitive widget
types
- `#11517` add GLSL live update when custom size is changed

Misc
- `#10374` consolidate `--color-coral-red` variables into
`--color-coral`
- `#11283` reduce noise in coverage Slack notifications

## Dropped commits (14) — need separate manual backport

These cherry-picks pulled in code that depends on infrastructure not
present on `cloud/1.43`, or required permissions a bot account does not
hold. Individual backport tickets recommended:

| PR | Subject | Blocker |
|---|---|---|
| #11224 | fix(ci): resolve pnpm version in release workflow for
frontend/ checkout path | touches
`.github/workflows/release-biweekly-comfyui.yaml` — bot lacks
`workflows` permission. Also depends on prerequisite #11223
(patch-release support) not on cloud/1.43. Needs human-authored
backport. |
| #11329 | prevent duplicate prepareForSave + conflicting is_new
telemetry on self-overwrite Save As | depends on
`ChangeTracker.prepareForSave` API added by #10816 / #11328 (refactors
not on cloud/1.43) |
| #11358 | render dates in Secrets panel for timestamps with >3
fractional-second digits | depends on `formatToISOWithSeconds` and other
utils in `dateTimeUtil.ts` not on cloud/1.43 |
| #11539 | cancel-subscription dialog renders Invalid Date for ISO
fractional seconds | depends on `parseIsoDateSafe` from #11358 |
| #11480 | avoid escaped secret date labels | builds on #11358's
SecretListItem changes |
| #11524 | localize secret date labels | builds on #11358's
SecretListItem changes |
| #11321 | show asset display names in bulk delete confirmation | brings
in `useMediaAssetActions.test.ts` requiring `downloadAssets` API not on
cloud/1.43 |
| #11610 | naming strategy for multi-job asset exports | same
`useMediaAssetActions` test infrastructure |
| #11737 | report total file count, not job count, in ZIP export toast |
same `useMediaAssetActions` test infrastructure |
| #11538 | resolve mesh widget thumbnails via asset preview API |
depends on `FormDropdownMenuItemProps` type, `useWidgetSelectItems` not
on cloud/1.43 |
| #11492 | use getAssetFilename in asset browser to avoid showing hashes
| same `useWidgetSelectItems` infrastructure |
| #11836 | fix(load3d): dispose THREE.Points GPU resources in
clearModel() | brings in `SceneModelManager.test.ts` referencing
`ModelAdapter` infrastructure not on cloud/1.43 (memory-leak fix —
manual backport recommended) |
| #11807 | fix(load3d): suppress error toast on 404 when loading output
model file | brings in `LoaderManager.ts` rewritten on main with
`ModelAdapter` not on cloud/1.43 |
| #11164 | enable playwright/no-force-option lint rule | requires oxlint
upgrade with playwright plugin not pinned in cloud/1.43 |

## Conflict resolution notes

Most picks applied cleanly. Conflicts encountered:

- **Test-only / snapshot-only conflicts** — resolved with `-X theirs`
(cloud/1.43 just doesn't have those test files yet).
- **Modify/delete conflicts** (test file deleted on cloud/1.43, modified
by the fix on main) — accepted the incoming version.
- **`src/components/searchbox/v2/NodeSearchListItem.test.ts`** — pulled
in by #11759 but referenced `ComfyNodeDefImpl` shape only on main.
Removed the test file from the backport commit so the runtime fix lands;
test should be rewritten/backported separately if desired.
- **`browser_tests/tests/vueNodes/glslPreview.spec.ts` and
`browser_tests/tests/subgraph/subgraphBreadcrumb.spec.ts`** — pulled in
by #11517 / #11573 but reference helpers
(`@e2e/fixtures/helpers/ExecutionHelper`, `SubgraphBreadcrumbHelper`)
not on cloud/1.43. Removed the spec files from the respective backport
commits; runtime fixes preserved.

## Out-of-scope review note

Code review flagged a behavioral concern in
`subgraphNavigationStore.ts:142-153,173-177` (`restoreViewport()` may
auto-fit on first root-graph load even when the workflow already had a
viewport). This is **pre-existing on `main`** (the cherry-picked code is
identical to `origin/main` for that file region) — not introduced by
this PR. Filing a separate ticket is recommended; not in scope for a
backport batch.

## How to validate locally

```bash
git checkout glary/cloud-1.43-batch-backport-fixes
pnpm install --frozen-lockfile
pnpm typecheck   # clean
pnpm knip        # clean
pnpm lint        # 0 errors
pnpm test:unit   # 7842 passed, 8 skipped
```

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11926-backport-cloud-1-43-consolidated-bug-fixes-from-main-v1-44-16-3566d73d3650812393d3e0ea79ab9a14)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Christian Byrne <cbyrne@comfy.org>
Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: Terry Jia <terryjia88@gmail.com>
Co-authored-by: Dante <bunggl@naver.com>
Co-authored-by: Benjamin Lu <benjaminlu1107@gmail.com>
Co-authored-by: dante <dante@danteui-MacStudio.local>
Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>
Co-authored-by: guill <jacob.e.segal@gmail.com>
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: pythongosssss <125205205+pythongosssss@users.noreply.github.com>
Co-authored-by: Simon Pinfold <synap5e@users.noreply.github.com>
Co-authored-by: comfydesigner <alextov@comfy.org>
Co-authored-by: Alex <alex@Mac.lan>
Co-authored-by: Robin Huang <robin.j.huang@gmail.com>
Co-authored-by: Kelly Yang <124ykl@gmail.com>
Co-authored-by: jaeone94 <89377375+jaeone94@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Dr.Lt.Data <128333288+ltdrdata@users.noreply.github.com>
2026-05-05 01:03:13 -07:00

234 lines
6.0 KiB
TypeScript

import { existsSync, readFileSync } from 'node:fs'
const TARGET = 80
const MILESTONE_STEP = 5
const MIN_DELTA = 0.05
const BAR_WIDTH = 20
interface CoverageData {
percentage: number
totalLines: number
coveredLines: number
}
interface SlackBlock {
type: 'section'
text: {
type: 'mrkdwn'
text: string
}
}
function parseLcovContent(content: string): CoverageData | null {
const perFile = new Map<string, { lf: number; lh: number }>()
let currentFile = ''
for (const line of content.split('\n')) {
if (line.startsWith('SF:')) {
currentFile = line.slice(3)
} else if (line.startsWith('LF:')) {
const n = parseInt(line.slice(3), 10) || 0
const entry = perFile.get(currentFile) ?? { lf: 0, lh: 0 }
entry.lf = n
perFile.set(currentFile, entry)
} else if (line.startsWith('LH:')) {
const n = parseInt(line.slice(3), 10) || 0
const entry = perFile.get(currentFile) ?? { lf: 0, lh: 0 }
entry.lh = n
perFile.set(currentFile, entry)
}
}
let totalLines = 0
let coveredLines = 0
for (const { lf, lh } of perFile.values()) {
totalLines += lf
coveredLines += lh
}
if (totalLines === 0) return null
return {
percentage: (coveredLines / totalLines) * 100,
totalLines,
coveredLines
}
}
function parseLcov(filePath: string): CoverageData | null {
if (!existsSync(filePath)) return null
return parseLcovContent(readFileSync(filePath, 'utf-8'))
}
function progressBar(percentage: number): string {
const clamped = Math.max(0, Math.min(100, percentage))
const filled = Math.round((clamped / 100) * BAR_WIDTH)
const empty = BAR_WIDTH - filled
return '█'.repeat(filled) + '░'.repeat(empty)
}
function formatPct(value: number): string {
return value.toFixed(1) + '%'
}
function formatDelta(delta: number): string {
const rounded = Math.abs(delta) < MIN_DELTA ? 0 : delta
const sign = rounded >= 0 ? '+' : ''
return sign + rounded.toFixed(1) + '%'
}
function crossedMilestone(prev: number, curr: number): number | null {
const prevBucket = Math.floor(prev / MILESTONE_STEP)
const currBucket = Math.floor(curr / MILESTONE_STEP)
if (currBucket > prevBucket) {
return currBucket * MILESTONE_STEP
}
return null
}
function buildMilestoneBlock(label: string, milestone: number): SlackBlock {
if (milestone >= TARGET) {
return {
type: 'section',
text: {
type: 'mrkdwn',
text: [
`🏆 *GOAL REACHED: ${label} coverage hit ${milestone}%!* 🏆`,
`\`${progressBar(milestone)}\` ${milestone}% ✅`,
'The team did it! 🎊🥳🎉'
].join('\n')
}
}
}
const remaining = TARGET - milestone
return {
type: 'section',
text: {
type: 'mrkdwn',
text: [
`🎉🎉🎉 *MILESTONE: ${label} coverage hit ${milestone}%!*`,
`\`${progressBar(milestone)}\` ${milestone}% → ${TARGET}% target`,
`${remaining} percentage point${remaining !== 1 ? 's' : ''} to go!`
].join('\n')
}
}
}
function parseArgs(argv: string[]): {
prUrl: string
prNumber: string
author: string
} {
let prUrl = ''
let prNumber = ''
let author = ''
for (const arg of argv) {
if (arg.startsWith('--pr-url=')) prUrl = arg.slice('--pr-url='.length)
else if (arg.startsWith('--pr-number='))
prNumber = arg.slice('--pr-number='.length)
else if (arg.startsWith('--author=')) author = arg.slice('--author='.length)
}
return { prUrl, prNumber, author }
}
function formatCoverageRow(
label: string,
current: CoverageData,
baseline: CoverageData
): string {
const delta = current.percentage - baseline.percentage
return `*${label}:* ${formatPct(baseline.percentage)}${formatPct(current.percentage)} (${formatDelta(delta)})`
}
function main() {
const { prUrl, prNumber, author } = parseArgs(process.argv.slice(2))
const unitCurrent = parseLcov('coverage/lcov.info')
const unitBaseline = parseLcov('temp/coverage-baseline/lcov.info')
const e2eCurrent = parseLcov('temp/e2e-coverage/coverage.lcov')
const e2eBaseline = parseLcov('temp/e2e-coverage-baseline/coverage.lcov')
const unitDelta =
unitCurrent !== null && unitBaseline !== null
? unitCurrent.percentage - unitBaseline.percentage
: 0
const e2eDelta =
e2eCurrent !== null && e2eBaseline !== null
? e2eCurrent.percentage - e2eBaseline.percentage
: 0
const unitImproved = unitDelta >= MIN_DELTA
const e2eImproved = e2eDelta >= MIN_DELTA
if (!unitImproved && !e2eImproved) {
process.exit(0)
}
const blocks: SlackBlock[] = []
const summaryLines: string[] = []
summaryLines.push(
`✅ *Coverage improved!* — <${prUrl}|PR #${prNumber}> by <https://github.com/${author}|${author}>`
)
summaryLines.push('')
if (unitImproved) {
summaryLines.push(formatCoverageRow('Unit', unitCurrent!, unitBaseline!))
}
if (e2eImproved) {
summaryLines.push(formatCoverageRow('E2E', e2eCurrent!, e2eBaseline!))
}
summaryLines.push('')
if (unitCurrent) {
summaryLines.push(
`\`${progressBar(unitCurrent.percentage)}\` ${formatPct(unitCurrent.percentage)} unit → ${TARGET}% target`
)
}
if (e2eCurrent) {
summaryLines.push(
`\`${progressBar(e2eCurrent.percentage)}\` ${formatPct(e2eCurrent.percentage)} e2e → ${TARGET}% target`
)
}
blocks.push({
type: 'section',
text: {
type: 'mrkdwn',
text: summaryLines.join('\n')
}
})
if (unitCurrent && unitBaseline) {
const milestone = crossedMilestone(
unitBaseline.percentage,
unitCurrent.percentage
)
if (milestone !== null) {
blocks.push(buildMilestoneBlock('Unit test', milestone))
}
}
if (e2eCurrent && e2eBaseline) {
const milestone = crossedMilestone(
e2eBaseline.percentage,
e2eCurrent.percentage
)
if (milestone !== null) {
blocks.push(buildMilestoneBlock('E2E test', milestone))
}
}
const payload = { text: 'Coverage improved!', blocks }
process.stdout.write(JSON.stringify(payload))
}
main()