Skip to content

feat(desktop): add path and branch info to workspace header#400

Merged
Kitenite merged 14 commits intomainfrom
more-top-bar-space
Dec 18, 2025
Merged

feat(desktop): add path and branch info to workspace header#400
Kitenite merged 14 commits intomainfrom
more-top-bar-space

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented Dec 17, 2025

Summary

  • Display worktree path with folder icon in workspace header
  • Add searchable branch dropdown with pagination (10 branches per page)
  • Show relative time since last commit for each branch (e.g., 2h, 3d)
  • Mark checked-out branches as disabled with "(in use)" label
  • Sort branches: current first, then checked-out, then by recent commit
  • Add switchBranch tRPC mutation to change branches within worktree
  • Extract formatRelativeTime utility with tests

Test plan

  • Verify path displays correctly with folder icon
  • Verify branch dropdown shows current branch
  • Test search functionality filters branches correctly
  • Test "Show more" pagination loads additional branches
  • Verify checked-out branches are disabled and show "(in use)"
  • Test branch switching works and updates UI
  • Run bun test formatRelativeTime to verify utility tests pass

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Branch switching workflow added (switch branches per worktree) and interactive branch picker with search, pagination, checked-out indicators, and last-commit times.
    • Workspace header refreshed: masked path display, clickable "open in app" action, and branch selector moved above the workspace.
  • Tests

    • Deterministic test suite added for relative-time formatting.
  • Chores

    • Workspace VSCode settings/recommendations cleaned up and .gitignore compacted.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 17, 2025

Warning

Rate limit exceeded

@Kitenite has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 14 minutes and 11 seconds before requesting another review.

⌛ 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 68a19cb and 3e7c7ac.

