Skip to content

Conversation

@yujonglee
Copy link
Contributor

@yujonglee yujonglee commented Nov 28, 2025

Summary

This PR extracts the calendar view functionality into a standalone extension by:

  1. Expanding the extension globals system to expose TinyBase store and Zustand tabs store to extensions
  2. Adding new UI components (ButtonGroup, Checkbox, Popover) to extension globals
  3. Creating a new extensions/calendar extension with the full calendar view functionality
  4. Updating the build system to handle @hypr/store and @hypr/tabs as external dependencies

The calendar extension can now access calendars, events, and sessions from the main store, and navigate to sessions using the tabs store.

Updates since last revision

  • Fixed main.js to use context.manifest.api_version (snake_case) instead of apiVersion (camelCase) to correctly log the API version
  • Added useEffect to sync selectedCalendars when calendarIds changes, preserving existing selections while automatically selecting newly added calendars
  • Resolved merge conflict with upstream main branch (kept both @hypr/store/@hypr/tabs externals and upstream's tinybase/ui-react external)

Review & Testing Checklist for Human

This is a high-risk change due to lack of end-to-end testing and new API surface exposure:

  • Verify extension loads: Run the desktop app and confirm the calendar extension appears and renders correctly
  • Test store integration: Verify calendars, events, and sessions display correctly in the calendar view
  • Test tab navigation: Click on events/sessions and verify they open in new tabs via useTabs.openNew()
  • Architectural review: Confirm that exposing __hypr_store and __hypr_tabs as window globals is the desired pattern for extension data access
  • Verify TinyBase indexes exist: The extension relies on store.INDEXES.eventsByDate, store.INDEXES.sessionByDateWithoutEvent, and store.INDEXES.sessionsByEvent - confirm these are defined in the main store

Recommended test plan:

  1. Build the extension: cd extensions && node build.mjs build calendar
  2. Install for dev: node build.mjs install calendar
  3. Run desktop app: ONBOARDING=0 pnpm -F desktop tauri dev
  4. Navigate to calendar extension panel
  5. Test month navigation, event display, session clicks, and calendar filtering sidebar

Notes

  • The E2E tests failed due to missing desktop binary (pre-existing environment issue, not related to this PR)
  • The calendar UI component is ~619 lines - adapted from the original apps/desktop/src/components/main/body/calendars component
  • This PR does NOT remove the original calendar component from the main bundle

Link to Devin run: https://app.devin.ai/sessions/19c73ebd08444ff198ca9f9e74b90285
Requested by: yujonglee (yujonglee.dev@gmail.com) / @yujonglee

- Update extension-globals.ts to expose TinyBase store and Zustand tabs store
- Update build.mjs to handle @hypr/store and @hypr/tabs externals
- Add additional UI components to extension globals (ButtonGroup, Checkbox, Popover)
- Create calendar extension with full calendar view functionality
- Extension can access calendars, events, and sessions from the main store
- Extension can navigate to sessions using the tabs store

Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
@devin-ai-integration
Copy link
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@netlify
Copy link

netlify bot commented Nov 28, 2025

Deploy Preview for hyprnote ready!

Name Link
🔨 Latest commit 718e7c2
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote/deploys/69291b5aba1ede000836ca6e
😎 Deploy Preview https://deploy-preview-1963--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.

@netlify
Copy link

netlify bot commented Nov 28, 2025

Deploy Preview for hyprnote-storybook ready!

Name Link
🔨 Latest commit 718e7c2
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote-storybook/deploys/69291b5a609ae40008875e51
😎 Deploy Preview https://deploy-preview-1963--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 Nov 28, 2025

📝 Walkthrough

Walkthrough

Adds a Calendar extension (manifest, runtime hooks, UI) and exposes additional host UI components and globals to extensions by registering ButtonGroup/Checkbox/Popover and window.__hypr_store / window.__hypr_tabs, plus build-time externals for @hypr/store and @hypr/tabs.

Changes

Cohort / File(s) Summary
Extension globals
apps/desktop/src/extension-globals.ts
Imported ButtonGroup, Checkbox, Popover, main, and useTabs; extended Window with __hypr_store and __hypr_tabs; added entries to window.__hypr_ui and set window.__hypr_store = main, window.__hypr_tabs = { useTabs }.
Build externals
extensions/build.mjs
Added onResolve rules for @hypr/store and @hypr/tabs mapping to hypr-global namespace; added onLoad handlers that return modules exporting window.__hypr_store and window.__hypr_tabs.
Calendar manifest
extensions/calendar/extension.json
New extension manifest for calendar (id, name, version, api_version, entry main.js, panel calendar.main with dist/ui.js).
Calendar runtime
extensions/calendar/main.js
Added __hypr_extension.activate(context), __hypr_extension.deactivate(), and __hypr_extension.getInfo() lifecycle/metadata hooks.
Calendar UI
extensions/calendar/ui.tsx
New month-view calendar UI component; exports ExtensionViewProps and default CalendarExtensionView; implements grid rendering, navigation, calendar filtering, events/sessions with popovers, and uses global store/tabs integrations.
Build scripts
extensions/package.json
Added npm script "build:calendar": "node build.mjs build calendar" to build the calendar extension.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Areas needing extra attention:
    • extensions/calendar/ui.tsx — complex date math, rendering loops, popover/overflow behavior, and calls into global store/tabs.
    • extensions/build.mjs — ensure onResolve/onLoad output modules meet extension import expectations and handle edge cases.
    • apps/desktop/src/extension-globals.ts — verify globals typing, initialization order, and that consumers won't access uninitialized values.

Possibly related PRs

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 accurately and concisely summarizes the main changes: adding a calendar extension while exposing store and tabs APIs to extensions.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, explaining the motivation, implementation details, updates since last revision, and testing considerations.
✨ 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 devin/1764298838-calendar-extension

📜 Recent 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 815e3d2 and 718e7c2.

📒 Files selected for processing (2)
  • apps/desktop/src/extension-globals.ts (2 hunks)
  • extensions/build.mjs (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • extensions/build.mjs
  • apps/desktop/src/extension-globals.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). (10)
  • GitHub Check: Redirect rules - hyprnote-storybook
  • GitHub Check: Header rules - hyprnote-storybook
  • GitHub Check: Pages changed - hyprnote-storybook
  • GitHub Check: Redirect rules - hyprnote
  • GitHub Check: Header rules - hyprnote
  • GitHub Check: Pages changed - hyprnote
  • GitHub Check: fmt
  • GitHub Check: ci (linux, depot-ubuntu-24.04-8)
  • GitHub Check: ci (macos, depot-macos-14)
  • GitHub Check: ci (linux, depot-ubuntu-22.04-8)

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 (3)
extensions/calendar/ui.tsx (2)

26-34: Unused extensionId prop.

The extensionId prop is declared in ExtensionViewProps and destructured in the component but never used. Consider removing it if not needed, or document its intended future use.

 export interface ExtensionViewProps {
-  extensionId: string;
   state?: Record<string, unknown>;
 }

 export default function CalendarExtensionView({
-  extensionId,
   state,
 }: ExtensionViewProps) {

486-607: Consider extracting icons to a shared location if reused.

The inline SVG icons work fine for this extension. If these icons (CalendarIcon, ChevronLeftIcon, etc.) are needed elsewhere, consider importing from a shared icon library or @hypr/ui instead of duplicating.

extensions/calendar/main.js (1)

13-19: Metadata duplication with manifest; consider deriving from context.

The getInfo return values duplicate and slightly differ from extension.json (name is "Calendar Extension" here vs "Calendar" in manifest). If activate receives the manifest via context, consider storing it and returning from getInfo to avoid drift.

📜 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 15f05db and a28cf19.

📒 Files selected for processing (6)
  • apps/desktop/src/extension-globals.ts (2 hunks)
  • extensions/build.mjs (2 hunks)
  • extensions/calendar/extension.json (1 hunks)
  • extensions/calendar/main.js (1 hunks)
  • extensions/calendar/ui.tsx (1 hunks)
  • extensions/package.json (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.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/extension-globals.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 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/extension-globals.ts
  • extensions/calendar/ui.tsx
🧬 Code graph analysis (1)
apps/desktop/src/extension-globals.ts (1)
apps/desktop/src/store/zustand/tabs/index.ts (1)
  • useTabs (29-38)
⏰ 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: Redirect rules - hyprnote
  • GitHub Check: Header rules - hyprnote
  • GitHub Check: Pages changed - hyprnote
  • GitHub Check: fmt
  • GitHub Check: ci (linux, depot-ubuntu-22.04-8)
  • GitHub Check: ci (macos, depot-macos-14)
  • GitHub Check: ci (linux, depot-ubuntu-24.04-8)
🔇 Additional comments (11)
extensions/package.json (1)

9-9: LGTM!

The build script follows the established pattern used by other extensions (e.g., build:hello-world).

extensions/calendar/ui.tsx (4)

242-249: Store instance access during render.

The filtering logic uses storeInstance?.getRow during render, which works but couples the component to synchronous store access. This is acceptable for read-only filtering, but be aware that if the store becomes async or the instance is null, all events will be filtered out silently.


345-359: Creating session with random UUID may need backend coordination.

When no linked session exists, handleOpenNote creates a new tab with crypto.randomUUID(). Ensure the tab system or backend properly handles creating a new session entity for this ID, otherwise this may result in orphan references or errors.

Also, the "dummy" fallback for useRow is a known pattern workaround but could be cleaner with conditional hook execution if supported.


417-440: LGTM!

The component correctly prioritizes displaying the event title when a session is linked to an event, with appropriate fallbacks.


162-169: Good use of cn utility with array syntax.

The className logic correctly uses the cn utility with array syntax as specified in the coding guidelines.

extensions/calendar/extension.json (1)

1-16: LGTM!

The extension manifest is well-structured with appropriate metadata. The panel entry correctly references the built output at dist/ui.js.

apps/desktop/src/extension-globals.ts (3)

6-13: LGTM!

The new imports for UI components and store/tabs modules align with the extension requirements. The pattern follows existing conventions in the file.


22-23: LGTM!

The type declarations correctly match the runtime assignments and provide proper typing for extension consumers.


33-42: LGTM!

The global assignments correctly expose the new UI components and state management hooks for extension consumption. The pattern is consistent with existing globals.

extensions/build.mjs (2)

182-193: LGTM! Implementation is consistent with existing external handlers.

The onLoad handlers for @hypr/store and @hypr/tabs correctly follow the established pattern used for React and other externals. The module exports resolve to the appropriate window globals.


132-139: Perfect! I have verified all the necessary information. The globals are properly initialized early in the application lifecycle, well before extensions would be loaded.

Verification complete – globals are properly initialized.

The @hypr/store and @hypr/tabs globals are defined in apps/desktop/src/extension-globals.ts and initialized via initExtensionGlobals() called at line 90 of main.tsx, which executes before React renders the application. This ensures the globals are available to extensions when they load. The implementation correctly follows the established pattern for external module resolution used by existing externals like @hypr/react and @hypr/ui.

…darIds

- Fix main.js to use context.manifest.api_version (snake_case) instead of apiVersion
- Add useEffect to sync selectedCalendars when calendarIds changes, preserving existing selections while adding new calendars

Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
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

🧹 Nitpick comments (6)
extensions/calendar/main.js (1)

13-19: Consider deriving getInfo from the manifest to avoid duplication.

The hardcoded values in getInfo duplicate the manifest data in extension.json. If the manifest is updated, this function will drift out of sync.

You could return the manifest data passed during activation instead:

+let cachedContext = null;
+
 __hypr_extension.activate = function (context) {
+  cachedContext = context;
   hypr.log.info(
     `Activating ${context.manifest.name} v${context.manifest.version}`,
   );
   // ...
 };

 __hypr_extension.getInfo = function () {
+  if (cachedContext) {
+    return {
+      name: cachedContext.manifest.name,
+      version: cachedContext.manifest.version,
+      description: cachedContext.manifest.description,
+    };
+  }
   return {
     name: "Calendar Extension",
     version: "0.1.0",
     description: "Calendar view extension for viewing events and sessions",
   };
 };
extensions/calendar/ui.tsx (5)

31-34: Unused extensionId prop.

The extensionId parameter is declared but never used within the component. If it's reserved for future use, consider adding a comment; otherwise, remove it from the props.


49-59: Return original reference when no calendars were added.

The effect always returns a new Set object even when no IDs were added, which triggers unnecessary re-renders. Return prev when unchanged:

 useEffect(() => {
   setSelectedCalendars((prev) => {
     const next = new Set(prev);
+    let changed = false;
     for (const id of calendarIds) {
       if (!prev.has(id)) {
         next.add(id);
+        changed = true;
       }
     }
-    return next;
+    return changed ? next : prev;
   });
 }, [calendarIds]);

208-233: Duplicate of CalendarCheckboxRow from main app.

This component duplicates apps/desktop/src/components/main/body/calendars/calendar-checkbox-row.tsx. Consider extracting to a shared location or importing if the extension build supports it.


357-361: Avoid "dummy" row lookup when linkedSessionId is falsy.

Using "dummy" as a fallback triggers an unnecessary store lookup for a non-existent row. Since hooks can't be conditional, consider returning early or memoizing:

 const linkedSessionId = sessionIds[0];
-const linkedSession = store.UI.useRow(
-  "sessions",
-  linkedSessionId || "dummy",
-  store.STORE_ID,
-);
+const linkedSession = store.UI.useRow(
+  "sessions",
+  linkedSessionId ?? "",
+  store.STORE_ID,
+);

If the store gracefully handles empty string lookups (returns undefined), this is cleaner. Otherwise, if linkedSessionId is commonly absent, consider restructuring to avoid the lookup entirely.


498-619: Consider importing icons from @hypr/ui or a shared icon library.

Six inline SVG icon components (~120 lines) could likely be replaced with imports from @hypr/ui or a shared icon package (e.g., lucide-react), reducing duplication and bundle size.

#!/bin/bash
# Check if similar icons exist in the UI package or if lucide-react is available
rg -l "CalendarIcon|ChevronLeftIcon|StickyNoteIcon" packages/ui/
fd "icon" packages/ui --type f | head -10
rg '"lucide-react"' package.json packages/*/package.json
📜 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 a28cf19 and 815e3d2.

📒 Files selected for processing (2)
  • extensions/calendar/main.js (1 hunks)
  • extensions/calendar/ui.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{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:

  • extensions/calendar/ui.tsx
🧬 Code graph analysis (1)
extensions/calendar/ui.tsx (3)
apps/desktop/src/components/main/body/calendars/calendar-checkbox-row.tsx (1)
  • CalendarCheckboxRow (5-30)
packages/utils/src/cn.ts (1)
  • cn (20-22)
apps/desktop/src/store/zustand/tabs/index.ts (1)
  • useTabs (29-38)
⏰ 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: Redirect rules - hyprnote
  • GitHub Check: Header rules - hyprnote
  • GitHub Check: Pages changed - hyprnote
  • GitHub Check: ci (linux, depot-ubuntu-24.04-8)
  • GitHub Check: ci (macos, depot-macos-14)
  • GitHub Check: ci (linux, depot-ubuntu-22.04-8)
  • GitHub Check: fmt
🔇 Additional comments (3)
extensions/calendar/main.js (1)

1-11: LGTM!

The lifecycle hooks are correctly implemented. The api_version property access uses the correct snake_case format matching the runtime manifest structure.

extensions/calendar/ui.tsx (2)

170-182: LGTM on cn usage.

The cn utility is correctly used with array syntax and logical groupings per coding guidelines.


433-434: Verify behavior when event_id is empty.

When session?.event_id is falsy, eventId becomes an empty string, and useRow fetches with "". Confirm the store handles this gracefully without errors or performance impact.

Resolved conflicts by keeping both:
- My changes: @hypr/store and @hypr/tabs externals
- Upstream changes: tinybase/ui-react external

Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
@yujonglee yujonglee merged commit ba772ba into main Nov 28, 2025
13 checks passed
@yujonglee yujonglee deleted the devin/1764298838-calendar-extension branch November 28, 2025 03:51
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