Skip to content

Conversation

@yujonglee
Copy link
Contributor

No description provided.

@netlify
Copy link

netlify bot commented Dec 14, 2025

Deploy Preview for hyprnote-storybook ready!

Name Link
🔨 Latest commit 9b7d696
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote-storybook/deploys/693e93bdc12463000873128c
😎 Deploy Preview https://deploy-preview-2301--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.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 14, 2025

📝 Walkthrough

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.81% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description check ❓ Inconclusive No pull request description was provided by the author, making it impossible to assess relevance to the changeset. Add a pull request description explaining the new updater behaviors, UI changes, and the purpose of these modifications.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main changes: introducing new updater plugin functionality and updating the UI to display update-related information.
✨ 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 new-update-behavior

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.

@netlify
Copy link

netlify bot commented Dec 14, 2025

Deploy Preview for hyprnote ready!

Name Link
🔨 Latest commit 9b7d696
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote/deploys/693e93bbc72e6a000885dcb0
😎 Deploy Preview https://deploy-preview-2301--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.

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: 3

Caution

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

⚠️ Outside diff range comments (1)
plugins/updater2/Cargo.toml (1)

5-5: Fix invalid Rust edition.

Rust edition "2024" is not valid. The supported editions are 2015, 2018, and 2021. This will prevent compilation.

-edition = "2024"
+edition = "2021"
🧹 Nitpick comments (3)
plugins/updater2/src/job.rs (1)

43-49: Consider the state consistency on partial failure.

If set_pending_update_version fails at line 43 but the update is already staged for restart, the UpdateReadyEvent is still emitted at line 47. This could lead to:

  1. The UI showing an update is ready
  2. But get_pending_update_version returning None

Consider returning early if setting the pending version fails, or ensure the event emission is conditional on successful state persistence.

     if let Err(e) = app.set_pending_update_version(Some(version.clone())) {
         tracing::error!("failed_to_set_pending_update_version: {}", e);
+        return;
     }
plugins/updater2/src/commands.rs (1)

5-9: Consider making this command non-async (or normalize the return).
This is marked async but doesn’t await; making it sync reduces overhead/complexity. Also consider returning None for empty/whitespace versions to keep behavior consistent with the tray’s is_empty() guard.

 #[tauri::command]
 #[specta::specta]