📒 Files selected for processing (15)
  • apps/desktop/src/lib/trpc/routers/changes/branches.ts (1 hunks)
  • apps/desktop/src/lib/trpc/routers/changes/changes.ts (0 hunks)
  • apps/desktop/src/lib/trpc/routers/changes/file-contents.ts (1 hunks)
  • apps/desktop/src/lib/trpc/routers/changes/index.ts (1 hunks)
  • apps/desktop/src/lib/trpc/routers/changes/staging.ts (1 hunks)
  • apps/desktop/src/lib/trpc/routers/changes/status.ts (1 hunks)
  • apps/desktop/src/lib/trpc/routers/changes/utils/apply-numstat.ts (1 hunks)
  • apps/desktop/src/renderer/components/NewWorkspaceModal/NewWorkspaceModal.tsx (2 hunks)
  • apps/desktop/src/renderer/components/OpenInButton/index.ts (1 hunks)
  • apps/desktop/src/renderer/lib/formatPath/formatPath.ts (1 hunks)
  • apps/desktop/src/renderer/lib/formatPath/index.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/StartView/index.tsx (2 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceHeader/WorkspaceHeader.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceHeader/components/BranchSelector/BranchSelector.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceHeader/components/PathDisplay/PathDisplay.tsx (1 hunks)

Walkthrough

Adds branch-management backend changes (last-commit dates, checked-out worktrees, new switchBranch mutation), introduces a branch-picker and path display in WorkspaceHeader with UI wiring, adds a compact formatRelativeTime utility with tests, and updates small exports and workspace config files.

Changes

Cohort / File(s) Summary
Branch Management Backend
apps/desktop/src/lib/trpc/routers/changes/changes.ts
getBranches now returns local: Array<{ branch: string; lastCommitDate: number }>, remote: string[], defaultBranch: string, checkedOutBranches: Record<string,string>. Local last-commit dates are fetched in a consolidated operation (fallback to 0). New switchBranch mutation: runs git.checkout for a worktree, persists branch to DB (worktree and worktree.gitStatus.branch if present), and surfaces errors.
WorkspaceHeader & Layout
apps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceHeader/WorkspaceHeader.tsx, apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx, apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx
WorkspaceHeader now shows a display-friendly path and conditionally renders branch UI; WorkspaceView renders WorkspaceHeader above resizable panels and moves Sidebar into a panel; TopBar no longer queries or renders the WorkspaceHeader.
Branch Picker & Path UI
apps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceHeader/components/BranchSelector/*, apps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceHeader/components/PathDisplay/*
New BranchSelector component: searchable/paginated branch picker, sorts current/checked-out/others, shows last-commit times, invokes switchBranch mutation and invalidates caches, supports "Open worktree" actions. New PathDisplay component: shows masked path, fetches last-used app, and triggers open-in-app mutation.
Relative Time Utility & Tests
apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.ts, apps/desktop/src/renderer/lib/formatRelativeTime/index.ts, apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.test.ts
Adds formatRelativeTime(timestamp: number) returning compact strings ("now","Xm","Xh","Xd","Xmo"), re-exports it, and a deterministic test suite that mocks Date.now covering boundaries.
Exports & Small API Surface
apps/desktop/src/renderer/components/OpenInButton/index.ts, apps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceHeader/components/BranchSelector/index.ts, apps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceHeader/components/PathDisplay/index.ts
Added re-exports and expanded the OpenInButton barrel to export getAppOption alongside OpenInButton, and re-exported BranchSelector and PathDisplay.
Workspace Config / Ignore
.gitignore, apps/desktop/.vscode/extensions.json, apps/desktop/.vscode/settings.json
.gitignore replaced two specific .vscode file ignores with a directory-wide .vscode pattern. Removed apps/desktop/.vscode/extensions.json and apps/desktop/.vscode/settings.json (workspace VSCode configuration deleted).

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant UI as WorkspaceHeader (UI)
    participant TRPC as TRPC Client
    participant Backend as changes.ts (Server)
    participant Git as Git
    participant DB as Database

    User->>UI: open BranchSelector / choose branch
    UI->>TRPC: switchBranch({ worktreePath, branch })
    TRPC->>Backend: invoke switchBranch mutation
    Backend->>Git: git.checkout(branch) on worktreePath
    alt checkout succeeds
        Git-->>Backend: success
        Backend->>DB: persist worktree.branch and worktree.gitStatus.branch (if present)
        DB-->>Backend: persisted
        Backend-->>TRPC: { success: true }
        TRPC-->>UI: success response
        UI->>UI: invalidate caches / refresh branch list
    else checkout fails
        Git-->>Backend: error
        Backend-->>TRPC: throw descriptive error
        TRPC-->>UI: error response
        UI->>UI: show error toast
    end
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

  • Pay extra attention to:
    • switchBranch implementation: git checkout invocation, error handling, and DB persistence (worktree and worktree.gitStatus.branch).
    • Consolidated local last-commit date retrieval and fallback-to-0 behavior.
    • BranchSelector sorting, pagination, search logic and correct cache invalidation after switching.
    • WorkspaceView structural changes (panel nesting) and TopBar removal of WorkspaceHeader.

Possibly related PRs

Poem

🐰
I hop through branches, keen and spry,
I nudge a checkout, watch it fly,
Times shrink to "now" and "5m" bright,
A picker guides the chosen bite,
Small rabbit cheers — the repo's right!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding path and branch information display to the workspace header.
Description check ✅ Passed The PR description covers the Summary, Test plan, and provides clear implementation details, though it lacks explicit sections for Related Issues and Type of Change as specified in the template.

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.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Dec 17, 2025

🚀 Preview Deployment

🔗 Preview Links

Service Status Link
Neon Database (Neon) View Branch
Vercel API (Vercel) Open Preview
Vercel Web (Vercel) Open Preview
Vercel Marketing (Vercel) Open Preview
Vercel Admin (Vercel) Open Preview
Vercel Docs (Vercel) Open Preview

Preview updates automatically with new commits

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (6)
apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.ts (1)

1-14: Consider handling future timestamps gracefully.

If timestamp is in the future (greater than Date.now()), diffMs becomes negative, resulting in outputs like "-5m". Depending on usage, you may want to guard against this:

 export function formatRelativeTime(timestamp: number): string {
 	const now = Date.now();
 	const diffMs = now - timestamp;
+	if (diffMs < 0) return "now"; // Future timestamp fallback
 	const diffMinutes = Math.floor(diffMs / 60000);

This is a minor edge case since lastCommitDate should always be in the past, but it adds defensive handling.

apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.test.ts (2)

19-25: Consider splitting to one assertion per test.

Per coding guidelines, tests should have one assert per test. Each test case currently contains multiple assertions, which can make it harder to identify which specific case failed.

Consider restructuring using it.each or separate test cases:

describe("returns 'now' for timestamps less than 1 minute ago", () => {
	it("returns 'now' for current time", () => {
		mockNow();
		expect(formatRelativeTime(NOW)).toBe("now");
		restoreNow();
	});

	it("returns 'now' for 30 seconds ago", () => {
		mockNow();
		expect(formatRelativeTime(NOW - 30 * 1000)).toBe("now");
		restoreNow();
	});
	// ...
});

Or use it.each for parameterized tests.

Based on coding guidelines: "Tests should have one assert per test".


10-17: Consider using beforeEach/afterEach for mock lifecycle.

The manual mockNow()/restoreNow() calls in each test are repetitive and could miss restoration if a test throws. Using test hooks improves reliability:

+	beforeEach(() => {
+		Date.now = () => NOW;
+	});
+
+	afterEach(() => {
+		Date.now = originalDateNow;
+	});

-	const mockNow = () => {
-		Date.now = () => NOW;
-	};
-	const restoreNow = () => {
-		Date.now = originalDateNow;
-	};

Then remove mockNow() and restoreNow() calls from individual tests.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceHeader/WorkspaceHeader.tsx (2)

48-63: checkedOutBranches Set causes unnecessary recalculations.

checkedOutBranches is a new Set instance on every render, making the useMemo dependency referentially unstable. The memoization won't prevent recalculations.

Move the Set creation inside the useMemo or memoize it separately:

-	const localBranches = branchData?.local ?? [];
-	const checkedOutBranches = new Set(branchData?.checkedOutBranches ?? []);
-
-	// Sort: current branch first, then other checked out branches, then the rest (already sorted by commit date)
-	const sortedBranches = useMemo(
-		() => [
+	const localBranches = branchData?.local ?? [];
+	const checkedOutBranchesArray = branchData?.checkedOutBranches ?? [];
+
+	const { sortedBranches, checkedOutBranches } = useMemo(() => {
+		const checkedOutSet = new Set(checkedOutBranchesArray);
+		return {
+			checkedOutBranches: checkedOutSet,
+			sortedBranches: [
-			...localBranches.filter((b) => b.branch === currentBranch),
-			...localBranches.filter(
-				(b) => checkedOutBranches.has(b.branch) && b.branch !== currentBranch,
-			),
-			...localBranches.filter(
-				(b) => !checkedOutBranches.has(b.branch) && b.branch !== currentBranch,
-			),
-		],
-		[localBranches, checkedOutBranches, currentBranch],
-	);
+				...localBranches.filter((b) => b.branch === currentBranch),
+				...localBranches.filter(
+					(b) => checkedOutSet.has(b.branch) && b.branch !== currentBranch,
+				),
+				...localBranches.filter(
+					(b) => !checkedOutSet.has(b.branch) && b.branch !== currentBranch,
+				),
+			],
+		};
+	}, [localBranches, checkedOutBranchesArray, currentBranch]);

79-91: Consider adding loading feedback during branch switch.

The handleBranchChange function closes the popover immediately but provides no visual feedback while the mutation is in flight. Users may be confused if the switch takes time or if there are uncommitted changes.

Consider showing a loading state or keeping the popover open until completion:

 const handleBranchChange = async (branch: string) => {
 	if (!worktreePath || branch === currentBranch) return;
-	setOpen(false);
 	try {
 		await switchBranch.mutateAsync({ worktreePath, branch });
+		setOpen(false);
 		utils.workspaces.getActive.invalidate();
 		utils.changes.getBranches.invalidate();
 	} catch (error) {
+		setOpen(false);
 		toast.error(
 			error instanceof Error ? error.message : "Failed to switch branch",
 		);
 	}
 };

Alternatively, use switchBranch.isPending to show a loading indicator on the button.

apps/desktop/src/lib/trpc/routers/changes/changes.ts (1)

50-74: Consider explicit validation for the parsing logic.

The git for-each-ref approach is efficient and well-structured. The try-catch with fallback to zero dates provides good error handling.

For added robustness, consider explicitly checking if lastSpaceIdx is -1 before parsing, though the current try-catch and includes-check already provide safety:

 const lastSpaceIdx = line.lastIndexOf(" ");
+if (lastSpaceIdx === -1) continue;
 const branch = line.substring(0, lastSpaceIdx);
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b3fc018 and e725351.

📒 Files selected for processing (7)
  • apps/desktop/src/lib/trpc/routers/changes/changes.ts (5 hunks)
  • apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.test.ts (1 hunks)
  • apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.ts (1 hunks)
  • apps/desktop/src/renderer/lib/formatRelativeTime/index.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceHeader/WorkspaceHeader.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx (2 hunks)
💤 Files with no reviewable changes (1)
  • apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx
🧰 Additional context used
📓 Path-based instructions (7)
apps/desktop/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

For Electron interprocess communication, ALWAYS use tRPC as defined in src/lib/trpc

Files:

  • apps/desktop/src/renderer/lib/formatRelativeTime/index.ts
  • apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx
  • apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.test.ts
  • apps/desktop/src/lib/trpc/routers/changes/changes.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceHeader/WorkspaceHeader.tsx
apps/desktop/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

apps/desktop/**/*.{ts,tsx}: Please use alias as defined in tsconfig.json when possible
Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary

Files:

  • apps/desktop/src/renderer/lib/formatRelativeTime/index.ts
  • apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx
  • apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.test.ts
  • apps/desktop/src/lib/trpc/routers/changes/changes.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceHeader/WorkspaceHeader.tsx
**/*.{ts,tsx,js,jsx,json}

📄 CodeRabbit inference engine (AGENTS.md)

Use Biome for code formatting and linting, running at root level for speed

Files:

  • apps/desktop/src/renderer/lib/formatRelativeTime/index.ts
  • apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx
  • apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.test.ts
  • apps/desktop/src/lib/trpc/routers/changes/changes.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceHeader/WorkspaceHeader.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Avoid any type and prioritize type safety in TypeScript code

Files:

  • apps/desktop/src/renderer/lib/formatRelativeTime/index.ts
  • apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx
  • apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.test.ts
  • apps/desktop/src/lib/trpc/routers/changes/changes.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceHeader/WorkspaceHeader.tsx
apps/desktop/src/renderer/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Call IPC methods from renderer process using window.ipcRenderer.invoke with type-safe object parameters

Files:

  • apps/desktop/src/renderer/lib/formatRelativeTime/index.ts
  • apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx
  • apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.test.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceHeader/WorkspaceHeader.tsx
**/components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/components/**/*.{ts,tsx}: Structure project folders as one folder per component with PascalCase naming (ComponentName/ComponentName.tsx + index.ts barrel export)
Co-locate component dependencies (utils, hooks, constants, config, tests, stories) next to the file using them
Use one component per file (no multi-component files)

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceHeader/WorkspaceHeader.tsx
apps/desktop/**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

apps/desktop/**/*.test.{ts,tsx,js,jsx}: Tests should have one assert per test
Tests should be readable
Tests should be fast
Tests should be independent
Tests should be repeatable

Files:

  • apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.test.ts
🧠 Learnings (8)
📚 Learning: 2025-11-24T21:33:13.267Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-11-24T21:33:13.267Z
Learning: Applies to apps/desktop/**/*.{ts,tsx} : Please use alias as defined in `tsconfig.json` when possible

Applied to files:

  • apps/desktop/src/renderer/lib/formatRelativeTime/index.ts
📚 Learning: 2025-12-12T05:45:09.686Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-12T05:45:09.686Z
Learning: Applies to apps/desktop/src/renderer/**/*.{ts,tsx,js,jsx} apps/desktop/src/lib/**/*.{ts,tsx,js,jsx} apps/desktop/src/shared/**/*.{ts,tsx,js,tsx} : Never import Node.js modules in renderer process or shared code, only in main process

Applied to files:

  • apps/desktop/src/renderer/lib/formatRelativeTime/index.ts
📚 Learning: 2025-11-24T21:33:13.267Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-11-24T21:33:13.267Z
Learning: Applies to apps/desktop/**/*.test.{ts,tsx,js,jsx} : Tests should be repeatable

Applied to files:

  • apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.test.ts
📚 Learning: 2025-11-24T21:33:13.267Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-11-24T21:33:13.267Z
Learning: Applies to apps/desktop/**/*.test.{ts,tsx,js,jsx} : Tests should be readable

Applied to files:

  • apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.test.ts
📚 Learning: 2025-11-24T21:33:13.267Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-11-24T21:33:13.267Z
Learning: Applies to apps/desktop/**/*.test.{ts,tsx,js,jsx} : Tests should be independent

Applied to files:

  • apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.test.ts
📚 Learning: 2025-11-24T21:33:13.267Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-11-24T21:33:13.267Z
Learning: Applies to apps/desktop/**/*.test.{ts,tsx,js,jsx} : Tests should be fast

Applied to files:

  • apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.test.ts
📚 Learning: 2025-12-12T05:45:09.686Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-12T05:45:09.686Z
Learning: Applies to **/components/**/*.test.{ts,tsx} : Place tests co-located with component files using .test.tsx suffix

Applied to files:

  • apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.test.ts
📚 Learning: 2025-12-12T05:45:09.686Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-12T05:45:09.686Z
Learning: Applies to packages/db/src/**/*.{ts,tsx} : Use Drizzle ORM for all database operations in the `packages/db` package

Applied to files:

  • apps/desktop/src/lib/trpc/routers/changes/changes.ts
🧬 Code graph analysis (3)
apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.ts (1)
apps/desktop/src/renderer/lib/formatRelativeTime/index.ts (1)
  • formatRelativeTime (1-1)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx (4)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceHeader/WorkspaceHeader.tsx (1)
  • WorkspaceHeader (25-211)
packages/ui/src/components/ui/resizable.tsx (3)
  • ResizablePanelGroup (56-56)
  • ResizablePanel (56-56)
  • ResizableHandle (56-56)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsx (1)
  • Sidebar (7-29)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/index.tsx (1)
  • ContentView (5-13)
apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.test.ts (1)
apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.ts (1)
  • formatRelativeTime (1-14)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Deploy Admin
  • GitHub Check: Deploy Web
  • GitHub Check: Deploy Marketing
  • GitHub Check: Deploy Docs
  • GitHub Check: Deploy API
  • GitHub Check: Build
🔇 Additional comments (5)
apps/desktop/src/renderer/lib/formatRelativeTime/index.ts (1)

1-1: LGTM!

Clean barrel export following project conventions.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx (2)

15-15: LGTM!

Clean import following project alias conventions.


109-139: LGTM!

The layout restructure cleanly integrates WorkspaceHeader above the resizable panel group. The existing resize state management is properly preserved, and the worktreePath prop is correctly passed from the active workspace query.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceHeader/WorkspaceHeader.tsx (1)

159-183: LGTM!

The branch list rendering logic is well-structured with clear conditional formatting for current, checked-out, and available branches. The relative time display using formatRelativeTime integrates cleanly.

apps/desktop/src/lib/trpc/routers/changes/changes.ts (1)

22-109: LGTM: getBranches enhancement aligns well with PR objectives.

The refactored getBranches procedure effectively:

  • Retrieves last commit dates for local branches via a single git command
  • Computes checked-out branches from the database to mark them as "(in use)" in the UI
  • Provides appropriate fallback handling

The implementation supports the PR's goal of displaying relative time since last commit and marking checked-out branches.

Comment thread apps/desktop/src/lib/trpc/routers/changes/changes.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceHeader/WorkspaceHeader.tsx (1)

34-35: Home directory regex is macOS-specific.

This issue was already flagged in a previous review. The regex /^\/Users\/[^/]+/ only matches macOS home directories and won't work for Linux or Windows paths.

🧹 Nitpick comments (1)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceHeader/WorkspaceHeader.tsx (1)

79-91: Consider adding loading state during branch switch.

The branch switching logic is well-implemented with proper guards, error handling, and query invalidation. However, there's no visual feedback while the mutation is in progress.

Consider adding a loading state:

 export function WorkspaceHeader({ worktreePath }: WorkspaceHeaderProps) {
 	const [open, setOpen] = useState(false);
 	const [search, setSearch] = useState("");
 	const [visibleCount, setVisibleCount] = useState(PAGE_SIZE);
+	const [isSwitching, setIsSwitching] = useState(false);

 	// ... rest of code ...

 	const handleBranchChange = async (branch: string) => {
 		if (!worktreePath || branch === currentBranch) return;
 		setOpen(false);
+		setIsSwitching(true);
 		try {
 			await switchBranch.mutateAsync({ worktreePath, branch });
 			utils.workspaces.getActive.invalidate();
 			utils.changes.getBranches.invalidate();
 		} catch (error) {
 			toast.error(
 				error instanceof Error ? error.message : "Failed to switch branch",
 			);
+		} finally {
+			setIsSwitching(false);
 		}
 	};

Then use isSwitching to disable the button or show a spinner.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e725351 and ca85ce9.

📒 Files selected for processing (7)
  • apps/desktop/src/lib/trpc/routers/changes/changes.ts (5 hunks)
  • apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.test.ts (1 hunks)
  • apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.ts (1 hunks)
  • apps/desktop/src/renderer/lib/formatRelativeTime/index.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceHeader/WorkspaceHeader.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx (2 hunks)
💤 Files with no reviewable changes (1)
  • apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/desktop/src/renderer/lib/formatRelativeTime/index.ts
  • apps/desktop/src/lib/trpc/routers/changes/changes.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx
🧰 Additional context used
📓 Path-based instructions (7)
apps/desktop/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

For Electron interprocess communication, ALWAYS use tRPC as defined in src/lib/trpc

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceHeader/WorkspaceHeader.tsx
  • apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.test.ts
  • apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.ts
apps/desktop/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

apps/desktop/**/*.{ts,tsx}: Please use alias as defined in tsconfig.json when possible
Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceHeader/WorkspaceHeader.tsx
  • apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.test.ts
  • apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.ts
**/*.{ts,tsx,js,jsx,json}

📄 CodeRabbit inference engine (AGENTS.md)

Use Biome for code formatting and linting, running at root level for speed

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceHeader/WorkspaceHeader.tsx
  • apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.test.ts
  • apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Avoid any type and prioritize type safety in TypeScript code

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceHeader/WorkspaceHeader.tsx
  • apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.test.ts
  • apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.ts
**/components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/components/**/*.{ts,tsx}: Structure project folders as one folder per component with PascalCase naming (ComponentName/ComponentName.tsx + index.ts barrel export)
Co-locate component dependencies (utils, hooks, constants, config, tests, stories) next to the file using them
Use one component per file (no multi-component files)

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceHeader/WorkspaceHeader.tsx
apps/desktop/src/renderer/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Call IPC methods from renderer process using window.ipcRenderer.invoke with type-safe object parameters

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceHeader/WorkspaceHeader.tsx
  • apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.test.ts
  • apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.ts
apps/desktop/**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

apps/desktop/**/*.test.{ts,tsx,js,jsx}: Tests should have one assert per test
Tests should be readable
Tests should be fast
Tests should be independent
Tests should be repeatable

Files:

  • apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.test.ts
🧠 Learnings (6)
📚 Learning: 2025-11-24T21:33:13.267Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-11-24T21:33:13.267Z
Learning: Applies to apps/desktop/**/*.{ts,tsx} : Please use alias as defined in `tsconfig.json` when possible

Applied to files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceHeader/WorkspaceHeader.tsx
📚 Learning: 2025-11-24T21:33:13.267Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-11-24T21:33:13.267Z
Learning: Applies to apps/desktop/**/*.test.{ts,tsx,js,jsx} : Tests should be repeatable

Applied to files:

  • apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.test.ts
📚 Learning: 2025-11-24T21:33:13.267Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-11-24T21:33:13.267Z
Learning: Applies to apps/desktop/**/*.test.{ts,tsx,js,jsx} : Tests should be independent

Applied to files:

  • apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.test.ts
📚 Learning: 2025-11-24T21:33:13.267Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-11-24T21:33:13.267Z
Learning: Applies to apps/desktop/**/*.test.{ts,tsx,js,jsx} : Tests should be fast

Applied to files:

  • apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.test.ts
📚 Learning: 2025-11-24T21:33:13.267Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-11-24T21:33:13.267Z
Learning: Applies to apps/desktop/**/*.test.{ts,tsx,js,jsx} : Tests should be readable

Applied to files:

  • apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.test.ts
📚 Learning: 2025-12-12T05:45:09.686Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-12T05:45:09.686Z
Learning: Applies to **/components/**/*.test.{ts,tsx} : Place tests co-located with component files using .test.tsx suffix

Applied to files:

  • apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.test.ts
🧬 Code graph analysis (3)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceHeader/WorkspaceHeader.tsx (4)
packages/ui/src/components/ui/tooltip.tsx (3)
  • Tooltip (76-76)
  • TooltipTrigger (76-76)
  • TooltipContent (76-76)
packages/ui/src/components/ui/command.tsx (5)
  • Command (175-175)
  • CommandInput (177-177)
  • CommandList (178-178)
  • CommandEmpty (179-179)
  • CommandItem (181-181)
apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.ts (1)
  • formatRelativeTime (1-14)
apps/desktop/src/renderer/components/OpenInButton/OpenInButton.tsx (1)
  • OpenInButton (86-226)
apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.test.ts (1)
apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.ts (1)
  • formatRelativeTime (1-14)
apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.ts (1)
apps/desktop/src/renderer/lib/formatRelativeTime/index.ts (1)
  • formatRelativeTime (1-1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Deploy Web
  • GitHub Check: Deploy Admin
  • GitHub Check: Deploy Docs
  • GitHub Check: Build
🔇 Additional comments (7)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceHeader/WorkspaceHeader.tsx (7)

1-19: LGTM!

Imports are well-organized and use appropriate aliases. The PAGE_SIZE constant for pagination is clearly defined.


37-46: LGTM!

TRPC queries and mutations are properly set up with type safety. The conditional query enablement based on worktreePath prevents unnecessary requests.


48-64: LGTM!

The branch sorting logic correctly prioritizes the current branch, then checked-out branches, then the rest. The useMemo optimization with proper dependencies prevents unnecessary re-sorting.


67-73: LGTM!

The search filtering is correctly implemented with case-insensitive matching and proper memoization dependencies.


75-77: LGTM!

Pagination logic is clean and straightforward, correctly calculating visible branches and remaining items.


93-99: LGTM!

The popover state management correctly resets search and pagination when closing, ensuring a clean state on reopen.


102-209: LGTM!

The rendering logic is well-structured with:

  • Proper conditional rendering based on data availability
  • Accessible tooltip for path display
  • Clean CSS-based text truncation with fade effect
  • Proper integration of Command component for searchable branch selection
  • Correct handling of branch states (current, checked-out, disabled)
  • Pagination controls for large branch lists

The lastCommitDate > 0 check on line 164 correctly prevents displaying stale time formatting for invalid timestamps.

Comment on lines +19 to +74
it('returns "now" for timestamps less than 1 minute ago', () => {
mockNow();
expect(formatRelativeTime(NOW)).toBe("now");
expect(formatRelativeTime(NOW - 30 * 1000)).toBe("now"); // 30 seconds ago
expect(formatRelativeTime(NOW - 59 * 1000)).toBe("now"); // 59 seconds ago
restoreNow();
});

it("returns minutes for timestamps between 1-59 minutes ago", () => {
mockNow();
expect(formatRelativeTime(NOW - 1 * MINUTE)).toBe("1m");
expect(formatRelativeTime(NOW - 5 * MINUTE)).toBe("5m");
expect(formatRelativeTime(NOW - 30 * MINUTE)).toBe("30m");
expect(formatRelativeTime(NOW - 59 * MINUTE)).toBe("59m");
restoreNow();
});

it("returns hours for timestamps between 1-23 hours ago", () => {
mockNow();
expect(formatRelativeTime(NOW - 1 * HOUR)).toBe("1h");
expect(formatRelativeTime(NOW - 5 * HOUR)).toBe("5h");
expect(formatRelativeTime(NOW - 12 * HOUR)).toBe("12h");
expect(formatRelativeTime(NOW - 23 * HOUR)).toBe("23h");
restoreNow();
});

it("returns days for timestamps between 1-29 days ago", () => {
mockNow();
expect(formatRelativeTime(NOW - 1 * DAY)).toBe("1d");
expect(formatRelativeTime(NOW - 7 * DAY)).toBe("7d");
expect(formatRelativeTime(NOW - 14 * DAY)).toBe("14d");
expect(formatRelativeTime(NOW - 29 * DAY)).toBe("29d");
restoreNow();
});

it("returns months for timestamps 30+ days ago", () => {
mockNow();
expect(formatRelativeTime(NOW - 30 * DAY)).toBe("1mo");
expect(formatRelativeTime(NOW - 60 * DAY)).toBe("2mo");
expect(formatRelativeTime(NOW - 90 * DAY)).toBe("3mo");
expect(formatRelativeTime(NOW - 365 * DAY)).toBe("12mo");
restoreNow();
});

it("handles edge cases at boundaries", () => {
mockNow();
// Just under 1 hour should show 59m
expect(formatRelativeTime(NOW - 59 * MINUTE - 59 * 1000)).toBe("59m");
// Exactly 1 hour should show 1h
expect(formatRelativeTime(NOW - 60 * MINUTE)).toBe("1h");
// Just under 1 day should show 23h
expect(formatRelativeTime(NOW - 23 * HOUR - 59 * MINUTE)).toBe("23h");
// Exactly 1 day should show 1d
expect(formatRelativeTime(NOW - 24 * HOUR)).toBe("1d");
restoreNow();
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Split tests to have one assertion per test case.

Each test currently contains multiple assertions (3-4 per test), which violates the coding guideline: "Tests should have one assert per test".

As per coding guidelines, split each test into separate test cases. For example:

-	it('returns "now" for timestamps less than 1 minute ago', () => {
+	it('returns "now" for current timestamp', () => {
 		mockNow();
 		expect(formatRelativeTime(NOW)).toBe("now");
-		expect(formatRelativeTime(NOW - 30 * 1000)).toBe("now"); // 30 seconds ago
-		expect(formatRelativeTime(NOW - 59 * 1000)).toBe("now"); // 59 seconds ago
 		restoreNow();
 	});
+
+	it('returns "now" for timestamp 30 seconds ago', () => {
+		mockNow();
+		expect(formatRelativeTime(NOW - 30 * 1000)).toBe("now");
+		restoreNow();
+	});
+
+	it('returns "now" for timestamp 59 seconds ago', () => {
+		mockNow();
+		expect(formatRelativeTime(NOW - 59 * 1000)).toBe("now");
+		restoreNow();
+	});

Apply similar splitting for all remaining test cases.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('returns "now" for timestamps less than 1 minute ago', () => {
mockNow();
expect(formatRelativeTime(NOW)).toBe("now");
expect(formatRelativeTime(NOW - 30 * 1000)).toBe("now"); // 30 seconds ago
expect(formatRelativeTime(NOW - 59 * 1000)).toBe("now"); // 59 seconds ago
restoreNow();
});
it("returns minutes for timestamps between 1-59 minutes ago", () => {
mockNow();
expect(formatRelativeTime(NOW - 1 * MINUTE)).toBe("1m");
expect(formatRelativeTime(NOW - 5 * MINUTE)).toBe("5m");
expect(formatRelativeTime(NOW - 30 * MINUTE)).toBe("30m");
expect(formatRelativeTime(NOW - 59 * MINUTE)).toBe("59m");
restoreNow();
});
it("returns hours for timestamps between 1-23 hours ago", () => {
mockNow();
expect(formatRelativeTime(NOW - 1 * HOUR)).toBe("1h");
expect(formatRelativeTime(NOW - 5 * HOUR)).toBe("5h");
expect(formatRelativeTime(NOW - 12 * HOUR)).toBe("12h");
expect(formatRelativeTime(NOW - 23 * HOUR)).toBe("23h");
restoreNow();
});
it("returns days for timestamps between 1-29 days ago", () => {
mockNow();
expect(formatRelativeTime(NOW - 1 * DAY)).toBe("1d");
expect(formatRelativeTime(NOW - 7 * DAY)).toBe("7d");
expect(formatRelativeTime(NOW - 14 * DAY)).toBe("14d");
expect(formatRelativeTime(NOW - 29 * DAY)).toBe("29d");
restoreNow();
});
it("returns months for timestamps 30+ days ago", () => {
mockNow();
expect(formatRelativeTime(NOW - 30 * DAY)).toBe("1mo");
expect(formatRelativeTime(NOW - 60 * DAY)).toBe("2mo");
expect(formatRelativeTime(NOW - 90 * DAY)).toBe("3mo");
expect(formatRelativeTime(NOW - 365 * DAY)).toBe("12mo");
restoreNow();
});
it("handles edge cases at boundaries", () => {
mockNow();
// Just under 1 hour should show 59m
expect(formatRelativeTime(NOW - 59 * MINUTE - 59 * 1000)).toBe("59m");
// Exactly 1 hour should show 1h
expect(formatRelativeTime(NOW - 60 * MINUTE)).toBe("1h");
// Just under 1 day should show 23h
expect(formatRelativeTime(NOW - 23 * HOUR - 59 * MINUTE)).toBe("23h");
// Exactly 1 day should show 1d
expect(formatRelativeTime(NOW - 24 * HOUR)).toBe("1d");
restoreNow();
});
it('returns "now" for current timestamp', () => {
mockNow();
expect(formatRelativeTime(NOW)).toBe("now");
restoreNow();
});
it('returns "now" for timestamp 30 seconds ago', () => {
mockNow();
expect(formatRelativeTime(NOW - 30 * 1000)).toBe("now");
restoreNow();
});
it('returns "now" for timestamp 59 seconds ago', () => {
mockNow();
expect(formatRelativeTime(NOW - 59 * 1000)).toBe("now");
restoreNow();
});
it("returns minutes for 1 minute ago", () => {
mockNow();
expect(formatRelativeTime(NOW - 1 * MINUTE)).toBe("1m");
restoreNow();
});
it("returns minutes for 5 minutes ago", () => {
mockNow();
expect(formatRelativeTime(NOW - 5 * MINUTE)).toBe("5m");
restoreNow();
});
it("returns minutes for 30 minutes ago", () => {
mockNow();
expect(formatRelativeTime(NOW - 30 * MINUTE)).toBe("30m");
restoreNow();
});
it("returns minutes for 59 minutes ago", () => {
mockNow();
expect(formatRelativeTime(NOW - 59 * MINUTE)).toBe("59m");
restoreNow();
});
it("returns hours for 1 hour ago", () => {
mockNow();
expect(formatRelativeTime(NOW - 1 * HOUR)).toBe("1h");
restoreNow();
});
it("returns hours for 5 hours ago", () => {
mockNow();
expect(formatRelativeTime(NOW - 5 * HOUR)).toBe("5h");
restoreNow();
});
it("returns hours for 12 hours ago", () => {
mockNow();
expect(formatRelativeTime(NOW - 12 * HOUR)).toBe("12h");
restoreNow();
});
it("returns hours for 23 hours ago", () => {
mockNow();
expect(formatRelativeTime(NOW - 23 * HOUR)).toBe("23h");
restoreNow();
});
it("returns days for 1 day ago", () => {
mockNow();
expect(formatRelativeTime(NOW - 1 * DAY)).toBe("1d");
restoreNow();
});
it("returns days for 7 days ago", () => {
mockNow();
expect(formatRelativeTime(NOW - 7 * DAY)).toBe("7d");
restoreNow();
});
it("returns days for 14 days ago", () => {
mockNow();
expect(formatRelativeTime(NOW - 14 * DAY)).toBe("14d");
restoreNow();
});
it("returns days for 29 days ago", () => {
mockNow();
expect(formatRelativeTime(NOW - 29 * DAY)).toBe("29d");
restoreNow();
});
it("returns months for 30 days ago", () => {
mockNow();
expect(formatRelativeTime(NOW - 30 * DAY)).toBe("1mo");
restoreNow();
});
it("returns months for 60 days ago", () => {
mockNow();
expect(formatRelativeTime(NOW - 60 * DAY)).toBe("2mo");
restoreNow();
});
it("returns months for 90 days ago", () => {
mockNow();
expect(formatRelativeTime(NOW - 90 * DAY)).toBe("3mo");
restoreNow();
});
it("returns months for 365 days ago", () => {
mockNow();
expect(formatRelativeTime(NOW - 365 * DAY)).toBe("12mo");
restoreNow();
});
it("shows 59m just under 1 hour", () => {
mockNow();
expect(formatRelativeTime(NOW - 59 * MINUTE - 59 * 1000)).toBe("59m");
restoreNow();
});
it("shows 1h exactly 1 hour", () => {
mockNow();
expect(formatRelativeTime(NOW - 60 * MINUTE)).toBe("1h");
restoreNow();
});
it("shows 23h just under 1 day", () => {
mockNow();
expect(formatRelativeTime(NOW - 23 * HOUR - 59 * MINUTE)).toBe("23h");
restoreNow();
});
it("shows 1d exactly 1 day", () => {
mockNow();
expect(formatRelativeTime(NOW - 24 * HOUR)).toBe("1d");
restoreNow();
});
🤖 Prompt for AI Agents
In apps/desktop/src/renderer/lib/formatRelativeTime/formatRelativeTime.test.ts
around lines 19-74, the tests contain multiple assertions per it() block; split
each assertion into its own test so there is exactly one expect() per it(). For
each original group, create separate it() cases for each expectation (e.g.,
"returns now for 30s ago", "returns now for 59s ago"; "returns 1m", "returns
5m", etc.), call mockNow() at the start and restoreNow() at the end of each new
test (or use beforeEach/afterEach to set/restore), keep descriptive test names
matching the single assertion, and preserve existing constants (NOW, MINUTE,
HOUR, DAY) and edge-case checks as individual tests.

- Display worktree path with folder icon in workspace header
- Add searchable branch dropdown with pagination (10 branches per page)
- Show relative time since last commit for each branch (e.g., 2h, 3d)
- Mark checked-out branches as disabled with "(in use)" label
- Sort branches: current first, then checked-out, then by recent commit
- Add switchBranch tRPC mutation to change branches within worktree
- Extract formatRelativeTime utility with tests

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Extract WorkspaceHeader into smaller, focused components following project structure guidelines:
- PathDisplay: Shows truncated path with tooltip
- BranchSelector: Branch dropdown with search, pagination, and checked-out branch handling

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Kitenite and others added 3 commits December 17, 2025 17:19
…play

Replace macOS-specific regex with shared shortenHomePath utility that
uses trpc to get the home directory and handles Windows backslashes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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