Implement membership notifications and deep-link handling for Electron desktop app #246

Merged
talksik merged 11 commits from free-monkey into main 2026-06-09 15:04:19 +00:00
talksik commented 2026-06-09 00:27:52 +00:00 (Migrated from github.com)

Summary by CodeRabbit

  • New Features

    • Deep link support for the desktop app (cold-start and runtime) with renderer integration.
    • New-member email notifications sent to prior members when someone joins.
  • Security

    • Hardened desktop app browser settings and window hardening for packaged builds.
  • Documentation

    • Updated desktop README with CORS behavior notes.
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Deep link support for the desktop app (cold-start and runtime) with renderer integration. * New-member email notifications sent to prior members when someone joins. * **Security** * Hardened desktop app browser settings and window hardening for packaged builds. * **Documentation** * Updated desktop README with CORS behavior notes. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
coderabbitai[bot] commented 2026-06-09 00:28:05 +00:00 (Migrated from github.com)

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds member-join email notifications to the network service and implements OS-level deep-link routing in the Electron desktop app, including mocks, wiring, templates, preload contracts, main-process handlers, and renderer navigation.

Changes

Member Join Email Notifications

Layer / File(s) Summary
Human service mock generation
go/internal/human/service.go, go/internal/human/mocks/service.go
GoMock generates MockService with methods for lookup, creation, listing, and notification-state updates to support tests.
Human lookup interface and service wiring
go/internal/network/service.go, go/cmd/orion/main.go
Introduce local humanLookup interface, wire it into serviceImpl, update NewService signature, and main now passes a Firestore-backed membership publisher.
Accept invitation email notification and templates
go/internal/network/service.go, go/internal/network/service_test.go
AcceptInvitation preloads prior member IDs, resolves human records, filters recipients, and sends “new member joined” emails; templates use centralized URL constants and shared desktop footer; tests pass a mocked human service.
Network test updates
go/internal/network/service_test.go
Tests construct a mock human service, stub GetByID, and pass it into network.NewService with the new arity.
Build tooling updates
.gitignore, go/Taskfile.yml
Add .gitignore entry for tags and add go fmt ./... format step to Taskfile generate task.

Electron Deep Linking

Layer / File(s) Summary
Deep-link type contracts and preload API
js/desktop/src/electron.d.ts, js/desktop/src/preload.ts
Global Window type extended with electronDeepLink and preload exposes getPending() and onNavigate() to the renderer.
Main process deep-link routing and IPC handlers
js/desktop/src/main.ts
Add deepLinkPath/tryNavigateToDeepLink, store pending deep links for cold-starts, route OS llink:// events, register IPC to deliver pending links, tighten webPreferences, and remove prior CORS header injection.
Renderer deep-link navigation listener
js/desktop/src/App.tsx
DeepLinkNavigationListener fetches pending deep-links on mount and subscribes to future navigate events via electronDeepLink, navigating with React Router.
Documentation and Windows environment setup
js/desktop/README.md, js/desktop/package.json
README documents CORS differences between dev and packaged modes; Windows packaging scripts now run with cross-env APP_ENV=prod.

Sequence Diagrams

sequenceDiagram
  participant Member as Network Member (Existing)
  participant Network as Network Service
  participant HumanLookup as Human Lookup
  participant Aero as Email Service
  Member->>Network: AcceptInvitation(ctx, invitationID)
  Network->>Network: Fetch network & prior member IDs
  Network->>HumanLookup: GetByID(ctx, priorMemberID)
  HumanLookup-->>Network: human.Human{Email}
  Network->>Aero: ShootEmail(newMemberTemplate, priorMember.Email)
  Aero-->>Network: email sent
  Network-->>Member: invitation accepted
sequenceDiagram
  participant OS as Operating System
  participant MainProc as Electron Main Process
  participant Renderer as Desktop App Renderer
  participant Router as React Router
  OS->>MainProc: open llink://path (cold-start or second-instance)
  MainProc->>MainProc: deepLinkPath(url) → parse route
  MainProc->>MainProc: tryNavigateToDeepLink(route)
  alt Renderer not ready
    MainProc->>MainProc: store pendingDeepLink
  else Renderer ready
    MainProc->>Renderer: ipc.send('deep-link:navigate', route)
  end
  Renderer->>Renderer: DeepLinkNavigationListener: getPending()
  Renderer-->>MainProc: ipc.invoke('deep-link:get-pending')
  MainProc-->>Renderer: Promise<route | null>
  Renderer->>Router: navigate(route)
  Router-->>Renderer: path matched, component rendered

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I sniff the new member's trail, a gentle bell—
Emails flutter out to friends who know them well.
Deep links whisper routes from OS into view,
Renderer hops the path and greets the rendezvous.
Hops and code—celebrate the new and true.

🚥 Pre-merge checks | 4 | 1

Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed checks (4 passed)
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Title check Passed The PR title accurately summarizes the main changes: implementing membership notifications (network service changes, human service mocking) and deep-link handling (Electron preload, main process, App component updates).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch free-monkey

Comment @coderabbitai help to get the list of available commands and usage tips.

