Skip to content

feat(review): Deep work trend card — daily bars and week-over-week - #56

Merged
KassaSana merged 1 commit into
masterfrom
feat/daily-trend-card
Sep 15, 2026
Merged

KassaSana merged 1 commit into
masterfrom
feat/daily-trend-card

Conversation

@KassaSana

Copy link
Copy Markdown
Owner

Second slice of the Review trends program, on top of #55: the card that answers "am I doing better deep work over time?"

New Review card "Deep work", between Trends and Summary:

  • One sentence first: "1h 30m of deep work this week — about 30m more than last week." Neutral by rule — deltas under ten minutes read "about the same", and there is no streak/guilt vocabulary anywhere (the attended-targets voice).
  • Week comparison strip: this ISO week vs last (Monday start, the same rule as attended_secs_in_local_week), deep work and attended time. It only appears when last week actually recorded something — no triumphs over a week that predates the install.
  • Daily bars: deep work (solid) drawn inside attended time (light), one slot per calendar day ending today, on a minutes-shaped axis rounded up to a friendly ceiling (30m/1h/2h/…). Hover/<title> labels carry the full day: "Wed Aug 5 · attended 4h 2m · deep 1h 17m · avg focus 63 (412 samples)".
  • Honesty rules inherited from analyticsChart.ts: geometry is a pure module (dailyTrendChart.ts) tested without a DOM; a measured zero is a visible sliver while a day with no data is a tick below the axis — never the same picture; every label names its true unit (10.13). Days the backend omitted are drawn as gaps, so a skipped day stays visible as a day.
  • "Today" shows headline + strip without pretending one bar is a trend; a capped series says the retention window cut it short; under three days of data the card says trends fill in with more sessions.

Wiring: dailySummary joins the existing Promise.all in useReviewWorkflow under the same request-id guard; DailyTrendCard renders from props only. Per CONTRIBUTING's formatting split, the three new files are Prettier-clean and the pre-existing files (App.tsx, styles.css, useReviewWorkflow.ts) received minimal diffs, not a reformat.

Tests (dailyTrendChart.test.ts, added to the test:unit chain): gap filling and ordering, measured-zero vs no-data, friendly axis ceilings, label units, ISO-week boundaries (Sunday/Monday/Saturday), week-comparison sums and the empty-last-week null, headline wording for more/less/same/no-comparison, range-day clamping. Verified locally: unit chain, 160 component tests, tsc --noEmit, lint, demo bundle builds.

The Review surface could say what an average was but never whether it
was getting better: no per-day series, no week-over-week, nothing shaped
like a trend. This adds the card that answers "am I doing better deep
work over time", fed by get_daily_summary.

The card leads with one sentence ("1h 30m of deep work this week —
about 30m more than last week"), then a comparison strip (this ISO week
versus last, Monday start, matching attended_secs_in_local_week), then
daily bars: deep work solid inside attended time light, on a
minutes-shaped axis rounded to a friendly ceiling. Deltas under ten
minutes read "about the same" — a measurement, not a verdict, and no
streak or guilt vocabulary anywhere (the attended-targets voice).

Chart rules inherited from analyticsChart.ts: geometry is a pure module
(dailyTrendChart.ts) tested without a DOM; a measured zero is a visible
sliver and a day with no data is a tick below the axis, never the same
picture; labels carry true units — durations in h/m, sample counts
named as samples (10.13). Days the backend omitted are drawn as gaps so
a skipped day stays visible as a day.

A comparison only appears when last week actually recorded something;
"today" shows the headline and strip without pretending one bar is a
trend; a capped series says the retention window cut it short.
@KassaSana
KassaSana force-pushed the feat/daily-trend-card branch from 77cc991 to dedb88d Compare September 15, 2026 13:23
Copilot AI lite review requested due to automatic review settings September 15, 2026 13:23
@KassaSana

Copy link
Copy Markdown
Owner Author

Rebased onto current master (2a68501); original commit dates preserved. Resolved two conflicts: useReviewWorkflow.ts keeps the loadReview/invalidateReview/refreshReview structure from daad737 with getDailySummary added to the Promise.all; App.tsx keeps the 2a68501 Review layout with DailyTrendCard after AnalyticsCard. Re-verified locally: tsc --noEmit, eslint (0 errors), test:unit incl. dailyTrendChart.test.ts, vitest 31 files / 166 tests, npm run build.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved range scoping, ISO-week comparison, chart-scaling, and empty/capped-state correctness issues remain.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds a Review “Deep work” trend card with daily attended/deep-work bars, neutral headlines, and week-over-week comparisons.

Changes:

  • Adds pure trend geometry, labels, comparisons, and unit tests.
  • Loads daily summaries through the Review workflow.
  • Integrates the card, styling, and test command updates.
File summaries
File Summary
frontend/tests/dailyTrendChart.test.ts Tests trend geometry, gaps, scaling, labels, comparisons, and wording.
frontend/src/useReviewWorkflow.ts Loads daily summaries with guarded workflow state updates.
frontend/src/styles.css Adds trend card and chart styling.
frontend/src/dailyTrendChart.ts Implements chart geometry, labels, scaling, and comparison calculations.
frontend/src/DailyTrendCard.tsx Renders the Deep work card and its states.
frontend/src/App.tsx Integrates the card into Review.
frontend/package.json Adds trend tests to the unit-test chain.
Review details

