Skip to content

add tab restore functionality#2411

Merged
yujonglee merged 3 commits intomainfrom
feat/tab-restore-functionality
Dec 19, 2025
Merged

add tab restore functionality#2411
yujonglee merged 3 commits intomainfrom
feat/tab-restore-functionality

Conversation

@ComputelessComputer
Copy link
Collaborator

@ComputelessComputer ComputelessComputer commented Dec 19, 2025

Overview

  • Implemented tab restore functionality
  • Added middleware to support tab restoration
  • Created Zustand store actions for managing restored tabs
  • Introduced keyboard shortcut for restoring last closed tab

This is part 1 of 3 in a stack made with GitButler:

@netlify
Copy link

netlify bot commented Dec 19, 2025

Deploy Preview for hyprnote-storybook ready!

Name Link
🔨 Latest commit 3bcc180
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote-storybook/deploys/6944c52f20247c0008846586
😎 Deploy Preview https://deploy-preview-2411--hyprnote-storybook.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link

netlify bot commented Dec 19, 2025

Deploy Preview for hyprnote ready!

Name Link
🔨 Latest commit 3bcc180
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote/deploys/6944c52f02fdba0008a92c93
😎 Deploy Preview https://deploy-preview-2411--hyprnote.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 19, 2025

📝 Walkthrough

Walkthrough

Adds a tab-restore feature: a new restore slice and middleware in the tabs Zustand store to track closed tabs (capped at 10) and reopen the last closed tab, plus a keyboard shortcut (mod+shift+t) wired to the restore action.

Changes

Cohort / File(s) Change Summary
Keyboard Shortcut Integration
apps/desktop/src/components/main/body/index.tsx
useTabsShortcuts now selects restoreLastClosedTab from the tabs store and registers a mod+shift+t hotkey that calls it with standard hotkey options (preventDefault, enableOnFormTags, enableOnContentEditable).
Store Type & Composition
apps/desktop/src/store/zustand/tabs/index.ts
Extended store types to include RestoreState and RestoreActions; composed createRestoreSlice into the store and applied restoreMiddleware in the middleware chain so restore state/actions are exposed by useTabs.
Restore Implementation
apps/desktop/src/store/zustand/tabs/restore.ts
New restore slice and middleware: adds closedTabs: Tab[], restoreLastClosedTab() action, tabToTabInput helper, MAX_CLOSED_TABS = 10, and restoreMiddleware implementation that detects closed tabs by diffing previous/next tabs and appends them to closedTabs (trimmed to cap). Exports createRestoreSlice and restoreMiddleware.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Review restore middleware diffing logic (slotId comparison) and edge cases during rapid state updates.
  • Check typing/any cast scope where closedTabs is updated.
  • Verify tabToTabInput strips runtime-only fields and restoreLastClosedTab() correctly reopens a tab and trims the history.

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 'add tab restore functionality' clearly and concisely summarizes the main change across all modified files, which implement tab restoration features including middleware, store actions, and keyboard shortcuts.
Description check ✅ Passed The description is directly related to the changeset, outlining the implementation of tab restore functionality, middleware support, Zustand store actions, and keyboard shortcuts that match the code changes.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/tab-restore-functionality

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.

Copy link
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 (1)
apps/desktop/src/store/zustand/tabs/restore.ts (1)

15-22: Remove the type assertion and use Omit for type-safe property exclusion.

The type assertion incorrectly asserts that Tab has a pinned property, which doesn't exist. Since only active and slotId need to be excluded, use type TabInput = Omit<Tab, 'active' | 'slotId'> or refactor the function to rely on proper type inference instead of unsafe assertions.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0fce426 and 1764664.

📒 Files selected for processing (3)
  • apps/desktop/src/components/main/body/index.tsx (2 hunks)
  • apps/desktop/src/store/zustand/tabs/index.ts (1 hunks)
  • apps/desktop/src/store/zustand/tabs/restore.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*

📄 CodeRabbit inference engine (AGENTS.md)

Format using dprint fmt from the root. Do not use cargo fmt.

Files:

  • apps/desktop/src/components/main/body/index.tsx
  • apps/desktop/src/store/zustand/tabs/index.ts
  • apps/desktop/src/store/zustand/tabs/restore.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Avoid creating a bunch of types/interfaces if they are not shared. Especially for function props. Just inline them.
