Analytics mode: time tracked, activity counts, habits (server-computed), calternal.js dashboard ported #125

Closed
opened 2026-09-25 21:55:19 +00:00 by kayg · 45 comments
Owner

DESIGN §35 (grill rounds 1-2): Analytics is its own mode (4th in the tray, after Calendar/Files/Photos; Ask 5th). First version: time tracked (the calternal.js set: total tracked, per active day, untracked per active day on a 16h waking day, days active, by category donut+bars, daily trend stacked bars, time-of-day heatmap, calendar heatmap, change vs the comparison range), activity counts (files, photos, notes, tasks, events added, with trends), habits (streaks, most active hours); storage next. Computed on the server from the Index, cached per finished day; the browser only draws. Periods day/week/month/quarter/year with a comparison range; deep links /analytics//?vs=. Every number links to the items behind it (e.g. '42 photos' opens Photos filtered to the range). Port the calternal.js dashboard (packages/web/src/routes/analytics, lib/analytics, components/analytics incl. the vendored bklit charts) verbatim where it applies; calternal.js rules: hidden events excluded, moments count as active days with no duration, overlaps merged per day, reversed entries are anomalies with 0 minutes, categories from the first tag segment minus area/.

DESIGN §35 (grill rounds 1-2): Analytics is its own mode (4th in the tray, after Calendar/Files/Photos; Ask 5th). First version: time tracked (the calternal.js set: total tracked, per active day, untracked per active day on a 16h waking day, days active, by category donut+bars, daily trend stacked bars, time-of-day heatmap, calendar heatmap, change vs the comparison range), activity counts (files, photos, notes, tasks, events added, with trends), habits (streaks, most active hours); storage next. Computed on the server from the Index, cached per finished day; the browser only draws. Periods day/week/month/quarter/year with a comparison range; deep links /analytics/<period>/<date>?vs=. Every number links to the items behind it (e.g. '42 photos' opens Photos filtered to the range). Port the calternal.js dashboard (packages/web/src/routes/analytics, lib/analytics, components/analytics incl. the vendored bklit charts) verbatim where it applies; calternal.js rules: hidden events excluded, moments count as active days with no duration, overlaps merged per day, reversed entries are anomalies with 0 minutes, categories from the first tag segment minus area/.
Author
Owner

Starting continuation on job/analytics, based on dev at 7ba2b8011b4cad6862ad8b1a890945395aae3e5c. The branch already has four feature commits and nine pending files from the stopped job. I am reviewing those changes against #125, then I will finish the implementation and run the requested gates.

Starting continuation on `job/analytics`, based on dev at `7ba2b8011b4cad6862ad8b1a890945395aae3e5c`. The branch already has four feature commits and nine pending files from the stopped job. I am reviewing those changes against #125, then I will finish the implementation and run the requested gates.
Author
Owner

Finding from review: activityHref mapped a quarter count to /calendar/month/<quarter-start>, which only showed the first month of a three-month count. Calendar has day/week/month/year views and no quarter or arbitrary-range route. I am changing quarter links to the containing Calendar year, the existing view that includes all three months, and will record this fallback in DESIGN.

Finding from review: `activityHref` mapped a quarter count to `/calendar/month/<quarter-start>`, which only showed the first month of a three-month count. Calendar has day/week/month/year views and no quarter or arbitrary-range route. I am changing quarter links to the containing Calendar year, the existing view that includes all three months, and will record this fallback in DESIGN.
Author
Owner

Production layout sweep finding: it checked 192 screens and exited 1. On Analytics at 390 px, every period and the comparison view fail because both RangeBar pills are 36 px high, and the Calendar tray button's 44 px hit target overlaps the dashboard. The same tray hit-area failure appears across Files, Photos, Notes and Calendar; Calendar Log entry targets also fail 44×44 checks. The merged ModeHeader sweep also reports collapse failures for Files and Calendar. I am tracing the shared tray layer and fixing the Analytics controls before rerunning the layout check.

Production layout sweep finding: it checked 192 screens and exited 1. On Analytics at 390 px, every period and the comparison view fail because both RangeBar pills are 36 px high, and the Calendar tray button's 44 px hit target overlaps the dashboard. The same tray hit-area failure appears across Files, Photos, Notes and Calendar; Calendar Log entry targets also fail 44×44 checks. The merged ModeHeader sweep also reports collapse failures for Files and Calendar. I am tracing the shared tray layer and fixing the Analytics controls before rerunning the layout check.
Author
Owner

Follow-up to the layout finding: after commit 935ef60, the production Analytics layout sweep passed all 28 Analytics screens (checked 28 screens; LAYOUT SWEEP PASSED). The mode tray now paints its hidden-edge fade with pointer-transparent overlays, so a visual fade cannot remove hit testing from visible tab corners. Analytics range pills now use the shared --hit size on coarse pointers. Focused Vitest passed 15 tests; production build passed. Full gates and the remaining adversarial/e2e checks are still running.

Follow-up to the layout finding: after commit 935ef60, the production Analytics layout sweep passed all 28 Analytics screens (`checked 28 screens`; `LAYOUT SWEEP PASSED`). The mode tray now paints its hidden-edge fade with pointer-transparent overlays, so a visual fade cannot remove hit testing from visible tab corners. Analytics range pills now use the shared `--hit` size on coarse pointers. Focused Vitest passed 15 tests; production build passed. Full gates and the remaining adversarial/e2e checks are still running.
Author
Owner

The ModeHeader collapse finding was a sweep timing race: the runtime schedules the scroll-state update in requestAnimationFrame, while the old sweep measured after a fixed wait. The collapse subset now waits for the expected collapsed class and the blur transition to finish before it checks row movement, title size and content position. It passed on desktop and touch routes (checked 0 screens; LAYOUT SWEEP PASSED) after commit a24a1d5. The Analytics-only layout pass remains green. The full layout sweep and final gates are still pending.

The ModeHeader collapse finding was a sweep timing race: the runtime schedules the scroll-state update in requestAnimationFrame, while the old sweep measured after a fixed wait. The collapse subset now waits for the expected collapsed class and the blur transition to finish before it checks row movement, title size and content position. It passed on desktop and touch routes (`checked 0 screens`; `LAYOUT SWEEP PASSED`) after commit a24a1d5. The Analytics-only layout pass remains green. The full layout sweep and final gates are still pending.
Author
Owner

The Analytics browser assertions reached the screenshot phase and passed deep links, every-number links, the empty state, keyboard/picker behavior, 320 px, both 200% widths and RTL. Chart switches had a worst main-thread task of 34 ms CPU. One full-height screenshot reached Playwright's default 30-second screenshot timeout after fonts loaded; the e2e capture now allows 120 seconds for pages up to 8000px on the loaded shared host. The test is being rerun with screenshots and the 10k-entry warm-year check.

The Analytics browser assertions reached the screenshot phase and passed deep links, every-number links, the empty state, keyboard/picker behavior, 320 px, both 200% widths and RTL. Chart switches had a worst main-thread task of 34 ms CPU. One full-height screenshot reached Playwright's default 30-second screenshot timeout after fonts loaded; the e2e capture now allows 120 seconds for pages up to 8000px on the loaded shared host. The test is being rerun with screenshots and the 10k-entry warm-year check.
Author
Owner