Suppressed comments (9)

frontend/src/DailyTrendCard.tsx:51

  • The native daily-summary query snaps its rolling cutoff down to local midnight, so a day response can contain yesterday and today, and a 7d response can contain eight calendar dates. dayBars trims those results to rangeDays, but this call passes every returned day into the headline, so the total can include an extra day (for example, Today includes yesterday). Restrict the headline input to the selected/rendered range or make the headline range-aware.
          <p className="trend-headline">{trendHeadline(days, todayIso, rangeLabel)}</p>

frontend/src/DailyTrendCard.tsx:30

  • days is limited by the selected Review request, but weekComparison needs complete current and previous ISO weeks. The native get_daily_summary receives the selected day/7d cutoff, so for Today or most 7-day dates this data cannot contain last week's full range; the strip is therefore absent or compares against only a tail of last week. Fetch an additional comparison window or extend the daily response before computing this.
  const comparison = weekComparison(days, todayIso);

frontend/src/DailyTrendCard.tsx:45

  • No component-level test supplies a non-empty DailySummary; the existing App/Review mocks fall through to null for get_daily_summary, so only this empty branch is exercised. The SVG bars, comparison strip, capped notice, and under-three-days message can change without a test noticing; add a Review card integration/component test with representative daily data.
      {days.length === 0 ? (

frontend/src/DailyTrendCard.tsx:48

  • When an all/custom request is capped but the retained window has no rows, this empty-state branch returns before the capped notice below it. The user therefore sees only “No deep work measured yet” and is not told that older data was clipped, contrary to the capped-series honesty rule. Render the retention-window notice in the empty state as well, or move it outside the data conditional.
      {days.length === 0 ? (
        <p className="helper-text">
          No deep work measured yet. Start a session to build the trend.
        </p>

frontend/src/dailyTrendChart.ts:280

  • reviewRangeLabel() supplies 7 days and 30 days, so production output becomes sentences such as 20m of deep work 7 days. The unit test avoids this by passing Last 7 days directly, which does not match the value used by DailyTrendCard. Normalize these preset labels to last 7 days/last 30 days (or pass a range-specific phrase) before composing the sentence.
  return `${formatFocusStretch(totalDeep)} of deep work ${rangeLabel.toLowerCase()}.`;

frontend/src/dailyTrendChart.ts:249

  • This guard treats a week with only recorded sessions or snapback events as empty. The daily-summary contract includes sessionCount and snapbackCount, and storage can return a session day with zero attended seconds and samples, so the comparison strip is incorrectly hidden even though last week has recorded data. Include those recorded-count fields in the presence check.
      if (entry.attendedSecs > 0 || entry.sampleCount > 0) lastWeekHasData = true;

frontend/src/dailyTrendChart.ts:289

  • Unlike the deep-work values above, these two attended totals are separated only by ·; neither is labeled as this week or last week. The comparison strip therefore does not tell users which attended total belongs to which week. Label both values explicitly.
    `Attended: ${formatFocusStretch(comparison.thisWeekAttendedSecs)} · ` +
    `${formatFocusStretch(comparison.lastWeekAttendedSecs)}.`

frontend/src/dailyTrendChart.ts:182

  • The storage series can contain prediction-only data (focused/deep time with attendedSecs === 0), so these values can be scaled independently and make the solid deep-work bar protrude above the light attended context. That contradicts the card's stated “deep work inside attended time” visual and can show deep work with no attended-time envelope. Define a bounded rendering rule or enforce an API invariant, and add a regression case for this data shape.
    const attendedHeight = scaled(entry.attendedSecs);
    const deepHeight = scaled(entry.deepFocusSecs);

frontend/src/dailyTrendChart.ts:280

  • The no-comparison fallback reduces every entry returned by the daily-summary request, but the backend snaps rolling cutoffs down to local midnight. A today request can therefore contain yesterday as well as today, and a 7d/30d request can contain an extra leading calendar day; this sentence can report yesterday's deep work as “today” or overstate the selected range. Filter the total (and empty-state decision) to the selected day slots, while retaining a separate full series for the ISO-week comparison.
  const totalDeep = days.reduce((sum, entry) => sum + entry.deepFocusSecs, 0);
  return `${formatFocusStretch(totalDeep)} of deep work ${rangeLabel.toLowerCase()}.`;
  • Files reviewed: 7/7 changed files
  • Comments generated: 3
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.


const maxSecs = drawn.reduce(
(max, entry) =>
entry ? Math.max(max, entry.attendedSecs, entry.focusedSecs, entry.deepFocusSecs) : max,
api.getSummaryReport(params),
api.getFocusSummary(params),
api.getSessionHistory(params),
api.getDailySummary(params),
api.getSummaryReport(params),
api.getFocusSummary(params),
api.getSessionHistory(params),
api.getDailySummary(params),
@KassaSana
KassaSana merged commit a7377ae into master Sep 15, 2026
16 checks passed
@KassaSana
KassaSana deleted the feat/daily-trend-card branch September 15, 2026 13:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants