feat(fase-17): /settings About section opens ChangelogModal

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 <noreply@anthropic.com>
This commit is contained in:
2026-05-19 01:20:00 +02:00
parent d11cf6ccef
commit f549dc53a2
2 changed files with 64 additions and 1 deletions

View File

@@ -10,6 +10,7 @@
import ThemeToggle from '$lib/components/ThemeToggle.svelte'; import ThemeToggle from '$lib/components/ThemeToggle.svelte';
import SyncConflictsPanel from '$lib/components/SyncConflictsPanel.svelte'; import SyncConflictsPanel from '$lib/components/SyncConflictsPanel.svelte';
import TagChip from '$lib/components/TagChip.svelte'; import TagChip from '$lib/components/TagChip.svelte';
import ChangelogModal from '$lib/components/ChangelogModal.svelte';
import { import {
tags as tagsStore, tags as tagsStore,
loadTags, loadTags,
@@ -41,6 +42,9 @@
let saving = $state(false); let saving = $state(false);
let saved = $state(false); let saved = $state(false);
// Fase 17.2.4 — changelog modal trigger from the About section.
let showChangelog = $state(false);
let debounceTimer: ReturnType<typeof setTimeout>; let debounceTimer: ReturnType<typeof setTimeout>;
// Cropper // Cropper
@@ -609,7 +613,8 @@
</section> </section>
<!-- About (Fase 14.3.4) — version / commit / build-time chip. <!-- About (Fase 14.3.4) — version / commit / build-time chip.
Not clickable, just informational. --> Fase 17.2.4 adds a "Ver historial" trigger that opens the
ChangelogModal with the bundled CHANGELOG.md content. -->
<section class="pt-8" data-testid="settings-about"> <section class="pt-8" data-testid="settings-about">
<p class="mb-2 text-[13px] font-semibold uppercase tracking-[0.05em] text-text-secondary"> <p class="mb-2 text-[13px] font-semibold uppercase tracking-[0.05em] text-text-secondary">
{m.settings_about()} {m.settings_about()}
@@ -617,6 +622,14 @@
<p class="text-xs text-text-secondary" data-testid="settings-about-version"> <p class="text-xs text-text-secondary" data-testid="settings-about-version">
{m.settings_about_version({ version, commit, date: builtAt.slice(0, 10) })} {m.settings_about_version({ version, commit, date: builtAt.slice(0, 10) })}
</p> </p>
<button
type="button"
data-testid="settings-about-view-changelog"
onclick={() => (showChangelog = true)}
class="mt-1 text-xs text-text-secondary underline underline-offset-2 hover:text-slate-700 dark:hover:text-slate-300"
>
{m.settings_about_view_changelog()}
</button>
</section> </section>
<!-- Danger zone (Fase 10.5) --> <!-- Danger zone (Fase 10.5) -->
@@ -722,3 +735,7 @@
</div> </div>
</div> </div>
{/if} {/if}
<!-- Fase 17.2.4 — changelog modal. Rendered last so it stacks above
every other modal on the page (delete-account, leave-collective). -->
<ChangelogModal open={showChangelog} onClose={() => (showChangelog = false)} />

View File

@@ -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 <h1 class="...">Changelog</h1>
// inside the body — the beta release heading is an <h2> with the
// literal string "[v0.0.0-beta] — 2026-05-19 — MVP + MVP2 (Fases 016)".
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 });
});
});