The seeded Analytics e2e returned HTTP 500 while uploading the 2025-11-25 Daily Note, after 328 daily-note uploads. The request used the local Tus upload API. The run stopped before the 10k warm-year timing check. I am investigating with the latest merged Flat Home rule and better error diagnostics; the test server removed its temporary data and did not preserve the response body or server logs.

The seeded Analytics e2e returned HTTP 500 while uploading the 2025-11-25 Daily Note, after 328 daily-note uploads. The request used the local Tus upload API. The run stopped before the 10k warm-year timing check. I am investigating with the latest merged Flat Home rule and better error diagnostics; the test server removed its temporary data and did not preserve the response body or server logs.
Author
Owner

Merge finding: rebuilding calternal-server after the dev merge failed in Calendar with E0425 (state was not in scope at view.rs:973) and E0603 (items.rs imported private parse_date). The Analytics merge had generalized the day projection to accept Db, but the merged attachment resolver still referenced CalendarState. I changed the resolver to use the same Db handle and made the existing strict date parser crate-visible for Calendar items. cargo test -p calternal-plugin-calendar is running to validate this repair before I continue the Analytics e2e.

Merge finding: rebuilding `calternal-server` after the `dev` merge failed in Calendar with E0425 (`state` was not in scope at `view.rs:973`) and E0603 (`items.rs` imported private `parse_date`). The Analytics merge had generalized the day projection to accept `Db`, but the merged attachment resolver still referenced `CalendarState`. I changed the resolver to use the same `Db` handle and made the existing strict date parser crate-visible for Calendar items. `cargo test -p calternal-plugin-calendar` is running to validate this repair before I continue the Analytics e2e.
Author
Owner

Analytics e2e finding: the 20-second .dashboard wait failed during the 200% viewport check after the deep-link, keyboard, accessibility and chart-switch checks passed. Sanitized server output showed an Analytics Index read taking 8.1 seconds, connection acquisition taking up to 8.1 seconds, and the host load average at 40.51 / 42.33 / 41.83. The run also logged full semantic-search notifications; calternal-embed documents that periodic reconcile recovers a full queue. I am extending the browser settle timeout to 120 seconds and adding page/API diagnostics, then I will rerun the checks.

Analytics e2e finding: the 20-second `.dashboard` wait failed during the 200% viewport check after the deep-link, keyboard, accessibility and chart-switch checks passed. Sanitized server output showed an Analytics Index read taking 8.1 seconds, connection acquisition taking up to 8.1 seconds, and the host load average at 40.51 / 42.33 / 41.83. The run also logged full semantic-search notifications; `calternal-embed` documents that periodic reconcile recovers a full queue. I am extending the browser settle timeout to 120 seconds and adding page/API diagnostics, then I will rerun the checks.
Author
Owner

Startup finding: the next Analytics e2e attempt exited after 120 seconds because startServer() did not see the setup token. It left the server child (PID 314930) and the test data directory running after the parent exited. I terminated the child. I am fixing the shared e2e harness to keep one bounded startup deadline for the token and /readyz, include sanitized startup output on failure, and always stop the child and remove its test data when startup fails. The host load average was about 50 during this attempt.

Startup finding: the next Analytics e2e attempt exited after 120 seconds because `startServer()` did not see the setup token. It left the server child (PID 314930) and the test data directory running after the parent exited. I terminated the child. I am fixing the shared e2e harness to keep one bounded startup deadline for the token and `/readyz`, include sanitized startup output on failure, and always stop the child and remove its test data when startup fails. The host load average was about 50 during this attempt.
Author
Owner

Analytics e2e finding: the production browser checks and 25 screenshot captures passed, and the 2025 fixture indexed all 10,220 Log entries. The warm-year check failed its 200 ms median target: median 2,035.8 ms, p90 2,717.6 ms; Server-Timing reported analytics;dur=1653.3;desc="hits=0 computed=791 stored=0 spans=3". The run then repeated seven warm reads and still reported no cached-day hits. I am tracing the cache invalidation/store path and will add a regression test before rerunning the benchmark.

Analytics e2e finding: the production browser checks and 25 screenshot captures passed, and the 2025 fixture indexed all 10,220 Log entries. The warm-year check failed its 200 ms median target: median 2,035.8 ms, p90 2,717.6 ms; Server-Timing reported `analytics;dur=1653.3;desc="hits=0 computed=791 stored=0 spans=3"`. The run then repeated seven warm reads and still reported no cached-day hits. I am tracing the cache invalidation/store path and will add a regression test before rerunning the benchmark.
Author
Owner

Cache-starvation finding: the 10k report computes 791 finished days, but stored=0 because the user's analytics epoch moves while a report computes. Notes reconcile unconditionally deleted and reinserted every note_calendar_logs projection; the Analytics Note trigger also bumped the epoch for unchanged upserts. I made Log projection indexing skip identical rows and added a WHEN guard to the Analytics trigger, plus a regression assertion that a second unchanged Daily Note reconcile leaves the epoch unchanged. The focused Analytics regression is compiling on the shared host; cargo fmt --all and git diff --check pass. This preserves visible/indexed data and timezone handling while removing invalidation from a no-op reconcile.

Cache-starvation finding: the 10k report computes 791 finished days, but `stored=0` because the user's analytics epoch moves while a report computes. Notes reconcile unconditionally deleted and reinserted every `note_calendar_logs` projection; the Analytics Note trigger also bumped the epoch for unchanged upserts. I made Log projection indexing skip identical rows and added a `WHEN` guard to the Analytics trigger, plus a regression assertion that a second unchanged Daily Note reconcile leaves the epoch unchanged. The focused Analytics regression is compiling on the shared host; `cargo fmt --all` and `git diff --check` pass. This preserves visible/indexed data and timezone handling while removing invalidation from a no-op reconcile.
Author
Owner

Regression check update: the repeat-reconcile test exposed that three unchanged Log rows still get replaced (epoch 4 → 10) despite the Note trigger guard. This confirms the remaining churn is in the projection equality check; no content or counts changed. I am instrumenting field-name differences only (without Note content) to find the mismatch, then I will remove the diagnostic and rerun the focused test.

Regression check update: the repeat-reconcile test exposed that three unchanged Log rows still get replaced (epoch 4 → 10) despite the Note trigger guard. This confirms the remaining churn is in the projection equality check; no content or counts changed. I am instrumenting field-name differences only (without Note content) to find the mismatch, then I will remove the diagnostic and rerun the focused test.
Author
Owner

The field-only trace isolated the projection mismatch: all Analytics data fields matched; only timezone differed. Log zones are Calendar display metadata and do not affect Analytics day, time, tag, or hidden-entry summaries. The indexer now updates zone metadata in place when it is the only difference, and the Analytics Log update trigger ignores zone-only changes. The temporary diagnostic is removed. The repeat-reconcile regression is running again.

The field-only trace isolated the projection mismatch: all Analytics data fields matched; only `timezone` differed. Log zones are Calendar display metadata and do not affect Analytics day, time, tag, or hidden-entry summaries. The indexer now updates zone metadata in place when it is the only difference, and the Analytics Log update trigger ignores zone-only changes. The temporary diagnostic is removed. The repeat-reconcile regression is running again.
Author
Owner

The repeat-reconcile regression now passes after accounting for timezone-only repairs. The Analytics crate suite passes: 29 passed, 0 failed, including the 10k warm-cache unit budget. Fix committed as 4a0e31b50be4baa40061947cd6471bb2637c2a5e. I am merging the current local dev branch now, then I will run the final workspace gates and adversarial checks.

