diff --git a/README.md b/README.md index cc783519..d268ce91 100644 --- a/README.md +++ b/README.md @@ -12,10 +12,10 @@ Prompt Manager saves your prompts as you generate, links each image back to the ## What's new: rebuilt from the ground up -- **A new UI.** One app, the workspace, replaces the separate dashboard, gallery and metadata pages. The old pages still work if you want them. +- **A new UI.** One app, the workspace, replaces the separate dashboard, gallery and metadata pages. - **Classic and Nodes 2.0.** The nodes work in both ComfyUI renderers. CI starts a real ComfyUI and checks both. - **Tested end to end.** Unit tests, browser tests in Chromium, Firefox, WebKit and touch emulation, and tests against a real ComfyUI. -- **Mac, Linux and Windows.** The Python suite runs on all three, on Python 3.12, 3.13 and 3.14. +- **Linux and Windows.** The Python suite runs on both, on Python 3.12, 3.13 and 3.14. macOS should work but is not tested. ## See it @@ -212,9 +212,9 @@ Set `PROMPT_MANAGER_DATA_DIR` to an absolute folder to keep the library somewher - **Older images:** a one-time notice offers a metadata scan that links them to prompts and negatives. Scans don't count as new runs. - **Going back isn't supported.** Workflows are now stored once, and an older version shows new records without their workflow or prompt graph. Back up first in System > Backup & export. See [the old README's changelog](README-legacy.md#changelog) for the manual way back. -## The old pages still work +## The old pages will be removed -They stay at their URLs, and System's Overview section links to them: +They still work at their URLs for now and will be removed in a future release. Everything they do is moving to the workspace. | Page | URL | | --- | --- | diff --git a/TESTING.md b/TESTING.md index 13ce2348..1b127eda 100644 --- a/TESTING.md +++ b/TESTING.md @@ -77,7 +77,7 @@ PM_TEST_WORKERS=8 npm run --prefix tools/vendor test:coverage # whole npm run --prefix tools/vendor test:coverage:shard -- 1/4 # node units, viewer suite, workflows 1/4 npm run --prefix tools/vendor test:coverage:shard -- 2/4 # workflows 2/4 (likewise 3/4, 4/4) npm run --prefix tools/vendor coverage:merge # report and baseline check of every raw file -npm run --prefix tools/vendor test:workflows -- --project webkit --shard 2/2 +npm run --prefix tools/vendor test:workflows -- --project webkit --shard 2/3 ``` `coverage:merge` refuses an empty raw directory; it does not count shards, so run @@ -193,8 +193,8 @@ waiting for the `frontend coverage` jobs. The `viewerjs` check runs on every PR, including backend-only changes. It is an aggregate job. It requires the four `frontend coverage (shard N/4)` jobs and -`frontend coverage (merge)` to succeed on every event. The four -`page workflows (firefox|webkit, shard N/2)` jobs run only in the merge queue, +`frontend coverage (merge)` to succeed on every event. The six +`page workflows (firefox|webkit, shard N/3)` jobs run only in the merge queue, on `main` and on `workflow_dispatch` (see "Pull requests and the merge queue"). `viewerjs` requires them to be skipped on a pull request and to succeed everywhere else. Any other failed, cancelled or skipped job fails it. Every browser job runs with `PM_TEST_WORKERS: 3` (the public @@ -2735,7 +2735,7 @@ the grid) and D43 (sorted chips, checked against the concept with its chips sort D44 needs no mapping: the comparison runs with no Refine filter active, so the button reads "Filters" as the concept's does. Nothing else follows the grid: the legacy "Current prompt library" link is removed from the shelf; the legacy Library stays at -`/prompt_manager/admin` by direct URL, linked only from System (#525, D165) +`/prompt_manager/admin` by direct URL (#525, #620) (`tests/browser/workspace.spec.mjs`). `library comparison detects mutations` injects `.card-footer { padding: 2px }` and removes `.card-meta .chip.red`. @@ -3243,7 +3243,7 @@ npm run --prefix tools/vendor test:workflows -- history-concept.spec.mjs \ `#/inspect` ("Inspect a file") is a native workspace route; it no longer hands off to the legacy metadata page, which stays at its URL -(`/prompt_manager/gallery.html`) linked only from System (#525, D165). The page is the concept's `#metadata` +(`/prompt_manager/gallery.html`) at its URL only (#525, #620). The page is the concept's `#metadata` (app.js `renderMetadata` a740–748, selection a1715–1729; style.css 709–722, 1180–1194, 1543–1545, 1559–1564) in the concept's own classes (`.surface`, `.file-drop`, `.spaced-20`, `.local-file-preview`, all from the synced `concept.css`), with no @@ -7495,7 +7495,7 @@ Deploying this backend needs a ComfyUI restart (Python and route changes). `#/tags` ("Tags & categories", page title "Tags") is a native workspace route; it no longer hands off to the legacy tag manager. That page stays at -`/prompt_manager/admin#/tags` linked only from System (#525, D165); tag search and sort, the untagged +`/prompt_manager/admin#/tags` at its URL only (#525, #620); tag search and sort, the untagged entry (D149) and multi-tag OR (the Library's Tag match, D150) moved to the workspace (#444); bulk prompt delete stays on the legacy admin page (bulk tags: the Prompt library's selection bar, D151). A prompt's category is changed in the Prompt library @@ -7627,7 +7627,8 @@ The legacy pages (`/prompt_manager/admin`, `admin#/tags`, `gallery`, `gallery.ht their URLs, and admin's Metadata and Gallery buttons still open their legacy pages (maintainer ruling; pinned by `legacy-system-links.spec.mjs`). -- **D165** (beyond the concept, maintainer ruling 2026-10-30): System's Overview section ends +- **D165 retired** (#620): the old pages show their removal notice since 3.5.1, so System no longer links them and the load failure below offers a reload and ComfyUI. The original ruling: + **D165** (beyond the concept, maintainer ruling 2026-10-30): System's Overview section ends with one small line, "Legacy pages: Dashboard · Tags · Gallery · Metadata viewer" (`.legacy-pages`, new tabs), outside `.settings-layout`. The concept has no such line; `mapOverviewConcept` adds it to the page tree. diff --git a/tests/browser/system-concept.spec.mjs b/tests/browser/system-concept.spec.mjs index 33b453bd..82bf6d55 100644 --- a/tests/browser/system-concept.spec.mjs +++ b/tests/browser/system-concept.spec.mjs @@ -167,8 +167,6 @@ const IMAGES_COUNT_URL = '/prompt_manager/images/output?mode=workspace&limit=1'; /** The concept's shell and Overview with the recorded deviations applied, given the server's real overview. */ function mapOverviewConcept(reference, overview, images) { - // D165: the discreet legacy-pages line follows the settings layout. - reference = { ...reference, tree: [...reference.tree, 'p.legacy-pages.small'] }; const relabel = { 'Database integrity': 'Not checked', 'Positive prompts': number(overview.library.positive_prompts), 'Negative variants': number(overview.library.negative_variants), Images: number(images) }; const rows = { @@ -265,7 +263,7 @@ const UNMAPPED_PATHS = [ 'columns.surfaces.0.rows.length', 'columns.surfaces.0.tree.4', 'columns.surfaces.0.tree.length', 'columns.surfaces.1.rows.0.description', 'columns.surfaces.1.rows.1.title', 'columns.surfaces.1.rows.2.description', 'heading.actions.0', 'heading.actions.length', 'metrics.items.3.value', 'nav.label', - 'save.button.cursor', 'save.button.opacity', 'save.status.text', 'tree.3', 'tree.length', + 'save.button.cursor', 'save.button.opacity', 'save.status.text', ].sort(); test('system comparison detects mutations', async ({ page, request }) => { diff --git a/tests/browser/workspace-saved-views.spec.mjs b/tests/browser/workspace-saved-views.spec.mjs index 69258546..b784f437 100644 --- a/tests/browser/workspace-saved-views.spec.mjs +++ b/tests/browser/workspace-saved-views.spec.mjs @@ -205,7 +205,7 @@ test('a view stored under older rules still lists and opens with its usable valu return (await response.json()).id; }; const imagesId = await raw({ page: 'images', name: 'Old cats', state: JSON.stringify({ q: 'cat', sort: 'shuffle', layout: 'grid' }) }); - await raw({ page: 'library', name: '', state: JSON.stringify({ q: 'dog', sort: 'name-desc' }) }); + await raw({ page: 'library', name: '', state: JSON.stringify({ q: 'dog', sort: 'shuffle' }) }); expect(await listed(request)).toEqual([ { id: imagesId, page: 'images', name: 'Old cats', state: { ...IMAGE_DEFAULTS, q: 'cat' } }, expect.objectContaining({ page: 'library', name: 'Saved view', state: { q: 'dog', category: '', sort: 'newest', tags: [], tag_mode: 'all', folder: '' } }), diff --git a/tests/browser/workspace.spec.mjs b/tests/browser/workspace.spec.mjs index db06e7ec..901e62a1 100644 --- a/tests/browser/workspace.spec.mjs +++ b/tests/browser/workspace.spec.mjs @@ -135,34 +135,11 @@ test('/prompt_manager/web lands on the workspace Library and Back does not bounc await expect(main.getByRole('heading', { level: 1, name: 'Overview', exact: true })).toBeVisible(); }); -// D165 (#525, maintainer ruling): a discreet line under System is the workspace's only visible way to the legacy pages. -test('System Overview links the legacy pages discreetly, and each one loads (#525)', async ({ page, context }) => { +// #620: the old pages are on their way out (notice since 3.5.1), so the workspace no longer links them. +test('System no longer links the old pages (#620)', async ({ page }) => { await page.goto(`${workspace}#/system/overview`); - const line = page.locator('#workspace-main .legacy-pages'); - await expect(line).toBeVisible(); - await expect(line).toContainText('Legacy pages'); - const links = line.getByRole('link'); - await expect(links).toHaveCount(4); - for (const [name, url, ready] of [ - ['Dashboard', '/prompt_manager/admin', '#addPromptBtn'], - ['Tags', '/prompt_manager/admin#/tags', '#addPromptBtn'], - ['Gallery', '/prompt_manager/gallery', 'body'], - ['Metadata viewer', '/prompt_manager/gallery.html', 'body'], - ]) { - const opened = context.waitForEvent('page'); - await line.getByRole('link', { name, exact: true }).click(); - const tab = await opened; - await tab.waitForLoadState('domcontentloaded'); - const target = new URL(tab.url()); - expect(target.pathname + target.hash).toBe(url); - await expect(tab.locator(ready).first()).toBeAttached(); - await tab.close(); - } - // Only on System's Overview section, and gone with the page. - await page.locator('#workspace-main .section-nav').getByRole('button', { name: 'Appearance', exact: true }).click(); - await expect(line).toBeHidden(); - await page.goto(`${workspace}#/library`); - await expect(page.locator('#workspace-main .legacy-pages')).toHaveCount(0); + await expect(page.locator('#workspace-main h1')).toHaveText('System'); + await expect(page.locator('#workspace-main a[href*="/admin"], #workspace-main a[href*="/gallery"]')).toHaveCount(0); }); const bootFailure = page => page.locator('#workspace-boot-failure'); @@ -175,7 +152,7 @@ test('a healthy workspace never shows the load failure message (#525)', async ({ }); for (const [label, pattern] of [['the entry module', '**/js/workspace/index.mjs*'], ['a dependency', '**/js/workspace/state.mjs*']]) { - test(`when ${label} fails to load the page still offers the legacy pages (#525)`, async ({ page }) => { + test(`when ${label} fails to load the page offers a reload and ComfyUI (#525, #620)`, async ({ page }) => { const pageErrors = []; page.on('pageerror', error => pageErrors.push(error.message)); await page.route(pattern, route => route.abort()); @@ -183,19 +160,17 @@ for (const [label, pattern] of [['the entry module', '**/js/workspace/index.mjs* const failure = bootFailure(page); await expect(failure).toBeVisible(); await expect(failure).toHaveAttribute('role', 'alert'); - const dashboard = failure.getByRole('link', { name: 'Dashboard', exact: true }); - await expect(dashboard).toBeVisible(); - await expect(failure.getByRole('link')).toHaveCount(4); - await dashboard.click(); - await expect(page).toHaveURL(/\/prompt_manager\/admin$/); + await expect(failure.getByRole('link', { name: 'Reload the page', exact: true })).toHaveAttribute('href', 'workspace'); + await expect(failure.getByRole('link', { name: 'ComfyUI', exact: true })).toHaveAttribute('href', '../'); + await expect(failure.getByRole('link')).toHaveCount(2); expect(pageErrors).toEqual([]); }); } -test('when a module throws while starting the page still offers the legacy pages (#525)', async ({ page }) => { +test('when a module throws while starting the page still offers a reload (#525, #620)', async ({ page }) => { await page.route('**/js/workspace/state.mjs*', route => route.fulfill({ contentType: 'text/javascript', body: "throw new Error('boot failure');" })); await page.goto(`${workspace}#/library`); - await expect(bootFailure(page).getByRole('link', { name: 'Dashboard', exact: true })).toBeVisible(); + await expect(bootFailure(page).getByRole('link', { name: 'Reload the page', exact: true })).toBeVisible(); // The broken module's own error is this test's premise, not a failure of the page. page.workspaceErrors.length = 0; }); @@ -569,7 +544,7 @@ test('System is native: the concept shell, section navigation, sections and the // D92: no "Sample configuration" chip. await expect(main.locator('.heading .chip')).toHaveCount(0); expect(await main.evaluate(node => [...node.children].map(child => `${child.localName}.${child.className}`))) - .toEqual(['div.heading', 'div.section-nav', 'div.settings-layout', 'p.small legacy-pages']); + .toEqual(['div.heading', 'div.section-nav', 'div.settings-layout']); const nav = main.getByRole('group', { name: 'System sections', exact: true }); await expect(nav.getByRole('button')).toHaveText(SYSTEM_SECTION_LABELS); await expect(nav.getByRole('button', { name: 'Overview', exact: true })).toHaveAttribute('aria-pressed', 'true'); diff --git a/tests/js/workspace-state.test.js b/tests/js/workspace-state.test.js index c4711f12..e6f0c51f 100644 --- a/tests/js/workspace-state.test.js +++ b/tests/js/workspace-state.test.js @@ -93,13 +93,6 @@ test('every Overview card opens its own native page, never a legacy one (#525)', } }); -test('the legacy pages System links are the four that stay at their URLs (#525 D165)', async () => { - const { LEGACY_PAGES } = await source; - assert.deepEqual(LEGACY_PAGES.map(page => page.href), ['/prompt_manager/admin', '/prompt_manager/admin#/tags', - '/prompt_manager/gallery', '/prompt_manager/gallery.html']); - for (const page of LEGACY_PAGES) assert.ok(page.label); -}); - test('a job of the Activity page is its own deep link', async () => { const { resolveRoute, routeTitle, isHandoff } = await source; const id = '0123456789abcdef'.repeat(2); diff --git a/web/js/workspace/state.mjs b/web/js/workspace/state.mjs index 7fe28a6d..2405382e 100644 --- a/web/js/workspace/state.mjs +++ b/web/js/workspace/state.mjs @@ -25,14 +25,6 @@ export const routes = [ description: 'Configuration, maintenance and recovery—with browsing kept in the workspace.', action: 'Open System' }, ]; -/** D165 (#525): the legacy pages, which stay at their URLs; System's Overview section links them discreetly. */ -export const LEGACY_PAGES = Object.freeze([ - { label: 'Dashboard', href: '/prompt_manager/admin' }, - { label: 'Tags', href: '/prompt_manager/admin#/tags' }, - { label: 'Gallery', href: '/prompt_manager/gallery' }, - { label: 'Metadata viewer', href: '/prompt_manager/gallery.html' }, -].map(page => Object.freeze(page))); - const NATIVE_ROUTES = new Set(['images', 'library', 'tags', 'inspect', 'overview', 'saved', 'favorites', 'activity', 'review', 'system', 'collection']); diff --git a/web/js/workspace/system.mjs b/web/js/workspace/system.mjs index 1a5e8129..659c103f 100644 --- a/web/js/workspace/system.mjs +++ b/web/js/workspace/system.mjs @@ -12,7 +12,7 @@ * listeners and timers never outlive it. The drafts are module-level (Q10): they live for the page session. */ import { createButton, createScope } from '../shared/toolkit/core.js'; -import { LEGACY_PAGES, SYSTEM_SECTIONS, navigateTo } from './state.mjs'; +import { SYSTEM_SECTIONS, navigateTo } from './state.mjs'; import { createSystemDrafts, DRAFT_DOMAINS, INVALID_SETTING_CODE } from './system-state.mjs'; import { sectionNav } from './system-controls.mjs'; import { mountOverview } from './system-overview.mjs'; @@ -145,22 +145,6 @@ export const DELIVERED_SECTIONS = Object.freeze({ * under one of this tab's drafts flags that draft (D92's conflict status), including a change made while System was * not mounted (the drafts outlive it, Q10). */ -/** D165 (#525): one small line under System's Overview, the workspace's only visible way to the legacy pages. */ -function legacyPagesLine() { - const line = el('p', 'small legacy-pages'); - line.append('Legacy pages: '); - LEGACY_PAGES.forEach((page, index) => { - const link = document.createElement('a'); - link.textContent = page.label; - link.href = window.ApiClient.resolveUrl(page.href); - link.target = '_blank'; - link.rel = 'noopener'; - if (index) line.append(' · '); - line.append(link); - }); - return line; -} - export function mountSystem(main, api, scope, { section, activity, counts = null, sections = DELIVERED_SECTIONS, navigate = navigateTo }) { const store = systemDrafts(api); @@ -187,7 +171,6 @@ export function mountSystem(main, api, scope, { section, activity, counts = null saveRow.append(status, save); layout.append(saveRow); main.append(nav, layout); - const legacyPages = legacyPagesLine(); const paintStatus = () => { status.textContent = store.statusText(); @@ -213,8 +196,6 @@ export function mountSystem(main, api, scope, { section, activity, counts = null // Every section is delivered (S12), and the router only names known sections (state.mjs). const nodes = sections[next](layout, { api, activity, scope: sectionScope, drafts: store, counts }); layout.prepend(...nodes); - if (next === 'overview') main.append(legacyPages); - else legacyPages.remove(); paintStatus(); } show(section); diff --git a/web/js/workspace/workspace.css b/web/js/workspace/workspace.css index 11053668..b6ce41f5 100644 --- a/web/js/workspace/workspace.css +++ b/web/js/workspace/workspace.css @@ -575,7 +575,6 @@ pre.prompt-text { font: inherit; font-size: 12px; line-height: 1.75; margin-bloc } .more-menu-list[hidden] { display: none; } .more-menu-list > button { justify-content: flex-start; text-align: left; width: 100%; white-space: nowrap; } -.legacy-pages { margin: 16px 0 0; color: var(--muted); /* D165 */ } /* D164 (#524): the prompt page has four heading actions and titles of any length, which the concept never shows. From the concept's sidebar breakpoint up the actions keep one row and the title takes what is left, clamped to two lines diff --git a/web/workspace.html b/web/workspace.html index 5de9998c..e8a87f90 100644 --- a/web/workspace.html +++ b/web/workspace.html @@ -60,12 +60,8 @@
The workspace couldn't load. Reload the page, or use a legacy page: - Dashboard · - Tags · - Gallery · - Metadata viewer
+The workspace couldn't load. Reload the page, or go back to ComfyUI.