<!-- This is an auto-generated comment: summarize by coderabbit.ai --> <!-- review_stack_entry_start --> [![Review Change Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/flowy-live/llink/pull/246?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- walkthrough_start --> <details> <summary>📝 Walkthrough</summary> ## Walkthrough This PR adds member-join email notifications to the network service and implements OS-level deep-link routing in the Electron desktop app, including mocks, wiring, templates, preload contracts, main-process handlers, and renderer navigation. ## Changes **Member Join Email Notifications** |Layer / File(s)|Summary| |---|---| |**Human service mock generation** <br> `go/internal/human/service.go`, `go/internal/human/mocks/service.go`|GoMock generates `MockService` with methods for lookup, creation, listing, and notification-state updates to support tests.| |**Human lookup interface and service wiring** <br> `go/internal/network/service.go`, `go/cmd/orion/main.go`|Introduce local `humanLookup` interface, wire it into `serviceImpl`, update `NewService` signature, and main now passes a Firestore-backed membership publisher.| |**Accept invitation email notification and templates** <br> `go/internal/network/service.go`, `go/internal/network/service_test.go`|`AcceptInvitation` preloads prior member IDs, resolves human records, filters recipients, and sends “new member joined” emails; templates use centralized URL constants and shared desktop footer; tests pass a mocked human service.| |**Network test updates** <br> `go/internal/network/service_test.go`|Tests construct a mock human service, stub `GetByID`, and pass it into `network.NewService` with the new arity.| |**Build tooling updates** <br> `.gitignore`, `go/Taskfile.yml`|Add `.gitignore` entry for `tags` and add `go fmt ./...` format step to Taskfile generate task.| **Electron Deep Linking** |Layer / File(s)|Summary| |---|---| |**Deep-link type contracts and preload API** <br> `js/desktop/src/electron.d.ts`, `js/desktop/src/preload.ts`|Global `Window` type extended with `electronDeepLink` and preload exposes `getPending()` and `onNavigate()` to the renderer.| |**Main process deep-link routing and IPC handlers** <br> `js/desktop/src/main.ts`|Add `deepLinkPath`/`tryNavigateToDeepLink`, store pending deep links for cold-starts, route OS `llink://` events, register IPC to deliver pending links, tighten `webPreferences`, and remove prior CORS header injection.| |**Renderer deep-link navigation listener** <br> `js/desktop/src/App.tsx`|`DeepLinkNavigationListener` fetches pending deep-links on mount and subscribes to future navigate events via `electronDeepLink`, navigating with React Router.| |**Documentation and Windows environment setup** <br> `js/desktop/README.md`, `js/desktop/package.json`|README documents CORS differences between dev and packaged modes; Windows packaging scripts now run with `cross-env APP_ENV=prod`.| ## Sequence Diagrams ```mermaid sequenceDiagram participant Member as Network Member (Existing) participant Network as Network Service participant HumanLookup as Human Lookup participant Aero as Email Service Member->>Network: AcceptInvitation(ctx, invitationID) Network->>Network: Fetch network & prior member IDs Network->>HumanLookup: GetByID(ctx, priorMemberID) HumanLookup-->>Network: human.Human{Email} Network->>Aero: ShootEmail(newMemberTemplate, priorMember.Email) Aero-->>Network: email sent Network-->>Member: invitation accepted ``` ```mermaid sequenceDiagram participant OS as Operating System participant MainProc as Electron Main Process participant Renderer as Desktop App Renderer participant Router as React Router OS->>MainProc: open llink://path (cold-start or second-instance) MainProc->>MainProc: deepLinkPath(url) → parse route MainProc->>MainProc: tryNavigateToDeepLink(route) alt Renderer not ready MainProc->>MainProc: store pendingDeepLink else Renderer ready MainProc->>Renderer: ipc.send('deep-link:navigate', route) end Renderer->>Renderer: DeepLinkNavigationListener: getPending() Renderer-->>MainProc: ipc.invoke('deep-link:get-pending') MainProc-->>Renderer: Promise<route | null> Renderer->>Router: navigate(route) Router-->>Renderer: path matched, component rendered ``` ## Estimated code review effort 🎯 4 (Complex) | ⏱️ ~45 minutes ## Poem > 🐰 I sniff the new member's trail, a gentle bell— > Emails flutter out to friends who know them well. > Deep links whisper routes from OS into view, > Renderer hops the path and greets the rendezvous. > Hops and code—celebrate the new and true. </details> <!-- walkthrough_end --> <!-- pre_merge_checks_walkthrough_start --> <details> <summary>🚥 Pre-merge checks | ✅ 4 | ❌ 1</summary> ### ❌ Failed checks (1 warning) | Check name | Status | Explanation | Resolution | | :----------------: | :--------- | :------------------------------------------------------------------------------------ | :--------------------------------------------------------------------------------- | | Docstring Coverage | ⚠️ Warning | Docstring coverage is 26.67% which is insufficient. The required threshold is 80.00%. | Write docstrings for the functions missing them to satisfy the coverage threshold. | <details> <summary>✅ Passed checks (4 passed)</summary> | Check name | Status | Explanation | | :------------------------: | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. | | Linked Issues check | ✅ Passed | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | ✅ Passed | Check skipped because no linked issues were found for this pull request. | | Title check | ✅ Passed | The PR title accurately summarizes the main changes: implementing membership notifications (network service changes, human service mocking) and deep-link handling (Electron preload, main process, App component updates). | </details> <sub>✏️ Tip: You can configure your own custom pre-merge checks in the settings.</sub> </details> <!-- pre_merge_checks_walkthrough_end --> <!-- finishing_touch_checkbox_start --> <details> <summary>✨ Finishing Touches</summary> <details> <summary>📝 Generate docstrings</summary> - [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> Create stacked PR - [ ] <!-- {"checkboxId": "3e1879ae-f29b-4d0d-8e06-d12b7ba33d98"} --> Commit on current branch </details> <details> <summary>🧪 Generate unit tests (beta)</summary> - [ ] <!-- {"checkboxId": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} --> Create PR with unit tests - [ ] <!-- {"checkboxId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} --> Commit unit tests in branch `free-monkey` </details> </details> <!-- finishing_touch_checkbox_end --> <!-- tips_start --> --- <sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub> <!-- tips_end --> <!-- internal state start --> <!-- DwQgtGAEAqAWCWBnSTIEMB26CuAXA9mAOYCmGJATmriQCaQDG+Ats2bgFyQAOFk+AIwBWJBrngA3EsgEBPRvlqU0AgfFwA6NPEgQAfACgjoCEejqANiS4AxCiRKRm+DAGsS8gMwA2AAwAGlGZefCk2DFxkKAApbHJIAA5cWAAaSAAmX3TvAwA5bGYBSi50gBYcgFUAJQAZLlhcXG5EDgB6VqJ1WGwBDSZmVoAzC3wAd1kwC0kSVospt1bubDnWssrEYshcNAtXRHhXAwBlfGwKBkcBKgwGWC5B+xIwZzcPA2g0ClJcSCvMW64zG0GGO21w2Ba/G4ZAMVRIEngJFGlEhBhqKhIFlRAGF7NQ6OhOBkst4wL5Sb4AJzQXy+DjpADsHAArOkAFpGfTGcBQMj0fCDHAEYhkZQ0ej9cJE3j8YSicRSGTyJhKKiqdRaHRckxQOCoVCYIWEUjkKjihSsdhcKijSCIApAijyOQKVUqNSabS6MCGbmmAwaTriIgYfD2DgGABE0YMAGJY5AAIIASRFpvx9HtrE+8gFjFgmFIiDMsEcAANA+p4CGwyQy5BBvArJBQ7bq6H7MhkuXtkREPX2E7fvI0LRaPAMEQUD8XOgW0jIPMSBojEZ40mLDQzfAXF38FtS5AlAwLJ9qDuMMg8yQAB7cMPmsM8HpTBiQdhV6RGXIAeVyAFEjBqCdpHzQs6C4ABqABGVoySMf9EHEIFzRVRx7ARBcSEGQYHy4GoxijGMDAgMAjCIfBWg+RBXEbKwNFkZgLAjaNIzjBMUzTMUCSzR1c0FW5wOLAxkywMsKKotAaLo5dGIsMs0m7SBxNFM06y2KTXEgUYpPfG8aAwJR6AISAKDiZSKIbZgfg0VoNHs+sXVHcdJznchbTLXCKBQ+skJIbhIAAClGLppyvUYsCURAGAoeBuHEWdMAlFggUMgBKdBBi3CyyBCAgK0gXJ93wbs+G2GjfhIAsESfJ8lEbDAq13bTKEcQTJzoFdV3XRNNzFC89wPRxj1PbdmuvO8HwJJ8lgEV93wiT9hN1TTtPgWLXOwbhaAzLgJwsyjqNoptZKYhTlPKvZA1U/F61bFAbgsbAornTywx8u0aACsyMEa1zxP3QZrMgWz7I0etAucgkdhcPt4CUIa9KQcR/pNPL8F8r60q64DyGQdrSFoKDmTg3wEKQ+AUIJNDTPhRFbWwryiQAWToeACiItiSP9CSGGYWhWjDC9WiBCdA3wFiYx61MTW4zMHRzfgBILDrhIB1o+YFoWXBF4FxbusYFEvXAzLEZAy3IXBRjDVwjgkBh6xC5J0CwZymowHYj38vkyAYWQuDLKZFQIewNFyJFWcKFEEG4AAFF8kFLChAsbTsQ5IbEpnYNKCsTZA0Fp+1N0Uw9LetigtI2CgEQuFBkBC+xjP3WapkQWAnBIKOKDbuLIC2naaGQCEJynJSdjxWgJhikgM0gGx1ukdPGCziI0gnPzRyVvv9lclwLHkMtAtoXpY/wfALECtK0jQSh8Dthg0jUOYR/vnOurXDi+rGy8tn3JSRrPAlH+E17wUEfHwFu8A3wfnEF+EihUXAkCAiBfGKtCZQVgmAaC5NkKzxphhem74cJ4UgKzccHNWKch5pRCcW4PYWFaN0VKIt8AMD2K0KuNdlwUUlmxaWXE1Ly2zEOPMBM4GJjHASdWtDKD0MYQUTALC2GIA4ZQLh+t0D50gAAcXwMzVhrgBGz2cGwhsT4lJliOGoqB6kZEUEGGgWu+0KyqOrjY/WGgYCHhkl7BqoEyz6LYVYtxFx6xJTCspQJttrEXCiXCJgFBVTnQnCeZ6I9EZMGNqbEOylw6jCicErhYTDIu2Uv+AAGrHf82JoCXwHBEJ095aGeOTD8Sm3ArBSmQCY9w9A2DJEUF2AsPwvI6USYwHYWIDwUFOEQduSkKI9KNibc+Vg+DhPsAk+gt5oRiGppM5AXkA7aJILgAAQrIf8ot5JpDLCc85shkwABFzp3NOT+CguIZ40AuVc7QNzlLASQr1AFZYKjbXxH8psRVxCNgYOeXc/4PZzToOdcJYKIU0HREhKFFgYXwDhQijAViIiJlwBWVc7ENxbiJYNf+ohRq0q3jsqa/IIEJ2gYtWBy1CoLlwLIaE6BJFEwugKxwBSYmOCQtk+sziJJ2LkUwxRPSVGcPcRRMs8C8lbDFUKoyAd+WColSEkgcTRBhlVJ9GVD0DqtAVTseRzCVWuPURqrVC5BhxDEBePVEFlKepuLy/JBjCk2MCmICgFhIAACpFkGI0NiFwKy5iUAytG41RSbXSIiLIh1SqMBKPYWqi4+t3W2n6bARQvqRWeS9UFZgMaM02IyhUqpNTL6NpDZKs1WzKCyrEvKnNFBFUKILc64t3CMZlqcAYgkFbBnVv4GJJtoSIyQF0P6utgUG3pq7SajK9zfnXPDbgG8yzbyaETTm/SaRO7/KtSPDKgVo35o0AACVHbeigMyKA5wMOuqAtbA3bs7UEyVB7TkXOeSes9mSaD6QTUmi9a9Mwm0fUFF9o732fvfN+sMf6AObuAzuldJAIO4A+V8/ER7/kwfPQhq98HcC3uuQ+ycT7MOpWw6lL9P6CMbqA2+EDu6wP7sgEC3AIK6NwYvYh69uAn0AG0AC6nHMDccwLx/DmrCOCfraB6JYnwUDxILi/FhKgGICRSoKwtBpNIYYw55jKBUMbSILe5FtnfhnwsBlSgP6dMCYDUJkje6uEZWM/ibFuAzMlQJVAolJLJO4Hs/JuTTGUNsfc1sLYlNlzmDYH5vDFBNVQG1Zsi1lAO4DNoPnYVS7IlhZsT2yrJW11Ba3cwPgInDNcJa4k1NOjIOXOPWIG8LH72YFkGmuNbCE2TMC4B4L9buukf66qcjUGnkwcy1NmbLB43YgW/+jrxHVtNdiQY+JrXyOUbxD8kbtGxsTabC7abMbZuuHm3MRbRGQvndE31q75qBu/vE8jKTY23v7Z6d9+SJ2ludYB715rwPe1g8izQWLsKEuWesyiuzz2XMeZs9DDA73Y0Hbm0dn7CO/srYM6Gy7bDrug4i5ikg0XsfxfhUApLZKdvE5y3tj7VOvs0/h2iFBYEOoipgp4TBZMDCIVwahRQ6E6ZYWIWA/ChFKHczIgYQddC82jpdeqiWnMqWcVloIu0CsRHKyEkYCRRlXrtB4bb26R4F7eqkL/Uy5kyw9JNPWLyykJ36zSF7lGU4VUPRMi48dkqPEIMD4tNgdpJWLnwJ0B+CfKAOIuIgNIT4WVgIJImWOyZ66tRl4Td+1uv5MpMvSk8gCBrMsmhXtlz45pQIWuIbl35EGN6pczTABLF5zxOkmehsgABelBkF43r36yC6RSY4Mpng9XtNMIM210SAiowrcG/IjQodciy423NyWnhVv+Ex54g7/ia+1Z5KZ+pe6jiLjxXziUkhLWFomWPmgRPgK4FtPWEoNCIZL7LmGJBOsmMEACmQDZukmWImAwH/rgKJAiGCBePWCZJ2OfP7rwDuHwGwF3IgIAJgEkA+akAzyyAtC+4DBFWiShyT4oYOOvOPqMyeAI8LSYkWBOBeB6gRK50SkE6LYhsgwpytwoESk2wXwpy84VsNsfcAUDwSaa0zsmS+wfkEQZS36MKNgpwhk9YQI3A3AghjBPwOwiAzc9gIwo4yAvAdMpwyAN+FcHcXc9Bo6jBTyMg2EtYTgeA54rkE4+BtKIsncRQ3cMcJeQ0GAgAKARoBZRVZKRUHxF1zVopGZLjhAKTLyAbCGRaKAA4BO5L4TkUIPgCBLQIALgE74rGJk5BT42RKIe0PwNEcUyAnc8U8gDBTBaQJBFgio74ji7cCIBcoBo64BkB3AGgh6jyLy18JSKST0L0e8gxo69coUBcoYGAYA/R/KzRk2JSTs7cd6r23BPOTKaBBOniuKtMDAcUiIRhTAKa3qs4qA3YWAEIBIJk8KcwykN8My98GgRwlaJUuK50lxZxr2b60AzMNQkAz+9A2hDaZYAg2ATYtAeSkc8RSJKJFKxw/kgCmIsga8GA0RQCLs9A7kzwcRVW1xUa9gcBbm2kuk/cs8QJg4Ow8AS+doBYjcXsNEBA3ArQyIAgkA1QqJMmEQni2q+h2wEQyAgUZYrJAA6iQAIImNYXKa8qyU8tIK4BKYabcsaWMBgC4bQIaRlOEgXG3J8FIsaaaRKWYSVJQHUvQZiNCHwMiPYAnjMrQNgBcLQGsfQAICVO3NibiRYLQGIQQS4MSTUMUvQHGXiQScyRQKmY7HXs4OOI2ICfuACYjNKegNYbKbUPSQ9KkgjFISKQSFFGafgFoWfNlPtN2OtJAKmY3j1M3pZgHm3oykOSAqyvwOyv3pykPoiDyjYF6nSfsCGNQGcI4DyeKAHJ/pKoFPeOfDGtwEQJNOfBoKfOfNfLfPfDwAIGCfgKebFHxJnO8c5k/PMEQFea+YIV/mkLNIuNMEAaHISdHHFPHP3m3INl/vWIAEmEuSSIX+u5PmB5R5e5Fgp5PmF54J9s15t595O+ToT57Aj8TYb5H5xFX5kqP5PQf5wctYGgQFCRIFCc4FFAaQYBZ8Cx/hqU8xW0GUkFWaxuuaDC3hrgd+k6mqUuq+Yicu0E6QCQcE0EpQ2+VMyUCMBCWuTMXAb61YsAZ+pEF+dqV+DqwlolAA+oPJoA/pQk/jdOaLxIrKImgnAqJLavakJacuXCJROmZYvFHojOZVnuCAFPdO0g+EMuWK5Y6pgPWNwI4q4GgKQLWTMT0iZfmr5Cnl4uWO5NAIvHxaWBYP6VyUPBzk3IwPdo4AXEsgwROmkEhFRW8g8s8kQfuPYOCMOnOO4fVPUcpK+h+qlGHk+FNg9EsJEJGTwFJEMqgMHgYr1VFQnvuBbO5TbGHHBZKgVHAI4O4TVBCMpMJctaMHxVEawkSjITnh1JQdQAoWFfwAmUbNKmGTkp8OoCOOscgPYB0o4gSPCUpFUYggeNQHWZsYoYeAwbDpSgOTSkOa3oeAAt/FeIKOXuAn3vNDAnOSPuQCvqBFJRgpgtgsrhTMpa6BrgfkQhpaQmzBQsRHpQYEICoi2RKa0FUP+ImE8szP+BoPzLwtbjLDZS/sIm/mImrDTa0HTW2QzUzSzWzfzAbG2I9M9KBLQKwgUOwMddwaBDsqeBOOkpWraNiD+FUEcJVNVBQT7jhCiJVFbA4FgPSlICMNwFKLWTFWwvFc2e6W2ZWQFIWdIDVbsncXMCOPwRcaWKVIjOyaqFVhsXLVdWWB8tWBOPWKWKOFVuEqMAgLcMpImNiImD+PWJ2PeJeI4AnaqPnEGTsvKJ1GDZ/BDZ3lDcNAyh3uNPDd3ojZAjOUtN+H+IBBJZjY5XLiTPBHjartTHvmpYfqTSfrpf6ELSLZKY7XFaQBoDTS4JzdZemLZa/lvALUYM5WWFPa7TPbFc7QvY4RgL5DFHFCNZAFqROAraMG4QfZ0JOK0DiXiYsExe3JKElLXkGRuSWenrobGTFPgIgIgMcTSUmLHLHCZf+LkAAGoAC8IQtAa1CAzBstL0bsrkBxC4ZYs9ztHAIUJ9doZ98UtZBM6SSkt4yMGBQI7g+DcdtZODb9dDhD0UsU/+AeEdCMZYgDwDoDEg4DkD0D8DiDEMm1O4EI+8Q0CBkjh1vSykmI8oMyRxXkCVYM0BvuuAkj4eV9hkYwiA2MFd1K/UzUNdXs7esNXeoCzdHKg+bd8CRwxD9hwqAcuDpAzD/ayku94potrjy4i9hDMF3DMyvDZA/DVegjsDCDIZ74VgEaLgYAKjG199jgEA71uAXkzAcDBDng6Q3onwtwcDN43gpQpWkADjbDPwP9NaND1gBDHjO9tNe9iwyTR9Lg0FPAb1fI5sPDIDoTAjUDkTIj9jjjmhJmNakCbc7jWaXjrZ+9Tt89/j7T7hsBtWykPTfD/TQjUTig4luM3d4EcungcE6QSlu+qlmuo9JCZC7MzAE9huMz9NiA5wrQ+pixkQN4y9HE3Nq9vNfEG9jlgtjT3jkpTzDALz1hGg7z0twZigYZoEMxJp/kuMrguQaACIRARKEmqk9Y/Qed7Af1PwrcBknBECfI6S/4sTKykUDg3AkwE4WkHs6Lx1yh3wABDJaL1Y+IyA0xkAcIjiPwVQpw2UKdZA7V1A7cBoEg/ypOLS9hWI+49oAgrD8ARQg0irGwAAjtgPi0oP5HS24C2Byxi3SfCOwNy/ADMQQzfRoAo3ExgIi9wMixoC4Ki0yzQOdMPP9Iy5y+6wHvwTQMOYeCEAiG7jFckJ4nqMgES6pLkaHa1PQOvPDOWImHgKWItLznQK82EiMJOPsA2YeJQxTJg3gG2aePIN68az6tG6aCTv3q5NGc7Lq7S/MFpI6SW+9RMLQLFFIFgBW8dZcV2YeGgKmx+Bm/QP61VuBXMP2ZXcYz/KYzDUyuOT3pOUjQPijXAlAK7gSFUa5RaHixEAHMFnSQ68i66z6xeFi6aD6c4g86LaC+C284gDeKSXs6ggcxgqUFvgPTvmruc8TYzFc+Tbc/rlTXeyC887a1SxoLQJCy0I/l80YmvXzf887gYA08LU0w+1B0ozB3BwOPpF04jGWDozffWEQCMDeVGoao4PCVgx5Dhy4Ke/S3dGgGwIgI7cuHYXqubN8LHGS5OJfFwLHDMswEgCQMANKukgAD4tjLAWB6D9V8AtWxR0yYM8ACdThNv6taRhvtzooutGv4jhqTI3lsJcC7nitcBSfsaQBwN6CQASB1G0BpQWcZT2eOfOdKf25Ktn1qCuQmTactuGtus+qmtqlBQtVnB/RTiGhxDqt+eODHsXgGPdSzsWMLt10WPLvWPTm2PD7wLaKUeex2JF7likdjD1PgccKQeUu4eweRBlhcBQz0AhD+mnGal1dMc0vIuOyhTzq1btaAZ8eadCeQAicsDieSdoauSycYDyeKd05liGduskAmdzBmeuAWd6fWczdEDucOdOfwyudBQHeefwziVpekKT5yFIQz7NiJjz5L4UAY3vuy5QRfv90q6/tD3/uEKAc65k3kIgeU2T1AuzM1dguiwYBwefNJjfNyz24ocOVoeRuQAUuKOzjQ9gAhDF6HKz4/3IBISfCBW1lBf0v0FJRvmys8eQA/hHCTCmtRrk8Gv+ta1+lm0alNvIuxziuvImyyDnvGskDQD4DMduBlgZTJD/UxXdzljPxuBtB2QOTVk1CoMmQThgBoBVkTtsvvhdBVZLBtwhcXuzimNxv2B8CUxsDjj4iSNPjE/G9KRylmJ8CfG0BgDE9gI0+0IhlwvmwrMjzi+uDpnKQW+UB8uTxoolLORaLh8UAJOOJa1U9VZc80s6ccDfA4+aeS9+unLReIyO+lj0DBcuC1xKTx8zpxCRBdRHDKGk/DCGwC0A1y2U+GRvkKAJme/1/KQK9bftD1hymHKicdOsLSDF1EBK1qnXxjgYFtlkBgBnDyT1qOL08OklJlgbAFFgDryqmhJBQVe32tC4zYA3gZTVtm3EFCuOBPi4QMDbUUM3hUOuRWtjCjX2DOAIgBdBsEKeHKTz9HEl+ZYVoJv3NSGQd+xsf4OpDv4QgwA2xQ2hyzDARsf+HhbarrX1oQCRA3xLACMDzw2oLE2vRYi4ECgAByCeLIBIE5w2+tANZEFB5agDgGF4GDthGHabgrEjAlwBoGlJwgtWPlFMjPCLrxISA0wJBhlB0ivVO4oQOgAAG5iO5A+Oinz4D3RyCjoJsMqDKp7FdGt9B2jMjx7y0aWf5NwIgC6hnIZkt9SdqIDOBPUjYjYSft/Fd6Ixoea0LQbkWDANBRQUZeQOrSgSWASipyWPMpGlJWJ7+sUflFwBNjatXkMmfSDXnPhEpwhZkOsKNQtjq5RINAIgN/HuAOE6wniQ/pTwGwxd0A8rPSDFTKKIxfGHvZ+jdQnDDUwAc0AxOkgKLuwOGaDRwAvjPgNo24D4e/pECCj7Q3YdJQLvCAIDnwVEzhfAKOGxhJgvYEgZ4HviBAMB6eR4AxCgEyQBUtoQUcPEoBtptl7aIrXtiVDGrzM6AGUdwqUTaRW1Dw58egFIG7g+pUAH/KQbB0Ma9Qq6JjP+NDSy5LtG6VjaaFOWRpcpUaWqRBK9zXxy5vA3gOCN4FxrfcCa+CC5iTRIRaU5kdzIwNVwfbuFbSsPBDvDyQ6/N7KTuVWC7nqz0d0eXXLAJiImH0Aq8yYEoUA1/oV8fYlvTRPI3JHB8CorSUfiG38QjdDII8H0oFCi7DoQCE3MThsGm6ck5uC3esPQLigMA4QHJSgBoFkZrcSBLPLbln0D6ThKBufAzhgCF7GdgSFgTblQJTpQJ24CXWKKq0Dblg1R6fFthwD7Y0ASB9YZMLHGxDvge29hEpEKJ/hxdLwPQZVkUAbCLkfU0vH4I8PGJKQL+FAGdkYwy4fDa65jb4XSJXYzQbGG7HlNu3oAUdBAnsSkZvBZT2hwwazJzKYPhjz0ixJAUSBPgnBakwwCZUgYx3tY9d6WJAtIAAG80SpyfjnyME4ndRRU3GzlOClFzA9ApefUUZxoDrdjRsVbblZyyxncjuLnNznZ0O7OdIAAAXyoG3twejzZ5gWIa79hQRWNSAPLm/awizmRNf7kfi4DXMKaXMLkAYB1ALR+QgoYdsKHRIWgpQ1oNALaDspDgXQaENUB6E1DehDAL4tHgaFdgls8RyUS0Iey9jbAToKlS4CMFMQAT5A1wIsMOEJogSNQXoJ8Ypg7GRhsJ1Y2gJGA4CkTwIJlTwJSDM6UhvAN5TwAyAZCRgUgkYPTpRMjCVhgwHYEgOxMjBe9cAezSiQyA4l8hRJHAcSUJNfzcTsxbkbBrxPbC1hoq1AOhBwxrBBkLEvYfsGNWSBDAToGgQSWhDOToTXAiaFApQ35TcSRgowSMJuJSAkSyJyYCiVRLIkmUlA0ESkCQEpAyVsIM8QSVxKokSQjoMkBiExEEnCSpJClCSYZBikJAOJmE+SfOA8gZNqAvkHoGAEuiFVF0cQS1GWEujGDn8ySBpLC1eKoxAYwMUGCrxMjpSfguiBsCdGQChlOSCyHmtpBtiN9RgxkjiaZPMmWSOk1k2QLZLGAOSnJ1EjqK5O4keTvADIC4MyASDQQSA3gBICQDYkcTgpkYXmPzEFixQdY0PcWFFPr4xTyQcU2gKdO8BJS5JVEhauoQrj3x+0VYAUgvhVqGwYqwDeFjPjTi1g6hsVOdDmR7gBQJmScIKIHH/Lpw9q9FIGaBVbhJwU4C8AChnBXgKZc+LBHaotQrh7VIKIxfyKeAqmjwf+xtFUtkifBGj/6QrA8KgBBlJ0vgU/TQCZPVxmSDEg0qwE/xslUSbeNzcac5PAjTT3JNEhIAIEGDMhFpvgZaatN8BBTxW3EgSsOlNxOoDEqqFPBRGOkk8Yp50qSZkGul81uJ61Jqc2ELpJ0SkjU3xt5hOgUA0mtHOvC12pL1l0kIVMBCS1LHyZXk9gYYPKH555ZJCh4WYn1SOFz0SAyQz7BWEZlKBmZbCVmcNNGn2THJvMqaW5MmmkATKDAaCIMAEAMBaApQMcJSCWnSzkgssy/CbgYT5pC0ysk1EdKSknSQIlE9INBE1k1yOApQUoDrL4jcSokOqaEHrxJn3VhYpdMQMdQ6QFB/OU4QMuVWcaNZAcNiV5GtjRytZXkBJC7EkNrIWJW01SWpFQIG4EtaYrVedoeHYKqhepkYfqSzJYBDT2ZI0qiXZJ5lJzyJM0miaUF8nNzvA6QZkJ4FKDMhBg+c2AIXIMrFzIqY6JWaJUrlCTq55ASiR/IbngKOA0EXwC3Nkm6yqJUSHiNngG7OyVOiIL/kQA1j3Z0k4SIllrV2ItQgyLXBrAEiXnnQZ4adJQFYErYBd5qzAPoCbFQoS5Q+vo8hmKnHbSBlgF9GGC/1CgJJc6LgFyFOHIVTzmcrgVnEkmqyVpVm4Yl4hahKFl0JQByRzha2UhSKJcV9ZIKzBqzQAxUocvqUzIGlny2ZT1biVzIKA3yXJicjyeLJIC+As5aAZkC/ISBoBv5v8iKqXOTwVzVZVc9WY3NgXMgoFAkmBQrlbk5h25s6egD/RkULox5i6WcGIpRz79w8BAIgBR3SSskZCPBY6g8S8zE8A24SDcuQ0PCng7u5w3LOx22DBA7ZmxDhdCC4W7zqBHfDUv5jDC6iSkKEW4OkgPlVY0FefToH5D4D9zzQh1XgruCPknzI5pi6OVfLGlxzb5/M2+SZV8ACAGQvgNANCMGAMgSAbijxSFKLmCUAFwCvxaAoCXQKZKISyidBE8ARKnQ3EtIX73DKvRPcPNHFilHxbyLMktgtcubBDxkAmq3Yn5hHhVkYwZ0yiOaspFsg+LXUGMKZcYtPlWSL5Mc6xXzNsU0TvAaALZZSEGC0AXFgwSkO4s2kyzDlf845cZS8rmUQF0UwJb4GuUwL65CCtuVRNdxeEFwjs3oWkt9leLR0RBXVOEnUD4wwwQivsaIuSqpVt4BIF0EoWnzSEG4ghMOSQAjkWTZlKK+ZbHImk2L75HUEyt4AYCOKdolIVOekAYDpADl20o5fLLcr3TPKkqbykhBpVgLQl5QBlcyHgXJSqJ/4QjmUIWqjBsqSEPikCV3ARCxAc4EGgYjSCdV4gQq5SMsUao7yC+RQI2mGFGqxKLEu1bciahxaTIWh9ZRwN9QXD5o5hpiGAj7BuDyBd+AgreEpDaJu8Q1pM9ZHTPtpEsEV4ckxcivMWczgOaKhOTquTmlA1l5IRxNCIcUCALV1XRmszVZrs0KJ/isBBrMjCSTG5wS5lZEtZVjh2VtoKdRLWWH38pQx1BUj8Cih+d0k6Ag2qMqZTcra6wLd2jam2GYhdh+LCQHfWOF9J1cSRThqUvQjnwb+goCxDHQfo50SAvAu7kbPWQlJBF0gPOiIvTqZ1s6hcPOhsF9KJ1u4ba5VR2vPldrIw18xZdqoFm6qVAtABgJSDonMg1ltASkBOr3E+MWm/jNWQusCVMrl1lyj1TdMjCH8QGzhWeBgDtpEMKmXhQ2BsB+CYEIGAzYRiGRlHqKgmQDXpjSSa7KRfGUzA0MKmSE1N3GIAkGVMxdKjNeS+4ThuWHWZ9Nwm4m7Zkg20gIBmw7gfyN+rtBsdHAGPO1nPDDAJU0mGTVoPk307NqzW6GlVVHPVU4aFlWq9Ff2pIAmVP56QLZbqVzmlAEgCQajZh2BaQ9H2cHG8AxpEkrrV1LG0JXNPuWXzIwbK5SMHwNFAIr2faNIGaLTqa1xAL0r6eqJJryh7g8hUsFoi1Fad7RFPPTg1mcDV9ayToxQvpucwbISAQyrcCojiD2BRtZtAuEaM24ODPUrVWunq2C5OiwuXooZP9QcL7hyZFsKcTkKVV+a1V2G3DcFr7UEbk53gZkAyFoBrKzVey9IAJJJUFyqJ6I55q81S3papJCQeBdlsonfa8t3Eq+p2CK2ti3AJWy9sjGxZQrMCI7dNhmCza4SLeDsn4OQH0g2ikYRbWLu2zLYm9K2s4GMb5sw1mKOZgWzVfHNIDLKPJtABkKtKxWlAGQ0EBkPToS3T1ktzYvDpEE+2Nz6dbq+lWuoeUbq3cJHa+mMBtZsjQdIfdHbmKo7KRD+/K6EHUrSTqcVIuAXsSIrCSIBZAgaLeewvU7zc5gpOLLPpP04x8xIK3H1upEtHn0fUW8+Rb6IAJ9wAxvnK0Ul1DGcCDtROuZaTt7UU6MVuqhILQEJXEbKQj8hkJSHNVPaf5L2mjRByh56xOd86jLdApkm/bGVAOqiZjkBKHhHNVLIIFYz/J3cNerQiPHlWX58K4YCMQtgEJz1KMZ0oZKwIgFkGhh+AwdXAQPmb6DtUAW/IBITqRVYaSdJ28nXfPO1hbvJpQTOWsoEClA0AngTwCzqw7PNDpCe85YxugXZAGVngPnZ6sjBwgPKWexwJxuFLdC8AEA4nisB/qtA4gu/XNZP0+BRlzJtZSMfvo6bG0H1ttKUHMIRgLClhCtUxFAlnDCaNhbet8PItwhzAxgBIDUB7r73E78tli5gD7uH0rLZ9y0zwLQD8CxbSN8+pLQ+yX2IAud0CuiQytKDpB09BW4VJfuKoPQlwe6+mcdRPVWiMCffJXgoPb7pIiychdksXnQA9MeAp4dJu9D15ZEeF8AU/Xvw2oYQPwPxHNIqWgMzLO1A+oLUPsp00TmQDABkAIH1W0AEgLihkPFsj3cTXtcesWMvtpXQKXFbqmSdvqeXlSd2C4bHrj3H6fR8QDg+rWz0nAuNNO7Iz6GGGdqJUw+TIiPgINkBh5TwRAXvfIf735bB9Syv3cnN8AkBQ9PgSLddq/kGHo9iWiHrgfj34HE9X2u5UuvimNyEgBR7fdiB7oBznalQ+MvQHcCyBoyd+o/WAh6G/BzJ5DfcFto6bhcfg7QlgE0dwAtH+g/nJlLuV/4SNpG8gbYSMKxCtBxhm8BJfULYSQHLhqAJoUUV8wRHVVCh6I0odiOhbaJ127yekBwhYrmQ0EbA1kcX05GCDoSmSj9qKOXL19/O/LRIiEAQhzQTg1/h5GlIicApXB6QECvyXlgghlg0ISEYYbRDcCjhfg4QTJ5IBDdKQpQGkJG3fxDFx8xFZEdgMWKe1eGkLSPpMrZBsVlIGnULPSCeA0jnE0lZGCMO6wTDuRlfUnpdWyViDuW54/JPqz1bZeO8bBattnB5V/S3ciowQIhbECyBwRnUS0syL7hAo0EDKNPBcNfGkigUdIBlCUA0Aw17vbviT174OiB+DYEfjg10Hj8tAXwCQNH3oCBRPAGUCdmopmIC8StIvMXhLrLCyDa1+0vgGWHkHZS8skAc9ShstQTgsBdJf6jXpcA0F2VtqxcGgFkBVZxBtMT/p1DkNbGoj3EhAHMkQMqHdV0+0oIzrWXj6BACQbwBcf3HGGYeph51RAspB87U9Zx0o+xrhDvUXl3+o4K0E43H8Jwp/dPMhALqKC9ijbDrazyFbpJk1iAigAHEIHOsMApAgAYv0jQSn7ooyO/YNEKlOgHTovdkbcgnOimt+wisQ1APnOGxdeOp+liwfQCmmzzk/LpEFFv6K0NgzBd8Uq2zjJDEA6REgG+hT6kD1RmfU5NnzFUHny0nwPYCHUCPKdgjaQE8DPG7iF904rXTTqr3f759hR5Qz4BsHHbX9Nj/m7DWmZ/m4mztKywlQyBn0Qiby805kMWfvYHi5jR4m439qumFGLpjc3OWQZsOhluDpIzrpjxbFIsWOsoIMw1kZGKi+A0uz2PQMhPljaAlYyaBsBrHAh6xkaJBhVvWgYFeR6ugPIaDdEeiywKonNSCT1EOmgV6l90f/0IYxj/6pSeLoGMS4hibgPepM5hcUNk69j+JgQNBDWn4qGAgwbyXRItVyzr8mMu1b4vwA0WYFlIBld5LIOFaLEvK/2ZyrtCJiDZbUQ0O7Nag3BywPVPlfgNLhIgZGZU1iwSBGAQENhcGKgGIAwtHaHLGZuI2FsyDeBaA6QWgLcrQAlGiz6Ry1eSutWtBKVYK4K+EvotST35zF3K/70Um2h0JnsP2ZgG4rcB+0W4Mrr4k1r/R4122InMd3aZlg1MMPGaoQ3hLtK+AKsGgYqqMXtqYDXumI/hpWWMg/AOTDy+ssSM+WrVfl21acqCt5HG5L8hlQkC33sbvVBkVZqAJNTIEOkfXZ2DMTYqFXprTUzEPQGKUc5zYWazNCZF/z+Qfgha20GWrgIVrayDcRwOoChWMjd5UiJAigTRPTLkzWJ7tcD0qv7HPAM+gs8RpcVknx1rV3y0ZX8vPXgrZJ0K31cblrAyDcIIvCHCurCSt4mBbAsjaTISEA8chAY/MiyuRnILkjcJMT0Mh37BSjgXa04EIEjxcZu8jAv+BMIlQzC+Ux2Gm0RjCUc84NhsP8j7PtxuCCTcwrBzsvlW4DOJ07b7v2NzTjVslIWQwAVxUbmbD11m09cjxnKzDoS1+VloePh3mQXN7fYVoLjuFu+/kLNCIXFs0lxCQCIFdLbTp1rqiVWIYkETBnfB6KW11yf2HX6tcjTdAG25TIcRNgHZ/64ZBbY4q7X1S8A3AQ/X25lXtjqK3C+7fxPpA6bLlyjb4FYnErKTz2tqxFS6uBWObGyjfb4DrOIKCtGRPgPaDFvANPUftL2FYACFKQoiGdn1I6Vn7f9HAHRFikbEKIXhiiWeMoQXEqL2GcykAWovUSaLZLWi7pvO1BZdCjEsFExHO5/YLu2nuqcxdiltCWLDZGqNVVwHFBsKuQTi8gJgrWTohbh0klM/NDXbwAIko0T4Ha6xluIWZO844F8wTlkGfFKWEg14jYXxaslu5qissLeQhJQkOysJMy5mQTLZku4eZbuymaonYWqb+JxabAoZAOJaQrl8e1tJZs2qPK7N164Qegh0XU9s+peyyvIOrNslLvFUpgF6GOlhSLpDMm6WBaektwPpVMr6XypVYASJVC3ojGnpQgyAwta0raTMRek+ANBxxMEy7D9F+D0gHh+Te9192kDHk0oJ4Gwj1X9VVZhwPdfauPWZHIdl6wyf6vNziDWQMg5nvNhVDEy6d5MhgDzIMNMnnDoksiTTIB4yySkC4A0lq30AKymjkNdo5Xn6bi9jZfR2KVmYuPOylwjXIqPoBmOhWw1Px6dYWXKY/QvIEpHmA/HGgea8En8UhP+S/c0JKwzCaZCEi4TgJ7oAiVqB9DPieQ349QCZXhiIATKI9OgCZWFtPiXxJATwAIFoBXPCLWyq7QWexUaGGAIT7yXIU2Uh78VGyhgAIB8BxbBQ2oHZzTY0MJBGJgwGmwwBKPlByQBqxiQkfdVT6jIH8s1SE52ibxznOzyUHs4OdHOLmJzvkOBO2cQAujJlNgCoRTmlhlEpznvk+I7EnZIwSAWOFUBVV0BLJUoU+H5DclW2sQQc+l0gB/C3DYokiDAJRO5cbAUg9L3/cOMTS3DnayJ61XX3xCiu6X66ddG1c1h7ThYh01WVwBVequ1XBAbYBYAXI2WBoor+uSdn1eRhkuu4bRbACeSsJhx+BrgGTH1eOTLXkAKe4ZRLlm5YVFuZVx64NclQdgJr7Ac68gAWv9Xarm15eDtcOuGATr81x6/ddWupHnVtm1Sp8o6vIAerq14a5Ddu7Lwor0g4G89cxvEAcbx13t3DeuvVXKb1V16//kz24VAbqN56/zfGvC34bluaW+tddvK3Cb6t8W+TcSurXNJ97e81bdRvIwHb0N5ZlFd3Le35bgd4m5dcjuPX1JmPclrwNTu83wbzt6a93Ciue3bbvt4e9jddB43q7iN8m5Oz1v0TSgKoOs9wBakZkNAETuYE0ahKxXvLtV+UtwDlHRAki7hZuHDeKYPXubhtwoTYSos2AoryMCaWVbxQfUgHthOxN7eFKIQoriIb++nfq1MARKeD6h8rgwPrCBIKAImkffPvwz9BbSozxtpI8/mqAIE47dHfTvPa8HsZDF3Q+nuhYD9HYMR9g/fvIwDB63S4DYhuu2P66SD2q+g8ot7N8Hq93t19NSCqApAHj9O8w/huHEPLqTw2/w8ewgEinqt5ySYCyuEqqAbIBoDmkABSCzeaNyLrxsAOEHwewGQEa4tWC8YyLAE7CVpqhyAT6xoFpA2fepvb0YgIXE9cAONoQ4aCZ5HjOylI5bpwEgG5NDQG0JkF8+IEQCDB5A5T1T34eSC+frhoX09xx6i9ceR4Gnq13x4nACfKX8nuD1F6ldDv13+rmT567k9Cf4PyLAkDXntDd0gPVXht1p+w+JC9Pargz4R6i/Ee7QpHxpZVHhRllm9LbSA8A21Zf0kuDthwckGpnydaYoGhmeN89dlfPXFXycEN7Vc1f6EgnhT1F5W+Jk1v0gPOHecQBSgJPdbvT+1+Pn1euvUXn8Fg7zAON5+vpgFvmEG9Hfzl4IcNzh4h+TejP03+r7N9gfSrRAw7ZDct/parf+vG3sxPlO28oNnwIJewAd5K/sf1cnHz4Nx4h9Xe6vQH37566FY/hBgQP6EOUaEjPfx+b31r6q6++dfbvnrz982Dk8XfPXI3hIdq1h93hTwhni8PB/1lMvcsX7ngyELt4lEHcatq6k4IFp7QrJUodJOfaBk5K7iQ5QKObekIC1WKARaQj0nQzhJ6t+11paGYpFUW0gTgxw8AzSCvN92iCIwoT2xgi/IwJ3yMGd6IAB+KsGAX5fYFFc6fxXvbmnxYBu+Nf23lgASbe/XTKY2POGqSKrqqCC/hPTOrZekEHsyVPr8KYjWgF8DYEabD2laeLN8CeWGQzIVQN4D2UJBrts+x+ci5p2lAXLvgGOyRsr+RaK/ZFzP10OWC0AVVTL6P9kMz/jhaAVQOIIh8cYXhiP0/3T5K/hgL+MAef1f+L9w8ifN/i/1hHXzcyIBd/WwRIQYE3EjOSXZL5OXJ8OcEvAXxLiZyZRioAlqX+IT/2AkJcGASJ/73niscyUOEAPxWXFKHUBE0avkolfAa/yJcoAV/3f8NgT/xoATKJ/y2cXxVymQCwtAhH2AXAb/x+BaXYPxi9llcWR8kPqAQDABdlQYF8BKAtAzQAwAEo0JUwAS7VHBfbUWQSBX5OfVgCMA71ywDcXBEFwCMAVAJKR9AIAA= --> <!-- internal state end -->
coderabbitai[bot] (Migrated from github.com) reviewed 2026-06-09 00:35:01 +00:00
coderabbitai[bot] (Migrated from github.com) left a comment

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)
go/internal/network/service_test.go (1)

34-37: 🛠️ Refactor suggestion | 🟠 Major | Quick win

Add explicit assertions for member-join notification behavior.

Using AnyTimes() for both ShootEmail and GetByID makes the new notification path in AcceptInvitation effectively untested. Please add a focused expectation in TestNetworkInvitations that validates the join-notification email is sent with the intended recipient set.

Also applies to: 46-55

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@go/internal/network/service_test.go` around lines 34 - 37, In
TestNetworkInvitations, replace the broad AnyTimes() Aero expectations with a
focused EXPECT for mockAero.ShootEmail that asserts the join-notification is
sent to the intended recipient: add an EXPECT call in the test that matches
gomock.AssignableToTypeOf or a custom gomock matcher on the request parameter
and checks the recipient/email field equals the expected member email, and keep
other general expectations (e.g., GetByID) restricted or separate so the
AcceptInvitation path actually exercises and verifies the join-notification
behavior for mockAero.ShootEmail.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@go/internal/network/service.go`:
- Around line 270-296: When building emailRecipients in the block that iterates
prevMembersHumanIds (using s.humanLookup.GetByID and appending to
emailRecipients), only append human.Email if it is non-empty and the human has
EmailNotificationsEnabled == true (or equivalent opt-in flag); after the loop,
skip calling s.aeroSvc.ShootEmail (and avoid calling buildNewMemberHTML) if
emailRecipients is empty to prevent sending with no recipients, and update the
warning log in the error branch to include network/name and recipient count for
context.

In `@js/desktop/README.md`:
- Line 3: Fix the possessive pronoun in the README sentence that reads "In dev:
each renderer process has it's own localhost port." — change "it's" to the
possessive "its" so the sentence becomes "In dev: each renderer process has its
own localhost port." Update the string in the README where that exact sentence
appears.

In `@js/desktop/src/App.tsx`:
- Line 84: DeepLinkNavigationListener is currently only mounted inside the
authenticated UI and so unmounts on sign-out, causing the main process to send
live deep-link events that get lost; fix by either moving the
DeepLinkNavigationListener to a non-auth-gated root so it stays mounted across
auth transitions (mount it in App.tsx outside the auth conditional) or implement
an explicit IPC lifecycle: add renderer-side messages (e.g. 'deep-link-ready'
and 'deep-link-unready') emitted by DeepLinkNavigationListener on mount/unmount
and update the main process to queue deep-link events until it receives a
'deep-link-ready' signal (or stop sending live events while unready). Ensure the
chosen approach updates the main process handler and the
DeepLinkNavigationListener mount/unmount hooks accordingly so no deep-link
events are lost.

---

Outside diff comments:
In `@go/internal/network/service_test.go`:
- Around line 34-37: In TestNetworkInvitations, replace the broad AnyTimes()
Aero expectations with a focused EXPECT for mockAero.ShootEmail that asserts the
join-notification is sent to the intended recipient: add an EXPECT call in the
test that matches gomock.AssignableToTypeOf or a custom gomock matcher on the
request parameter and checks the recipient/email field equals the expected
member email, and keep other general expectations (e.g., GetByID) restricted or
separate so the AcceptInvitation path actually exercises and verifies the
join-notification behavior for mockAero.ShootEmail.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 09d6616b-68c6-4a27-ab02-d79b6565c946

📥 Commits

Reviewing files that changed from the base of the PR and between 9184d002d3 and e3bd3b7aa6.

📒 Files selected for processing (13)
  • .gitignore
  • go/Taskfile.yml
  • go/cmd/orion/main.go
  • go/internal/human/mocks/service.go
  • go/internal/human/service.go
  • go/internal/network/service.go
  • go/internal/network/service_test.go
  • js/desktop/README.md
  • js/desktop/package.json
  • js/desktop/src/App.tsx
  • js/desktop/src/electron.d.ts
  • js/desktop/src/main.ts
  • js/desktop/src/preload.ts
**Actionable comments posted: 3** > [!CAUTION] > Some comments are outside the diff and can’t be posted inline due to platform limitations. > > > > <details> > <summary>⚠️ Outside diff range comments (1)</summary><blockquote> > > <details> > <summary>go/internal/network/service_test.go (1)</summary><blockquote> > > `34-37`: _🛠️ Refactor suggestion_ | _🟠 Major_ | _⚡ Quick win_ > > **Add explicit assertions for member-join notification behavior.** > > Using `AnyTimes()` for both `ShootEmail` and `GetByID` makes the new notification path in `AcceptInvitation` effectively untested. Please add a focused expectation in `TestNetworkInvitations` that validates the join-notification email is sent with the intended recipient set. > > > > Also applies to: 46-55 > > <details> > <summary>🤖 Prompt for AI Agents</summary> > > ``` > Verify each finding against current code. Fix only still-valid issues, skip the > rest with a brief reason, keep changes minimal, and validate. > > In `@go/internal/network/service_test.go` around lines 34 - 37, In > TestNetworkInvitations, replace the broad AnyTimes() Aero expectations with a > focused EXPECT for mockAero.ShootEmail that asserts the join-notification is > sent to the intended recipient: add an EXPECT call in the test that matches > gomock.AssignableToTypeOf or a custom gomock matcher on the request parameter > and checks the recipient/email field equals the expected member email, and keep > other general expectations (e.g., GetByID) restricted or separate so the > AcceptInvitation path actually exercises and verifies the join-notification > behavior for mockAero.ShootEmail. > ``` > > </details> > > <!-- cr-comment:v1:0f1a1799cf1d642a38b6e25c --> > > </blockquote></details> > > </blockquote></details> <details> <summary>🤖 Prompt for all review comments with AI agents</summary> ``` Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. Inline comments: In `@go/internal/network/service.go`: - Around line 270-296: When building emailRecipients in the block that iterates prevMembersHumanIds (using s.humanLookup.GetByID and appending to emailRecipients), only append human.Email if it is non-empty and the human has EmailNotificationsEnabled == true (or equivalent opt-in flag); after the loop, skip calling s.aeroSvc.ShootEmail (and avoid calling buildNewMemberHTML) if emailRecipients is empty to prevent sending with no recipients, and update the warning log in the error branch to include network/name and recipient count for context. In `@js/desktop/README.md`: - Line 3: Fix the possessive pronoun in the README sentence that reads "In dev: each renderer process has it's own localhost port." — change "it's" to the possessive "its" so the sentence becomes "In dev: each renderer process has its own localhost port." Update the string in the README where that exact sentence appears. In `@js/desktop/src/App.tsx`: - Line 84: DeepLinkNavigationListener is currently only mounted inside the authenticated UI and so unmounts on sign-out, causing the main process to send live deep-link events that get lost; fix by either moving the DeepLinkNavigationListener to a non-auth-gated root so it stays mounted across auth transitions (mount it in App.tsx outside the auth conditional) or implement an explicit IPC lifecycle: add renderer-side messages (e.g. 'deep-link-ready' and 'deep-link-unready') emitted by DeepLinkNavigationListener on mount/unmount and update the main process to queue deep-link events until it receives a 'deep-link-ready' signal (or stop sending live events while unready). Ensure the chosen approach updates the main process handler and the DeepLinkNavigationListener mount/unmount hooks accordingly so no deep-link events are lost. --- Outside diff comments: In `@go/internal/network/service_test.go`: - Around line 34-37: In TestNetworkInvitations, replace the broad AnyTimes() Aero expectations with a focused EXPECT for mockAero.ShootEmail that asserts the join-notification is sent to the intended recipient: add an EXPECT call in the test that matches gomock.AssignableToTypeOf or a custom gomock matcher on the request parameter and checks the recipient/email field equals the expected member email, and keep other general expectations (e.g., GetByID) restricted or separate so the AcceptInvitation path actually exercises and verifies the join-notification behavior for mockAero.ShootEmail. ``` </details> <details> <summary>🪄 Autofix (Beta)</summary> Fix all unresolved CodeRabbit comments on this PR: - [ ] <!-- {"checkboxId": "4b0d0e0a-96d7-4f10-b296-3a18ea78f0b9"} --> Push a commit to this branch (recommended) - [ ] <!-- {"checkboxId": "ff5b1114-7d8c-49e6-8ac1-43f82af23a33"} --> Create a new PR with the fixes </details> --- <details> <summary>ℹ️ Review info</summary> <details> <summary>⚙️ Run configuration</summary> **Configuration used**: defaults **Review profile**: CHILL **Plan**: Pro Plus **Run ID**: `09d6616b-68c6-4a27-ab02-d79b6565c946` </details> <details> <summary>📥 Commits</summary> Reviewing files that changed from the base of the PR and between 9184d002d319b2f67f4d5821af19a482810f719e and e3bd3b7aa657b8aa7bc4319fe0a94d570cb3688f. </details> <details> <summary>📒 Files selected for processing (13)</summary> * `.gitignore` * `go/Taskfile.yml` * `go/cmd/orion/main.go` * `go/internal/human/mocks/service.go` * `go/internal/human/service.go` * `go/internal/network/service.go` * `go/internal/network/service_test.go` * `js/desktop/README.md` * `js/desktop/package.json` * `js/desktop/src/App.tsx` * `js/desktop/src/electron.d.ts` * `js/desktop/src/main.ts` * `js/desktop/src/preload.ts` </details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
@@ -289,0 +378,4 @@
<table role="presentation" width="100%%" cellspacing="0" cellpadding="0" border="0" style="max-width:480px;background-color:#ffffff;border-radius:12px;padding:40px;">
<tr>
<td style="font-size:22px;font-weight:600;color:#111111;padding-bottom:16px;">
A new member joined %s
coderabbitai[bot] (Migrated from github.com) commented 2026-06-09 00:34:59 +00:00

⚠️ Potential issue | 🟠 Major | Quick win

Honor notification preferences before sending join emails.

Line 280 appends every resolved member email without checking EmailNotificationsEnabled (and without guarding blank emails). That can notify opted-out users and trigger ShootEmail with no valid recipients.

[sraise_placeholder]

Proposed fix
 	if membersErr == nil && len(prevMembersHumanIds) > 0 {
 		emailRecipients := make([]string, 0, len(prevMembersHumanIds))
 		for _, memberHumanId := range prevMembersHumanIds {
 			if memberHumanId != "" {
 				human, err := s.humanLookup.GetByID(ctx, memberHumanId)
 				if err != nil {
 					flog.Warn("unable to find human", "error", err, "humanId", memberHumanId)
 					continue
 				}
-
-				emailRecipients = append(emailRecipients, human.Email)
+				if !human.EmailNotificationsEnabled || strings.TrimSpace(human.Email) == "" {
+					continue
+				}
+				emailRecipients = append(emailRecipients, human.Email)
 			}
 		}
 
-		newMemberEmailPrefix := strings.Split(normalized, "@")[0]
-		_, err = s.aeroSvc.ShootEmail(ctx, &pbaero.ShootEmailRequest{
-			ToEmails: emailRecipients,
-			Subject:  fmt.Sprintf("A new member has joined %s", network.Name),
-			TemplateData: &pbaero.ShootEmailRequest_SimpleHtmlData{
-				SimpleHtmlData: &pbaero.SimpleHtmlData{
-					Html: buildNewMemberHTML(network.Name, newMemberEmailPrefix),
-				},
-			},
-		})
-		if err != nil {
-			flog.Warn("unable to send email notification", "email", email, "network", network.Name)
+		if len(emailRecipients) > 0 {
+			newMemberEmailPrefix := strings.Split(normalized, "@")[0]
+			_, err = s.aeroSvc.ShootEmail(ctx, &pbaero.ShootEmailRequest{
+				ToEmails: emailRecipients,
+				Subject:  fmt.Sprintf("A new member has joined %s", network.Name),
+				TemplateData: &pbaero.ShootEmailRequest_SimpleHtmlData{
+					SimpleHtmlData: &pbaero.SimpleHtmlData{
+						Html: buildNewMemberHTML(network.Name, newMemberEmailPrefix),
+					},
+				},
+			})
+			if err != nil {
+				flog.Warn("unable to send email notification", "email", email, "network", network.Name)
+			}
 		}
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@go/internal/network/service.go` around lines 270 - 296, When building
emailRecipients in the block that iterates prevMembersHumanIds (using
s.humanLookup.GetByID and appending to emailRecipients), only append human.Email
if it is non-empty and the human has EmailNotificationsEnabled == true (or
equivalent opt-in flag); after the loop, skip calling s.aeroSvc.ShootEmail (and
avoid calling buildNewMemberHTML) if emailRecipients is empty to prevent sending
with no recipients, and update the warning log in the error branch to include
network/name and recipient count for context.

Addressed in commit 60b1c3e

_⚠️ Potential issue_ | _🟠 Major_ | _⚡ Quick win_ **Honor notification preferences before sending join emails.** Line 280 appends every resolved member email without checking `EmailNotificationsEnabled` (and without guarding blank emails). That can notify opted-out users and trigger `ShootEmail` with no valid recipients. [sraise_placeholder] <details> <summary>Proposed fix</summary> ```diff if membersErr == nil && len(prevMembersHumanIds) > 0 { emailRecipients := make([]string, 0, len(prevMembersHumanIds)) for _, memberHumanId := range prevMembersHumanIds { if memberHumanId != "" { human, err := s.humanLookup.GetByID(ctx, memberHumanId) if err != nil { flog.Warn("unable to find human", "error", err, "humanId", memberHumanId) continue } - - emailRecipients = append(emailRecipients, human.Email) + if !human.EmailNotificationsEnabled || strings.TrimSpace(human.Email) == "" { + continue + } + emailRecipients = append(emailRecipients, human.Email) } } - newMemberEmailPrefix := strings.Split(normalized, "@")[0] - _, err = s.aeroSvc.ShootEmail(ctx, &pbaero.ShootEmailRequest{ - ToEmails: emailRecipients, - Subject: fmt.Sprintf("A new member has joined %s", network.Name), - TemplateData: &pbaero.ShootEmailRequest_SimpleHtmlData{ - SimpleHtmlData: &pbaero.SimpleHtmlData{ - Html: buildNewMemberHTML(network.Name, newMemberEmailPrefix), - }, - }, - }) - if err != nil { - flog.Warn("unable to send email notification", "email", email, "network", network.Name) + if len(emailRecipients) > 0 { + newMemberEmailPrefix := strings.Split(normalized, "@")[0] + _, err = s.aeroSvc.ShootEmail(ctx, &pbaero.ShootEmailRequest{ + ToEmails: emailRecipients, + Subject: fmt.Sprintf("A new member has joined %s", network.Name), + TemplateData: &pbaero.ShootEmailRequest_SimpleHtmlData{ + SimpleHtmlData: &pbaero.SimpleHtmlData{ + Html: buildNewMemberHTML(network.Name, newMemberEmailPrefix), + }, + }, + }) + if err != nil { + flog.Warn("unable to send email notification", "email", email, "network", network.Name) + } } } ``` </details> <details> <summary>🤖 Prompt for AI Agents</summary> ``` Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@go/internal/network/service.go` around lines 270 - 296, When building emailRecipients in the block that iterates prevMembersHumanIds (using s.humanLookup.GetByID and appending to emailRecipients), only append human.Email if it is non-empty and the human has EmailNotificationsEnabled == true (or equivalent opt-in flag); after the loop, skip calling s.aeroSvc.ShootEmail (and avoid calling buildNewMemberHTML) if emailRecipients is empty to prevent sending with no recipients, and update the warning log in the error branch to include network/name and recipient count for context. ``` </details> <!-- fingerprinting:phantom:poseidon:hawk --> <!-- cr-comment:v1:179253e249b046480879cce2 --> <!-- This is an auto-generated comment by CodeRabbit --> ✅ Addressed in commit 60b1c3e
@@ -0,0 +1,5 @@
# Notes
## CORS for desktop app
In dev: each renderer process has it's own localhost port. The renderer process passes this in the `Origin` header for requests, and expects appropriate ACAO headers in the response.
coderabbitai[bot] (Migrated from github.com) commented 2026-06-09 00:34:59 +00:00

⚠️ Potential issue | 🟡 Minor | Quick win

Fix possessive pronoun.

Change "it's own" to "its own" (possessive, not contraction).

📝 Proposed fix
-In dev: each renderer process has it's own localhost port. The renderer process passes this in the `Origin` header for requests, and expects appropriate ACAO headers in the response.
+In dev: each renderer process has its own localhost port. The renderer process passes this in the `Origin` header for requests, and expects appropriate ACAO headers in the response.
📝 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.

In dev: each renderer process has its own localhost port. The renderer process passes this in the `Origin` header for requests, and expects appropriate ACAO headers in the response.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@js/desktop/README.md` at line 3, Fix the possessive pronoun in the README
sentence that reads "In dev: each renderer process has it's own localhost port."
— change "it's" to the possessive "its" so the sentence becomes "In dev: each
renderer process has its own localhost port." Update the string in the README
where that exact sentence appears.
_⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Quick win_ **Fix possessive pronoun.** Change "it's own" to "its own" (possessive, not contraction). <details> <summary>📝 Proposed fix</summary> ```diff -In dev: each renderer process has it's own localhost port. The renderer process passes this in the `Origin` header for requests, and expects appropriate ACAO headers in the response. +In dev: each renderer process has its own localhost port. The renderer process passes this in the `Origin` header for requests, and expects appropriate ACAO headers in the response. ``` </details> <!-- suggestion_start --> <details> <summary>📝 Committable suggestion</summary> > ‼️ **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. ```suggestion In dev: each renderer process has its own localhost port. The renderer process passes this in the `Origin` header for requests, and expects appropriate ACAO headers in the response. ``` </details> <!-- suggestion_end --> <details> <summary>🤖 Prompt for AI Agents</summary> ``` Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@js/desktop/README.md` at line 3, Fix the possessive pronoun in the README sentence that reads "In dev: each renderer process has it's own localhost port." — change "it's" to the possessive "its" so the sentence becomes "In dev: each renderer process has its own localhost port." Update the string in the README where that exact sentence appears. ``` </details> <!-- fingerprinting:phantom:poseidon:puma --> <!-- cr-comment:v1:a04e62ddae6a345cfe823d8a --> <!-- This is an auto-generated comment by CodeRabbit -->
coderabbitai[bot] (Migrated from github.com) commented 2026-06-09 00:34:59 +00:00

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Deep-link listener is auth-gated and can drop OS deep links

DeepLinkNavigationListener only mounts in authenticated UI (Line 84). When auth flips away from authenticated, it unmounts, but the main process remains in “ready” mode and sends live deep-link events instead of queueing them; those events are lost before re-authentication.

Keep deep-link subscription active across auth states, or add explicit ready/unready IPC lifecycle so main only live-sends when a listener is actually mounted.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@js/desktop/src/App.tsx` at line 84, DeepLinkNavigationListener is currently
only mounted inside the authenticated UI and so unmounts on sign-out, causing
the main process to send live deep-link events that get lost; fix by either
moving the DeepLinkNavigationListener to a non-auth-gated root so it stays
mounted across auth transitions (mount it in App.tsx outside the auth
conditional) or implement an explicit IPC lifecycle: add renderer-side messages
(e.g. 'deep-link-ready' and 'deep-link-unready') emitted by
DeepLinkNavigationListener on mount/unmount and update the main process to queue
deep-link events until it receives a 'deep-link-ready' signal (or stop sending
live events while unready). Ensure the chosen approach updates the main process
handler and the DeepLinkNavigationListener mount/unmount hooks accordingly so no
deep-link events are lost.
_⚠️ Potential issue_ | _🟠 Major_ | _🏗️ Heavy lift_ **Deep-link listener is auth-gated and can drop OS deep links** `DeepLinkNavigationListener` only mounts in authenticated UI (Line 84). When auth flips away from authenticated, it unmounts, but the main process remains in “ready” mode and sends live deep-link events instead of queueing them; those events are lost before re-authentication. Keep deep-link subscription active across auth states, or add explicit ready/unready IPC lifecycle so main only live-sends when a listener is actually mounted. <details> <summary>🤖 Prompt for AI Agents</summary> ``` Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@js/desktop/src/App.tsx` at line 84, DeepLinkNavigationListener is currently only mounted inside the authenticated UI and so unmounts on sign-out, causing the main process to send live deep-link events that get lost; fix by either moving the DeepLinkNavigationListener to a non-auth-gated root so it stays mounted across auth transitions (mount it in App.tsx outside the auth conditional) or implement an explicit IPC lifecycle: add renderer-side messages (e.g. 'deep-link-ready' and 'deep-link-unready') emitted by DeepLinkNavigationListener on mount/unmount and update the main process to queue deep-link events until it receives a 'deep-link-ready' signal (or stop sending live events while unready). Ensure the chosen approach updates the main process handler and the DeepLinkNavigationListener mount/unmount hooks accordingly so no deep-link events are lost. ``` </details> <!-- fingerprinting:phantom:poseidon:hawk --> <!-- cr-comment:v1:8717b9072fb04377f6b22cb9 --> <!-- This is an auto-generated comment by CodeRabbit -->
Sign in to join this conversation.