The repeat-reconcile regression now passes after accounting for timezone-only repairs. The Analytics crate suite passes: 29 passed, 0 failed, including the 10k warm-cache unit budget. Fix committed as `4a0e31b50be4baa40061947cd6471bb2637c2a5e`. I am merging the current local `dev` branch now, then I will run the final workspace gates and adversarial checks.
Author
Owner

Merged-dev build finding: the first real-server adversarial run stopped before probes because files::index::record_once added an unchanged-row early return as Ok(()) even though its Result success value is bool. The surrounding contract uses false for a changed fingerprint and true for a stable file, so I corrected the no-op return to Ok(true) and added a repeated-index regression test. The runner cleaned its private server work directory after the compile failure; no probes or API findings were produced in that run.

Merged-dev build finding: the first real-server adversarial run stopped before probes because `files::index::record_once` added an unchanged-row early return as `Ok(())` even though its `Result` success value is `bool`. The surrounding contract uses `false` for a changed fingerprint and `true` for a stable file, so I corrected the no-op return to `Ok(true)` and added a repeated-index regression test. The runner cleaned its private server work directory after the compile failure; no probes or API findings were produced in that run.
Author
Owner

Merged-dev build repair committed as 1109748cbe5ac209eb5f945165060bc0d8f84438. The focused regression passes: the second unchanged index attempt succeeds and keeps the same item ID. Output: test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 88 filtered out. The Analytics crate suite also remains green (29 passed). I am restarting the full adversarial runner against the merged tree.

Merged-dev build repair committed as `1109748cbe5ac209eb5f945165060bc0d8f84438`. The focused regression passes: the second unchanged index attempt succeeds and keeps the same item ID. Output: `test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 88 filtered out`. The Analytics crate suite also remains green (29 passed). I am restarting the full adversarial runner against the merged tree.
Author
Owner

Adversarial load note (merged local server, 127.0.0.1:9891): /readyz returned 200 during the round, but the broad write probes logged request timeouts for Calendar Event from Log, Journal Log create, Journal conditional DELETE, and multiple Saved Search create-storm requests. Successful requests in the same run took 5–27 seconds. Host load average was 55.73 / 52.49 / 46.78 with at least seven other adversarial run scripts and multiple local servers active across worktrees. I am recording these non-SLOW timeouts here per the job rule; this saturated-host run cannot establish an Analytics correctness failure. I will capture the completed run summary and any Analytics-section results before closing out.

Adversarial load note (merged local server, 127.0.0.1:9891): `/readyz` returned 200 during the round, but the broad write probes logged request timeouts for Calendar Event from Log, Journal Log create, Journal conditional DELETE, and multiple Saved Search create-storm requests. Successful requests in the same run took 5–27 seconds. Host load average was 55.73 / 52.49 / 46.78 with at least seven other adversarial run scripts and multiple local servers active across worktrees. I am recording these non-SLOW timeouts here per the job rule; this saturated-host run cannot establish an Analytics correctness failure. I will capture the completed run summary and any Analytics-section results before closing out.
Author
Owner

The adversarial sync section reproduced existing #88 data loss: after its test deleted and recreated the local sync root, remote keep/k0.txt and keep/k1.txt disappeared. I filed this evidence on #88, which already documents the root-deletion bug and a fix on the unmerged job/sync-load branch. This is outside Analytics and remains for the Sync owner. The full adversarial run is continuing to its Analytics section.

The adversarial sync section reproduced existing #88 data loss: after its test deleted and recreated the local sync root, remote `keep/k0.txt` and `keep/k1.txt` disappeared. I filed this evidence on #88, which already documents the root-deletion bug and a fix on the unmerged `job/sync-load` branch. This is outside Analytics and remains for the Sync owner. The full adversarial run is continuing to its Analytics section.
Author
Owner

Analytics-specific adversarial result: the endpoint completed its hostile-parameter, isolation, tag-bound, cross-midnight, and cache probes without a reported status/consistency error. During the final 90-read/30-write concurrency storm, three requests timed out; successful Log writes took 20.5–29.5 seconds. The server remained alive and the host load average was 45.80 / 46.60 / 47.96 with several concurrent adversarial servers. This non-SLOW timeout is recorded for follow-up; the probe's post-storm consistency checks are still running.

Analytics-specific adversarial result: the endpoint completed its hostile-parameter, isolation, tag-bound, cross-midnight, and cache probes without a reported status/consistency error. During the final 90-read/30-write concurrency storm, three requests timed out; successful Log writes took 20.5–29.5 seconds. The server remained alive and the host load average was 45.80 / 46.60 / 47.96 with several concurrent adversarial servers. This non-SLOW timeout is recorded for follow-up; the probe's post-storm consistency checks are still running.
Author
Owner

Follow-up from the live adversarial probes: the Analytics storm completed its post-storm cache consistency checks with no stale/missing/disagreeing report. Journal race also passed its content checks: 42 entries found, 30 creates answered 201, 10 timed out (6 landed exactly once), 5 conditional uploads succeeded; only SLOW/no-response-under-load warnings were reported. Round 2 is continuing through the remaining shared API sections.

Follow-up from the live adversarial probes: the Analytics storm completed its post-storm cache consistency checks with no stale/missing/disagreeing report. Journal race also passed its content checks: 42 entries found, 30 creates answered 201, 10 timed out (6 landed exactly once), 5 conditional uploads succeeded; only SLOW/no-response-under-load warnings were reported. Round 2 is continuing through the remaining shared API sections.
Author
Owner

I checked the remaining non-SLOW adversarial findings against existing tickets. The shared Photos timeline miss is now recorded on #188 (empty days after an owner photo share and member opt-in); the local root-deletion case is on #88, and the timezone Search date: miss is on #141. These are outside Analytics. The full runner has completed Round 2 and is finishing the restart probe; its output showed Analytics consistency checks passing.

I checked the remaining non-SLOW adversarial findings against existing tickets. The shared Photos timeline miss is now recorded on #188 (empty `days` after an owner photo share and member opt-in); the local root-deletion case is on #88, and the timezone Search `date:` miss is on #141. These are outside Analytics. The full runner has completed Round 2 and is finishing the restart probe; its output showed Analytics consistency checks passing.
Author
Owner

Analytics mode is implemented on job/analytics and merged with the current local dev tip. No push or deploy was performed.

Head: f014534f34ea7977d692ec1e4963a872feb37807

Built:

  • Server-computed Analytics reports, finished-day caching and invalidation, activity links, range comparison, habits, charts, keyboard/screen-reader fallbacks, responsive layouts, and deep links from the Analytics tray.
  • Fixed cache starvation found by the 10k-entry production run. Daily Note reconciliation now leaves identical projections unchanged; timezone-only log repair updates the row in place. Analytics update triggers invalidate only fields that affect the report.
  • Fixed the Files index no-op return type surfaced by merging dev; added regression coverage.
  • E2E performance output now reports cache data for all seven samples.

Relevant commits: 4a0e31b5, 1109748c, 599c3e1d, merge f014534f.

