From f549dc53a2f275f58c026da50476715cad949e65 Mon Sep 17 00:00:00 2001 From: Oier Bravo Urtasun Date: Tue, 19 May 2026 01:20:00 +0200 Subject: [PATCH] feat(fase-17): /settings About section opens ChangelogModal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a discreet "Ver historial" text-button under the existing version chip in `/settings` › About. The button toggles a local `showChangelog` $state that drives the ChangelogModal mounted at the bottom of the template, after every other modal so it stacks correctly. Playwright CL-01 verifies the trigger → modal → CHANGELOG header path, CL-02 verifies Escape closes the modal. Both pass against the bundled ?raw import (no fetch, exercises the production load path). Co-Authored-By: Claude Opus 4.7 --- .../src/routes/(app)/settings/+page.svelte | 19 +++++++- apps/web/tests/e2e/changelog.test.ts | 46 +++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 apps/web/tests/e2e/changelog.test.ts diff --git a/apps/web/src/routes/(app)/settings/+page.svelte b/apps/web/src/routes/(app)/settings/+page.svelte index edb5394..cc48546 100644 --- a/apps/web/src/routes/(app)/settings/+page.svelte +++ b/apps/web/src/routes/(app)/settings/+page.svelte @@ -10,6 +10,7 @@ import ThemeToggle from '$lib/components/ThemeToggle.svelte'; import SyncConflictsPanel from '$lib/components/SyncConflictsPanel.svelte'; import TagChip from '$lib/components/TagChip.svelte'; + import ChangelogModal from '$lib/components/ChangelogModal.svelte'; import { tags as tagsStore, loadTags, @@ -41,6 +42,9 @@ let saving = $state(false); let saved = $state(false); + + // Fase 17.2.4 — changelog modal trigger from the About section. + let showChangelog = $state(false); let debounceTimer: ReturnType; // Cropper @@ -609,7 +613,8 @@ + Fase 17.2.4 adds a "Ver historial" trigger that opens the + ChangelogModal with the bundled CHANGELOG.md content. -->

{m.settings_about()} @@ -617,6 +622,14 @@

{m.settings_about_version({ version, commit, date: builtAt.slice(0, 10) })}

+
@@ -722,3 +735,7 @@ {/if} + + + (showChangelog = false)} /> diff --git a/apps/web/tests/e2e/changelog.test.ts b/apps/web/tests/e2e/changelog.test.ts new file mode 100644 index 0000000..8d190af --- /dev/null +++ b/apps/web/tests/e2e/changelog.test.ts @@ -0,0 +1,46 @@ +/** + * CL-series — Fase 17.4.2 (changelog modal on /settings). + * + * CL-01: clicking "Ver historial" in the About section opens the modal + * and the [v0.0.0-beta] heading is visible. + * CL-02: Escape closes the modal. + * + * The CHANGELOG content is bundled at build time via ?raw, so these + * assertions exercise the production load path (no fetch, no SSR). + */ +import { test, expect } from '@playwright/test'; +import { USERS } from '../fixtures/users.js'; +import { loginAs } from '../fixtures/login.js'; + +test.describe('Changelog modal', () => { + test('CL-01: opens from /settings About section and shows beta heading', async ({ page }) => { + await loginAs(page, USERS.ana); + await page.goto('/settings'); + + const trigger = page.getByTestId('settings-about-view-changelog'); + await expect(trigger).toBeVisible({ timeout: 10_000 }); + await trigger.click(); + + const modal = page.getByTestId('changelog-modal'); + await expect(modal).toBeVisible({ timeout: 5_000 }); + + // The CHANGELOG header is parsed into an

Changelog

+ // inside the body — the beta release heading is an

with the + // literal string "[v0.0.0-beta] — 2026-05-19 — MVP + MVP2 (Fases 0–16)". + const body = page.getByTestId('changelog-modal-body'); + await expect(body.getByRole('heading', { level: 1, name: 'Changelog' })).toBeVisible(); + await expect(body.getByText(/v0\.0\.0-beta/)).toBeVisible(); + }); + + test('CL-02: Escape closes the modal', async ({ page }) => { + await loginAs(page, USERS.ana); + await page.goto('/settings'); + + await page.getByTestId('settings-about-view-changelog').click(); + const modal = page.getByTestId('changelog-modal'); + await expect(modal).toBeVisible({ timeout: 5_000 }); + + await page.keyboard.press('Escape'); + await expect(modal).toHaveCount(0, { timeout: 5_000 }); + }); +});