* feat(gallery): save gallery voices as profiles, with validated audio references Work-in-progress lifted from the concurrent gallery session at the owner's request (its uncommitted working tree, preserved verbatim from base 92b1ee5d; safety snapshot remains at rescue/gallery-wip): - gallery voices can be saved as local profiles: audio is copied into the profile store with content-addressed filenames, existing profiles are detected and refreshed only when the source clip changed - backend/core/audio_validation.py: symlink-rejecting, root-contained resolution for persisted profile WAV references, with tests - archetype/community routers and the Voice Gallery UI updated for the save-as-profile handoff (spec: docs/specs/longform/26-gallery-use-handoff.md) - locale updates for the new gallery strings across all 21 files Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: drop a stray local screenshot script that rode in with the tree copy * fix(community): explain the tolerated Content-Length parse failure; drop an unused import CodeQL on #1542: the empty except now says why it is safe (the streamed byte counter enforces the same cap regardless), and the test file loses an unused Path import. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(gallery): review findings — copy outside the write lock, no stale completions CodeRabbit on #1542, all findings addressed: - the profile-audio copy stages to a .part temp BEFORE BEGIN IMMEDIATE and publishes via atomic os.replace inside it — other backend writers no longer block for the duration of an audio copy; a mid-copy failure leaves no temp droppings and no profile row (both pinned by tests) - VoiceGallery async ops carry per-operation generation tokens: a preview or save-as-profile that resolves after unmount (or after a newer operation) can no longer play audio, redirect into a workspace, or touch state — three fail-before regression tests - VoiceGalleryActions imports the page at test runtime; the e2e locator uses a stable data-testid instead of a translated string; symlink tests skip cleanly where the OS can't create symlinks; the changelog line carries its PR ref Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ci: static ffmpeg fallback when the chocolatey feed is down Third feed outage to break a PR run (2026-07-20, 2026-07-28, today — three attempts, three 'installed 0/1'). Chocolatey is a distribution channel, not the dependency: after the retry loop exhausts, fetch the static gyan.dev build from its GitHub release mirror and put it on PATH — same binary, no feed in the path. URL verified live (HTTP 200). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
33 lines
1.2 KiB
TypeScript
33 lines
1.2 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
import { gotoMode } from './_helpers';
|
|
|
|
const MIN_WINDOW = { width: 900, height: 600 };
|
|
|
|
test.describe('Support page stays on one screen @ 900x600', () => {
|
|
test.use({ viewport: MIN_WINDOW });
|
|
|
|
test('support, commercial licence and contact panels do not overflow', async ({ page }) => {
|
|
await gotoMode(page, 'donate');
|
|
await expect(page.getByRole('heading', { name: 'Support VoiceStudio' })).toBeVisible({
|
|
timeout: 20_000,
|
|
});
|
|
|
|
for (const tabName of ['Support', 'Commercial License', 'Contact']) {
|
|
const tab = page.getByRole('tab', { name: tabName });
|
|
if ((await tab.getAttribute('aria-selected')) !== 'true') await tab.click({ force: true });
|
|
|
|
await expect(tab).toHaveAttribute('aria-selected', 'true');
|
|
const panel = page.getByRole('tabpanel');
|
|
await expect(panel).toBeVisible();
|
|
const { clientHeight, scrollHeight } = await panel.evaluate((element) => ({
|
|
clientHeight: element.clientHeight,
|
|
scrollHeight: element.scrollHeight,
|
|
}));
|
|
expect(
|
|
scrollHeight,
|
|
`${tabName} panel should not need vertical scrolling`,
|
|
).toBeLessThanOrEqual(clientHeight + 1);
|
|
}
|
|
});
|
|
});
|