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 -->
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.
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 -->
[](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 -->
<!-- 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 -->
<!-- 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
Copy Link
Copy Source
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
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 -->
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.
_⚠️ 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
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.
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 -->
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 -->
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary by CodeRabbit
New Features
Security
Documentation
📝 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
go/internal/human/service.go,go/internal/human/mocks/service.goMockServicewith methods for lookup, creation, listing, and notification-state updates to support tests.go/internal/network/service.go,go/cmd/orion/main.gohumanLookupinterface, wire it intoserviceImpl, updateNewServicesignature, and main now passes a Firestore-backed membership publisher.go/internal/network/service.go,go/internal/network/service_test.goAcceptInvitationpreloads 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.go/internal/network/service_test.goGetByID, and pass it intonetwork.NewServicewith the new arity..gitignore,go/Taskfile.yml.gitignoreentry fortagsand addgo fmt ./...format step to Taskfile generate task.Electron Deep Linking
js/desktop/src/electron.d.ts,js/desktop/src/preload.tsWindowtype extended withelectronDeepLinkand preload exposesgetPending()andonNavigate()to the renderer.js/desktop/src/main.tsdeepLinkPath/tryNavigateToDeepLink, store pending deep links for cold-starts, route OSllink://events, register IPC to deliver pending links, tightenwebPreferences, and remove prior CORS header injection.js/desktop/src/App.tsxDeepLinkNavigationListenerfetches pending deep-links on mount and subscribes to future navigate events viaelectronDeepLink, navigating with React Router.js/desktop/README.md,js/desktop/package.jsoncross-env APP_ENV=prod.Sequence Diagrams
Estimated code review effort
🎯 4 (Complex) | ⏱️ ~45 minutes
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings.
✨ Finishing Touches
📝 Generate docstrings
🧪 Generate unit tests (beta)
free-monkeyComment
@coderabbitai helpto get the list of available commands and usage tips.Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
ℹ️ 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
9184d002d3ande3bd3b7aa6.📒 Files selected for processing (13)
.gitignorego/Taskfile.ymlgo/cmd/orion/main.gogo/internal/human/mocks/service.gogo/internal/human/service.gogo/internal/network/service.gogo/internal/network/service_test.gojs/desktop/README.mdjs/desktop/package.jsonjs/desktop/src/App.tsxjs/desktop/src/electron.d.tsjs/desktop/src/main.tsjs/desktop/src/preload.ts@@ -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⚠️ 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 triggerShootEmailwith no valid recipients.[sraise_placeholder]
Proposed fix
🤖 Prompt for AI Agents
✅ Addressed in commit
60b1c3e@@ -0,0 +1,5 @@# Notes## CORS for desktop appIn 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.⚠️ Potential issue | 🟡 Minor | ⚡ Quick win
Fix possessive pronoun.
Change "it's own" to "its own" (possessive, not contraction).
📝 Proposed fix
📝 Committable suggestion
🤖 Prompt for AI Agents
⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift
Deep-link listener is auth-gated and can drop OS deep links
DeepLinkNavigationListeneronly 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