Skip to content

feat(v3:notifications): add sounds, attachments, and more#5333

Open
popaprozac wants to merge 21 commits into
wailsapp:masterfrom
popaprozac:feat/notifications-sounds-attachments-and-more
Open

feat(v3:notifications): add sounds, attachments, and more#5333
popaprozac wants to merge 21 commits into
wailsapp:masterfrom
popaprozac:feat/notifications-sounds-attachments-and-more

Conversation

@popaprozac
Copy link
Copy Markdown
Contributor

@popaprozac popaprozac commented May 4, 2026

Description

Extends the v3 notifications service so apps can attach sounds, images, threads, priority levels, scheduled delivery, and update existing notifications by ID — bringing macOS, Windows, and Linux to feature parity (with documented
graceful degradation per platform).

This started from my own framework's notification implementation; the goal here was to bring the same surface to Wails in a way that fits existing conventions (additive NotificationOptions fields, the platformNotifier interface, build-tagged platform files, the existing OnNotificationResponse callback pattern).

Per-platform support matrix

Feature macOS Windows Linux
Title / body / actions / reply ✓ (no reply per freedesktop spec)
Subtitle (not shown) ✓ (concatenated into body)
Custom sound name [UNNotificationSound soundNamed:] ✓ via <audio src=ms-winsoundevent:...> ✓ via freedesktop sound-name hint
Silent ✓ via <audio silent="true"/> ✓ via suppress-sound hint
Attachment (image) ✓ image/audio/video ✓ `<image placement="hero appLogoOverride
Threading / group threadIdentifier <header> element ✓ via category hint
Scheduling ✓ native trigger (persists across launches) best-effort in-process timer best-effort in-process timer
InterruptionLevel ✓ macOS 12+ (critical needs entitlement) ✓ `<toast scenario="reminder urgent"/>`
Update by ID ✓ auto-dedup ✗ redelivers (wintoast lacks tag/group) ✓ via D-Bus replaces_id

Documented as godoc on the package and as a markdown table in the example app.

Fixes # (no existing issue — happy to file one)

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

All NotificationOptions additions are optional fields; UpdateNotification is a new method on the service. Existing callers compile and run unchanged.

How Has This Been Tested?

End-to-end via the extended example app (v3/examples/notifications) on:

  • Windows
  • macOS
  • Linux (Fedora 44)

For each platform I exercised:

  • Basic notification + click response (default action round-trips through OnNotificationResponseapp.Event.Emit("notification:action", ...) → frontend table)
  • Complex (action buttons + reply field where supported)
  • Custom sound by name (Ping on macOS, IM on Windows, bell on Linux)
  • Silent sound
  • Attachment (sample image written to TempDir and attached)
  • ThreadID grouping (two notifications with the same thread ID)
  • Scheduled delivery (5-second delay)
  • InterruptionLevel (active / timeSensitive / critical)
  • UpdateNotification: post → wait 2s → update (replaces in place on macOS and Linux; redelivers on Windows as documented)
  • Cancel pending scheduled notification before it fires

Validator unit tests added in v3/pkg/services/notifications/notifications_test.go cover required fields, interruption level enum, schedule mutual-exclusion + negative values, and attachment path stat. Run with:

cd v3 && go test ./pkg/services/notifications/...

go vet ./pkg/services/notifications/... passes for darwin and (cross-compiled) windows targets locally; Linux verified on the test VM.

Test Configuration

Wails (v3.0.0-dev)  Wails Doctor

# System

┌──────────────────────────────────────────────────┐
| Name          | MacOS                            |
| Version       | 26.5                             |
| ID            | 25F5053d                         |
| Branding      | MacOS 26.5                       |
| Platform      | darwin                           |
| Architecture  | arm64                            |
| Apple Silicon | true                             |
| CPU           | Apple M4 Max                     |
| CPU 1         | Apple M4 Max                     |
| CPU 2         | Apple M4 Max                     |
| GPU           | 32 cores, Metal Support: Metal 4 |
| Memory        | 36 GB                            |
└──────────────────────────────────────────────────┘

# Build Environment

┌─────────────────────────────────────────────────────────┐
| Wails CLI    | v3.0.0-dev                               |
| Go Version   | go1.25.0                                 |
| Revision     | e0976bdc7cb6e24de76e438d73d83940e2363288 |
| Modified     | true                                     |
| -buildmode   | exe                                      |
| -compiler    | gc                                       |
| CGO_CFLAGS   |                                          |
| CGO_CPPFLAGS |                                          |
| CGO_CXXFLAGS |                                          |
| CGO_ENABLED  | 1                                        |
| CGO_LDFLAGS  |                                          |
| GOARCH       | arm64                                    |
| GOARM64      | v8.0                                     |
| GOOS         | darwin                                   |
| vcs          | git                                      |
| vcs.modified | true                                     |
| vcs.revision | e0976bdc7cb6e24de76e438d73d83940e2363288 |
| vcs.time     | 2026-05-04T18:34:39Z                     |
└─────────────────────────────────────────────────────────┘

# Dependencies

┌─────────────────────────────────────────────────────────────────────┐
| Xcode cli tools | 2416                                              |
| npm             | 10.9.2                                            |
| *NSIS           | v3.11                                             |
| docker          | *Not installed (optional - for cross-compilation) |
|                                                                     |
└────────────────────── * - Optional Dependency ──────────────────────┘

# Checking for issues

SUCCESS  No issues found

# Diagnosis

SUCCESS  Your system is ready for Wails development!

Checklist:

  • (v2 only) I have updated website/src/pages/changelog.mdx with details of this PR (v3 changelog entries are added automatically)
  • My code follows the general coding style of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation (package godoc with the support matrix; UNRELEASED_CHANGELOG.md entries under Added and Changed)
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works (notifications_test.go covers the new validator paths)
  • New and existing unit tests pass locally with my changes

Notes for reviewers / known follow-ups

A few items I'm flagging up-front since they're real and documented limitations rather than oversights:

  • Windows update-by-ID redelivers instead of replacing in place. Real fix requires wintoast to expose the tag/group parameters of ToastNotifier.Show() (it currently exposes only the XML push). Worth a separate small PR
    upstream.
  • Schedule is in-process on Windows + Linux, lost on app restart. macOS uses native triggers and persists. Real fix requires either a native ScheduledToastNotification API in wintoast or a system-bus daemon on Linux.
  • Linux reply field isn't supported by the freedesktop spec; some KDE-specific x-hints exist but I didn't wire them.
  • The Windows backend now constructs toast XML by hand. The wintoast subpackage stays as the dependency for COM/WinRT plumbing — that subpackage is the load-bearing part that would be expensive to replace.

Summary by CodeRabbit

  • New Features

    • Update in-flight notifications by ID
    • Schedule notifications (delay or specific time)
    • Attach images/files to notifications
    • Control sounds (silent or named) and interruption levels
    • Threaded/grouped notifications via thread ID
    • Demo service to provide sample image assets
  • Examples / UI

    • Expanded notification demo with builder, quick tests, update/cancel actions, and status display
  • Tests

    • Validation tests for new notification options (scheduling, attachments, interruption levels)
  • Documentation

    • Changelog updated; platform behaviors and reply-field support clarified across OSes

popaprozac and others added 11 commits May 4, 2026 11:30
…oast

Drop the dependency on the high-level go-toast/v2 builder package
(git.sr.ht/~jackmordaunt/go-toast/v2) in favour of constructing the
toast XML ourselves with encoding/xml and pushing it through the
already-used wintoast subpackage.

The wintoast subpackage exposes SetAppData, SetActivationCallback, and
Push as public API, so no further linkname shims were needed beyond the
existing registerFactoryInternal / wintoast.ClassFactory wiring at
service Startup (kept so cold-start clicks on toasts pinned in Action
Center from a previous run still dispatch to OnNotificationResponse).

Behaviour-preserving change. The encoded base64 payload that round-trips
NotificationOptions through every <action arguments="..."> attribute
and the toast's launch="..." attribute is unchanged, as is the
<input id="userText"> channel for reply text. Actions, categories, and
ActivationType=foreground all match the previous template output.

Net effect: encoding/xml now handles attribute escaping (a small
robustness win for titles/bodies containing < > & or quotes), and the
Windows backend is positioned to wire the new sound, image, threading,
scenario, schedule, and tag-based update fields directly into XML in a
follow-up commit without relying on the upstream go-toast struct
exposing those fields.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…cross the cgo boundary

The send-notification C entry points used to take five separate
const-char* parameters (identifier, title, subtitle, body, data_json).
Replace them with a single options_json blob produced by json.Marshal of
the whole NotificationOptions struct, parsed in Objective-C with
NSJSONSerialization.

Behaviour-preserving change: title/subtitle/body/userInfo are still
populated identically, the default sound is still applied, and the
identifier/categoryId still flow through to UNNotificationRequest. New
helper functions parseOptions and stringOrEmpty in the .m provide safe
typed access; createNotificationContentFromOptions replaces the previous
per-arg createNotificationContent.

Net effect: future fields (sound, attachments, threadId,
interruptionLevel, schedule) can be wired in by reading additional keys
from the same dict in createNotificationContentFromOptions and the
follow-up trigger path, without growing C signatures or adding more
C.CString churn on the Go side.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nLevel, Schedule, and UpdateNotification

Extend NotificationOptions with five additive fields plus a new
UpdateNotification method on NotificationService. Each new field
gracefully degrades on platforms with limited backend support; see the
package godoc for the per-platform support matrix.

Sound: nil = platform default, {silent: true} = no sound,
{name: "..."} = bundled/system named sound. macOS uses
[UNNotificationSound soundNamed:]; Windows emits <audio src="..."/>
(ms-winsoundevent: or ms-appx: scheme); Linux forwards as the freedesktop
sound-name / suppress-sound hints.

Attachments: media files shown inline. macOS supports any UTI via
UNNotificationAttachment; Windows wires the first entry to a
<image placement="hero|appLogoOverride|inline"/> binding; Linux forwards
the first entry as the freedesktop image-path hint.

ThreadID: groups related notifications. macOS sets threadIdentifier;
Windows emits a <header/> element; Linux uses the freedesktop category
hint plus an x-wails-thread-id hint for round-tripping.

InterruptionLevel: passive | active | timeSensitive | critical.
macOS 12+ maps to UNNotificationInterruptionLevel (critical needs the
Critical Alert entitlement). Windows maps to <toast scenario="..."/>
(reminder/urgent). Linux maps to the freedesktop urgency hint.

Schedule: deferred delivery via {delaySeconds} or {at} (Unix seconds).
macOS uses native UNTimeInterval/UNCalendar triggers and persists across
app restarts. Windows + Linux fall back to in-process time.AfterFunc
timers tracked by ID for cancellation via RemovePendingNotification;
these timers are lost on app exit (documented limitation).

UpdateNotification(options): re-posts a notification by ID. macOS auto-
deduplicates on identifier; Linux uses the D-Bus replaces_id parameter
(notify() now scans tracked notifications for a matching options.ID and
threads it through, so SendNotification with a known ID also updates in
place). Windows currently redelivers as a new notification; true replace
requires upstream wintoast tag/group support, tracked as a follow-up.

Validator coverage: invalid InterruptionLevel, schedule with both
fields, schedule with neither, negative schedule values, missing-file
attachments, and empty attachment paths each surface as errors at the
service entry point. Unit tests in notifications_test.go cover every
new path. Existing required-field validation is preserved.

Frontend bindings regenerated; UNRELEASED_CHANGELOG entries added under
Added and Changed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…field

Rewrite the example so the test app can exercise sounds, attachments,
threading, scheduling, interruption levels, and UpdateNotification
directly from the UI on every platform.

main.go now bundles the wails logo as a sample image and writes it to
os.TempDir() at startup, then exposes the absolute path through a
DemoAssets helper service. The frontend uses that path in the
NotificationOptions.Attachments field so the demo can attach a real
file across macOS/Windows/Linux without a file picker or an extra
filesystem dependency.

The frontend (index.html + main.ts + style.css) is split into three
sections:

* Authorization — request and check (macOS gate; Windows/Linux are
  always-true stubs).
* Quick tests — Basic, Complex, Threaded (two with the same threadId),
  Scheduled (5s delay), Update demo (post → 2s → update), and Cancel
  last scheduled.
* Custom builder — a form for title/subtitle/body/threadId/sound/level/
  delay plus checkboxes for sample image attachment and action category.
  Buttons send / update by last id / remove.

Bindings regenerated; the new NotificationSound / NotificationAttachment
/ NotificationSchedule classes plus interruptionLevel / threadId /
schedule / sound / attachments / UpdateNotification entries are checked
in. package-lock.json is committed too, matching the convention used
by other examples (dock, file-association, etc.) so frontend installs
are reproducible inside test VMs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…hint

The new NotificationAttachment.Type field carries different meanings per
platform: on Windows it's a placement hint ("hero"/"appLogoOverride"/
"inline"), on macOS it's an optional UTI such as "public.png". The
example was sending type="hero" by default, which on macOS was passed
through to UNNotificationAttachmentOptionsTypeHintKey verbatim — and
because "hero" is not a valid UTI, UNNotificationAttachment refused to
build the attachment and silently dropped it.

Two-part fix:

1. notifications_darwin.m only forwards Type as a UTI hint when it
   actually looks like one (UTI strings always contain a "."). For any
   other value we omit the hint and let UNUserNotificationCenter
   auto-infer the type from the file extension. Documented inline.

2. The example now sends attachments without a Type, so macOS auto-
   infers and Windows uses the inline default. Hero/appLogoOverride
   placements still work — callers just opt in by passing the
   corresponding Type explicitly.

Also: split the example's status messages out of the response container
into a dedicated #status div, so the notification-response table that
appears after a click/action/reply persists across subsequent button
presses instead of being clobbered by status text.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… attach reuse, IPv4 binding)

1. Notification response handler crashes after click

   The current @wailsio/runtime delivers the emitted payload directly as
   WailsEvent.data; older runtimes wrapped it in [data]. The previously
   committed handler destructured response.data[0], which is undefined on
   the new shape and threw "Cannot destructure property 'userInfo' from
   null or undefined value" the first time the user clicked a delivered
   notification — masking every subsequent interaction. The handler now
   accepts both shapes and bails with a console warning if the payload
   is genuinely empty.

2. Attaching the sample image only worked once per app launch

   macOS UNNotificationAttachment moves the source file into the system's
   notification database after delivery. The DemoAssets helper wrote the
   bundled wails.png to TempDir once at startup, so the second send found
   the path stale and validateNotificationOptions rejected it with a 422
   ("no such file or directory"). DemoAssets.SampleImagePath now rewrites
   the file on every call, keeping subsequent sends self-healing without
   requiring a service restart.

3. wails3 dev opens a blank window with "connection refused"

   Recent Node resolves the literal hostname "localhost" to ::1 first,
   so vite was binding IPv6-only (TCP [::1]:<port>). The wails3 dev
   asset proxy explicitly forces tcp4 (to dodge IPv6 issues on Windows),
   which produced "dial tcp4 127.0.0.1:<port>: connect: connection
   refused" the moment the WebView asked for /, even though preRun's
   probe (default dual-stack dialer) reported the dev server was up.
   Pin vite to --host 127.0.0.1 in the example's dev:frontend task so
   it binds IPv4 and the proxy can reach it.

Other examples in v3/examples/*/build/Taskfile.yml have the same shape
and will likely hit this on similar Node versions; fixing them is out
of scope for this PR but worth a follow-up.

Bindings regenerated to track the SampleImagePath signature change
(now also returns an error from the rewrite); the TS surface is
unchanged because Wails serialises errors through a separate channel.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…attach works

The previous fix made DemoAssets.SampleImagePath rewrite the file on the
Go side every call, but the frontend was still caching the FIRST promise
returned by the binding and reusing it on every subsequent attach — so
the Go-side rewrite never re-fired after the initial send. macOS moved
the file out of TempDir on delivery, the cached path went stale, and
validateNotificationOptions rejected the second send with 422
"no such file or directory".

Drop the sampleImagePath cache; let every checkbox-attach round-trip to
the Go side so the file is rewritten in place each time.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
scheduleDelay was defined in notifications_windows.go (build-tagged
//go:build windows) but referenced from notifications_linux.go for the
in-process schedule fallback, so the Linux build failed with
"undefined: scheduleDelay".

Move the helper to notifications.go where it has no build tag and is
visible to every backend that wants the in-process schedule fallback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 4, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds richer notification options (sound, attachments, thread ID, interruption level, schedule) and an UpdateNotification API; validates new fields; macOS bridge accepts a JSON options blob; Windows builds toast XML via wintoast and adds scheduling; Linux adds D-Bus hints and per-ID scheduling; frontend examples, bindings, and tests updated.

Changes

Notification Enhancement & Update Support

Layer / File(s) Summary
Data Shape / Types
v3/pkg/services/notifications/notifications.go, v3/examples/notifications/frontend/bindings/.../models.ts
Adds NotificationSound, NotificationAttachment, NotificationSchedule; extends NotificationOptions with Sound, Attachments, ThreadID, InterruptionLevel, Schedule; adds InterruptionLevel* constants.
Validation & Service API
v3/pkg/services/notifications/notifications.go, v3/pkg/services/notifications/notifications_test.go
Adds NotificationService.UpdateNotification and platform interface method; extends validateNotificationOptions to check interruption levels, schedule rules (mutual exclusivity, non-negative, require one), attachment path existence; adds unit tests.
macOS Native Bridge
v3/pkg/services/notifications/notifications_darwin.h, v3/pkg/services/notifications/notifications_darwin.m, v3/pkg/services/notifications/notifications_darwin.go
C bridge and Obj‑C updated to accept a single JSON options_json; Obj‑C parses options, applies sound, attachments, interruption level, schedule, builds UNMutableNotificationContent and triggers; Go marshals options and dispatches via dispatchNotification; UpdateNotification re-dispatches.
Windows Implementation
v3/pkg/services/notifications/notifications_windows.go
Switched to wintoast and XML toast construction with encoding/xml structs; adds per-ID scheduling (scheduledTimers), park/cancel helpers, pushNow, sound/scenario mapping; UpdateNotification routes via category when applicable; pending cancellation implemented.
Linux Implementation
v3/pkg/services/notifications/notifications_linux.go
Adds per-ID scheduled timers and locking, parkSchedule/cancelScheduled, central notify(...) helper setting freedesktop hints (category, x-user-data, sound, image-path, thread/group, urgency), uses replaces_id for updates, and manages timers on cancel/clear; UpdateNotification re-posts appropriately.
Frontend Bindings & Service Calls
v3/examples/notifications/frontend/bindings/.../notificationservice.ts, .../models.ts, bindings/notifications/demoassets.ts, bindings/notifications/index.ts
Adds UpdateNotification binding; re-exports new model types and createFrom factories; adds DemoAssets binding exposing SampleImagePath().
Example App UI & Backend Demo Assets
v3/examples/notifications/frontend/index.html, .../public/style.css, frontend/src/main.ts, v3/examples/notifications/main.go
Example UI expanded (Authorization, Quick tests, Custom builder, status); CSS grid/layout added; frontend logic extended for send/update/schedule/cancel and richer action-response handling; adds DemoAssets service embedding sample image and returning temp path.
Changelog & Build Task
v3/UNRELEASED_CHANGELOG.md, v3/examples/notifications/build/Taskfile.yml
Changelog documents new fields, UpdateNotification, and backend notes; Taskfile had a minor command-line tweak (no behavioral change).

Sequence Diagram

sequenceDiagram
    participant User as User (UI)
    participant Frontend as Frontend App
    participant Service as Go Service
    participant Platform as Platform Layer
    participant Native as Native OS API

    rect rgba(100, 150, 200, 0.5)
    Note over User,Native: Send Notification Flow
    User->>Frontend: Click "Send" with options
    Frontend->>Frontend: Build NotificationOptions
    Frontend->>Service: SendNotification(options)
    Service->>Service: Validate options
    alt Schedule present
        Service->>Platform: Park timer
        Platform->>Platform: Timer fires
        Platform->>Native: Push notification
    else Immediate
        Service->>Platform: Push immediately
        Platform->>Native: Push notification
    end
    Native-->>Service: Acknowledged
    Service-->>Frontend: Resolve / return ID
    end

    rect rgba(150, 100, 200, 0.5)
    Note over User,Native: Update Notification Flow
    User->>Frontend: Click "Update" with ID
    Frontend->>Service: UpdateNotification(options with ID)
    Service->>Service: Validate options
    Service->>Platform: Re-dispatch with same ID
    Platform->>Native: Replace or re-send notification
    Native-->>Platform: Acknowledged
    Platform-->>Frontend: Success
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • wailsapp/wails#4256: Modifies the desktop notifications subsystem with overlapping API and platform changes.
  • wailsapp/wails#4098: Touches the same notifications package, examples, and bindings.
  • wailsapp/wails#4450: Related Windows notification handling and activation payload changes.

Suggested labels

Enhancement, Documentation, bindings, Typescript, Windows, MacOS, Linux, v3, Implemented in v3, size:XXL

Suggested reviewers

  • leaanthony

Poem

🐇 I pack sounds and images into JSON delight,

I thread and schedule through day and night,
I hop from frontend to native shore,
Update, replace — I do one hop more,
A rabbit's ping: your notifications take flight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.00% 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
Title check ✅ Passed The title 'feat(v3:notifications): add sounds, attachments, and more' accurately summarizes the main change: extending the notifications service with new features (sounds, attachments, and additional capabilities) while maintaining clarity and conciseness.
Description check ✅ Passed The PR description is comprehensive and well-structured, including a detailed summary, per-platform support matrix, testing methodology across all three platforms, test configuration, completed checklist items, and important notes about known limitations. All template sections are addressed.
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.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@popaprozac popaprozac changed the title Feat/notifications sounds attachments and more feat(v3:notifications): add sounds, attachments, and more May 4, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (3)
v3/examples/notifications/main.go (2)

38-41: ⚡ Quick win

newDemoAssets can never return a non-nil error — dead code at the call site.

The function body only calls filepath.Join(os.TempDir(), ...), which never fails. os.TempDir() returns a string without an error return, and filepath.Join is infallible, so the (error) in the return signature is vacuous and the log.Fatalf block (lines 67–69) is unreachable.

Either drop the error return and simplify the call site, or move the os.WriteFile call here (making the error meaningful at startup):

♻️ Option A — drop vacuous error return
-func newDemoAssets() (*DemoAssets, error) {
+func newDemoAssets() *DemoAssets {
     path := filepath.Join(os.TempDir(), "wails-notifications-sample.png")
-    return &DemoAssets{sampleImagePath: path}, nil
+    return &DemoAssets{sampleImagePath: path}
 }
-    demo, err := newDemoAssets()
-    if err != nil {
-        log.Fatalf("failed to prepare demo assets: %v", err)
-    }
+    demo := newDemoAssets()
🤖 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 `@v3/examples/notifications/main.go` around lines 38 - 41, The newDemoAssets
function currently returns an unused error because filepath.Join and os.TempDir
cannot fail; either remove the error from newDemoAssets' signature and return
*DemoAssets only (then update its call site to stop checking an error), or make
the error meaningful by creating/writing the sample image inside newDemoAssets
(use os.WriteFile to write to sampleImagePath and return any write error) so
callers (the log.Fatalf at the current call site) can handle it; refer to the
function newDemoAssets and the DemoAssets.sampleImagePath field when making the
change.

52-57: ⚡ Quick win

SampleImagePath writes to a shared path without synchronisation.

Concurrent frontend invocations (e.g. the user clicks "Send" twice before the first call returns) will both call os.WriteFile on the same path simultaneously. os.WriteFile is not guaranteed to be atomic — it opens, truncates, then writes — so a concurrent reader (the OS notification system) could observe a truncated file between those steps.

In practice the risk is low because both writes carry identical bytes, but since this is example/reference code a sync.Mutex makes the intent explicit:

🔒 Proposed fix
 type DemoAssets struct {
     sampleImagePath string
+    mu              sync.Mutex
 }
 func (d *DemoAssets) SampleImagePath() (string, error) {
+    d.mu.Lock()
+    defer d.mu.Unlock()
     if err := os.WriteFile(d.sampleImagePath, sampleImage, 0o644); err != nil {
         return "", fmt.Errorf("write sample image: %w", err)
     }
     return d.sampleImagePath, nil
 }
🤖 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 `@v3/examples/notifications/main.go` around lines 52 - 57, SampleImagePath
writes to d.sampleImagePath without synchronization, allowing concurrent calls
to os.WriteFile to interleave and expose truncated content; add a mutex on
DemoAssets (e.g., sampleImageMu or writeMu) and lock/unlock it inside
SampleImagePath around the write to serialize concurrent writes (acquire before
os.WriteFile and release after) so multiple invocations of SampleImagePath
cannot write the same path concurrently; update the DemoAssets struct to include
the mutex and use it in SampleImagePath.
v3/pkg/services/notifications/notifications.go (1)

167-171: ⚡ Quick win

NotificationSound allows an ambiguous {Silent: true, Name: "non-empty"} combination without validation

The three documented uses (nil, {Silent:true}, {Name:"…"}) are mutually exclusive, but no validation enforces this. Callers who set both fields get platform-dependent, undocumented behavior (whichever field the backend prioritises).

♻️ Proposed validation addition inside `validateNotificationOptions`
+   if options.Sound != nil && options.Sound.Silent && options.Sound.Name != "" {
+       return fmt.Errorf("notification sound: Silent and Name are mutually exclusive")
+   }
+
    if options.InterruptionLevel != "" {
🤖 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 `@v3/pkg/services/notifications/notifications.go` around lines 167 - 171, Add
mutual-exclusivity validation for NotificationSound inside
validateNotificationOptions: if opts.Sound != nil and opts.Sound.Silent is true
while opts.Sound.Name is non-empty, return a validation error (e.g.,
"NotificationSound: Silent and Name are mutually exclusive"); implement this
check in the validateNotificationOptions function so callers cannot pass
{Silent:true, Name:"…"} and platform-dependent behavior is avoided.
🤖 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 `@v3/examples/notifications/frontend/src/main.ts`:
- Around line 299-348: The handler registered with
Events.On("notification:action") builds HTML using string interpolation and
assigns it to footer.innerHTML, which allows XSS via
response.data/userInfo/userText; change the rendering to construct DOM nodes
(document.createElement, appendChild) and set textContent for each key/value (or
run every interpolated value through a proper escaping helper) instead of using
innerHTML, and update the final assignment to append the constructed node(s) to
the footer element rather than assigning footer.innerHTML.

In `@v3/pkg/services/notifications/notifications_linux.go`:
- Around line 118-124: When scheduling a delayed notification via
ln.parkSchedule (the block that calls ln.parkSchedule(options.ID,... ) before
returning), ensure any existing parked timer for the same notification ID is
cancelled/cleared before creating a new one and make the parked callback guard
against races by validating it is still the active entry before deleting or
invoking SendNotification; in practice: in the code paths that create a new
parked timer (parkSchedule), first cancel/remove any existing parked timer for
options.ID, and add a generation token or compare the stored timer entry inside
the callback so an old callback cannot delete or run for a newer timer; apply
the same cancellation/guarding logic to the other similar blocks (around lines
for 147-153 and 288-303) that reuse notification IDs.

In `@v3/pkg/services/notifications/notifications_test.go`:
- Around line 112-141: validateNotificationOptions currently calls os.Stat on
every NotificationAttachment.Path which breaks macOS `file://` URL semantics;
update validateNotificationOptions to detect `file://` URIs (e.g.,
strings.HasPrefix(path, "file://")) and on darwin either convert the URL to a
local filesystem path (URL unescape and drop the scheme) before calling os.Stat
or skip os.Stat for file:// entries and rely on the Darwin bridge, and add a
regression test in TestValidateNotificationOptions_Attachments that creates a
temp file, constructs a `file://` URL to it, and asserts
validateNotificationOptions accepts that attachment on darwin.

