Skip to content

[codex] add task timer dropdowns and AnimalDetail - #60

Merged
archae0pteryx merged 7 commits into
mainfrom
codex/fix-animal-timer-scale
May 16, 2026
Merged

[codex] add task timer dropdowns and AnimalDetail#60
archae0pteryx merged 7 commits into
mainfrom
codex/fix-animal-timer-scale

Conversation

@archae0pteryx

@archae0pteryx archae0pteryx commented May 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds independent task timers alongside the existing focus-level timer, and consolidates timer editing into compact clock/time dropdowns. The clicked-animal detail surface has also been renamed from PigDetail to AnimalDetail, with matching CSS/test IDs and current docs updated.

What changed

  • Adds optional Task.timer to the domain and regenerated TypeScript type.
  • Persists task timers in a task-timers.json sidecar, indexed alongside the markdown task list.
  • Adds Tauri/command APIs to start and clear focus timers and task timers.
  • Introduces a reusable TimerDropdown that shows a clock icon when no timer is set and the current remaining time when one is set.
  • Renames PigDetail to AnimalDetail and removes the heavy offset shadow that created the bubbly rounded artifact behind the detail card.
  • Updates README, PRD, CONTEXT, CHANGELOG, and local issue tracking for the new timer behavior.

Local issues

  • Adds completed local issue issues/done/052-task-timers-and-clock-dropdown-editing.md for this PR's slice.
  • Adds follow-up issue issues/053-task-timer-expiry-workflow.md for persisted Task timer expiry/notification semantics.

Impact

Users can set separate timers per task while keeping the global focus timer. Timer editing now lives on the clock/time control instead of a permanently visible picker. Existing focus timers and task markdown remain compatible.

Validation

  • task test
  • npm run lint
  • npm run typecheck
  • npm run test -- AnimalDetail App

Note

Task timers are persisted and displayed in the UI, but the existing background expiry notification workflow still only transitions focus-level timers to persisted Expired state. That follow-up is now tracked in issue 053.

Summary by CodeRabbit

  • New Features

    • Per-task countdown timers independent of focus timers
    • Clock-based timer controls in the animal detail card for focus and individual tasks
    • Timer presets available when starting timers from the detail card
  • Style

    • Renamed detail card from "PigDetail" to "AnimalDetail"
    • Expired animals now display with ghostly appearance, stop moving, and face away
    • Removed heavy shadow effect from detail card
  • Documentation

    • Updated specifications and guides for task timer functionality

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 15, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@archae0pteryx has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 43 minutes and 1 second before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d5b1e032-26bb-4c81-a50d-7acc3aa74fcb

📥 Commits

Reviewing files that changed from the base of the PR and between 7fdafc6 and 270c058.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • crates/storage/Cargo.toml
  • crates/storage/src/focus_store.rs
  • issues/053-task-timer-expiry-workflow.md
  • src/components/AnimalDetail.test.tsx
  • src/components/App.test.tsx
  • src/components/App.tsx
  • src/components/TimerDropdown.tsx
  • src/hooks/useAppState.test.tsx
  • src/hooks/useFocusController.ts
  • src/hooks/useOpenFocusDetailRequest.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fix-animal-timer-scale

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 and usage tips.

@archae0pteryx
archae0pteryx force-pushed the codex/fix-animal-timer-scale branch from 88dad10 to 7fdafc6 Compare May 15, 2026 20:01
@archae0pteryx
archae0pteryx marked this pull request as ready for review May 16, 2026 22:41

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/AnimalDetail.test.tsx (1)

186-194: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Wrap the Date.now spy in try/finally to guarantee cleanup.

Without try/finally, if the assertion on line 193 throws, mockRestore() is never called. Vitest's configuration lacks restoreAllMocks, so the mocked time persists and can leak into subsequent tests.

Suggested patch
   it("shows remaining time for a running timer", () => {
     const nowSpy = vi.spyOn(Date, "now").mockReturnValue(1_030_000);
-    renderDetail({
-      focus: {
-        ...baseFocus,
-        timer: { duration_secs: 120, started_at: 1_000, status: "Running" },
-      },
-    });
-    expect(screen.getByRole("button", { name: /edit focus timer/i })).toHaveTextContent("01:30");
-    nowSpy.mockRestore();
+    try {
+      renderDetail({
+        focus: {
+          ...baseFocus,
+          timer: { duration_secs: 120, started_at: 1_000, status: "Running" },
+        },
+      });
+      expect(screen.getByRole("button", { name: /edit focus timer/i })).toHaveTextContent("01:30");
+    } finally {
+      nowSpy.mockRestore();
+    }
   });