Gates:

  • cargo fmt --check: exit 0, no output.
  • cargo clippy --all-targets -- -D warnings: passed; output was Finished dev profile [unoptimized + debuginfo] target(s) in 7m 25s.
  • Full cargo test retry with RUST_TEST_THREADS=4: exit 0. Output began Finished test profile [unoptimized + debuginfo] target(s) in 3m 34s; Analytics output was test result: ok. 29 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 8.72s. Across the 69 result lines: 1,204 passed, 0 failed, 12 ignored. The initial high-contention run missed two collaboration five-second save deadlines; both focused retries passed (4.16s and 3.25s), and both passed in the full retry.
  • bun run check: svelte-check found 0 errors and 0 warnings.
  • bun run test: Test Files 66 passed (66) and Tests 520 passed (520); Vitest printed jsdom CSS/scrollTo warnings.
  • Production real-server browser E2E passed its route, deep-link, links, empty-state, keyboard, screen-reader, responsive, and RTL checks and captured 25 screenshots. Screenshots are available at /home/kayg/Developer/calternal-wt/analytics-review.

Known gap: The production 10,220-entry warm-year E2E indexed all entries and retained 790 of 791 day summaries on every warm sample, but measured year of 10220 entries (warm): median 382.2 ms, p90 894.3 ms and failed the <200 ms check. The run overlapped several server/browser builds and host load was about 42–53. The Rust 10k warm-cache test passed. Recheck the wall-clock target on a quiet host; no cache-starvation symptom remained.

Adversarial round: Both rounds completed with the server alive; hostile-byte findings and CSP reports were zero, and the Analytics cache remained consistent after its concurrency storm. Three Analytics storm requests timed out under shared load. Other non-SLOW cross-plugin findings were reported on #88 (sync root deletion), #141 (Search timezone date filter), and #188 (shared Photos timeline); load-heavy SLOW observations were recorded separately.

Decision not specified in DESIGN §35: Treat only report-relevant projection changes as Analytics invalidations. Normalize timezone-only log changes in place so routine reconciliation preserves completed-day cache rows.

Analytics mode is implemented on `job/analytics` and merged with the current local `dev` tip. No push or deploy was performed. Head: `f014534f34ea7977d692ec1e4963a872feb37807` Built: - Server-computed Analytics reports, finished-day caching and invalidation, activity links, range comparison, habits, charts, keyboard/screen-reader fallbacks, responsive layouts, and deep links from the Analytics tray. - Fixed cache starvation found by the 10k-entry production run. Daily Note reconciliation now leaves identical projections unchanged; timezone-only log repair updates the row in place. Analytics update triggers invalidate only fields that affect the report. - Fixed the Files index no-op return type surfaced by merging `dev`; added regression coverage. - E2E performance output now reports cache data for all seven samples. Relevant commits: `4a0e31b5`, `1109748c`, `599c3e1d`, merge `f014534f`. Gates: - `cargo fmt --check`: exit 0, no output. - `cargo clippy --all-targets -- -D warnings`: passed; output was `Finished `dev` profile [unoptimized + debuginfo] target(s) in 7m 25s`. - Full `cargo test` retry with `RUST_TEST_THREADS=4`: exit 0. Output began `Finished `test` profile [unoptimized + debuginfo] target(s) in 3m 34s`; Analytics output was `test result: ok. 29 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 8.72s`. Across the 69 result lines: 1,204 passed, 0 failed, 12 ignored. The initial high-contention run missed two collaboration five-second save deadlines; both focused retries passed (4.16s and 3.25s), and both passed in the full retry. - `bun run check`: `svelte-check found 0 errors and 0 warnings`. - `bun run test`: `Test Files 66 passed (66)` and `Tests 520 passed (520)`; Vitest printed jsdom CSS/scrollTo warnings. - Production real-server browser E2E passed its route, deep-link, links, empty-state, keyboard, screen-reader, responsive, and RTL checks and captured 25 screenshots. Screenshots are available at `/home/kayg/Developer/calternal-wt/analytics-review`. Known gap: The production 10,220-entry warm-year E2E indexed all entries and retained 790 of 791 day summaries on every warm sample, but measured `year of 10220 entries (warm): median 382.2 ms, p90 894.3 ms` and failed the `<200 ms` check. The run overlapped several server/browser builds and host load was about 42–53. The Rust 10k warm-cache test passed. Recheck the wall-clock target on a quiet host; no cache-starvation symptom remained. Adversarial round: Both rounds completed with the server alive; hostile-byte findings and CSP reports were zero, and the Analytics cache remained consistent after its concurrency storm. Three Analytics storm requests timed out under shared load. Other non-SLOW cross-plugin findings were reported on #88 (sync root deletion), #141 (Search timezone date filter), and #188 (shared Photos timeline); load-heavy SLOW observations were recorded separately. Decision not specified in DESIGN §35: Treat only report-relevant projection changes as Analytics invalidations. Normalize timezone-only log changes in place so routine reconciliation preserves completed-day cache rows.
Author
Owner

Claude review of analytics-year-paper-1440.png (not merging yet):

  1. Two headers: 'Analytics ⌄' above 'Analytics 2026'. One header row per mode (DESIGN §34/§38): the title is the sub-view menu; the range lives in the controls row. Remove the duplicate.
  2. BUG: 'Daily trend' and 'Time of day' charts render empty (axes/labels only) although the other cards have data. Fix and add an e2e assertion that each chart draws marks when data exists.
  3. Every stat card shows a '—' delta. When there is no comparison value, show nothing (no placeholder dash); when 'vs Last year' has data, show the signed delta with the theme's up/down colours.
  4. The 382 ms warm median vs 200 ms threshold under load: re-measure when the host is quieter; not a merge blocker.
    Re-take screenshots (year, month, week; light and dark; 1440 and 390) after the fixes.
Claude review of analytics-year-paper-1440.png (not merging yet): 1. Two headers: 'Analytics ⌄' above 'Analytics 2026'. One header row per mode (DESIGN §34/§38): the title is the sub-view menu; the range lives in the controls row. Remove the duplicate. 2. BUG: 'Daily trend' and 'Time of day' charts render empty (axes/labels only) although the other cards have data. Fix and add an e2e assertion that each chart draws marks when data exists. 3. Every stat card shows a '—' delta. When there is no comparison value, show nothing (no placeholder dash); when 'vs Last year' has data, show the signed delta with the theme's up/down colours. 4. The 382 ms warm median vs 200 ms threshold under load: re-measure when the host is quieter; not a merge blocker. Re-take screenshots (year, month, week; light and dark; 1440 and 390) after the fixes.
Author
Owner

Starting continuation on branch job/analytics at f014534f34ea7977d692ec1e4963a872feb37807. The branch's merge base with dev is e976bff6c74b3131266b3baeb33ab318edccd46e; current dev is 116cd780b2b5c175e4728ed229965d3f790fb8df. I am tracing and fixing the four latest review points, then I will capture production-build screenshots, merge dev, run the requested gates, and report the exact output.

Starting continuation on branch `job/analytics` at `f014534f34ea7977d692ec1e4963a872feb37807`. The branch's merge base with `dev` is `e976bff6c74b3131266b3baeb33ab318edccd46e`; current `dev` is `116cd780b2b5c175e4728ed229965d3f790fb8df`. I am tracing and fixing the four latest review points, then I will capture production-build screenshots, merge `dev`, run the requested gates, and report the exact output.
Author
Owner