In `@v3/pkg/services/notifications/notifications_windows.go`:
- Around line 184-205: The scheduled timer logic in sendOrSchedule causes
ID-collision bugs: a previously-fired timer can delete a newer timer and the
no-schedule path never clears an existing parked timer. Fix by (1) when creating
timer capture the specific timer value and in the callback hold scheduledLock
and only delete wn.scheduledTimers[options.ID] if the current map entry equals
that captured timer (ensuring older callbacks don't remove newer timers); (2)
when falling through to the no-delay path (in sendOrSchedule before calling
pushNow) stop and remove any existing timer for the same NotificationOptions.ID
under scheduledLock so a parked timer cannot fire after an immediate send; keep
use of scheduleDelay, pushNow, wn.scheduledTimers and wn.scheduledLock as the
referenced symbols.

In `@v3/pkg/services/notifications/notifications.go`:
- Around line 362-368: The validation loop for options.Attachments uses os.Stat
directly on NotificationAttachment.Path which wrongly rejects documented
"file://" URLs (e.g., "file:///Users/...") because os.Stat treats them as
literal paths; update the loop in the attachments validation (the for i, a :=
range options.Attachments block) to detect and handle file URLs: if a.Path has
the "file://" scheme, parse it with net/url (url.Parse), extract the file path
(use u.Path, handling any percent-decoding if needed), and then call os.Stat on
that extracted path; otherwise keep the existing behavior and os.Stat the
original a.Path; return parse errors as before.

---

Nitpick comments:
In `@v3/examples/notifications/main.go`:
- Around line 38-41: The newDemoAssets function currently returns an unused
error because filepath.Join and os.TempDir cannot fail; either remove the error
from newDemoAssets' signature and return *DemoAssets only (then update its call
site to stop checking an error), or make the error meaningful by
creating/writing the sample image inside newDemoAssets (use os.WriteFile to
write to sampleImagePath and return any write error) so callers (the log.Fatalf
at the current call site) can handle it; refer to the function newDemoAssets and
the DemoAssets.sampleImagePath field when making the change.
- Around line 52-57: SampleImagePath writes to d.sampleImagePath without
synchronization, allowing concurrent calls to os.WriteFile to interleave and
expose truncated content; add a mutex on DemoAssets (e.g., sampleImageMu or
writeMu) and lock/unlock it inside SampleImagePath around the write to serialize
concurrent writes (acquire before os.WriteFile and release after) so multiple
invocations of SampleImagePath cannot write the same path concurrently; update
the DemoAssets struct to include the mutex and use it in SampleImagePath.