Never do manual state management for form/mutation. Use useForm from tanstack-form and useQuery/useMutation from tanstack-query for 99% cases.

Files:

  • apps/desktop/src/components/main/body/index.tsx
  • apps/desktop/src/store/zustand/tabs/index.ts
  • apps/desktop/src/store/zustand/tabs/restore.ts
**/*.{ts,tsx,rs,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

By default, avoid writing comments at all. If you write one, it should be about 'Why', not 'What'.

Files:

  • apps/desktop/src/components/main/body/index.tsx
  • apps/desktop/src/store/zustand/tabs/index.ts
  • apps/desktop/src/store/zustand/tabs/restore.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: If there are many classNames with conditional logic, use cn (import from @hypr/utils). Always pass an array and split by logical grouping.
Use motion/react instead of framer-motion.

Files:

  • apps/desktop/src/components/main/body/index.tsx
  • apps/desktop/src/store/zustand/tabs/index.ts
  • apps/desktop/src/store/zustand/tabs/restore.ts
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.ts: Agent implementations should use TypeScript and follow the established architectural patterns defined in the agent framework
Agent communication should use defined message protocols and interfaces

Files:

  • apps/desktop/src/store/zustand/tabs/index.ts
  • apps/desktop/src/store/zustand/tabs/restore.ts
🧬 Code graph analysis (1)
apps/desktop/src/components/main/body/index.tsx (2)
apps/desktop/src/store/zustand/tabs/index.ts (1)
  • useTabs (36-48)
extensions/shared/types/hypr-extension.d.ts (1)
  • useTabs (81-107)
🪛 GitHub Actions: .github/workflows/desktop_ci.yaml
apps/desktop/src/components/main/body/index.tsx

[error] 623-623: TS2339: Property 'selectNext' does not exist on type 'BasicState & NavigationState & LifecycleState & RestoreState & BasicActions & StateBasicActions & NavigationActions & LifecycleActions & RestoreActions'.

⏰ 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: Redirect rules - hyprnote
  • GitHub Check: Header rules - hyprnote
  • GitHub Check: Pages changed - hyprnote
  • GitHub Check: fmt
🔇 Additional comments (2)
apps/desktop/src/store/zustand/tabs/index.ts (1)

17-47: LGTM! Clean integration of restore functionality.

The restore slice is properly integrated with:

  • Type extensions for State and Actions
  • Correct middleware wrapping order (restore → lifecycle → navigation)
  • Consistent logging patterns for the new slice
apps/desktop/src/components/main/body/index.tsx (1)

694-703: LGTM! Standard hotkey implementation.

The keyboard shortcut mod+shift+t follows the browser convention for reopening closed tabs and is implemented consistently with other hotkeys in the file.

@ComputelessComputer ComputelessComputer force-pushed the feat/tab-restore-functionality branch from 1764664 to 7dd3c0a Compare December 19, 2025 03:05
@ComputelessComputer ComputelessComputer force-pushed the feat/tab-restore-functionality branch 3 times, most recently from fe44195 to fab426c Compare December 19, 2025 03:19
Copy link
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: 0

♻️ Duplicate comments (1)
apps/desktop/src/store/zustand/tabs/restore.ts (1)

60-89: Address the double state update issue flagged in previous review.

The middleware's double set() call (lines 67 and 83) remains unaddressed. This causes two separate state updates and subscriber notifications, leading to potential race conditions and performance issues. The previous review comment provides a comprehensive solution to merge both updates into a single state change.

🧹 Nitpick comments (2)
apps/desktop/src/store/zustand/tabs/restore.ts (2)

15-22: Consider simplifying type assertions.

The explicit type assertion on lines 16-20 and the cast on line 21 suggest potential type definition issues. If Tab already includes active, slotId, and pinned as required fields, the assertion as Tab & { ... } is redundant.

🔎 Simplified version if Tab properties are required
 const tabToTabInput = (tab: Tab): TabInput => {
-  const { active, slotId, pinned, ...rest } = tab as Tab & {
-    active: boolean;
-    slotId: string;
-    pinned: boolean;
-  };
-  return rest as TabInput;
+  const { active, slotId, pinned, ...rest } = tab;
+  return rest;
 };

43-58: Consider inlining the RestoreMiddleware type.

Based on the coding guideline to avoid creating types that aren't shared, this type definition could be inlined directly into the restoreMiddlewareImpl declaration, as it's only used once.

🔎 Inlined version
-type RestoreMiddleware = <
-  T extends {
-    tabs: Tab[];
-    closedTabs: Tab[];
-  },
->(
-  f: (
-    set: StoreApi<T>["setState"],
-    get: StoreApi<T>["getState"],
-    api: StoreApi<T>,
-  ) => T,
-) => (
-  set: StoreApi<T>["setState"],
-  get: StoreApi<T>["getState"],
-  api: StoreApi<T>,
-) => T;
-
-const restoreMiddlewareImpl: RestoreMiddleware =
+const restoreMiddlewareImpl = <
+  T extends {
+    tabs: Tab[];
+    closedTabs: Tab[];
+  },
+>(
+  config: (
+    set: StoreApi<T>["setState"],
+    get: StoreApi<T>["getState"],
+    api: StoreApi<T>,
+  ) => T,
+): ((
+  set: StoreApi<T>["setState"],
+  get: StoreApi<T>["getState"],
+  api: StoreApi<T>,
+) => T) =>
-  (config) => (set, get, api) => {
+  (set, get, api) => {
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1764664 and 3bcc180.

📒 Files selected for processing (3)
  • apps/desktop/src/components/main/body/index.tsx (2 hunks)
  • apps/desktop/src/store/zustand/tabs/index.ts (1 hunks)
  • apps/desktop/src/store/zustand/tabs/restore.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/desktop/src/store/zustand/tabs/index.ts
  • apps/desktop/src/components/main/body/index.tsx
🧰 Additional context used
📓 Path-based instructions (5)
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.ts: Agent implementations should use TypeScript and follow the established architectural patterns defined in the agent framework
Agent communication should use defined message protocols and interfaces

Files:

  • apps/desktop/src/store/zustand/tabs/restore.ts
**/*

