Compare commits

...

2 Commits

Author SHA1 Message Date
6999b3dd73 Document title follows the configured instance name (#323)
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 6m58s
CI / Build container images (pull_request) Successful in 1m26s
CI / Auth e2e pack (pull_request) Successful in 9m7s
CI / Import/export fidelity gate (pull_request) Successful in 1m2s
CD / Build and push images (push) Successful in 13s
CD / Deploy to Test (push) Successful in 14s
CI / Lint, typecheck, test (push) Successful in 7m31s
CI / Build container images (push) Has been skipped
CD / Smoke tests against Test (push) Successful in 1m24s
CD / Promote to Int (push) Successful in 13s
CI / Auth e2e pack (push) Successful in 9m25s
CI / Import/export fidelity gate (push) Successful in 55s
useDocumentTitle pinned APP_NAME = 'Dorfteich', so every route title —
tab, bookmarks, the window title a screen reader announces (WCAG 2.4.2)
— named the product instead of the operator's instance. The trailing
name now comes from the public branding query, exactly like the TopBar
brand (#306); until the query resolves (or when it cannot, e.g.
maintenance mode) the shipped default keeps the title stable, so an
untouched instance reads exactly as before. The static index.html title
stays the pre-JS placeholder — server-rendering it is #179's territory,
deliberately out of scope (recorded in the issue).

The admin-settings e2e now also asserts the title carries the new name
right after saving, without a reload.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017aviRTgWCcAHUh1SBoxf6P
2026-08-04 11:43:05 +02:00
4d6a27194f Follow the field rename in vs-nfd-marking locators
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 7m0s
CI / Build container images (pull_request) Successful in 1m23s
CI / Auth e2e pack (pull_request) Successful in 9m0s
CI / Import/export fidelity gate (pull_request) Successful in 1m1s
CD / Deploy to Test (push) Blocked by required conditions
CD / Smoke tests against Test (push) Blocked by required conditions
CD / Promote to Int (push) Blocked by required conditions
CI / Auth e2e pack (push) Blocked by required conditions
CI / Import/export fidelity gate (push) Blocked by required conditions
CI / Build container images (push) Blocked by required conditions
CI / Lint, typecheck, test (push) Has been cancelled
CD / Build and push images (push) Has been cancelled
The pack addresses the registration-mode select by its DOM name
attribute, which react-hook-form derives from the field name — now
`registrationMode` (dot-free, see admin-settings-form.ts). Caught by CI
run 713; the pack needs VS_NFD_MODE stages and was not part of the local
verification set.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017aviRTgWCcAHUh1SBoxf6P
2026-08-04 11:42:55 +02:00
3 changed files with 21 additions and 9 deletions

View File

@ -33,9 +33,10 @@ test('instance name changed in the general settings form persists', async ({ bro
// Scoped to the card: the page has several forms with status regions. // Scoped to the card: the page has several forms with status regions.
await expect(generalCard.getByRole('status')).toHaveText(/^(Saved\.|Gespeichert\.)$/); await expect(generalCard.getByRole('status')).toHaveText(/^(Saved\.|Gespeichert\.)$/);
// The TopBar shows the new name without a reload — the save invalidates // The TopBar and the document title show the new name without a reload —
// the branding query the TopBar reads from. // the save invalidates the branding query both read from (issue #323).
await expect(page.locator('.topbar__brand')).toHaveText(newName); await expect(page.locator('.topbar__brand')).toHaveText(newName);
await expect(page).toHaveTitle(new RegExp(`${newName}$`));
// The proof the form really persisted: the value survives a reload and // The proof the form really persisted: the value survives a reload and
// the api returns it. // the api returns it.

View File

@ -34,7 +34,7 @@ test('mode marked: card, checkbox marking, and point-of-choice marking', async (
// select value — compliant choice clears it, violating choice brings it // select value — compliant choice clears it, violating choice brings it
// back, no save in between. // back, no save in between.
const regField = page.locator('label.field', { const regField = page.locator('label.field', {
has: page.locator('select[name="auth.registrationMode"]'), has: page.locator('select[name="registrationMode"]'),
}); });
const regSelect = regField.locator('select'); const regSelect = regField.locator('select');
await regSelect.selectOption('open'); await regSelect.selectOption('open');
@ -72,7 +72,7 @@ test('mode hidden: rows disappear, notes mark the hiding, a11y clean', async ({
// Value-listed control: the compliant registration mode keeps only its // Value-listed control: the compliant registration mode keeps only its
// compliant choice (seed leaves it open = violating? then all options). // compliant choice (seed leaves it open = violating? then all options).
const regSelect = page.locator('select[name="auth.registrationMode"]'); const regSelect = page.locator('select[name="registrationMode"]');
const regField = page.locator('label.field', { has: regSelect }); const regField = page.locator('label.field', { has: regSelect });
const optionCount = await regSelect.locator('option').count(); const optionCount = await regSelect.locator('option').count();
const marked = await regField.locator('.vs-nfd-mark').count(); const marked = await regField.locator('.vs-nfd-mark').count();

View File

@ -1,22 +1,33 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useBranding } from '../branding/use-branding';
const APP_NAME = 'Dorfteich'; const APP_NAME = 'Dorfteich';
/** /**
* Route-specific document title (issue #163, WCAG 2.4.2): joins the given * Route-specific document title (issue #163, WCAG 2.4.2): joins the given
* parts with the app name ("Page — Pond — Dorfteich"). Empty/undefined * parts with the instance name ("Page — Pond — My Wiki"). Empty/undefined
* parts are skipped, so callers can pass still-loading data directly. * parts are skipped, so callers can pass still-loading data directly.
* Falls back to the bare app name on unmount. * Falls back to the bare instance name on unmount.
*
* The trailing name is the OPERATOR'S instance name, not the product name
* (issue #323) same reasoning as the TopBar brand (issue #306). Until
* the branding query resolves (or when it cannot, e.g. maintenance mode)
* the shipped default keeps the title stable, so an untouched instance
* reads exactly as before.
*/ */
export function useDocumentTitle(...parts: (string | null | undefined)[]): void { export function useDocumentTitle(...parts: (string | null | undefined)[]): void {
const joined = [...parts.filter(Boolean), APP_NAME].join(' — '); const appName = useBranding()?.instanceName.trim() || APP_NAME;
const joined = [...parts.filter(Boolean), appName].join(' — ');
useEffect(() => { useEffect(() => {
document.title = joined; document.title = joined;
}, [joined]); }, [joined]);
useEffect( useEffect(
// On unmount only in effect: `joined` always changes with `appName`,
// so the title effect above re-runs right after this cleanup.
() => () => { () => () => {
document.title = APP_NAME; document.title = appName;
}, },
[], [appName],
); );
} }