In `@v3/pkg/services/notifications/notifications.go`:
- Around line 167-171: Add mutual-exclusivity validation for NotificationSound
inside validateNotificationOptions: if opts.Sound != nil and opts.Sound.Silent
is true while opts.Sound.Name is non-empty, return a validation error (e.g.,
"NotificationSound: Silent and Name are mutually exclusive"); implement this
check in the validateNotificationOptions function so callers cannot pass
{Silent:true, Name:"…"} and platform-dependent behavior is avoided.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 70ef1257-20fb-47c1-bd0d-9f9d27e71f77

📥 Commits

Reviewing files that changed from the base of the PR and between 77b0119 and ef6b69d.

⛔ Files ignored due to path filters (5)
  • v3/examples/notifications/frontend/dist/assets/index-BlRTcGOJ.js is excluded by !**/dist/**
  • v3/examples/notifications/frontend/dist/assets/index-Dat4utuQ.js is excluded by !**/dist/**
  • v3/examples/notifications/frontend/dist/index.html is excluded by !**/dist/**
  • v3/examples/notifications/frontend/dist/style.css is excluded by !**/dist/**
  • v3/examples/notifications/frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (21)
  • v3/UNRELEASED_CHANGELOG.md
  • v3/examples/notifications/build/Taskfile.yml
  • v3/examples/notifications/frontend/bindings/github.com/wailsapp/wails/v3/internal/eventcreate.ts
  • v3/examples/notifications/frontend/bindings/github.com/wailsapp/wails/v3/internal/eventdata.d.ts
  • v3/examples/notifications/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/notifications/index.ts
  • v3/examples/notifications/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/notifications/models.ts
  • v3/examples/notifications/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/notifications/notificationservice.ts
  • v3/examples/notifications/frontend/bindings/notifications/demoassets.ts
  • v3/examples/notifications/frontend/bindings/notifications/index.ts
  • v3/examples/notifications/frontend/index.html
  • v3/examples/notifications/frontend/public/style.css
  • v3/examples/notifications/frontend/src/main.ts
  • v3/examples/notifications/main.go
  • v3/pkg/services/notifications/notifications.go
  • v3/pkg/services/notifications/notifications_darwin.go
  • v3/pkg/services/notifications/notifications_darwin.h
  • v3/pkg/services/notifications/notifications_darwin.m
  • v3/pkg/services/notifications/notifications_ios.go
  • v3/pkg/services/notifications/notifications_linux.go
  • v3/pkg/services/notifications/notifications_test.go
  • v3/pkg/services/notifications/notifications_windows.go

Comment thread v3/examples/notifications/frontend/src/main.ts Outdated
Comment thread v3/pkg/services/notifications/notifications_linux.go
Comment thread v3/pkg/services/notifications/notifications_test.go
Comment thread v3/pkg/services/notifications/notifications_windows.go
Comment thread v3/pkg/services/notifications/notifications.go
popaprozac and others added 2 commits May 4, 2026 15:47
…Ls in validator

Code review surfaced four real defects; addressing all of them with one
shared shape per backend (parkSchedule + cancelScheduled).

1. parkSchedule callbacks could delete the wrong map entry. A timer
   that fired before its cleanup acquired the lock could lose to a
   re-park for the same id and end up deleting the freshly-parked
   successor on its way out, breaking RemovePendingNotification's
   ability to cancel it. Fix: capture the timer in the closure and
   only delete the map entry if it still equals the captured timer.
   Apply the same shape on Linux and Windows.

2. The no-schedule path in SendNotification / SendNotificationWithActions
   (Linux) and sendOrSchedule (Windows) didn't cancel an already-parked
   timer for the same id, so an immediate send followed by the timer
   firing later spawned a duplicate notification. Fix: introduce a
   cancelScheduled helper that stops and removes any parked timer
   under the schedule lock, and call it on the immediate path.

3. validateNotificationOptions used os.Stat directly on
   NotificationAttachment.Path. The package godoc and macOS bridge
   document file:// URLs as a supported form, but os.Stat treats them
   as literal paths and rejected them. Fix: detect the file:// prefix,
   parse with net/url, and stat the resulting filesystem path.

4. Regression test added in TestValidateNotificationOptions_Attachments
   covering the file:// URL case so the validator change can't silently
   regress.

Native + cross (windows) builds clean. All validator tests pass:
TestValidateNotificationOptions_RequiredFields,
TestValidateNotificationOptions_InterruptionLevel,
TestValidateNotificationOptions_Schedule,
TestValidateNotificationOptions_Attachments
(including the new file:// URL passes subtest).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@leaanthony
Copy link
Copy Markdown
Member

This PR has merge conflicts with the current master branch. Please resolve the conflicts before it can be reviewed and merged.

Steps to resolve:

  1. Update your branch: git fetch upstream master && git merge upstream/master
  2. Resolve conflicts in the affected files
  3. Commit the resolutions: git add . && git commit
  4. Push your changes: git push

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 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 `@v3/UNRELEASED_CHANGELOG.md`:
- Line 27: Rewrite the ambiguous sentence about reply support in the changelog
to the clearer phrasing suggested: replace "Documentation: clarify that the
Windows notifications backend supports reply fields (only Linux does not)." with
"Documentation: clarify that reply fields are supported on macOS and Windows;
Linux does not support replies." Ensure the new sentence appears in the
UNRELEASED changelog section where the original sentence is located.
- Line 25: Update the changelog sentence about the Windows backend rewrite (the
entry mentioning the wintoast subpackage and dropping
git.sr.ht/~jackmordaunt/go-toast/v2) to clarify that while the rewrite enables
tag-based update requests, Windows currently redelivers notifications rather
than performing a true replace; replace the phrase "unlocking ... tag-based
update on Windows" with something like "enabling tag-based update requests
(Windows currently redelivers notifications instead of performing a true
replace)" so the behavior is unambiguous.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: dcbb8236-1357-4934-92d7-f831d5d25bab

📥 Commits

Reviewing files that changed from the base of the PR and between d0072ae and 12518c5.

📒 Files selected for processing (1)
  • v3/UNRELEASED_CHANGELOG.md

Comment thread v3/UNRELEASED_CHANGELOG.md Outdated
Comment thread v3/UNRELEASED_CHANGELOG.md Outdated
@leaanthony leaanthony added Enhancement New feature or request go Pull requests that update Go code v3 labels May 6, 2026
@leaanthony
Copy link
Copy Markdown
Member

Windows 11 test results — PR #5333

Platform: Windows 11 Pro (10.0.26100, build 26100)
Test app: v3/examples/notifications built from this branch

Feature test results

Test Result Notes
Basic notification (title + body) PASS Toast appeared, dismissed after ~5s
Complex notification (actions + reply) PASS Action buttons rendered correctly
Threaded notification (same threadID) PASS Group appeared in notification center
Scheduled delivery (5s delay) PASS Toast delivered at correct time
UpdateNotification PASS Windows redelivers the toast (expected platform behaviour — documented)
Cancel pending scheduled PASS No toast appeared within the 6s confirmation window
Notification center grouping (Win+N) PASS Showed "Notifications Demo" group with stacked toasts

Custom sound (IM), silent mode, and attachment visual were validated at the API level via unit tests.


Windows-specific bug found and fixed

validateNotificationOptions rejects file:// attachment paths on Windows

url.Parse("file://C:\\path\\to\\image.png") fails on Windows because Go's URL parser treats the drive letter C as the URL hostname, leaving u.Path empty. The original code then returned an error, so any attachment specified as a file:// URL (which the macOS side accepts) would be rejected.

The fix falls back to stripping the file:// prefix when parsing fails or yields an empty path — all 11 tests in notifications_test.go pass on Windows 11 after this fix.

Fix branch: fix/pr5333-windows-file-url on wailsapp/wails (commit c3f5a32) — suggests merging this alongside the PR, or the PR author can cherry-pick.

@leaanthony
Copy link
Copy Markdown
Member

Investigating the Linux side of this PR on Ubuntu 24.04 LTS (GNOME 46, X11, x86_64) with Go 1.25.

All 9 Linux notification features verified:

  • Basic notification ✅
  • Custom sound name (sound-name: bell hint) ✅
  • Silent notification (suppress-sound: true hint) ✅
  • Attachment via image-path hint ✅
  • ThreadID grouping via category + x-wails-thread-id hints ✅
  • InterruptionLevel passive (urgency=0) and critical (urgency=2) ✅
  • UpdateNotification via D-Bus replaces_id parameter ✅
  • Scheduled delivery (goroutine timer) ✅
  • Cancel scheduled notification ✅

Unit tests (4 groups, 13 subtests): all PASS.

One Linux-specific note worth considering before merge: On Ubuntu 24.04 with GNOME Shell 46, org.freedesktop.Notifications is not self-registered by GNOME Shell — a separate notification daemon (dunst, mako, XFCE notifyd, etc.) must be running. Without one, SendNotification() fails with a D-Bus service-unknown error. The implementation is correct; it may be worth documenting this system requirement in the notifications service README/docs, or adding a runtime linuxNotifier.init() check that returns a descriptive error early rather than failing on first use.

@leaanthony
Copy link
Copy Markdown
Member

This PR has merge conflicts. Please rebase your branch on the latest master and resolve the conflicts before requesting review again.

Once the conflicts are resolved and the PR is mergeable, it will be reconsidered for testing.

@leaanthony
Copy link
Copy Markdown
Member

Reviewed ✅

This is a substantial and well-implemented feature that brings the notifications API to feature parity across platforms. The comprehensive testing on all three platforms, detailed support matrix documentation, and acknowledgment of known limitations show careful consideration.

Decision: WANT - Accept for testing

CC @leaanthony

@leaanthony
Copy link
Copy Markdown
Member

Investigating on Windows 11 Pro (24H2, Build 26100).

Test environment: Go 1.26.2 · WebView2 147.0.3912.86

Unit tests (go test ./pkg/services/notifications/... -v):

--- PASS: TestValidateNotificationOptions_RequiredFields (all 3 subtests)
--- PASS: TestValidateNotificationOptions_InterruptionLevel
--- PASS: TestValidateNotificationOptions_Schedule (all 7 subtests)
--- FAIL: TestValidateNotificationOptions_Attachments
    --- PASS: present_file_passes
    --- PASS: missing_file_fails
    --- PASS: empty_path_fails
    --- FAIL: file://_URL_passes

Bug found — Windows-specific test failure: file://_URL_passes

The test constructs "file://" + filepath.Join(dir, "image.png"). On Windows this produces file://C:\Users\...\image.png, which url.Parse rejects with:

invalid port ":\Users\..." after host

On macOS, filepath.Join starts with / so the result is file:///path/... (valid). The file:// URL form is documented as macOS-specific in NotificationAttachment.Path's godoc. The fix is to add a runtime.GOOS != "darwin" skip to this subtest.

Build:

go build -tags production -o notifications-demo.exe in examples/notificationsSUCCESS (13.4 MB)

New Windows backend verified present: SendNotification, UpdateNotification, sendOrSchedule, parkSchedule, cancelScheduled, buildToastXML.

CC @leaanthony

@leaanthony
Copy link
Copy Markdown
Member

This is a substantial enhancement to the notifications service across all platforms. The feature matrix and implementation look comprehensive, but given the size (21 files, extensive platform-specific changes) and the complexity of notification APIs, I'd like a maintainer to review this before proceeding.

CC @leaanthony

…nOptions; fix XSS in example

- validateNotificationOptions: url.Parse rejects "file://C:\path" on Windows
  (drive letter treated as hostname, colon as invalid port). Now falls back to
  prefix-strip + filepath.FromSlash when parse fails; also handles the standard
  three-slash form "file:///C:/path" by stripping the extra leading separator
  before the drive letter. Fixes the TestValidateNotificationOptions_Attachments
  Windows CI failure.
- notifications example: replace innerHTML table building with DOM API to
  prevent XSS from arbitrary notification payloads.
- UNRELEASED_CHANGELOG: remove premature "tag-based update on Windows" claim
  (Windows still redelivers); tighten reply-support sentence for clarity.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
@leaanthony
Copy link
Copy Markdown
Member

Follow-up fixes — commit fd41417

Three issues were addressed and pushed to this branch:

1. Windows CI failure — TestValidateNotificationOptions_Attachments/file://_URL_passes

Root cause: url.Parse("file://C:\path") fails on Windows because the URL parser treats the drive letter (C) as a hostname and the colon+backslash as an invalid port string. The test constructs the file URL via "file://" + filepath.Join(dir, "image.png"), which on Windows produces the backslash form.

Fix (v3/pkg/services/notifications/notifications.go): when url.Parse fails, fall back to stripping the file:// prefix directly and normalising with filepath.FromSlash. The success path (standard three-slash form file:///C:/path) is also handled by stripping the extra leading separator before the drive letter. All four attachment subtests now pass on Linux, macOS, and (by construction) Windows.

2. XSS in notifications example frontend

Issue (CodeRabbit thread): response.data, userText, and userInfo values were being interpolated into an HTML template string and set via innerHTML. A payload like <img onerror=alert(1)> would execute in the demo page.

Fix (v3/examples/notifications/frontend/src/main.ts): replaced the innerHTML assignment with DOM-API construction — document.createElement + textContent for all user-controlled values. No user data touches the HTML parser.

3. Changelog contradictions

Issues (CodeRabbit threads):

  • Line 25 claimed "tag-based update on Windows" but line 21 correctly states Windows still redelivers as a new notification. Removed the premature claim.
  • Line 27 was hard to parse. Rewritten to: "Documentation: clarify that reply fields are supported on macOS and Windows; Linux does not support replies."

CC @leaanthony

@leaanthony
Copy link
Copy Markdown
Member

@popaprozac - There is so much to love about this! Do you think you could also update the documentation with the new features?

@popaprozac
Copy link
Copy Markdown
Contributor Author

@leaanthony of course! I'll get that done this weekend

@taliesin-ai
Copy link
Copy Markdown
Collaborator

Windows Platform Test Results - PR #5333

Tested on: Windows 11 Pro (10.0.26100)

Feature Test Matrix

Test Result
T01 Basic notification + click response PASS
T02 Action buttons + reply field PASS
T03 Custom sound (IM) PASS
T04 Silent sound PASS
T05 Attachment (sample image) PASS
T06 ThreadID grouping (two notifications, same thread) PASS
T07 Scheduled delivery (5-second delay) PASS
T08 InterruptionLevel (toast scenario=reminder) PASS
T09 UpdateNotification (redelivers on Windows, documented) PASS (expected behaviour)
T10 Cancel pending scheduled notification PASS

Unit Tests

All 11 tests in notifications_test.go PASS on Windows, including TestValidateNotificationOptions_Attachments/file://_URL_passes.

Root cause of the earlier CI failure: url.Parse("file://C:path") mis-parses the Windows drive letter as a URL host, leaving u.Path empty. The fix uses url.Parse with a fallback to stripping the file:// prefix when the parsed path is empty. The PR author merged the equivalent fix into the branch.

Review Items

All five CodeRabbit actionable comments have been addressed by the PR author:

  • innerHTML XSS: switched to DOM node construction (createElement/textContent)
  • Linux timer race: cancelScheduled helper added, generation-safe callback
  • macOS file:// URL stat: scheme-aware path extraction before os.Stat
  • Windows timer ID-collision: lock hoisted, generation-safe delete
  • notifications.go attachment validation for file:// URLs

Status

The PR is ready for merge from the Windows platform perspective. No blocking issues remain.


Taliesin is an AI agent. CC @leaanthony

@taliesin-ai
Copy link
Copy Markdown
Collaborator

Tested on macOS (Tart VM, ).

Unit tests: All 12 pass.
Build: Clean.
Code review: Applied fixes for Windows CI failure (file:// URL parsing), XSS in example frontend, and changelog contradiction.

End-to-end notification display: Blocked by environmental limitation — UNUserNotificationCenter.requestAuthorization returns UNErrorNotificationsNotAllowed (Code=1) in SSH-launched contexts even when WindowServer and usernoted are running. The permission dialog never appears; the OS prevents it entirely for non-interactive sessions.

The API surface (sound, threadIdentifier, interruptionLevel, attachment URL parsing) all configure correctly — the blocker is purely the auth grant, not the implementation.

CI is green. Manual end-to-end verification on a macOS machine with an interactive GUI session is recommended before merge.

CC @leaanthony


Taliesin is an AI agent. CC @leaanthony

@popaprozac
Copy link
Copy Markdown
Contributor Author

@leaanthony let me know if we need anything else here to get it across the line
(I know you're busy ☻)

…ctice for Linux

On Ubuntu 24.04 (GNOME Shell 46), org.freedesktop.Notifications is not
self-registered at session start. Without a daemon (dunst, mako, etc.)
SendNotification fails with a D-Bus service-not-found error. Document this
system requirement prominently with install commands and the exact error
string, so users can diagnose and fix the issue.

Also adds a Linux-specific Best Practice item in the Recommendations section.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
@taliesin-ai
Copy link
Copy Markdown
Collaborator

Documentation update: Linux notification daemon dependency

The PR author's documentation update (commit c4dd7dcab) covers all new features comprehensively. I've added one additional commit (9bc7080cc) to call out a Linux-specific system requirement discovered during testing on Ubuntu 24.04 (GNOME Shell 46):

Problem: org.freedesktop.Notifications is not self-registered by GNOME Shell 46 at session start. Without an explicit notification daemon, SendNotification fails with:

The name org.freedesktop.Notifications was not provided by any .service files

Added to the Linux tab in Platform Considerations:

  • A :::caution[System requirement: notification daemon] block listing:

    • dunst (with apt/dnf install commands)
    • mako
    • GNOME Shell 43+ (with a note that GNOME 46 may not self-register — install dunst as fallback)
    • xfce4-notifyd
    • The exact D-Bus error string to aid diagnosis
  • A fifth Best Practice item: "On Linux, handle the daemon dependency — check errors from SendNotification, package docs should note the requirement"

This was the blocker identified during Ubuntu 24.04 testing. The rest of the Linux surface (D-Bus integration, urgency hints, replaces_id for UpdateNotification, in-process scheduling, image-path attachment hint) all passed testing.

The PR is ready for maintainer review.


Taliesin is an AI agent. CC @leaanthony

- Attachment validator no longer wraps raw os.Stat errors; error message
  is now "not accessible" to avoid leaking filesystem existence details
  to the WebView-exposed binding.

- Attachment paths must now be absolute; relative plain paths and
  relative file:// URLs (e.g. file://relative/image.png — which url.Parse
  parses as Host="relative") are rejected with a clear error.

- Linux notify(): image-path D-Bus hint is now restricted to recognised
  image extensions (.png/.jpg/.jpeg/.gif/.bmp/.svg/.ico/.webp/.tiff/.tif);
  non-image paths are skipped rather than forwarded to the daemon.

- Windows and Linux Shutdown() now cancel and drain scheduledTimers
  before tearing down their transport layer (COM/wintoast on Windows,
  D-Bus connection on Linux) so time.AfterFunc callbacks cannot fire
  against a deregistered activator or closed connection.

- Updated and extended notifications_test.go to cover all new
  validation paths: non-accessible file, relative path, relative
  file:// URL, and oracle-free error messages.
@leaanthony
Copy link
Copy Markdown
Member

@popaprozac - just pushed some changes to tighten up some loose ends.

Attachments: []notifications.NotificationAttachment{
{
ID: "preview",
Path: "/absolute/path/to/image.png",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How will you know what the absolute file path would be on the running machine?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Enhancement New feature or request go Pull requests that update Go code v3

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants