Skip to content

feat(companion): activity display#142

Merged
tkhwang merged 5 commits into
mainfrom
feat/calendar-workflow
Jul 5, 2026
Merged

feat(companion): activity display#142
tkhwang merged 5 commits into
mainfrom
feat/calendar-workflow

Conversation

@tkhwang

@tkhwang tkhwang commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features
    • Added an Activity calendar view in Companion with day and three-day timelines, project filtering, and selectable session details.
    • Calendar data can be reloaded automatically when Companion refreshes.
  • Documentation
    • Updated README and Companion docs to clarify shared state sources and how the activity timeline is derived from the local activity log.
  • Bug Fixes
    • Improved activity loading to handle missing logs and tolerate malformed/legacy entries without breaking the view.
  • Tests
    • Added/expanded unit and rendering tests for activity parsing, session/lane logic, and client event loading.
  • Chores
    • Added a Companion test watch script.

tkhwang added 2 commits July 5, 2026 08:14
- Update READMEs to reflect the new activity timeline feature in Companion
- Add a new `activity_store.rs` module to handle reading and writing activity events
- Implement `read_activity_events_default` to read activity events within a specified epoch range

This introduces the activity timeline feature to the Workbranch Companion, allowing users to visualize their task sessions in a calendar view. The timeline is a navigation and reporting surface, with activity recording still driven by Companion refreshes and CLI state changes.
- Introduce `MIN_DISPLAY_LANE_SECONDS` to ensure a minimum visual length for sessions
- Refactor `assignLanes` to accept a `minimumLaneSeconds` parameter
- Create `assignDisplayLanes` as a wrapper for `assignLanes` with the new minimum
- Remove `maybeRenderPlanTitle` and related logic for displaying plan titles
- Simplify session rendering in `ActivityCalendarView` to only show task and time
- Update `assignLanes` to use `laneEnd` for calculating cluster end times, respecting the minimum lane duration
- Add a test case for `assignLanes` to verify correct lane separation with `minimumLaneSeconds`
- Adjust existing `CalendarTimeline` tests to reflect the updated display logic

This change improves the readability of the activity calendar by ensuring that
even very short sessions are visually distinct and occupy a reasonable amount
of space. It also simplifies the display logic by removing the conditional
rendering of plan titles, focusing on the core task and time information.
The `assignLanes` function is now more flexible, allowing for different
minimum display durations based on context.
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@tkhwang, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 50 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: be1cdad2-15c7-433c-907f-4c98c6e141c5

📥 Commits

Reviewing files that changed from the base of the PR and between e43e656 and abe9b2a.

📒 Files selected for processing (2)
  • apps/companion/src/activity/displaySessions.ts
  • apps/companion/tests/activity-display-sessions.test.ts
📝 Walkthrough

Walkthrough

Adds a Companion Activity view: a Rust Tauri command reads append-only activity log events within an epoch range, a new TS calendar domain module derives sessions/lanes/color indices, and a React ActivityCalendarView renders day/three-day timelines. Updates styles, themes, docs, README, and tests accordingly.

Changes

Companion Activity Calendar Timeline

Layer / File(s) Summary
Rust activity event range reading
apps/companion/src-tauri/src/activity_store.rs, apps/companion/src-tauri/src/lib.rs
Adds read_activity_events_in_range/read_activity_events_default to stream-parse and filter activity.jsonl by inclusive epoch range, skipping malformed/legacy lines; registers a new read_activity_events Tauri command with unit tests.
TS client binding and event validation
apps/companion/src/infrastructure/tauriClient.ts, apps/companion/src/application/activity.ts, apps/companion/tests/tauri-client.test.ts
Adds readActivityEvents invoking the Tauri command and filtering results, exports IDLE_GAP_SECONDS/LEAD_PAD_SECONDS, and adds tests validating filtering of invalid rows.
Calendar session and display-lane domain
apps/companion/src/activity/calendar.ts, apps/companion/src/activity/displaySessions.ts, apps/companion/tests/activity-calendar.test.tsx, apps/companion/tests/activity-display-sessions.test.ts
Adds event normalization, sessionization, clipping, lane packing, hour-range, and color-index helpers, plus display-session coalescing and lane assignment, with tests for merging, filtering, and lane behavior.
ActivityCalendarView UI and wiring
apps/companion/src/activity/ActivityCalendarView.tsx, apps/companion/src/activity/activity-calendar.css, apps/companion/src/App.tsx, apps/companion/src/style.css, apps/companion/src/styles/themes.css, apps/companion/tests/activity-calendar.test.tsx, apps/companion/tests/app-shell.test.tsx
Adds CalendarTimeline/ActivityCalendarView components rendering day/three-day session timelines with project filtering and a details panel, wires it into App.tsx replacing the placeholder, adds calendar CSS and theme --cal-* variables, with rendering tests.
Docs, README, and script updates
docs/plans/0041-companion-activity-calendar-timeline.md, README.md, README.ko.md, apps/companion/README.md, package.json
Adds an implementation plan document, updates README files to describe the Activity view and Companion's shared-state reading, and adds a companion:test:watch script.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Sequence Diagram(s)