Finding and fix (commit 8fe78324af45addd8abdcc10959f8d3107ff0d4f): The time-tracked stat cards inferred comparison availability from event_count; this hid deltas when the server reported comparison data with zero Log entries. Missing comparisons also rendered an em dash. The Added card rendered the previous count (for example ↑ 1 when the current value was 4 and the prior value was 1), rather than the signed difference. The UI now uses comparison_has_data, omits the visible delta when that flag is false, retains a screen-reader-only no-data explanation, and shows actual count differences with theme tones. Evidence: the focused analytics Vitest run reports Test Files 3 passed (3) and Tests 24 passed (24); bun run check reports svelte-check found 0 errors and 0 warnings.

Decision not specified in DESIGN §35: for Added counts, an increase uses --accent, a decrease uses --now, and no change uses --muted. This keeps direction clear without implying that a particular count is inherently good or bad.

Finding and fix (commit `8fe78324af45addd8abdcc10959f8d3107ff0d4f`): The time-tracked stat cards inferred comparison availability from `event_count`; this hid deltas when the server reported comparison data with zero Log entries. Missing comparisons also rendered an em dash. The Added card rendered the previous count (for example `↑ 1` when the current value was 4 and the prior value was 1), rather than the signed difference. The UI now uses `comparison_has_data`, omits the visible delta when that flag is false, retains a screen-reader-only no-data explanation, and shows actual count differences with theme tones. Evidence: the focused analytics Vitest run reports `Test Files 3 passed (3)` and `Tests 24 passed (24)`; `bun run check` reports `svelte-check found 0 errors and 0 warnings`. Decision not specified in DESIGN §35: for Added counts, an increase uses `--accent`, a decrease uses `--now`, and no change uses `--muted`. This keeps direction clear without implying that a particular count is inherently good or bad.
Author
Owner

Finding and fix (commit 91c49f0074cd94eb213f6fb8bd0cf757028a52f7): The route rendered AnalyticsHeader below the shared ModeHeader, which rendered the title and period menu from usePageChrome. The duplicate route header and its unused period-selector wrapper are removed. The shared mode title remains the period menu, RangeBar remains the range-control row, and Copy link remains in the shared ⋯ action menu. The regression test first failed on <AnalyticsHeader> in the route; after the change, BklitAnalytics.test.ts reports Test Files 1 passed (1) and Tests 10 passed (10). The e2e now checks the single-header/range-row state, uses the shared menu for Copy link and keyboard period changes, and captures only the requested week/month/year views.

Finding and fix (commit `91c49f0074cd94eb213f6fb8bd0cf757028a52f7`): The route rendered `AnalyticsHeader` below the shared `ModeHeader`, which rendered the title and period menu from `usePageChrome`. The duplicate route header and its unused period-selector wrapper are removed. The shared mode title remains the period menu, `RangeBar` remains the range-control row, and Copy link remains in the shared ⋯ action menu. The regression test first failed on `<AnalyticsHeader>` in the route; after the change, `BklitAnalytics.test.ts` reports `Test Files 1 passed (1)` and `Tests 10 passed (10)`. The e2e now checks the single-header/range-row state, uses the shared menu for Copy link and keyboard period changes, and captures only the requested week/month/year views.
Author
Owner

Checkpoint E2E review found two regression-fixture defects before rerun: the year-delta assertion collected its tone classes into ones but then read tones, so the new check would throw; and Daily Note uploads still targeted Notes/Journal, contrary to DESIGN §40's flat Notes/ layout. I am correcting the property and moving those fixtures directly under Notes/, then I will rerun the real-server production E2E and screenshots.

Checkpoint E2E review found two regression-fixture defects before rerun: the year-delta assertion collected its tone classes into `ones` but then read `tones`, so the new check would throw; and Daily Note uploads still targeted `Notes/Journal`, contrary to DESIGN §40's flat `Notes/` layout. I am correcting the property and moving those fixtures directly under `Notes/`, then I will rerun the real-server production E2E and screenshots.
Author
Owner

Merged-dev web check found an extra closing brace in the combined Analytics/Ask mode registry (apps/web/src/lib/navigation.ts:125), introduced while preserving both modes across the conflict. I removed it; rerunning bun run check now.

Merged-dev web check found an extra closing brace in the combined Analytics/Ask mode registry (`apps/web/src/lib/navigation.ts:125`), introduced while preserving both modes across the conflict. I removed it; rerunning `bun run check` now.
Author
Owner

The production E2E failed its bounded 120-second Analytics readiness check after uploading the Daily Note fixtures directly to Notes/: current and prior-year Daily Notes are indexed remained false. The test server had 102 flat Daily Note files and six Log entries from the journal API. Evidence in crates/plugins/notes/src/store.rs (journal_path_date) and the journal handlers shows Daily Note projections and writes still recognize/create Notes/Journal/. This conflicts with DESIGN §40. Changing that would alter Notes behavior outside this Analytics job, so I am recording it as a Notes follow-up and will use test-only fixtures in the currently indexed path to complete Analytics coverage.

The production E2E failed its bounded 120-second Analytics readiness check after uploading the Daily Note fixtures directly to `Notes/`: `current and prior-year Daily Notes are indexed` remained false. The test server had 102 flat Daily Note files and six Log entries from the journal API. Evidence in `crates/plugins/notes/src/store.rs` (`journal_path_date`) and the journal handlers shows Daily Note projections and writes still recognize/create `Notes/Journal/`. This conflicts with DESIGN §40. Changing that would alter Notes behavior outside this Analytics job, so I am recording it as a Notes follow-up and will use test-only fixtures in the currently indexed path to complete Analytics coverage.
Author
Owner

The real-server Analytics E2E passes route, deep-link, accessibility, responsive, comparison-delta, and painted-chart checks, and captures the requested screenshots. Its chart-switch CPU gate still fails twice: year→quarter 78 ms and week→year 56 ms against 50 ms; quarter→month was 8 ms and month→week had no task. I am tracing the recorded main-thread tasks before attributing this to shared-host load or changing the chart code.

The real-server Analytics E2E passes route, deep-link, accessibility, responsive, comparison-delta, and painted-chart checks, and captures the requested screenshots. Its chart-switch CPU gate still fails twice: year→quarter 78 ms and week→year 56 ms against 50 ms; quarter→month was 8 ms and month→week had no task. I am tracing the recorded main-thread tasks before attributing this to shared-host load or changing the chart code.
Author
Owner

The stylesheet regression test first passed under the test implementation, but bun run check identified that the unit project defaults to Node and has no @types/jsdom. I changed only this test to the project's jsdom environment; the focused test now reports Test Files 1 passed (1), Tests 11 passed (11), and bun run check reports svelte-check found 0 errors and 0 warnings. No dependency was added.

The stylesheet regression test first passed under the test implementation, but `bun run check` identified that the unit project defaults to Node and has no `@types/jsdom`. I changed only this test to the project's jsdom environment; the focused test now reports `Test Files 1 passed (1)`, `Tests 11 passed (11)`, and `bun run check` reports `svelte-check found 0 errors and 0 warnings`. No dependency was added.
Author
Owner

Production E2E update after moving the shared Bklit CSS to one style element per document: route, accessibility, responsive, delta, and painted-chart checks passed. Three chart switches had no task over 50 ms; week→year still measured 70 ms CPU / 79 ms wall against 50 ms. The refreshed 26 screenshots were captured. I am collecting a focused trace breakdown before deciding whether more chart work is justified.

Production E2E update after moving the shared Bklit CSS to one style element per document: route, accessibility, responsive, delta, and painted-chart checks passed. Three chart switches had no task over 50 ms; week→year still measured 70 ms CPU / 79 ms wall against 50 ms. The refreshed 26 screenshots were captured. I am collecting a focused trace breakdown before deciding whether more chart work is justified.
Author
Owner