-pub(crate) async fn get_pending_update<R: tauri::Runtime>(
+pub(crate) fn get_pending_update<R: tauri::Runtime>(
     app: tauri::AppHandle<R>,
 ) -> Result<Option<String>, String> {
-    app.get_pending_update_version().map_err(|e| e.to_string())
+    app.get_pending_update_version()
+        .map(|opt| opt.and_then(|s| {
+            let s = s.trim().to_string();
+            (!s.is_empty()).then_some(s)
+        }))
+        .map_err(|e| e.to_string())
 }
plugins/tray/src/lib.rs (1)

16-42: Add minimal error logging for TrayCheckUpdate::set_state failures.
Right now failures are swallowed (let _ = ...). At least log at warn/error so broken menus aren’t silent (and consider logging event payload/version for easier support).

             tauri_plugin_updater2::UpdateReadyEvent::listen(app, move |_event| {
-                let _ = menu_items::TrayCheckUpdate::set_state(
+                if let Err(e) = menu_items::TrayCheckUpdate::set_state(
                     &handle,
                     UpdateMenuState::RestartToApply,
-                );
+                ) {
+                    tracing::warn!("failed_to_set_tray_update_state: {e}");
+                }
             });
📜 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 aa3da55 and 240898f.

⛔ Files ignored due to path filters (7)
  • Cargo.lock is excluded by !**/*.lock
  • plugins/updater2/js/bindings.gen.ts is excluded by !**/*.gen.ts
  • plugins/updater2/permissions/autogenerated/commands/get_pending_update.toml is excluded by !plugins/**/permissions/**
  • plugins/updater2/permissions/autogenerated/commands/ping.toml is excluded by !plugins/**/permissions/**
  • plugins/updater2/permissions/autogenerated/reference.md is excluded by !plugins/**/permissions/**
  • plugins/updater2/permissions/default.toml is excluded by !plugins/**/permissions/**
  • plugins/updater2/permissions/schemas/schema.json is excluded by !plugins/**/permissions/**
📒 Files selected for processing (19)
  • apps/desktop/src/components/main/body/index.tsx (2 hunks)
  • apps/desktop/src/components/main/body/update.tsx (1 hunks)
  • apps/web/content-collections.ts (1 hunks)
  • apps/web/content/docs/_/0.installation.mdx (1 hunks)
  • apps/web/content/docs/_/1.update.mdx (1 hunks)
  • apps/web/content/docs/_/2.uninstall.mdx (1 hunks)
  • apps/web/src/routes/_view/docs/-structure.ts (1 hunks)
  • plugins/tray/Cargo.toml (1 hunks)
  • plugins/tray/src/lib.rs (1 hunks)
  • plugins/tray/src/menu_items/tray_check_update.rs (2 hunks)
  • plugins/updater2/Cargo.toml (2 hunks)
  • plugins/updater2/build.rs (1 hunks)
  • plugins/updater2/src/commands.rs (1 hunks)
  • plugins/updater2/src/error.rs (1 hunks)
  • plugins/updater2/src/events.rs (1 hunks)
  • plugins/updater2/src/ext.rs (3 hunks)
  • plugins/updater2/src/job.rs (1 hunks)
  • plugins/updater2/src/lib.rs (2 hunks)
  • plugins/updater2/src/store.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{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 instead.
Never do manual state management for form/mutation. Use useForm (from tanstack-form) and useQuery/useMutation (from tanstack-query) instead for 99% of cases. Avoid patterns like setError.
If there are many classNames with conditional logic, use cn (import from @hypr/utils). It is similar to clsx. Always pass an array and split by logical grouping.
Use motion/react instead of framer-motion.

Files:

  • apps/desktop/src/components/main/body/update.tsx
  • apps/web/content-collections.ts
  • apps/web/src/routes/_view/docs/-structure.ts
  • apps/desktop/src/components/main/body/index.tsx
plugins/*/src/lib.rs

📄 CodeRabbit inference engine (plugins/AGENTS.md)

After updating commands in plugins/<NAME>/src/lib.rs, run codegen, update plugins/<NAME>/permissions/default.toml, and apps/desktop/src-tauri/capabilities/default.json

Files:

  • plugins/tray/src/lib.rs
  • plugins/updater2/src/lib.rs
**/*.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/web/content-collections.ts
  • apps/web/src/routes/_view/docs/-structure.ts
🧠 Learnings (1)
📚 Learning: 2025-11-27T11:40:22.782Z
Learnt from: CR
Repo: fastrepl/hyprnote PR: 0
File: plugins/AGENTS.md:0-0
Timestamp: 2025-11-27T11:40:22.782Z
Learning: Applies to plugins/*/src/lib.rs : After updating commands in `plugins/<NAME>/src/lib.rs`, run `codegen`, update `plugins/<NAME>/permissions/default.toml`, and `apps/desktop/src-tauri/capabilities/default.json`

Applied to files:

  • plugins/tray/Cargo.toml
  • plugins/tray/src/menu_items/tray_check_update.rs
  • plugins/tray/src/lib.rs
  • plugins/updater2/src/events.rs
  • plugins/updater2/src/commands.rs
  • plugins/updater2/build.rs
  • plugins/updater2/src/ext.rs
  • plugins/updater2/Cargo.toml
  • plugins/updater2/src/lib.rs
🧬 Code graph analysis (4)
apps/desktop/src/components/main/body/update.tsx (2)
extensions/shared/types/hypr-extension.d.ts (1)
  • Button (159-159)
packages/utils/src/cn.ts (1)
  • cn (20-22)
plugins/updater2/src/events.rs (1)
packages/store/src/schema-external.ts (1)
  • Event (165-165)
plugins/updater2/src/lib.rs (2)
plugins/updater2/src/commands.rs (1)
  • get_pending_update (5-9)
plugins/updater2/src/job.rs (1)
  • check_and_download_update (7-50)
apps/desktop/src/components/main/body/index.tsx (1)
apps/desktop/src/components/main/body/update.tsx (1)
  • Update (9-53)
🪛 LanguageTool
apps/web/content/docs/_/1.update.mdx

[grammar] ~8-~8: Ensure spelling is correct
Context: ...st version." --- ## Automatic Updates Hyprnote checks for updates automatically. When ...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~24-~24: Ensure spelling is correct
Context: ...ts to install it. ## Release Channels Hyprnote has two release channels: - *Nightly...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_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). (8)
  • GitHub Check: Redirect rules - hyprnote
  • GitHub Check: Header rules - hyprnote
  • GitHub Check: Pages changed - hyprnote
  • GitHub Check: fmt
  • GitHub Check: desktop_ci (macos, depot-macos-14)
  • GitHub Check: ci
  • GitHub Check: desktop_ci (linux, depot-ubuntu-24.04-8)
  • GitHub Check: desktop_ci (linux, depot-ubuntu-22.04-8)
🔇 Additional comments (18)
plugins/updater2/Cargo.toml (1)

19-19: New dependencies look good.

The additions of tauri-plugin-updater and tokio with the ["time"] feature align with the PR's updater system implementation and the 30-second polling behavior described in the summary. Using workspace = true is appropriate for monorepo consistency.

Also applies to: 29-29

apps/web/content-collections.ts (1)

185-189: LGTM! Clean slug normalization.

The filtering of "_" from path parts ensures that docs in the _ directory (getting started section) generate clean URLs without underscores in the slug.

apps/web/src/routes/_view/docs/-structure.ts (1)

4-6: LGTM! Docs structure properly extended.

The new "getting started" section and its default page mapping to "installation" correctly support the new documentation pages added in this PR.

apps/desktop/src/components/main/body/index.tsx (1)

46-46: LGTM! Clean UI composition.

The Update component is properly imported and integrated into the header alongside the Search component, following the existing pattern.

Also applies to: 202-205

plugins/updater2/build.rs (1)

1-1: LGTM!

Command registration correctly updated to get_pending_update.

Based on learnings, ensure you've also updated plugins/updater2/permissions/default.toml and apps/desktop/src-tauri/capabilities/default.json after running codegen.

plugins/updater2/src/error.rs (1)

9-10: LGTM!

The new Updater error variant follows the existing pattern and correctly enables automatic conversion from tauri_plugin_updater::Error via the #[from] attribute.

plugins/updater2/src/events.rs (1)

1-10: LGTM!

Event structures are well-defined with appropriate derives for Tauri event emission and TypeScript type generation via specta.

plugins/tray/Cargo.toml (2)

27-27: LGTM!

The tauri-plugin-updater2 dependency addition correctly enables the tray plugin to integrate with the new update management functionality.


5-5: Edition 2024 is consistent across the entire workspace and intentional.

All 100+ Cargo.toml files use edition = "2024", confirming this is a deliberate project-wide decision, not a typo. No action required.

plugins/updater2/src/job.rs (1)

28-34: Verify the download method callback signature for the cancellation parameter.

The second closure || {} in the download call appears to be a cancellation check callback. Based on standard Rust API patterns for download operations, this likely should return a bool (e.g., || false to indicate "don't cancel") rather than (). Confirm this against the tauri-plugin-updater API documentation to ensure the callback types match the expected signature.

plugins/tray/src/menu_items/tray_check_update.rs (1)

79-88: Keep updater “source of truth” consistent (UpdaterExt vs Updater2PluginExt).
This file now reads pending state via tauri_plugin_updater2::Updater2PluginExt but still performs check/download/install via tauri_plugin_updater::UpdaterExt. If that’s intentional, fine—but please confirm there isn’t a split-brain where updater2 stages an update while the tray item simultaneously uses the legacy updater APIs.

plugins/updater2/src/store.rs (1)

3-7: LGTM—just ensure store key naming stays stable across upgrades.
Adding PendingUpdateVersion is straightforward; ensure the serialized key name used by the store won’t change unintentionally (e.g., via rename/formatting changes), since that would effectively “lose” the pending-update flag across app upgrades.

plugins/updater2/src/lib.rs (2)

3-13: [rewritten comment text]
[classification tag]


34-41: Unable to verify review comment—source files not found in repository.

The plugins/updater2/src/lib.rs and plugins/updater2/src/job.rs files referenced in this review are not accessible in the current repository state. Cannot verify:

  • Whether job::check_and_download_update() actually performs install or only download+staging
  • Whether concurrent execution safeguards (mutex/store flags) already exist
  • Whether codegen and capability/permission files were updated per coding guidelines

To complete verification, the actual file contents and implementation details of the background update job must be available.

plugins/updater2/src/ext.rs (4)

4-4: LGTM!

The import correctly references UpdatedEvent from the crate::events module, aligning with the modularization of event types.


9-10: LGTM!

The new trait methods follow the established pattern of the existing version tracking methods, with consistent signatures and return types.


51-53: LGTM!

Clearing the pending update at app start is correct—any previously pending update should be cleared once the app launches with that version. The warning on failure is appropriate for debugging, and continuing execution is reasonable since this is a best-effort state cleanup rather than a critical operation.


27-40: The set_pending_update_version method's use of unwrap_or_default() to convert None to an empty string is not actually problematic. The calling code in plugins/tray/src/menu_items/tray_check_update.rs (lines 82-86) explicitly checks both the Option and the string's emptiness with if !pending.is_empty() before acting on the pending update. This two-level check ensures that empty strings are filtered out and never cause the update state to change. The semantic distinction between "no pending update" and "empty pending update string" is preserved through this validation at the call site.

Likely an incorrect or invalid review comment.

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: 1

Caution

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

⚠️ Outside diff range comments (1)
plugins/updater2/src/ext.rs (1)

42-54: Clearing pending update unconditionally at startup loses "restart needed" UI state

maybe_emit_updated() clears the pending update (lines 51-53) before checking versions. On app startup after a staged update is applied, this clears the pending version before the tray menu has a chance to check it. TrayCheckUpdate::build() relies on the pending version to display "Restart to Apply Update" state; clearing it early causes the UI to show "Check for Updates" instead, hiding the restart requirement from the user.

Only clear pending when the staged version has been applied (pending version == current version).

-        if let Err(e) = self.set_pending_update_version(None) {
-            tracing::warn!("failed_to_clear_pending_update: {}", e);
-        }
+        // Only clear pending update when the staged version has been applied.
+        if let Ok(Some(pending)) = self.get_pending_update_version() {
+            if pending == current_version {
+                if let Err(e) = self.set_pending_update_version(None) {
+                    tracing::warn!("failed_to_clear_pending_update: {}", e);
+                }
+            }
+        }
🧹 Nitpick comments (2)
plugins/updater2/src/ext.rs (1)

9-10: Pending update persistence uses “empty string” as None sentinel—consider storing/removing Option directly
set_pending_update_version(None) writes "", and get_pending_update_version() filters empty strings. This works, but it’s a leaky abstraction (callers think it’s an Option, store actually contains String). If tauri-plugin-store2 supports storing Option<String> (or removing a key), that would be clearer and avoids sentinel values.

 fn set_pending_update_version(&self, version: Option<String>) -> Result<(), crate::Error> {
     let store = self.scoped_store(crate::PLUGIN_NAME)?;
-    store.set(
-        crate::StoreKey::PendingUpdateVersion,
-        version.unwrap_or_default(),
-    )?;
+    // Prefer storing the Option directly (or remove the key if store supports it).
+    store.set(crate::StoreKey::PendingUpdateVersion, version)?;
     Ok(())
 }

Also applies to: 27-40

plugins/updater2/src/lib.rs (1)

34-44: Background update loop: add cancellation/guardrails (and consider skipping when a pending update exists)
The spawned task runs forever with no shutdown signal. Also, hourly check_and_download_update() may repeatedly download/stage the same update unless the updater API prevents it; consider short-circuiting when get_pending_update_version() is Some(_), and consider adding a timeout around network work.

📜 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 9443674 and 707f048.

📒 Files selected for processing (2)
  • plugins/updater2/src/ext.rs (3 hunks)
  • plugins/updater2/src/lib.rs (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
plugins/*/src/lib.rs

📄 CodeRabbit inference engine (plugins/AGENTS.md)

After updating commands in plugins/<NAME>/src/lib.rs, run codegen, update plugins/<NAME>/permissions/default.toml, and apps/desktop/src-tauri/capabilities/default.json

Files:

  • plugins/updater2/src/lib.rs
🧠 Learnings (1)
📚 Learning: 2025-11-27T11:40:22.782Z
Learnt from: CR
Repo: fastrepl/hyprnote PR: 0
File: plugins/AGENTS.md:0-0
Timestamp: 2025-11-27T11:40:22.782Z
Learning: Applies to plugins/*/src/lib.rs : After updating commands in `plugins/<NAME>/src/lib.rs`, run `codegen`, update `plugins/<NAME>/permissions/default.toml`, and `apps/desktop/src-tauri/capabilities/default.json`

Applied to files:

  • plugins/updater2/src/lib.rs
🧬 Code graph analysis (1)
plugins/updater2/src/lib.rs (2)
plugins/updater2/src/commands.rs (1)
  • get_pending_update (5-9)
plugins/updater2/src/job.rs (1)
  • check_and_download_update (7-50)
⏰ 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). (8)
  • GitHub Check: Redirect rules - hyprnote
  • GitHub Check: Header rules - hyprnote
  • GitHub Check: Pages changed - hyprnote
  • GitHub Check: ci
  • GitHub Check: desktop_ci (linux, depot-ubuntu-24.04-8)
  • GitHub Check: desktop_ci (macos, depot-macos-14)
  • GitHub Check: desktop_ci (linux, depot-ubuntu-22.04-8)
  • GitHub Check: fmt
🔇 Additional comments (1)
plugins/updater2/src/lib.rs (1)

16-25: Repo guideline: command changes in plugins/*/src/lib.rs require codegen + permissions/capabilities updates
You changed specta commands/events wiring here (Line 19-25). Per repo guidelines, ensure codegen was run and the permission/capabilities files were updated accordingly. Based on learnings, this should include updating plugins/updater2/permissions/default.toml and apps/desktop/src-tauri/capabilities/default.json.

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

Caution

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

⚠️ Outside diff range comments (2)
plugins/updater2/src/lib.rs (1)

29-45: 5-second update polling is too aggressive; use longer interval and de-duplicate event emissions.

The 5-second interval causes excessive polling. Additionally, UpdateReadyEvent will be emitted repeatedly for the same version on every poll cycle since there is no deduplication—the get_last_seen_version() and set_last_seen_version() methods exist in ext.rs but are not used in job.rs.

Suggested approach:

  • Increase interval to a reasonable duration (e.g., 1 hour)
  • Track the last emitted version and skip emission if it hasn't changed
 pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
     let specta_builder = make_specta_builder();
 
     tauri::plugin::Builder::new(PLUGIN_NAME)
         .invoke_handler(specta_builder.invoke_handler())
         .setup(|app, _api| {
             let handle = app.clone();
             tauri::async_runtime::spawn(async move {
+                let mut interval =
+                    tokio::time::interval(std::time::Duration::from_secs(60 * 60)); // 1h
                 loop {
-                    tokio::time::sleep(std::time::Duration::from_secs(5)).await;
+                    interval.tick().await;
                     job::check_and_download_update(&handle).await;
                 }
             });
             Ok(())
         })
         .build()
 }

Then in job.rs, check get_last_seen_version() against the detected version before emitting UpdateReadyEvent, and call set_last_seen_version() after emission.

apps/desktop/src/components/changelog-listener.tsx (1)

12-33: Fix potential listener leak: handle unmount before listen() resolves.

The events.updatedEvent.listen() returns a Promise that resolves after the listener is registered. If the component unmounts before .then() executes, the cleanup runs with unlisten === null, and the listener is never removed. The stale callback may fire after unmount and call the old openNew closure. This pattern is already correctly implemented in apps/desktop/src/contexts/network.tsx (lines 14-36) using a cancelled flag—apply the same approach here:

   useEffect(() => {
     if (getCurrentWebviewWindowLabel() !== "main") {
       return;
     }

     let unlisten: null | UnlistenFn = null;
+    let cancelled = false;
+
     events.updatedEvent
       .listen(({ payload: { previous, current } }) => {
         openNew({
           type: "changelog",
           state: { previous, current },
         });
       })
       .then((f) => {
-        unlisten = f;
+        if (cancelled) {
+          f();
+        } else {
+          unlisten = f;
+        }
       });
 
     return () => {
+      cancelled = true;
       unlisten?.();
       unlisten = null;
     };
   }, [openNew]);
♻️ Duplicate comments (2)
plugins/updater2/src/lib.rs (2)

1-13: Don’t forget the required permission/capability artifacts after command/event changes.
Per repo guidelines for plugins/*/src/lib.rs, please confirm codegen was run and plugins/updater2/permissions/default.toml + apps/desktop/src-tauri/capabilities/default.json were updated. (Based on learnings.)

Also applies to: 29-45


16-27: Generic specta builder shouldn’t hardcode ::<tauri::Wry> for commands.
This is the same runtime-mismatch concern as previously noted: use ::<R> to keep the command runtime consistent with Builder<R>.

 fn make_specta_builder<R: tauri::Runtime>() -> tauri_specta::Builder<R> {
@@
         .commands(tauri_specta::collect_commands![
-            commands::get_pending_update::<tauri::Wry>,
+            commands::get_pending_update::<R>,
         ])
🧹 Nitpick comments (2)
apps/desktop/src/components/main/body/changelog/index.tsx (1)

39-74: Harden external URLs: encode versions + avoid floating promise lint.
Recommend encodeURIComponent for previous/current in URLs and void openUrl(...) to avoid unhandled-promise lint. Also consider type="button" for safety if this ever renders inside a <form>.

-            onClick={() =>
-              openUrl(`https://hyprnote.com/changelog/v${current}`)
-            }
+            type="button"
+            onClick={() => {
+              const v = encodeURIComponent(current);
+              void openUrl(`https://hyprnote.com/changelog/v${v}`);
+            }}
@@
-              onClick={() =>
-                openUrl(
-                  `https://github.com/fastrepl/hyprnote/compare/v${previous}...v${current}`,
-                )
-              }
+              type="button"
+              onClick={() => {
+                const from = encodeURIComponent(previous);
+                const to = encodeURIComponent(current);
+                void openUrl(
+                  `https://github.com/fastrepl/hyprnote/compare/v${from}...v${to}`,
+                );
+              }}
plugins/updater2/src/ext.rs (1)

10-11: LGTM! Consider adding doc comments.

The new trait methods for managing pending update version are well-designed. The Option<String> parameter in set_pending_update_version appropriately allows clearing the pending update state.

Optionally, consider adding doc comments to clarify the semantics of these public trait methods:

+    /// Returns the version of a pending update, or None if no update is pending.
+    /// Empty strings are treated as None.
     fn get_pending_update_version(&self) -> Result<Option<String>, crate::Error>;
+    /// Sets or clears the pending update version. Pass None to clear.
     fn set_pending_update_version(&self, version: Option<String>) -> Result<(), crate::Error>;
📜 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 707f048 and 9b7d696.

⛔ Files ignored due to path filters (1)
  • plugins/updater2/js/bindings.gen.ts is excluded by !**/*.gen.ts
📒 Files selected for processing (9)
  • apps/desktop/src-tauri/src/lib.rs (1 hunks)
  • apps/desktop/src/components/changelog-listener.tsx (2 hunks)
  • apps/desktop/src/components/main/body/changelog/index.tsx (3 hunks)
  • apps/desktop/src/components/main/body/update.tsx (1 hunks)
  • apps/desktop/src/store/zustand/tabs/schema.ts (2 hunks)
  • plugins/updater2/src/events.rs (1 hunks)
  • plugins/updater2/src/ext.rs (3 hunks)
  • plugins/updater2/src/job.rs (1 hunks)
  • plugins/updater2/src/lib.rs (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/desktop/src/components/main/body/update.tsx
  • plugins/updater2/src/job.rs
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{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 instead.
Never do manual state management for form/mutation. Use useForm (from tanstack-form) and useQuery/useMutation (from tanstack-query) instead for 99% of cases. Avoid patterns like setError.
If there are many classNames with conditional logic, use cn (import from @hypr/utils). It is similar to clsx. Always pass an array and split by logical grouping.
Use motion/react instead of framer-motion.

Files:

  • apps/desktop/src/components/main/body/changelog/index.tsx
  • apps/desktop/src/store/zustand/tabs/schema.ts
  • apps/desktop/src/components/changelog-listener.tsx
plugins/*/src/lib.rs

📄 CodeRabbit inference engine (plugins/AGENTS.md)

After updating commands in plugins/<NAME>/src/lib.rs, run codegen, update plugins/<NAME>/permissions/default.toml, and apps/desktop/src-tauri/capabilities/default.json

Files:

  • plugins/updater2/src/lib.rs
**/*.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/schema.ts
🧠 Learnings (3)
📚 Learning: 2025-11-24T16:32:29.314Z
Learnt from: CR
Repo: fastrepl/hyprnote PR: 0
File: apps/web/content/changelog/AGENTS.md:0-0
Timestamp: 2025-11-24T16:32:29.314Z
Learning: Applies to apps/web/content/changelog/** : Only keep desktop-related changes when maintaining changelog entries from commits and diffs

Applied to files:

  • apps/desktop/src/components/main/body/changelog/index.tsx
📚 Learning: 2025-11-24T16:32:30.770Z
Learnt from: CR
Repo: fastrepl/hyprnote PR: 0
File: apps/web/content/changelog/AGENTS.md:0-0
Timestamp: 2025-11-24T16:32:30.770Z
Learning: Applies to apps/web/content/changelog/** : Only include desktop-related changes in the changelog when reading through commits and diffs

Applied to files:

  • apps/desktop/src/components/main/body/changelog/index.tsx
📚 Learning: 2025-11-27T11:40:22.782Z
Learnt from: CR
Repo: fastrepl/hyprnote PR: 0
File: plugins/AGENTS.md:0-0
Timestamp: 2025-11-27T11:40:22.782Z
Learning: Applies to plugins/*/src/lib.rs : After updating commands in `plugins/<NAME>/src/lib.rs`, run `codegen`, update `plugins/<NAME>/permissions/default.toml`, and `apps/desktop/src-tauri/capabilities/default.json`

Applied to files:

  • plugins/updater2/src/lib.rs
  • plugins/updater2/src/events.rs
🧬 Code graph analysis (3)
apps/desktop/src/components/main/body/changelog/index.tsx (1)
apps/desktop/src/components/main/body/shared.tsx (1)
  • TabItemBase (36-157)
plugins/updater2/src/lib.rs (2)
plugins/updater2/src/commands.rs (1)
  • get_pending_update (5-9)
plugins/updater2/src/job.rs (1)
  • check_and_download_update (7-38)
plugins/updater2/src/events.rs (1)
packages/store/src/schema-external.ts (1)
  • Event (165-165)
⏰ 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). (8)
  • GitHub Check: Redirect rules - hyprnote
  • GitHub Check: Header rules - hyprnote
  • GitHub Check: Pages changed - hyprnote
  • GitHub Check: ci
  • GitHub Check: desktop_ci (linux, depot-ubuntu-24.04-8)
  • GitHub Check: desktop_ci (linux, depot-ubuntu-22.04-8)
  • GitHub Check: desktop_ci (macos, depot-macos-14)
  • GitHub Check: fmt
🔇 Additional comments (10)
apps/desktop/src/store/zustand/tabs/schema.ts (2)

125-130: Changelog previous nullable change looks consistent—just ensure it’s never “missing”.
Zod now requires previous to exist (but allows null). If any producer sends previous: undefined / omits the field, parsing will fail; consider .default(null) if that’s a realistic payload/storage shape.


146-201: TabInput updated correctly for nullable previous.
Keeps the TS-side contract aligned with the zod schema and the event payload shape.

apps/desktop/src-tauri/src/lib.rs (1)

158-161: Confirm maybe_emit_updated() semantics didn’t regress first-run/onboarding UX.
Since the onboarding/existing-install signal is no longer passed here, please verify a fresh install on 2025-12-14 doesn’t incorrectly emit “updated” and open changelog.

apps/desktop/src/components/main/body/changelog/index.tsx (1)

2-30: Tab item refactor is clean and keeps behavior unchanged.

plugins/updater2/src/events.rs (1)

1-10: Event payloads and derives look correct and future-proofed (Option<String> for previous).

plugins/updater2/src/ext.rs (5)

1-5: LGTM!

The imports are correct. The UpdatedEvent is now properly imported from crate::events, aligning with the centralized event definitions mentioned in the PR summary.


28-32: Verify the empty string handling is intentional.

The implementation filters out empty strings on line 31, treating them as None. This pairs with set_pending_update_version (line 38) which converts None to an empty string via unwrap_or_default(). This creates a round-trip: None""None.

While this pattern works correctly, please verify this is intentional and aligns with the store API's constraints for handling Option<String> values.


52-54: Verify clearing pending update at function start is always correct.

The pending update is cleared before checking if the version actually changed. This assumes that maybe_emit_updated is only called when the app has started (potentially with a new version installed).

If this function could be called in other contexts, clearing the pending update optimistically might be incorrect. Please verify this function is only invoked at app startup.


56-65: Verify emitting UpdatedEvent on first run is intended.

When there's no previous version stored (first run) or an empty previous version, the code emits an UpdatedEvent with previous: None (line 60).

Please confirm this is the intended semantic—that an "updated" event should be emitted even when there's no previous version to compare against. This might be intentional for initialization or onboarding flows.


67-81: LGTM! Error handling is appropriate.

The error handling strategy is consistent and appropriate for this "maybe" function:

  • All errors are logged at appropriate levels (warning for pending update clear, errors for critical operations)
  • Execution continues despite errors, which is correct for a best-effort update notification function
  • Even if one step fails (e.g., emitting the event), subsequent steps (e.g., updating last seen version) still execute

This ensures the updater state remains as consistent as possible even when individual operations fail.

@yujonglee yujonglee merged commit 6f70b04 into main Dec 14, 2025
17 checks passed
@yujonglee yujonglee deleted the new-update-behavior branch December 14, 2025 10:46
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