sequenceDiagram
  participant View as ActivityCalendarView
  participant Client as tauriClient
  participant Command as read_activity_events (Rust)
  participant Store as activity_store.rs
  participant Domain as calendar.ts

  View->>Client: readActivityEvents(fromEpoch, toEpoch)
  Client->>Command: invoke("read_activity_events", {fromEpoch, toEpoch})
  Command->>Store: read_activity_events_default(from, to)
  Store->>Store: read_activity_events_in_range(path, from, to)
  Store-->>Command: Vec<serde_json::Value>
  Command-->>Client: Vec<Value>
  Client->>Domain: calendarEventFromUnknown(each item)
  Domain-->>Client: CalendarEventInput[] (filtered)
  Client-->>View: readonly CalendarEventInput[]
  View->>Domain: sessionsFromEvents / clipToRange / assignLanes / assignDisplayLanes
  Domain-->>View: LaneSession[]
  View->>View: render CalendarTimeline
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately points to the new companion activity display feature, which matches the main changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/calendar-workflow

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 032350901d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread apps/companion/src/activity/ActivityCalendarView.tsx Outdated
Comment thread apps/companion/src/activity/ActivityCalendarView.tsx Outdated
Comment thread apps/companion/src/activity/calendar.ts Outdated
Comment thread apps/companion/src/activity/ActivityCalendarView.tsx
Comment thread apps/companion/src/activity/ActivityCalendarView.tsx Outdated
Comment thread apps/companion/src/activity/ActivityCalendarView.tsx Outdated
Comment thread apps/companion/src/activity/activity-calendar.css Outdated

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 4

🧹 Nitpick comments (2)
apps/companion/src-tauri/src/activity_store.rs (1)

96-124: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

Full linear scan of the append-only log on every read.

