···7788## [Unreleased]
991010+### Changed
1111+- Calendar: topbar settings button now renders as a proper 8-tooth gear glyph instead of a sunburst that was visually indistinguishable from the adjacent theme-toggle sun (v0.62.4, #697). The old `#btn-cal-settings` SVG was a circle-with-8-radiating-lines (`circle r=2.5` + eight `M..v.. / M..h..` rays) and the theme-toggle `#btn-theme-toggle` right next to it was the same circle-with-8-rays pattern at `r=3.5` — users clicked the wrong one constantly, with the settings panel hiding some destructive controls. Replaced the settings icon with a true 32-vertex 8-tooth gear polygon (outer tooth tip radius 6.8, valley radius 4.6, hub circle r=2.2) traced in `src/calendar/index.html`. Added an `icon-gear` marker class so future refactors can't silently regress to a sunburst, plus four Playwright regression tests in `e2e/calendar.spec.ts` that pin the contract: (1) the icon carries the `icon-gear` class; (2) the settings path `d` attribute differs from the theme-toggle path; (3) the path has either curves, a closed subpath, or >24 draw commands (sunburst had ~16 and no close); (4) both buttons remain visible and adjacent. Also added `aria-label` + `aria-hidden` attributes that the theme-toggle already had for a11y parity. (#697)
1212+1013### Fixed
1114- Calendar: mini-calendar date cells now expose a full spoken date via `aria-label` (v0.62.3, #696). Previously the `.cal-mini-day` buttons contained only the day number as text, so screen readers announced "button, 29" with no month/year/weekday context — unusable for non-sighted navigation. `renderMiniCalendar` now builds an `aria-label` like `"Tuesday, March 29, 2026"` (plus `, today`, `, selected`, and `, N events` suffixes when applicable) using a new shared `formatLongDate(d)` helper in `src/calendar/helpers.ts` that wraps `toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })`. The today cell also carries `aria-current="date"` and the selected cell carries `aria-pressed="true"` so state is conveyed through standard AT semantics rather than class names alone. 4 regression tests in `tests/calendar-mini-day-aria.test.ts` pin the helper output and verify every emitted `.cal-mini-day` button tag carries an `aria-label=` attribute. (#696)
1215- CSP: externalized 3 inline scripts from every page template so the app's `script-src 'self'` CSP stops silently blocking them (v0.62.2, #694). Theme init (FOUC prevention, reads localStorage before paint), theme-toggle click handler, and service-worker update→reload handler now live in `public/theme-init.js`, `public/theme-toggle.js`, and `public/sw-reload.js` respectively, loaded via `<script src="...">` so they satisfy the strict CSP without needing nonces or `unsafe-inline`. Before: the theme toggle button did nothing, dark-mode users saw a flash of light theme on every page load, and users never auto-reloaded when a new version deployed. All 7 HTML templates (landing + 6 editors) had the inline blocks; all 7 are now externalized. Added `tests/csp-no-inline-scripts.test.ts` which scans every template for inline `<script>` blocks and fails if any are reintroduced. Caught live by driving the deployed v0.62.1 app via Playwright MCP. (#694)
+72
e2e/calendar.spec.ts
···563563 expect(restoredTheme).toBe(initialTheme);
564564 });
565565});
566566+567567+// ---------------------------------------------------------------------------
568568+// Calendar - Topbar Icon Distinction (#697)
569569+// ---------------------------------------------------------------------------
570570+//
571571+// The calendar settings button (#btn-cal-settings) and the theme toggle
572572+// (#btn-theme-toggle) historically rendered as near-identical sun/sunburst
573573+// glyphs (circle + 8 radiating lines). Users clicked the wrong one.
574574+//
575575+// These tests lock in that the settings icon is a distinct gear glyph — it
576576+// must have visibly different SVG geometry than the theme toggle, and it
577577+// must carry a marker class so future refactors don't silently regress to
578578+// a sunburst.
579579+// ---------------------------------------------------------------------------
580580+581581+test.describe('Calendar - Topbar Icon Distinction (#697)', () => {
582582+ test.beforeEach(async ({ page }) => {
583583+ await createNewCalendar(page);
584584+ });
585585+586586+ test('settings icon uses a gear glyph, not a sunburst', async ({ page }) => {
587587+ const settingsSvg = page.locator('#btn-cal-settings svg');
588588+ // The gear icon carries a dedicated marker class so tooling / future
589589+ // visual tests can identify it without parsing path data.
590590+ await expect(settingsSvg).toHaveClass(/icon-gear/);
591591+ });
592592+593593+ test('settings icon SVG geometry differs from theme toggle SVG geometry', async ({
594594+ page,
595595+ }) => {
596596+ const settingsPathData = await page
597597+ .locator('#btn-cal-settings svg path')
598598+ .first()
599599+ .getAttribute('d');
600600+ const themePathData = await page
601601+ .locator('#btn-theme-toggle svg path')
602602+ .first()
603603+ .getAttribute('d');
604604+605605+ expect(settingsPathData).toBeTruthy();
606606+ expect(themePathData).toBeTruthy();
607607+ expect(settingsPathData).not.toBe(themePathData);
608608+ });
609609+610610+ test('settings icon does not contain the sunburst radiating-lines pattern', async ({
611611+ page,
612612+ }) => {
613613+ // The old buggy icon was a sunburst: 8 short single-segment rays with
614614+ // 8 separate M moves, ~16 total draw commands, and no closed subpath.
615615+ // A proper gear glyph must be substantially more geometrically complex:
616616+ // either many more segments, or include curves/arcs.
617617+ const d = await page
618618+ .locator('#btn-cal-settings svg path')
619619+ .first()
620620+ .getAttribute('d');
621621+ expect(d).toBeTruthy();
622622+ const hasCurves = /[ACQ]/i.test(d!);
623623+ const hasClosedSubpath = /[Zz]/.test(d!);
624624+ // Count all drawing commands (M, L, H, V, C, Q, A, Z).
625625+ const commandCount = (d!.match(/[MLHVCQAZ]/gi) || []).length;
626626+ // Sunburst had ~16 commands (8 M + 8 L-ish). Gear polygon has ~34+ or
627627+ // curved gear has arcs. Require clear differentiation from sunburst.
628628+ expect(hasCurves || hasClosedSubpath || commandCount > 24).toBe(true);
629629+ });
630630+631631+ test('settings and theme buttons remain visually adjacent and both visible', async ({
632632+ page,
633633+ }) => {
634634+ await expect(page.locator('#btn-cal-settings')).toBeVisible();
635635+ await expect(page.locator('#btn-theme-toggle')).toBeVisible();
636636+ });
637637+});