📄 CodeRabbit inference engine (AGENTS.md)

Format using dprint fmt from the root. Do not use cargo fmt.

Files:

  • apps/desktop/src/store/zustand/tabs/restore.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Avoid creating a bunch of types/interfaces if they are not shared. Especially for function props. Just inline them.
Never do manual state management for form/mutation. Use useForm from tanstack-form and useQuery/useMutation from tanstack-query for 99% cases.

Files:

  • apps/desktop/src/store/zustand/tabs/restore.ts
**/*.{ts,tsx,rs,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

By default, avoid writing comments at all. If you write one, it should be about 'Why', not 'What'.

Files:

  • apps/desktop/src/store/zustand/tabs/restore.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: If there are many classNames with conditional logic, use cn (import from @hypr/utils). Always pass an array and split by logical grouping.
Use motion/react instead of framer-motion.

Files:

  • apps/desktop/src/store/zustand/tabs/restore.ts
⏰ 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). (7)
  • GitHub Check: desktop_ci (macos, depot-macos-14)
  • GitHub Check: desktop_ci (linux, depot-ubuntu-24.04-8)
  • GitHub Check: desktop_ci (linux, depot-ubuntu-22.04-8)
  • GitHub Check: fmt
  • GitHub Check: Redirect rules - hyprnote
  • GitHub Check: Header rules - hyprnote
  • GitHub Check: Pages changed - hyprnote
🔇 Additional comments (3)
apps/desktop/src/store/zustand/tabs/restore.ts (3)

1-9: LGTM!

The imports, constant definition, and RestoreState type are well-structured. The 10-tab history limit is reasonable for balancing memory usage with user convenience.


11-13: LGTM!

The RestoreActions type is concise and clearly defines the public API for tab restoration.


24-41: LGTM!

The createRestoreSlice implementation correctly handles the LIFO restoration pattern. The early return prevents errors, and the immutable update using slice is appropriate for Zustand state management.

@yujonglee yujonglee merged commit 2bd64ad into main Dec 19, 2025
16 checks passed
@yujonglee yujonglee deleted the feat/tab-restore-functionality branch December 19, 2025 04:41
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

Comments