read_activity_events_in_range reads and JSON-parses every line in activity.jsonl regardless of the requested range, on every invocation from the UI (via spawn_blocking). As the log grows unbounded over long-term usage, this becomes an O(n) scan per calendar view/period-navigation request. Consider maintaining a lightweight index (e.g., periodic offsets by day) or log rotation/compaction so lookups scale with the requested range rather than total log size.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/companion/src-tauri/src/activity_store.rs` around lines 96 - 124,
`read_activity_events_in_range` currently does a full line-by-line JSON scan of
`activity.jsonl` on every call, which will not scale as the log grows. Update
the `read_activity_events_in_range` path (and any related append-only log
handling in `activity_store.rs`) to use a lightweight lookup strategy such as a
periodic index by time bucket or log rotation/compaction, so range reads only
touch the relevant portion of the file instead of all events.
apps/companion/tests/activity-calendar.test.tsx (1)

387-399: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a three-day test with cross-day sessions.

The "renders three day columns" test only passes sessions={[]}. Consider adding a case with sessions on day 2/3 to cover the hourRange computation for non-empty multi-day sets — this would have caught the hourRange day-reference bug flagged in calendar.ts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/companion/tests/activity-calendar.test.tsx` around lines 387 - 399, Add
a three-day CalendarTimeline test that includes sessions spanning day 2 and day
3 instead of only sessions={[]}, so the hourRange path is exercised with a
non-empty multi-day set. Extend the existing renders three day columns in
three-day mode case in activity-calendar.test.tsx using CalendarTimeline and the
day/session fixtures to verify the computed range still renders correctly across
multiple days and catches the calendar.ts day-reference issue.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/companion/src-tauri/src/activity_store.rs`:
- Around line 109-122: The `read_activity_events_in_range` loop currently uses
`line?`, which aborts the entire read on invalid UTF-8 instead of skipping the
bad entry like the existing malformed JSON and missing `observedAt` cases.
Update the `BufReader::new(file).lines()` handling in
`read_activity_events_in_range` to treat per-line read/decoding failures as
skippable and continue scanning subsequent lines. Keep the same filtering
behavior for valid `serde_json::Value` records and only push events that pass
the `observedAt` range check.

In `@apps/companion/src/activity/calendar.ts`:
- Around line 267-291: The hour bounds in hourRange are being calculated from a
single dayStart even when sessions span multiple visible days, so day 2/3
sessions get clamped to 24 and force the full range. Update hourRange in
calendar.ts to normalize each CalendarSession against its own containing day
before computing sessionStartHour/sessionEndHour, and keep the clampHour
behavior only as a final safety net. Use the hourRange and clampHour symbols to
locate the fix, and add a regression test covering multi-day sessions in
three-day CalendarTimeline usage.

In `@README.ko.md`:
- Line 14: The README.ko.md wording still conflates the CLI state source with
the activity timeline source; update the affected intro text and the
Companion/Activity description so it clearly says the CLI reads
workspace/project state while the Activity view is powered by the local
append-only log. Use the existing README sections around the shared `workbranch`
description and the Companion feature summary to separate these contracts
without implying `workbranch list --global --json` feeds the activity timeline.

In `@README.md`:
- Line 14: Update the README wording to separate the CLI state from the
activity-log source: the CLI state should be described as coming from the shared
workbranch project state, while the Activity view should be described as reading
from the local append-only log. Adjust the copy in the main overview and any
matching text near the later reference so terms like “same workbranch project
state” no longer imply `workbranch list --global --json` feeds the activity
timeline; use the existing README section text to locate and revise the affected
descriptions.

---

Nitpick comments:
In `@apps/companion/src-tauri/src/activity_store.rs`:
- Around line 96-124: `read_activity_events_in_range` currently does a full
line-by-line JSON scan of `activity.jsonl` on every call, which will not scale
as the log grows. Update the `read_activity_events_in_range` path (and any
related append-only log handling in `activity_store.rs`) to use a lightweight
lookup strategy such as a periodic index by time bucket or log
rotation/compaction, so range reads only touch the relevant portion of the file
instead of all events.

In `@apps/companion/tests/activity-calendar.test.tsx`:
- Around line 387-399: Add a three-day CalendarTimeline test that includes
sessions spanning day 2 and day 3 instead of only sessions={[]}, so the
hourRange path is exercised with a non-empty multi-day set. Extend the existing
renders three day columns in three-day mode case in activity-calendar.test.tsx
using CalendarTimeline and the day/session fixtures to verify the computed range
still renders correctly across multiple days and catches the calendar.ts
day-reference issue.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c25feff8-a878-4e97-82ab-0758a5fbba84

📥 Commits

Reviewing files that changed from the base of the PR and between 4d718a3 and 0323509.

📒 Files selected for processing (17)
  • README.ko.md
  • README.md
  • apps/companion/README.md
  • apps/companion/src-tauri/src/activity_store.rs
  • apps/companion/src-tauri/src/lib.rs
  • apps/companion/src/App.tsx
  • apps/companion/src/activity/ActivityCalendarView.tsx
  • apps/companion/src/activity/activity-calendar.css
  • apps/companion/src/activity/calendar.ts
  • apps/companion/src/application/activity.ts
  • apps/companion/src/infrastructure/tauriClient.ts
  • apps/companion/src/style.css
  • apps/companion/src/styles/themes.css
  • apps/companion/tests/activity-calendar.test.tsx
  • apps/companion/tests/tauri-client.test.ts
  • docs/plans/0041-companion-activity-calendar-timeline.md
  • package.json

Comment thread apps/companion/src-tauri/src/activity_store.rs
Comment thread apps/companion/src/activity/calendar.ts
Comment thread README.ko.md Outdated
Comment thread README.md Outdated
tkhwang added 2 commits July 5, 2026 09:42
- Update READMEs (English and Korean) to distinguish activity view data source
- Explain that activity view reads from a local append-only log
- Clarify that companion itself shows workspace/project state

The previous documentation might have implied that the activity timeline was also derived from the shared `workbranch list --global --json` output. This change clarifies that the activity view uses a separate local log for its timeline, providing a more accurate description of the system's architecture.

Fixes: #123
- Extract `assignDisplayLanes` into a new `displaySessions.ts` module
- Implement `coalesceDisplaySessions` to merge close, non-overlapping sessions
  for the same task into a single display session
- Update `assignDisplayLanes` to use the new coalescing logic
- Add tests for `assignDisplayLanes` to verify coalescing behavior

This change improves the visual representation of activity sessions in the
calendar view by reducing clutter and making it easier to understand
continuous work on a single task, even if it's broken into multiple
short sessions.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/companion/tests/activity-display-sessions.test.ts (1)

17-51: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a regression test for sessions exceeding the 90-minute display-lane threshold.

Both cases here use sub-90-minute sessions. Consider adding a case where one same-task session's own duration already exceeds MIN_DISPLAY_LANE_SECONDS (90 min) followed by a short-gap continuation — this is the scenario affected by the shouldCoalesce/displayLaneEnd anchoring issue flagged in displaySessions.ts (lines 13-41), and would guard against regressions once fixed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/companion/tests/activity-display-sessions.test.ts` around lines 17 - 51,
Add a regression test in assignDisplayLanes to cover same-task sessions where
the first session already exceeds MIN_DISPLAY_LANE_SECONDS and is followed by a
short-gap continuation, since the current cases only cover sub-90-minute
sessions. Use sessionsFromEvents with event entries to create that
over-threshold scenario and assert the resulting lane is coalesced correctly,
with the expected laneCount, start/end, and single-lane behavior. Reference
assignDisplayLanes and the threshold/anchoring behavior from displaySessions.ts
to ensure the test protects the shouldCoalesce and displayLaneEnd fix.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/companion/src/activity/displaySessions.ts`:
- Around line 13-41: The coalescing logic in shouldCoalesce/displayLaneEnd is
using a start-anchored window that collapses once a session exceeds
MIN_DISPLAY_LANE_SECONDS, so long sessions can no longer merge even with tiny
gaps. Update shouldCoalesce to use a fixed gap tolerance for the next session
start, and adjust mergeDisplaySession so the merged session’s start is updated
appropriately when combining CalendarSession blocks. Keep the changes localized
to displayLaneEnd, shouldCoalesce, and mergeDisplaySession.

---

Nitpick comments:
In `@apps/companion/tests/activity-display-sessions.test.ts`:
- Around line 17-51: Add a regression test in assignDisplayLanes to cover
same-task sessions where the first session already exceeds
MIN_DISPLAY_LANE_SECONDS and is followed by a short-gap continuation, since the
current cases only cover sub-90-minute sessions. Use sessionsFromEvents with
event entries to create that over-threshold scenario and assert the resulting
lane is coalesced correctly, with the expected laneCount, start/end, and
single-lane behavior. Reference assignDisplayLanes and the threshold/anchoring
behavior from displaySessions.ts to ensure the test protects the shouldCoalesce
and displayLaneEnd fix.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6446f943-e431-45b3-8acc-3d42d3134e1b

📥 Commits

Reviewing files that changed from the base of the PR and between ff7e99c and e43e656.

📒 Files selected for processing (3)
  • apps/companion/src/activity/ActivityCalendarView.tsx
  • apps/companion/src/activity/displaySessions.ts
  • apps/companion/tests/activity-display-sessions.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/companion/src/activity/ActivityCalendarView.tsx

Comment thread apps/companion/src/activity/displaySessions.ts
- Adjust `displayLaneEnd` calculation to always extend the end time
- Modify `shouldCoalesce` to use inclusive comparison for `next.start`
- Add `start: Math.min(current.start, next.start)` to `mergeDisplaySession`

The previous logic for `displayLaneEnd` could result in a shorter display lane than intended if `session.end` was already greater than `session.start + MIN_DISPLAY_LANE_SECONDS`. This change ensures the display lane always extends by `MIN_DISPLAY_LANE_SECONDS` from the session's end.

The `shouldCoalesce` function was updated to use an inclusive comparison for `next.start <= displayLaneEnd(current)`. This allows sessions that start exactly at the calculated display lane end to be coalesced, which was previously missed.

Additionally, `mergeDisplaySession` now correctly sets the start of the merged session to the minimum of the current and next session's start times, ensuring that the merged session spans the entire duration of both original sessions.

These changes fix an issue where closely spaced sessions, especially longer ones with small gaps, were not being correctly coalesced into a single display session, leading to fragmented display in the activity view.
@tkhwang tkhwang merged commit 32775f6 into main Jul 5, 2026
4 checks passed
@tkhwang tkhwang deleted the feat/calendar-workflow branch July 5, 2026 04:24
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.

1 participant