Trace follow-up: after the CSS deduplication, the week→year switch measured 70 ms CPU / 79 ms wall in the screenshot run. Two no-screenshot traces on the shared host measured year→quarter at 63–99 ms CPU and week→year at 79–90 ms CPU. The longest trace events were React scheduler work in the production bundle (one 63 ms FunctionCall) and browser Layout (72 ms CPU); they did not identify CSS matching as the dominant remaining cost. Quarter→month and month→week stayed below 30 ms CPU. One E2E retry first stopped at virtual-passkey sign-in (401 CredentialPossibleCompromise); the next two runs reached all Analytics checks. The refreshed run captured 26 screenshots. I removed trace-only logging; the 50 ms switch target remains a known performance gap under current shared-host load.

Trace follow-up: after the CSS deduplication, the week→year switch measured 70 ms CPU / 79 ms wall in the screenshot run. Two no-screenshot traces on the shared host measured year→quarter at 63–99 ms CPU and week→year at 79–90 ms CPU. The longest trace events were React scheduler work in the production bundle (one 63 ms FunctionCall) and browser Layout (72 ms CPU); they did not identify CSS matching as the dominant remaining cost. Quarter→month and month→week stayed below 30 ms CPU. One E2E retry first stopped at virtual-passkey sign-in (401 CredentialPossibleCompromise); the next two runs reached all Analytics checks. The refreshed run captured 26 screenshots. I removed trace-only logging; the 50 ms switch target remains a known performance gap under current shared-host load.
Author
Owner

Analytics continuation completed on job/analytics; no push or deploy was performed. The local dev tip (2527f0d0) was merged before final gates.

Head: 7cdaf67df3229c5e2fcb1ebb1a607808bf7de085

Commits from this continuation:

  • f13f7aad — fix the E2E year-delta assertion to read its collected tone classes.
  • 7cdaf67d — put the shared Bklit stylesheet in one style element per owning document; add an idempotence and per-document isolation test.
  • Merge 6cbfc6b9 and follow-up 6bced616 retain the Analytics and Ask modes and close the combined mode registry.

Built in this issue: server-computed Analytics reports and finished-day caching; period ranges and comparison; activity links and deep links; habits and charts; accessible chart marks and tables; responsive layouts; shared mode header and empty states. The earlier cache, Calendar, Files, Notes, and adversarial findings are detailed in the progress comments above.

Files: crates/plugins/analytics/; apps/web/src/lib/analytics/; apps/web/src/lib/components/analytics/; apps/web/src/routes/analytics/; apps/web/e2e/analytics.mjs and the shared E2E harness; API/schema files (contracts/openapi.json, packages/api-client/src/generated.ts); server wiring; and merged-dev fixes in navigation, Calendar, Files, and Notes. This continuation changed apps/web/e2e/analytics.mjs, apps/web/src/lib/components/analytics/BklitAnalytics.tsx, and apps/web/src/lib/components/analytics/BklitAnalytics.test.ts.

Final gates (after the dev merge):

  • cargo fmt --check: exit 0; no output.
  • cargo clippy --all-targets -- -D warnings: exit 0. Exact output: Finished dev profile [unoptimized + debuginfo] target(s) in 11m 51s.
  • cargo test (RUST_TEST_THREADS=4): exit 0. Exact output: Finished test profile [unoptimized + debuginfo] target(s) in 5m 25s. Across 69 suites: 1,217 passed, 0 failed, 12 ignored.
  • bun run build: ✓ built in 48.28s; Vite printed existing use client module-directive warnings.
  • bun run check: svelte-check found 0 errors and 0 warnings.
  • bun run test: Test Files 68 passed (68); Tests 529 passed (529). Vitest also printed jsdom CSS parse and scrollTo() warnings.

Production real-server E2E passed its route, deep-link, count-link, empty-state, keyboard, screen-reader, delta, painted-chart, responsive, and RTL checks. It captured 26 screenshots at /home/kayg/Developer/calternal-wt/analytics-review. The separate 50 ms chart-switch CPU assertion remains over budget in repeated runs: the screenshot run measured week→year at 70 ms; trace runs measured 63–99 ms React scheduler work and 72 ms layout. One passkey setup retry returned 401 before Analytics; the next two runs reached the Analytics checks. The prior 10,220-entry warm-year check remains at 382.2 ms median / 894.3 ms p90 under shared-host load and was not repeated here.

Known gap: E2E Daily Note fixtures under flat Notes/ did not index. The current Notes plugin still expects Notes/Journal/, contrary to DESIGN §40. Analytics fixtures use the currently indexed path; the Notes behavior needs its own follow-up.

Decisions not specified in DESIGN:

  • Quarter activity links open the containing Calendar year because Calendar has no quarter or arbitrary-range route.
  • Added-count increases use the accent tone, decreases use --now, and no change uses muted; count direction does not imply good or bad.
  • Cache invalidation follows Analytics-relevant fields only; timezone-only Log projection repairs update metadata in place.
  • Chart CSS lives once in each owning Document for the lifetime of that document; the test verifies idempotence and separate-document installation.
  • This issue leaves the flat-Notes indexing correction to the Notes owner and keeps Analytics fixtures on Notes/Journal.

Cleanup: cargo clean output was Removed 18208 files, 14.1GiB total; apps/web/build and .svelte-kit/output were removed. The worktree is clean.

Analytics continuation completed on `job/analytics`; no push or deploy was performed. The local `dev` tip (`2527f0d0`) was merged before final gates. Head: `7cdaf67df3229c5e2fcb1ebb1a607808bf7de085` Commits from this continuation: - `f13f7aad` — fix the E2E year-delta assertion to read its collected tone classes. - `7cdaf67d` — put the shared Bklit stylesheet in one style element per owning document; add an idempotence and per-document isolation test. - Merge `6cbfc6b9` and follow-up `6bced616` retain the Analytics and Ask modes and close the combined mode registry. Built in this issue: server-computed Analytics reports and finished-day caching; period ranges and comparison; activity links and deep links; habits and charts; accessible chart marks and tables; responsive layouts; shared mode header and empty states. The earlier cache, Calendar, Files, Notes, and adversarial findings are detailed in the progress comments above. Files: `crates/plugins/analytics/`; `apps/web/src/lib/analytics/`; `apps/web/src/lib/components/analytics/`; `apps/web/src/routes/analytics/`; `apps/web/e2e/analytics.mjs` and the shared E2E harness; API/schema files (`contracts/openapi.json`, `packages/api-client/src/generated.ts`); server wiring; and merged-dev fixes in navigation, Calendar, Files, and Notes. This continuation changed `apps/web/e2e/analytics.mjs`, `apps/web/src/lib/components/analytics/BklitAnalytics.tsx`, and `apps/web/src/lib/components/analytics/BklitAnalytics.test.ts`. Final gates (after the dev merge): - `cargo fmt --check`: exit 0; no output. - `cargo clippy --all-targets -- -D warnings`: exit 0. Exact output: Finished `dev` profile [unoptimized + debuginfo] target(s) in 11m 51s. - `cargo test` (`RUST_TEST_THREADS=4`): exit 0. Exact output: Finished `test` profile [unoptimized + debuginfo] target(s) in 5m 25s. Across 69 suites: 1,217 passed, 0 failed, 12 ignored. - `bun run build`: `✓ built in 48.28s`; Vite printed existing `use client` module-directive warnings. - `bun run check`: `svelte-check found 0 errors and 0 warnings`. - `bun run test`: `Test Files 68 passed (68)`; `Tests 529 passed (529)`. Vitest also printed jsdom CSS parse and `scrollTo()` warnings. Production real-server E2E passed its route, deep-link, count-link, empty-state, keyboard, screen-reader, delta, painted-chart, responsive, and RTL checks. It captured 26 screenshots at `/home/kayg/Developer/calternal-wt/analytics-review`. The separate 50 ms chart-switch CPU assertion remains over budget in repeated runs: the screenshot run measured week→year at 70 ms; trace runs measured 63–99 ms React scheduler work and 72 ms layout. One passkey setup retry returned 401 before Analytics; the next two runs reached the Analytics checks. The prior 10,220-entry warm-year check remains at 382.2 ms median / 894.3 ms p90 under shared-host load and was not repeated here. Known gap: E2E Daily Note fixtures under flat `Notes/` did not index. The current Notes plugin still expects `Notes/Journal/`, contrary to DESIGN §40. Analytics fixtures use the currently indexed path; the Notes behavior needs its own follow-up. Decisions not specified in DESIGN: - Quarter activity links open the containing Calendar year because Calendar has no quarter or arbitrary-range route. - Added-count increases use the accent tone, decreases use `--now`, and no change uses muted; count direction does not imply good or bad. - Cache invalidation follows Analytics-relevant fields only; timezone-only Log projection repairs update metadata in place. - Chart CSS lives once in each owning `Document` for the lifetime of that document; the test verifies idempotence and separate-document installation. - This issue leaves the flat-Notes indexing correction to the Notes owner and keeps Analytics fixtures on `Notes/Journal`. Cleanup: `cargo clean` output was `Removed 18208 files, 14.1GiB total`; `apps/web/build` and `.svelte-kit/output` were removed. The worktree is clean.
Author
Owner

Claude review round 2 (analytics-year-tokyo-night-1440.png): much better (one header, charts draw). Remaining before merge:

  1. The delta badge overlaps the big value in the first card ('313h 40m' under '↑ >999%'). The value and badge must never overlap: badge on its own line under the label, or the value shrinks with clamp(); check all four cards at 1440, 1024 and 390.
  2. When the comparison period has no data (baseline 0), show no delta at all (no '>999%'). Only show a percentage when the baseline is non-zero; '→ 0' style for counts is fine.
  3. Daily trend: add a month (year view) / day (month view) x-axis with a few labels so the bars have a time reference.
    Re-take the same screenshots.
Claude review round 2 (analytics-year-tokyo-night-1440.png): much better (one header, charts draw). Remaining before merge: 1. The delta badge overlaps the big value in the first card ('313h 40m' under '↑ >999%'). The value and badge must never overlap: badge on its own line under the label, or the value shrinks with clamp(); check all four cards at 1440, 1024 and 390. 2. When the comparison period has no data (baseline 0), show no delta at all (no '>999%'). Only show a percentage when the baseline is non-zero; '→ 0' style for counts is fine. 3. Daily trend: add a month (year view) / day (month view) x-axis with a few labels so the bars have a time reference. Re-take the same screenshots.
Author
Owner

Starting continuation on job/analytics at 7cdaf67df3229c5e2fcb1ebb1a607808bf7de085, with local dev at 19676b443485821c2df96e03f8f0e357cf32ee74. I am addressing the three latest review points: prevent delta badge overlap, suppress percentage deltas for a zero baseline, and add date labels to Daily trend. I will commit each fix separately, retake the requested screenshots, merge the current dev tip before final gates, and report the exact gate output.

Starting continuation on `job/analytics` at `7cdaf67df3229c5e2fcb1ebb1a607808bf7de085`, with local `dev` at `19676b443485821c2df96e03f8f0e357cf32ee74`. I am addressing the three latest review points: prevent delta badge overlap, suppress percentage deltas for a zero baseline, and add date labels to Daily trend. I will commit each fix separately, retake the requested screenshots, merge the current `dev` tip before final gates, and report the exact gate output.
Author
Owner

Finding 1 reproduced: StatRow floated the comparison chip to the right but forced .value to white-space: nowrap, so a long headline could not move below the chip as the layout comment intended. The new regression test failed on that rule before the fix. I removed the restriction and added production E2E geometry checks for all four stat cards at 1440, 1024, and 390 px. Focused Vitest: 12 passed; bun run check: svelte-check found 0 errors and 0 warnings.

Finding 1 reproduced: `StatRow` floated the comparison chip to the right but forced `.value` to `white-space: nowrap`, so a long headline could not move below the chip as the layout comment intended. The new regression test failed on that rule before the fix. I removed the restriction and added production E2E geometry checks for all four stat cards at 1440, 1024, and 390 px. Focused Vitest: 12 passed; `bun run check`: `svelte-check found 0 errors and 0 warnings`.
Author
Owner

Finding 2 reproduced: when a comparison had data but the stat metric baseline was zero, computeDelta returned an amount without a percentage and StatRow rendered it as a static chip. The delta regression failed first with +1h 30m where the new rule expects no delta. Zero baselines now return an empty delta; the screen-reader “No data” text appears only when the comparison range itself has no data. Added counts retain their own → 0 behavior. Focused Vitest: 26 passed; bun run check: svelte-check found 0 errors and 0 warnings.

Finding 2 reproduced: when a comparison had data but the stat metric baseline was zero, `computeDelta` returned an amount without a percentage and `StatRow` rendered it as a static chip. The delta regression failed first with `+1h 30m` where the new rule expects no delta. Zero baselines now return an empty delta; the screen-reader “No data” text appears only when the comparison range itself has no data. Added counts retain their own `→ 0` behavior. Focused Vitest: 26 passed; `bun run check`: `svelte-check found 0 errors and 0 warnings`.
Author
Owner

Finding 3 reproduced: trendLabel returned no labels for year data, and TrendChart suppressed the axis in year views; its per-day grid would also clip month names to one bar column. I added month labels at month starts, kept the existing day labels for month views, and made each label span through the next tick segment. Production E2E checks now measure year and month label visibility. Focused Vitest: 12 passed; bun run check: svelte-check found 0 errors and 0 warnings. Production E2E and screenshot recapture are pending.

Finding 3 reproduced: `trendLabel` returned no labels for year data, and `TrendChart` suppressed the axis in year views; its per-day grid would also clip month names to one bar column. I added month labels at month starts, kept the existing day labels for month views, and made each label span through the next tick segment. Production E2E checks now measure year and month label visibility. Focused Vitest: 12 passed; `bun run check`: `svelte-check found 0 errors and 0 warnings`. Production E2E and screenshot recapture are pending.
Author
Owner

Merge finding: git merge dev at 57894643c840de27a35d5bd4215db103ce601a8a had conflicts in crates/plugins/files/src/lib.rs and packages/ui/src/index.ts. I combined the Files idempotency assertions for record and record_preserve, and kept both the SegmentedOption type export and the PopoverSurface/floating exports. Conflict markers are clear and the staged merge passes git diff --check --cached.

Merge finding: `git merge dev` at `57894643c840de27a35d5bd4215db103ce601a8a` had conflicts in `crates/plugins/files/src/lib.rs` and `packages/ui/src/index.ts`. I combined the Files idempotency assertions for `record` and `record_preserve`, and kept both the `SegmentedOption` type export and the `PopoverSurface`/floating exports. Conflict markers are clear and the staged merge passes `git diff --check --cached`.
Author
Owner