🤖 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 `@src/components/AnimalDetail.test.tsx` around lines 186 - 194, The Date.now
spy (nowSpy) in the test block around renderDetail should be cleaned up even if
assertions fail; wrap the spy and the test actions/assertions in a try/finally
so nowSpy.mockRestore() is always called. Locate the test that calls
vi.spyOn(Date, "now") and renderDetail(...) (and the expect on the edit focus
timer text) and move the expect and renderDetail into the try, with
nowSpy.mockRestore() in the finally block to guarantee restoration.
🤖 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 `@crates/storage/src/focus_store.rs`:
- Around line 219-220: The sidecar cleanup calls clear_expired_timer(focus_id)
and clear_fired_timer(focus_id) run after the primary mutation (writing
focus.md) has been committed but currently propagate errors with ?, which causes
the API to return Err even though the mutation succeeded; change these calls to
catch and log or convert their errors to non-fatal (e.g., match or map_err/log)
so the function returns Ok(()) after the primary write regardless of sidecar I/O
failures, while still recording the cleanup error for diagnostics (refer to
clear_expired_timer and clear_fired_timer and the surrounding commit logic).

In `@issues/053-task-timer-expiry-workflow.md`:
- Line 34: The "Blocked by" ordered-list entry currently reads "52." which
triggers a markdown list warning; update the "Blocked by" section in
issues/053-task-timer-expiry-workflow.md to use a plain issue reference instead
of a numbered list item (e.g., replace the "52." entry with a plain reference
like "`#52`" or "Blocked by: `#52`") so it matches the issues/README.md template and
other issue files.

In `@src/components/App.tsx`:
- Around line 70-94: The handler handleAddTask currently performs I/O (calls
focusWriter.appendTask and onWriteFailure) inside the App component; extract
that logic into a controller/hook (e.g., useFocusController) so components
remain view-only. Create a hook or controller function (e.g.,
useFocusController().appendTask or appendTaskController) that accepts focusId
and text, performs focusWriter.appendTask, calls onWriteFailure, and returns the
outcome; then update App's handleAddTask to only call the controller, build the
optimistic newTask, and call setOptimisticFocuses (removing direct focusWriter
usage and onWriteFailure calls from App). Also apply the same extraction for the
similar I/O block referenced around lines 116-126 so all focusWriter and fetch
side-effects live in the hook/controller (keep symbols: handleAddTask,
focusWriter.appendTask, onWriteFailure, setOptimisticFocuses, readerFocuses,
focuses, selectedFocus).

In `@src/components/TimerDropdown.tsx`:
- Around line 38-39: Toggle currently only flips open via setOpen, leaving any
custom-value validation state set; update the onClick handler used in
TimerDropdown (where setOpen is called) to also clear the custom-value
validation state when toggling — e.g., call the validation-state setter
(customError / setCustomError or whatever validation state exists) to set
no-error when toggling the dropdown so stale errors are removed on close/reopen,
and ensure this runs both when closing and opening the menu.

---

Outside diff comments:
In `@src/components/AnimalDetail.test.tsx`:
- Around line 186-194: The Date.now spy (nowSpy) in the test block around
renderDetail should be cleaned up even if assertions fail; wrap the spy and the
test actions/assertions in a try/finally so nowSpy.mockRestore() is always
called. Locate the test that calls vi.spyOn(Date, "now") and renderDetail(...)
(and the expect on the edit focus timer text) and move the expect and
renderDetail into the try, with nowSpy.mockRestore() in the finally block to
guarantee restoration.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 513e4ba8-052b-4f51-8f93-a405e4fb0ca7

📥 Commits

Reviewing files that changed from the base of the PR and between d8f00ec and 7fdafc6.

⛔ Files ignored due to path filters (1)
  • src/types/generated/Task.ts is excluded by !**/generated/**
📒 Files selected for processing (32)
  • CHANGELOG.md
  • CONTEXT.md
  • PRD.md
  • README.md
  • crates/commands/src/caps.rs
  • crates/commands/src/focus.rs
  • crates/domain/src/caps.rs
  • crates/domain/src/focus.rs
  • crates/domain/src/parse.rs
  • crates/storage/src/focus_store.rs
  • issues/051-ranch-animal-vocabulary-seam.md
  • issues/053-task-timer-expiry-workflow.md
  • issues/README.md
  • issues/done/030-pig-growth-expired-visual-tray-list.md
  • issues/done/052-task-timers-and-clock-dropdown-editing.md
  • src-tauri/src/app/mod.rs
  • src-tauri/src/app/tray.rs
  • src-tauri/src/ui_bridge/mod.rs
  • src/api/fixtureFocusWriter.ts
  • src/api/focusWriter.test.ts
  • src/api/focusWriter.ts
  • src/api/tauriFocusReader.test.ts
  • src/api/tauriFocusReader.ts
  • src/components/AnimalDetail.test.tsx
  • src/components/AnimalDetail.tsx
  • src/components/App.test.tsx
  • src/components/App.tsx
  • src/components/PigSprite.tsx
  • src/components/TimerDropdown.tsx
  • src/hooks/usePigMovement.test.ts
  • src/hooks/usePigMovement.ts
  • src/styles.css

Comment thread crates/storage/src/focus_store.rs Outdated
Comment thread issues/053-task-timer-expiry-workflow.md Outdated
Comment thread src/components/App.tsx
Comment thread src/components/TimerDropdown.tsx Outdated
@archae0pteryx
archae0pteryx merged commit f9d3b6d into main May 16, 2026
2 checks passed
@archae0pteryx
archae0pteryx deleted the codex/fix-animal-timer-scale branch May 16, 2026 23:00
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