Analytics: move out of the Tab Bar into an overlay (like Settings and Search) #973

Open
opened 2026-10-03 06:30:09 +00:00 by kayg · 12 comments
Owner

Request (owner, 2026-10-03)

"In the same spirit I feel like analytics should be removed from the tab group and should be an overlay instead like in calternal.js."

Context: the owner agreed that a Canvas is a file type in Notes/Files, not a new Tab (#509, Q2). The chrome rule (DESIGN §34) keeps the Tab Bar for modes you work in. Analytics is a view you look at, so it moves out of the Tab Bar.

Port source

calternal.js opens Analytics as an overlay, the same way as Settings and Search:

  • /home/kayg/Developer/calternal.js/packages/web/src/lib/overlay/state.svelte.ts: overlay openers, pushState open, close without history.back(), generic unmount teardown;
  • /home/kayg/Developer/calternal.js/packages/web/src/routes/analytics/[[period]]/[[anchor]]/+page.svelte: the deep-linkable route.

Reuse the calternal overlay primitive that Settings and Search already use. Do not build a new one (reuse gate).

Scope

  1. Remove Analytics from the mode tray default order (DESIGN §34 "Mode tray"). Migrate a saved mode_order that contains analytics by dropping the ID. Keep the order of the other modes.
  2. Open Analytics as an overlay from:
    • the sidebar header or account area, next to Settings;
    • the palette ("Analytics", "Time tracked this week", …);
    • every Calendar/Log place that shows a tracked-time total today (click the total → Analytics for that period).
  3. Deep links: /analytics/<period>/<anchor> opens the overlay over the last mode (or Today on a cold open). Escape and close return to where the User was. Copy link in the overlay header.
  4. Motion: the shared overlay spring. Keyboard opens and closes animate the same way (#611). Content shows as soon as the data exists (no waiting on motion).
  5. Accessibility: dialog semantics, focus trap, focus return, reduced motion, touch (sheet on phones).
  6. Update DESIGN §34 ("Analytics: its own mode" → overlay) and the mode-order text.

Tests

  • e2e as a User: open from the sidebar, the palette and a deep link; close with Escape; check focus return; check that the Tab Bar has no Analytics entry; check that a migrated saved order keeps the other modes in the same order.
  • Screenshots: production build at 390/820/1440, light and dark, overlay open over Calendar.
## Request (owner, 2026-10-03) "In the same spirit I feel like analytics should be removed from the tab group and should be an overlay instead like in calternal.js." Context: the owner agreed that a Canvas is a file type in Notes/Files, not a new Tab (#509, Q2). The chrome rule (DESIGN §34) keeps the Tab Bar for modes you work in. Analytics is a view you look at, so it moves out of the Tab Bar. ## Port source calternal.js opens Analytics as an overlay, the same way as Settings and Search: - `/home/kayg/Developer/calternal.js/packages/web/src/lib/overlay/state.svelte.ts`: overlay openers, pushState open, close without `history.back()`, generic unmount teardown; - `/home/kayg/Developer/calternal.js/packages/web/src/routes/analytics/[[period]]/[[anchor]]/+page.svelte`: the deep-linkable route. Reuse the calternal overlay primitive that Settings and Search already use. Do not build a new one (reuse gate). ## Scope 1. Remove Analytics from the mode tray default order (DESIGN §34 "Mode tray"). Migrate a saved `mode_order` that contains `analytics` by dropping the ID. Keep the order of the other modes. 2. Open Analytics as an overlay from: - the sidebar header or account area, next to Settings; - the palette ("Analytics", "Time tracked this week", …); - every Calendar/Log place that shows a tracked-time total today (click the total → Analytics for that period). 3. Deep links: `/analytics/<period>/<anchor>` opens the overlay over the last mode (or Today on a cold open). Escape and close return to where the User was. Copy link in the overlay header. 4. Motion: the shared overlay spring. Keyboard opens and closes animate the same way (#611). Content shows as soon as the data exists (no waiting on motion). 5. Accessibility: dialog semantics, focus trap, focus return, reduced motion, touch (sheet on phones). 6. Update DESIGN §34 ("Analytics: its own mode" → overlay) and the mode-order text. ## Tests - e2e as a User: open from the sidebar, the palette and a deep link; close with Escape; check focus return; check that the Tab Bar has no Analytics entry; check that a migrated saved order keeps the other modes in the same order. - Screenshots: production build at 390/820/1440, light and dark, overlay open over Calendar.
Author
Owner

Started on branch job/analytics-973 at base 48c94c9776. I am reading the Analytics entry points and the existing overlay primitive before implementation.

Started on branch job/analytics-973 at base 48c94c9776660cee105be86c5a6ace90dd425367. I am reading the Analytics entry points and the existing overlay primitive before implementation.
Author
Owner

Finding: the web Calendar and Journal routes do not currently render a tracked-time total; finds no such surface, so there is no existing Calendar/Log total to wire in this change.

Finding: the web Calendar and Journal routes do not currently render a tracked-time total; finds no such surface, so there is no existing Calendar/Log total to wire in this change.
Author
Owner

Finding: the Calendar and Journal route source contains no tracked-time total to open Analytics from. Search evidence: rg -n -i 'tracked|duration|time total|total.*minute|minute.*total' apps/web/src/routes/calendar apps/web/src/routes/journal found only Calendar undo-duration constants. This change therefore adds Analytics entry points to the account menu and command palette; no existing Calendar or Journal total was available to wire.

Finding: the Calendar and Journal route source contains no tracked-time total to open Analytics from. Search evidence: `rg -n -i 'tracked|duration|time total|total.*minute|minute.*total' apps/web/src/routes/calendar apps/web/src/routes/journal` found only Calendar undo-duration constants. This change therefore adds Analytics entry points to the account menu and command palette; no existing Calendar or Journal total was available to wire.
Author
Owner

Finding: Selecting “Time tracked this week” from the Search palette closed Search and opened Analytics in the same Svelte update. Search surface cleanup consumed Analytics’ newly captured source route. The production E2E reproduced this from /files: Escape returned to /today and focus did not return to Search. Search now waits one Svelte tick after closing before invoking a selected action; the production E2E passes with /files and Search focus restored.

Finding: On phones, Account and Search live in the sidebar sheet. That sheet closes when Analytics opens, so its opener is no longer a valid focus target. Analytics now restores focus to the visible sidebar toggle on phone widths; the production E2E verifies this at 390 px.

Refreshed macOS production screenshots (390/820/1440 px, paper and tokyo-night):

Finding: Selecting “Time tracked this week” from the Search palette closed Search and opened Analytics in the same Svelte update. Search surface cleanup consumed Analytics’ newly captured source route. The production E2E reproduced this from `/files`: Escape returned to `/today` and focus did not return to Search. Search now waits one Svelte tick after closing before invoking a selected action; the production E2E passes with `/files` and Search focus restored. Finding: On phones, Account and Search live in the sidebar sheet. That sheet closes when Analytics opens, so its opener is no longer a valid focus target. Analytics now restores focus to the visible sidebar toggle on phone widths; the production E2E verifies this at 390 px. Refreshed macOS production screenshots (390/820/1440 px, paper and tokyo-night): - 390 paper: https://git.kayg.org/attachments/e14ecbf3-d1df-4fec-a972-4ed01cf414ac - 390 tokyo-night: https://git.kayg.org/attachments/45d238a8-c2ca-469c-b433-637eef8c65e4 - 820 paper: https://git.kayg.org/attachments/d7058b91-a35f-4528-b3b1-ebe6093c117d - 820 tokyo-night: https://git.kayg.org/attachments/0fa4422c-fd66-4828-8e71-42368e0fd0ff - 1440 paper: https://git.kayg.org/attachments/87c3f06c-e2e9-49ff-9b5e-8e74534c9742 - 1440 tokyo-night: https://git.kayg.org/attachments/10d86a95-16ca-4928-9c2a-222c4e33ea8a
Author
Owner

Built

Analytics is no longer a Tab Bar mode. A saved mode order drops only analytics, preserving the order of the remaining IDs. Account menu and Search palette actions open the deep-linkable Analytics route overlay. Period changes update the route, Copy link preserves its period and anchor, and close returns to the opener route; a cold deep link closes to Today.

The Search action now runs after Search overlay cleanup, so cleanup cannot erase Analytics' captured source route. On phones, Account and Search open from the sidebar sheet; Analytics close returns focus to the visible sidebar toggle. Added production E2E coverage for these paths and refreshed six macOS screenshots at 390, 820 and 1440 px in Paper and Tokyo Night. The six screenshot attachments are linked in the preceding finding comment.

Added Analytics overlay-open and 10k-Log large-Home scenarios to the existing bench/ profiles and report integration. No Rust or server API code changed.

Files

  • Navigation and design: apps/web/src/lib/navigation.ts, apps/web/src/lib/navigation/modePreload.ts, apps/web/src/lib/components/app-sidebar.svelte, deleted apps/web/src/lib/components/analytics/AnalyticsSidebar.svelte, apps/web/src/lib/navigation.test.ts, apps/web/src/lib/trayModes.test.ts, docs/DESIGN.md.
  • Overlay and Search: apps/web/src/lib/analytics/open.ts, apps/web/src/lib/analytics/open.test.ts, apps/web/src/lib/nav/restoreLaunchRoute.ts, apps/web/src/lib/overlay/state.svelte.ts, apps/web/src/lib/components/analytics/PeriodSelector.svelte, apps/web/src/lib/search/providers.ts, apps/web/src/lib/search/access.test.ts, apps/web/src/lib/components/search-dialog.svelte, apps/web/src/routes/analytics/[period]/[date]/+page.svelte, apps/web/src/routes/+layout.svelte, packages/ui/src/components/ChromeActions.svelte, packages/ui/src/components/ModeHeader.svelte.
  • E2E and profiles: apps/web/e2e/analytics-overlay-973.mjs, apps/web/e2e/analytics.mjs, apps/web/e2e/harness.mjs, apps/web/e2e/layout-sweep.mjs, apps/web/e2e/search.mjs, apps/web/e2e/shell.mjs, apps/web/e2e/route-perf.mjs, apps/web/e2e/photos-perf.mjs, apps/web/package.json, bench/record.py, bench/test_record.py.

Decisions

  • Calendar and Journal contain no tracked-time total to wire. rg -n -i 'tracked|duration|time total|total.*minute|minute.*total' apps/web/src/routes/calendar apps/web/src/routes/journal found only Calendar undo-duration constants. I added no invented UI or placeholder data.
  • Search waits one Svelte tick after closing before running any selected action. This lets overlay cleanup finish before a new overlay captures the source route.
  • A phone opener inside the closing sidebar sheet restores focus to its always-visible toggle.

UX gaps closed / left

Closed: Search route and focus return; phone Escape focus return; keyboard and pointer period selection; route copy; saved mode-order migration; real empty Analytics state; account, palette and cold-link open/close coverage.

Left: no Calendar or Journal tracked-time total exists for an inline opener. Visual review is for the orchestrator; the production screenshots are attached for that review.

Verification

cargo fmt --check exited 0 with no output. No Rust files changed, so per-crate Clippy and Rust tests did not apply.

bun run check output:

User browser caches use userStorage; only documented device/public-link exceptions remain.
Text sizes and UI shape values use shared role tokens.
UI transitions and animation options use shared motion tokens or documented exceptions.
Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/analytics-973/apps/web
Getting Svelte diagnostics...

svelte-check found 0 errors and 0 warnings

Focused Vitest output:

 Test Files  4 passed (4)
      Tests  23 passed (23)
   Start at  12:35:59
   Duration  8.99s (transform 87%, import 12%, tests 1%)

Focused production E2E command: CALTERNAL_SERVER_BIN=/mnt/hdd/targets/jobs/analytics-973/debug/calternal-server bun run test:e2e:analytics-overlay-973.

PASS mode order migration
PASS account menu opened Analytics
PASS keyboard period selection
PASS pointer period selection
PASS account route and focus restored
PASS palette opened Analytics
PASS palette source route restored
PASS palette route and focus restored
PASS cold deep link closes to Today
PASS phone Escape returns focus to the visible sidebar toggle
PASS Analytics account/palette/deep-link openers, Escape and focus return, no Tab Bar entry, saved-order migration

bun run build succeeded; output included existing vendor "use client" directive warnings. Success lines:

✓ built in 41.21s
Run npm run preview to preview your production build locally.
> Using @sveltejs/adapter-static
  Wrote site to "build"
  ✔ done

python3 -m unittest bench.test_record output:

..........
----------------------------------------------------------------------
Ran 10 tests in 0.084s

OK

cargo clean output: Removed 7237 files, 4.6GiB total. Generated web build output and temporary upload files were removed. Worktree is clean.

For the merge round

Per the verification policy, the full suites, adversarial matrices and perf-VM measurements were not run in this feature job. Run the full web tests with cd apps/web && bun run test; exercise the changed production flows with cd apps/web && bun run test:e2e:analytics, cd apps/web && bun run test:e2e:search, cd apps/web && bun run test:e2e, and cd apps/web && bun run test:e2e:layout; run the one-time adversarial matrix with tests/adversarial/run.sh.

The Analytics profile is in the existing route and large-Home profiles. On the perf VM, hold the lock for each measurement and record load average inside it:

flock /root/perf.lock bash -c 'uptime; PERF_ONLY=routes PERF_RUNS=3 bench/run.sh --measure-only --runs 3'
flock /root/perf.lock bash -c 'uptime; PERF_ONLY=large-home PERF_RUNS=3 PERF_HOME_NOTES=100000 PERF_HOME_FILES=1000000 PERF_PHOTOS=20000 bench/run.sh --full --measure-only --runs 3'

The routes run measures open latency, idle CPU/RSS and the Analytics API burst. The large-Home run exercises the 10k-Log Analytics report and overlay open. Compare both with docs/perf/baseline.json.

Commits

Head: 3a03b7ce29d00e45de73cd9e7433340355c92a4b.

  • 035341777 remove Analytics from the mode tray
  • 47d0fc237 open Analytics as a route overlay
  • 508293735 measure overlay opens on real workloads
  • e8c608f07 cover the overlay on real builds
  • b0cf02bbe restore palette and phone focus
  • 3a03b7ce2 document profile coverage in test docs
## Built Analytics is no longer a Tab Bar mode. A saved mode order drops only `analytics`, preserving the order of the remaining IDs. Account menu and Search palette actions open the deep-linkable Analytics route overlay. Period changes update the route, Copy link preserves its period and anchor, and close returns to the opener route; a cold deep link closes to Today. The Search action now runs after Search overlay cleanup, so cleanup cannot erase Analytics' captured source route. On phones, Account and Search open from the sidebar sheet; Analytics close returns focus to the visible sidebar toggle. Added production E2E coverage for these paths and refreshed six macOS screenshots at 390, 820 and 1440 px in Paper and Tokyo Night. The six screenshot attachments are linked in the preceding finding comment. Added Analytics overlay-open and 10k-Log large-Home scenarios to the existing `bench/` profiles and report integration. No Rust or server API code changed. ## Files - Navigation and design: `apps/web/src/lib/navigation.ts`, `apps/web/src/lib/navigation/modePreload.ts`, `apps/web/src/lib/components/app-sidebar.svelte`, deleted `apps/web/src/lib/components/analytics/AnalyticsSidebar.svelte`, `apps/web/src/lib/navigation.test.ts`, `apps/web/src/lib/trayModes.test.ts`, `docs/DESIGN.md`. - Overlay and Search: `apps/web/src/lib/analytics/open.ts`, `apps/web/src/lib/analytics/open.test.ts`, `apps/web/src/lib/nav/restoreLaunchRoute.ts`, `apps/web/src/lib/overlay/state.svelte.ts`, `apps/web/src/lib/components/analytics/PeriodSelector.svelte`, `apps/web/src/lib/search/providers.ts`, `apps/web/src/lib/search/access.test.ts`, `apps/web/src/lib/components/search-dialog.svelte`, `apps/web/src/routes/analytics/[period]/[date]/+page.svelte`, `apps/web/src/routes/+layout.svelte`, `packages/ui/src/components/ChromeActions.svelte`, `packages/ui/src/components/ModeHeader.svelte`. - E2E and profiles: `apps/web/e2e/analytics-overlay-973.mjs`, `apps/web/e2e/analytics.mjs`, `apps/web/e2e/harness.mjs`, `apps/web/e2e/layout-sweep.mjs`, `apps/web/e2e/search.mjs`, `apps/web/e2e/shell.mjs`, `apps/web/e2e/route-perf.mjs`, `apps/web/e2e/photos-perf.mjs`, `apps/web/package.json`, `bench/record.py`, `bench/test_record.py`. ## Decisions - Calendar and Journal contain no tracked-time total to wire. `rg -n -i 'tracked|duration|time total|total.*minute|minute.*total' apps/web/src/routes/calendar apps/web/src/routes/journal` found only Calendar undo-duration constants. I added no invented UI or placeholder data. - Search waits one Svelte tick after closing before running any selected action. This lets overlay cleanup finish before a new overlay captures the source route. - A phone opener inside the closing sidebar sheet restores focus to its always-visible toggle. ## UX gaps closed / left Closed: Search route and focus return; phone Escape focus return; keyboard and pointer period selection; route copy; saved mode-order migration; real empty Analytics state; account, palette and cold-link open/close coverage. Left: no Calendar or Journal tracked-time total exists for an inline opener. Visual review is for the orchestrator; the production screenshots are attached for that review. ## Verification `cargo fmt --check` exited 0 with no output. No Rust files changed, so per-crate Clippy and Rust tests did not apply. `bun run check` output: ```text User browser caches use userStorage; only documented device/public-link exceptions remain. Text sizes and UI shape values use shared role tokens. UI transitions and animation options use shared motion tokens or documented exceptions. Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/analytics-973/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` Focused Vitest output: ```text Test Files 4 passed (4) Tests 23 passed (23) Start at 12:35:59 Duration 8.99s (transform 87%, import 12%, tests 1%) ``` Focused production E2E command: `CALTERNAL_SERVER_BIN=/mnt/hdd/targets/jobs/analytics-973/debug/calternal-server bun run test:e2e:analytics-overlay-973`. ```text PASS mode order migration PASS account menu opened Analytics PASS keyboard period selection PASS pointer period selection PASS account route and focus restored PASS palette opened Analytics PASS palette source route restored PASS palette route and focus restored PASS cold deep link closes to Today PASS phone Escape returns focus to the visible sidebar toggle PASS Analytics account/palette/deep-link openers, Escape and focus return, no Tab Bar entry, saved-order migration ``` `bun run build` succeeded; output included existing vendor `"use client"` directive warnings. Success lines: ```text ✓ built in 41.21s Run npm run preview to preview your production build locally. > Using @sveltejs/adapter-static Wrote site to "build" ✔ done ``` `python3 -m unittest bench.test_record` output: ```text .......... ---------------------------------------------------------------------- Ran 10 tests in 0.084s OK ``` `cargo clean` output: `Removed 7237 files, 4.6GiB total`. Generated web build output and temporary upload files were removed. Worktree is clean. ## For the merge round Per the verification policy, the full suites, adversarial matrices and perf-VM measurements were not run in this feature job. Run the full web tests with `cd apps/web && bun run test`; exercise the changed production flows with `cd apps/web && bun run test:e2e:analytics`, `cd apps/web && bun run test:e2e:search`, `cd apps/web && bun run test:e2e`, and `cd apps/web && bun run test:e2e:layout`; run the one-time adversarial matrix with `tests/adversarial/run.sh`. The Analytics profile is in the existing route and large-Home profiles. On the perf VM, hold the lock for each measurement and record load average inside it: ```sh flock /root/perf.lock bash -c 'uptime; PERF_ONLY=routes PERF_RUNS=3 bench/run.sh --measure-only --runs 3' flock /root/perf.lock bash -c 'uptime; PERF_ONLY=large-home PERF_RUNS=3 PERF_HOME_NOTES=100000 PERF_HOME_FILES=1000000 PERF_PHOTOS=20000 bench/run.sh --full --measure-only --runs 3' ``` The routes run measures open latency, idle CPU/RSS and the Analytics API burst. The large-Home run exercises the 10k-Log Analytics report and overlay open. Compare both with `docs/perf/baseline.json`. ## Commits Head: `3a03b7ce29d00e45de73cd9e7433340355c92a4b`. - `035341777` remove Analytics from the mode tray - `47d0fc237` open Analytics as a route overlay - `508293735` measure overlay opens on real workloads - `e8c608f07` cover the overlay on real builds - `b0cf02bbe` restore palette and phone focus - `3a03b7ce2` document profile coverage in test docs
Author
Owner

Follow-up started on job/analytics-973, continuing from 3a03b7ce29d00e45de73cd9e7433340355c92a4b; current origin/dev base is f06679b11cde29cc0b7120fdab5f721389caf695.

The screenshot review found two gaps: the 1440 px Paper capture did not prove the dialog was visible, and the 390 px Tokyo Night shot duplicated the empty-state text instead of showing the Analytics title and period. I am tracing the real header and empty-state rendering and updating the production E2E to assert the dialog before each capture. I will also seed logs and item activity through the real APIs so the requested screenshots show populated charts and comparisons, plus a separate empty-state capture.

Follow-up started on `job/analytics-973`, continuing from `3a03b7ce29d00e45de73cd9e7433340355c92a4b`; current `origin/dev` base is `f06679b11cde29cc0b7120fdab5f721389caf695`. The screenshot review found two gaps: the 1440 px Paper capture did not prove the dialog was visible, and the 390 px Tokyo Night shot duplicated the empty-state text instead of showing the Analytics title and period. I am tracing the real header and empty-state rendering and updating the production E2E to assert the dialog before each capture. I will also seed logs and item activity through the real APIs so the requested screenshots show populated charts and comparisons, plus a separate empty-state capture.
Author
Owner

Finding: the first production e2e run wrote 14 duration Logs and four Photos, and the Analytics API reported notes: 2 and photos: 2 in both weeks. It reported tasks: 0 in both weeks because the uploaded Markdown had Note frontmatter, not the Task identity/status fields and root checkbox that the Notes Plugin indexes as a Task. I changed the fixture to use that Task shape; the next run asserts at least one Task in each week before capture.

Finding: the first production e2e run wrote 14 duration Logs and four Photos, and the Analytics API reported `notes: 2` and `photos: 2` in both weeks. It reported `tasks: 0` in both weeks because the uploaded Markdown had Note frontmatter, not the Task identity/status fields and root checkbox that the Notes Plugin indexes as a Task. I changed the fixture to use that Task shape; the next run asserts at least one Task in each week before capture.
Author
Owner

Finding from the populated capture run: the phone period-selector UX check switched week → month → week, and Analytics correctly preserved the selected range start. The resulting week was /analytics/week/2026-08-30, outside the two-week fixture seeded from the current date. The capture then timed out looking for the lazy donut chart. I changed the test to close and reopen Analytics after the selector check, which restores the default seeded week; dashboard synchronization now waits for a visible stat, empty state, or error state before capture.

Finding from the populated capture run: the phone period-selector UX check switched week → month → week, and Analytics correctly preserved the selected range start. The resulting week was `/analytics/week/2026-08-30`, outside the two-week fixture seeded from the current date. The capture then timed out looking for the lazy donut chart. I changed the test to close and reopen Analytics after the selector check, which restores the default seeded week; dashboard synchronization now waits for a visible stat, empty state, or error state before capture.
Author
Owner

Completed the #973 review send-back and attached the seven screenshots below.

Built

  • The overlay heading is “Analytics” plus the selected period. On phones the shared sheet chrome owns the visible title; the clipped route h1 remains the level-one page heading. The empty-state message appears once.
  • The screenshot e2e now asserts the visible Analytics dialog, its period heading, and the phone sheet title before capture. It mounts each lazy chart and resets both sheet and overlay scrollports.
  • The fixture writes 14 dated Logs with durations and Tags, four Notes with indexed Tasks, and four Photos across the current and comparison weeks. It waits for both real reports to contain those records, then captures populated and empty states.
  • The 1440 Paper capture now contains the Analytics overlay instead of only the Calendar page.

UX gaps closed

The capture covers 390, 820, and 1440 px in Paper and Tokyo Night, with macOS platform emulation. It checks account and palette openers, a cold deep link, Copy link, period selection, Escape, focus return, phone touch target size, and the absence of an Analytics Tab Bar entry. The populated captures show the comparison cards, activity charts, time heatmap, and daily heatmap. The empty capture verifies one empty-state heading.

UX gaps left

Calendar and Journal do not currently show a tracked-time total, so there is no existing total control to link to Analytics. The Analytics search actions remain available. The build reports existing vendored Bklit “use client” module-directive warnings; it completes successfully.

Decisions (not specified in DESIGN)

  • The phone uses OverlaySurface’s sheetTitle for its single visible period title; the route h1 remains clipped on phone layouts and visible on wider dashboard layouts.
  • The fixture is relative to today and covers 14 days. Four ordinary Note Markdown files carry the existing Task identity/status/date fields and checkboxes so indexed Tasks appear in both weeks.
  • The empty capture uses week 2000-01-03, outside the fixture dates.

Files

  • Analytics route and e2e: apps/web/src/routes/analytics/[period]/[date]/+page.svelte, apps/web/e2e/analytics-overlay-973.mjs, apps/web/e2e/analytics.mjs, apps/web/e2e/harness.mjs.
  • Navigation and chrome: apps/web/src/routes/+layout.svelte, apps/web/src/lib/analytics/open.ts, apps/web/src/lib/analytics/open.test.ts, apps/web/src/lib/components/analytics/AnalyticsSidebar.svelte, apps/web/src/lib/components/analytics/PeriodSelector.svelte, apps/web/src/lib/components/app-sidebar.svelte, apps/web/src/lib/components/search-dialog.svelte, apps/web/src/lib/nav/restoreLaunchRoute.ts, apps/web/src/lib/navigation.ts, apps/web/src/lib/navigation.test.ts, apps/web/src/lib/navigation/modePreload.ts, apps/web/src/lib/overlay/state.svelte.ts, apps/web/src/lib/search/access.test.ts, apps/web/src/lib/search/providers.ts, apps/web/src/lib/trayModes.test.ts, packages/ui/src/components/ChromeActions.svelte, packages/ui/src/components/ModeHeader.svelte.
  • E2E and performance support: apps/web/e2e/layout-sweep.mjs, apps/web/e2e/photos-perf.mjs, apps/web/e2e/route-perf.mjs, apps/web/e2e/search.mjs, apps/web/e2e/shell.mjs, apps/web/package.json, bench/record.py, bench/test_record.py.
  • Design: docs/DESIGN.md.

Verification

cargo fmt --check — exit 0, no output.

bun run check:

User browser caches use userStorage; only documented device/public-link exceptions remain.
Text sizes and UI shape values use shared role tokens.
UI transitions and animation options use shared motion tokens or documented exceptions.
Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/analytics-973/apps/web
Getting Svelte diagnostics...

svelte-check found 0 errors and 0 warnings

Focused Vitest, bunx vitest run src/lib/analytics/open.test.ts src/lib/navigation.test.ts --maxWorkers=2:

 Test Files  2 passed (2)
      Tests  14 passed (14)
   Start at  15:04:32
   Duration  8.05s (transform 90%, import 10%)

Production build:

✓ built in 33.41s
  Wrote site to "build"
  ✔ done

Issue-specific production e2e, CALTERNAL_SERVER_BIN=/mnt/hdd/targets/jobs/analytics-973/debug/calternal-server bun run test:e2e:analytics-overlay-973:

READY Analytics fixture: 14 dated Logs across two weeks and non-empty comparison reports
PASS Analytics account/palette/deep-link openers, Escape and focus return, no Tab Bar entry, saved-order migration

cargo clean:

     Removed 7237 files, 4.6GiB total

No Rust files changed, so Rust clippy/test were not applicable. Full web and shared E2E suites were not run in this job. Performance was not measured because #973 is a UI issue; the branch retains the Analytics benchmark profile.

For the merge round

  • cd apps/web && bun run test — run the complete web Vitest suite.
  • cd apps/web && bun run test:e2e — run the shared shell E2E suite.

Head SHA: d95137fa9f7b6c54aa1254bccd6da2b0dc095616.

Screenshots attached to this issue

Completed the #973 review send-back and attached the seven screenshots below. ## Built - The overlay heading is “Analytics” plus the selected period. On phones the shared sheet chrome owns the visible title; the clipped route h1 remains the level-one page heading. The empty-state message appears once. - The screenshot e2e now asserts the visible Analytics dialog, its period heading, and the phone sheet title before capture. It mounts each lazy chart and resets both sheet and overlay scrollports. - The fixture writes 14 dated Logs with durations and Tags, four Notes with indexed Tasks, and four Photos across the current and comparison weeks. It waits for both real reports to contain those records, then captures populated and empty states. - The 1440 Paper capture now contains the Analytics overlay instead of only the Calendar page. ## UX gaps closed The capture covers 390, 820, and 1440 px in Paper and Tokyo Night, with macOS platform emulation. It checks account and palette openers, a cold deep link, Copy link, period selection, Escape, focus return, phone touch target size, and the absence of an Analytics Tab Bar entry. The populated captures show the comparison cards, activity charts, time heatmap, and daily heatmap. The empty capture verifies one empty-state heading. ## UX gaps left Calendar and Journal do not currently show a tracked-time total, so there is no existing total control to link to Analytics. The Analytics search actions remain available. The build reports existing vendored Bklit “use client” module-directive warnings; it completes successfully. ## Decisions (not specified in DESIGN) - The phone uses OverlaySurface’s `sheetTitle` for its single visible period title; the route h1 remains clipped on phone layouts and visible on wider dashboard layouts. - The fixture is relative to today and covers 14 days. Four ordinary Note Markdown files carry the existing Task identity/status/date fields and checkboxes so indexed Tasks appear in both weeks. - The empty capture uses week `2000-01-03`, outside the fixture dates. ## Files - Analytics route and e2e: `apps/web/src/routes/analytics/[period]/[date]/+page.svelte`, `apps/web/e2e/analytics-overlay-973.mjs`, `apps/web/e2e/analytics.mjs`, `apps/web/e2e/harness.mjs`. - Navigation and chrome: `apps/web/src/routes/+layout.svelte`, `apps/web/src/lib/analytics/open.ts`, `apps/web/src/lib/analytics/open.test.ts`, `apps/web/src/lib/components/analytics/AnalyticsSidebar.svelte`, `apps/web/src/lib/components/analytics/PeriodSelector.svelte`, `apps/web/src/lib/components/app-sidebar.svelte`, `apps/web/src/lib/components/search-dialog.svelte`, `apps/web/src/lib/nav/restoreLaunchRoute.ts`, `apps/web/src/lib/navigation.ts`, `apps/web/src/lib/navigation.test.ts`, `apps/web/src/lib/navigation/modePreload.ts`, `apps/web/src/lib/overlay/state.svelte.ts`, `apps/web/src/lib/search/access.test.ts`, `apps/web/src/lib/search/providers.ts`, `apps/web/src/lib/trayModes.test.ts`, `packages/ui/src/components/ChromeActions.svelte`, `packages/ui/src/components/ModeHeader.svelte`. - E2E and performance support: `apps/web/e2e/layout-sweep.mjs`, `apps/web/e2e/photos-perf.mjs`, `apps/web/e2e/route-perf.mjs`, `apps/web/e2e/search.mjs`, `apps/web/e2e/shell.mjs`, `apps/web/package.json`, `bench/record.py`, `bench/test_record.py`. - Design: `docs/DESIGN.md`. ## Verification `cargo fmt --check` — exit 0, no output. `bun run check`: ```text User browser caches use userStorage; only documented device/public-link exceptions remain. Text sizes and UI shape values use shared role tokens. UI transitions and animation options use shared motion tokens or documented exceptions. Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/analytics-973/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` Focused Vitest, `bunx vitest run src/lib/analytics/open.test.ts src/lib/navigation.test.ts --maxWorkers=2`: ```text Test Files 2 passed (2) Tests 14 passed (14) Start at 15:04:32 Duration 8.05s (transform 90%, import 10%) ``` Production build: ```text ✓ built in 33.41s Wrote site to "build" ✔ done ``` Issue-specific production e2e, `CALTERNAL_SERVER_BIN=/mnt/hdd/targets/jobs/analytics-973/debug/calternal-server bun run test:e2e:analytics-overlay-973`: ```text READY Analytics fixture: 14 dated Logs across two weeks and non-empty comparison reports PASS Analytics account/palette/deep-link openers, Escape and focus return, no Tab Bar entry, saved-order migration ``` `cargo clean`: ```text Removed 7237 files, 4.6GiB total ``` No Rust files changed, so Rust clippy/test were not applicable. Full web and shared E2E suites were not run in this job. Performance was not measured because #973 is a UI issue; the branch retains the Analytics benchmark profile. ## For the merge round - `cd apps/web && bun run test` — run the complete web Vitest suite. - `cd apps/web && bun run test:e2e` — run the shared shell E2E suite. Head SHA: `d95137fa9f7b6c54aa1254bccd6da2b0dc095616`. ## Screenshots attached to this issue - [390 Paper](https://git.kayg.org/attachments/a3058557-6409-4042-bb8f-04167b132735) - [390 Tokyo Night](https://git.kayg.org/attachments/f1b3a75e-dc52-435c-ab72-eabd688b6553) - [820 Paper](https://git.kayg.org/attachments/bf24cfd1-17bc-4d38-82a5-d4752e1ea331) - [820 Tokyo Night](https://git.kayg.org/attachments/bd55bcdc-c790-4445-a31b-af844f7218dd) - [1440 Paper](https://git.kayg.org/attachments/85405d23-72ba-484a-824f-a7f2b2d0f77d) - [1440 Tokyo Night](https://git.kayg.org/attachments/093d5b53-71ce-4283-970d-62805a82b413) - [1440 Paper empty state](https://git.kayg.org/attachments/f1a44156-138c-4d1f-8a11-12eaa8ec479d)
Author
Owner

Starting send-back round 3 on job/analytics-973 at d95137fa9f7b6c54aa1254bccd6da2b0dc095616, based on origin/dev d4e7188810a89fb0e8e6b162279917f7e23989f9. I’m tracing the three requested visual/metric fixes and the existing production screenshot workflow before editing.

Starting send-back round 3 on `job/analytics-973` at `d95137fa9f7b6c54aa1254bccd6da2b0dc095616`, based on `origin/dev` `d4e7188810a89fb0e8e6b162279917f7e23989f9`. I’m tracing the three requested visual/metric fixes and the existing production screenshot workflow before editing.
Author
Owner

Findings from the requested phone review and source trace:

  • The summary value used min(var(--type-stat-size), 8cqi) while sharing a line with a floated delta chip. The 390 px screenshots show values split at the space (for example 6h / 20m and 15h / 6m).
  • computeDelta chose direction from the raw difference before rounding its percent. A +1 minute change against 250 minutes therefore rounded to 0% but retained the up arrow. Added counts already use exact integer comparisons; I extended their regression test to check all unchanged Added cells.
  • At phone width the hidden title left a flexible first grid column before the period selector, and the selector was right-aligned. This left Weekly displaced from the leading edge.

I’ve changed these paths and added focused regression checks. I’ll verify the layout assertions in the production screenshot run.

Findings from the requested phone review and source trace: - The summary value used `min(var(--type-stat-size), 8cqi)` while sharing a line with a floated delta chip. The 390 px screenshots show values split at the space (for example `6h` / `20m` and `15h` / `6m`). - `computeDelta` chose direction from the raw difference before rounding its percent. A +1 minute change against 250 minutes therefore rounded to `0%` but retained the up arrow. Added counts already use exact integer comparisons; I extended their regression test to check all unchanged Added cells. - At phone width the hidden title left a flexible first grid column before the period selector, and the selector was right-aligned. This left `Weekly` displaced from the leading edge. I’ve changed these paths and added focused regression checks. I’ll verify the layout assertions in the production screenshot run.
Author
Owner

Built

  • Compact Analytics cards put the delta on its own row and use the shared Title 1 role for the value. Each metric value stays on one line on phones.
  • A percentage that rounds to 0% now uses the neutral direction for every summary metric. Unchanged counts in the Added grid remain → 0; the regression test checks every count tile.
  • The phone period control starts at the header’s leading edge. The action group stays at the trailing edge.
  • The production screenshot test asserts one-line metric values and leading-edge control alignment at 390 px. It captured the six requested viewport/theme combinations and the separate empty state, with macOS platform emulation. All seven PNGs are attached below.

UX gaps closed

Phone metric wrapping, zero-percent directional arrows, and the displaced phone period control.

UX gaps left

None for this send-back round.

Decisions (DESIGN does not specify these details)

  • A percentage that rounds to 0% counts as no change across both delta display forms. This also shows a neutral amount when the percent is 0%.
  • At a stat-row container width below 720 px, the delta gets its own row and the value uses --text-title-1. This keeps the number clear of the delta and follows the existing container-based compact layout.
  • On phones, the period selector uses the leading grid track. The actions use the trailing track below the sheet title.

Files

  • apps/web/src/lib/analytics/delta.ts and delta.test.ts
  • apps/web/src/lib/components/analytics/widgets/StatRow.svelte and StatRow.svelte.test.ts
  • apps/web/src/lib/components/analytics/ActivityCard.svelte and ActivityCard.svelte.test.ts
  • apps/web/src/lib/components/analytics/PeriodSelector.svelte
  • apps/web/src/routes/analytics/[period]/[date]/+page.svelte
  • apps/web/e2e/analytics-overlay-973.mjs

Verification

Focused Vitest command: bunx vitest run src/lib/analytics/delta.test.ts src/lib/components/analytics/widgets/StatRow.svelte.test.ts src/lib/components/analytics/ActivityCard.svelte.test.ts --maxWorkers=2

 RUN  v5.0.1 /home/kayg/Developer/calternal-wt/analytics-973/apps/web

Could not parse CSS stylesheet

 Test Files  3 passed (3)
      Tests  28 passed (28)
   Start at  16:07:48
   Duration  24.28s (transform 74%, environment 13%, import 8%, tests 3%, setup 3%)

  Transform  |component| transforming modules took 20.68s · 69% of tracked time, re-done on every run
             persist transforms across runs with fsModuleCache: true
             learn more: https://vitest.dev/guide/improving-performance#caching-between-reruns

bun run check:

$ node scripts/check-user-storage.mjs && node scripts/check-type-tokens.mjs && node scripts/check-motion-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json
User browser caches use userStorage; only documented device/public-link exceptions remain.
Text sizes and UI shape values use shared role tokens.
UI transitions and animation options use shared motion tokens or documented exceptions.
Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/analytics-973/apps/web
Getting Svelte diagnostics...

svelte-check found 0 errors and 0 warnings

Production web build succeeded:

✓ built in 51.31s
  Wrote site to "build"
  ✔ done

The issue-specific production E2E passed. It checked mode-order migration, account and palette openers, deep links, keyboard and pointer period changes, Escape, focus return, populated charts, the empty state, phone value wrapping, and phone control alignment. It captured:

The screenshot run used a locally built server because /mnt/hdd/targets/shared-server/CURRENT was absent. Cargo output:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 36m 57s

No Rust source changed, so Rust format, Clippy and tests did not apply. Cleanup output:

     Removed 7237 files, 4.6GiB total

Build output in apps/web/build and apps/web/.svelte-kit/output was removed. The web build logged existing vendor use client directive warnings. Vitest also emitted the non-fatal Could not parse CSS stylesheet line above; all 28 focused tests passed.

For the merge round

  • cd apps/web && bun run test — run the full web test suite on the combined branch.

Shared origin update

The one required pre-gate git fetch origin && git merge origin/dev reported Already up to date while origin/dev pointed to d4e7188810a89fb0e8e6b162279917f7e23989f9. During the local server build and screenshot run, the shared origin/dev ref advanced to cfee85c6b11537968aaa0685d1ed1c3ac68a8c3e. The branch now reports 20 commits ahead and 323 behind that ref. I did not fetch or merge again because the owner rule calls for one pre-gate merge and says not to loop. Integrate with the current origin/dev before merging this job.

Commits

  • 7459b845b71288e5ec8edd9c7e7ec763f250f373 — fix compact Analytics metrics and phone controls
  • 29e39d6df2f353842788a6952ee812c5b4776f8f — document comparison and test invariants

Head SHA: 29e39d6df2f353842788a6952ee812c5b4776f8f.

## Built - Compact Analytics cards put the delta on its own row and use the shared Title 1 role for the value. Each metric value stays on one line on phones. - A percentage that rounds to 0% now uses the neutral direction for every summary metric. Unchanged counts in the Added grid remain `→ 0`; the regression test checks every count tile. - The phone period control starts at the header’s leading edge. The action group stays at the trailing edge. - The production screenshot test asserts one-line metric values and leading-edge control alignment at 390 px. It captured the six requested viewport/theme combinations and the separate empty state, with macOS platform emulation. All seven PNGs are attached below. ## UX gaps closed Phone metric wrapping, zero-percent directional arrows, and the displaced phone period control. ## UX gaps left None for this send-back round. ## Decisions (DESIGN does not specify these details) - A percentage that rounds to 0% counts as no change across both delta display forms. This also shows a neutral amount when the percent is 0%. - At a stat-row container width below 720 px, the delta gets its own row and the value uses `--text-title-1`. This keeps the number clear of the delta and follows the existing container-based compact layout. - On phones, the period selector uses the leading grid track. The actions use the trailing track below the sheet title. ## Files - `apps/web/src/lib/analytics/delta.ts` and `delta.test.ts` - `apps/web/src/lib/components/analytics/widgets/StatRow.svelte` and `StatRow.svelte.test.ts` - `apps/web/src/lib/components/analytics/ActivityCard.svelte` and `ActivityCard.svelte.test.ts` - `apps/web/src/lib/components/analytics/PeriodSelector.svelte` - `apps/web/src/routes/analytics/[period]/[date]/+page.svelte` - `apps/web/e2e/analytics-overlay-973.mjs` ## Verification Focused Vitest command: `bunx vitest run src/lib/analytics/delta.test.ts src/lib/components/analytics/widgets/StatRow.svelte.test.ts src/lib/components/analytics/ActivityCard.svelte.test.ts --maxWorkers=2` ```text RUN v5.0.1 /home/kayg/Developer/calternal-wt/analytics-973/apps/web Could not parse CSS stylesheet Test Files 3 passed (3) Tests 28 passed (28) Start at 16:07:48 Duration 24.28s (transform 74%, environment 13%, import 8%, tests 3%, setup 3%) Transform |component| transforming modules took 20.68s · 69% of tracked time, re-done on every run persist transforms across runs with fsModuleCache: true learn more: https://vitest.dev/guide/improving-performance#caching-between-reruns ``` `bun run check`: ```text $ node scripts/check-user-storage.mjs && node scripts/check-type-tokens.mjs && node scripts/check-motion-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json User browser caches use userStorage; only documented device/public-link exceptions remain. Text sizes and UI shape values use shared role tokens. UI transitions and animation options use shared motion tokens or documented exceptions. Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/analytics-973/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` Production web build succeeded: ```text ✓ built in 51.31s Wrote site to "build" ✔ done ``` The issue-specific production E2E passed. It checked mode-order migration, account and palette openers, deep links, keyboard and pointer period changes, Escape, focus return, populated charts, the empty state, phone value wrapping, and phone control alignment. It captured: - [390 Paper](https://git.kayg.org/attachments/f41df19f-5f64-4390-b91b-514dceac05e3) - [390 Tokyo Night](https://git.kayg.org/attachments/45335667-008c-45ae-b10b-6cf814dc0d56) - [820 Paper](https://git.kayg.org/attachments/05793e67-978b-4687-b240-7f2bb8b5de1b) - [820 Tokyo Night](https://git.kayg.org/attachments/c12da6d6-52b9-4bc9-bf61-8987c33ea81f) - [1440 Paper](https://git.kayg.org/attachments/3529c719-d199-4a5a-ae2d-76f7b51a7555) - [1440 Tokyo Night](https://git.kayg.org/attachments/ec2e0acc-50f6-4e33-bc70-70424b1c0230) - [1440 Paper empty state](https://git.kayg.org/attachments/c5bfeebf-e21e-4b3a-9cb6-079a18ff3076) The screenshot run used a locally built server because `/mnt/hdd/targets/shared-server/CURRENT` was absent. Cargo output: ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 36m 57s ``` No Rust source changed, so Rust format, Clippy and tests did not apply. Cleanup output: ```text Removed 7237 files, 4.6GiB total ``` Build output in `apps/web/build` and `apps/web/.svelte-kit/output` was removed. The web build logged existing vendor `use client` directive warnings. Vitest also emitted the non-fatal `Could not parse CSS stylesheet` line above; all 28 focused tests passed. ## For the merge round - `cd apps/web && bun run test` — run the full web test suite on the combined branch. ## Shared origin update The one required pre-gate `git fetch origin && git merge origin/dev` reported `Already up to date` while `origin/dev` pointed to `d4e7188810a89fb0e8e6b162279917f7e23989f9`. During the local server build and screenshot run, the shared `origin/dev` ref advanced to `cfee85c6b11537968aaa0685d1ed1c3ac68a8c3e`. The branch now reports 20 commits ahead and 323 behind that ref. I did not fetch or merge again because the owner rule calls for one pre-gate merge and says not to loop. Integrate with the current `origin/dev` before merging this job. ## Commits - `7459b845b71288e5ec8edd9c7e7ec763f250f373` — fix compact Analytics metrics and phone controls - `29e39d6df2f353842788a6952ee812c5b4776f8f` — document comparison and test invariants Head SHA: `29e39d6df2f353842788a6952ee812c5b4776f8f`.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/calternal#973
No description provided.