Production build finding after merging dev: the first bun run build exited 1 because Vite could not resolve @calternal/ui/floating from packages/editor/src/slash.ts. The package manifest already exports that path; packages/editor/node_modules lacked its @calternal/ui workspace link. bun install --frozen-lockfile restored the link (packages/editor/node_modules/@calternal/ui -> ../../../ui) without lockfile changes. A fresh production build then exited 0 and wrote apps/web/build (✓ built in 49.18s).

Production build finding after merging `dev`: the first `bun run build` exited 1 because Vite could not resolve `@calternal/ui/floating` from `packages/editor/src/slash.ts`. The package manifest already exports that path; `packages/editor/node_modules` lacked its `@calternal/ui` workspace link. `bun install --frozen-lockfile` restored the link (`packages/editor/node_modules/@calternal/ui -> ../../../ui`) without lockfile changes. A fresh production build then exited 0 and wrote `apps/web/build` (`✓ built in 49.18s`).
Author
Owner

Production real-server E2E result: the new stat overlap checks passed at 1440, 1024, and 390 px. Year trends showed 12 readable month labels; month trends showed readable day labels 1, 8, 15, 22, and 29. The existing route, links, empty state, keyboard, screen-reader, responsive, and RTL checks also passed. The runner refreshed 13 screenshots in /home/kayg/Developer/calternal-wt/analytics-review (week/month/year, Paper and Tokyo Night, 1440 and 390, plus RTL week). The command exited 1 only on the existing 50 ms chart-switch CPU check: year→quarter 83 ms and week→year 112 ms; quarter→month and month→week passed. These are the same host-sensitive performance checks noted in the previous review round.

Production real-server E2E result: the new stat overlap checks passed at 1440, 1024, and 390 px. Year trends showed 12 readable month labels; month trends showed readable day labels 1, 8, 15, 22, and 29. The existing route, links, empty state, keyboard, screen-reader, responsive, and RTL checks also passed. The runner refreshed 13 screenshots in `/home/kayg/Developer/calternal-wt/analytics-review` (week/month/year, Paper and Tokyo Night, 1440 and 390, plus RTL week). The command exited 1 only on the existing 50 ms chart-switch CPU check: year→quarter 83 ms and week→year 112 ms; quarter→month and month→week passed. These are the same host-sensitive performance checks noted in the previous review round.
Author
Owner

Continuation complete on job/analytics. Merged dev before the final gates. Head: fe7c245d1447a4aa61a5dd96a99aa26265450434.

Changes:

  • Removed the stat value's no-wrap rule so long values wrap beside the floated delta badge. Added production geometry checks at 1440, 1024, and 390 px.
  • Omitted percentage deltas when the baseline is zero.
  • Added readable Daily trend x-axis labels: abbreviated month labels for year view and day-of-month labels for month view. Year and month ticks span their chart interval to keep labels visible.
  • Corrected a stale tooltip material test: Toast uses common overlay material; only the mode tray and chart readout share the island material rule.

Production screenshots were refreshed in /home/kayg/Developer/calternal-wt/analytics-review (25 screenshots present, including week/month/year in paper and Tokyo Night at 1440 and 390 px, plus RTL).

Gates, verbatim output excerpts:

  • cargo fmt --check: no output; exit 0.
  • cargo clippy --all-targets -- -D warnings:
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 6m 50s
    
  • cargo test:
    Finished `test` profile [unoptimized + debuginfo] target(s) in 8m 39s
    exit_code=0
    
  • bun run check:
    Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/analytics/apps/web
    Getting Svelte diagnostics...
    svelte-check found 0 errors and 0 warnings
    
  • bun run test:
    Test Files  68 passed (68)
         Tests  534 passed (534)
       Duration  63.05s (transform 73%, import 10%, environment 9%, tests 5%, setup 2%)
    
  • Production build: ✓ built in 49.18s, Wrote site to "build", ✔ done.
  • Production Playwright E2E: all new overlap and Daily trend axis checks passed. The run exited 1 only on the chart-switch CPU threshold: year→quarter 83 ms and week→year 112 ms against 50 ms under shared-host load. Other chart switches passed.
  • Cleanup: Removed 15834 files, 12.8GiB total. Web build output was deleted. Worktree is clean.

Decisions inferred from the review and design: zero-baseline percentage deltas are hidden; year-axis labels use abbreviated en-US month names; month-axis labels show days 1, 8, 15, 22, and 29. The tooltip test follows the current shared UI stylesheet rule.

Commits: a114595f, ba337e29, 540a3a64, merge e7eeee5f, test correction fe7c245d.

Continuation complete on `job/analytics`. Merged `dev` before the final gates. Head: `fe7c245d1447a4aa61a5dd96a99aa26265450434`. Changes: - Removed the stat value's no-wrap rule so long values wrap beside the floated delta badge. Added production geometry checks at 1440, 1024, and 390 px. - Omitted percentage deltas when the baseline is zero. - Added readable Daily trend x-axis labels: abbreviated month labels for year view and day-of-month labels for month view. Year and month ticks span their chart interval to keep labels visible. - Corrected a stale tooltip material test: Toast uses common overlay material; only the mode tray and chart readout share the island material rule. Production screenshots were refreshed in `/home/kayg/Developer/calternal-wt/analytics-review` (25 screenshots present, including week/month/year in paper and Tokyo Night at 1440 and 390 px, plus RTL). Gates, verbatim output excerpts: - `cargo fmt --check`: no output; exit 0. - `cargo clippy --all-targets -- -D warnings`: ``` Finished `dev` profile [unoptimized + debuginfo] target(s) in 6m 50s ``` - `cargo test`: ``` Finished `test` profile [unoptimized + debuginfo] target(s) in 8m 39s exit_code=0 ``` - `bun run check`: ``` Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/analytics/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` - `bun run test`: ``` Test Files 68 passed (68) Tests 534 passed (534) Duration 63.05s (transform 73%, import 10%, environment 9%, tests 5%, setup 2%) ``` - Production build: `✓ built in 49.18s`, `Wrote site to "build"`, `✔ done`. - Production Playwright E2E: all new overlap and Daily trend axis checks passed. The run exited 1 only on the chart-switch CPU threshold: year→quarter 83 ms and week→year 112 ms against 50 ms under shared-host load. Other chart switches passed. - Cleanup: `Removed 15834 files, 12.8GiB total`. Web build output was deleted. Worktree is clean. Decisions inferred from the review and design: zero-baseline percentage deltas are hidden; year-axis labels use abbreviated en-US month names; month-axis labels show days 1, 8, 15, 22, and 29. The tooltip test follows the current shared UI stylesheet rule. Commits: `a114595f`, `ba337e29`, `540a3a64`, merge `e7eeee5f`, test correction `fe7c245d`.
kayg referenced this issue from a commit 2026-09-26 22:23:52 +00:00
Author
Owner

Merged into dev at c6570d81 after Claude's round-3 review (one header, charts draw, no overlap, axis labels). Deploy status on #203.

Merged into dev at c6570d81 after Claude's round-3 review (one header, charts draw, no overlap, axis labels). Deploy status on #203.
kayg closed this issue 2026-09-26 22:23:53 +00:00
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#125
No description provided.