diff --git a/.github/workflows/ci-lint-format.yaml b/.github/workflows/ci-lint-format.yaml index 04c1145d1..8de5d46b9 100644 --- a/.github/workflows/ci-lint-format.yaml +++ b/.github/workflows/ci-lint-format.yaml @@ -26,6 +26,14 @@ jobs: - name: Setup frontend uses: ./.github/actions/setup-frontend + - name: Detect browser_tests changes + id: changed-paths + uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + with: + filters: | + browser_tests: + - 'browser_tests/**' + - name: Run ESLint with auto-fix run: pnpm lint:fix @@ -60,6 +68,10 @@ jobs: pnpm format:check pnpm knip + - name: Typecheck browser tests + if: steps.changed-paths.outputs.browser_tests == 'true' + run: pnpm typecheck:browser + - name: Comment on PR about auto-fix if: steps.verify-changed-files.outputs.changed == 'true' && github.event.pull_request.head.repo.full_name == github.repository continue-on-error: true diff --git a/AGENTS.md b/AGENTS.md index 4603eeabc..52cd688e6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -37,6 +37,10 @@ See @docs/guidance/\*.md for file-type-specific conventions (auto-loaded by glob The project uses **Nx** for build orchestration and task management +## Package Manager + +This project uses **pnpm**. Always prefer scripts defined in `package.json` (e.g., `pnpm test:unit`, `pnpm lint`). To run arbitrary packages not in scripts, use `pnpx` or `pnpm dlx` — never `npx`. + ## Build, Test, and Development Commands - `pnpm dev`: Start Vite dev server. diff --git a/browser_tests/assets/subgraphs/subgraph-with-multiple-promoted-previews.json b/browser_tests/assets/subgraphs/subgraph-with-multiple-promoted-previews.json new file mode 100644 index 000000000..9c8e6c838 --- /dev/null +++ b/browser_tests/assets/subgraphs/subgraph-with-multiple-promoted-previews.json @@ -0,0 +1,394 @@ +{ + "id": "43e9499c-2512-43b5-a5a1-2485eb65da32", + "revision": 0, + "last_node_id": 8, + "last_link_id": 10, + "nodes": [ + { + "id": 1, + "type": "LoadImage", + "pos": [170.55728894250745, 515.6401487466619], + "size": [282.8166809082031, 363.8333435058594], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [7, 9] + }, + { + "name": "MASK", + "type": "MASK", + "links": null + } + ], + "properties": { + "Node name for S&R": "LoadImage" + }, + "widgets_values": ["example.png", "image"] + }, + { + "id": 7, + "type": "21dea088-e1b4-47a4-a01f-3d1bf4504001", + "pos": [500.2639113468392, 519.9960755960157], + "size": [464.95001220703125, 615.8333129882812], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 7 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [10] + } + ], + "properties": { + "proxyWidgets": [ + ["2", "$$canvas-image-preview"], + ["4", "$$canvas-image-preview"] + ] + }, + "widgets_values": [] + }, + { + "id": 8, + "type": "a7a0350a-af99-4d26-9391-450b4f726206", + "pos": [1000.5293620197185, 499.9253405678786], + "size": [225, 359.8333435058594], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "name": "image1", + "type": "IMAGE", + "link": 9 + }, + { + "name": "image2", + "type": "IMAGE", + "link": 10 + } + ], + "outputs": [], + "properties": { + "proxyWidgets": [["6", "$$canvas-image-preview"]] + }, + "widgets_values": [] + } + ], + "links": [ + [7, 1, 0, 7, 0, "IMAGE"], + [9, 1, 0, 8, 0, "IMAGE"], + [10, 7, 0, 8, 1, "IMAGE"] + ], + "groups": [], + "definitions": { + "subgraphs": [ + { + "id": "21dea088-e1b4-47a4-a01f-3d1bf4504001", + "version": 1, + "state": { + "lastGroupId": 0, + "lastNodeId": 8, + "lastLinkId": 10, + "lastRerouteId": 0 + }, + "revision": 0, + "config": {}, + "name": "New Subgraph", + "inputNode": { + "id": -10, + "bounding": [297.7833638107301, 502.6302057820892, 120, 60] + }, + "outputNode": { + "id": -20, + "bounding": [1052.8175480718996, 502.6302057820892, 120, 60] + }, + "inputs": [ + { + "id": "afc8dbc3-12e6-4b3c-9840-9b398d06e6bd", + "name": "images", + "type": "IMAGE", + "linkIds": [1, 2], + "localized_name": "images", + "pos": [397.7833638107301, 522.6302057820892] + } + ], + "outputs": [ + { + "id": "d0a84974-5f4d-4f4b-b23a-2e7288a9689d", + "name": "IMAGE", + "type": "IMAGE", + "linkIds": [5], + "localized_name": "IMAGE", + "pos": [1072.8175480718996, 522.6302057820892] + } + ], + "widgets": [], + "nodes": [ + { + "id": 4, + "type": "PreviewImage", + "pos": [767.8225773415076, 602.8695134060456], + "size": [225, 303.8333435058594], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "localized_name": "images", + "name": "images", + "type": "IMAGE", + "link": 3 + } + ], + "outputs": [], + "properties": { + "Node name for S&R": "PreviewImage" + }, + "widgets_values": [] + }, + { + "id": 2, + "type": "PreviewImage", + "pos": [754.9358989867657, 188.55375831225257], + "size": [225, 303.8333435058594], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [ + { + "localized_name": "images", + "name": "images", + "type": "IMAGE", + "link": 1 + } + ], + "outputs": [], + "properties": { + "Node name for S&R": "PreviewImage" + }, + "widgets_values": [] + }, + { + "id": 3, + "type": "ImageInvert", + "pos": [477.783932416778, 542.2440719627998], + "size": [225, 71.83333587646484], + "flags": { + "collapsed": false + }, + "order": 1, + "mode": 0, + "inputs": [ + { + "localized_name": "image", + "name": "image", + "type": "IMAGE", + "link": 2 + } + ], + "outputs": [ + { + "localized_name": "IMAGE", + "name": "IMAGE", + "type": "IMAGE", + "links": [3, 5] + } + ], + "properties": { + "Node name for S&R": "ImageInvert" + }, + "widgets_values": [] + } + ], + "groups": [], + "links": [ + { + "id": 3, + "origin_id": 3, + "origin_slot": 0, + "target_id": 4, + "target_slot": 0, + "type": "IMAGE" + }, + { + "id": 1, + "origin_id": -10, + "origin_slot": 0, + "target_id": 2, + "target_slot": 0, + "type": "IMAGE" + }, + { + "id": 2, + "origin_id": -10, + "origin_slot": 0, + "target_id": 3, + "target_slot": 0, + "type": "IMAGE" + }, + { + "id": 5, + "origin_id": 3, + "origin_slot": 0, + "target_id": -20, + "target_slot": 0, + "type": "IMAGE" + } + ], + "extra": {} + }, + { + "id": "a7a0350a-af99-4d26-9391-450b4f726206", + "version": 1, + "state": { + "lastGroupId": 0, + "lastNodeId": 8, + "lastLinkId": 10, + "lastRerouteId": 0 + }, + "revision": 0, + "config": {}, + "name": "New Subgraph", + "inputNode": { + "id": -10, + "bounding": [973.7423316105073, 561.9744246746379, 120, 80] + }, + "outputNode": { + "id": -20, + "bounding": [1905.487372786412, 581.9744246746379, 120, 40] + }, + "inputs": [ + { + "id": "20ac4159-6814-4d40-a217-ea260152b689", + "name": "image1", + "type": "IMAGE", + "linkIds": [4], + "localized_name": "image1", + "pos": [1073.7423316105073, 581.9744246746379] + }, + { + "id": "c3759a8c-914e-4450-bc41-ca683ffce96b", + "name": "image2", + "type": "IMAGE", + "linkIds": [8], + "localized_name": "image2", + "shape": 7, + "pos": [1073.7423316105073, 601.9744246746379] + } + ], + "outputs": [], + "widgets": [], + "nodes": [ + { + "id": 5, + "type": "ImageStitch", + "pos": [1153.7423085222254, 396.2033931749105], + "size": [270, 225.1666717529297], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [ + { + "localized_name": "image1", + "name": "image1", + "type": "IMAGE", + "link": 4 + }, + { + "localized_name": "image2", + "name": "image2", + "shape": 7, + "type": "IMAGE", + "link": 8 + } + ], + "outputs": [ + { + "localized_name": "IMAGE", + "name": "IMAGE", + "type": "IMAGE", + "links": [6] + } + ], + "properties": { + "Node name for S&R": "ImageStitch" + }, + "widgets_values": ["right", true, 0, "white"] + }, + { + "id": 6, + "type": "PreviewImage", + "pos": [1620.4874189629757, 529.9122050216333], + "size": [225, 307.8333435058594], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [ + { + "localized_name": "images", + "name": "images", + "type": "IMAGE", + "link": 6 + } + ], + "outputs": [], + "properties": { + "Node name for S&R": "PreviewImage" + }, + "widgets_values": [] + } + ], + "groups": [], + "links": [ + { + "id": 6, + "origin_id": 5, + "origin_slot": 0, + "target_id": 6, + "target_slot": 0, + "type": "IMAGE" + }, + { + "id": 4, + "origin_id": -10, + "origin_slot": 0, + "target_id": 5, + "target_slot": 0, + "type": "IMAGE" + }, + { + "id": 8, + "origin_id": -10, + "origin_slot": 1, + "target_id": 5, + "target_slot": 1, + "type": "IMAGE" + } + ], + "extra": {} + } + ] + }, + "config": {}, + "extra": { + "ds": { + "scale": 0.7269777827561446, + "offset": [-35.273237658266034, -55.17394203309256] + }, + "frontendVersion": "1.40.8" + }, + "version": 0.4 +} diff --git a/browser_tests/assets/subgraphs/subgraph-with-preview-node.json b/browser_tests/assets/subgraphs/subgraph-with-preview-node.json new file mode 100644 index 000000000..7db8b388c --- /dev/null +++ b/browser_tests/assets/subgraphs/subgraph-with-preview-node.json @@ -0,0 +1,170 @@ +{ + "id": "preview-subgraph-test-001", + "revision": 0, + "last_node_id": 11, + "last_link_id": 2, + "nodes": [ + { + "id": 5, + "type": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "pos": [318.6320139868054, 212.9091015141833], + "size": [225, 368], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 2 + } + ], + "outputs": [], + "properties": { + "proxyWidgets": [ + ["10", "filename_prefix"], + ["10", "$$canvas-image-preview"] + ], + "cnr_id": "comfy-core", + "ver": "0.13.0", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.6.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": [] + }, + { + "id": 11, + "type": "LoadImage", + "pos": [-0.5080003681592018, 211.3051121416672], + "size": [282.8333435058594, 364], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [2] + }, + { + "name": "MASK", + "type": "MASK", + "links": null + } + ], + "properties": { + "ue_properties": { + "widget_ue_connectable": {}, + "input_ue_unconnectable": {} + }, + "cnr_id": "comfy-core", + "ver": "0.13.0", + "Node name for S&R": "LoadImage" + }, + "widgets_values": ["example.png", "image"] + } + ], + "links": [[2, 11, 0, 5, 0, "IMAGE"]], + "groups": [], + "definitions": { + "subgraphs": [ + { + "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "version": 1, + "state": { + "lastGroupId": 0, + "lastNodeId": 11, + "lastLinkId": 2, + "lastRerouteId": 0 + }, + "revision": 0, + "config": {}, + "name": "New Subgraph", + "inputNode": { + "id": -10, + "bounding": [300, 350, 120, 60] + }, + "outputNode": { + "id": -20, + "bounding": [900, 350, 120, 40] + }, + "inputs": [ + { + "id": "img-slot-001", + "name": "images", + "type": "IMAGE", + "linkIds": [1], + "pos": [400, 370] + } + ], + "outputs": [], + "widgets": [], + "nodes": [ + { + "id": 10, + "type": "SaveImage", + "pos": [500.0046924937855, 300.0146992076527], + "size": [315, 340], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [ + { + "localized_name": "images", + "name": "images", + "type": "IMAGE", + "link": 1 + } + ], + "outputs": [], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.13.0", + "Node name for S&R": "SaveImage", + "ue_properties": { + "widget_ue_connectable": {}, + "version": "7.6.2", + "input_ue_unconnectable": {} + } + }, + "widgets_values": ["ComfyUI"] + } + ], + "groups": [], + "links": [ + { + "id": 1, + "origin_id": -10, + "origin_slot": 0, + "target_id": 10, + "target_slot": 0, + "type": "IMAGE" + } + ], + "extra": { + "ue_links": [], + "links_added_by_ue": [] + } + } + ] + }, + "config": {}, + "extra": { + "ds": { + "scale": 1.1819400303977265, + "offset": [81.66005130613983, -19.028558221588725] + }, + "frontendVersion": "1.40.3", + "ue_links": [], + "links_added_by_ue": [], + "VHS_latentpreview": false, + "VHS_latentpreviewrate": 0, + "VHS_MetadataImage": true, + "VHS_KeepIntermediate": true + }, + "version": 0.4 +} diff --git a/browser_tests/fixtures/helpers/CommandHelper.ts b/browser_tests/fixtures/helpers/CommandHelper.ts index 2d8420fd1..c46906afd 100644 --- a/browser_tests/fixtures/helpers/CommandHelper.ts +++ b/browser_tests/fixtures/helpers/CommandHelper.ts @@ -11,7 +11,10 @@ export class CommandHelper { ): Promise { await this.page.evaluate( ({ commandId, metadata }) => { - return window['app'].extensionManager.command.execute(commandId, { + const app = window.app + if (!app) throw new Error('window.app is not available') + + return app.extensionManager.command.execute(commandId, { metadata }) }, diff --git a/browser_tests/fixtures/helpers/DragDropHelper.ts b/browser_tests/fixtures/helpers/DragDropHelper.ts index f55c73915..181404d8b 100644 --- a/browser_tests/fixtures/helpers/DragDropHelper.ts +++ b/browser_tests/fixtures/helpers/DragDropHelper.ts @@ -115,6 +115,16 @@ export class DragDropHelper { const dragOverEvent = new DragEvent('dragover', eventOptions) const dropEvent = new DragEvent('drop', eventOptions) + const graphCanvasElement = document.querySelector('#graph-canvas') + + // Keep Litegraph's drag-over node tracking in sync when the drop target is a + // Vue node DOM overlay outside of the graph canvas element. + if (graphCanvasElement && !graphCanvasElement.contains(targetElement)) { + graphCanvasElement.dispatchEvent( + new DragEvent('dragover', eventOptions) + ) + } + Object.defineProperty(dropEvent, 'preventDefault', { value: () => {}, writable: false diff --git a/browser_tests/fixtures/helpers/NodeOperationsHelper.ts b/browser_tests/fixtures/helpers/NodeOperationsHelper.ts index 5f11b473b..429809679 100644 --- a/browser_tests/fixtures/helpers/NodeOperationsHelper.ts +++ b/browser_tests/fixtures/helpers/NodeOperationsHelper.ts @@ -33,6 +33,10 @@ export class NodeOperationsHelper { }) } + async getNodeCount(): Promise { + return await this.page.evaluate(() => window.app!.graph.nodes.length) + } + async getNodes(): Promise { return await this.page.evaluate(() => { return window.app!.graph.nodes diff --git a/browser_tests/fixtures/helpers/SubgraphHelper.ts b/browser_tests/fixtures/helpers/SubgraphHelper.ts index 645c05448..eed6cf585 100644 --- a/browser_tests/fixtures/helpers/SubgraphHelper.ts +++ b/browser_tests/fixtures/helpers/SubgraphHelper.ts @@ -36,7 +36,7 @@ export class SubgraphHelper { const currentGraph = app.canvas!.graph! // Check if we're in a subgraph - if (currentGraph.constructor.name !== 'Subgraph') { + if (!('inputNode' in currentGraph)) { throw new Error( 'Not in a subgraph - this method only works inside subgraphs' ) diff --git a/browser_tests/fixtures/selectors.ts b/browser_tests/fixtures/selectors.ts index 4ad8d1d82..11994e860 100644 --- a/browser_tests/fixtures/selectors.ts +++ b/browser_tests/fixtures/selectors.ts @@ -27,6 +27,7 @@ export const TestIds = { settingsContainer: 'settings-container', settingsTabAbout: 'settings-tab-about', confirm: 'confirm-dialog', + missingNodes: 'missing-nodes-warning', about: 'about-panel', whatsNewSection: 'whats-new-section' }, @@ -46,8 +47,12 @@ export const TestIds = { widgets: { decrement: 'decrement', increment: 'increment', + domWidgetTextarea: 'dom-widget-textarea', subgraphEnterButton: 'subgraph-enter-button' }, + breadcrumb: { + subgraph: 'subgraph-breadcrumb' + }, templates: { content: 'template-workflows-content', workflowCard: (id: string) => `template-workflow-${id}` @@ -70,6 +75,7 @@ export type TestIdValue = | (typeof TestIds.propertiesPanel)[keyof typeof TestIds.propertiesPanel] | (typeof TestIds.node)[keyof typeof TestIds.node] | (typeof TestIds.widgets)[keyof typeof TestIds.widgets] + | (typeof TestIds.breadcrumb)[keyof typeof TestIds.breadcrumb] | Exclude< (typeof TestIds.templates)[keyof typeof TestIds.templates], (id: string) => string diff --git a/browser_tests/fixtures/utils/litegraphUtils.ts b/browser_tests/fixtures/utils/litegraphUtils.ts index 08e504150..b1295e5a8 100644 --- a/browser_tests/fixtures/utils/litegraphUtils.ts +++ b/browser_tests/fixtures/utils/litegraphUtils.ts @@ -128,7 +128,8 @@ class NodeSlotReference { nodeSize: [node.size[0], node.size[1]], rawConnectionPos: [rawPos[0], rawPos[1]], convertedPos: [convertedPos[0], convertedPos[1]], - currentGraphType: window.app!.canvas.graph!.constructor.name + currentGraphType: + 'inputNode' in window.app!.canvas.graph! ? 'Subgraph' : 'LGraph' } ) @@ -461,18 +462,44 @@ export class NodeReference { // Try multiple positions to avoid DOM widget interference const clickPositions = [ { x: nodePos.x + nodeSize.width / 2, y: nodePos.y + titleHeight + 5 }, - { x: nodePos.x + nodeSize.width / 2, y: nodePos.y + nodeSize.height / 2 }, + { + x: nodePos.x + nodeSize.width / 2, + y: nodePos.y + nodeSize.height / 2 + }, { x: nodePos.x + 20, y: nodePos.y + titleHeight + 5 } ] + // Click the enter_subgraph title button (top-right of title bar). + // This is more reliable than dblclick on the node body because + // promoted DOM widgets can overlay the body and intercept events. + const subgraphButtonPos = { + x: nodePos.x + nodeSize.width - 15, + y: nodePos.y - titleHeight / 2 + } + const checkIsInSubgraph = async () => { return this.comfyPage.page.evaluate(() => { const graph = window.app!.canvas.graph - return graph?.constructor?.name === 'Subgraph' + return !!graph && 'inputNode' in graph }) } await expect(async () => { + // Try just clicking the enter button first + await this.comfyPage.canvas.click({ + position: { x: 250, y: 250 }, + force: true + }) + await this.comfyPage.nextFrame() + + await this.comfyPage.canvas.click({ + position: subgraphButtonPos, + force: true + }) + await this.comfyPage.nextFrame() + + if (await checkIsInSubgraph()) return + for (const position of clickPositions) { // Clear any selection first await this.comfyPage.canvas.click({ diff --git a/browser_tests/fixtures/ws.ts b/browser_tests/fixtures/ws.ts index 4fa701918..02e68bdb7 100644 --- a/browser_tests/fixtures/ws.ts +++ b/browser_tests/fixtures/ws.ts @@ -1,9 +1,5 @@ import { test as base } from '@playwright/test' -interface TestWindow extends Window { - __ws__?: Record -} - export const webSocketFixture = base.extend<{ ws: { trigger(data: unknown, url?: string): Promise } }>({ diff --git a/browser_tests/helpers/promotedWidgets.ts b/browser_tests/helpers/promotedWidgets.ts new file mode 100644 index 000000000..074fc77ce --- /dev/null +++ b/browser_tests/helpers/promotedWidgets.ts @@ -0,0 +1,64 @@ +import type { ComfyPage } from '../fixtures/ComfyPage' + +export type PromotedWidgetEntry = [string, string] + +export function isPromotedWidgetEntry( + entry: unknown +): entry is PromotedWidgetEntry { + return ( + Array.isArray(entry) && + entry.length === 2 && + typeof entry[0] === 'string' && + typeof entry[1] === 'string' + ) +} + +export function normalizePromotedWidgets( + value: unknown +): PromotedWidgetEntry[] { + if (!Array.isArray(value)) return [] + return value.filter(isPromotedWidgetEntry) +} + +export async function getPromotedWidgets( + comfyPage: ComfyPage, + nodeId: string +): Promise { + const raw = await comfyPage.page.evaluate((id) => { + const node = window.app!.canvas.graph!.getNodeById(id) + return node?.properties?.proxyWidgets ?? [] + }, nodeId) + + return normalizePromotedWidgets(raw) +} + +export async function getPromotedWidgetNames( + comfyPage: ComfyPage, + nodeId: string +): Promise { + const promotedWidgets = await getPromotedWidgets(comfyPage, nodeId) + return promotedWidgets.map(([, widgetName]) => widgetName) +} + +export async function getPromotedWidgetCount( + comfyPage: ComfyPage, + nodeId: string +): Promise { + const promotedWidgets = await getPromotedWidgets(comfyPage, nodeId) + return promotedWidgets.length +} + +export async function getPromotedWidgetCountByName( + comfyPage: ComfyPage, + nodeId: string, + widgetName: string +): Promise { + return comfyPage.page.evaluate( + ([id, name]) => { + const node = window.app!.canvas.graph!.getNodeById(id) + const widgets = node?.widgets ?? [] + return widgets.filter((widget) => widget.name === name).length + }, + [nodeId, widgetName] as const + ) +} diff --git a/browser_tests/tests/browserTabTitle.spec.ts b/browser_tests/tests/browserTabTitle.spec.ts index 06bf55ec8..1ab294be3 100644 --- a/browser_tests/tests/browserTabTitle.spec.ts +++ b/browser_tests/tests/browserTabTitle.spec.ts @@ -14,7 +14,9 @@ test.describe('Browser tab title', { tag: '@smoke' }, () => { return (window.app!.extensionManager as WorkspaceStore).workflow .activeWorkflow?.filename }) - expect(await comfyPage.page.title()).toBe(`*${workflowName} - ComfyUI`) + await expect + .poll(() => comfyPage.page.title()) + .toBe(`*${workflowName} - ComfyUI`) }) // Failing on CI @@ -51,7 +53,7 @@ test.describe('Browser tab title', { tag: '@smoke' }, () => { }) test('Can display default title', async ({ comfyPage }) => { - expect(await comfyPage.page.title()).toBe('ComfyUI') + await expect.poll(() => comfyPage.page.title()).toBe('ComfyUI') }) }) }) diff --git a/browser_tests/tests/changeTracker.spec.ts b/browser_tests/tests/changeTracker.spec.ts index 80d00c336..ad1217934 100644 --- a/browser_tests/tests/changeTracker.spec.ts +++ b/browser_tests/tests/changeTracker.spec.ts @@ -160,12 +160,12 @@ test.describe('Change Tracker', { tag: '@workflow' }, () => { }) // Click empty space to trigger a change detection. await comfyPage.canvasOps.clickEmptySpace() - expect(await comfyPage.workflow.getUndoQueueSize()).toBe(1) + await expect.poll(() => comfyPage.workflow.getUndoQueueSize()).toBe(1) }) test('Ignores changes in workflow.ds', async ({ comfyPage }) => { expect(await comfyPage.workflow.getUndoQueueSize()).toBe(0) await comfyPage.canvasOps.pan({ x: 10, y: 10 }) - expect(await comfyPage.workflow.getUndoQueueSize()).toBe(0) + await expect.poll(() => comfyPage.workflow.getUndoQueueSize()).toBe(0) }) }) diff --git a/browser_tests/tests/colorPalette.spec.ts b/browser_tests/tests/colorPalette.spec.ts index 66312a7dc..31aa14d7d 100644 --- a/browser_tests/tests/colorPalette.spec.ts +++ b/browser_tests/tests/colorPalette.spec.ts @@ -245,11 +245,18 @@ test.describe( await comfyPage.settings.setSetting('Comfy.ColorPalette', 'light') await comfyPage.nextFrame() const parsed = await comfyPage.page.evaluate(() => { - return window['app'].graph.serialize() + const graph = window.app!.graph! + if (typeof graph.serialize !== 'function') { + throw new Error('app.graph.serialize is not available') + } + return graph.serialize() as { + nodes: Array<{ bgcolor?: string; color?: string }> + } }) expect(parsed.nodes).toBeDefined() expect(Array.isArray(parsed.nodes)).toBe(true) - for (const node of parsed.nodes) { + const nodes = parsed.nodes + for (const node of nodes) { if (node.bgcolor) expect(node.bgcolor).not.toMatch(/hsla/) if (node.color) expect(node.color).not.toMatch(/hsla/) } diff --git a/browser_tests/tests/dialog.spec.ts b/browser_tests/tests/dialog.spec.ts index a241c0750..993281c64 100644 --- a/browser_tests/tests/dialog.spec.ts +++ b/browser_tests/tests/dialog.spec.ts @@ -4,6 +4,7 @@ import { expect } from '@playwright/test' import type { Keybinding } from '../../src/platform/keybindings/types' import { comfyPageFixture as test } from '../fixtures/ComfyPage' import { DefaultGraphPositions } from '../fixtures/constants/defaultGraphPositions' +import { TestIds } from '../fixtures/selectors' test.beforeEach(async ({ comfyPage }) => { await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Disabled') @@ -15,8 +16,9 @@ test.describe('Load workflow warning', { tag: '@ui' }, () => { }) => { await comfyPage.workflow.loadWorkflow('missing/missing_nodes') - // Wait for the element with the .comfy-missing-nodes selector to be visible - const missingNodesWarning = comfyPage.page.locator('.comfy-missing-nodes') + const missingNodesWarning = comfyPage.page.getByTestId( + TestIds.dialogs.missingNodes + ) await expect(missingNodesWarning).toBeVisible() }) @@ -25,8 +27,9 @@ test.describe('Load workflow warning', { tag: '@ui' }, () => { }) => { await comfyPage.workflow.loadWorkflow('missing/missing_nodes_in_subgraph') - // Wait for the element with the .comfy-missing-nodes selector to be visible - const missingNodesWarning = comfyPage.page.locator('.comfy-missing-nodes') + const missingNodesWarning = comfyPage.page.getByTestId( + TestIds.dialogs.missingNodes + ) await expect(missingNodesWarning).toBeVisible() // Verify the missing node text includes subgraph context @@ -38,13 +41,14 @@ test.describe('Load workflow warning', { tag: '@ui' }, () => { test('Does not report warning on undo/redo', async ({ comfyPage }) => { await comfyPage.settings.setSetting('Comfy.NodeSearchBoxImpl', 'v1 (legacy)') + const missingNodesWarning = comfyPage.page.getByTestId( + TestIds.dialogs.missingNodes + ) await comfyPage.workflow.loadWorkflow('missing/missing_nodes') - await comfyPage.page - .locator('.p-dialog') - .getByRole('button', { name: 'Close' }) - .click({ force: true }) - await comfyPage.page.locator('.p-dialog').waitFor({ state: 'hidden' }) + await expect(missingNodesWarning).toBeVisible() + await comfyPage.page.keyboard.press('Escape') + await expect(missingNodesWarning).not.toBeVisible() // Wait for any async operations to complete after dialog closes await comfyPage.nextFrame() @@ -55,9 +59,14 @@ test('Does not report warning on undo/redo', async ({ comfyPage }) => { // Undo and redo the change await comfyPage.keyboard.undo() - await expect(comfyPage.page.locator('.comfy-missing-nodes')).not.toBeVisible() + await expect(async () => { + await expect(missingNodesWarning).not.toBeVisible() + }).toPass({ timeout: 5000 }) + await comfyPage.keyboard.redo() - await expect(comfyPage.page.locator('.comfy-missing-nodes')).not.toBeVisible() + await expect(async () => { + await expect(missingNodesWarning).not.toBeVisible() + }).toPass({ timeout: 5000 }) }) test.describe('Execution error', () => { @@ -401,7 +410,7 @@ test.describe('Signin dialog', () => { test('Paste content to signin dialog should not paste node on canvas', async ({ comfyPage }) => { - const nodeNum = (await comfyPage.nodeOps.getNodes()).length + const nodeNum = await comfyPage.nodeOps.getNodeCount() await comfyPage.canvas.click({ position: DefaultGraphPositions.emptyLatentWidgetClick }) @@ -424,6 +433,6 @@ test.describe('Signin dialog', () => { await input.press('Control+v') await expect(input).toHaveValue('test_password') - expect(await comfyPage.nodeOps.getNodes()).toHaveLength(nodeNum) + expect(await comfyPage.nodeOps.getNodeCount()).toBe(nodeNum) }) }) diff --git a/browser_tests/tests/menu.spec.ts b/browser_tests/tests/menu.spec.ts index 50005afc5..d5d64dd0a 100644 --- a/browser_tests/tests/menu.spec.ts +++ b/browser_tests/tests/menu.spec.ts @@ -203,19 +203,19 @@ test.describe('Menu', { tag: '@ui' }, () => { await topbar.switchTheme('light') // Verify menu stays open and Light theme shows as active - await expect(menu).toBeVisible() - await expect(themeSubmenu).toBeVisible() - - // Check that Light theme is active - expect(await topbar.isMenuItemActive(lightThemeItem)).toBe(true) + await expect(async () => { + await expect(menu).toBeVisible() + await expect(themeSubmenu).toBeVisible() + expect(await topbar.isMenuItemActive(lightThemeItem)).toBe(true) + }).toPass({ timeout: 5000 }) // Screenshot with light theme active await comfyPage.attachScreenshot('theme-menu-light-active') // Verify ColorPalette setting is set to "light" - expect(await comfyPage.settings.getSetting('Comfy.ColorPalette')).toBe( - 'light' - ) + await expect + .poll(() => comfyPage.settings.getSetting('Comfy.ColorPalette')) + .toBe('light') // Close menu to see theme change await topbar.closeTopbarMenu() @@ -228,20 +228,22 @@ test.describe('Menu', { tag: '@ui' }, () => { await topbar.switchTheme('dark') // Verify menu stays open and Dark theme shows as active - await expect(menu).toBeVisible() - await expect(themeItems2.submenu).toBeVisible() - - // Check that Dark theme is active and Light theme is not - expect(await topbar.isMenuItemActive(themeItems2.darkTheme)).toBe(true) - expect(await topbar.isMenuItemActive(themeItems2.lightTheme)).toBe(false) + await expect(async () => { + await expect(menu).toBeVisible() + await expect(themeItems2.submenu).toBeVisible() + expect(await topbar.isMenuItemActive(themeItems2.darkTheme)).toBe(true) + expect(await topbar.isMenuItemActive(themeItems2.lightTheme)).toBe( + false + ) + }).toPass({ timeout: 5000 }) // Screenshot with dark theme active await comfyPage.attachScreenshot('theme-menu-dark-active') // Verify ColorPalette setting is set to "dark" - expect(await comfyPage.settings.getSetting('Comfy.ColorPalette')).toBe( - 'dark' - ) + await expect + .poll(() => comfyPage.settings.getSetting('Comfy.ColorPalette')) + .toBe('dark') // Close menu await topbar.closeTopbarMenu() diff --git a/browser_tests/tests/nodeSearchBox.spec.ts-snapshots/auto-linked-node-chromium-linux.png b/browser_tests/tests/nodeSearchBox.spec.ts-snapshots/auto-linked-node-chromium-linux.png index 126b2613c..fbcbdb1fc 100644 Binary files a/browser_tests/tests/nodeSearchBox.spec.ts-snapshots/auto-linked-node-chromium-linux.png and b/browser_tests/tests/nodeSearchBox.spec.ts-snapshots/auto-linked-node-chromium-linux.png differ diff --git a/browser_tests/tests/nodeSearchBox.spec.ts-snapshots/link-context-menu-search-chromium-linux.png b/browser_tests/tests/nodeSearchBox.spec.ts-snapshots/link-context-menu-search-chromium-linux.png index 126b2613c..fbcbdb1fc 100644 Binary files a/browser_tests/tests/nodeSearchBox.spec.ts-snapshots/link-context-menu-search-chromium-linux.png and b/browser_tests/tests/nodeSearchBox.spec.ts-snapshots/link-context-menu-search-chromium-linux.png differ diff --git a/browser_tests/tests/sidebar/workflows.spec.ts b/browser_tests/tests/sidebar/workflows.spec.ts index 572c58f7d..f2cf6b4cf 100644 --- a/browser_tests/tests/sidebar/workflows.spec.ts +++ b/browser_tests/tests/sidebar/workflows.spec.ts @@ -83,17 +83,15 @@ test.describe('Workflows sidebar', () => { const tab = comfyPage.menu.workflowsTab await tab.open() await comfyPage.command.executeCommand('Comfy.LoadDefaultWorkflow') - const originalNodeCount = (await comfyPage.nodeOps.getNodes()).length + const originalNodeCount = await comfyPage.nodeOps.getNodeCount() await tab.insertWorkflow(tab.getPersistedItem('workflow1.json')) await expect - .poll(() => comfyPage.nodeOps.getNodes().then((n) => n.length)) + .poll(() => comfyPage.nodeOps.getNodeCount()) .toEqual(originalNodeCount + 1) await tab.getPersistedItem('workflow1.json').click() - await expect - .poll(() => comfyPage.nodeOps.getNodes().then((n) => n.length)) - .toEqual(1) + await expect.poll(() => comfyPage.nodeOps.getNodeCount()).toEqual(1) }) test('Can rename nested workflow from opened workflow item', async ({ diff --git a/browser_tests/tests/subgraph.spec.ts b/browser_tests/tests/subgraph.spec.ts index 94f1fbad7..43fda40ee 100644 --- a/browser_tests/tests/subgraph.spec.ts +++ b/browser_tests/tests/subgraph.spec.ts @@ -53,7 +53,7 @@ test.describe('Subgraph Operations', { tag: ['@slow', '@subgraph'] }, () => { ): Promise { return await comfyPage.page.evaluate(() => { const graph = window.app!.canvas.graph - return graph?.constructor?.name === 'Subgraph' + return !!graph && 'inputNode' in graph }) } diff --git a/browser_tests/tests/subgraphPromotion.spec.ts b/browser_tests/tests/subgraphPromotion.spec.ts new file mode 100644 index 000000000..d5dd23390 --- /dev/null +++ b/browser_tests/tests/subgraphPromotion.spec.ts @@ -0,0 +1,622 @@ +import { expect } from '@playwright/test' + +import type { ComfyWorkflowJSON } from '@/platform/workflow/validation/schemas/workflowSchema' + +import type { ComfyPage } from '../fixtures/ComfyPage' +import { comfyPageFixture as test } from '../fixtures/ComfyPage' +import { TestIds } from '../fixtures/selectors' +import { fitToViewInstant } from '../helpers/fitToView' +import { + getPromotedWidgetNames, + getPromotedWidgetCount, + getPromotedWidgets +} from '../helpers/promotedWidgets' + +/** + * Check whether we're currently in a subgraph. + */ +async function isInSubgraph(comfyPage: ComfyPage): Promise { + return comfyPage.page.evaluate(() => { + const graph = window.app!.canvas.graph + return !!graph && 'inputNode' in graph + }) +} + +async function exitSubgraphViaBreadcrumb(comfyPage: ComfyPage): Promise { + const breadcrumb = comfyPage.page.getByTestId(TestIds.breadcrumb.subgraph) + await breadcrumb.waitFor({ state: 'visible', timeout: 5000 }) + + const parentLink = breadcrumb.getByRole('link').first() + await expect(parentLink).toBeVisible() + await parentLink.click() + await comfyPage.nextFrame() +} + +test.describe( + 'Subgraph Widget Promotion', + { tag: ['@subgraph', '@widget'] }, + () => { + test.describe('Auto-promotion on Convert to Subgraph', () => { + test('Recommended widgets are auto-promoted when creating a subgraph', async ({ + comfyPage + }) => { + await comfyPage.workflow.loadWorkflow('default') + + // Select just the KSampler node (id 3) which has a "seed" widget + const ksampler = await comfyPage.nodeOps.getNodeRefById('3') + await ksampler.click('title') + const subgraphNode = await ksampler.convertToSubgraph() + await comfyPage.nextFrame() + + // SubgraphNode should exist + expect(await subgraphNode.exists()).toBe(true) + + // The KSampler has a "seed" widget which is in the recommended list. + // The promotion store should have at least the seed widget promoted. + const nodeId = String(subgraphNode.id) + const promotedNames = await getPromotedWidgetNames(comfyPage, nodeId) + expect(promotedNames).toContain('seed') + + // SubgraphNode should have widgets (promoted views) + const widgetCount = await getPromotedWidgetCount(comfyPage, nodeId) + expect(widgetCount).toBeGreaterThan(0) + }) + + test('CLIPTextEncode text widget is auto-promoted', async ({ + comfyPage + }) => { + await comfyPage.workflow.loadWorkflow('default') + + // Select the positive CLIPTextEncode node (id 6) + const clipNode = await comfyPage.nodeOps.getNodeRefById('6') + await clipNode.click('title') + const subgraphNode = await clipNode.convertToSubgraph() + await comfyPage.nextFrame() + + const nodeId = String(subgraphNode.id) + const promotedNames = await getPromotedWidgetNames(comfyPage, nodeId) + expect(promotedNames.length).toBeGreaterThan(0) + + // CLIPTextEncode is in the recommendedNodes list, so its text widget + // should be promoted + expect(promotedNames).toContain('text') + }) + + test('SaveImage/PreviewImage nodes get pseudo-widget promoted', async ({ + comfyPage + }) => { + await comfyPage.workflow.loadWorkflow('default') + await fitToViewInstant(comfyPage) + + // Select the SaveImage node (id 9 in default workflow) + const saveNode = await comfyPage.nodeOps.getNodeRefById('9') + await saveNode.click('title') + const subgraphNode = await saveNode.convertToSubgraph() + await comfyPage.nextFrame() + + const promotedNames = await getPromotedWidgetNames( + comfyPage, + String(subgraphNode.id) + ) + + // SaveImage is in the recommendedNodes list, so filename_prefix is promoted + expect(promotedNames).toContain('filename_prefix') + }) + }) + + test.describe('Promoted Widget Visibility in LiteGraph Mode', () => { + test('Promoted text widget is visible on SubgraphNode', async ({ + comfyPage + }) => { + await comfyPage.workflow.loadWorkflow( + 'subgraphs/subgraph-with-promoted-text-widget' + ) + await comfyPage.nextFrame() + + // The subgraph node (id 11) should have a text widget promoted + const textarea = comfyPage.page.getByTestId( + TestIds.widgets.domWidgetTextarea + ) + await expect(textarea).toBeVisible() + await expect(textarea).toHaveCount(1) + }) + + test('Multiple promoted widgets all render on SubgraphNode', async ({ + comfyPage + }) => { + await comfyPage.workflow.loadWorkflow( + 'subgraphs/subgraph-with-multiple-promoted-widgets' + ) + await comfyPage.nextFrame() + + const textareas = comfyPage.page.getByTestId( + TestIds.widgets.domWidgetTextarea + ) + await expect(textareas.first()).toBeVisible() + const count = await textareas.count() + expect(count).toBeGreaterThan(1) + }) + }) + + test.describe('Promoted Widget Visibility in Vue Mode', () => { + test.beforeEach(async ({ comfyPage }) => { + await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true) + }) + + test('Promoted text widget renders on SubgraphNode in Vue mode', async ({ + comfyPage + }) => { + await comfyPage.workflow.loadWorkflow( + 'subgraphs/subgraph-with-promoted-text-widget' + ) + await comfyPage.vueNodes.waitForNodes() + + // SubgraphNode (id 11) should render with its body + const subgraphVueNode = comfyPage.vueNodes.getNodeLocator('11') + await expect(subgraphVueNode).toBeVisible() + + // It should have the Enter Subgraph button + const enterButton = subgraphVueNode.getByTestId('subgraph-enter-button') + await expect(enterButton).toBeVisible() + + // The promoted text widget should render inside the node + const nodeBody = subgraphVueNode.locator('[data-testid="node-body-11"]') + await expect(nodeBody).toBeVisible() + + // Widgets section should exist and have at least one widget + const widgets = nodeBody.locator('.lg-node-widgets > div') + await expect(widgets.first()).toBeVisible() + }) + + test('Enter Subgraph button navigates into subgraph in Vue mode', async ({ + comfyPage + }) => { + await comfyPage.workflow.loadWorkflow( + 'subgraphs/subgraph-with-promoted-text-widget' + ) + await comfyPage.vueNodes.waitForNodes() + + await comfyPage.vueNodes.enterSubgraph('11') + await comfyPage.nextFrame() + + expect(await isInSubgraph(comfyPage)).toBe(true) + }) + + test('Multiple promoted widgets render on SubgraphNode in Vue mode', async ({ + comfyPage + }) => { + await comfyPage.workflow.loadWorkflow( + 'subgraphs/subgraph-with-multiple-promoted-widgets' + ) + await comfyPage.vueNodes.waitForNodes() + + const subgraphVueNode = comfyPage.vueNodes.getNodeLocator('11') + await expect(subgraphVueNode).toBeVisible() + + const nodeBody = subgraphVueNode.locator('[data-testid="node-body-11"]') + const widgets = nodeBody.locator('.lg-node-widgets > div') + const count = await widgets.count() + expect(count).toBeGreaterThan(1) + }) + }) + + test.describe('Promoted Widget Reactivity', () => { + test('Value changes on promoted widget sync to interior widget', async ({ + comfyPage + }) => { + await comfyPage.workflow.loadWorkflow( + 'subgraphs/subgraph-with-promoted-text-widget' + ) + await comfyPage.nextFrame() + + const testContent = 'promoted-value-sync-test' + + // Type into the promoted textarea on the SubgraphNode + const textarea = comfyPage.page.getByTestId( + TestIds.widgets.domWidgetTextarea + ) + await textarea.fill(testContent) + await comfyPage.nextFrame() + + // Navigate into subgraph + const subgraphNode = await comfyPage.nodeOps.getNodeRefById('11') + await subgraphNode.navigateIntoSubgraph() + + // Interior CLIPTextEncode textarea should have the same value + const interiorTextarea = comfyPage.page.getByTestId( + TestIds.widgets.domWidgetTextarea + ) + await expect(interiorTextarea).toHaveValue(testContent) + }) + + test('Value changes on interior widget sync to promoted widget', async ({ + comfyPage + }) => { + await comfyPage.workflow.loadWorkflow( + 'subgraphs/subgraph-with-promoted-text-widget' + ) + await comfyPage.nextFrame() + + const testContent = 'interior-value-sync-test' + + // Navigate into subgraph + const subgraphNode = await comfyPage.nodeOps.getNodeRefById('11') + await subgraphNode.navigateIntoSubgraph() + + // Type into the interior CLIPTextEncode textarea + const interiorTextarea = comfyPage.page.getByTestId( + TestIds.widgets.domWidgetTextarea + ) + await interiorTextarea.fill(testContent) + await comfyPage.nextFrame() + + // Navigate back to parent graph + await exitSubgraphViaBreadcrumb(comfyPage) + + // Promoted textarea on SubgraphNode should have the same value + const promotedTextarea = comfyPage.page.getByTestId( + TestIds.widgets.domWidgetTextarea + ) + await expect(promotedTextarea).toHaveValue(testContent) + }) + + test('Value persists through repeated navigation', async ({ + comfyPage + }) => { + await comfyPage.workflow.loadWorkflow( + 'subgraphs/subgraph-with-promoted-text-widget' + ) + await comfyPage.nextFrame() + + const testContent = 'persistence-through-navigation' + + // Set value on promoted widget + const textarea = comfyPage.page.getByTestId( + TestIds.widgets.domWidgetTextarea + ) + await textarea.fill(testContent) + + // Navigate in and out multiple times + for (let i = 0; i < 3; i++) { + const subgraphNode = await comfyPage.nodeOps.getNodeRefById('11') + await subgraphNode.navigateIntoSubgraph() + const interiorTextarea = comfyPage.page.getByTestId( + TestIds.widgets.domWidgetTextarea + ) + await expect(interiorTextarea).toHaveValue(testContent) + + await exitSubgraphViaBreadcrumb(comfyPage) + + const promotedTextarea = comfyPage.page.getByTestId( + TestIds.widgets.domWidgetTextarea + ) + await expect(promotedTextarea).toHaveValue(testContent) + } + }) + }) + + test.describe('Manual Promote/Demote via Context Menu', () => { + test.beforeEach(async ({ comfyPage }) => { + await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top') + }) + + test('Can promote a widget from inside a subgraph', async ({ + comfyPage + }) => { + await comfyPage.workflow.loadWorkflow('subgraphs/basic-subgraph') + + const subgraphNode = await comfyPage.nodeOps.getNodeRefById('2') + await subgraphNode.navigateIntoSubgraph() + + // Get the KSampler node (id 1) inside the subgraph + const ksampler = await comfyPage.nodeOps.getNodeRefById('1') + + // Right-click on the KSampler's "steps" widget (index 2) to promote it + const stepsWidget = await ksampler.getWidget(2) + const widgetPos = await stepsWidget.getPosition() + await comfyPage.canvas.click({ + position: widgetPos, + button: 'right', + force: true + }) + await comfyPage.nextFrame() + + // Look for the Promote Widget menu entry + const promoteEntry = comfyPage.page + .locator('.litemenu-entry') + .filter({ hasText: /Promote Widget/ }) + + await expect(promoteEntry).toBeVisible() + await promoteEntry.click() + await comfyPage.nextFrame() + + // Navigate back to parent + await exitSubgraphViaBreadcrumb(comfyPage) + + // SubgraphNode should now have the promoted widget + const widgetCount = await getPromotedWidgetCount(comfyPage, '2') + expect(widgetCount).toBeGreaterThan(0) + }) + + test('Can un-promote a widget from inside a subgraph', async ({ + comfyPage + }) => { + await comfyPage.workflow.loadWorkflow('subgraphs/basic-subgraph') + + // First promote a canvas-rendered widget (KSampler "steps") + const subgraphNode = await comfyPage.nodeOps.getNodeRefById('2') + await subgraphNode.navigateIntoSubgraph() + + const ksampler = await comfyPage.nodeOps.getNodeRefById('1') + const stepsWidget = await ksampler.getWidget(2) + const widgetPos = await stepsWidget.getPosition() + + await comfyPage.canvas.click({ + position: widgetPos, + button: 'right', + force: true + }) + await comfyPage.nextFrame() + + const promoteEntry = comfyPage.page + .locator('.litemenu-entry') + .filter({ hasText: /Promote Widget/ }) + await expect(promoteEntry).toBeVisible() + await promoteEntry.click() + await comfyPage.nextFrame() + + // Navigate back and verify promotion took effect + await exitSubgraphViaBreadcrumb(comfyPage) + await fitToViewInstant(comfyPage) + await comfyPage.nextFrame() + + const initialWidgetCount = await getPromotedWidgetCount(comfyPage, '2') + expect(initialWidgetCount).toBeGreaterThan(0) + + // Navigate back in and un-promote + const subgraphNode2 = await comfyPage.nodeOps.getNodeRefById('2') + await subgraphNode2.navigateIntoSubgraph() + const stepsWidget2 = await ( + await comfyPage.nodeOps.getNodeRefById('1') + ).getWidget(2) + const widgetPos2 = await stepsWidget2.getPosition() + + await comfyPage.canvas.click({ + position: widgetPos2, + button: 'right', + force: true + }) + await comfyPage.nextFrame() + + const unpromoteEntry = comfyPage.page + .locator('.litemenu-entry') + .filter({ hasText: /Un-Promote Widget/ }) + + await expect(unpromoteEntry).toBeVisible() + await unpromoteEntry.click() + await comfyPage.nextFrame() + + // Navigate back to parent + await exitSubgraphViaBreadcrumb(comfyPage) + + // SubgraphNode should have fewer widgets + const finalWidgetCount = await getPromotedWidgetCount(comfyPage, '2') + expect(finalWidgetCount).toBeLessThan(initialWidgetCount) + }) + }) + + test.describe('Pseudo-Widget Promotion', () => { + test('Promotion store tracks pseudo-widget entries for subgraph with preview node', async ({ + comfyPage + }) => { + await comfyPage.workflow.loadWorkflow( + 'subgraphs/subgraph-with-preview-node' + ) + await comfyPage.nextFrame() + + // The SaveImage node is in the recommendedNodes list, so its + // filename_prefix widget should be auto-promoted + const promotedNames = await getPromotedWidgetNames(comfyPage, '5') + expect(promotedNames.length).toBeGreaterThan(0) + expect(promotedNames).toContain('filename_prefix') + }) + + test('Converting SaveImage to subgraph promotes its widgets', async ({ + comfyPage + }) => { + await comfyPage.workflow.loadWorkflow('default') + await fitToViewInstant(comfyPage) + + // Select SaveImage (id 9) + const saveNode = await comfyPage.nodeOps.getNodeRefById('9') + await saveNode.click('title') + const subgraphNode = await saveNode.convertToSubgraph() + await comfyPage.nextFrame() + + // SaveImage is a recommended node, so filename_prefix should be promoted + const nodeId = String(subgraphNode.id) + const promotedNames = await getPromotedWidgetNames(comfyPage, nodeId) + expect(promotedNames.length).toBeGreaterThan(0) + + const widgetCount = await getPromotedWidgetCount(comfyPage, nodeId) + expect(widgetCount).toBeGreaterThan(0) + }) + }) + + test.describe('Legacy And Round-Trip Coverage', () => { + test('Legacy -1 proxyWidgets entries are hydrated to concrete interior node IDs', async ({ + comfyPage + }) => { + await comfyPage.workflow.loadWorkflow( + 'subgraphs/subgraph-compressed-target-slot' + ) + await comfyPage.nextFrame() + + const promotedWidgets = await getPromotedWidgets(comfyPage, '2') + expect(promotedWidgets.length).toBeGreaterThan(0) + expect( + promotedWidgets.some(([interiorNodeId]) => interiorNodeId === '-1') + ).toBe(false) + expect( + promotedWidgets.some( + ([interiorNodeId, widgetName]) => + interiorNodeId !== '-1' && widgetName === 'batch_size' + ) + ).toBe(true) + }) + + test('Promoted widgets survive serialize -> loadGraphData round-trip', async ({ + comfyPage + }) => { + await comfyPage.workflow.loadWorkflow( + 'subgraphs/subgraph-with-promoted-text-widget' + ) + await comfyPage.nextFrame() + + const beforePromoted = await getPromotedWidgetNames(comfyPage, '11') + expect(beforePromoted).toContain('text') + + const serialized = await comfyPage.page.evaluate(() => { + return window.app!.graph!.serialize() + }) + + await comfyPage.page.evaluate((workflow: ComfyWorkflowJSON) => { + return window.app!.loadGraphData(workflow) + }, serialized as ComfyWorkflowJSON) + await comfyPage.nextFrame() + + const afterPromoted = await getPromotedWidgetNames(comfyPage, '11') + expect(afterPromoted).toContain('text') + + const widgetCount = await getPromotedWidgetCount(comfyPage, '11') + expect(widgetCount).toBeGreaterThan(0) + }) + + test('Cloning a subgraph node keeps promoted widget entries on original and clone', async ({ + comfyPage + }) => { + await comfyPage.workflow.loadWorkflow( + 'subgraphs/subgraph-with-promoted-text-widget' + ) + await comfyPage.nextFrame() + + const originalNode = await comfyPage.nodeOps.getNodeRefById('11') + const originalPos = await originalNode.getPosition() + + await comfyPage.page.mouse.move(originalPos.x + 16, originalPos.y + 16) + await comfyPage.page.keyboard.down('Alt') + await comfyPage.page.mouse.down() + await comfyPage.nextFrame() + await comfyPage.page.mouse.move(originalPos.x + 72, originalPos.y + 72) + await comfyPage.page.mouse.up() + await comfyPage.page.keyboard.up('Alt') + await comfyPage.nextFrame() + + const subgraphNodeIds = await comfyPage.page.evaluate(() => { + const graph = window.app!.canvas.graph! + return graph.nodes + .filter( + (n) => + typeof n.isSubgraphNode === 'function' && n.isSubgraphNode() + ) + .map((n) => String(n.id)) + }) + + expect(subgraphNodeIds.length).toBeGreaterThan(1) + for (const nodeId of subgraphNodeIds) { + const promotedWidgets = await getPromotedWidgets(comfyPage, nodeId) + expect(promotedWidgets.length).toBeGreaterThan(0) + expect( + promotedWidgets.some(([, widgetName]) => widgetName === 'text') + ).toBe(true) + } + }) + }) + + test.describe('Vue Mode - Promoted Preview Content', () => { + test.beforeEach(async ({ comfyPage }) => { + await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true) + }) + + test('SubgraphNode with preview node shows hasCustomContent area in Vue mode', async ({ + comfyPage + }) => { + await comfyPage.workflow.loadWorkflow( + 'subgraphs/subgraph-with-preview-node' + ) + await comfyPage.vueNodes.waitForNodes() + + const subgraphVueNode = comfyPage.vueNodes.getNodeLocator('5') + await expect(subgraphVueNode).toBeVisible() + + // The node body should exist + const nodeBody = subgraphVueNode.locator('[data-testid="node-body-5"]') + await expect(nodeBody).toBeVisible() + }) + }) + + test.describe('Promotion Cleanup', () => { + test('Removing subgraph node clears promotion store entries', async ({ + comfyPage + }) => { + await comfyPage.workflow.loadWorkflow( + 'subgraphs/subgraph-with-promoted-text-widget' + ) + await comfyPage.nextFrame() + + // Verify promotions exist + const namesBefore = await getPromotedWidgetNames(comfyPage, '11') + expect(namesBefore.length).toBeGreaterThan(0) + + // Delete the subgraph node + const subgraphNode = await comfyPage.nodeOps.getNodeRefById('11') + await subgraphNode.click('title') + await comfyPage.page.keyboard.press('Delete') + await comfyPage.nextFrame() + + // Node no longer exists, so promoted widgets should be gone + const nodeExists = await comfyPage.page.evaluate(() => { + return !!window.app!.canvas.graph!.getNodeById('11') + }) + expect(nodeExists).toBe(false) + }) + + test('Removing I/O slot removes associated promoted widget', async ({ + comfyPage + }) => { + await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top') + + await comfyPage.workflow.loadWorkflow( + 'subgraphs/subgraph-with-promoted-text-widget' + ) + + const initialWidgetCount = await getPromotedWidgetCount(comfyPage, '11') + expect(initialWidgetCount).toBeGreaterThan(0) + + // Navigate into subgraph + const subgraphNode = await comfyPage.nodeOps.getNodeRefById('11') + await subgraphNode.navigateIntoSubgraph() + + // Remove the text input slot + await comfyPage.subgraph.rightClickInputSlot('text') + await comfyPage.contextMenu.clickLitegraphMenuItem('Remove Slot') + await comfyPage.nextFrame() + + // Navigate back via breadcrumb + await comfyPage.page + .getByTestId(TestIds.breadcrumb.subgraph) + .waitFor({ state: 'visible', timeout: 5000 }) + const homeBreadcrumb = comfyPage.page.getByRole('link', { + name: 'subgraph-with-promoted-text-widget' + }) + await homeBreadcrumb.waitFor({ state: 'visible' }) + await homeBreadcrumb.click() + await comfyPage.nextFrame() + + // Widget count should be reduced + const finalWidgetCount = await getPromotedWidgetCount(comfyPage, '11') + expect(finalWidgetCount).toBeLessThan(initialWidgetCount) + }) + }) + } +) diff --git a/browser_tests/tests/vueNodes/interactions/node/imagePreview.spec.ts b/browser_tests/tests/vueNodes/interactions/node/imagePreview.spec.ts index 21ca4b985..4eeee0e7c 100644 --- a/browser_tests/tests/vueNodes/interactions/node/imagePreview.spec.ts +++ b/browser_tests/tests/vueNodes/interactions/node/imagePreview.spec.ts @@ -2,15 +2,20 @@ import { expect } from '@playwright/test' import type { ComfyPage } from '../../../../fixtures/ComfyPage' import { comfyPageFixture as test } from '../../../../fixtures/ComfyPage' +import { + getPromotedWidgetNames, + getPromotedWidgetCountByName +} from '../../../../helpers/promotedWidgets' test.describe('Vue Nodes Image Preview', () => { test.beforeEach(async ({ comfyPage }) => { await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true) - await comfyPage.workflow.loadWorkflow('widgets/load_image_widget') - await comfyPage.vueNodes.waitForNodes() }) async function loadImageOnNode(comfyPage: ComfyPage) { + await comfyPage.workflow.loadWorkflow('widgets/load_image_widget') + await comfyPage.vueNodes.waitForNodes() + const loadImageNode = ( await comfyPage.nodeOps.getNodeRefsByType('LoadImage') )[0] @@ -25,16 +30,19 @@ test.describe('Vue Nodes Image Preview', () => { await expect(imagePreview.locator('img')).toBeVisible() await expect(imagePreview).toContainText('x') - return imagePreview + return { + imagePreview, + nodeId: String(loadImageNode.id) + } } // TODO(#8143): Re-enable after image preview sync is working in CI test.fixme('opens mask editor from image preview button', async ({ comfyPage }) => { - const imagePreview = await loadImageOnNode(comfyPage) + const { imagePreview } = await loadImageOnNode(comfyPage) - await imagePreview.locator('[role="img"]').hover() + await imagePreview.locator('[role="img"]').focus() await comfyPage.page.getByLabel('Edit or mask image').click() await expect(comfyPage.page.locator('.mask-editor-dialog')).toBeVisible() @@ -42,9 +50,11 @@ test.describe('Vue Nodes Image Preview', () => { // TODO(#8143): Re-enable after image preview sync is working in CI test.fixme('shows image context menu options', async ({ comfyPage }) => { - await loadImageOnNode(comfyPage) + const { nodeId } = await loadImageOnNode(comfyPage) - const nodeHeader = comfyPage.vueNodes.getNodeByTitle('Load Image') + const nodeHeader = comfyPage.vueNodes + .getNodeLocator(nodeId) + .locator('.lg-node-header') await nodeHeader.click() await nodeHeader.click({ button: 'right' }) @@ -55,4 +65,69 @@ test.describe('Vue Nodes Image Preview', () => { await expect(contextMenu.getByText('Save Image')).toBeVisible() await expect(contextMenu.getByText('Open in Mask Editor')).toBeVisible() }) + + test( + 'renders promoted image previews for each subgraph node', + { tag: '@screenshot' }, + async ({ comfyPage }) => { + await comfyPage.workflow.loadWorkflow( + 'subgraphs/subgraph-with-multiple-promoted-previews' + ) + await comfyPage.vueNodes.waitForNodes() + + const firstSubgraphNode = comfyPage.vueNodes.getNodeLocator('7') + const secondSubgraphNode = comfyPage.vueNodes.getNodeLocator('8') + + await expect(firstSubgraphNode).toBeVisible() + await expect(secondSubgraphNode).toBeVisible() + + const firstPromotedWidgets = await getPromotedWidgetNames(comfyPage, '7') + const secondPromotedWidgets = await getPromotedWidgetNames(comfyPage, '8') + expect(firstPromotedWidgets).toEqual([ + '$$canvas-image-preview', + '$$canvas-image-preview' + ]) + expect(secondPromotedWidgets).toEqual(['$$canvas-image-preview']) + + expect( + await getPromotedWidgetCountByName( + comfyPage, + '7', + '$$canvas-image-preview' + ) + ).toBe(2) + expect( + await getPromotedWidgetCountByName( + comfyPage, + '8', + '$$canvas-image-preview' + ) + ).toBe(1) + + await expect( + firstSubgraphNode.locator('.lg-node-widgets') + ).not.toContainText('$$canvas-image-preview') + await expect( + secondSubgraphNode.locator('.lg-node-widgets') + ).not.toContainText('$$canvas-image-preview') + + await comfyPage.command.executeCommand('Comfy.Canvas.FitView') + await comfyPage.command.executeCommand('Comfy.QueuePrompt') + + const firstPreviewImages = firstSubgraphNode.locator('.image-preview img') + const secondPreviewImages = + secondSubgraphNode.locator('.image-preview img') + + await expect(firstPreviewImages).toHaveCount(2, { timeout: 30_000 }) + await expect(secondPreviewImages).toHaveCount(1, { timeout: 30_000 }) + + await expect(firstPreviewImages.first()).toBeVisible({ timeout: 30_000 }) + await expect(firstPreviewImages.nth(1)).toBeVisible({ timeout: 30_000 }) + await expect(secondPreviewImages.first()).toBeVisible({ timeout: 30_000 }) + + await expect(comfyPage.canvas).toHaveScreenshot( + 'vue-node-multiple-promoted-previews.png' + ) + } + ) }) diff --git a/browser_tests/tests/vueNodes/interactions/node/imagePreview.spec.ts-snapshots/vue-node-multiple-promoted-previews-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/node/imagePreview.spec.ts-snapshots/vue-node-multiple-promoted-previews-chromium-linux.png new file mode 100644 index 000000000..ee093ff42 Binary files /dev/null and b/browser_tests/tests/vueNodes/interactions/node/imagePreview.spec.ts-snapshots/vue-node-multiple-promoted-previews-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts b/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts index c7186e2d6..077e56a4a 100644 --- a/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts +++ b/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts @@ -9,17 +9,20 @@ test.describe('Vue Upload Widgets', () => { await comfyPage.vueNodes.waitForNodes() }) - test( - 'should hide canvas-only upload buttons', - { tag: '@screenshot' }, - async ({ comfyPage }) => { - await comfyPage.setup() - await comfyPage.workflow.loadWorkflow('widgets/all_load_widgets') - await comfyPage.vueNodes.waitForNodes() + test('should hide canvas-only upload buttons', async ({ comfyPage }) => { + await comfyPage.setup() + await comfyPage.workflow.loadWorkflow('widgets/all_load_widgets') + await comfyPage.vueNodes.waitForNodes() - await expect(comfyPage.canvas).toHaveScreenshot( - 'vue-nodes-upload-widgets.png' - ) - } - ) + await expect( + comfyPage.page.getByText('choose file to upload', { exact: true }) + ).not.toBeVisible() + + await expect + .poll(() => comfyPage.page.getByText('Error loading image').count()) + .toBeGreaterThan(0) + await expect + .poll(() => comfyPage.page.getByText('Error loading video').count()) + .toBeGreaterThan(0) + }) }) diff --git a/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png b/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png index 1f89adc80..36608e1d8 100644 Binary files a/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png and b/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png differ diff --git a/docs/guidance/typescript.md b/docs/guidance/typescript.md index 17b2f3989..56b718a54 100644 --- a/docs/guidance/typescript.md +++ b/docs/guidance/typescript.md @@ -36,6 +36,25 @@ When you must handle uncertain types, prefer these approaches in order: - Don't expose internal implementation types (e.g., Pinia store internals) - Reactive refs (`ComputedRef`) 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) diff --git a/lint-staged.config.ts b/lint-staged.config.ts index c239881f8..dc4561066 100644 --- a/lint-staged.config.ts +++ b/lint-staged.config.ts @@ -6,10 +6,19 @@ export default { './**/*.js': (stagedFiles: string[]) => formatAndEslint(stagedFiles), - './**/*.{ts,tsx,vue,mts}': (stagedFiles: string[]) => [ - ...formatAndEslint(stagedFiles), - 'pnpm typecheck' - ] + './**/*.{ts,tsx,vue,mts}': (stagedFiles: string[]) => { + const commands = [...formatAndEslint(stagedFiles), 'pnpm typecheck'] + + const hasBrowserTestsChanges = stagedFiles + .map((f) => path.relative(process.cwd(), f).replace(/\\/g, '/')) + .some((f) => f.startsWith('browser_tests/')) + + if (hasBrowserTestsChanges) { + commands.push('pnpm typecheck:browser') + } + + return commands + } } function formatAndEslint(fileNames: string[]) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3e1fcdc45..e0ced7565 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -843,11 +843,11 @@ importers: version: 1.2.79 '@iconify/tailwind4': specifier: 'catalog:' - version: 1.2.1(tailwindcss@4.1.12) + version: 1.2.1(tailwindcss@4.2.0) devDependencies: tailwindcss: specifier: 'catalog:' - version: 4.1.12 + version: 4.2.0 typescript: specifier: 'catalog:' version: 5.9.3 @@ -967,26 +967,14 @@ packages: resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.28.5': - resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} - engines: {node: '>=6.9.0'} - '@babel/compat-data@7.29.0': resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} engines: {node: '>=6.9.0'} - '@babel/core@7.28.5': - resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} - engines: {node: '>=6.9.0'} - '@babel/core@7.29.0': resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} engines: {node: '>=6.9.0'} - '@babel/generator@7.28.5': - resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} - engines: {node: '>=6.9.0'} - '@babel/generator@7.29.1': resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} engines: {node: '>=6.9.0'} @@ -995,20 +983,10 @@ packages: resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.27.2': - resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} - engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.28.6': resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} engines: {node: '>=6.9.0'} - '@babel/helper-create-class-features-plugin@7.28.5': - resolution: {integrity: sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - '@babel/helper-create-class-features-plugin@7.28.6': resolution: {integrity: sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==} engines: {node: '>=6.9.0'} @@ -1034,20 +1012,10 @@ packages: resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==} engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.27.1': - resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} - engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.28.6': resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.28.3': - resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - '@babel/helper-module-transforms@7.28.6': resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} engines: {node: '>=6.9.0'} @@ -1058,10 +1026,6 @@ packages: resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} engines: {node: '>=6.9.0'} - '@babel/helper-plugin-utils@7.27.1': - resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} - engines: {node: '>=6.9.0'} - '@babel/helper-plugin-utils@7.28.6': resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} engines: {node: '>=6.9.0'} @@ -1072,12 +1036,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-replace-supers@7.27.1': - resolution: {integrity: sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - '@babel/helper-replace-supers@7.28.6': resolution: {integrity: sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==} engines: {node: '>=6.9.0'} @@ -1104,10 +1062,6 @@ packages: resolution: {integrity: sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.28.4': - resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} - engines: {node: '>=6.9.0'} - '@babel/helpers@7.28.6': resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} engines: {node: '>=6.9.0'} @@ -1147,12 +1101,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/plugin-proposal-decorators@7.28.0': - resolution: {integrity: sha512-zOiZqvANjWDUaUS9xMxbMcK/Zccztbe/6ikvUXaG9nsPH3w6qh5UaPGAnirI/WhIbZ8m3OHU0ReyPrknG+ZKeg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-proposal-decorators@7.29.0': resolution: {integrity: sha512-CVBVv3VY/XRMxRYq5dwr2DS7/MvqPm23cOCjbwNnVrfOqcWlnefua1uUs0sjdKOGjvPUG633o07uWzJq4oI6dA==} engines: {node: '>=6.9.0'} @@ -1165,12 +1113,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-decorators@7.27.1': - resolution: {integrity: sha512-YMq8Z87Lhl8EGkmb0MwYkt36QnxC+fzCgrl66ereamPlYToRpIk5nUjKUY3QKLWq8mwUB1BgbeXcTJhZOCDg5A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-decorators@7.28.6': resolution: {integrity: sha512-71EYI0ONURHJBL4rSFXnITXqXrrY8q4P0q006DPfN+Rk+ASM+++IBXem/ruokgBZR8YNEWZ8R6B+rCb8VcUTqA==} engines: {node: '>=6.9.0'} @@ -1183,12 +1125,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-import-attributes@7.27.1': - resolution: {integrity: sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-import-attributes@7.28.6': resolution: {integrity: sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==} engines: {node: '>=6.9.0'} @@ -1200,24 +1136,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-jsx@7.27.1': - resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-jsx@7.28.6': resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-typescript@7.27.1': - resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-typescript@7.28.6': resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==} engines: {node: '>=6.9.0'} @@ -1518,12 +1442,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-typescript@7.28.5': - resolution: {integrity: sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-typescript@7.28.6': resolution: {integrity: sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==} engines: {node: '>=6.9.0'} @@ -1579,18 +1497,10 @@ packages: resolution: {integrity: sha512-1DViPYJpRU50irpGMfLBQ9B4kyfQuL6X7SS7pwTeWeZX0mNkjzPi0XFqxCjSdddZXUQy4AhnQnnesA/ZHnvAdw==} engines: {node: '>=6.9.0'} - '@babel/template@7.27.2': - resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} - engines: {node: '>=6.9.0'} - '@babel/template@7.28.6': resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.28.5': - resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} - engines: {node: '>=6.9.0'} - '@babel/traverse@7.29.0': resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} engines: {node: '>=6.9.0'} @@ -1666,15 +1576,9 @@ packages: '@dual-bundle/import-meta-resolve@4.2.1': resolution: {integrity: sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg==} - '@emnapi/core@1.7.1': - resolution: {integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==} - '@emnapi/core@1.8.1': resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==} - '@emnapi/runtime@1.7.1': - resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==} - '@emnapi/runtime@1.8.1': resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} @@ -1990,12 +1894,6 @@ packages: cpu: [x64] os: [win32] - '@eslint-community/eslint-utils@4.9.0': - resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - '@eslint-community/eslint-utils@4.9.1': resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2018,10 +1916,6 @@ packages: resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/eslintrc@3.3.1': - resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/eslintrc@3.3.3': resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3956,12 +3850,6 @@ packages: peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/tsconfig-utils@8.50.0': - resolution: {integrity: sha512-vxd3G/ybKTSlm31MOA96gqvrRGv9RJ7LGtZCn2Vrc5htA0zCDvcMqUkifcjrWNNKXHUU3WCkYOzzVSFBd0wa2w==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/tsconfig-utils@8.56.0': resolution: {integrity: sha512-bSJoIIt4o3lKXD3xmDh9chZcjCz5Lk8xS7Rxn+6l5/pKrDpkCwtQNQQwZ2qRPk7TkUYhrq3WPIHXOXlbXP0itg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3979,10 +3867,6 @@ packages: resolution: {integrity: sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/types@8.50.0': - resolution: {integrity: sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/types@8.56.0': resolution: {integrity: sha512-DBsLPs3GsWhX5HylbP9HNG15U0bnwut55Lx12bHB9MpXxQ+R5GC8MwQe+N1UFXxAeQDvEsEDY6ZYwX03K7Z6HQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -4228,36 +4112,24 @@ packages: '@vue/compiler-core@3.5.13': resolution: {integrity: sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==} - '@vue/compiler-core@3.5.25': - resolution: {integrity: sha512-vay5/oQJdsNHmliWoZfHPoVZZRmnSWhug0BYT34njkYTPqClh3DNWLkZNJBVSjsNMrg0CCrBfoKkjZQPM/QVUw==} - '@vue/compiler-core@3.5.28': resolution: {integrity: sha512-kviccYxTgoE8n6OCw96BNdYlBg2GOWfBuOW4Vqwrt7mSKWKwFVvI8egdTltqRgITGPsTFYtKYfxIG8ptX2PJHQ==} '@vue/compiler-dom@3.5.13': resolution: {integrity: sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==} - '@vue/compiler-dom@3.5.25': - resolution: {integrity: sha512-4We0OAcMZsKgYoGlMjzYvaoErltdFI2/25wqanuTu+S4gismOTRTBPi4IASOjxWdzIwrYSjnqONfKvuqkXzE2Q==} - '@vue/compiler-dom@3.5.28': resolution: {integrity: sha512-/1ZepxAb159jKR1btkefDP+J2xuWL5V3WtleRmxaT+K2Aqiek/Ab/+Ebrw2pPj0sdHO8ViAyyJWfhXXOP/+LQA==} '@vue/compiler-sfc@3.5.13': resolution: {integrity: sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==} - '@vue/compiler-sfc@3.5.25': - resolution: {integrity: sha512-PUgKp2rn8fFsI++lF2sO7gwO2d9Yj57Utr5yEsDf3GNaQcowCLKL7sf+LvVFvtJDXUp/03+dC6f2+LCv5aK1ag==} - '@vue/compiler-sfc@3.5.28': resolution: {integrity: sha512-6TnKMiNkd6u6VeVDhZn/07KhEZuBSn43Wd2No5zaP5s3xm8IqFTHBj84HJah4UepSUJTro5SoqqlOY22FKY96g==} '@vue/compiler-ssr@3.5.13': resolution: {integrity: sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==} - '@vue/compiler-ssr@3.5.25': - resolution: {integrity: sha512-ritPSKLBcParnsKYi+GNtbdbrIE1mtuFEJ4U1sWeuOMlIziK5GtOL85t5RhsNy4uWIXPgk+OUdpnXiTdzn8o3A==} - '@vue/compiler-ssr@3.5.28': resolution: {integrity: sha512-JCq//9w1qmC6UGLWJX7RXzrGpKkroubey/ZFqTpvEIDJEKGgntuDMqkuWiZvzTzTA5h2qZvFBFHY7fAAa9475g==} @@ -4323,9 +4195,6 @@ packages: '@vue/shared@3.5.13': resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==} - '@vue/shared@3.5.25': - resolution: {integrity: sha512-AbOPdQQnAnzs58H2FrrDxYj/TJfmeS2jdfEEhgiKINy+bnOANmVizIEgq1r+C5zsbs6l1CCQxtcj71rwNQ4jWg==} - '@vue/shared@3.5.28': resolution: {integrity: sha512-cfWa1fCGBxrvaHRhvV3Is0MgmrbSCxYTXCSCau2I0a1Xw1N1pHAvkWCiXPRAqjvToILvguNyEwjevUqAuBQWvQ==} @@ -4484,9 +4353,6 @@ packages: ajv: optional: true - ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - ajv@6.14.0: resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} @@ -4496,9 +4362,6 @@ packages: ajv@8.13.0: resolution: {integrity: sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==} - ajv@8.17.1: - resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} - ajv@8.18.0: resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} @@ -4522,10 +4385,6 @@ packages: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} - ansi-escapes@7.2.0: - resolution: {integrity: sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==} - engines: {node: '>=18'} - ansi-escapes@7.3.0: resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} engines: {node: '>=18'} @@ -5635,9 +5494,6 @@ packages: resolution: {integrity: sha512-dBR+30yHAqBGvOuxxQdnn2lTLHCO6r/9B+M4yF8mNrzr3u1yiF+YVJ6u3GTyPN/VRWqaE1FcscZDdBgVKmrmQQ==} engines: {node: '>=18.2.0'} - fast-uri@3.0.3: - resolution: {integrity: sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==} - fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} @@ -5803,10 +5659,6 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - get-east-asian-width@1.4.0: - resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} - engines: {node: '>=18'} - get-east-asian-width@1.5.0: resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} engines: {node: '>=18'} @@ -7326,9 +7178,6 @@ packages: prosemirror-keymap@1.2.3: resolution: {integrity: sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==} - prosemirror-markdown@1.13.1: - resolution: {integrity: sha512-Sl+oMfMtAjWtlcZoj/5L/Q39MpEnVZ840Xo330WJWUvgyhNmLBLN7MsHn07s53nG/KImevWHSE6fEj4q/GihHw==} - prosemirror-markdown@1.13.4: resolution: {integrity: sha512-D98dm4cQ3Hs6EmjK500TdAOew4Z03EV71ajEFiWra3Upr7diytJsjF4mPV2dW+eK5uNectiRj0xFxYI9NLXDbw==} @@ -7820,10 +7669,6 @@ packages: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} - string-width@8.1.0: - resolution: {integrity: sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==} - engines: {node: '>=20'} - string-width@8.2.0: resolution: {integrity: sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==} engines: {node: '>=20'} @@ -7951,9 +7796,6 @@ packages: peerDependencies: tailwindcss: '>=3.1.0' - tailwindcss@4.1.12: - resolution: {integrity: sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==} - tailwindcss@4.2.0: resolution: {integrity: sha512-yYzTZ4++b7fNYxFfpnberEEKu43w44aqDMNM9MHMmcKuCH7lL8jJ4yJ7LGHv7rSwiqM0nkiobF9I6cLlpS2P7Q==} @@ -8063,12 +7905,6 @@ packages: trough@2.2.0: resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} - ts-api-utils@2.1.0: - resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} - engines: {node: '>=18.12'} - peerDependencies: - typescript: '>=4.8.4' - ts-api-utils@2.4.0: resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} engines: {node: '>=18.12'} @@ -8661,18 +8497,6 @@ packages: resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - ws@8.18.3: - resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - ws@8.19.0: resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} engines: {node: '>=10.0.0'} @@ -8925,30 +8749,8 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.28.5': {} - '@babel/compat-data@7.29.0': {} - '@babel/core@7.28.5': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/generator': 7.28.5 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) - '@babel/helpers': 7.28.4 - '@babel/parser': 7.29.0 - '@babel/template': 7.27.2 - '@babel/traverse': 7.28.5 - '@babel/types': 7.29.0 - '@jridgewell/remapping': 2.3.5 - convert-source-map: 2.0.0 - debug: 4.4.3 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - '@babel/core@7.29.0': dependencies: '@babel/code-frame': 7.29.0 @@ -8969,14 +8771,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.28.5': - dependencies: - '@babel/parser': 7.29.0 - '@babel/types': 7.29.0 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - jsesc: 3.1.0 - '@babel/generator@7.29.1': dependencies: '@babel/parser': 7.29.0 @@ -8989,14 +8783,6 @@ snapshots: dependencies: '@babel/types': 7.29.0 - '@babel/helper-compilation-targets@7.27.2': - dependencies: - '@babel/compat-data': 7.28.5 - '@babel/helper-validator-option': 7.27.1 - browserslist: 4.28.1 - lru-cache: 5.1.1 - semver: 6.3.1 - '@babel/helper-compilation-targets@7.28.6': dependencies: '@babel/compat-data': 7.29.0 @@ -9005,19 +8791,6 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.28.5(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 - '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-member-expression-to-functions': 7.28.5 - '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.5) - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/traverse': 7.28.5 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 @@ -9053,14 +8826,7 @@ snapshots: '@babel/helper-member-expression-to-functions@7.28.5': dependencies: - '@babel/traverse': 7.28.5 - '@babel/types': 7.29.0 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-imports@7.27.1': - dependencies: - '@babel/traverse': 7.28.5 + '@babel/traverse': 7.29.0 '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color @@ -9072,15 +8838,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 - '@babel/helper-module-imports': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.28.5 - transitivePeerDependencies: - - supports-color - '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 @@ -9094,8 +8851,6 @@ snapshots: dependencies: '@babel/types': 7.29.0 - '@babel/helper-plugin-utils@7.27.1': {} - '@babel/helper-plugin-utils@7.28.6': {} '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.29.0)': @@ -9107,15 +8862,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-replace-supers@7.27.1(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 - '@babel/helper-member-expression-to-functions': 7.28.5 - '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/traverse': 7.28.5 - transitivePeerDependencies: - - supports-color - '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 @@ -9127,7 +8873,7 @@ snapshots: '@babel/helper-skip-transparent-expression-wrappers@7.27.1': dependencies: - '@babel/traverse': 7.28.5 + '@babel/traverse': 7.29.0 '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color @@ -9146,11 +8892,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helpers@7.28.4': - dependencies: - '@babel/template': 7.27.2 - '@babel/types': 7.29.0 - '@babel/helpers@7.28.6': dependencies: '@babel/template': 7.28.6 @@ -9195,15 +8936,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-proposal-decorators@7.28.0(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-decorators': 7.27.1(@babel/core@7.28.5) - transitivePeerDependencies: - - supports-color - '@babel/plugin-proposal-decorators@7.29.0(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 @@ -9217,11 +8949,6 @@ snapshots: dependencies: '@babel/core': 7.29.0 - '@babel/plugin-syntax-decorators@7.27.1(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-decorators@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 @@ -9232,36 +8959,21 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.5)': + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 @@ -9595,17 +9307,6 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-typescript@7.28.5(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 - '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.5) - transitivePeerDependencies: - - supports-color - '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 @@ -9738,30 +9439,12 @@ snapshots: '@babel/standalone@7.28.5': {} - '@babel/template@7.27.2': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/parser': 7.29.0 - '@babel/types': 7.29.0 - '@babel/template@7.28.6': dependencies: '@babel/code-frame': 7.29.0 '@babel/parser': 7.29.0 '@babel/types': 7.29.0 - '@babel/traverse@7.28.5': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/generator': 7.28.5 - '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.29.0 - '@babel/template': 7.27.2 - '@babel/types': 7.29.0 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - '@babel/traverse@7.29.0': dependencies: '@babel/code-frame': 7.29.0 @@ -9834,22 +9517,11 @@ snapshots: '@dual-bundle/import-meta-resolve@4.2.1': {} - '@emnapi/core@1.7.1': - dependencies: - '@emnapi/wasi-threads': 1.1.0 - tslib: 2.8.1 - optional: true - '@emnapi/core@1.8.1': dependencies: '@emnapi/wasi-threads': 1.1.0 tslib: 2.8.1 - '@emnapi/runtime@1.7.1': - dependencies: - tslib: 2.8.1 - optional: true - '@emnapi/runtime@1.8.1': dependencies: tslib: 2.8.1 @@ -10013,11 +9685,6 @@ snapshots: '@esbuild/win32-x64@0.27.3': optional: true - '@eslint-community/eslint-utils@4.9.0(eslint@9.39.1(jiti@2.6.1))': - dependencies: - eslint: 9.39.1(jiti@2.6.1) - eslint-visitor-keys: 3.4.3 - '@eslint-community/eslint-utils@4.9.1(eslint@9.39.1(jiti@2.6.1))': dependencies: eslint: 9.39.1(jiti@2.6.1) @@ -10041,20 +9708,6 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/eslintrc@3.3.1': - dependencies: - ajv: 6.12.6 - debug: 4.4.3 - espree: 10.4.0 - globals: 14.0.0 - ignore: 5.3.2 - import-fresh: 3.3.1 - js-yaml: 4.1.1 - minimatch: 3.1.2 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - '@eslint/eslintrc@3.3.3': dependencies: ajv: 6.14.0 @@ -10450,12 +10103,12 @@ snapshots: '@iconify/types': 2.0.0 pathe: 1.1.2 - '@iconify/tailwind4@1.2.1(tailwindcss@4.1.12)': + '@iconify/tailwind4@1.2.1(tailwindcss@4.2.0)': dependencies: '@iconify/tools': 5.0.3 '@iconify/types': 2.0.0 '@iconify/utils': 3.1.0 - tailwindcss: 4.1.12 + tailwindcss: 4.2.0 '@iconify/tools@5.0.3': dependencies: @@ -10718,8 +10371,8 @@ snapshots: '@napi-rs/wasm-runtime@0.2.12': dependencies: - '@emnapi/core': 1.7.1 - '@emnapi/runtime': 1.7.1 + '@emnapi/core': 1.8.1 + '@emnapi/runtime': 1.8.1 '@tybys/wasm-util': 0.10.1 optional: true @@ -11458,7 +11111,7 @@ snapshots: '@sentry/bundler-plugin-core@4.6.0': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@sentry/babel-plugin-component-annotate': 4.6.0 '@sentry/cli': 2.57.0 dotenv: 16.6.1 @@ -12000,7 +11653,7 @@ snapshots: '@types/node-fetch@2.6.13': dependencies: - '@types/node': 24.10.4 + '@types/node': 25.0.3 form-data: 4.0.5 '@types/node@18.19.130': @@ -12062,7 +11715,7 @@ snapshots: eslint: 9.39.1(jiti@2.6.1) ignore: 7.0.5 natural-compare: 1.4.0 - ts-api-utils: 2.1.0(typescript@5.9.3) + ts-api-utils: 2.4.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -12081,8 +11734,8 @@ snapshots: '@typescript-eslint/project-service@8.49.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.50.0(typescript@5.9.3) - '@typescript-eslint/types': 8.50.0 + '@typescript-eslint/tsconfig-utils': 8.56.0(typescript@5.9.3) + '@typescript-eslint/types': 8.56.0 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: @@ -12111,10 +11764,6 @@ snapshots: dependencies: typescript: 5.9.3 - '@typescript-eslint/tsconfig-utils@8.50.0(typescript@5.9.3)': - dependencies: - typescript: 5.9.3 - '@typescript-eslint/tsconfig-utils@8.56.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 @@ -12126,15 +11775,13 @@ snapshots: '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) debug: 4.4.3 eslint: 9.39.1(jiti@2.6.1) - ts-api-utils: 2.1.0(typescript@5.9.3) + ts-api-utils: 2.4.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color '@typescript-eslint/types@8.49.0': {} - '@typescript-eslint/types@8.50.0': {} - '@typescript-eslint/types@8.56.0': {} '@typescript-eslint/typescript-estree@8.49.0(typescript@5.9.3)': @@ -12147,7 +11794,7 @@ snapshots: minimatch: 9.0.5 semver: 7.7.4 tinyglobby: 0.2.15 - ts-api-utils: 2.1.0(typescript@5.9.3) + ts-api-utils: 2.4.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -12169,7 +11816,7 @@ snapshots: '@typescript-eslint/utils@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.1(jiti@2.6.1)) '@typescript-eslint/scope-manager': 8.49.0 '@typescript-eslint/types': 8.49.0 '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) @@ -12389,30 +12036,30 @@ snapshots: '@vue/babel-helper-vue-transform-on@1.4.0': {} - '@vue/babel-plugin-jsx@1.4.0(@babel/core@7.28.5)': + '@vue/babel-plugin-jsx@1.4.0(@babel/core@7.29.0)': dependencies: - '@babel/helper-module-imports': 7.27.1 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.5) - '@babel/template': 7.27.2 - '@babel/traverse': 7.28.5 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 '@babel/types': 7.29.0 '@vue/babel-helper-vue-transform-on': 1.4.0 - '@vue/babel-plugin-resolve-type': 1.4.0(@babel/core@7.28.5) + '@vue/babel-plugin-resolve-type': 1.4.0(@babel/core@7.29.0) '@vue/shared': 3.5.28 optionalDependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 transitivePeerDependencies: - supports-color - '@vue/babel-plugin-resolve-type@1.4.0(@babel/core@7.28.5)': + '@vue/babel-plugin-resolve-type@1.4.0(@babel/core@7.29.0)': dependencies: '@babel/code-frame': 7.29.0 - '@babel/core': 7.28.5 - '@babel/helper-module-imports': 7.27.1 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 '@babel/parser': 7.29.0 - '@vue/compiler-sfc': 3.5.25 + '@vue/compiler-sfc': 3.5.28 transitivePeerDependencies: - supports-color @@ -12424,14 +12071,6 @@ snapshots: estree-walker: 2.0.2 source-map-js: 1.2.1 - '@vue/compiler-core@3.5.25': - dependencies: - '@babel/parser': 7.29.0 - '@vue/shared': 3.5.25 - entities: 4.5.0 - estree-walker: 2.0.2 - source-map-js: 1.2.1 - '@vue/compiler-core@3.5.28': dependencies: '@babel/parser': 7.29.0 @@ -12445,11 +12084,6 @@ snapshots: '@vue/compiler-core': 3.5.13 '@vue/shared': 3.5.13 - '@vue/compiler-dom@3.5.25': - dependencies: - '@vue/compiler-core': 3.5.25 - '@vue/shared': 3.5.25 - '@vue/compiler-dom@3.5.28': dependencies: '@vue/compiler-core': 3.5.28 @@ -12467,18 +12101,6 @@ snapshots: postcss: 8.5.6 source-map-js: 1.2.1 - '@vue/compiler-sfc@3.5.25': - dependencies: - '@babel/parser': 7.29.0 - '@vue/compiler-core': 3.5.25 - '@vue/compiler-dom': 3.5.25 - '@vue/compiler-ssr': 3.5.25 - '@vue/shared': 3.5.25 - estree-walker: 2.0.2 - magic-string: 0.30.21 - postcss: 8.5.6 - source-map-js: 1.2.1 - '@vue/compiler-sfc@3.5.28': dependencies: '@babel/parser': 7.29.0 @@ -12496,11 +12118,6 @@ snapshots: '@vue/compiler-dom': 3.5.13 '@vue/shared': 3.5.13 - '@vue/compiler-ssr@3.5.25': - dependencies: - '@vue/compiler-dom': 3.5.25 - '@vue/shared': 3.5.25 - '@vue/compiler-ssr@3.5.28': dependencies: '@vue/compiler-dom': 3.5.28 @@ -12629,8 +12246,6 @@ snapshots: '@vue/shared@3.5.13': {} - '@vue/shared@3.5.25': {} - '@vue/shared@3.5.28': {} '@vue/test-utils@2.4.6': @@ -12744,13 +12359,6 @@ snapshots: optionalDependencies: ajv: 8.18.0 - ajv@6.12.6: - dependencies: - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.4.1 - uri-js: 4.4.1 - ajv@6.14.0: dependencies: fast-deep-equal: 3.1.3 @@ -12772,13 +12380,6 @@ snapshots: require-from-string: 2.0.2 uri-js: 4.4.1 - ajv@8.17.1: - dependencies: - fast-deep-equal: 3.1.3 - fast-uri: 3.0.3 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - ajv@8.18.0: dependencies: fast-deep-equal: 3.1.3 @@ -12814,10 +12415,6 @@ snapshots: ansi-colors@4.1.3: {} - ansi-escapes@7.2.0: - dependencies: - environment: 1.1.0 - ansi-escapes@7.3.0: dependencies: environment: 1.1.0 @@ -13219,7 +12816,7 @@ snapshots: cli-truncate@5.1.1: dependencies: slice-ansi: 7.1.2 - string-width: 8.1.0 + string-width: 8.2.0 cliui@8.0.1: dependencies: @@ -13862,7 +13459,7 @@ snapshots: eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.56.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1)): dependencies: - '@typescript-eslint/types': 8.50.0 + '@typescript-eslint/types': 8.56.0 comment-parser: 1.4.1 debug: 4.4.3 eslint: 9.39.1(jiti@2.6.1) @@ -13929,7 +13526,7 @@ snapshots: eslint-plugin-vue@10.6.2(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@9.39.1(jiti@2.6.1))): dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.1(jiti@2.6.1)) eslint: 9.39.1(jiti@2.6.1) natural-compare: 1.4.0 nth-check: 2.1.1 @@ -13960,19 +13557,19 @@ snapshots: eslint@9.39.1(jiti@2.6.1): dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.1(jiti@2.6.1)) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.21.1 '@eslint/config-helpers': 0.4.2 '@eslint/core': 0.17.0 - '@eslint/eslintrc': 3.3.1 + '@eslint/eslintrc': 3.3.3 '@eslint/js': 9.39.1 '@eslint/plugin-kit': 0.4.1 '@humanfs/node': 0.16.7 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 '@types/estree': 1.0.8 - ajv: 6.12.6 + ajv: 6.14.0 chalk: 4.1.2 cross-spawn: 7.0.6 debug: 4.4.3 @@ -14104,8 +13701,6 @@ snapshots: '@babel/runtime': 7.28.6 tslib: 2.8.1 - fast-uri@3.0.3: {} - fast-uri@3.1.0: {} fastest-levenshtein@1.0.16: {} @@ -14292,8 +13887,6 @@ snapshots: get-caller-file@2.0.5: {} - get-east-asian-width@1.4.0: {} - get-east-asian-width@1.5.0: {} get-intrinsic@1.3.0: @@ -14652,7 +14245,7 @@ snapshots: is-fullwidth-code-point@5.1.0: dependencies: - get-east-asian-width: 1.4.0 + get-east-asian-width: 1.5.0 is-generator-function@1.1.2: dependencies: @@ -14871,7 +14464,7 @@ snapshots: webidl-conversions: 8.0.0 whatwg-mimetype: 4.0.0 whatwg-url: 15.1.0 - ws: 8.18.3 + ws: 8.19.0 xml-name-validator: 5.0.0 transitivePeerDependencies: - '@exodus/crypto' @@ -15108,7 +14701,7 @@ snapshots: log-update@6.1.0: dependencies: - ansi-escapes: 7.2.0 + ansi-escapes: 7.3.0 cli-cursor: 5.0.0 slice-ansi: 7.1.2 strip-ansi: 7.1.2 @@ -16137,12 +15730,6 @@ snapshots: prosemirror-state: 1.4.4 w3c-keyname: 2.2.8 - prosemirror-markdown@1.13.1: - dependencies: - '@types/markdown-it': 14.1.2 - markdown-it: 14.1.1 - prosemirror-model: 1.25.4 - prosemirror-markdown@1.13.4: dependencies: '@types/markdown-it': 14.1.2 @@ -16817,12 +16404,7 @@ snapshots: string-width@7.2.0: dependencies: emoji-regex: 10.4.0 - get-east-asian-width: 1.4.0 - strip-ansi: 7.1.2 - - string-width@8.1.0: - dependencies: - get-east-asian-width: 1.4.0 + get-east-asian-width: 1.5.0 strip-ansi: 7.1.2 string-width@8.2.0: @@ -16991,7 +16573,7 @@ snapshots: table@6.9.0: dependencies: - ajv: 8.17.1 + ajv: 8.18.0 lodash.truncate: 4.4.2 slice-ansi: 4.0.0 string-width: 4.2.3 @@ -17005,8 +16587,6 @@ snapshots: dependencies: tailwindcss: 4.2.0 - tailwindcss@4.1.12: {} - tailwindcss@4.2.0: {} tapable@2.3.0: {} @@ -17061,7 +16641,7 @@ snapshots: '@types/markdown-it': 13.0.9 markdown-it: 14.1.1 markdown-it-task-lists: 2.1.1 - prosemirror-markdown: 1.13.1 + prosemirror-markdown: 1.13.4 tldts-core@7.0.19: {} @@ -17103,10 +16683,6 @@ snapshots: trough@2.2.0: {} - ts-api-utils@2.1.0(typescript@5.9.3): - dependencies: - typescript: 5.9.3 - ts-api-utils@2.4.0(typescript@5.9.3): dependencies: typescript: 5.9.3 @@ -17541,12 +17117,12 @@ snapshots: vite-plugin-vue-inspector@5.3.2(vite@8.0.0-beta.13(@types/node@24.10.4)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.39.2)(tsx@4.19.4)(yaml@2.8.2)): dependencies: - '@babel/core': 7.28.5 - '@babel/plugin-proposal-decorators': 7.28.0(@babel/core@7.28.5) - '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.5) - '@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.28.5) - '@vue/babel-plugin-jsx': 1.4.0(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@babel/plugin-proposal-decorators': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0) + '@vue/babel-plugin-jsx': 1.4.0(@babel/core@7.29.0) '@vue/compiler-dom': 3.5.28 kolorist: 1.8.0 magic-string: 0.30.21 @@ -17556,12 +17132,12 @@ snapshots: vite-plugin-vue-inspector@5.3.2(vite@8.0.0-beta.13(@types/node@25.0.3)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.39.2)(tsx@4.19.4)(yaml@2.8.2)): dependencies: - '@babel/core': 7.28.5 - '@babel/plugin-proposal-decorators': 7.28.0(@babel/core@7.28.5) - '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.5) - '@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.28.5) - '@vue/babel-plugin-jsx': 1.4.0(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@babel/plugin-proposal-decorators': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0) + '@vue/babel-plugin-jsx': 1.4.0(@babel/core@7.29.0) '@vue/compiler-dom': 3.5.28 kolorist: 1.8.0 magic-string: 0.30.21 @@ -17891,8 +17467,6 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 4.1.0 - ws@8.18.3: {} - ws@8.19.0: {} wsl-utils@0.1.0: diff --git a/src/components/breadcrumb/SubgraphBreadcrumb.vue b/src/components/breadcrumb/SubgraphBreadcrumb.vue index 9bcd24da0..a3abaa4eb 100644 --- a/src/components/breadcrumb/SubgraphBreadcrumb.vue +++ b/src/components/breadcrumb/SubgraphBreadcrumb.vue @@ -1,6 +1,7 @@