diff --git a/.all-contributorsrc b/.all-contributorsrc index ced3b155b..a2d13c2fc 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -1,6 +1,7 @@ { "files": [ - "website/src/pages/credits.mdx" + "website/src/pages/credits.mdx", + "docs/src/assets/contributors.html" ], "imageSize": 75, "commit": false, diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl new file mode 100644 index 000000000..67f7a6830 --- /dev/null +++ b/.beads/issues.jsonl @@ -0,0 +1,55 @@ +{"id":"wails-webview2gtk6-0mc","title":"GTK4: Drag and drop not working - needs manual testing","description":"Native file drag-and-drop from file managers works in GTK3 but NOT in GTK4.\n\n**Symptoms:**\n- Dragging files onto GTK4 app causes WebKit to open file like a browser\n- GtkDropTarget signals (enter, leave, motion, drop) never fire\n- Debug logging shows no [DND-GTK4] messages during drag operations\n\n**Investigation Done:**\n- Added GtkDropControllerMotion with GTK_PHASE_CAPTURE for motion tracking\n- Added GtkDropTarget with GTK_PHASE_CAPTURE and accept handler for file drops\n- Both controllers added to WebKitWebView\n- WebKitGTK uses GtkDropTargetAsync internally in bubble phase\n\n**Key Finding:**\n- WebKitGTK 2.50.3 disabled file access (CVE-2025-13947) but this should not affect native GTK drops\n- Capture phase should run before WebKit's bubble phase handlers\n\n**Files Modified:**\n- v3/pkg/application/linux_cgo_gtk4.c - DND handlers with debug logging\n- v3/pkg/application/linux_cgo_gtk4.go - enableDND/disableDND\n\n**Testing Required:**\n1. Build: cd v3/examples/drag-n-drop \u0026\u0026 go build -a -o drag-n-drop-gtk4 .\n2. Run: ./drag-n-drop-gtk4\n3. Drag file from file manager to window\n4. Watch for [DND-GTK4] messages in console\n\n**If DropControllerMotion fires but GtkDropTarget doesn't:**\n- WebKit intercepts at lower level, may need GtkDropTargetAsync approach\n\n**If nothing fires:**\n- Issue with controller attachment to WebKitWebView","status":"closed","priority":1,"issue_type":"bug","created_at":"2026-01-08T15:26:58.12691099+11:00","updated_at":"2026-01-08T15:50:34.104083842+11:00","closed_at":"2026-01-08T15:50:34.104083842+11:00","close_reason":"Fixed - implemented GtkDropControllerMotion + GtkDropTarget with GTK_PHASE_CAPTURE"} +{"id":"wails-webview2gtk6-588","title":"doctor-ng: New TUI doctor package","description":"","status":"closed","priority":1,"issue_type":"feature","created_at":"2026-01-06T15:53:57.74361022+11:00","updated_at":"2026-01-06T15:54:05.035250923+11:00","closed_at":"2026-01-06T15:54:05.035250923+11:00","close_reason":"Implemented: pkg/doctor-ng with modern bubbletea TUI, public API for GUI reuse, CLI command wails3 doctor-ng"} +{"id":"wails-webview2gtk6-95s","title":"Window not found warning on application quit","description":"When quitting a systray application, a warning is logged: 'Window #1 not found'. This happens during cleanup when trying to access windows that have already been destroyed.\n\nReproduction:\n1. Run any systray test (e.g., custom-handlers-gtk4)\n2. Use the menu to quit\n3. Observe warning in console\n\nExpected: Clean shutdown without warnings\nActual: WRN Window #1 not found","status":"open","priority":2,"issue_type":"bug","created_at":"2026-01-06T14:35:05.897126324+11:00","updated_at":"2026-01-06T14:35:05.897126324+11:00"} +{"id":"wails-webview2gtk6-e8m","title":"Cursor warps to window center on second systray click (Hyprland)","description":"On Hyprland, when using window-menu systray configuration:\n\n1. Click systray icon -\u003e window shows, cursor stays in place\n2. Click systray icon again -\u003e window hides then shows, cursor warps to window center\n\nThis only happens when toggling via systray icon click. Using menu 'Show Window' doesn't cause the warp.\n\nLikely related to PositionWindow() interaction with Hyprland's focus handling.\n\nEnvironment: Hyprland, GTK4\nPriority: Low (cosmetic issue)","status":"open","priority":3,"issue_type":"bug","created_at":"2026-01-06T14:49:45.489579343+11:00","updated_at":"2026-01-06T14:49:45.489579343+11:00"} +{"id":"wails-webview2gtk6-m4c","title":"Systray API v2: Refactor for cleaner separation of concerns","description":"Redesign systray API for cleaner separation of concerns:\n\n## Core Principles\n1. Registration separate from behavior - AttachWindow/SetMenu only register resources\n2. Smart defaults with explicit overrides - Works out of the box, customizable when needed\n3. Window behavior belongs on window - HideOnFocusLost, HideOnEscape are window options\n4. Platform differences handled internally - User expresses intent, implementation adapts\n\n## Smart Defaults\n| Configuration | Left-Click | Right-Click |\n|--------------|------------|-------------|\n| Window only | ToggleWindow | Nothing |\n| Menu only | Nothing | ShowMenu |\n| Window + Menu | ToggleWindow | ShowMenu |\n\n## Scope\n- Window options: HideOnFocusLost, HideOnEscape\n- Systray smart defaults\n- GTK3 \u0026 GTK4 compatibility\n- Documentation, tests, examples","status":"closed","priority":1,"issue_type":"epic","created_at":"2026-01-06T12:41:49.962413423+11:00","updated_at":"2026-01-06T15:33:03.609804696+11:00","closed_at":"2026-01-06T15:33:03.609804696+11:00","close_reason":"All implementation tasks complete. Systray API v2 implemented with smart defaults, HideOnEscape, HideOnFocusLost, and comprehensive manual tests. GTK3/GTK4 verified."} +{"id":"wails-webview2gtk6-m4c.1","title":"Add HideOnFocusLost window option","description":"Add HideOnFocusLost option to WebviewWindowOptions.\n\nFiles to modify:\n- webview_window_options.go: Add HideOnFocusLost bool field\n- webview_window.go: Implement focus-lost handler in setupBehaviorOptions()\n- linux_cgo.go: GTK3 focus-lost signal (focus-out-event)\n- linux_cgo_gtk4.go: GTK4 focus-lost signal (notify::is-active)\n- webview_window_darwin.go: macOS focus-lost handling\n- webview_window_windows.go: Windows focus-lost handling\n\nPlatform behavior:\n- Standard WMs: Hide on focus lost\n- Focus-follows-mouse WMs (Hyprland, Sway, i3): Silently disabled\n\nImplementation:\nif options.HideOnFocusLost {\n if runtime.GOOS == \"linux\" \u0026\u0026 isFocusFollowsMouse() {\n return // Skip - would cause immediate hide\n }\n w.OnWindowEvent(events.Common.WindowLostFocus, func(e *WindowEvent) {\n w.Hide()\n })\n}","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-06T12:42:15.123041365+11:00","updated_at":"2026-01-06T13:02:45.845914238+11:00","closed_at":"2026-01-06T13:02:45.845914238+11:00","close_reason":"Implemented HideOnFocusLost option","dependencies":[{"issue_id":"wails-webview2gtk6-m4c.1","depends_on_id":"wails-webview2gtk6-m4c","type":"parent-child","created_at":"2026-01-06T12:42:15.128286221+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-m4c.2","title":"Add HideOnEscape window option","description":"Add HideOnEscape option to WebviewWindowOptions.\n\nFiles to modify:\n- webview_window_options.go: Add HideOnEscape bool field\n- webview_window.go: Register Escape keybinding in setupBehaviorOptions()\n\nImplementation:\nif options.HideOnEscape {\n w.registerKeyBinding(\"escape\", func() {\n w.Hide()\n })\n}\n\nNotes:\n- Should work on all platforms (GTK3, GTK4, macOS, Windows)\n- Uses existing keybinding infrastructure","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-06T12:42:23.973789683+11:00","updated_at":"2026-01-06T13:02:46.561848535+11:00","closed_at":"2026-01-06T13:02:46.561848535+11:00","close_reason":"Implemented HideOnEscape option","dependencies":[{"issue_id":"wails-webview2gtk6-m4c.2","depends_on_id":"wails-webview2gtk6-m4c","type":"parent-child","created_at":"2026-01-06T12:42:23.978484152+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-m4c.3","title":"Implement systray smart defaults","description":"Implement smart defaults for systray click behavior.\n\nSmart Defaults:\n| Configuration | Left-Click | Right-Click |\n|--------------|------------|-------------|\n| Window only | ToggleWindow | Nothing |\n| Menu only | Nothing | ShowMenu |\n| Window + Menu | ToggleWindow | ShowMenu |\n| Neither | Nothing | Nothing |\n\nFiles to modify:\n- systemtray.go: Add applySmartDefaults() method, call from Run()\n\nImplementation:\nfunc (s *SystemTray) applySmartDefaults() {\n hasWindow := s.attachedWindow.Window != nil\n hasMenu := s.menu != nil\n \n if s.clickHandler == nil {\n if hasWindow {\n s.clickHandler = s.ToggleWindow\n }\n }\n \n if s.rightClickHandler == nil {\n if hasMenu {\n s.rightClickHandler = s.ShowMenu\n }\n }\n}\n\nUser-specified OnLeftClick/OnRightClick handlers override these defaults.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-06T12:42:34.796175422+11:00","updated_at":"2026-01-06T13:02:47.607457075+11:00","closed_at":"2026-01-06T13:02:47.607457075+11:00","close_reason":"Implemented smart defaults in applySmartDefaults()","dependencies":[{"issue_id":"wails-webview2gtk6-m4c.3","depends_on_id":"wails-webview2gtk6-m4c","type":"parent-child","created_at":"2026-01-06T12:42:34.801495751+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-m4c.4","title":"Fix Linux systray Activate/SecondaryActivate handlers","description":"Fix Linux systray DBus handlers to call correct click handlers.\n\nCurrent issues:\n- Activate calls doubleClickHandler instead of clickHandler\n- ItemIsMenu is always true, causing host to intercept left-click\n- Menu property registered even when no menu\n\nFiles to modify:\n- systemtray_linux.go\n\nChanges:\n1. Activate() should call s.parent.clickHandler (not doubleClickHandler)\n2. SecondaryActivate() should call s.parent.rightClickHandler\n3. ItemIsMenu = false when window attached (let our code handle clicks)\n4. Only register Menu property when s.menu != nil\n\nImplementation already partially done in this session - verify and clean up.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-06T12:42:47.726574187+11:00","updated_at":"2026-01-06T13:02:48.48112849+11:00","closed_at":"2026-01-06T13:02:48.48112849+11:00","close_reason":"Fixed Activate/SecondaryActivate handlers","dependencies":[{"issue_id":"wails-webview2gtk6-m4c.4","depends_on_id":"wails-webview2gtk6-m4c","type":"parent-child","created_at":"2026-01-06T12:42:47.731830675+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-m4c.5","title":"Add systray API v2 unit tests","description":"Add unit tests for new systray behavior.\n\nTest cases:\n- TestSystraySmartDefaults_WindowOnly: Left=toggle, Right=nothing\n- TestSystraySmartDefaults_MenuOnly: Left=nothing, Right=menu\n- TestSystraySmartDefaults_WindowAndMenu: Left=toggle, Right=menu\n- TestSystraySmartDefaults_Neither: Left=nothing, Right=nothing\n- TestSystrayOverride_LeftClick: Custom handler overrides default\n- TestSystrayOverride_RightClick: Custom handler overrides default\n- TestWindowOption_HideOnFocusLost: Window hides on focus lost\n- TestWindowOption_HideOnEscape: Window hides on Escape\n- TestWindowOption_FocusFollowsMouse: HideOnFocusLost disabled on tiling WMs\n\nFiles:\n- systemtray_test.go\n- webview_window_test.go","status":"open","priority":2,"issue_type":"task","created_at":"2026-01-06T12:42:58.128530198+11:00","updated_at":"2026-01-06T12:42:58.128530198+11:00","dependencies":[{"issue_id":"wails-webview2gtk6-m4c.5","depends_on_id":"wails-webview2gtk6-m4c","type":"parent-child","created_at":"2026-01-06T12:42:58.133914168+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-m4c.6","title":"Update systray documentation","description":"Update documentation for new systray API.\n\nFiles to update:\n- mkdocs-website/docs/learn/systray.md: Full rewrite\n- mkdocs-website/docs/learn/windows.md: Document HideOnFocusLost, HideOnEscape\n- mkdocs-website/docs/api/systray.md: API reference\n\nDocumentation structure:\n1. Basic Usage\n - Window-based systray (popup)\n - Menu-based systray\n - Window + Menu\n2. Smart Defaults explanation\n3. Customizing Behavior\n - OnLeftClick/OnRightClick overrides\n4. Window auto-hide options\n - HideOnFocusLost\n - HideOnEscape\n - Platform considerations (focus-follows-mouse)\n5. Platform Notes (Linux, macOS, Windows)","status":"open","priority":2,"issue_type":"task","created_at":"2026-01-06T12:43:08.693794811+11:00","updated_at":"2026-01-06T12:43:08.693794811+11:00","dependencies":[{"issue_id":"wails-webview2gtk6-m4c.6","depends_on_id":"wails-webview2gtk6-m4c","type":"parent-child","created_at":"2026-01-06T12:43:08.699255848+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-m4c.7","title":"Update systray examples","description":"Update and create systray examples.\n\nUpdate:\n- examples/systray-basic/main.go: Show HideOnFocusLost, HideOnEscape options\n\nCreate:\n- examples/systray-menu/main.go: Menu-only example\n- examples/systray-window-menu/main.go: Window + Menu example\n\nEach example should demonstrate:\n- Minimal setup\n- Smart defaults in action\n- How to customize if needed\n\nExample for systray-basic:\nwindow := app.NewWindow(WebviewWindowOptions{\n Hidden: true,\n Frameless: true,\n AlwaysOnTop: true,\n HideOnFocusLost: true, // NEW\n HideOnEscape: true, // NEW\n})\nsystray.AttachWindow(window)","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T12:43:19.315995474+11:00","updated_at":"2026-01-06T13:02:49.08857561+11:00","closed_at":"2026-01-06T13:02:49.08857561+11:00","close_reason":"Updated systray-basic example","dependencies":[{"issue_id":"wails-webview2gtk6-m4c.7","depends_on_id":"wails-webview2gtk6-m4c","type":"parent-child","created_at":"2026-01-06T12:43:19.323424534+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-m4c.8","title":"Verify GTK3 and GTK4 compatibility","description":"Verify all systray changes work on both GTK3 and GTK4.\n\nTest matrix:\n| Feature | GTK3 | GTK4 |\n|---------|------|------|\n| Left-click toggle window | Test | Test |\n| Right-click show menu | Test | Test |\n| HideOnFocusLost | Test | Test |\n| HideOnEscape | Test | Test |\n| ItemIsMenu property | Test | Test |\n| Menu property conditional | Test | Test |\n\nBuild commands:\n- GTK4: go build ./pkg/application/...\n- GTK3: go build -tags gtk3 ./pkg/application/...\n\nTest on:\n- Hyprland (tiling, focus-follows-mouse)\n- GNOME (standard WM)\n\nDocument any GTK3/GTK4 differences in behavior.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-06T12:43:30.274572191+11:00","updated_at":"2026-01-06T15:32:18.160668676+11:00","closed_at":"2026-01-06T15:32:18.160668676+11:00","close_reason":"Verified GTK3/GTK4 compatibility - all tests pass","dependencies":[{"issue_id":"wails-webview2gtk6-m4c.8","depends_on_id":"wails-webview2gtk6-m4c","type":"parent-child","created_at":"2026-01-06T12:43:30.279370657+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-m4c.9","title":"Clean up current session systray changes","description":"Clean up partial implementation from current session.\n\nChanges made in this session that need review/cleanup:\n1. systemtray_linux.go:\n - Activate() now calls clickHandler (correct)\n - SecondaryActivate() calls rightClickHandler or OpenMenu (correct)\n - ItemIsMenu conditional on window/menu presence (correct)\n - Menu property only registered when menu exists (correct)\n - Removed 'Open window' menu item injection (correct)\n\n2. environment_linux.go:\n - Added isTilingWM() helper (keep)\n - Removed unused Hyprland IPC functions (keep)\n - Cleaned up debug output (keep)\n\n3. linux_cgo.go / linux_cgo_gtk4.go:\n - Added setOpacity() - keep for future use\n\n4. webview_window_linux.go:\n - Removed unused compositorWindowID field (keep)\n\n5. examples/systray-basic/main.go:\n - Added Escape keybinding manually (will be replaced by HideOnEscape option)\n\nReview all changes, ensure they align with spec, commit clean state.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-06T12:43:44.358797107+11:00","updated_at":"2026-01-06T13:02:50.19506054+11:00","closed_at":"2026-01-06T13:02:50.19506054+11:00","close_reason":"Cleanup complete","dependencies":[{"issue_id":"wails-webview2gtk6-m4c.9","depends_on_id":"wails-webview2gtk6-m4c","type":"parent-child","created_at":"2026-01-06T12:43:44.36368827+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e","title":"WebKitGTK 6.0 / GTK4 Support for Wails v3","description":"Add WebKitGTK 6.0 (GTK4) support to Wails v3 as the DEFAULT target. GTK3/WebKit4.1 available via -tags gtk3 for legacy systems.\n\nArchitecture:\n- Default (no tag): GTK4 + WebKitGTK 6.0\n- Legacy (-tags gtk3): GTK3 + WebKit2GTK 4.1\n\nDocker container provides BOTH library sets for cross-compilation:\n- task build:linux → GTK4/WebKit6 (modern, default)\n- task build:linux:gtk3 → GTK3/WebKit4.1 (legacy backport)\n\nSame pattern as macOS/Windows cross-compilation from Linux.","status":"open","priority":1,"issue_type":"epic","created_at":"2026-01-04T12:06:52.983769501+11:00","updated_at":"2026-01-04T12:52:12.372486756+11:00"} +{"id":"wails-webview2gtk6-t4e.1","title":"Phase 1: Add gtk3 build constraint to existing GTK3/WebKit4.1 files","description":"Rename/constrain existing GTK3 files to require -tags gtk3:\n- linux_cgo.go → add //go:build linux \u0026\u0026 gtk3\n- clipboard_linux.go → add //go:build linux \u0026\u0026 gtk3\n- menu_linux.go → add //go:build linux \u0026\u0026 gtk3\n- menuitem_linux.go → add //go:build linux \u0026\u0026 gtk3\n- screen_linux.go → add //go:build linux \u0026\u0026 gtk3\n- dialogs_linux.go → add //go:build linux \u0026\u0026 gtk3\n- webkit2.go → add //go:build linux \u0026\u0026 gtk3\n\nThese become the LEGACY path, only built with -tags gtk3","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-04T12:07:40.301322984+11:00","updated_at":"2026-01-06T15:34:36.338594373+11:00","closed_at":"2026-01-06T15:34:36.338594373+11:00","close_reason":"GTK3 files have gtk3 build tag","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.1","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:07:40.306919353+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e.10","title":"Phase 4: Implement webkit6 asset server","description":"webkit6.go, request_linux_webkit6.go, responsewriter_linux_webkit6.go - URI scheme handling with WebKitGTK 6.0 API (webkit_uri_scheme_response_new).","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-04T12:08:08.383542838+11:00","updated_at":"2026-01-06T15:34:41.942498525+11:00","closed_at":"2026-01-06T15:34:41.942498525+11:00","close_reason":"WebKit6 asset server implemented - URI scheme handling works","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.10","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:08:08.389188642+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e.11","title":"Phase 5: Implement GMenu/GAction menu system","description":"Complete rewrite from GtkMenu/GtkMenuItem to GMenu/GAction/GtkPopoverMenuBar. Most significant GTK4 change. Reference v2 PR #4570 menu_webkit6.go but fix race conditions.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-04T12:08:40.990519767+11:00","updated_at":"2026-01-06T20:27:18.582723928+11:00","closed_at":"2026-01-06T20:27:18.582723928+11:00","close_reason":"Completed - GMenu/GAction implemented in menu_linux_gtk4.go and linux_cgo_gtk4.c","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.11","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:08:40.995669687+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e.12","title":"Phase 5: Implement menu item state handling","description":"GSimpleAction for text/checkbox/radio items. Stateful actions with g_simple_action_new_stateful. Proper checked state sync. Fix nil checks from v2 PR issues.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-04T12:08:45.52200628+11:00","updated_at":"2026-01-06T20:27:20.311243355+11:00","closed_at":"2026-01-06T20:27:20.311243355+11:00","close_reason":"Completed - GSimpleAction with stateful actions for checkbox/radio implemented","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.12","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:08:45.526654215+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e.13","title":"Phase 5: Implement GTK4 keyboard accelerators","description":"gtk_application_set_accels_for_action for menu shortcuts. Map existing accelerator format to GTK4 format.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T12:08:49.061335186+11:00","updated_at":"2026-01-06T20:27:20.562596782+11:00","closed_at":"2026-01-06T20:27:20.562596782+11:00","close_reason":"Completed - gtk_application_set_accels_for_action() implemented in Phase 9","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.13","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:08:49.066422576+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e.14","title":"Phase 6: Implement GTK4 clipboard API","description":"Replace gtk_clipboard_get with gdk_display_get_clipboard/gdk_display_get_primary_clipboard. Use GdkContentProvider for setting text.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T12:08:53.275247028+11:00","updated_at":"2026-01-06T20:27:22.183597579+11:00","closed_at":"2026-01-06T20:27:22.183597579+11:00","close_reason":"Completed - GdkClipboard implemented in linux_cgo_gtk4.go","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.14","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:08:53.279707286+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e.15","title":"Phase 6: Implement GTK4 dialogs","description":"File chooser and message dialogs. Use deprecated-but-functional APIs (gtk_file_chooser_dialog_new deprecated 4.10 but works). Consider GtkFileDialog for future.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T12:08:59.838538142+11:00","updated_at":"2026-01-06T20:27:22.294219031+11:00","closed_at":"2026-01-06T20:27:22.294219031+11:00","close_reason":"Completed - GtkFileDialog and GtkAlertDialog implemented in Phase 8","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.15","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:08:59.843372832+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e.16","title":"Phase 6: Implement GTK4 screen/monitor handling","description":"GdkMonitor/GdkDisplay GTK4 changes. Note: gdk_monitor_is_primary removed - handle gracefully or use alternative detection.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T12:09:08.402102924+11:00","updated_at":"2026-01-06T20:27:35.337382837+11:00","closed_at":"2026-01-06T20:27:35.337382837+11:00","close_reason":"N/A - GTK4 uses GdkDisplay/GdkMonitor which are mostly backwards compatible; no special handling needed","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.16","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:09:08.406691355+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e.17","title":"Phase 6: Implement GTK4 event controllers","description":"Replace GTK3 signals with GTK4 event controllers: GtkGestureClick for button-press, GtkEventControllerKey for key events, GtkEventControllerMotion for motion.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T12:09:11.122143159+11:00","updated_at":"2026-01-06T20:27:23.251573875+11:00","closed_at":"2026-01-06T20:27:23.251573875+11:00","close_reason":"Completed - GtkEventControllerFocus/GtkGestureClick/GtkEventControllerKey implemented in Phase 3","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.17","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:09:11.127168452+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e.18","title":"Phase 7: Create webkit6 test matrix","description":"Test on Ubuntu 24.04, Fedora 40+, Arch Linux. Both X11 and Wayland sessions. Verify window lifecycle, menus, dialogs, clipboard, asset serving.","status":"open","priority":1,"issue_type":"task","created_at":"2026-01-04T12:09:14.440029393+11:00","updated_at":"2026-01-04T12:09:14.440029393+11:00","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.18","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:09:14.444560094+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e.19","title":"Phase 7: Update UNRELEASED_CHANGELOG.md","description":"Document: webkit_6 build flag, auto-detection via capabilities command, Taskfile integration, known limitations (window positioning on Wayland).","status":"open","priority":1,"issue_type":"task","created_at":"2026-01-04T12:09:17.821748561+11:00","updated_at":"2026-01-04T12:09:17.821748561+11:00","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.19","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:09:17.826107005+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e.2","title":"Phase 1: Create GTK4/WebKit6 files as DEFAULT (no build tag)","description":"Create new GTK4/WebKit6 implementation files as the default (no tag required):\n- linux_cgo.go → //go:build linux \u0026\u0026 !gtk3 with pkg-config: gtk4 webkitgtk-6.0\n- clipboard_linux.go → //go:build linux \u0026\u0026 !gtk3\n- menu_linux.go → //go:build linux \u0026\u0026 !gtk3 (GMenu/GAction)\n- menuitem_linux.go → //go:build linux \u0026\u0026 !gtk3\n- screen_linux.go → //go:build linux \u0026\u0026 !gtk3\n- dialogs_linux.go → //go:build linux \u0026\u0026 !gtk3\n- webkit6.go → //go:build linux \u0026\u0026 !gtk3\n\nGTK4 is now the DEFAULT path, built without any tags","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-04T12:07:41.961703449+11:00","updated_at":"2026-01-06T15:34:38.035912813+11:00","closed_at":"2026-01-06T15:34:38.035912813+11:00","close_reason":"GTK4 files exist as default (linux_cgo_gtk4.go, menu_linux_gtk4.go, etc.)","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.2","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:07:41.966174608+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e.20","title":"Phase 7: Write GTK4/WebKit6 documentation","description":"Migration guide covering: distro requirements, build flags, window positioning limitations, menu system changes, API differences from GTK3.","status":"open","priority":2,"issue_type":"task","created_at":"2026-01-04T12:09:20.269607376+11:00","updated_at":"2026-01-04T12:09:20.269607376+11:00","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.20","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:09:20.274078494+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e.21","title":"Phase 8: V2 PR #4570 Fixes Tracker","description":"Track issues to backport to v2 PR after v3 port complete. Current known issues:\n- Race conditions: menu maps accessed without sync (menu_webkit6.go)\n- Nil checks missing: gActionIdToMenuItem.Load ignores ok bool (gtk_webkit6.go:18,22,34,45)\n- Memory leak: C.CString(title) not freed (window_webkit6.go:384)\n- Header mismatch: sendShowInspectorMessage signature wrong (window_webkit6.h:127)\n- Wrong cast: GtkWindow passed as GdkToplevel (window_webkit6.c:672,699)\n- Broken logic: IsMinimised returns wrong value (window_webkit6.c:383-387)\n- Incomplete: drag-drop just prints paths (window_webkit6.c:574-585)\n- Missing: separator handling in menus\nUPDATE THIS AS MORE ISSUES FOUND DURING V3 PORT","status":"open","priority":3,"issue_type":"task","created_at":"2026-01-04T12:09:35.71638895+11:00","updated_at":"2026-01-04T12:09:35.71638895+11:00","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.21","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:09:35.720786909+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e.22","title":"Test Strategy: Build tag verification","description":"Verify build constraints work correctly:\n\nDefault build (no tags):\n- Uses GTK4/WebKitGTK 6.0 files\n- Compiles cleanly with gtk4 and webkitgtk-6.0 pkg-config\n\nLegacy build (-tags gtk3):\n- Uses GTK3/WebKit2GTK 4.1 files\n- Compiles cleanly with gtk+-3.0 and webkit2gtk-4.1 pkg-config\n\nVerify:\n- No file conflicts or duplicate symbols in either config\n- Both builds produce working binaries\n- CI matrix tests both configurations\n- Docker cross-compilation works for both targets","status":"open","priority":1,"issue_type":"task","created_at":"2026-01-04T12:40:55.065828451+11:00","updated_at":"2026-01-04T12:53:15.945916832+11:00","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.22","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:40:55.071357519+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e.23","title":"Test Strategy: GTK3 legacy build regression tests","description":"Ensure GTK3 legacy path (-tags gtk3) still works:\n- All existing Linux tests pass WITH -tags gtk3\n- Window lifecycle (create, show, hide, destroy)\n- Menu system (items, checkboxes, radio, separators, accelerators)\n- Dialogs (file open/save, message dialogs)\n- Clipboard operations\n- Asset serving via wails:// scheme\n- JavaScript execution and callbacks\n\nRun via Docker on systems without GTK3 dev libs locally.\nThis is the LEGACY path for older distros.","status":"open","priority":1,"issue_type":"task","created_at":"2026-01-04T12:40:57.736554584+11:00","updated_at":"2026-01-04T12:53:21.402683536+11:00","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.23","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:40:57.74152021+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e.24","title":"Test Strategy: GTK4 default build tests","description":"Verify GTK4 (default, no tags) works correctly:\n- Window lifecycle identical to GTK3 behavior\n- Menu system: GMenu/GAction produces same UX as GtkMenu\n- Dialogs: same file filters, default paths work\n- Clipboard: copy/paste text works identically \n- Asset serving: wails:// scheme works\n- JS execution: evaluate_javascript produces same results\n\nDocument intentional differences:\n- Window positioning is no-op on Wayland (expected)\n- Minor visual differences due to GTK4 theming","status":"open","priority":1,"issue_type":"task","created_at":"2026-01-04T12:41:01.206249936+11:00","updated_at":"2026-01-04T12:53:26.644766099+11:00","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.24","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:41:01.21075179+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e.25","title":"Test Strategy: Cross-distro matrix","description":"Test on multiple distributions:\n| Distro | GTK4/WebKit6 | GTK3/WebKit4.1 | Priority |\n|--------|--------------|----------------|----------|\n| Ubuntu 24.04 | ✓ test | ✓ test | HIGH |\n| Ubuntu 22.04 | N/A | ✓ test | HIGH |\n| Fedora 40+ | ✓ test | ✓ test | HIGH |\n| Arch Linux | ✓ test | ✓ test | MEDIUM |\n| Debian 12 | ✓ backports | ✓ test | MEDIUM |\n\nUse Docker containers or VMs for reproducible testing.","status":"open","priority":1,"issue_type":"task","created_at":"2026-01-04T12:41:11.624965746+11:00","updated_at":"2026-01-04T12:41:11.624965746+11:00","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.25","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:41:11.62962541+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e.26","title":"Test Strategy: X11 vs Wayland session testing","description":"Test both display servers:\nX11 Session:\n- Window positioning should work (GTK3) or no-op gracefully (GTK4)\n- All features functional\n- Performance baseline\n\nWayland Session:\n- Window positioning confirmed as no-op (expected)\n- No crashes or errors from positioning attempts\n- Native Wayland rendering (GTK4 benefits)\n- Verify GDK_BACKEND handling\n\nTest with: GDK_BACKEND=x11 and GDK_BACKEND=wayland","status":"open","priority":1,"issue_type":"task","created_at":"2026-01-04T12:41:15.327289032+11:00","updated_at":"2026-01-04T12:41:15.327289032+11:00","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.26","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:41:15.331938707+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e.27","title":"Test Strategy: Doctor and capabilities command tests","description":"Test tooling works correctly:\n- wails3 doctor shows GTK3/4 and WebKit4.1/6.0 availability\n- wails3 doctor only shows 'installed' for actual dev packages (not runtime)\n- wails3 capabilities --json returns correct structure\n- wails3 capabilities --linux-tags returns 'webkit_6' when available, empty when not\n- Taskfile integration works (auto-selects correct tags)\n- Test on system WITH gtk4/webkit6 dev packages\n- Test on system WITHOUT gtk4/webkit6 (only gtk3)","status":"open","priority":1,"issue_type":"task","created_at":"2026-01-04T12:41:19.283226161+11:00","updated_at":"2026-01-04T12:41:19.283226161+11:00","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.27","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:41:19.287996887+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e.28","title":"Test Strategy: Auto-detection fallback behavior","description":"Test the smart default behavior:\nScenario 1: System has GTK4 + WebKit6\n- capabilities recommends webkit_6\n- Taskfile uses webkit_6 automatically\n- Build succeeds with GTK4 features\n\nScenario 2: System has only GTK3 + WebKit4.1\n- capabilities recommends default (no tag)\n- Taskfile uses no extra tags\n- Build succeeds with GTK3\n\nScenario 3: Neither available\n- Doctor shows clear error\n- Install commands displayed per distro\n- Build fails with helpful message","status":"open","priority":1,"issue_type":"task","created_at":"2026-01-04T12:41:22.845473972+11:00","updated_at":"2026-01-04T12:41:22.845473972+11:00","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.28","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:41:22.850043685+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e.29","title":"Test Strategy: Docker test containers","description":"Update Docker containers to support both GTK versions:\n\nSingle container with both library sets:\n- Ubuntu 24.04 base (has both GTK3 and GTK4)\n- libgtk-4-dev + libwebkitgtk-6.0-dev (default)\n- libgtk-3-dev + libwebkit2gtk-4.1-dev (legacy)\n\nBuild script parameters:\n- docker run wails-linux ./build.sh # GTK4 default\n- docker run wails-linux ./build.sh --gtk3 # GTK3 legacy\n\nArchitecture variants:\n- Dockerfile.linux-x86_64 (amd64)\n- Dockerfile.linux-arm64 (aarch64)\n\nStore in v3/test/docker/ directory","status":"open","priority":1,"issue_type":"task","created_at":"2026-01-04T12:41:52.701057271+11:00","updated_at":"2026-01-04T12:53:38.599341789+11:00","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.29","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:41:52.706373965+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e.3","title":"Phase 2: Update wails doctor for GTK4 as default","description":"Update doctor to check for GTK4/WebKit6 as primary requirement:\n\nPrimary checks (required for default build):\n- pkg-config --exists gtk4\n- pkg-config --exists webkitgtk-6.0\n\nSecondary checks (for legacy builds):\n- pkg-config --exists gtk+-3.0\n- pkg-config --exists webkit2gtk-4.1\n\nOutput example:\n GTK4 (gtk4) ✓ 4.14.1 [required for default build]\n WebKitGTK 6.0 ✓ 2.44.1 [required for default build]\n GTK3 (gtk+-3.0) ✓ 3.24.38 [for -tags gtk3 builds]\n WebKit2GTK 4.1 ✓ 2.44.1 [for -tags gtk3 builds]\n\nIf GTK4 missing: suggest Docker build or install commands","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-04T12:07:43.111264576+11:00","updated_at":"2026-01-06T15:38:15.173987437+11:00","closed_at":"2026-01-06T15:38:15.173987437+11:00","close_reason":"Doctor already correctly shows GTK4 as primary and GTK3 as legacy. Verified output shows gtk4 4.20.3, webkitgtk-6.0 2.50.3 as required, gtk3/webkit2gtk marked (legacy) as optional.","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.3","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:07:43.11571264+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e.30","title":"Test Strategy: Docker-based doctor/capabilities tests","description":"Test both GTK versions via Docker:\n\nTest matrix for Docker builds:\n| Command | Expected Result |\n|---------|-----------------|\n| task build:linux | GTK4/WebKit6 binary |\n| task build:linux:gtk3 | GTK3/WebKit4.1 binary |\n\nVerify in Docker container:\n- Default build links against libgtk-4.so, libwebkitgtk-6.0.so\n- GTK3 build links against libgtk-3.so, libwebkit2gtk-4.1.so\n\nCross-compilation test:\n- Build from macOS → Linux GTK4 (Docker)\n- Build from macOS → Linux GTK3 (Docker)\n- Build from Windows → Linux GTK4 (Docker)","status":"open","priority":1,"issue_type":"task","created_at":"2026-01-04T12:41:56.664141365+11:00","updated_at":"2026-01-04T12:53:44.010563993+11:00","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.30","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:41:56.668983135+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e.31","title":"Test Strategy: Docker-based build verification","description":"Verify Docker builds produce correct binaries:\n\nDefault (GTK4) builds:\n- docker run wails-linux ./build.sh\n- Binary requires libgtk-4, libwebkitgtk-6.0 at runtime\n- Runs on Ubuntu 22.04+, Fedora 38+, Arch, Debian 12+\n\nLegacy (GTK3) builds:\n- docker run wails-linux ./build.sh --gtk3\n- Binary requires libgtk-3, libwebkit2gtk-4.1 at runtime\n- Runs on older distros (Ubuntu 20.04, Debian 11, RHEL 8)\n\nTest both binaries actually run on target systems (not just compile)","status":"open","priority":1,"issue_type":"task","created_at":"2026-01-04T12:42:00.581467132+11:00","updated_at":"2026-01-04T12:53:49.143142805+11:00","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.31","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:42:00.586144259+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e.32","title":"Test Strategy: CI workflow for multi-distro testing","description":"Add GitHub Actions workflow for automated testing:\n\n.github/workflows/linux-webkit6-tests.yml:\n- Trigger on PRs touching v3/pkg/application/*linux*, v3/internal/doctor/*\n- Matrix strategy with Docker containers\n- Jobs:\n 1. Build verification (both tag configs)\n 2. Doctor command output validation\n 3. Capabilities command JSON validation\n \nCache Docker images for faster CI runs.\nFail CI if any distro/config combination breaks.","status":"open","priority":2,"issue_type":"task","created_at":"2026-01-04T12:42:04.299112575+11:00","updated_at":"2026-01-04T12:42:04.299112575+11:00","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.32","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:42:04.30363047+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e.33","title":"Test Strategy: Unit tests for new Go code","description":"Add unit tests for new functionality:\n\n- capabilities command parsing and JSON output\n- pkg-config detection wrapper functions\n- Build tag detection logic\n- DevPackageInstalled() / DevPackageVersion() functions\n- Taskfile tag generation logic\n\nTests should be runnable without GTK installed (mock pkg-config calls).\nLocation: v3/internal/doctor/*_test.go, v3/cmd/wails3/*_test.go","status":"open","priority":1,"issue_type":"task","created_at":"2026-01-04T12:42:59.917988736+11:00","updated_at":"2026-01-04T12:42:59.917988736+11:00","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.33","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:42:59.91880054+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e.34","title":"Test Strategy: Edge cases and error handling","description":"Test edge cases for webkit_6 implementation:\n\nMenu edge cases:\n- Empty menu\n- Deeply nested submenus (5+ levels)\n- Menu items with special characters (unicode, \u0026, \u003c, \u003e)\n- Rapid menu updates\n- Menu with 100+ items\n\nWindow edge cases:\n- Multiple windows simultaneously\n- Window close during JS execution\n- Rapid show/hide cycles\n\nError handling:\n- Graceful handling when WebKit crashes\n- Clear error messages for missing dependencies\n- Proper cleanup on application exit","status":"open","priority":2,"issue_type":"task","created_at":"2026-01-04T12:43:02.108202689+11:00","updated_at":"2026-01-04T12:43:02.108202689+11:00","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.34","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:43:02.114118944+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e.35","title":"Test Strategy: Memory and resource leak testing","description":"Verify no memory leaks in webkit_6 implementation:\n\nCritical areas (from v2 PR issues):\n- C.CString allocations must be freed\n- GObject ref counting (g_object_unref calls)\n- Menu rebuilds don't leak\n- Window create/destroy cycles don't leak\n\nTesting approach:\n- Use valgrind or AddressSanitizer\n- Create/destroy 1000 windows, measure memory\n- Rebuild menus 1000 times, measure memory\n- Long-running test (1 hour) with periodic operations\n\nCompare memory behavior between GTK3 and GTK4 builds.","status":"open","priority":2,"issue_type":"task","created_at":"2026-01-04T12:43:08.315395906+11:00","updated_at":"2026-01-04T12:43:08.315395906+11:00","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.35","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:43:08.320587181+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e.36","title":"Test Strategy: Example apps verification","description":"Verify all v3 example apps work with webkit_6:\n\nTest each example in v3/examples/:\n- Build with default tags (GTK3)\n- Build with -tags webkit_6 (GTK4)\n- Run and verify functionality matches\n\nPriority examples:\n- plain (basic window)\n- menu (menu system)\n- dialogs (file/message dialogs)\n- clipboard (copy/paste)\n- events (window events)\n- binding (Go-JS bindings)\n\nDocument any examples that need webkit_6-specific adjustments.","status":"open","priority":1,"issue_type":"task","created_at":"2026-01-04T12:43:12.575574602+11:00","updated_at":"2026-01-04T12:43:12.575574602+11:00","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.36","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:43:12.58115082+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e.37","title":"Phase 2: Update Docker container with both GTK3 and GTK4 libraries","description":"Update Dockerfile.linux-x86_64 and Dockerfile.linux-arm64 to include BOTH library sets:\n\nRUN apt-get install -y \\\n # GTK4 + WebKitGTK 6.0 (default/modern)\n libgtk-4-dev \\\n libwebkitgtk-6.0-dev \\\n # GTK3 + WebKit2GTK 4.1 (legacy)\n libgtk-3-dev \\\n libwebkit2gtk-4.1-dev \\\n libayatana-appindicator3-dev\n\nUpdate build script to accept GTK version parameter:\n- build-linux.sh gtk4 → builds with no tags (default)\n- build-linux.sh gtk3 → builds with -tags gtk3\n\nThis enables cross-compilation to EITHER target from any platform.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-04T12:53:05.672476127+11:00","updated_at":"2026-01-06T20:27:23.926443978+11:00","closed_at":"2026-01-06T20:27:23.926443978+11:00","close_reason":"Completed - Docker containers updated in Phase 6","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.37","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:53:05.678037266+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e.38","title":"Test Strategy: Comprehensive performance benchmark suite","description":"Create benchmark suite to measure GTK4/WebKit6 vs GTK3/WebKit4.1 performance.\n\nRENDERING BENCHMARKS:\n- Time to First Paint (TTFP): Measure ms from app start to first meaningful content\n- Time to Interactive (TTI): When app responds to user input\n- Frame rate during scroll: 60fps target, measure drops\n- Large DOM: Render 10,000 list items, measure time and FPS\n- DOM manipulation: Add/remove 1000 elements, measure time\n- CSS animations: Complex animations, measure frame consistency\n- Reflow/repaint: Measure layout thrashing scenarios\n\nBINDING BENCHMARKS:\n- Call latency: Round-trip time for Go function call from JS\n- Throughput: Calls per second (simple function)\n- Large payload: Transfer 1MB JSON Go→JS and JS→Go\n- Concurrent calls: 100 simultaneous binding calls\n- Callback performance: Go calling back into JS\n\nASSET SERVER BENCHMARKS:\n- Small file latency: Load 1KB file, measure ms\n- Large file throughput: Load 10MB file, measure MB/s\n- Many files: Load 100 small files sequentially\n- Parallel loading: Load 20 files simultaneously\n- Streaming: Video/audio streaming performance\n\nMEMORY BENCHMARKS:\n- Initial footprint: Memory after app start\n- After heavy ops: Memory after DOM/binding stress\n- Leak detection: Memory after 1000 window open/close cycles\n- Long-running: Memory growth over 1 hour of use\n\nOUTPUT FORMAT:\n- JSON results for CI integration\n- Markdown report for human review\n- Compare GTK4 vs GTK3 side-by-side\n\nIMPLEMENTATION:\n- Create v3/test/benchmarks/ directory\n- Benchmark app with standardized test pages\n- Go benchmark harness using testing.B\n- Browser performance APIs (performance.now(), PerformanceObserver)","status":"open","priority":1,"issue_type":"task","created_at":"2026-01-04T12:54:15.661692112+11:00","updated_at":"2026-01-04T12:54:15.661692112+11:00","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.38","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:54:15.667316351+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e.39","title":"Test Strategy: Create benchmark test application","description":"Create dedicated benchmark application in v3/test/benchmarks/app/\n\nFRONTEND (benchmark pages):\n1. rendering.html - DOM/CSS performance tests\n - Virtual scroll with 100,000 items\n - CSS grid with 1000 cells\n - Animation stress test (50 simultaneous)\n - Canvas 2D drawing benchmark\n \n2. bindings.html - Go\u003c-\u003eJS communication tests\n - Ping-pong latency measurement\n - Large data serialization\n - Concurrent call stress test\n - Event emission throughput\n\n3. assets.html - Asset server tests\n - Image gallery (100 images)\n - Large file download\n - Streaming video playback\n - WebSocket throughput (if applicable)\n\n4. memory.html - Memory profiling\n - Allocate/deallocate cycles\n - DOM node creation/destruction\n - Image loading/unloading\n\nBACKEND (Go harness):\n- BenchmarkService with standardized test methods\n- Metrics collection and reporting\n- Automated test runner\n- Results export (JSON, CSV, Markdown)\n\nCOMPARISON MODE:\n- Run same tests on GTK4 and GTK3 builds\n- Generate comparison report\n- Highlight improvements/regressions\n- Statistical significance testing (multiple runs)","status":"open","priority":1,"issue_type":"task","created_at":"2026-01-04T12:54:33.607772979+11:00","updated_at":"2026-01-04T12:54:33.607772979+11:00","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.39","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:54:33.612719138+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e.4","title":"Phase 2: Update wails3 capabilities command","description":"Update capabilities command for GTK4-default architecture:\n\nOutput structure:\n{\n \"linux\": {\n \"gtk4\": true,\n \"gtk3\": true,\n \"webkitgtk_6_0\": true,\n \"webkit2gtk_4_1\": true,\n \"default_available\": true, // Can build without tags\n \"legacy_available\": true, // Can build with -tags gtk3\n \"recommended\": \"default\" // or \"legacy\" or \"docker\"\n }\n}\n\nIf GTK4 not available locally, recommend Docker build.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-04T12:07:44.735354235+11:00","updated_at":"2026-01-06T20:46:36.935228449+11:00","closed_at":"2026-01-06T20:46:36.935228449+11:00","close_reason":"Implemented wails3 tool capabilities command","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.4","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:07:44.739955792+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e.40","title":"Test Strategy: CI benchmark regression tracking","description":"Add CI workflow to track performance over time:\n\nWORKFLOW (.github/workflows/benchmarks.yml):\n- Trigger: Weekly schedule + manual dispatch\n- Run benchmarks on both GTK4 and GTK3 builds\n- Store results as artifacts\n- Compare against baseline\n\nTRACKING:\n- Store historical benchmark data\n- Alert on \u003e10% regression\n- Track trends over releases\n- Generate performance dashboard\n\nBASELINE ESTABLISHMENT:\n- Run benchmarks before GTK4 migration (current GTK3)\n- This becomes the baseline for comparison\n- Document expected improvements from GTK4/WebKit6\n\nREPORTING:\n- Post results to PR comments (for benchmark PRs)\n- Update performance docs with latest numbers\n- Publish comparison: GTK4 vs GTK3 vs other frameworks (Electron, Tauri)","status":"open","priority":2,"issue_type":"task","created_at":"2026-01-04T12:54:39.84655827+11:00","updated_at":"2026-01-04T12:54:39.84655827+11:00","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.40","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:54:39.851364533+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e.5","title":"Phase 2: Update Taskfile with build:linux and build:linux:gtk3","description":"Update Taskfile.yaml with Linux build targets:\n\ntask build:linux → Default GTK4/WebKit6 build\ntask build:linux:gtk3 → Legacy GTK3/WebKit4.1 build (uses -tags gtk3)\n\nBoth work via Docker for cross-compilation:\n- Docker container has BOTH library sets installed\n- Build script accepts target parameter\n\nExample usage:\n task build:linux # GTK4 native or Docker\n task build:linux:gtk3 # GTK3 via Docker (or native with -tags gtk3)","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T12:07:46.101012374+11:00","updated_at":"2026-01-06T20:27:33.971988614+11:00","closed_at":"2026-01-06T20:27:33.971988614+11:00","close_reason":"Completed - Taskfile updated with test:example:linux:gtk3 and docker targets in Phase 6","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.5","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:07:46.105510654+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e.6","title":"Phase 3: Implement GTK4 application lifecycle","description":"linux_cgo_webkit6.go - GTK4 app init with gtk_application_new, g_application_run, proper signal handlers. No gtk_init needed in GTK4.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-04T12:07:56.71551144+11:00","updated_at":"2026-01-06T15:34:38.511762855+11:00","closed_at":"2026-01-06T15:34:38.511762855+11:00","close_reason":"GTK4 application lifecycle implemented in application_linux_gtk4.go","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.6","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:07:56.721263416+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e.7","title":"Phase 3: Implement GTK4 window management","description":"Window creation with gtk_window_set_child (not gtk_container_add). Widgets visible by default (no show_all). Fullscreen/maximize/minimize state handling.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-04T12:07:59.625663163+11:00","updated_at":"2026-01-06T15:34:39.557173339+11:00","closed_at":"2026-01-06T15:34:39.557173339+11:00","close_reason":"GTK4 window management implemented in linux_cgo_gtk4.go","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.7","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:07:59.63049083+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e.8","title":"Phase 3: Implement WebKitGTK 6.0 WebView integration","description":"Use webkit_web_view_evaluate_javascript (not run_javascript), WebKitNetworkSession for network, URI scheme handler registration with new API.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-04T12:08:02.595646332+11:00","updated_at":"2026-01-06T15:34:40.794896929+11:00","closed_at":"2026-01-06T15:34:40.794896929+11:00","close_reason":"WebKitGTK 6.0 WebView integration implemented in linux_cgo_gtk4.go","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.8","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:08:02.601222032+11:00","created_by":"daemon"}]} +{"id":"wails-webview2gtk6-t4e.9","title":"Phase 3: Window positioning as no-op for webkit_6","description":"SetPosition, Center, GetPosition return gracefully on GTK4/Wayland. gtk_window_move removed intentionally by GTK. Document as known limitation.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T12:08:05.494126564+11:00","updated_at":"2026-01-06T20:27:32.813843908+11:00","closed_at":"2026-01-06T20:27:32.813843908+11:00","close_reason":"Completed - Window positioning documented as no-op on Wayland in Decision 3 of IMPLEMENTATION.md","dependencies":[{"issue_id":"wails-webview2gtk6-t4e.9","depends_on_id":"wails-webview2gtk6-t4e","type":"parent-child","created_at":"2026-01-04T12:08:05.498782524+11:00","created_by":"daemon"}]} diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 000000000..5edf609fa --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,71 @@ +language: en-US +tone_instructions: '' +early_access: false +enable_free_tier: true +reviews: + profile: chill + request_changes_workflow: false + high_level_summary: true + high_level_summary_placeholder: '@coderabbitai summary' + auto_title_placeholder: '@coderabbitai' + review_status: true + poem: true + collapse_walkthrough: false + sequence_diagrams: true + path_filters: [] + path_instructions: [] + abort_on_close: true + auto_review: + enabled: true + auto_incremental_review: true + ignore_title_keywords: [] + labels: [] + drafts: false + base_branches: ['v3-alpha', 'master'] + tools: + shellcheck: + enabled: true + ruff: + enabled: true + markdownlint: + enabled: true + github-checks: + enabled: true + timeout_ms: 90000 + languagetool: + enabled: true + enabled_only: false + level: default + biome: + enabled: true + hadolint: + enabled: true + swiftlint: + enabled: true + phpstan: + enabled: true + level: default + golangci-lint: + enabled: true + yamllint: + enabled: true + gitleaks: + enabled: true + checkov: + enabled: true + detekt: + enabled: true + eslint: + enabled: true +chat: + auto_reply: true +knowledge_base: + opt_out: false + learnings: + scope: auto + issues: + scope: auto + jira: + project_keys: [] + linear: + team_keys: [] diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..807d5983d --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ + +# Use bd merge for beads JSONL files +.beads/issues.jsonl merge=beads diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 84b7cb6dc..c8ace9747 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -7,6 +7,8 @@ body: - type: markdown attributes: value: | + ***Please note: No bug reports are currently being accepted for Wails v3*** + ***Please note: No bug reports are currently being accepted for Wails v3*** ***Please note: No bug reports are currently being accepted for Wails v3*** Before submitting this issue, please do the following: - Do a web search for your error. This usually leads to a much better understanding of the issue. diff --git a/.github/file-labeler.yml b/.github/file-labeler.yml new file mode 100644 index 000000000..69494cbae --- /dev/null +++ b/.github/file-labeler.yml @@ -0,0 +1,44 @@ +# File path specific labels +v2-only: + - 'v2/**/*' + +v3-alpha: + - 'v3/**/*' + +windows: + - '**/*_windows.go' + - 'v2/internal/frontend/desktop/windows/**/*' + +macos: + - '**/*_darwin.go' + - 'v2/internal/frontend/desktop/darwin/**/*' + +linux: + - '**/*_linux.go' + - 'v2/internal/frontend/desktop/linux/**/*' + +cli: + - 'v2/cmd/**/*' + - 'v3/cmd/**/*' + - '**/cli/**/*' + - '**/commands/**/*' + +documentation: + - '**/*.md' + - 'docs/**/*' + - 'website/**/*' + - 'mkdocs-website/**/*' + +templates: + - '**/templates/**/*' + - '**/template/**/*' + +runtime: + - '**/runtime/**/*' + - 'v2/internal/runtime/**/*' + - 'v3/internal/runtime/**/*' + +bindings: + - 'v2/internal/binding/**/*' + - 'v3/internal/generator/**/*' + diff --git a/.github/issue-labeler.yml b/.github/issue-labeler.yml new file mode 100644 index 000000000..0a7949051 --- /dev/null +++ b/.github/issue-labeler.yml @@ -0,0 +1,144 @@ +# Version labels +v2-only: + - '\[v2\]' + - '\(v2\)' + - 'v2:' + - 'version 2' + - 'wails v2' + - 'using v2' + - 'master branch' + +v3-alpha: + - '\[v3\]' + - '\(v3\)' + - 'v3:' + - '\[v3-alpha\]' + - '\(v3-alpha\)' + - 'version 3' + - 'wails v3' + - 'using v3' + - 'v3-alpha branch' + +# Component labels +webview2: + - 'webview2' + - 'windows' + - 'microsoft edge' + - 'edge browser' + - 'IE' + - 'Explorer' + - 'browser crashes' + +macos: + - 'macOS' + - 'mac OS' + - 'OS X' + - 'darwin' + - 'cocoa' + - 'Safari' + - 'Catalyst' + - 'Ventura' + - 'Sonoma' + - 'apple' + +linux: + - 'linux' + - 'ubuntu' + - 'debian' + - 'fedora' + - 'gtk' + - 'webkitgtk' + - 'webkit2gtk' + - 'gnome' + - 'x11' + - 'wayland' + +cli: + - 'cli' + - 'command line' + - 'wails doctor' + - 'wails init' + - 'wails build' + - 'wails dev' + - 'template' + - 'scaffolding' + +# Type labels +bug: + - 'bug' + - 'crash' + - 'broken' + - 'failure' + - 'error' + - 'failed' + - 'panic' + - 'segfault' + - 'issue' + - 'not working' + - 'problem' + +enhancement: + - 'feature' + - 'enhancement' + - 'request' + - 'add' + - 'new' + - 'improve' + - 'functionality' + - 'support for' + - 'please add' + - 'would be nice' + +documentation: + - 'docs' + - 'documentation' + - 'readme' + - 'example' + - 'tutorial' + - 'guide' + - 'explanation' + - 'clarification' + - 'instructions' + +security: + - 'security' + - 'vulnerability' + - 'exploit' + - 'hack' + - 'CVE' + - 'secure' + - 'encryption' + - 'hardening' + +performance: + - 'performance' + - 'slow' + - 'speed' + - 'memory leak' + - 'cpu usage' + - 'high memory' + - 'lag' + - 'freeze' + - 'optimization' + +# Priority labels +high-priority: + - 'urgent' + - 'critical' + - 'security' + - 'high priority' + - 'important' + - 'production' + - 'blocker' + - 'blocking' + +question: + - 'how to' + - 'how do i' + - 'can I' + - 'is it possible' + - 'question' + - 'help me' + - 'need help' + - 'assistance' + - 'confused' diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index bf3d8de39..9f8d049ba 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,9 +1,20 @@ # Description @@ -14,7 +25,7 @@ Fixes # (issue) ## Type of change -Please delete options that are not relevant. +Please select the option that is relevant. - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) @@ -28,6 +39,8 @@ Please describe the tests that you ran to verify your changes. Provide instructi - [ ] Windows - [ ] macOS - [ ] Linux + +If you checked Linux, please specify the distro and version. ## Test Configuration @@ -35,7 +48,7 @@ Please paste the output of `wails doctor`. If you are unable to run this command # Checklist: -- [ ] I have updated `website/src/pages/changelog.mdx` with details of this PR +- [ ] I have updated `v3/UNRELEASED_CHANGELOG.md` with details of this PR - [ ] 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 diff --git a/.github/stale.yml b/.github/stale.yml index 805bd589d..d8bcc83ec 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -1,7 +1,7 @@ # Number of days of inactivity before an issue becomes stale -daysUntilStale: 30 +daysUntilStale: 45 # Number of days of inactivity before a stale issue is closed -daysUntilClose: 7 +daysUntilClose: 10 # Issues with these labels will never be considered stale exemptLabels: - pinned @@ -9,14 +9,28 @@ exemptLabels: - onhold - inprogress - "Selected For Development" + - bug + - enhancement + - v3-alpha + - high-priority # Label to use when marking an issue as stale -staleLabel: "Wont Fix" +staleLabel: "stale" # Comment to post when marking an issue as stale. Set to `false` to disable markComment: > This issue has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. Thank you - for your contributions. + recent activity. It will be closed if no further activity occurs within the next 10 days. + + If this issue is still relevant, please add a comment to keep it open. + Thank you for your contributions. # Comment to post when closing a stale issue. Set to `false` to disable -closeComment: false +closeComment: > + This issue has been automatically closed due to lack of activity. + Please feel free to reopen it if it's still relevant. exemptMilestones: true exemptAssignees: true +# Only mark issues (not PRs) +only: issues +# Exempt issues created before a certain date +exemptCreatedBefore: "2024-01-01T00:00:00Z" +# Starts checking issues only after the specified date +startDate: "2025-06-01T00:00:00Z" diff --git a/.github/workflows/auto-label-issues.yml b/.github/workflows/auto-label-issues.yml new file mode 100644 index 000000000..097eba533 --- /dev/null +++ b/.github/workflows/auto-label-issues.yml @@ -0,0 +1,33 @@ +name: Auto Label Issues + +on: + issues: + types: [opened, edited, reopened] + pull_request_target: + types: [opened, edited, reopened, synchronize] + +jobs: + auto-label: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + contents: read + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Label issues and PRs by content + uses: github/issue-labeler@v3.4 + with: + repo-token: "${{ secrets.GITHUB_TOKEN }}" + configuration-path: .github/issue-labeler.yml + enable-versioned-regex: 0 + include-title: 1 + + - name: Label issues and PRs by file paths + uses: actions/labeler@v4 + with: + repo-token: "${{ secrets.GITHUB_TOKEN }}" + configuration-path: .github/file-labeler.yml + sync-labels: true diff --git a/.github/workflows/automated-releases.yml b/.github/workflows/automated-releases.yml new file mode 100644 index 000000000..83445d869 --- /dev/null +++ b/.github/workflows/automated-releases.yml @@ -0,0 +1,373 @@ +name: Automated Nightly Releases + +on: + workflow_dispatch: + inputs: + force_release: + description: 'Force release even if no changes detected' + required: false + default: false + type: boolean + dry_run: + description: 'Run in dry-run mode (no actual releases)' + required: false + default: false + type: boolean + # schedule: + # Run at 2 AM UTC every day - DISABLED for safety until ready + # - cron: '0 2 * * *' + +env: + GO_VERSION: '1.24' + +jobs: + check-permissions: + name: Check Release Permissions + permissions: {} + runs-on: ubuntu-latest + outputs: + authorized: ${{ steps.check.outputs.authorized }} + steps: + - name: Check if user is authorized for releases + id: check + run: | + # Only allow specific users to trigger releases + AUTHORIZED_USERS="leaanthony" + + if [[ "$AUTHORIZED_USERS" == *"${{ github.actor }}"* ]]; then + echo "✅ User ${{ github.actor }} is authorized for releases" + echo "authorized=true" >> $GITHUB_OUTPUT + else + echo "❌ User ${{ github.actor }} is not authorized for releases" + echo "authorized=false" >> $GITHUB_OUTPUT + fi + + detect-v2-changes: + name: Detect v2 Changes + runs-on: ubuntu-latest + needs: check-permissions + if: needs.check-permissions.outputs.authorized == 'true' + outputs: + has_changes: ${{ steps.changes.outputs.has_changes }} + commits_since_last: ${{ steps.changes.outputs.commits_since_last }} + last_release_tag: ${{ steps.changes.outputs.last_release_tag }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Check for v2 changes since last release + id: changes + run: | + echo "🔍 Checking for v2 changes since last release..." + + # Find the last v2 release tag + LAST_TAG=$(git tag -l "v2.*" --sort=-version:refname | head -n 1) + if [ -z "$LAST_TAG" ]; then + echo "No previous v2 tags found, assuming first release" + LAST_TAG=$(git rev-list --max-parents=0 HEAD) + echo "has_changes=true" >> $GITHUB_OUTPUT + echo "commits_since_last=999" >> $GITHUB_OUTPUT + echo "last_release_tag=none" >> $GITHUB_OUTPUT + else + echo "Last v2 release tag: $LAST_TAG" + echo "last_release_tag=$LAST_TAG" >> $GITHUB_OUTPUT + + # Count commits since last release affecting v2 or root files + COMMITS_COUNT=$(git rev-list --count ${LAST_TAG}..HEAD -- v2/ website/ README.md CHANGELOG.md || echo "0") + echo "Commits since last v2 release: $COMMITS_COUNT" + echo "commits_since_last=$COMMITS_COUNT" >> $GITHUB_OUTPUT + + if [ "$COMMITS_COUNT" -gt 0 ] || [ "${{ github.event.inputs.force_release }}" == "true" ]; then + echo "✅ Changes detected or forced release" + echo "has_changes=true" >> $GITHUB_OUTPUT + else + echo "ℹ️ No changes detected since last release" + echo "has_changes=false" >> $GITHUB_OUTPUT + fi + fi + + detect-v3-changes: + name: Detect v3-alpha Changes + runs-on: ubuntu-latest + needs: check-permissions + if: needs.check-permissions.outputs.authorized == 'true' + outputs: + has_changes: ${{ steps.changes.outputs.has_changes }} + commits_since_last: ${{ steps.changes.outputs.commits_since_last }} + last_release_tag: ${{ steps.changes.outputs.last_release_tag }} + steps: + - name: Checkout v3-alpha branch + uses: actions/checkout@v4 + with: + ref: v3-alpha + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Check for v3-alpha changes since last release + id: changes + run: | + echo "🔍 Checking for v3-alpha changes since last release..." + + # Find the last v3 alpha release tag + LAST_TAG=$(git tag -l "v3.*-alpha.*" --sort=-version:refname | head -n 1) + if [ -z "$LAST_TAG" ]; then + echo "No previous v3-alpha tags found, assuming first release" + LAST_TAG=$(git rev-list --max-parents=0 HEAD) + echo "has_changes=true" >> $GITHUB_OUTPUT + echo "commits_since_last=999" >> $GITHUB_OUTPUT + echo "last_release_tag=none" >> $GITHUB_OUTPUT + else + echo "Last v3-alpha release tag: $LAST_TAG" + echo "last_release_tag=$LAST_TAG" >> $GITHUB_OUTPUT + + # Count commits since last release affecting v3 or docs + COMMITS_COUNT=$(git rev-list --count ${LAST_TAG}..HEAD -- v3/ docs/ || echo "0") + echo "Commits since last v3-alpha release: $COMMITS_COUNT" + echo "commits_since_last=$COMMITS_COUNT" >> $GITHUB_OUTPUT + + if [ "$COMMITS_COUNT" -gt 0 ] || [ "${{ github.event.inputs.force_release }}" == "true" ]; then + echo "✅ Changes detected or forced release" + echo "has_changes=true" >> $GITHUB_OUTPUT + else + echo "ℹ️ No changes detected since last release" + echo "has_changes=false" >> $GITHUB_OUTPUT + fi + fi + + release-v2: + name: Create v2 Release + runs-on: ubuntu-latest + needs: [check-permissions, detect-v2-changes] + if: | + needs.check-permissions.outputs.authorized == 'true' && + needs.detect-v2-changes.outputs.has_changes == 'true' + outputs: + version: ${{ steps.release.outputs.version }} + release_notes: ${{ steps.release.outputs.release_notes }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Go + uses: actions/setup-go@v4 + with: + go-version: ${{ env.GO_VERSION }} + + - name: Run v2 release script and extract notes + id: release + run: | + echo "🚀 Running v2 release script..." + cd v2/tools/release + + # Run release script and capture output + RELEASE_OUTPUT=$(go run release.go 2>&1) + echo "$RELEASE_OUTPUT" + + # Extract version from output or version file + NEW_VERSION=$(cat ../../cmd/wails/internal/version.txt) + echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT + + # Extract release notes from delimited output + RELEASE_NOTES=$(echo "$RELEASE_OUTPUT" | sed -n '/=== RELEASE NOTES FOR/,/=== END RELEASE NOTES ===/p' | sed '1d;$d') + + # Save release notes to file for multiline output + echo "$RELEASE_NOTES" > ../../../release_notes_v2.md + + # Set output (escape for GitHub Actions) + { + echo "release_notes<> $GITHUB_OUTPUT + + echo "✅ v2 release script completed - version: $NEW_VERSION" + + - name: Create v2 git tag and release + if: github.event.inputs.dry_run != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + VERSION="${{ steps.release.outputs.version }}" + echo "📝 Creating v2 release: $VERSION" + + # Configure git + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + # Commit the changelog changes + git add website/src/pages/changelog.mdx v2/cmd/wails/internal/version.txt + git commit -m "chore: release $VERSION + + Automated release created by GitHub Actions + + 🤖 Generated with [Claude Code](https://claude.ai/code) + + Co-Authored-By: Claude " + + # Create and push tag + git tag -a "$VERSION" -m "Release $VERSION" + git push origin master + git push origin "$VERSION" + + # Create GitHub release with notes + gh release create "$VERSION" \ + --title "Release $VERSION" \ + --notes-file release_notes_v2.md \ + --target master + + - name: Log dry-run results for v2 + if: github.event.inputs.dry_run == 'true' + run: | + echo "🧪 DRY RUN - Would have created v2 release:" + echo "Version: ${{ steps.release.outputs.version }}" + echo "Release Notes:" + cat release_notes_v2.md + + release-v3: + name: Create v3-alpha Release + runs-on: ubuntu-latest + needs: [check-permissions, detect-v3-changes] + if: | + needs.check-permissions.outputs.authorized == 'true' && + needs.detect-v3-changes.outputs.has_changes == 'true' + outputs: + version: ${{ steps.release.outputs.version }} + release_notes: ${{ steps.release.outputs.release_notes }} + steps: + - name: Checkout v3-alpha branch + uses: actions/checkout@v4 + with: + ref: v3-alpha + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Go + uses: actions/setup-go@v4 + with: + go-version: ${{ env.GO_VERSION }} + + - name: Run v3 release script and extract notes + id: release + run: | + echo "🚀 Running v3-alpha release script..." + cd v3/tasks/release + + # Run release script and capture output + RELEASE_OUTPUT=$(go run release.go 2>&1) + echo "$RELEASE_OUTPUT" + + # Extract version from output or version file + NEW_VERSION=$(cat ../../internal/version/version.txt) + echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT + + # Extract release notes from delimited output + RELEASE_NOTES=$(echo "$RELEASE_OUTPUT" | sed -n '/=== RELEASE NOTES FOR/,/=== END RELEASE NOTES ===/p' | sed '1d;$d') + + # Save release notes to file for multiline output + echo "$RELEASE_NOTES" > ../../../release_notes_v3.md + + # Set output (escape for GitHub Actions) + { + echo "release_notes<> $GITHUB_OUTPUT + + echo "✅ v3-alpha release script completed - version: $NEW_VERSION" + + - name: Create v3-alpha git tag and release + if: github.event.inputs.dry_run != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + VERSION="${{ steps.release.outputs.version }}" + echo "📝 Creating v3-alpha release: $VERSION" + + # Configure git + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + # Commit the changelog changes + git add docs/src/content/docs/changelog.mdx v3/internal/version/version.txt + git commit -m "chore: release $VERSION + + Automated v3-alpha release created by GitHub Actions + + 🤖 Generated with [Claude Code](https://claude.ai/code) + + Co-Authored-By: Claude " + + # Create and push tag + git tag -a "$VERSION" -m "Release $VERSION" + git push origin v3-alpha + git push origin "$VERSION" + + # Create GitHub release with notes + gh release create "$VERSION" \ + --title "Release $VERSION" \ + --notes-file release_notes_v3.md \ + --target v3-alpha \ + --prerelease + + - name: Log dry-run results for v3-alpha + if: github.event.inputs.dry_run == 'true' + run: | + echo "🧪 DRY RUN - Would have created v3-alpha release:" + echo "Version: ${{ steps.release.outputs.version }}" + echo "Release Notes:" + cat release_notes_v3.md + + summary: + name: Release Summary + runs-on: ubuntu-latest + needs: [check-permissions, detect-v2-changes, detect-v3-changes, release-v2, release-v3] + if: always() && needs.check-permissions.outputs.authorized == 'true' + steps: + - name: Create release summary + run: | + echo "# 🚀 Automated Release Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Repository**: ${{ github.repository }}" >> $GITHUB_STEP_SUMMARY + echo "**Triggered by**: ${{ github.actor }}" >> $GITHUB_STEP_SUMMARY + echo "**Dry Run Mode**: ${{ github.event.inputs.dry_run || 'false' }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # v2 Summary + echo "## v2 Release" >> $GITHUB_STEP_SUMMARY + if [ "${{ needs.detect-v2-changes.outputs.has_changes }}" == "true" ]; then + if [ "${{ needs.release-v2.result }}" == "success" ]; then + echo "✅ **v2 Release**: Created successfully" >> $GITHUB_STEP_SUMMARY + echo " - Version: ${{ needs.release-v2.outputs.version }}" >> $GITHUB_STEP_SUMMARY + echo " - Commits since last: ${{ needs.detect-v2-changes.outputs.commits_since_last }}" >> $GITHUB_STEP_SUMMARY + else + echo "❌ **v2 Release**: Failed" >> $GITHUB_STEP_SUMMARY + fi + else + echo "⏭️ **v2 Release**: Skipped (no changes)" >> $GITHUB_STEP_SUMMARY + echo " - Commits since last: ${{ needs.detect-v2-changes.outputs.commits_since_last }}" >> $GITHUB_STEP_SUMMARY + fi + + # v3 Summary + echo "## v3-alpha Release" >> $GITHUB_STEP_SUMMARY + if [ "${{ needs.detect-v3-changes.outputs.has_changes }}" == "true" ]; then + if [ "${{ needs.release-v3.result }}" == "success" ]; then + echo "✅ **v3-alpha Release**: Created successfully" >> $GITHUB_STEP_SUMMARY + echo " - Version: ${{ needs.release-v3.outputs.version }}" >> $GITHUB_STEP_SUMMARY + echo " - Commits since last: ${{ needs.detect-v3-changes.outputs.commits_since_last }}" >> $GITHUB_STEP_SUMMARY + else + echo "❌ **v3-alpha Release**: Failed" >> $GITHUB_STEP_SUMMARY + fi + else + echo "⏭️ **v3-alpha Release**: Skipped (no changes)" >> $GITHUB_STEP_SUMMARY + echo " - Commits since last: ${{ needs.detect-v3-changes.outputs.commits_since_last }}" >> $GITHUB_STEP_SUMMARY + fi + + echo "" >> $GITHUB_STEP_SUMMARY + echo "---" >> $GITHUB_STEP_SUMMARY + echo "🤖 **Automated Release System** | Generated with [Claude Code](https://claude.ai/code)" >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/.github/workflows/build-and-test-v3.yml b/.github/workflows/build-and-test-v3.yml new file mode 100644 index 000000000..cc2c6ded0 --- /dev/null +++ b/.github/workflows/build-and-test-v3.yml @@ -0,0 +1,317 @@ +name: Build + Test v3 + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + branches: + - v3-alpha + pull_request_review: + types: [submitted] + branches: + - v3-alpha + +jobs: + check_approval: + name: Check PR Approval + runs-on: ubuntu-latest + if: github.base_ref == 'v3-alpha' + outputs: + approved: ${{ steps.check.outputs.approved }} + steps: + - name: Check if PR is approved + id: check + run: | + if [[ "${{ github.event.review.state }}" == "approved" || "${{ github.event.pull_request.approved }}" == "true" ]]; then + echo "approved=true" >> $GITHUB_OUTPUT + else + echo "approved=false" >> $GITHUB_OUTPUT + fi + + test_js: + name: Run JS Tests + needs: check_approval + runs-on: ubuntu-latest + if: github.base_ref == 'v3-alpha' + strategy: + matrix: + node-version: [20.x] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + + - name: Install Task + uses: arduino/setup-task@v2 + with: + version: 3.x + repo-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Install dependencies + working-directory: v3/internal/runtime/desktop/@wailsio/runtime + run: | + npm ci + npx --yes esbuild@latest --version + + - name: Clean build artifacts + working-directory: v3/internal/runtime/desktop/@wailsio/runtime + run: npm run clean + + - name: Type-check runtime + working-directory: v3 + run: task runtime:check + + - name: Test runtime + working-directory: v3 + run: task runtime:test + + - name: Check that the bundled runtime builds + working-directory: v3 + run: task runtime:build + + - name: Check that the npm package builds + working-directory: v3/internal/runtime/desktop/@wailsio/runtime + run: npm run build + + - name: Pack runtime for template tests + working-directory: v3/internal/runtime/desktop/@wailsio/runtime + run: npm pack + + - name: Store runtime build artifacts + uses: actions/upload-artifact@v4 + with: + name: runtime-build-artifacts + path: | + v3/internal/runtime/desktop/@wailsio/runtime/dist/ + v3/internal/runtime/desktop/@wailsio/runtime/types/ + v3/internal/runtime/desktop/@wailsio/runtime/tsconfig.tsbuildinfo + + - name: Store runtime package + uses: actions/upload-artifact@v4 + with: + name: runtime-package + path: v3/internal/runtime/desktop/@wailsio/runtime/*.tgz + + test_go: + name: Run Go Tests v3 + needs: [check_approval, test_js] + runs-on: ${{ matrix.os }} + if: github.base_ref == 'v3-alpha' + strategy: + fail-fast: false + matrix: + os: [windows-latest, ubuntu-latest, macos-latest] + go-version: [1.24] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install linux dependencies (GTK3) + uses: awalsh128/cache-apt-pkgs-action@latest + if: matrix.os == 'ubuntu-latest' + with: + packages: libgtk-3-dev libwebkit2gtk-4.1-dev libwayland-dev build-essential pkg-config xvfb x11-xserver-utils at-spi2-core xdg-desktop-portal-gtk + version: 1.0 + + - name: Install linux dependencies (GTK4) - webkit-gtk6-support branch only + if: matrix.os == 'ubuntu-latest' && github.head_ref == 'feature/webkit-gtk6-support' + run: | + sudo apt-get update + sudo apt-get install -y libgtk-4-dev libwebkitgtk-6.0-dev + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: ${{ matrix.go-version }} + cache: true + cache-dependency-path: "v3/go.sum" + + - name: Install Task + uses: arduino/setup-task@v2 + with: + version: 3.x + repo-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Retrieve runtime build artifacts + uses: actions/download-artifact@v4 + with: + name: runtime-build-artifacts + path: v3/internal/runtime/desktop/@wailsio/runtime/ + + - name: Build Examples (GTK3 default) + working-directory: v3 + run: | + echo "Starting example compilation tests (GTK3)..." + task test:examples + echo "Example compilation tests (GTK3) completed successfully" + + - name: Build Examples (GTK4 experimental) + if: matrix.os == 'ubuntu-latest' && github.head_ref == 'feature/webkit-gtk6-support' + working-directory: v3 + run: | + echo "Starting example compilation tests (GTK4)..." + BUILD_TAGS=gtk4 task test:examples + echo "Example compilation tests (GTK4) completed successfully" + + - name: Run tests (mac) + if: matrix.os == 'macos-latest' + env: + CGO_LDFLAGS: -framework UniformTypeIdentifiers -mmacosx-version-min=10.13 + working-directory: v3 + run: go test -v ./... + + - name: Run tests (windows) + if: matrix.os == 'windows-latest' + working-directory: v3 + run: go test -v ./... + + - name: Run tests (ubuntu) - GTK3 default + if: matrix.os == 'ubuntu-latest' + working-directory: v3 + run: > + xvfb-run --auto-servernum + sh -c ' + dbus-update-activation-environment --systemd --all && + go test -v ./... + ' + + - name: Run tests (ubuntu) - GTK4 experimental + if: matrix.os == 'ubuntu-latest' && github.head_ref == 'feature/webkit-gtk6-support' + working-directory: v3 + # Skip all service tests that hang in CI due to GTK4 display requirements + # The services tests require a fully functional GTK4 display which xvfb cannot provide + run: > + xvfb-run --auto-servernum + sh -c ' + dbus-update-activation-environment --systemd --all && + go test -tags gtk4 -v -skip "TestService" ./... + ' + + - name: Typecheck binding generator output + working-directory: v3 + run: task generator:test:check + + cleanup: + name: Cleanup build artifacts + if: always() + needs: [test_js, test_go, test_templates] + runs-on: ubuntu-latest + steps: + - uses: geekyeggo/delete-artifact@v5 + with: + name: | + runtime-build-artifacts + runtime-package + failOnError: false + + test_templates: + name: Test Templates + needs: [test_js, test_go] + runs-on: ${{ matrix.os }} + if: github.base_ref == 'v3-alpha' + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + template: + - svelte + - svelte-ts + - vue + - vue-ts + - react + - react-ts + - preact + - preact-ts + - lit + - lit-ts + - vanilla + - vanilla-ts + go-version: [1.24] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install linux dependencies (GTK3) + uses: awalsh128/cache-apt-pkgs-action@latest + if: matrix.os == 'ubuntu-latest' + with: + packages: libgtk-3-dev libwebkit2gtk-4.1-dev libwayland-dev build-essential pkg-config + version: 1.0 + + - name: Install linux dependencies (GTK4) - webkit-gtk6-support branch only + if: matrix.os == 'ubuntu-latest' && github.head_ref == 'feature/webkit-gtk6-support' + run: | + sudo apt-get update + sudo apt-get install -y libgtk-4-dev libwebkitgtk-6.0-dev + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: ${{ matrix.go-version }} + cache: true + cache-dependency-path: "v3/go.sum" + + - name: Install Task + uses: arduino/setup-task@v2 + with: + version: 3.x + repo-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Build Wails3 CLI + working-directory: v3 + run: | + task install + wails3 doctor + + - name: Download runtime package + uses: actions/download-artifact@v4 + with: + name: runtime-package + path: wails-runtime-temp + + - name: Generate template '${{ matrix.template }}' (GTK3 default) + shell: bash + run: | + # Get absolute path - use pwd -W on Windows for native paths, pwd elsewhere + if [[ "$RUNNER_OS" == "Windows" ]]; then + RUNTIME_TGZ="$(cd wails-runtime-temp && pwd -W)/$(ls wails-runtime-temp/*.tgz | xargs basename)" + else + RUNTIME_TGZ="$(cd wails-runtime-temp && pwd)/$(ls wails-runtime-temp/*.tgz | xargs basename)" + fi + mkdir -p ./test-${{ matrix.template }} + cd ./test-${{ matrix.template }} + wails3 init -n ${{ matrix.template }} -t ${{ matrix.template }} + cd ${{ matrix.template }}/frontend + # Replace @wailsio/runtime version with local tarball + npm pkg set dependencies.@wailsio/runtime="file://$RUNTIME_TGZ" + cd .. + wails3 build + + # Note: GTK4 template builds are not tested here as wails build doesn't + # support -tags flag yet. GTK4 compilation is verified by Go tests. + + build_results: + if: ${{ always() }} + runs-on: ubuntu-latest + name: v3 Build Results + needs: [test_go, test_js, test_templates] + steps: + - run: | + go_result="${{ needs.test_go.result }}" + js_result="${{ needs.test_js.result }}" + templates_result="${{ needs.test_templates.result }}" + + if [[ $go_result == "success" || $go_result == "skipped" ]] && \ + [[ $js_result == "success" || $js_result == "skipped" ]] && \ + [[ $templates_result == "success" || $templates_result == "skipped" ]]; then + echo "All required jobs succeeded or were skipped" + exit 0 + else + echo "One or more required jobs failed" + exit 1 + fi \ No newline at end of file diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index cd9cfa42a..a6ee1a0bb 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -28,7 +28,7 @@ jobs: - uses: awalsh128/cache-apt-pkgs-action@latest if: matrix.os == 'ubuntu-24.04' with: - packages: libgtk-3-dev libwebkit2gtk-4.1-dev build-essential pkg-config + packages: libgtk-3-dev libwebkit2gtk-4.1-dev libwayland-dev build-essential pkg-config version: 1.0 - name: Setup Go @@ -126,7 +126,7 @@ jobs: - name: Install linux dependencies ( 24.04 ) if: matrix.os == 'ubuntu-24.04' - run: sudo apt-get update -y && sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev build-essential pkg-config + run: sudo apt-get update -y && sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libwayland-dev build-essential pkg-config - name: Generate & Build template '${{ matrix.template }}' if: matrix.os != 'ubuntu-24.04' diff --git a/.github/workflows/build-cross-image.yml b/.github/workflows/build-cross-image.yml new file mode 100644 index 000000000..1af95a59b --- /dev/null +++ b/.github/workflows/build-cross-image.yml @@ -0,0 +1,289 @@ +name: Build Cross-Compiler Image + +on: + workflow_dispatch: + inputs: + branch: + description: 'Branch containing Dockerfile' + required: true + default: 'v3-alpha' + sdk_version: + description: 'macOS SDK version' + required: true + default: '14.5' + zig_version: + description: 'Zig version' + required: true + default: '0.14.0' + image_version: + description: 'Image version tag' + required: true + default: 'latest' + skip_tests: + description: 'Skip cross-compilation tests' + required: false + default: 'false' + type: boolean + push: + branches: + - v3-alpha + paths: + - 'v3/internal/commands/build_assets/docker/Dockerfile.cross' + +env: + REGISTRY: ghcr.io + IMAGE_NAME: wailsapp/wails-cross + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + outputs: + image_tag: ${{ steps.vars.outputs.image_version }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ inputs.branch || github.ref }} + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set build variables + id: vars + run: | + echo "sdk_version=${{ inputs.sdk_version || '14.5' }}" >> $GITHUB_OUTPUT + echo "zig_version=${{ inputs.zig_version || '0.14.0' }}" >> $GITHUB_OUTPUT + echo "image_version=${{ inputs.image_version || 'latest' }}" >> $GITHUB_OUTPUT + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=raw,value=latest + type=raw,value=${{ steps.vars.outputs.image_version }} + type=raw,value=sdk-${{ steps.vars.outputs.sdk_version }} + + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: v3/internal/commands/build_assets/docker + file: v3/internal/commands/build_assets/docker/Dockerfile.cross + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: | + ${{ steps.meta.outputs.labels }} + io.wails.zig.version=${{ steps.vars.outputs.zig_version }} + io.wails.sdk.version=${{ steps.vars.outputs.sdk_version }} + build-args: | + ZIG_VERSION=${{ steps.vars.outputs.zig_version }} + MACOS_SDK_VERSION=${{ steps.vars.outputs.sdk_version }} + cache-from: | + type=gha + type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache + cache-to: | + type=gha,mode=max + type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max + + # Test cross-compilation using wails3 task system (3 parallel jobs) + # Runs on Linux/amd64 runner - tests actual cross-compilation only: + # - darwin: cross-platform (arm64) + # - windows: cross-platform (arm64) + # - linux: cross-architecture (arm64 from amd64 runner) + test-cross-compile: + needs: build + if: ${{ inputs.skip_tests != 'true' }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + # Darwin arm64 (Intel Macs are EOL, skip amd64) + - os: darwin + arch: arm64 + expected: "Mach-O 64-bit.*arm64" + # Windows - both archs + - os: windows + arch: amd64 + expected: "PE32.*x86-64" + - os: windows + arch: arm64 + expected: "PE32.*Aarch64" + # Linux - both archs via Docker + - os: linux + arch: amd64 + expected: "ELF 64-bit LSB.*x86-64" + - os: linux + arch: arm64 + expected: "ELF 64-bit LSB.*ARM aarch64" + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ inputs.branch || github.ref }} + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.24' + cache: false + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install Linux dev dependencies + run: | + sudo apt-get update + sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libwayland-dev + + - name: Install wails3 CLI + run: | + cd v3 + go install ./cmd/wails3 + + - name: Set up QEMU + if: matrix.os == 'linux' + uses: docker/setup-qemu-action@v3 + + - name: Log in to Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Pull and tag Docker image + run: | + # Pull both platform variants and tag them for the task system + # The task uses --platform flag which requires matching arch variant + IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.build.outputs.image_tag || 'latest' }}" + + # For Linux arm64 test, we need the arm64 variant + if [ "${{ matrix.os }}" = "linux" ] && [ "${{ matrix.arch }}" = "arm64" ]; then + docker pull --platform linux/arm64 "$IMAGE" + docker tag "$IMAGE" wails-cross + else + docker pull "$IMAGE" + docker tag "$IMAGE" wails-cross + fi + + - name: Generate wails3 test project + run: | + cd /tmp + wails3 init -n test-wails-app -t vanilla + cd test-wails-app + + # Update replace directive to use absolute path (wails3 init adds relative path) + sed -i 's|replace github.com/wailsapp/wails/v3 => .*|replace github.com/wailsapp/wails/v3 => ${{ github.workspace }}/v3|' go.mod + + - name: Cross-compile ${{ matrix.os }}/${{ matrix.arch }} via task + run: | + cd /tmp/test-wails-app + # For Linux, always use docker build to test the Docker image + if [ "${{ matrix.os }}" = "linux" ]; then + wails3 task linux:build:docker ARCH=${{ matrix.arch }} + else + wails3 task ${{ matrix.os }}:build ARCH=${{ matrix.arch }} + fi + + - name: Verify binary + run: | + cd /tmp/test-wails-app/bin + ls -la + + if [ "${{ matrix.os }}" = "windows" ]; then + BINARY="test-wails-app.exe" + else + BINARY="test-wails-app" + fi + + echo "Checking: $BINARY" + FILE_OUTPUT=$(file "$BINARY") + echo " $FILE_OUTPUT" + + if echo "$FILE_OUTPUT" | grep -qE "${{ matrix.expected }}"; then + echo " ✅ Cross-compilation verified: ${{ matrix.os }}/${{ matrix.arch }}" + else + echo " ❌ Format mismatch! Expected: ${{ matrix.expected }}" + exit 1 + fi + + - name: Check library dependencies (Linux only) + if: matrix.os == 'linux' + run: | + cd /tmp/test-wails-app/bin + + echo "## Library Dependencies" + echo "" + echo "### NEEDED libraries:" + readelf -d test-wails-app | grep NEEDED || echo "No dynamic dependencies" + echo "" + + NEEDED=$(readelf -d test-wails-app | grep NEEDED) + MISSING="" + for lib in libwebkit2gtk-4.1.so libgtk-3.so libglib-2.0.so libc.so; do + if echo "$NEEDED" | grep -q "$lib"; then + echo "✅ $lib" + else + echo "❌ $lib MISSING" + MISSING="$MISSING $lib" + fi + done + + if [ -n "$MISSING" ]; then + echo "ERROR: Missing required libraries:$MISSING" + exit 1 + fi + + # Summary job + test-summary: + needs: [build, test-cross-compile] + if: always() && inputs.skip_tests != 'true' + runs-on: ubuntu-latest + steps: + - name: Check test results + run: | + echo "## Cross-Compilation Test Results" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ "${{ needs.test-cross-compile.result }}" = "success" ]; then + echo "✅ **All Tests Passed**" >> $GITHUB_STEP_SUMMARY + else + echo "❌ **Some Tests Failed**" >> $GITHUB_STEP_SUMMARY + fi + + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Cross-Compilation Tests (from Linux/amd64 runner)" >> $GITHUB_STEP_SUMMARY + echo "| Target | Status |" >> $GITHUB_STEP_SUMMARY + echo "|--------|--------|" >> $GITHUB_STEP_SUMMARY + echo "| Darwin/arm64 | ✅ |" >> $GITHUB_STEP_SUMMARY + echo "| Windows/amd64 | ✅ |" >> $GITHUB_STEP_SUMMARY + echo "| Windows/arm64 | ✅ |" >> $GITHUB_STEP_SUMMARY + echo "| Linux/amd64 | ✅ |" >> $GITHUB_STEP_SUMMARY + echo "| Linux/arm64 | ✅ |" >> $GITHUB_STEP_SUMMARY + + # Fail if any test failed + if [ "${{ needs.test-cross-compile.result }}" != "success" ]; then + echo "" + echo "❌ Some tests failed. Check the individual job logs for details." + exit 1 + fi diff --git a/.github/workflows/changelog-validation-v3.yml b/.github/workflows/changelog-validation-v3.yml new file mode 100644 index 000000000..8d4b97726 --- /dev/null +++ b/.github/workflows/changelog-validation-v3.yml @@ -0,0 +1,74 @@ +name: Changelog Validation (v3) + +on: + pull_request: + branches: [ v3-alpha ] + workflow_dispatch: + inputs: + pr_number: + description: 'PR number to validate (for manual testing)' + required: true + type: string + +jobs: + validate-changelog: + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' || github.event.inputs.pr_number + + permissions: + contents: write + pull-requests: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha || format('refs/pull/{0}/head', github.event.inputs.pr_number) }} + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Go + uses: actions/setup-go@v4 + with: + go-version: '1.23' + + - name: Get PR information + id: pr_info + run: | + if [ "${{ github.event_name }}" = "pull_request" ]; then + echo "pr_number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT + echo "base_ref=${{ github.event.pull_request.base.ref }}" >> $GITHUB_OUTPUT + else + echo "pr_number=${{ github.event.inputs.pr_number }}" >> $GITHUB_OUTPUT + echo "base_ref=v3-alpha" >> $GITHUB_OUTPUT + fi + + - name: Check if changelog was modified + id: changelog_check + run: | + git fetch origin ${{ steps.pr_info.outputs.base_ref }} + if git diff --name-only origin/${{ steps.pr_info.outputs.base_ref }}..HEAD | grep -q "v3/UNRELEASED_CHANGELOG.md"; then + echo "changelog_modified=true" >> $GITHUB_OUTPUT + echo "✅ UNRELEASED_CHANGELOG.md was modified in this PR" + else + echo "changelog_modified=false" >> $GITHUB_OUTPUT + echo "⚠️ UNRELEASED_CHANGELOG.md was not modified" + fi + + - name: Comment on PR about missing changelog + if: steps.changelog_check.outputs.changelog_modified == 'false' && github.event_name == 'pull_request' + uses: actions/github-script@v7 + with: + script: | + const author = context.payload.pull_request.user.login; + const message = '## ⚠️ Missing Changelog Update\n\n' + + `Hi @${author}, please update \`v3/UNRELEASED_CHANGELOG.md\` with a description of your changes.\n\n` + + 'This helps us keep track of changes for the next release.'; + + await github.rest.issues.createComment({ + issue_number: ${{ steps.pr_info.outputs.pr_number }}, + owner: context.repo.owner, + repo: context.repo.repo, + body: message + }); + diff --git a/.github/workflows/cross-compile-test-v3.yml b/.github/workflows/cross-compile-test-v3.yml new file mode 100644 index 000000000..2d7c3dbcb --- /dev/null +++ b/.github/workflows/cross-compile-test-v3.yml @@ -0,0 +1,135 @@ +name: Cross-Compile Test v3 + +on: + pull_request_review: + types: [submitted] + branches: + - v3-alpha + workflow_dispatch: + inputs: + pr_number: + description: 'PR number to test (optional, uses current branch if not specified)' + required: false + type: string + +jobs: + check_approval: + name: Check PR Approval + runs-on: ubuntu-latest + if: github.event_name == 'workflow_dispatch' || github.event.review.state == 'approved' + outputs: + approved: ${{ steps.check.outputs.approved }} + steps: + - name: Check if PR is approved or manual dispatch + id: check + run: | + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + echo "Manual dispatch, proceeding with cross-compile tests" + else + echo "PR approved, proceeding with cross-compile tests" + fi + echo "approved=true" >> $GITHUB_OUTPUT + + cross_compile: + name: Cross-Compile (${{ matrix.target_os }}/${{ matrix.target_arch }}) + needs: check_approval + runs-on: ${{ matrix.runner }} + if: needs.check_approval.outputs.approved == 'true' + strategy: + fail-fast: false + matrix: + include: + - target_os: darwin + target_arch: arm64 + runner: ubuntu-latest + - target_os: darwin + target_arch: amd64 + runner: ubuntu-latest + - target_os: linux + target_arch: arm64 + runner: ubuntu-24.04-arm # Native ARM64 runner - much faster than QEMU + - target_os: linux + target_arch: amd64 + runner: ubuntu-latest + - target_os: windows + target_arch: arm64 + runner: ubuntu-latest + - target_os: windows + target_arch: amd64 + runner: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Checkout PR (if specified) + if: github.event_name == 'workflow_dispatch' && inputs.pr_number != '' + run: gh pr checkout ${{ inputs.pr_number }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: '1.25' + cache: true + cache-dependency-path: "v3/go.sum" + + - name: Install Task + uses: arduino/setup-task@v2 + with: + version: 3.x + repo-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Install Linux dependencies + uses: awalsh128/cache-apt-pkgs-action@latest + with: + packages: libgtk-3-dev libwebkit2gtk-4.1-dev libwayland-dev build-essential pkg-config + version: 1.0 + + - name: Install Wails3 CLI + working-directory: v3 + run: | + go install ./cmd/wails3 + wails3 version + + - name: Create test project + run: | + mkdir -p test-cross-compile + cd test-cross-compile + wails3 init -n crosstest -t vanilla + + - name: Setup Docker cross-compile image + working-directory: test-cross-compile/crosstest + run: task common:setup:docker + + - name: Fix replace directive for Docker build + working-directory: test-cross-compile/crosstest + run: | + # Change the replace directive to use absolute path that matches Docker mount + go mod edit -dropreplace github.com/wailsapp/wails/v3 + go mod edit -replace github.com/wailsapp/wails/v3=${{ github.workspace }}/v3 + + - name: Cross-compile for ${{ matrix.target_os }}/${{ matrix.target_arch }} + working-directory: test-cross-compile/crosstest + run: | + echo "Cross-compiling for ${{ matrix.target_os }}/${{ matrix.target_arch }}..." + task ${{ matrix.target_os }}:build ARCH=${{ matrix.target_arch }} + echo "Cross-compilation successful!" + ls -la bin/ + + cross_compile_results: + if: ${{ always() }} + runs-on: ubuntu-latest + name: Cross-Compile Results + needs: [cross_compile] + steps: + - run: | + result="${{ needs.cross_compile.result }}" + echo "Cross-compile result: $result" + if [[ $result == "success" || $result == "skipped" ]]; then + echo "Cross-compile tests passed (or were skipped)!" + exit 0 + else + echo "One or more cross-compile tests failed" + exit 1 + fi diff --git a/.github/workflows/generate-sponsor-image.yml b/.github/workflows/generate-sponsor-image.yml index b8bb0c4ac..585d7e19f 100644 --- a/.github/workflows/generate-sponsor-image.yml +++ b/.github/workflows/generate-sponsor-image.yml @@ -29,7 +29,12 @@ jobs: with: commit-message: "chore: update sponsors.svg" add-paths: "website/static/img/sponsors.svg" - title: Update Sponsor Image - body: Generated new image + title: "chore: update sponsors.svg" + body: | + Auto-generated by the sponsor image workflow + + [skip ci] [skip actions] branch: update-sponsors + base: master delete-branch: true + draft: false diff --git a/.github/workflows/issue-triage-automation.yml b/.github/workflows/issue-triage-automation.yml new file mode 100644 index 000000000..4a827d527 --- /dev/null +++ b/.github/workflows/issue-triage-automation.yml @@ -0,0 +1,77 @@ +name: Issue Triage Automation + +on: + issues: + types: [opened, reopened, labeled, unlabeled] + +jobs: + triage: + runs-on: ubuntu-latest + permissions: + issues: write + contents: read + steps: + # Request more info for unclear bug reports + - name: Request more info + uses: actions/github-script@v6 + if: | + contains(github.event.issue.labels.*.name, 'bug') && + !contains(github.event.issue.body, 'wails doctor') && + !contains(github.event.issue.body, 'reproduction') + with: + script: | + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: `👋 Thanks for reporting this issue! To help us investigate, could you please: + + 1. Add the output of \`wails doctor\` if not already included + 2. Provide clear steps to reproduce the issue + 3. If possible, create a minimal reproduction of the issue + + This will help us resolve your issue much faster. Thank you!` + }); + github.rest.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ['awaiting feedback'] + }); + + # Prioritize security issues + - name: Prioritize security issues + uses: actions/github-script@v6 + if: contains(github.event.issue.labels.*.name, 'security') + with: + script: | + github.rest.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ['high-priority'] + }); + + # Tag version-specific issues for project boards + - name: Add to v2 project + uses: actions/github-script@v6 + if: | + contains(github.event.issue.labels.*.name, 'v2-only') && + !contains(github.event.issue.labels.*.name, 'v3-alpha') + with: + script: | + // Replace PROJECT_ID with your actual GitHub project ID + // This is a placeholder as the actual implementation would require + // GraphQL API calls to add to a project board + console.log('Would add to v2 project board'); + + # Tag version-specific issues for project boards + - name: Add to v3 project + uses: actions/github-script@v6 + if: contains(github.event.issue.labels.*.name, 'v3-alpha') + with: + script: | + // Replace PROJECT_ID with your actual GitHub project ID + // This is a placeholder as the actual implementation would require + // GraphQL API calls to add to a project board + console.log('Would add to v3 project board'); diff --git a/.github/workflows/nightly-release-v3.yml b/.github/workflows/nightly-release-v3.yml new file mode 100644 index 000000000..ae56ba7bc --- /dev/null +++ b/.github/workflows/nightly-release-v3.yml @@ -0,0 +1,210 @@ +name: Nightly Release v3-alpha + +on: + schedule: + - cron: '0 2 * * *' # 2 AM UTC daily + workflow_dispatch: + inputs: + force_release: + description: 'Force release even if no changes detected' + required: false + default: false + type: boolean + dry_run: + description: 'Run in dry-run mode (no actual release)' + required: false + default: true + type: boolean + +jobs: + nightly-release: + runs-on: ubuntu-latest + + permissions: + contents: write + pull-requests: read + actions: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: v3-alpha + fetch-depth: 0 + token: ${{ secrets.WAILS_REPO_TOKEN || github.token }} + + - name: Setup Go + uses: actions/setup-go@v4 + with: + go-version: '1.24' + cache: true + cache-dependency-path: 'v3/go.sum' + + - name: Install Task + uses: arduino/setup-task@v2 + with: + version: 3.x + repo-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Git + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + + # Configure git to use the token for authentication + git config --global url."https://x-access-token:${{ secrets.WAILS_REPO_TOKEN || github.token }}@github.com/".insteadOf "https://github.com/" + + - name: Check for existing release tag + id: check_tag + run: | + if git describe --tags --exact-match HEAD 2>/dev/null; then + echo "has_tag=true" >> $GITHUB_OUTPUT + echo "tag=$(git describe --tags --exact-match HEAD)" >> $GITHUB_OUTPUT + else + echo "has_tag=false" >> $GITHUB_OUTPUT + echo "tag=" >> $GITHUB_OUTPUT + fi + + - name: Check for unreleased changelog content + id: changelog_check + run: | + echo "🔍 Checking UNRELEASED_CHANGELOG.md for content..." + + # Run the release script in check mode to see if there's content + cd v3/tasks/release + + # Use the release script itself to check for content + if go run release.go --check-only 2>/dev/null; then + echo "has_unreleased_content=true" >> $GITHUB_OUTPUT + echo "✅ Found unreleased changelog content" + else + echo "has_unreleased_content=false" >> $GITHUB_OUTPUT + echo "ℹ️ No unreleased changelog content found" + fi + + - name: Quick change detection and early exit + id: quick_check + run: | + echo "🔍 Quick check for changes to determine if we should continue..." + + # First check if we have unreleased changelog content + if [ "${{ steps.changelog_check.outputs.has_unreleased_content }}" == "true" ]; then + echo "✅ Found unreleased changelog content, proceeding with release" + echo "has_changes=true" >> $GITHUB_OUTPUT + echo "should_continue=true" >> $GITHUB_OUTPUT + echo "reason=Found unreleased changelog content" >> $GITHUB_OUTPUT + exit 0 + fi + + # If no unreleased changelog content, check for git changes as fallback + echo "No unreleased changelog content found, checking for git changes..." + + # Check if current commit has a release tag + if git describe --tags --exact-match HEAD 2>/dev/null; then + CURRENT_TAG=$(git describe --tags --exact-match HEAD) + echo "Current commit has release tag: $CURRENT_TAG" + + # For tagged commits, check if there are changes since the tag + COMMIT_COUNT=$(git rev-list ${CURRENT_TAG}..HEAD --count) + if [ "$COMMIT_COUNT" -eq 0 ]; then + echo "has_changes=false" >> $GITHUB_OUTPUT + echo "should_continue=false" >> $GITHUB_OUTPUT + echo "reason=No changes since existing tag $CURRENT_TAG and no unreleased changelog content" >> $GITHUB_OUTPUT + else + echo "has_changes=true" >> $GITHUB_OUTPUT + echo "should_continue=true" >> $GITHUB_OUTPUT + fi + else + # No current tag, check against latest release + LATEST_TAG=$(git tag --list "v3.0.0-alpha.*" | sort -V | tail -1) + if [ -z "$LATEST_TAG" ]; then + echo "No previous release found, proceeding with release" + echo "has_changes=true" >> $GITHUB_OUTPUT + echo "should_continue=true" >> $GITHUB_OUTPUT + else + COMMIT_COUNT=$(git rev-list ${LATEST_TAG}..HEAD --count) + if [ "$COMMIT_COUNT" -gt 0 ]; then + echo "Found $COMMIT_COUNT commits since $LATEST_TAG" + echo "has_changes=true" >> $GITHUB_OUTPUT + echo "should_continue=true" >> $GITHUB_OUTPUT + else + echo "has_changes=false" >> $GITHUB_OUTPUT + echo "should_continue=false" >> $GITHUB_OUTPUT + echo "reason=No changes since latest release $LATEST_TAG and no unreleased changelog content" >> $GITHUB_OUTPUT + fi + fi + fi + + - name: Early exit - No changes detected + if: | + steps.quick_check.outputs.should_continue == 'false' && + github.event.inputs.force_release != 'true' + run: | + echo "🛑 EARLY EXIT: ${{ steps.quick_check.outputs.reason }}" + echo "" + echo "ℹ️ No changes detected since last release and force_release is not enabled." + echo " Workflow will exit early to save resources." + echo "" + echo " To force a release anyway, run this workflow with 'force_release=true'" + echo "" + echo "## 🛑 Early Exit Summary" >> $GITHUB_STEP_SUMMARY + echo "**Reason:** ${{ steps.quick_check.outputs.reason }}" >> $GITHUB_STEP_SUMMARY + echo "**Action:** Workflow exited early to save resources" >> $GITHUB_STEP_SUMMARY + echo "**Force Release:** Set 'force_release=true' to override this behavior" >> $GITHUB_STEP_SUMMARY + exit 0 + + - name: Continue with release process + if: | + steps.quick_check.outputs.should_continue == 'true' || + github.event.inputs.force_release == 'true' + run: | + echo "✅ Proceeding with release process..." + if [ "${{ github.event.inputs.force_release }}" == "true" ]; then + echo "🔨 FORCE RELEASE: Overriding change detection" + fi + + - name: Run release script + id: release + if: | + steps.quick_check.outputs.should_continue == 'true' || + github.event.inputs.force_release == 'true' + env: + WAILS_REPO_TOKEN: ${{ secrets.WAILS_REPO_TOKEN || github.token }} + GITHUB_TOKEN: ${{ secrets.WAILS_REPO_TOKEN || github.token }} + run: | + cd v3/tasks/release + ARGS=() + if [ "${{ github.event.inputs.dry_run }}" == "true" ]; then + ARGS+=(--dry-run) + fi + go run release.go "${ARGS[@]}" + + - name: Summary + if: always() + run: | + if [ "${{ github.event.inputs.dry_run }}" == "true" ]; then + echo "## 🧪 DRY RUN Release Summary" >> $GITHUB_STEP_SUMMARY + else + echo "## 🚀 Nightly Release Summary" >> $GITHUB_STEP_SUMMARY + fi + echo "================================" >> $GITHUB_STEP_SUMMARY + + if [ -n "${{ steps.release.outputs.release_version }}" ]; then + echo "- **Version:** ${{ steps.release.outputs.release_version }}" >> $GITHUB_STEP_SUMMARY + echo "- **Tag:** ${{ steps.release.outputs.release_tag }}" >> $GITHUB_STEP_SUMMARY + echo "- **Status:** ${{ steps.release.outcome == 'success' && '✅ Success' || '⚠️ Failed' }}" >> $GITHUB_STEP_SUMMARY + echo "- **Mode:** ${{ steps.release.outputs.release_dry_run == 'true' && '🧪 Dry Run' || '🚀 Live release' }}" >> $GITHUB_STEP_SUMMARY + if [ -n "${{ steps.release.outputs.release_url }}" ]; then + echo "- **Release URL:** ${{ steps.release.outputs.release_url }}" >> $GITHUB_STEP_SUMMARY + fi + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Changelog" >> $GITHUB_STEP_SUMMARY + if [ "${{ steps.changelog_check.outputs.has_unreleased_content }}" == "true" ]; then + echo "✅ Unreleased changelog processed and reset." >> $GITHUB_STEP_SUMMARY + else + echo "ℹ️ No unreleased changelog content detected." >> $GITHUB_STEP_SUMMARY + fi + else + echo "- Release script did not run (skipped or failed before execution)." >> $GITHUB_STEP_SUMMARY + fi + diff --git a/.github/workflows/pr.yml b/.github/workflows/pr-master.yml similarity index 64% rename from .github/workflows/pr.yml rename to .github/workflows/pr-master.yml index c70050276..1de533199 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr-master.yml @@ -1,20 +1,23 @@ -name: PR Checks +name: PR Checks (master) on: pull_request: + branches: + - master pull_request_review: types: [submitted] - + branches: + - master jobs: check_docs: name: Check Docs - if: ${{github.repository == 'wailsapp/wails' && contains(github.head_ref,'feature/')}} + if: ${{github.repository == 'wailsapp/wails' && github.base_ref == 'master'}} runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Verify Changed files - uses: tj-actions/verify-changed-files@v17 + uses: step-security/changed-files@3dbe17c78367e7d60f00d78ae6781a35be47b4a1 # v45.0.1 id: verify-changed-files with: files: | @@ -26,49 +29,30 @@ jobs: run: | echo "::warning::Feature branch does not contain any changes to the website." -# lint_go: -# name: Run Go Linters -# runs-on: ubuntu-latest -# steps: -# - name: Checkout code -# uses: actions/checkout@v4 -# -# - name: Setup Go -# uses: actions/setup-go@v4 -# with: -# go-version: "1.21" -# -# - name: Update go modules -# working-directory: ./v2 -# run: go mod tidy -# -# - name: Run Linter -# uses: golangci/golangci-lint-action@v3 -# with: -# version: v1.54 -# working-directory: ./v2 -# args: --timeout=10m0s --config ./.golangci.yml - test_go: name: Run Go Tests runs-on: ${{ matrix.os }} - if: github.event.review.state == 'approved' + if: > + github.event.review.state == 'approved' && + github.repository == 'wailsapp/wails' && + github.base_ref == 'master' && + github.event.pull_request.head.ref != 'update-sponsors' strategy: matrix: os: [ubuntu-22.04, windows-latest, macos-latest, ubuntu-24.04] - go-version: ['1.21'] + go-version: ['1.23'] steps: - name: Checkout code uses: actions/checkout@v3 - - name: Install linux dependencies ( 22.04 ) + - name: Install linux dependencies (22.04) if: matrix.os == 'ubuntu-22.04' run: sudo apt-get update -y && sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev build-essential pkg-config - - name: Install linux dependencies ( 24.04 ) + - name: Install linux dependencies (24.04) if: matrix.os == 'ubuntu-24.04' - run: sudo apt-get update -y && sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev build-essential pkg-config + run: sudo apt-get update -y && sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libwayland-dev build-essential pkg-config - name: Setup Go uses: actions/setup-go@v3 diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml new file mode 100644 index 000000000..3ebd2f282 --- /dev/null +++ b/.github/workflows/publish-npm.yml @@ -0,0 +1,119 @@ +on: + push: + branches: ['v3-alpha'] + workflow_dispatch: + +concurrency: + group: publish-npm-v3 + cancel-in-progress: true + +jobs: + detect: + name: Detect committed changes + if: github.event_name != 'workflow_dispatch' + outputs: + changed: ${{ steps.package-json-changes.outputs.any_modified == 'true' || steps.source-changes.outputs.any_modified == 'true' }} + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ github.sha }} + persist-credentials: 'true' + + - name: Detect committed package.json changes + id: package-json-changes + uses: step-security/changed-files@3dbe17c78367e7d60f00d78ae6781a35be47b4a1 # v45.0.1 + with: + files: | + v3/internal/runtime/desktop/@wailsio/runtime/package.json + v3/internal/runtime/desktop/@wailsio/runtime/package-lock.json + + - name: Detect committed source changes + if: >- + steps.package-json-changes.outputs.any_modified != 'true' + id: source-changes + uses: step-security/changed-files@3dbe17c78367e7d60f00d78ae6781a35be47b4a1 # v45.0.1 + with: + files: | + v3/internal/runtime/Taskfile.yaml + v3/internal/runtime/desktop/@wailsio/compiled/main.js + v3/internal/runtime/desktop/@wailsio/runtime/tsconfig.json + v3/internal/runtime/desktop/@wailsio/runtime/src/** + v3/pkg/events/events.txt + v3/tasks/events/** + + rebuild_and_publish: + name: Rebuild and publish + needs: [detect] + if: >- + !failure() && !cancelled() + && (github.event_name == 'workflow_dispatch' || needs.detect.outputs.changed == 'true') + runs-on: ubuntu-latest + permissions: + contents: write + actions: read + pull-requests: read + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: 'v3-alpha' + token: ${{ secrets.WAILS_REPO_TOKEN || github.token }} + + - name: Configure git + run: | + git config --global user.email "github-actions@github.com" + git config --global user.name "GitHub Actions" + git config --global url."https://x-access-token:${{ secrets.WAILS_REPO_TOKEN || github.token }}@github.com/".insteadOf "https://github.com/" + + - name: Install Task + uses: arduino/setup-task@v2 + with: + version: 3.x + repo-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Use Node.js 20 + uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: Install dependencies + working-directory: v3/internal/runtime/desktop/@wailsio/runtime + run: | + npm ci + npx --yes esbuild@latest --version + + - name: Clean build artifacts + working-directory: v3/internal/runtime/desktop/@wailsio/runtime + run: npm run clean + + - name: Build bundled runtime + working-directory: v3 + run: task runtime:build + + - name: Test+Build npm package + working-directory: v3/internal/runtime/desktop/@wailsio/runtime + run: | + npm test + npm run build + + - name: Bump version + id: bump-version + working-directory: v3/internal/runtime/desktop/@wailsio/runtime + run: | + echo "version=$(npm --no-git-tag-version --force version prerelease)" >> "$GITHUB_OUTPUT" + + - name: Commit changes + run: | + git add . + git commit -m "[skip ci] Publish @wailsio/runtime ${{ steps.bump-version.outputs.version }}" + git push + + - name: Publish npm package + uses: JS-DevTools/npm-publish@v3 + with: + package: v3/internal/runtime/desktop/@wailsio/runtime + access: public + token: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index 453e4cb85..a59818660 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -5,6 +5,7 @@ on: branches: - main - master + - v3-alpha paths: - .github/workflows/semgrep.yml schedule: @@ -14,7 +15,7 @@ name: Semgrep jobs: semgrep: name: semgrep/ci - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 env: SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} container: diff --git a/.github/workflows/stale-issues.yml b/.github/workflows/stale-issues.yml new file mode 100644 index 000000000..c4ffd25fe --- /dev/null +++ b/.github/workflows/stale-issues.yml @@ -0,0 +1,57 @@ +name: Mark and Close Stale Issues + +on: + schedule: + - cron: '0 1 * * *' # Run at 1 AM UTC every day + workflow_dispatch: # Allow manual triggering + +jobs: + stale: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + + steps: + - uses: actions/stale@v9 + with: + # General settings + repo-token: ${{ secrets.GITHUB_TOKEN }} + days-before-stale: 45 + days-before-close: 10 + stale-issue-label: 'stale' + operations-per-run: 250 # Increased from 50 to 250 + + # Issue specific settings + stale-issue-message: | + This issue has been automatically marked as stale because it has not had recent activity. + It will be closed if no further activity occurs within the next 10 days. + + If this issue is still relevant, please add a comment to keep it open. + Thank you for your contributions. + + close-issue-message: | + This issue has been automatically closed due to lack of activity. + Please feel free to reopen it if it's still relevant. + + # PR specific settings - We will not mark PRs as stale + days-before-pr-stale: -1 # Disable PR staling + days-before-pr-close: -1 # Disable PR closing + + # Exemptions + exempt-issue-labels: 'pinned,security,onhold,inprogress,Selected For Development,bug,enhancement,v3-alpha,high-priority' + exempt-all-issue-milestones: true + exempt-all-issue-assignees: true + + # Protection for existing issues + exempt-issue-created-before: '2024-01-01T00:00:00Z' + start-date: '2025-06-01T00:00:00Z' # Don't start checking until June 1, 2025 + + # Only process issues, not PRs + only-labels: '' + any-of-labels: '' + remove-stale-when-updated: true + + # Debug options + debug-only: false # Set to true to test without actually marking issues + ascending: true # Process older issues first diff --git a/.github/workflows/test-nightly-releases.yml b/.github/workflows/test-nightly-releases.yml new file mode 100644 index 000000000..1e6f12a69 --- /dev/null +++ b/.github/workflows/test-nightly-releases.yml @@ -0,0 +1,218 @@ +name: Test Nightly Releases (Dry Run) +permissions: + contents: read + +on: + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no actual releases)' + required: false + default: true + type: boolean + test_branch: + description: 'Branch to test against' + required: false + default: 'master' + type: string + +env: + GO_VERSION: '1.24' + +jobs: + test-permissions: + name: Test Release Permissions + runs-on: ubuntu-latest + outputs: + authorized: ${{ steps.check.outputs.authorized }} + steps: + - name: Check if user is authorized + id: check + run: | + # Test authorization logic + AUTHORIZED_USERS="leaanthony" + + if [[ "$AUTHORIZED_USERS" == *"${{ github.actor }}"* ]]; then + echo "✅ User ${{ github.actor }} is authorized" + echo "authorized=true" >> $GITHUB_OUTPUT + else + echo "❌ User ${{ github.actor }} is not authorized" + echo "authorized=false" >> $GITHUB_OUTPUT + fi + + test-changelog-extraction: + name: Test Changelog Extraction + runs-on: ubuntu-latest + needs: test-permissions + if: needs.test-permissions.outputs.authorized == 'true' + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.test_branch }} + fetch-depth: 0 + + - name: Test v2 changelog extraction + run: | + echo "🧪 Testing v2 changelog extraction..." + CHANGELOG_FILE="website/src/pages/changelog.mdx" + + if [ ! -f "$CHANGELOG_FILE" ]; then + echo "❌ v2 changelog file not found" + exit 1 + fi + + # Extract unreleased section + awk ' + /^## \[Unreleased\]/ { found=1; next } + found && /^## / { exit } + found && !/^$/ { print } + ' $CHANGELOG_FILE > v2_release_notes.md + + echo "📝 v2 changelog content (first 10 lines):" + head -10 v2_release_notes.md || echo "No content found" + echo "Total lines: $(wc -l < v2_release_notes.md)" + + - name: Test v3 changelog extraction (if accessible) + run: | + echo "🧪 Testing v3 changelog extraction..." + + if git show v3-alpha:docs/src/content/docs/changelog.mdx > /dev/null 2>&1; then + echo "✅ v3 changelog accessible" + + git show v3-alpha:docs/src/content/docs/changelog.mdx | awk ' + /^## \[Unreleased\]/ { found=1; next } + found && /^## / { exit } + found && !/^$/ { print } + ' > v3_release_notes.md + + echo "📝 v3 changelog content (first 10 lines):" + head -10 v3_release_notes.md || echo "No content found" + echo "Total lines: $(wc -l < v3_release_notes.md)" + else + echo "⚠️ v3 changelog not accessible from current context" + fi + + test-version-detection: + name: Test Version Detection + runs-on: ubuntu-latest + needs: test-permissions + if: needs.test-permissions.outputs.authorized == 'true' + outputs: + v2_current_version: ${{ steps.versions.outputs.v2_current }} + v2_next_patch: ${{ steps.versions.outputs.v2_next_patch }} + v2_next_minor: ${{ steps.versions.outputs.v2_next_minor }} + v2_next_major: ${{ steps.versions.outputs.v2_next_major }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Test version detection logic + id: versions + run: | + echo "🧪 Testing version detection..." + + # Test v2 version parsing + if [ -f "v2/cmd/wails/internal/version.txt" ]; then + CURRENT_V2=$(cat v2/cmd/wails/internal/version.txt | sed 's/^v//') + echo "Current v2 version: v$CURRENT_V2" + echo "v2_current=v$CURRENT_V2" >> $GITHUB_OUTPUT + + # Parse and increment + IFS='.' read -ra VERSION_PARTS <<< "$CURRENT_V2" + MAJOR=${VERSION_PARTS[0]} + MINOR=${VERSION_PARTS[1]} + PATCH=${VERSION_PARTS[2]} + + PATCH_VERSION="v$MAJOR.$MINOR.$((PATCH + 1))" + MINOR_VERSION="v$MAJOR.$((MINOR + 1)).0" + MAJOR_VERSION="v$((MAJOR + 1)).0.0" + + echo "v2_next_patch=$PATCH_VERSION" >> $GITHUB_OUTPUT + echo "v2_next_minor=$MINOR_VERSION" >> $GITHUB_OUTPUT + echo "v2_next_major=$MAJOR_VERSION" >> $GITHUB_OUTPUT + + echo "✅ Patch: v$CURRENT_V2 → $PATCH_VERSION" + echo "✅ Minor: v$CURRENT_V2 → $MINOR_VERSION" + echo "✅ Major: v$CURRENT_V2 → $MAJOR_VERSION" + else + echo "❌ v2 version file not found" + fi + + test-commit-analysis: + name: Test Commit Analysis + runs-on: ubuntu-latest + needs: test-permissions + if: needs.test-permissions.outputs.authorized == 'true' + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Test commit analysis + run: | + echo "🧪 Testing commit analysis..." + + # Get recent commits for testing + echo "Recent commits:" + git log --oneline -10 + + # Test conventional commit detection + RECENT_COMMITS=$(git log --oneline --since="7 days ago") + echo "Commits from last 7 days:" + echo "$RECENT_COMMITS" + + # Analyze for release type + RELEASE_TYPE="patch" + if echo "$RECENT_COMMITS" | grep -q "feat!\|fix!\|BREAKING CHANGE:"; then + RELEASE_TYPE="major" + elif echo "$RECENT_COMMITS" | grep -q "feat\|BREAKING CHANGE"; then + RELEASE_TYPE="minor" + fi + + echo "✅ Detected release type: $RELEASE_TYPE" + + test-summary: + name: Test Summary + runs-on: ubuntu-latest + needs: [test-permissions, test-changelog-extraction, test-version-detection, test-commit-analysis] + if: always() + steps: + - name: Print test results + run: | + echo "# 🧪 Nightly Release Workflow Test Results" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ "${{ needs.test-permissions.result }}" == "success" ]; then + echo "✅ **Permissions Test**: Passed" >> $GITHUB_STEP_SUMMARY + else + echo "❌ **Permissions Test**: Failed" >> $GITHUB_STEP_SUMMARY + fi + + if [ "${{ needs.test-changelog-extraction.result }}" == "success" ]; then + echo "✅ **Changelog Extraction**: Passed" >> $GITHUB_STEP_SUMMARY + else + echo "❌ **Changelog Extraction**: Failed" >> $GITHUB_STEP_SUMMARY + fi + + if [ "${{ needs.test-version-detection.result }}" == "success" ]; then + echo "✅ **Version Detection**: Passed" >> $GITHUB_STEP_SUMMARY + echo " - Current v2: ${{ needs.test-version-detection.outputs.v2_current_version }}" >> $GITHUB_STEP_SUMMARY + echo " - Next patch: ${{ needs.test-version-detection.outputs.v2_next_patch }}" >> $GITHUB_STEP_SUMMARY + echo " - Next minor: ${{ needs.test-version-detection.outputs.v2_next_minor }}" >> $GITHUB_STEP_SUMMARY + echo " - Next major: ${{ needs.test-version-detection.outputs.v2_next_major }}" >> $GITHUB_STEP_SUMMARY + else + echo "❌ **Version Detection**: Failed" >> $GITHUB_STEP_SUMMARY + fi + + if [ "${{ needs.test-commit-analysis.result }}" == "success" ]; then + echo "✅ **Commit Analysis**: Passed" >> $GITHUB_STEP_SUMMARY + else + echo "❌ **Commit Analysis**: Failed" >> $GITHUB_STEP_SUMMARY + fi + + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Note**: This was a dry-run test. No actual releases were created." >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/.github/workflows/test-simple.yml b/.github/workflows/test-simple.yml new file mode 100644 index 000000000..8c4c88295 --- /dev/null +++ b/.github/workflows/test-simple.yml @@ -0,0 +1,11 @@ +name: Test Simple + +on: + workflow_dispatch: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Test + run: echo "Hello World" \ No newline at end of file diff --git a/.github/workflows/unreleased-changelog-trigger.yml b/.github/workflows/unreleased-changelog-trigger.yml new file mode 100644 index 000000000..381023052 --- /dev/null +++ b/.github/workflows/unreleased-changelog-trigger.yml @@ -0,0 +1,128 @@ +name: Auto Release on Changelog Update + +on: + push: + branches: + - v3-alpha + paths: + - 'v3/UNRELEASED_CHANGELOG.md' + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no actual release)' + required: false + default: false + type: boolean + +jobs: + check-permissions: + name: Check Release Permissions + runs-on: ubuntu-latest + outputs: + authorized: ${{ steps.check.outputs.authorized }} + steps: + - name: Check if user is authorized for releases + id: check + run: | + # Only allow specific users to trigger releases + AUTHORIZED_USERS="leaanthony" + + if [[ "$AUTHORIZED_USERS" == *"${{ github.actor }}"* ]]; then + echo "✅ User ${{ github.actor }} is authorized for releases" + echo "authorized=true" >> $GITHUB_OUTPUT + else + echo "❌ User ${{ github.actor }} is not authorized for releases" + echo "authorized=false" >> $GITHUB_OUTPUT + fi + + trigger-release: + name: Trigger v3-alpha Release + permissions: + contents: read + runs-on: ubuntu-latest + needs: check-permissions + if: needs.check-permissions.outputs.authorized == 'true' + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: v3-alpha + fetch-depth: 0 + token: ${{ secrets.WAILS_REPO_TOKEN || github.token }} + + - name: Check for unreleased changelog content + id: changelog_check + run: | + echo "🔍 Checking UNRELEASED_CHANGELOG.md for content..." + + cd v3 + # Check if UNRELEASED_CHANGELOG.md has actual content beyond the template + if [ -f "UNRELEASED_CHANGELOG.md" ]; then + # Use a simple check for actual content (bullet points starting with -) + CONTENT_LINES=$(grep -E "^\s*-\s+[^[:space:]]" UNRELEASED_CHANGELOG.md | wc -l) + if [ "$CONTENT_LINES" -gt 0 ]; then + echo "✅ Found $CONTENT_LINES content lines in UNRELEASED_CHANGELOG.md" + echo "has_content=true" >> $GITHUB_OUTPUT + else + echo "ℹ️ No actual content found in UNRELEASED_CHANGELOG.md" + echo "has_content=false" >> $GITHUB_OUTPUT + fi + else + echo "❌ UNRELEASED_CHANGELOG.md not found" + echo "has_content=false" >> $GITHUB_OUTPUT + fi + + - name: Trigger nightly release workflow + if: steps.changelog_check.outputs.has_content == 'true' + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.WAILS_REPO_TOKEN || github.token }} + script: | + const response = await github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: 'nightly-release-v3.yml', + ref: 'v3-alpha', + inputs: { + force_release: 'true', + dry_run: '${{ github.event.inputs.dry_run || "false" }}' + } + }); + + console.log('🚀 Successfully triggered nightly release workflow'); + console.log(`Workflow dispatch response status: ${response.status}`); + + // Create a summary + core.summary + .addHeading('🚀 Auto Release Triggered') + .addRaw('The v3-alpha release workflow has been automatically triggered due to changes in UNRELEASED_CHANGELOG.md') + .addTable([ + [{data: 'Trigger', header: true}, {data: 'Value', header: true}], + ['Repository', context.repo.repo], + ['Branch', 'v3-alpha'], + ['Actor', context.actor], + ['Dry Run', '${{ github.event.inputs.dry_run || "false" }}'], + ['Force Release', 'true'] + ]) + .addRaw('\n---\n*This release was automatically triggered by the unreleased-changelog-trigger workflow*') + .write(); + + - name: No content found + if: steps.changelog_check.outputs.has_content == 'false' + run: | + echo "ℹ️ No content found in UNRELEASED_CHANGELOG.md, skipping release trigger" + echo "## ℹ️ No Release Triggered" >> $GITHUB_STEP_SUMMARY + echo "**Reason:** UNRELEASED_CHANGELOG.md does not contain actual changelog content" >> $GITHUB_STEP_SUMMARY + echo "**Action:** No release workflow was triggered" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "To trigger a release, add actual changelog entries to the UNRELEASED_CHANGELOG.md file." >> $GITHUB_STEP_SUMMARY + + - name: Unauthorized user + if: needs.check-permissions.outputs.authorized == 'false' + run: | + echo "❌ User ${{ github.actor }} is not authorized to trigger releases" + echo "## ❌ Unauthorized Release Attempt" >> $GITHUB_STEP_SUMMARY + echo "**User:** ${{ github.actor }}" >> $GITHUB_STEP_SUMMARY + echo "**Action:** Release trigger was blocked due to insufficient permissions" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Only authorized users can trigger automatic releases via changelog updates." >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/.github/workflows/upload-source-documents.yml b/.github/workflows/upload-source-documents.yml index df15246fc..69d6c3e48 100644 --- a/.github/workflows/upload-source-documents.yml +++ b/.github/workflows/upload-source-documents.yml @@ -15,7 +15,7 @@ jobs: - name: Verify Changed files id: changed-files - uses: tj-actions/changed-files@v41 + uses: step-security/changed-files@3dbe17c78367e7d60f00d78ae6781a35be47b4a1 # v45.0.1 with: files: | website/**/*.mdx diff --git a/.github/workflows/v3-docs.yml b/.github/workflows/v3-docs.yml new file mode 100644 index 000000000..c70bffb8a --- /dev/null +++ b/.github/workflows/v3-docs.yml @@ -0,0 +1,51 @@ +name: Deploy to GitHub Pages + +on: + # Trigger the workflow every time you push to the `main` branch + # Using a different branch name? Replace `main` with your branch's name + push: + branches: [v3-alpha] + paths: + - 'docs/**' + - '.github/workflows/v3-docs.yml' + # Allows you to run this workflow manually from the Actions tab on GitHub. + workflow_dispatch: + +# Allow this job to clone the repo and create a page deployment +permissions: + contents: read + pages: write + id-token: write + +jobs: + build: + runs-on: ubuntu-latest + if: github.event.repository.fork == false + steps: + - name: Checkout your repository using git + uses: actions/checkout@v4 + - name: Install D2 + run: | + curl -fsSL https://d2lang.com/install.sh > install.sh + chmod +x install.sh + ./install.sh + sudo cp ~/.local/bin/d2 /usr/local/bin/d2 + d2 --version + rm install.sh + - name: Install, build, and upload your site output + uses: withastro/action@v2 + with: + path: docs + node-version: 20 # The specific version of Node that should be used to build your site. Defaults to 18. (optional) + # package-manager: pnpm@latest # The Node package manager that should be used to install dependencies and build your site. Automatically detected based on your lockfile. (optional) + + deploy: + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 \ No newline at end of file diff --git a/.gitignore b/.gitignore index e7888b44a..61efe756b 100644 --- a/.gitignore +++ b/.gitignore @@ -37,5 +37,61 @@ v2/cmd/wails/internal/commands/initialise/templates/testtemplates/ /websitev3/site/ /v3/examples/plugins/bin/testapp +# V3 Example binaries - ignore executables that match directory names +/v3/examples/badge-custom/badge-custom +/v3/examples/badge/badge +/v3/examples/binding/binding +/v3/examples/cancel-async/cancel-async +/v3/examples/cancel-chaining/cancel-chaining +/v3/examples/clipboard/clipboard +/v3/examples/contextmenus/contextmenus +/v3/examples/dev/dev +/v3/examples/dialogs-basic/dialogs-basic +/v3/examples/dialogs/dialogs +/v3/examples/drag-n-drop/drag-n-drop +/v3/examples/environment/environment +/v3/examples/events-bug/events-bug +/v3/examples/events/events +/v3/examples/file-association/file-association +/v3/examples/frameless/frameless +/v3/examples/gin-example/gin-example +/v3/examples/gin-routing/gin-routing +/v3/examples/gin-service/gin-service +/v3/examples/hide-window/hide-window +/v3/examples/html-dnd-api/html-dnd-api +/v3/examples/ignore-mouse/ignore-mouse +/v3/examples/keybindings/keybindings +/v3/examples/menu/menu +/v3/examples/notifications/notifications +/v3/examples/panic-handling/panic-handling +/v3/examples/plain/plain +/v3/examples/raw-message/raw-message +/v3/examples/screen/screen +/v3/examples/services/services +/v3/examples/show-macos-toolbar/show-macos-toolbar +/v3/examples/single-instance/single-instance +/v3/examples/systray-basic/systray-basic +/v3/examples/systray-custom/systray-custom +/v3/examples/systray-menu/systray-menu +/v3/examples/video/video +/v3/examples/window-api/window-api +/v3/examples/window-call/window-call +/v3/examples/window-menu/window-menu +/v3/examples/window/window +/v3/examples/wml/wml + +# Common binary names in examples +/v3/examples/*/main +/v3/examples/*/app +/v3/examples/*/changeme +/v3/examples/*/testbuild-* + # Temporary called mkdocs, should be renamed to more standard -website or similar -/mkdocs-website/site +/docs/site +.aider* +.aider* +.cache +.local + +# Ignore local iOS test app +/v3/testiosapp/ diff --git a/.prettierrc.yml b/.prettierrc.yml index 685d8b6e7..1e2d2dada 100644 --- a/.prettierrc.yml +++ b/.prettierrc.yml @@ -1,6 +1,7 @@ overrides: - files: - "**/*.md" + - "**/*.mdx" options: printWidth: 80 proseWrap: always diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..0d4177023 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,197 @@ +# AI Agent Instructions for Wails v3 + +## Issue Tracking with bd (beads) + +**IMPORTANT**: This project uses **bd (beads)** for ALL issue tracking. Do NOT use markdown TODOs, task lists, or other tracking methods. + +### Why bd? + +- Dependency-aware: Track blockers and relationships between issues +- Git-friendly: Auto-syncs to JSONL for version control +- Agent-optimized: JSON output, ready work detection, discovered-from links +- Prevents duplicate tracking systems and confusion + +### Quick Start + +**Check for ready work:** +```bash +bd ready --json +``` + +**Create new issues:** +```bash +bd create "Issue title" -t bug|feature|task -p 0-4 --json +bd create "Issue title" -p 1 --deps discovered-from:bd-123 --json +bd create "Subtask" --parent --json # Hierarchical subtask (gets ID like epic-id.1) +``` + +**Claim and update:** +```bash +bd update bd-42 --status in_progress --json +bd update bd-42 --priority 1 --json +``` + +**Complete work:** +```bash +bd close bd-42 --reason "Completed" --json +``` + +### Issue Types + +- `bug` - Something broken +- `feature` - New functionality +- `task` - Work item (tests, docs, refactoring) +- `epic` - Large feature with subtasks +- `chore` - Maintenance (dependencies, tooling) + +### Priorities + +- `0` - Critical (security, data loss, broken builds) +- `1` - High (major features, important bugs) +- `2` - Medium (default, nice-to-have) +- `3` - Low (polish, optimization) +- `4` - Backlog (future ideas) + +### Workflow for AI Agents + +1. **Check ready work**: `bd ready` shows unblocked issues +2. **Claim your task**: `bd update --status in_progress` +3. **Work on it**: Implement, test, document +4. **Discover new work?** Create linked issue: + - `bd create "Found bug" -p 1 --deps discovered-from:` +5. **Complete**: `bd close --reason "Done"` +6. **Commit together**: Always commit the `.beads/issues.jsonl` file together with the code changes so issue state stays in sync with code state + +### Auto-Sync + +bd automatically syncs with git: +- Exports to `.beads/issues.jsonl` after changes (5s debounce) +- Imports from JSONL when newer (e.g., after `git pull`) +- No manual export/import needed! + +### GitHub Copilot Integration + +If using GitHub Copilot, also create `.github/copilot-instructions.md` for automatic instruction loading. +Run `bd onboard` to get the content, or see step 2 of the onboard instructions. + +### MCP Server (Recommended) + +If using Claude or MCP-compatible clients, install the beads MCP server: + +```bash +pip install beads-mcp +``` + +Add to MCP config (e.g., `~/.config/claude/config.json`): +```json +{ + "beads": { + "command": "beads-mcp", + "args": [] + } +} +``` + +Then use `mcp__beads__*` functions instead of CLI commands. + +### Managing AI-Generated Planning Documents + +AI assistants often create planning and design documents during development: +- PLAN.md, IMPLEMENTATION.md, ARCHITECTURE.md +- DESIGN.md, CODEBASE_SUMMARY.md, INTEGRATION_PLAN.md +- TESTING_GUIDE.md, TECHNICAL_DESIGN.md, and similar files + +**Best Practice: Use a dedicated directory for these ephemeral files** + +**Recommended approach:** +- Create a `history/` directory in the project root +- Store ALL AI-generated planning/design docs in `history/` +- Keep the repository root clean and focused on permanent project files +- Only access `history/` when explicitly asked to review past planning + +**Example .gitignore entry (optional):** +``` +# AI planning documents (ephemeral) +history/ +``` + +**Benefits:** +- Clean repository root +- Clear separation between ephemeral and permanent documentation +- Easy to exclude from version control if desired +- Preserves planning history for archeological research +- Reduces noise when browsing the project + +### CLI Help + +Run `bd --help` to see all available flags for any command. +For example: `bd create --help` shows `--parent`, `--deps`, `--assignee`, etc. + +### Important Rules + +- Use bd for ALL task tracking +- Always use `--json` flag for programmatic use +- Link discovered work with `discovered-from` dependencies +- Check `bd ready` before asking "what should I work on?" +- Store AI planning docs in `history/` directory +- Run `bd --help` to discover available flags +- **ALWAYS run `coderabbit --plain` before committing** to get code analysis and catch issues early +- Do NOT create markdown TODO lists +- Do NOT use external issue trackers +- Do NOT duplicate tracking systems +- Do NOT clutter repo root with planning documents + +For more details, see README.md and QUICKSTART.md. + +## Implementation Tracking (IMPLEMENTATION.md) + +**IMPORTANT**: The `IMPLEMENTATION.md` file at the repository root is a **persistent tracking document** for the WebKitGTK 6.0 / GTK4 implementation work. It is NOT an ephemeral planning document. + +### Requirements + +1. **Update with EVERY commit** that touches GTK4/WebKitGTK 6.0 related code +2. **Track all architectural decisions** with context, decision, and rationale +3. **Maintain progress status** for each implementation phase +4. **Document API differences** between GTK3 and GTK4 +5. **Keep file references** accurate and up-to-date + +### What to Update + +- Phase completion status (✅ COMPLETE, 🔄 IN PROGRESS, 📋 PENDING) +- New decisions made during implementation +- Files created or modified +- Changelog entries with dates +- TODO items discovered during work + +### Commit Message Pattern + +When updating IMPLEMENTATION.md: +``` +docs: update implementation tracker for [phase/feature] +``` + +## Landing the Plane (Session Completion) + +**When ending a work session**, you MUST complete ALL steps below. Work is NOT complete until `git push` succeeds. + +**MANDATORY WORKFLOW:** + +1. **File issues for remaining work** - Create issues for anything that needs follow-up +2. **Run quality gates** (if code changed) - Tests, linters, builds +3. **Update issue status** - Close finished work, update in-progress items +4. **PUSH TO REMOTE** - This is MANDATORY: + ```bash + git pull --rebase + bd sync + git push + git status # MUST show "up to date with origin" + ``` +5. **Clean up** - Clear stashes, prune remote branches +6. **Verify** - All changes committed AND pushed +7. **Hand off** - Provide context for next session + +**CRITICAL RULES:** +- Work is NOT complete until `git push` succeeds +- NEVER stop before pushing - that leaves work stranded locally +- NEVER say "ready to push when you are" - YOU must push +- If push fails, resolve and retry until it succeeds diff --git a/CNAME b/CNAME new file mode 100644 index 000000000..23caa94af --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +v3alpha.wails.io \ No newline at end of file diff --git a/IMPLEMENTATION.md b/IMPLEMENTATION.md new file mode 100644 index 000000000..16d048f3f --- /dev/null +++ b/IMPLEMENTATION.md @@ -0,0 +1,543 @@ +# WebKitGTK 6.0 / GTK4 Implementation Tracker + +## Overview + +This document tracks the implementation of WebKitGTK 6.0 (GTK4) support for Wails v3 on Linux. + +**Goal**: Provide GTK4/WebKitGTK 6.0 support as an EXPERIMENTAL opt-in via `-tags gtk4`, while maintaining GTK3/WebKit2GTK 4.1 as the stable default. + +## Architecture Decisions + +### Decision 1: GTK3 as Default, GTK4 Opt-In (2026-02-04) +**Context**: Need to support modern Linux distributions with GTK4 while maintaining stability for existing apps. + +**Decision**: GTK3 remains the stable default (no build tag required). GTK4 is available as experimental via `-tags gtk4`. + +**Rationale**: +- GTK3/WebKit2GTK 4.1 is battle-tested and widely deployed +- GTK4 support needs more community testing before becoming default +- Allows gradual migration and feedback collection +- Protects existing apps from unexpected breakage + +**Build Tags**: +- Default (no tag): `//go:build linux && cgo && !gtk4 && !android` +- Experimental GTK4: `//go:build linux && cgo && gtk4 && !android` + +### Decision 2: pkg-config Libraries (2026-01-04) +**GTK4/WebKitGTK 6.0**: +``` +#cgo linux pkg-config: gtk4 webkitgtk-6.0 libsoup-3.0 +``` + +**GTK3/WebKit2GTK 4.1** (legacy): +``` +#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.1 libsoup-3.0 +``` + +### Decision 3: Wayland Window Positioning (2026-01-04) +**Context**: GTK4/Wayland doesn't support arbitrary window positioning - this is a Wayland protocol limitation. + +**Decision**: Window positioning functions (`move()`, `setPosition()`, `center()`) are documented NO-OPs on GTK4/Wayland. + +**Rationale**: This is a fundamental Wayland design decision, not a limitation we can work around. Users need to be aware of this behavioral difference. + +### Decision 4: Menu System Architecture (2026-01-04) +**Context**: GTK4 removes GtkMenu/GtkMenuItem in favor of GMenu/GAction. + +**Decision**: Complete rewrite of menu system for GTK4 using GMenu/GAction/GtkPopoverMenuBar. + +**Status**: Stub implementations only. Full implementation pending. + +### Decision 5: System Tray Compatibility (2026-01-04) +**Context**: v3's system tray uses D-Bus StatusNotifierItem protocol. + +**Decision**: No changes needed - system tray is already GTK-agnostic. + +## Implementation Progress + +### Phase 1: Build Infrastructure ✅ COMPLETE + +**Commit**: `a0ca13fdc` (2026-01-04) + +#### 1.1 Add gtk3 constraint to existing files +Files modified: +- `v3/pkg/application/application_linux.go` - Added `gtk3` constraint +- `v3/pkg/application/linux_cgo.go` - Added `gtk3` constraint +- `v3/internal/assetserver/webview/request_linux.go` - Added `gtk3` constraint +- `v3/internal/assetserver/webview/responsewriter_linux.go` - Added `gtk3` constraint +- `v3/internal/assetserver/webview/webkit2.go` - Added `gtk3` constraint + +#### 1.2 Create GTK4 stub files +Files created: +- `v3/pkg/application/linux_cgo_gtk4.go` (~1000 lines) + - Main CGO file with GTK4 bindings + - Implements: window management, clipboard, basic menu stubs + - Uses `gtk4 webkitgtk-6.0` pkg-config + +- `v3/pkg/application/application_linux_gtk4.go` (~250 lines) + - Application lifecycle management + - System theme detection via D-Bus + - NVIDIA DMA-BUF workaround for Wayland + +#### 1.3 Create WebKitGTK 6.0 asset server stubs +Files created: +- `v3/internal/assetserver/webview/webkit6.go` +- `v3/internal/assetserver/webview/request_linux_gtk4.go` +- `v3/internal/assetserver/webview/responsewriter_linux_gtk4.go` + +### Phase 2: Doctor & Capabilities ✅ COMPLETE + +**Goal**: Update `wails doctor` to check for GTK4 as primary, GTK3 as secondary. + +#### 2.1 Package Manager Updates +All 7 package managers updated to check GTK4/WebKitGTK 6.0 as primary, GTK3 as optional/legacy: +- `v3/internal/doctor/packagemanager/apt.go` ✅ +- `v3/internal/doctor/packagemanager/dnf.go` ✅ +- `v3/internal/doctor/packagemanager/pacman.go` ✅ +- `v3/internal/doctor/packagemanager/zypper.go` ✅ +- `v3/internal/doctor/packagemanager/emerge.go` ✅ +- `v3/internal/doctor/packagemanager/eopkg.go` ✅ +- `v3/internal/doctor/packagemanager/nixpkgs.go` ✅ + +Package key naming convention: `gtk3`, `webkit2gtk-4.1` (primary/default), `gtk4`, `webkitgtk-6.0` (experimental, optional) + +#### 2.2 Capabilities Detection +Files created/updated: +- `v3/internal/capabilities/capabilities.go` - Added `GTKVersion` (int) and `WebKitVersion` (string) fields +- `v3/internal/capabilities/capabilities_linux.go` - GTK4 default: `GTKVersion: 4, WebKitVersion: "6.0"` +- `v3/internal/capabilities/capabilities_linux_gtk3.go` - GTK3 legacy: `GTKVersion: 3, WebKitVersion: "4.1"` + +TODO (deferred to Phase 3): +- [ ] Update `v3/internal/doctor/doctor_linux.go` - Improve output to show GTK4 vs GTK3 status + +### Phase 3: Window Management ✅ COMPLETE + +#### 3.1 GTK4 Event Controllers +GTK4 replaces direct signal handlers with `GtkEventController` objects: +- `GtkEventControllerFocus` for focus in/out events +- `GtkGestureClick` for button press/release events +- `GtkEventControllerKey` for keyboard events +- Window signals: `close-request`, `notify::maximized`, `notify::fullscreened` + +New C function `setupWindowEventControllers()` sets up all event controllers. + +#### 3.2 Window Drag and Resize +GTK4 uses `GdkToplevel` API instead of GTK3's `gtk_window_begin_move_drag`: +- `gdk_toplevel_begin_move()` for window drag +- `gdk_toplevel_begin_resize()` for window resize +- Requires `gtk_native_get_surface()` to get the GdkSurface + +#### 3.3 Drag-and-Drop with GtkDropTarget +Complete implementation using GTK4's `GtkDropTarget`: +- `on_drop_enter` / `on_drop_leave` for drag enter/exit events +- `on_drop_motion` for drag position updates +- `on_drop` handles file drops via `GDK_TYPE_FILE_LIST` +- Go callbacks: `onDropEnter`, `onDropLeave`, `onDropMotion`, `onDropFiles` + +#### 3.4 Window State Detection +- `isMinimised()` uses `gdk_toplevel_get_state()` with `GDK_TOPLEVEL_STATE_MINIMIZED` +- `isMaximised()` uses `gtk_window_is_maximized()` +- `isFullscreen()` uses `gtk_window_is_fullscreen()` + +#### 3.5 Size Constraints +GTK4 removed `gtk_window_set_geometry_hints()`. Now using `gtk_widget_set_size_request()` for minimum size. + +TODO (deferred): +- [ ] Test window lifecycle on GTK4 with actual GTK4 libraries + +### Phase 4: Menu System ✅ COMPLETE + +GTK4 completely replaced the menu system. GTK3's GtkMenu/GtkMenuItem are gone. + +#### 4.1 GMenu/GAction Architecture +- `GMenu` - Menu model (data structure, not a widget) +- `GMenuItem` - Individual menu item in the model +- `GSimpleAction` - Action that gets triggered when menu item is activated +- `GSimpleActionGroup` - Container for actions, attached to widgets + +#### 4.2 Menu Bar Implementation +- `GtkPopoverMenuBar` created from `GMenu` model via `create_menu_bar_from_model()` +- Action group attached to window with `attach_action_group_to_widget()` +- Actions use "app.action_name" namespace + +#### 4.3 New Files Created +- `v3/pkg/application/menu_linux_gtk4.go` - GTK4 menu processing +- `v3/pkg/application/menuitem_linux_gtk4.go` - GTK4 menu item handling + +#### 4.4 Build Tag Changes +- `menu_linux.go` - Added `gtk3` tag +- `menuitem_linux.go` - Added `gtk3` tag + +#### 4.5 Key Functions +- `menuActionActivated()` - Callback when GAction is triggered +- `menuItemNewWithId()` - Creates GMenuItem + associated GSimpleAction +- `menuCheckItemNewWithId()` - Creates stateful toggle action +- `menuRadioItemNewWithId()` - Creates radio action +- `set_action_enabled()` / `set_action_state()` - Manage action state + +TODO (deferred): +- [ ] Context menus with GtkPopoverMenu + +### Phase 5: Asset Server ✅ COMPLETE + +WebKitGTK 6.0 uses the same URI scheme handler API as WebKitGTK 4.1. +The asset server implementation is identical between GTK3 and GTK4. + +#### 5.1 Asset Server Files (already created in Phase 1) +- `v3/internal/assetserver/webview/webkit6.go` - WebKitGTK 6.0 helpers +- `v3/internal/assetserver/webview/request_linux_gtk4.go` - Request handling +- `v3/internal/assetserver/webview/responsewriter_linux_gtk4.go` - Response writing + +#### 5.2 Missing Exports Added +The GTK4 CGO file was missing two critical exports that were in the GTK3 file: +- `onProcessRequest` - Handles URI scheme requests from WebKit +- `sendMessageToBackend` - Handles JavaScript to Go communication + +Both exports were added to `linux_cgo_gtk4.go`. + +#### 5.3 Key Differences from GTK3 +| Aspect | GTK3 | GTK4 | +|--------|------|------| +| pkg-config | `webkit2gtk-4.1` | `webkitgtk-6.0` | +| Headers | `webkit2/webkit2.h` | `webkit/webkit.h` | +| Min version | 2.40 | 6.0 | +| URI scheme API | Same | Same | + +TODO (deferred to testing phase): +- [ ] Test asset loading on actual GTK4 system +- [ ] Verify JavaScript execution works correctly + +### Phase 6: Docker & Build System ✅ COMPLETE + +#### 6.1 Docker Container Updates +Updated both Dockerfile.linux-x86_64 and Dockerfile.linux-arm64 to install: +- GTK3 + WebKit2GTK 4.1 (default build target) +- GTK4 + WebKitGTK 6.0 (for experimental `-tags gtk4` builds) + +Build scripts now support `BUILD_TAGS` environment variable: +- Default: Builds with GTK3/WebKit2GTK 4.1 +- `BUILD_TAGS=gtk4`: Builds with GTK4/WebKitGTK 6.0 (experimental) + +#### 6.2 Taskfile Targets +New targets added to `v3/Taskfile.yaml`: + +| Target | Description | +|--------|-------------| +| `test:example:linux` | Build single example with GTK3 (native, default) | +| `test:example:linux:gtk4` | Build single example with GTK4 (native, experimental) | +| `test:examples:linux:docker:x86_64` | Build all examples with GTK3 in Docker | +| `test:examples:linux:docker:x86_64:gtk4` | Build all examples with GTK4 in Docker (experimental) | +| `test:examples:linux:docker:arm64` | Build all examples with GTK3 in Docker (ARM64) | +| `test:examples:linux:docker:arm64:gtk4` | Build all examples with GTK4 in Docker (ARM64, experimental) | + +TODO (deferred): +- [ ] Update CI/CD workflows to test both GTK versions + +### Phase 8: Dialog System ✅ COMPLETE + +GTK4 completely replaced the dialog APIs. GTK3's `GtkFileChooserDialog` and +`gtk_message_dialog_new` are deprecated/removed. + +#### 8.1 File Dialogs +GTK4 uses `GtkFileDialog` with async API: +- `gtk_file_dialog_open()` - Open single file +- `gtk_file_dialog_open_multiple()` - Open multiple files +- `gtk_file_dialog_select_folder()` - Select folder +- `gtk_file_dialog_select_multiple_folders()` - Select multiple folders +- `gtk_file_dialog_save()` - Save file + +Key differences: +- No more `gtk_dialog_run()` - everything is async with callbacks +- Filters use `GListStore` of `GtkFileFilter` objects +- Results delivered via `GAsyncResult` callbacks +- Custom button text via `gtk_file_dialog_set_accept_label()` + +#### 8.1.1 GTK4 File Dialog Limitations (Portal-based) + +GTK4's `GtkFileDialog` uses **xdg-desktop-portal** for native file dialogs. This provides +better desktop integration but removes some application control: + +| Feature | GTK3 | GTK4 | Notes | +|---------|------|------|-------| +| `ShowHiddenFiles()` | ✅ Works | ❌ No effect | User controls via portal UI toggle | +| `CanCreateDirectories()` | ✅ Works | ❌ No effect | Always enabled in portal | +| `ResolvesAliases()` | ✅ Works | ❌ No effect | Portal handles symlinks | +| `SetButtonText()` | ✅ Works | ✅ Works | `gtk_file_dialog_set_accept_label()` | +| Multiple folders | ✅ Works | ✅ Works | `gtk_file_dialog_select_multiple_folders()` | + +**Why these limitations exist**: GTK4's portal-based dialogs delegate UI control to the +desktop environment (GNOME, KDE, etc.). This is intentional - the portal provides +consistent UX across applications and respects user preferences. + +#### 8.2 Message Dialogs +GTK4 uses `GtkAlertDialog`: +- `gtk_alert_dialog_choose()` - Show dialog with buttons +- Buttons specified as NULL-terminated string array +- Default and cancel button indices configurable + +#### 8.3 Implementation Details +- Request ID tracking for async callback matching +- `fileDialogCallback` / `alertDialogCallback` C exports for results +- `runChooserDialog()` and `runQuestionDialog()` Go wrappers +- `runOpenFileDialog()` and `runSaveFileDialog()` convenience functions + +| GTK3 | GTK4 | +|------|------| +| `GtkFileChooserDialog` | `GtkFileDialog` | +| `gtk_dialog_run()` | Async callbacks | +| `gtk_message_dialog_new()` | `GtkAlertDialog` | +| `gtk_widget_destroy()` | `g_object_unref()` | + +### Phase 9: Keyboard Accelerators ✅ COMPLETE + +GTK4 uses `gtk_application_set_accels_for_action()` to bind keyboard shortcuts to GActions. + +#### 9.1 Key Components + +**C Helper Functions** (in `linux_cgo_gtk4.go`): +- `set_action_accelerator(app, action_name, accel)` - Sets accelerator for a GAction +- `build_accelerator_string(key, mods)` - Converts key+modifiers to GTK accelerator string + +**Go Functions** (in `linux_cgo_gtk4.go`): +- `namedKeysToGTK` - Map of key names to GDK keysym values (e.g., "backspace" → 0xff08) +- `parseKeyGTK(key)` - Converts Wails key string to GDK keysym +- `parseModifiersGTK(modifiers)` - Converts Wails modifiers to GdkModifierType +- `acceleratorToGTK(accel)` - Converts full accelerator to GTK format +- `setMenuItemAccelerator(itemId, accel)` - Sets accelerator for a menu item + +**Integration** (in `menuitem_linux_gtk4.go`): +- `setAccelerator()` method on `linuxMenuItem` calls `setMenuItemAccelerator()` +- `newMenuItemImpl()`, `newCheckMenuItemImpl()`, `newRadioMenuItemImpl()` all set accelerators during creation + +#### 9.2 Accelerator String Format + +GTK accelerator strings use format like: +- `q` - Ctrl+Q +- `s` - Ctrl+Shift+S +- `F4` - Alt+F4 +- `e` - Super+E (Windows/Command key) + +#### 9.3 Modifier Mapping + +| Wails Modifier | GDK Modifier | +|----------------|--------------| +| `CmdOrCtrlKey` | `GDK_CONTROL_MASK` | +| `ControlKey` | `GDK_CONTROL_MASK` | +| `OptionOrAltKey` | `GDK_ALT_MASK` | +| `ShiftKey` | `GDK_SHIFT_MASK` | +| `SuperKey` | `GDK_SUPER_MASK` | + +### Phase 10: Testing 📋 PENDING + +TODO: +- [ ] Test on Ubuntu 24.04 (native GTK4) +- [ ] Test on Ubuntu 22.04 (backported WebKitGTK 6.0) +- [ ] Test legacy build on older systems +- [ ] Performance benchmarks +- [ ] Verify file dialogs work correctly +- [ ] Verify message dialogs work correctly + +## API Differences: GTK3 vs GTK4 + +| Feature | GTK3 | GTK4 | +|---------|------|------| +| Init | `gtk_init(&argc, &argv)` | `gtk_init_check()` | +| Container | `gtk_container_add()` | `gtk_window_set_child()` | +| Show | `gtk_widget_show_all()` | Widgets visible by default | +| Hide | `gtk_widget_hide()` | `gtk_widget_set_visible(w, FALSE)` | +| Clipboard | `GtkClipboard` | `GdkClipboard` | +| Menu | `GtkMenu/GtkMenuItem` | `GMenu/GAction` | +| Menu Bar | `GtkMenuBar` | `GtkPopoverMenuBar` | +| Window Move | `gtk_window_move()` | NO-OP on Wayland | +| Window Position | `gtk_window_get_position()` | Not available on Wayland | +| Destroy | `gtk_widget_destroy()` | `gtk_window_destroy()` | +| Drag Start | `gtk_window_begin_move_drag()` | `gtk_native_get_surface()` + surface drag | + +## Files Reference + +### GTK3 (Default) Files +``` +v3/pkg/application/ + linux_cgo.go # Main CGO (!gtk4 tag - default) + application_linux.go # App lifecycle (!gtk4 tag - default) + +v3/internal/assetserver/webview/ + webkit2.go # WebKit2GTK helpers (!gtk4 tag - default) + request_linux.go # Request handling (!gtk4 tag - default) + responsewriter_linux.go # Response writing (!gtk4 tag - default) + +v3/internal/capabilities/ + capabilities_linux_gtk3.go # GTK3 capabilities (!gtk4 tag - default) + +v3/internal/operatingsystem/ + webkit_linux.go # WebKit version info (!gtk4 tag - default) +``` + +### GTK4 (Experimental) Files +``` +v3/pkg/application/ + linux_cgo_gtk4.go # Main CGO (gtk4 tag - experimental) + application_linux_gtk4.go # App lifecycle (gtk4 tag - experimental) + +v3/internal/assetserver/webview/ + webkit6.go # WebKitGTK 6.0 helpers (gtk4 tag - experimental) + request_linux_gtk4.go # Request handling (gtk4 tag - experimental) + responsewriter_linux_gtk4.go # Response writing (gtk4 tag - experimental) + +v3/internal/capabilities/ + capabilities_linux.go # GTK4 capabilities (gtk4 tag - experimental) + +v3/internal/operatingsystem/ + webkit_linux_gtk4.go # WebKit version info (gtk4 tag - experimental) +``` + +### Shared Files (no GTK-specific code) +``` +v3/pkg/application/ + webview_window_linux.go # Window wrapper (uses methods from CGO files) + systemtray_linux.go # D-Bus based, GTK-agnostic + +v3/internal/assetserver/webview/ + request.go # Interface definitions + responsewriter.go # Interface definitions +``` + +## Changelog + +### 2026-01-07 (Session 11) +- Fixed GTK4 dialog system bugs +- **File Dialog Fix**: Removed premature `g_object_unref()` that freed dialog before async callback + - GTK4 async dialogs manage their own lifecycle + - Commit: `6f9c5beb5` +- **Alert Dialog Fixes**: + - Removed premature `g_object_unref(dialog)` from `show_alert_dialog()` (same issue as file dialogs) + - Fixed deadlock in `dialogs_linux.go` - `InvokeAsync` → `go func()` since `runQuestionDialog` blocks internally + - Fixed `runQuestionDialog` to use `options.Title` as message (was using `options.Message`) + - Added default "OK" button when no buttons specified + - Commit: `1a77e6091` +- **Other Fixes**: + - Fixed checkptr errors with `-race` flag by changing C signal functions to accept `uintptr_t` (`3999f1f24`) + - Fixed ExecJS race condition by adding mutex for `runtimeLoaded`/`pendingJS` (`8e386034e`) +- Added DEBUG_LOG macro for compile-time debug output: `CGO_CFLAGS="-DWAILS_GTK_DEBUG" go build ...` +- Added manual dialog test suite in `v3/test/manual/dialog/` +- **Additional Dialog Fixes** (Session 11 continued): + - Added `gtk_file_dialog_set_accept_label()` for custom button text + - Added `gtk_file_dialog_select_multiple_folders()` for multiple directory selection + - Fixed data race in `application.go` cleanup - was using RLock() when writing `a.windows = nil` + - Documented GTK4 portal limitations (ShowHiddenFiles, CanCreateDirectories have no effect) +- Files modified: + - `v3/pkg/application/linux_cgo_gtk4.go` - dialog fixes, race fixes, accept label, multiple folders + - `v3/pkg/application/linux_cgo_gtk4.c` - DEBUG_LOG macro, alert dialog lifecycle fix, select_multiple_folders callback + - `v3/pkg/application/linux_cgo_gtk4.h` - uintptr_t for signal functions + - `v3/pkg/application/dialogs_linux.go` - deadlock fix + - `v3/pkg/application/webview_window.go` - pendingJS mutex + - `v3/pkg/application/application.go` - RLock → Lock for cleanup writes + - `docs/src/content/docs/reference/dialogs.mdx` - documented GTK4 limitations + +### 2026-01-04 (Session 10) +- Fixed Window → Zoom menu behavior to toggle maximize/restore (was incorrectly calling webview zoomIn) +- Fixed radio button styling in GTK4 GMenu (now shows dots instead of checkmarks) + - Implemented proper GMenu radio groups with string-valued stateful actions + - All items in group share same action name with unique target values + - Added `create_radio_menu_item()` C helper and `menuRadioItemNewWithGroup()` Go wrapper +- Researched Wayland minimize behavior: + - `gtk_window_minimize()` works on GNOME/KDE (sends xdg_toplevel_set_minimized) + - May be no-op on tiling WMs (Sway, etc.) per Wayland protocol design +- Fixed app not terminating when last window closed + - Added quit logic to `unregisterWindow()` in `application_linux_gtk4.go` + - Respects `DisableQuitOnLastWindowClosed` option +- Fixed menu separators not showing + - GMenu uses sections for visual separators (not separate separator items) + - Rewrote menu processing to group items into sections, separators create new sections + - Added `menuNewSection()`, `menuAppendSection()`, `menuAppendItemToSection()` helpers +- Added CSS provider to reduce popover menu padding +- Removed all debug println statements +- Files modified: + - `v3/pkg/application/linux_cgo_gtk4.go` - added radio group support, section helpers + - `v3/pkg/application/linux_cgo_gtk4.c` - added create_radio_menu_item(), init_menu_css() + - `v3/pkg/application/linux_cgo_gtk4.h` - added function declaration + - `v3/pkg/application/application_linux_gtk4.go` - added quit-on-last-window logic + - `v3/pkg/application/menu_linux_gtk4.go` - section-based menu processing, radio groups + - `v3/pkg/application/menuitem_linux_gtk4.go` - updated radio item creation + - `v3/pkg/application/webview_window_linux.go` - fixed zoom() to toggle maximize + - `v3/pkg/application/window_manager.go` - removed debug output + +### 2026-01-04 (Session 9) +- Fixed GTK4 window creation crash (SIGSEGV in gtk_application_window_new) +- **Root Cause**: GTK4 requires app to be "activated" before creating windows +- **Solution**: Added activation synchronization mechanism: + - Added `activated` channel and `sync.Once` to `linuxApp` struct + - Added `markActivated()` method called from `activateLinux()` callback + - Added `waitForActivation()` method for callers to block until ready + - Modified `WebviewWindow.Run()` to wait for activation before `InvokeSync` +- Files modified: + - `v3/pkg/application/application_linux_gtk4.go` - activation gate + - `v3/pkg/application/linux_cgo_gtk4.go` - call markActivated() in activateLinux + - `v3/pkg/application/webview_window.go` - wait for activation on GTK4 +- GTK4 apps now create windows successfully without crashes + +### 2026-01-04 (Session 8) +- Fixed GTK3/GTK4 symbol conflict in operatingsystem package +- Added `gtk3` build tag to `v3/internal/operatingsystem/webkit_linux.go` +- Created `v3/internal/operatingsystem/webkit_linux_gtk4.go` with GTK4/WebKitGTK 6.0 +- Moved app initialization from `init()` to `newPlatformApp()` for cleaner setup +- Resolved runtime crash: "GTK 2/3 symbols detected in GTK 4 process" +- Verified menu example runs successfully with GTK 4.20.3 and WebKitGTK 2.50.3 + +### 2026-01-04 (Session 7) +- Completed Phase 9: Keyboard Accelerators +- Added namedKeysToGTK map with GDK keysym values for all special keys +- Added parseKeyGTK() and parseModifiersGTK() conversion functions +- Added acceleratorToGTK() to convert Wails accelerator format to GTK +- Added setMenuItemAccelerator() Go wrapper that calls C helpers +- Integrated accelerator setting in all menu item creation functions +- Uses gtk_application_set_accels_for_action() for GTK4 shortcut binding + +### 2026-01-04 (Session 6) +- Completed Phase 8: Dialog System +- Implemented GtkFileDialog for file open/save/folder dialogs +- Implemented GtkAlertDialog for message dialogs +- Added async callback system for GTK4 dialogs (no more gtk_dialog_run) +- Added C helper functions and Go wrapper functions + +### 2026-01-04 (Session 5 continued) +- Completed Phase 6: Docker & Build System +- Updated Dockerfile.linux-x86_64 and Dockerfile.linux-arm64 for GTK4 + GTK3 +- Added BUILD_TAGS environment variable support in build scripts +- Added Taskfile targets for GTK4 (default) and GTK3 (legacy) builds + +### 2026-01-04 (Session 5) +- Completed Phase 5: Asset Server +- Verified WebKitGTK 6.0 uses same URI scheme handler API as WebKitGTK 4.1 +- Added missing `onProcessRequest` export to linux_cgo_gtk4.go +- Added missing `sendMessageToBackend` export to linux_cgo_gtk4.go +- Confirmed asset server files (webkit6.go, request/responsewriter) are complete + +### 2026-01-04 (Session 4) +- Completed Phase 4: Menu System +- Implemented GMenu/GAction architecture for GTK4 menus +- Created GtkPopoverMenuBar integration +- Added menu_linux_gtk4.go and menuitem_linux_gtk4.go +- Added gtk3 build tags to original menu files +- Implemented stateful actions for checkboxes and radio items + +### 2026-01-04 (Session 3) +- Completed Phase 3: Window Management +- Implemented GTK4 event controllers (GtkEventControllerFocus, GtkGestureClick, GtkEventControllerKey) +- Implemented window drag using GdkToplevel API (gdk_toplevel_begin_move/resize) +- Implemented complete drag-and-drop with GtkDropTarget +- Fixed window state detection (isMinimised, isMaximised, isFullscreen) +- Fixed size() function to properly return window dimensions +- Updated windowSetGeometryHints for GTK4 (uses gtk_widget_set_size_request) + +### 2026-01-04 (Session 2) +- Completed Phase 2: Doctor & Capabilities +- Updated all 7 package managers for GTK4/WebKitGTK 6.0 as primary +- Added GTKVersion and WebKitVersion fields to Capabilities struct +- Created capabilities_linux_gtk3.go for legacy build path + +### 2026-01-04 (Session 1) +- Initial implementation of GTK4 build infrastructure +- Added `gtk3` constraint to 5 existing files +- Created 5 new GTK4 stub files +- Updated UNRELEASED_CHANGELOG.md diff --git a/IOS_ARCHITECTURE.md b/IOS_ARCHITECTURE.md new file mode 100644 index 000000000..2e07f4f0c --- /dev/null +++ b/IOS_ARCHITECTURE.md @@ -0,0 +1,419 @@ +# Wails v3 iOS Architecture + +## Executive Summary + +This document provides a comprehensive technical architecture for iOS support in Wails v3. The implementation enables Go applications to run natively on iOS with a WKWebView frontend, maintaining the Wails philosophy of using web technologies for UI while leveraging Go for business logic. + +## Table of Contents + +1. [Architecture Overview](#architecture-overview) +2. [Core Components](#core-components) +3. [Layer Architecture](#layer-architecture) +4. [Implementation Details](#implementation-details) +5. [Battery Optimization](#battery-optimization) +6. [Build System](#build-system) +7. [Security Considerations](#security-considerations) +8. [API Reference](#api-reference) + +## Architecture Overview + +### Design Principles + +1. **Battery Efficiency First**: All architectural decisions prioritize battery life +2. **No Network Ports**: Asset serving happens in-process via native APIs +3. **Minimal WebView Instances**: Maximum 2 concurrent WebViews (1 primary, 1 for transitions) +4. **Native Integration**: Deep iOS integration using Objective-C runtime +5. **Wails v3 Compatibility**: Maintain API compatibility with existing Wails v3 applications + +### High-Level Architecture + +``` +┌─────────────────────────────────────────────────────────────┐ +│ iOS Application │ +├─────────────────────────────────────────────────────────────┤ +│ UIKit Framework │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ WailsViewController │ │ +│ │ ┌───────────────────────────────────────────────┐ │ │ +│ │ │ WKWebView Instance │ │ │ +│ │ │ ┌─────────────────────────────────────────┐ │ │ │ +│ │ │ │ Web Application (HTML/JS) │ │ │ │ +│ │ │ └─────────────────────────────────────────┘ │ │ │ +│ │ └───────────────────────────────────────────────┘ │ │ +│ └─────────────────────────────────────────────────────┘ │ +├─────────────────────────────────────────────────────────────┤ +│ Bridge Layer (CGO) │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │URL Handler │ │JS Bridge │ │Message Handler│ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +├─────────────────────────────────────────────────────────────┤ +│ Go Runtime │ +│ ┌──────────────────────────────────────────────────────┐ │ +│ │ Wails Application │ │ +│ │ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │ │ +│ │ │App Logic │ │Services │ │Asset Server │ │ │ +│ │ └──────────┘ └──────────┘ └──────────────────┘ │ │ +│ └──────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +## Core Components + +### 1. Platform Layer (`application_ios.go`) + +**Purpose**: Go interface for iOS platform operations + +**Key Functions**: +- `platformRun()`: Initialize and run the iOS application +- `platformQuit()`: Gracefully shutdown the application +- `isDarkMode()`: Detect iOS dark mode state +- `ExecuteJavaScript(windowID uint, js string)`: Execute JS in WebView + +**Exported Go Functions (Called from Objective-C)**: +- `ServeAssetRequest(windowID C.uint, urlStr *C.char, callbackID C.uint)` +- `HandleJSMessage(windowID C.uint, message *C.char)` + +### 2. Native iOS Layer (`application_ios.m`) + +**Components**: + +#### WailsSchemeHandler +```objc +@interface WailsSchemeHandler : NSObject +``` +- Implements `WKURLSchemeHandler` protocol +- Intercepts `wails://` URL requests +- Bridges to Go for asset serving +- Manages pending requests with callback IDs + +**Methods**: +- `startURLSchemeTask:`: Intercept request, call Go handler +- `stopURLSchemeTask:`: Cancel pending request +- `completeRequest:withData:mimeType:`: Complete request with data from Go + +#### WailsMessageHandler +```objc +@interface WailsMessageHandler : NSObject +``` +- Implements JavaScript to Go communication +- Handles `window.webkit.messageHandlers.external.postMessage()` +- Serializes messages to JSON for Go processing + +**Methods**: +- `userContentController:didReceiveScriptMessage:`: Process JS messages + +#### WailsViewController +```objc +@interface WailsViewController : UIViewController +``` +- Main view controller containing WKWebView +- Manages WebView lifecycle +- Handles JavaScript execution requests + +**Properties**: +- `webView`: WKWebView instance +- `schemeHandler`: Custom URL scheme handler +- `messageHandler`: JS message handler +- `windowID`: Unique window identifier + +**Methods**: +- `viewDidLoad`: Initialize WebView with configuration +- `executeJavaScript:`: Run JS code in WebView + +### 3. Bridge Layer (CGO) + +**C Interface Functions**: +```c +void ios_app_init(void); // Initialize iOS app +void ios_app_run(void); // Run main loop +void ios_app_quit(void); // Quit application +bool ios_is_dark_mode(void); // Check dark mode +unsigned int ios_create_webview(void); // Create WebView +void ios_execute_javascript(unsigned int windowID, const char* js); +void ios_complete_request(unsigned int callbackID, const char* data, const char* mimeType); +``` + +## Layer Architecture + +### Layer 1: Presentation Layer (WebView) + +**Responsibilities**: +- Render HTML/CSS/JavaScript UI +- Handle user interactions +- Communicate with native layer + +**Key Features**: +- WKWebView for modern web standards +- Hardware-accelerated rendering +- Efficient memory management + +### Layer 2: Communication Layer + +**Request Interception**: +``` +WebView Request → WKURLSchemeHandler → Go ServeAssetRequest → AssetServer → Response +``` + +**JavaScript Bridge**: +``` +JS postMessage → WKScriptMessageHandler → Go HandleJSMessage → Process → ExecuteJavaScript +``` + +### Layer 3: Application Layer (Go) + +**Components**: +- Application lifecycle management +- Service binding and method calls +- Asset serving from embedded fs.FS +- Business logic execution + +### Layer 4: Platform Integration Layer + +**iOS-Specific Features**: +- Dark mode detection +- System appearance integration +- iOS-specific optimizations + +## Implementation Details + +### Request Handling Flow + +1. **WebView makes request** to `wails://localhost/path` +2. **WKURLSchemeHandler intercepts** request +3. **Creates callback ID** and stores `WKURLSchemeTask` +4. **Calls Go function** `ServeAssetRequest` with URL and callback ID +5. **Go processes request** through AssetServer +6. **Go calls** `ios_complete_request` with response data +7. **Objective-C completes** the `WKURLSchemeTask` with response + +### JavaScript Execution Flow + +1. **Go calls** `ios_execute_javascript` with JS code +2. **Bridge dispatches** to main thread +3. **WKWebView evaluates** JavaScript +4. **Completion handler** logs any errors + +### Message Passing Flow + +1. **JavaScript calls** `window.webkit.messageHandlers.wails.postMessage(data)` +2. **WKScriptMessageHandler receives** message +3. **Serializes to JSON** and passes to Go +4. **Go processes** message in `HandleJSMessage` +5. **Go can respond** via `ExecuteJavaScript` + +## Battery Optimization + +### WebView Configuration + +```objc +// Disable unnecessary features +config.suppressesIncrementalRendering = NO; +config.allowsInlineMediaPlayback = YES; +config.mediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypeNone; +``` + +### Memory Management + +1. **Single WebView Instance**: Reuse instead of creating new instances +2. **Automatic Reference Counting**: Use ARC for Objective-C objects +3. **Lazy Loading**: Initialize components only when needed +4. **Resource Cleanup**: Properly release resources when done + +### Request Optimization + +1. **In-Process Serving**: No network overhead +2. **Direct Memory Transfer**: Pass data directly without serialization +3. **Efficient Caching**: Leverage WKWebView's built-in cache +4. **Minimal Wake Locks**: No background network activity + +## Build System + +### Build Tags + +```go +//go:build ios +``` + +### CGO Configuration + +```go +#cgo CFLAGS: -x objective-c -fobjc-arc +#cgo LDFLAGS: -framework Foundation -framework UIKit -framework WebKit +``` + +### Build Script (`build_ios.sh`) + +**Steps**: +1. Check dependencies (go, xcodebuild, xcrun) +2. Set up iOS cross-compilation environment +3. Build Go binary with iOS tags +4. Create app bundle structure +5. Generate Info.plist +6. Sign for simulator +7. Create launch script + +**Environment Variables**: +```bash +export CGO_ENABLED=1 +export GOOS=ios +export GOARCH=arm64 +export SDK_PATH=$(xcrun --sdk iphonesimulator --show-sdk-path) +``` + +### Simulator Deployment + +```bash +xcrun simctl install "$DEVICE_ID" "WailsIOSDemo.app" +xcrun simctl launch "$DEVICE_ID" "com.wails.iosdemo" +``` + +## Security Considerations + +### URL Scheme Security + +1. **Custom Scheme**: Use `wails://` to avoid conflicts +2. **Origin Validation**: Only serve to authorized WebViews +3. **No External Access**: Scheme handler only responds to app's WebView + +### JavaScript Execution + +1. **Input Validation**: Sanitize JS code before execution +2. **Sandboxed Execution**: WKWebView provides isolation +3. **No eval()**: Avoid dynamic code evaluation + +### Data Protection + +1. **In-Memory Only**: No temporary files on disk +2. **ATS Compliance**: App Transport Security enabled +3. **Secure Communication**: All data stays within app process + +## API Reference + +### Go API + +#### Application Functions + +```go +// Create new iOS application +app := application.New(application.Options{ + Name: "App Name", + Description: "App Description", +}) + +// Run the application +app.Run() + +// Execute JavaScript +app.ExecuteJavaScript(windowID, "console.log('Hello')") +``` + +#### Service Binding + +```go +type MyService struct{} + +func (s *MyService) Greet(name string) string { + return fmt.Sprintf("Hello, %s!", name) +} + +app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&MyService{}), + }, +}) +``` + +### JavaScript API + +#### Send Message to Go + +```javascript +window.webkit.messageHandlers.wails.postMessage({ + type: 'methodCall', + service: 'MyService', + method: 'Greet', + args: ['World'] +}); +``` + +#### Receive from Go + +```javascript +window.wailsCallback = function(data) { + console.log('Received:', data); +}; +``` + +### Objective-C Bridge API + +#### From Go to Objective-C + +```c +// Execute JavaScript +ios_execute_javascript(windowID, "alert('Hello')"); + +// Complete asset request +ios_complete_request(callbackID, htmlData, "text/html"); +``` + +#### From Objective-C to Go + +```c +// Serve asset request +ServeAssetRequest(windowID, urlString, callbackID); + +// Handle JavaScript message +HandleJSMessage(windowID, jsonMessage); +``` + +## Performance Metrics + +### Target Metrics + +- **WebView Creation**: < 100ms +- **Asset Request**: < 10ms for cached, < 50ms for first load +- **JS Execution**: < 5ms for simple scripts +- **Message Passing**: < 2ms round trip +- **Memory Usage**: < 50MB baseline +- **Battery Impact**: < 2% per hour active use + +### Monitoring + +1. **Xcode Instruments**: CPU, Memory, Energy profiling +2. **WebView Inspector**: JavaScript performance +3. **Go Profiling**: pprof for Go code analysis + +## Future Enhancements + +### Phase 1: Core Stability +- [ ] Production-ready error handling +- [ ] Comprehensive test suite +- [ ] Performance optimization + +### Phase 2: Feature Parity +- [ ] Multiple window support +- [ ] System tray integration +- [ ] Native menu implementation + +### Phase 3: iOS-Specific Features +- [ ] Widget extension support +- [ ] App Clip support +- [ ] ShareSheet integration +- [ ] Siri Shortcuts + +### Phase 4: Advanced Features +- [ ] Background task support +- [ ] Push notifications +- [ ] CloudKit integration +- [ ] Apple Watch companion app + +## Conclusion + +This architecture provides a solid foundation for iOS support in Wails v3. The design prioritizes battery efficiency, native performance, and seamless integration with the existing Wails ecosystem. The proof of concept demonstrates all four required capabilities: + +1. ✅ **WebView Creation**: Native WKWebView with optimized configuration +2. ✅ **Request Interception**: Custom scheme handler without network ports +3. ✅ **JavaScript Execution**: Bidirectional communication bridge +4. ✅ **iOS Simulator Support**: Complete build and deployment pipeline + +The architecture is designed to scale from this proof of concept to a full production implementation while maintaining the simplicity and elegance that Wails developers expect. \ No newline at end of file diff --git a/Taskfile.yaml b/Taskfile.yaml index 7cc165825..051da3e42 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -16,6 +16,11 @@ includes: dir: v3 optional: true + docs: + taskfile: docs + dir: docs + optional: true + tasks: contributors:check: cmds: diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 000000000..6240da8b1 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,21 @@ +# build output +dist/ +# generated types +.astro/ + +# dependencies +node_modules/ + +# logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + + +# environment variables +.env +.env.production + +# macOS-specific files +.DS_Store diff --git a/docs/.npmrc b/docs/.npmrc new file mode 100644 index 000000000..cffe8cdef --- /dev/null +++ b/docs/.npmrc @@ -0,0 +1 @@ +save-exact=true diff --git a/docs/.vscode/extensions.json b/docs/.vscode/extensions.json new file mode 100644 index 000000000..22a15055d --- /dev/null +++ b/docs/.vscode/extensions.json @@ -0,0 +1,4 @@ +{ + "recommendations": ["astro-build.astro-vscode"], + "unwantedRecommendations": [] +} diff --git a/docs/.vscode/launch.json b/docs/.vscode/launch.json new file mode 100644 index 000000000..d64220976 --- /dev/null +++ b/docs/.vscode/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "command": "./node_modules/.bin/astro dev", + "name": "Development server", + "request": "launch", + "type": "node-terminal" + } + ] +} diff --git a/docs/CNAME b/docs/CNAME new file mode 100644 index 000000000..23caa94af --- /dev/null +++ b/docs/CNAME @@ -0,0 +1 @@ +v3alpha.wails.io \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..108b2a1fa --- /dev/null +++ b/docs/README.md @@ -0,0 +1,65 @@ +# Wails v3 Documentation + +[![Built with Starlight](https://astro.badg.es/v2/built-with-starlight/tiny.svg)](https://starlight.astro.build) + +World-class documentation for Wails v3, redesigned following Netflix documentation principles. + +## 📚 Documentation Redesign (2025-10-01) + +This documentation has been completely redesigned to follow the **Netflix approach** to developer documentation: + +- **Problem-first framing** - Start with why, not what +- **Progressive disclosure** - Multiple entry points for different skill levels +- **Real production examples** - No toy code +- **Story-Code-Context pattern** - Why → How → When +- **Scannable content** - Clear structure, visual aids + +**Status:** Foundation complete (~20%), ready for content migration + +See [IMPLEMENTATION_SUMMARY.md](./IMPLEMENTATION_SUMMARY.md) for full details. + +## 🚀 Project Structure + +Inside of your Astro + Starlight project, you'll see the following folders and +files: + +```sh +. +├── public/ +├── src/ +│ ├── assets/ +│ ├── content/ +│ │ ├── docs/ +│ │ └── config.ts +│ └── env.d.ts +├── astro.config.mjs +├── package.json +└── tsconfig.json +``` + +Starlight looks for `.md` or `.mdx` files in the `src/content/docs/` directory. +Each file is exposed as a route based on its file name. + +Images can be added to `src/assets/` and embedded in Markdown with a relative +link. + +Static assets, like favicons, can be placed in the `public/` directory. + +## 🧞 Commands + +All commands are run from the root of the project, from a terminal: + +| Command | Action | +| :------------------------ | :----------------------------------------------- | +| `npm install` | Installs dependencies | +| `npm run dev` | Starts local dev server at `localhost:4321` | +| `npm run build` | Build your production site to `./dist/` | +| `npm run preview` | Preview your build locally, before deploying | +| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` | +| `npm run astro -- --help` | Get help using the Astro CLI | + +## 👀 Want to learn more? + +Check out [Starlight’s docs](https://starlight.astro.build/), read +[the Astro documentation](https://docs.astro.build), or jump into the +[Astro Discord server](https://astro.build/chat). diff --git a/docs/Taskfile.yml b/docs/Taskfile.yml new file mode 100644 index 000000000..9f7c01fd6 --- /dev/null +++ b/docs/Taskfile.yml @@ -0,0 +1,36 @@ +# https://taskfile.dev + +version: '3' + +vars: + # Change this to switch package managers: bun, npm, pnpm, yarn + PKG_MANAGER: npm + +tasks: + + setup: + summary: Setup the project (including D2 diagram tool) + preconditions: + - sh: '{{.PKG_MANAGER}} --version' + msg: "Looks like {{.PKG_MANAGER}} isn't installed." + - sh: 'go version' + msg: "Go is not installed. Install from https://go.dev/dl/" + cmds: + - '{{.PKG_MANAGER}} install' + - go install oss.terrastruct.com/d2@latest + - echo "✓ Setup complete. D2 installed to $(go env GOPATH)/bin/d2" + + dev: + summary: Run the dev server + deps: [setup] + cmds: + - '{{.PKG_MANAGER}} run dev' + + build: + summary: Build the docs + preconditions: + - sh: '{{.PKG_MANAGER}} --version' + msg: "Looks like {{.PKG_MANAGER}} isn't installed." + cmds: + - '{{.PKG_MANAGER}} run build' + diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs new file mode 100644 index 000000000..03133e2f5 --- /dev/null +++ b/docs/astro.config.mjs @@ -0,0 +1,351 @@ +// @ts-check +import { defineConfig } from "astro/config"; +import starlight from "@astrojs/starlight"; +import sitemap from "@astrojs/sitemap"; +import starlightLinksValidator from "starlight-links-validator"; +import starlightImageZoom from "starlight-image-zoom"; +import starlightBlog from "starlight-blog"; +import { authors } from "./src/content/authors"; +import d2 from 'astro-d2'; +import react from '@astrojs/react'; + +// https://astro.build/config +export default defineConfig({ + site: "https://wails.io", + trailingSlash: "ignore", + compressHTML: true, + output: "static", + build: { format: "directory" }, + devToolbar: { enabled: true }, + integrations: [ + react(), + d2(), + sitemap(), + starlight({ + title: "", + titleDelimiter: "", + logo: { + dark: "./src/assets/wails-logo-horizontal-dark.svg", + light: "./src/assets/wails-logo-horizontal-light.svg", + }, + favicon: "./public/favicon.svg", + description: "Build beautiful desktop applications using Go and modern web technologies.", + pagefind: true, + customCss: ["./src/stylesheets/extra.css"], + lastUpdated: true, + pagination: true, + editLink: { + baseUrl: "https://github.com/wailsapp/wails/edit/v3-alpha/docs", + }, + social: [ + { icon: 'github', label: 'GitHub', href: 'https://github.com/wailsapp/wails' }, + { icon: 'discord', label: 'Discord', href: 'https://discord.gg/JDdSxwjhGf' }, + { icon: 'x.com', label: 'X', href: 'https://x.com/wailsapp' }, + ], + head: [ + { + tag: 'script', + content: ` + document.addEventListener('DOMContentLoaded', () => { + const socialLinks = document.querySelector('.social-icons'); + if (socialLinks) { + const sponsorLink = document.createElement('a'); + sponsorLink.href = 'https://github.com/sponsors/leaanthony'; + sponsorLink.className = 'sl-flex'; + sponsorLink.title = 'Sponsor'; + sponsorLink.innerHTML = 'Sponsor'; + socialLinks.appendChild(sponsorLink); + } + }); + `, + }, + ], + defaultLocale: "root", + locales: { + root: { label: "English", lang: "en", dir: "ltr" }, + }, + plugins: [ + starlightImageZoom(), + starlightBlog({ + title: "Wails Blog", + authors: authors, + }), + ], + sidebar: [ + { label: "Home", link: "/" }, + + // Progressive Onboarding - Netflix Principle: Start with the problem + { label: "Why Wails?", link: "/quick-start/why-wails" }, + + { + label: "Quick Start", + collapsed: false, + items: [ + { label: "Installation", link: "/quick-start/installation" }, + { label: "Your First App", link: "/quick-start/first-app" }, + { label: "Next Steps", link: "/quick-start/next-steps" }, + ], + }, + + // Tutorials + { + label: "Tutorials", + collapsed: true, + autogenerate: { directory: "tutorials" }, + }, + + // Core Concepts + { + label: "Core Concepts", + collapsed: true, + items: [ + { label: "How Wails Works", link: "/concepts/architecture" }, + { label: "Manager API", link: "/concepts/manager-api" }, + { label: "Application Lifecycle", link: "/concepts/lifecycle" }, + { label: "Go-Frontend Bridge", link: "/concepts/bridge" }, + { label: "Build System", link: "/concepts/build-system" }, + ], + }, + + { + label: "Features", + collapsed: true, + items: [ + { + label: "Windows", + collapsed: true, + items: [ + { label: "Window Basics", link: "/features/windows/basics" }, + { label: "Window Options", link: "/features/windows/options" }, + { label: "Multiple Windows", link: "/features/windows/multiple" }, + { label: "Frameless Windows", link: "/features/windows/frameless" }, + { label: "Window Events", link: "/features/windows/events" }, + ], + }, + { + label: "Menus", + collapsed: true, + items: [ + { label: "Application Menus", link: "/features/menus/application" }, + { label: "Context Menus", link: "/features/menus/context" }, + { label: "System Tray Menus", link: "/features/menus/systray" }, + { label: "Menu Reference", link: "/features/menus/reference" }, + ], + }, + { + label: "Bindings & Services", + collapsed: true, + items: [ + { label: "Method Binding", link: "/features/bindings/methods" }, + { label: "Services", link: "/features/bindings/services" }, + { label: "Data Models", link: "/features/bindings/models" }, + { label: "Enums", link: "/features/bindings/enums" }, + { label: "Advanced Binding", link: "/features/bindings/advanced" }, + { label: "Best Practices", link: "/features/bindings/best-practices" }, + ], + }, + { + label: "Events", + collapsed: true, + items: [ + { label: "Event System", link: "/features/events/system" }, + { label: "Application Events", link: "/features/events/application" }, + { label: "Window Events", link: "/features/events/window" }, + { label: "Custom Events", link: "/features/events/custom" }, + ], + }, + { + label: "Dialogs", + collapsed: true, + items: [ + { label: "File Dialogs", link: "/features/dialogs/file" }, + { label: "Message Dialogs", link: "/features/dialogs/message" }, + { label: "Custom Dialogs", link: "/features/dialogs/custom" }, + ], + }, + { + label: "Clipboard", + collapsed: true, + autogenerate: { directory: "features/clipboard" }, + }, + { + label: "Browser", + collapsed: true, + autogenerate: { directory: "features/browser" }, + }, + { + label: "Drag & Drop", + collapsed: true, + autogenerate: { directory: "features/drag-and-drop" }, + }, + { + label: "Keyboard", + collapsed: true, + autogenerate: { directory: "features/keyboard" }, + }, + { + label: "Notifications", + collapsed: true, + autogenerate: { directory: "features/notifications" }, + }, + { + label: "Screens", + collapsed: true, + autogenerate: { directory: "features/screens" }, + }, + { + label: "Environment", + collapsed: true, + autogenerate: { directory: "features/environment" }, + }, + { + label: "Platform-Specific", + collapsed: true, + autogenerate: { directory: "features/platform" }, + }, + ], + }, + + // Guides - Task-oriented patterns (Netflix: When to use it, when not to use it) + { + label: "Guides", + collapsed: true, + items: [ + { + label: "Development", + collapsed: true, + items: [ + { label: "Project Structure", link: "/guides/dev/project-structure" }, + { label: "Development Workflow", link: "/guides/dev/workflow" }, + { label: "Debugging", link: "/guides/dev/debugging" }, + { label: "Testing", link: "/guides/dev/testing" }, + ], + }, + { + label: "Building & Packaging", + collapsed: true, + items: [ + { label: "Building Applications", link: "/guides/build/building" }, + { label: "Build Customization", link: "/guides/build/customization" }, + { label: "Cross-Platform Builds", link: "/guides/build/cross-platform" }, + { label: "Code Signing", link: "/guides/build/signing" }, + { label: "Windows Packaging", link: "/guides/build/windows" }, + { label: "macOS Packaging", link: "/guides/build/macos" }, + { label: "Linux Packaging", link: "/guides/build/linux" }, + { label: "MSIX Packaging", link: "/guides/build/msix" }, + ], + }, + { + label: "Distribution", + collapsed: true, + items: [ + { label: "Auto-Updates", link: "/guides/distribution/auto-updates" }, + { label: "File Associations", link: "/guides/distribution/file-associations" }, + { label: "Custom Protocols", link: "/guides/distribution/custom-protocols" }, + { label: "Single Instance", link: "/guides/distribution/single-instance" }, + ], + }, + { + label: "Integration Patterns", + collapsed: true, + items: [ + { label: "Using Gin Router", link: "/guides/patterns/gin-routing" }, + { label: "Gin Services", link: "/guides/patterns/gin-services" }, + { label: "Database Integration", link: "/guides/patterns/database" }, + { label: "REST APIs", link: "/guides/patterns/rest-api" }, + ], + }, + { + label: "Advanced Topics", + collapsed: true, + items: [ + { label: "Server Build", link: "/guides/server-build", badge: { text: "Experimental", variant: "caution" } }, + { label: "Custom Templates", link: "/guides/advanced/custom-templates" }, + { label: "WML (Wails Markup)", link: "/guides/advanced/wml" }, + { label: "Panic Handling", link: "/guides/advanced/panic-handling" }, + { label: "Security Best Practices", link: "/guides/advanced/security" }, + ], + }, + ], + }, + + // Reference - Comprehensive API docs (Netflix: Complete technical reference) + { + label: "API Reference", + collapsed: true, + items: [ + { label: "Overview", link: "/reference/overview" }, + { label: "Application", link: "/reference/application" }, + { label: "Window", link: "/reference/window" }, + { label: "Menu", link: "/reference/menu" }, + { label: "Events", link: "/reference/events" }, + { label: "Dialogs", link: "/reference/dialogs" }, + { label: "Frontend Runtime", link: "/reference/frontend-runtime" }, + { label: "CLI", link: "/reference/cli" }, + ], + }, + + // Contributing + { + label: "Contributing", + collapsed: true, + items: [ + { label: "Getting Started", link: "/contributing/getting-started" }, + { label: "Development Setup", link: "/contributing/setup" }, + { label: "Coding Standards", link: "/contributing/standards" }, + ], + }, + + // Migration & Troubleshooting + { + label: "Migration", + collapsed: true, + items: [ + { label: "From v2 to v3", link: "/migration/v2-to-v3" }, + { label: "From Electron", link: "/migration/from-electron" }, + ], + }, + + { + label: "Troubleshooting", + collapsed: true, + autogenerate: { directory: "troubleshooting" }, + }, + + // Community & Resources + { + label: "Community", + collapsed: true, + items: [ + { label: "Links", link: "/community/links" }, + { label: "Templates", link: "/community/templates" }, + { + label: "Showcase", + collapsed: true, + items: [ + { label: "Overview", link: "/community/showcase" }, + { + label: "Applications", + autogenerate: { + directory: "community/showcase", + collapsed: true, + }, + }, + ], + }, + ], + }, + + { label: "What's New", link: "/whats-new" }, + { label: "Status", link: "/status" }, + { label: "Changelog", link: "/changelog" }, + { + label: "Sponsor", + link: "https://github.com/sponsors/leaanthony", + badge: { text: "❤️" }, + }, + { label: "Credits", link: "/credits" }, + ], + }), + ], +}); diff --git a/docs/package-lock.json b/docs/package-lock.json new file mode 100644 index 000000000..428b7ec7b --- /dev/null +++ b/docs/package-lock.json @@ -0,0 +1,9082 @@ +{ + "name": "wails-docs", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "wails-docs", + "version": "0.0.1", + "dependencies": { + "@astrojs/check": "^0.9.4", + "@astrojs/react": "^4.1.0", + "@astrojs/starlight": "0.36.2", + "@types/react": "^19.0.1", + "@types/react-dom": "^19.0.2", + "astro": "^5.0.0", + "astro-d2": "^0.5.0", + "framer-motion": "^11.14.4", + "motion": "^11.14.4", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "sharp": "^0.33.5", + "starlight-blog": "0.25.1", + "starlight-image-zoom": "^0.9.0", + "starlight-links-validator": "^0.13.4", + "starlight-showcases": "^0.2.0", + "typescript": "^5.7.2" + } + }, + "node_modules/@astro-community/astro-embed-twitter": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/@astro-community/astro-embed-twitter/-/astro-embed-twitter-0.5.9.tgz", + "integrity": "sha512-bTIP/2LB3iEzlZ58L7dFyLJuWLeFDXgzZUQZKlWIfsXiKYqKIfLTQ01U10sh9UiHpm1M+4kOVPpue5LbUpJXHw==", + "license": "MIT", + "dependencies": { + "@astro-community/astro-embed-utils": "^0.1.5" + }, + "peerDependencies": { + "astro": "^2.0.0 || ^3.0.0-beta || ^4.0.0-beta || ^5.0.0-beta" + } + }, + "node_modules/@astro-community/astro-embed-utils": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@astro-community/astro-embed-utils/-/astro-embed-utils-0.1.5.tgz", + "integrity": "sha512-0RlP7J1YEWrguWDfEDsm4uDCXk4FKn0HHakmSOSwHLg6YR8WNEN/LGMGhhsxLc/mDqO2lRh1VqfJy+yPLLkzsQ==", + "license": "MIT", + "dependencies": { + "linkedom": "^0.18.12" + } + }, + "node_modules/@astro-community/astro-embed-youtube": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/@astro-community/astro-embed-youtube/-/astro-embed-youtube-0.5.9.tgz", + "integrity": "sha512-8Uk2SKbyZVb+jxwqSAMoEpQo+063XYwCI3yRy9cbkyHpu09mDabGZNTF5XrL8CKr3NtR5haBkeYK/kSuKUkJ/g==", + "license": "MIT", + "dependencies": { + "lite-youtube-embed": "^0.3.4" + }, + "peerDependencies": { + "astro": "^2.0.0 || ^3.0.0-beta || ^4.0.0-beta || ^5.0.0-beta" + } + }, + "node_modules/@astrojs/check": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/@astrojs/check/-/check-0.9.5.tgz", + "integrity": "sha512-88vc8n2eJ1Oua74yXSGo/8ABMeypfQPGEzuoAx2awL9Ju8cE6tZ2Rz9jVx5hIExHK5gKVhpxfZj4WXm7e32g1w==", + "license": "MIT", + "dependencies": { + "@astrojs/language-server": "^2.15.0", + "chokidar": "^4.0.1", + "kleur": "^4.1.5", + "yargs": "^17.7.2" + }, + "bin": { + "astro-check": "dist/bin.js" + }, + "peerDependencies": { + "typescript": "^5.0.0" + } + }, + "node_modules/@astrojs/compiler": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-2.13.0.tgz", + "integrity": "sha512-mqVORhUJViA28fwHYaWmsXSzLO9osbdZ5ImUfxBarqsYdMlPbqAqGJCxsNzvppp1BEzc1mJNjOVvQqeDN8Vspw==", + "license": "MIT" + }, + "node_modules/@astrojs/internal-helpers": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.7.5.tgz", + "integrity": "sha512-vreGnYSSKhAjFJCWAwe/CNhONvoc5lokxtRoZims+0wa3KbHBdPHSSthJsKxPd8d/aic6lWKpRTYGY/hsgK6EA==", + "license": "MIT" + }, + "node_modules/@astrojs/language-server": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/@astrojs/language-server/-/language-server-2.16.0.tgz", + "integrity": "sha512-oX2KkuIfEEM5d4/+lfuxy6usRDYko0S02YvtHFTrnqW0h9e4ElAfWZRKyqxWlwpuPdciBPKef5YJ7DFH3PPssw==", + "license": "MIT", + "dependencies": { + "@astrojs/compiler": "^2.10.3", + "@astrojs/yaml2ts": "^0.2.2", + "@jridgewell/sourcemap-codec": "^1.4.15", + "@volar/kit": "~2.4.23", + "@volar/language-core": "~2.4.23", + "@volar/language-server": "~2.4.23", + "@volar/language-service": "~2.4.23", + "fast-glob": "^3.2.12", + "muggle-string": "^0.4.1", + "volar-service-css": "0.0.66", + "volar-service-emmet": "0.0.66", + "volar-service-html": "0.0.66", + "volar-service-prettier": "0.0.66", + "volar-service-typescript": "0.0.66", + "volar-service-typescript-twoslash-queries": "0.0.66", + "volar-service-yaml": "0.0.66", + "vscode-html-languageservice": "^5.5.2", + "vscode-uri": "^3.1.0" + }, + "bin": { + "astro-ls": "bin/nodeServer.js" + }, + "peerDependencies": { + "prettier": "^3.0.0", + "prettier-plugin-astro": ">=0.11.0" + }, + "peerDependenciesMeta": { + "prettier": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + } + } + }, + "node_modules/@astrojs/markdown-remark": { + "version": "6.3.9", + "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-6.3.9.tgz", + "integrity": "sha512-hX2cLC/KW74Io1zIbn92kI482j9J7LleBLGCVU9EP3BeH5MVrnFawOnqD0t/q6D1Z+ZNeQG2gNKMslCcO36wng==", + "license": "MIT", + "dependencies": { + "@astrojs/internal-helpers": "0.7.5", + "@astrojs/prism": "3.3.0", + "github-slugger": "^2.0.0", + "hast-util-from-html": "^2.0.3", + "hast-util-to-text": "^4.0.2", + "import-meta-resolve": "^4.2.0", + "js-yaml": "^4.1.0", + "mdast-util-definitions": "^6.0.0", + "rehype-raw": "^7.0.0", + "rehype-stringify": "^10.0.1", + "remark-gfm": "^4.0.1", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.2", + "remark-smartypants": "^3.0.2", + "shiki": "^3.13.0", + "smol-toml": "^1.4.2", + "unified": "^11.0.5", + "unist-util-remove-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "unist-util-visit-parents": "^6.0.2", + "vfile": "^6.0.3" + } + }, + "node_modules/@astrojs/mdx": { + "version": "4.3.12", + "resolved": "https://registry.npmjs.org/@astrojs/mdx/-/mdx-4.3.12.tgz", + "integrity": "sha512-pL3CVPtuQrPnDhWjy7zqbOibNyPaxP4VpQS8T8spwKqKzauJ4yoKyNkVTD8jrP7EAJHmBhZ7PTmUGZqOpKKp8g==", + "license": "MIT", + "dependencies": { + "@astrojs/markdown-remark": "6.3.9", + "@mdx-js/mdx": "^3.1.1", + "acorn": "^8.15.0", + "es-module-lexer": "^1.7.0", + "estree-util-visit": "^2.0.0", + "hast-util-to-html": "^9.0.5", + "piccolore": "^0.1.3", + "rehype-raw": "^7.0.0", + "remark-gfm": "^4.0.1", + "remark-smartypants": "^3.0.2", + "source-map": "^0.7.6", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.3" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0" + }, + "peerDependencies": { + "astro": "^5.0.0" + } + }, + "node_modules/@astrojs/prism": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-3.3.0.tgz", + "integrity": "sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ==", + "license": "MIT", + "dependencies": { + "prismjs": "^1.30.0" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0" + } + }, + "node_modules/@astrojs/react": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@astrojs/react/-/react-4.4.2.tgz", + "integrity": "sha512-1tl95bpGfuaDMDn8O3x/5Dxii1HPvzjvpL2YTuqOOrQehs60I2DKiDgh1jrKc7G8lv+LQT5H15V6QONQ+9waeQ==", + "license": "MIT", + "dependencies": { + "@vitejs/plugin-react": "^4.7.0", + "ultrahtml": "^1.6.0", + "vite": "^6.4.1" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0" + }, + "peerDependencies": { + "@types/react": "^17.0.50 || ^18.0.21 || ^19.0.0", + "@types/react-dom": "^17.0.17 || ^18.0.6 || ^19.0.0", + "react": "^17.0.2 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@astrojs/rss": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@astrojs/rss/-/rss-4.0.14.tgz", + "integrity": "sha512-KCe1imDcADKOOuO/wtKOMDO/umsBD6DWF+94r5auna1jKl5fmlK9vzf+sjA3EyveXA/FoB3khtQ/u/tQgETmTw==", + "license": "MIT", + "dependencies": { + "fast-xml-parser": "^5.3.0", + "piccolore": "^0.1.3" + } + }, + "node_modules/@astrojs/sitemap": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@astrojs/sitemap/-/sitemap-3.6.0.tgz", + "integrity": "sha512-4aHkvcOZBWJigRmMIAJwRQXBS+ayoP5z40OklTXYXhUDhwusz+DyDl+nSshY6y9DvkVEavwNcFO8FD81iGhXjg==", + "license": "MIT", + "dependencies": { + "sitemap": "^8.0.0", + "stream-replace-string": "^2.0.0", + "zod": "^3.25.76" + } + }, + "node_modules/@astrojs/starlight": { + "version": "0.36.2", + "resolved": "https://registry.npmjs.org/@astrojs/starlight/-/starlight-0.36.2.tgz", + "integrity": "sha512-QR8NfO7+7DR13kBikhQwAj3IAoptLLNs9DkyKko2M2l3PrqpcpVUnw1JBJ0msGDIwE6tBbua2UeBND48mkh03w==", + "license": "MIT", + "peer": true, + "dependencies": { + "@astrojs/markdown-remark": "^6.3.1", + "@astrojs/mdx": "^4.2.3", + "@astrojs/sitemap": "^3.3.0", + "@pagefind/default-ui": "^1.3.0", + "@types/hast": "^3.0.4", + "@types/js-yaml": "^4.0.9", + "@types/mdast": "^4.0.4", + "astro-expressive-code": "^0.41.1", + "bcp-47": "^2.1.0", + "hast-util-from-html": "^2.0.1", + "hast-util-select": "^6.0.2", + "hast-util-to-string": "^3.0.0", + "hastscript": "^9.0.0", + "i18next": "^23.11.5", + "js-yaml": "^4.1.0", + "klona": "^2.0.6", + "mdast-util-directive": "^3.0.0", + "mdast-util-to-markdown": "^2.1.0", + "mdast-util-to-string": "^4.0.0", + "pagefind": "^1.3.0", + "rehype": "^13.0.1", + "rehype-format": "^5.0.0", + "remark-directive": "^3.0.0", + "ultrahtml": "^1.6.0", + "unified": "^11.0.5", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.2" + }, + "peerDependencies": { + "astro": "^5.5.0" + } + }, + "node_modules/@astrojs/telemetry": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.0.tgz", + "integrity": "sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ==", + "license": "MIT", + "dependencies": { + "ci-info": "^4.2.0", + "debug": "^4.4.0", + "dlv": "^1.1.3", + "dset": "^3.1.4", + "is-docker": "^3.0.0", + "is-wsl": "^3.1.0", + "which-pm-runs": "^1.1.0" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0" + } + }, + "node_modules/@astrojs/yaml2ts": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@astrojs/yaml2ts/-/yaml2ts-0.2.2.tgz", + "integrity": "sha512-GOfvSr5Nqy2z5XiwqTouBBpy5FyI6DEe+/g/Mk5am9SjILN1S5fOEvYK0GuWHg98yS/dobP4m8qyqw/URW35fQ==", + "license": "MIT", + "dependencies": { + "yaml": "^2.5.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@capsizecss/unpack": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@capsizecss/unpack/-/unpack-3.0.1.tgz", + "integrity": "sha512-8XqW8xGn++Eqqbz3e9wKuK7mxryeRjs4LOHLxbh2lwKeSbuNR4NFifDZT4KzvjU6HMOPbiNTsWpniK5EJfTWkg==", + "license": "MIT", + "dependencies": { + "fontkit": "^2.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.2.0.tgz", + "integrity": "sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@emmetio/abbreviation": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@emmetio/abbreviation/-/abbreviation-2.3.3.tgz", + "integrity": "sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA==", + "license": "MIT", + "dependencies": { + "@emmetio/scanner": "^1.0.4" + } + }, + "node_modules/@emmetio/css-abbreviation": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@emmetio/css-abbreviation/-/css-abbreviation-2.1.8.tgz", + "integrity": "sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw==", + "license": "MIT", + "dependencies": { + "@emmetio/scanner": "^1.0.4" + } + }, + "node_modules/@emmetio/css-parser": { + "version": "0.4.0", + "resolved": "git+ssh://git@github.com/ramya-rao-a/css-parser.git#370c480ac103bd17c7bcfb34bf5d577dc40d3660", + "license": "MIT", + "dependencies": { + "@emmetio/stream-reader": "^2.2.0", + "@emmetio/stream-reader-utils": "^0.1.0" + } + }, + "node_modules/@emmetio/html-matcher": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@emmetio/html-matcher/-/html-matcher-1.3.0.tgz", + "integrity": "sha512-NTbsvppE5eVyBMuyGfVu2CRrLvo7J4YHb6t9sBFLyY03WYhXET37qA4zOYUjBWFCRHO7pS1B9khERtY0f5JXPQ==", + "license": "ISC", + "dependencies": { + "@emmetio/scanner": "^1.0.0" + } + }, + "node_modules/@emmetio/scanner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@emmetio/scanner/-/scanner-1.0.4.tgz", + "integrity": "sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA==", + "license": "MIT" + }, + "node_modules/@emmetio/stream-reader": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@emmetio/stream-reader/-/stream-reader-2.2.0.tgz", + "integrity": "sha512-fXVXEyFA5Yv3M3n8sUGT7+fvecGrZP4k6FnWWMSZVQf69kAq0LLpaBQLGcPR30m3zMmKYhECP4k/ZkzvhEW5kw==", + "license": "MIT" + }, + "node_modules/@emmetio/stream-reader-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@emmetio/stream-reader-utils/-/stream-reader-utils-0.1.0.tgz", + "integrity": "sha512-ZsZ2I9Vzso3Ho/pjZFsmmZ++FWeEd/txqybHTm4OgaZzdS8V9V/YYWQwg5TC38Z7uLWUV1vavpLLbjJtKubR1A==", + "license": "MIT" + }, + "node_modules/@emnapi/runtime": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", + "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@expressive-code/core": { + "version": "0.41.3", + "resolved": "https://registry.npmjs.org/@expressive-code/core/-/core-0.41.3.tgz", + "integrity": "sha512-9qzohqU7O0+JwMEEgQhnBPOw5DtsQRBXhW++5fvEywsuX44vCGGof1SL5OvPElvNgaWZ4pFZAFSlkNOkGyLwSQ==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^4.0.4", + "hast-util-select": "^6.0.2", + "hast-util-to-html": "^9.0.1", + "hast-util-to-text": "^4.0.1", + "hastscript": "^9.0.0", + "postcss": "^8.4.38", + "postcss-nested": "^6.0.1", + "unist-util-visit": "^5.0.0", + "unist-util-visit-parents": "^6.0.1" + } + }, + "node_modules/@expressive-code/plugin-frames": { + "version": "0.41.3", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-frames/-/plugin-frames-0.41.3.tgz", + "integrity": "sha512-rFQtmf/3N2CK3Cq/uERweMTYZnBu+CwxBdHuOftEmfA9iBE7gTVvwpbh82P9ZxkPLvc40UMhYt7uNuAZexycRQ==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.41.3" + } + }, + "node_modules/@expressive-code/plugin-shiki": { + "version": "0.41.3", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-shiki/-/plugin-shiki-0.41.3.tgz", + "integrity": "sha512-RlTARoopzhFJIOVHLGvuXJ8DCEme/hjV+ZnRJBIxzxsKVpGPW4Oshqg9xGhWTYdHstTsxO663s0cdBLzZj9TQA==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.41.3", + "shiki": "^3.2.2" + } + }, + "node_modules/@expressive-code/plugin-text-markers": { + "version": "0.41.3", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-text-markers/-/plugin-text-markers-0.41.3.tgz", + "integrity": "sha512-SN8tkIzDpA0HLAscEYD2IVrfLiid6qEdE9QLlGVSxO1KEw7qYvjpbNBQjUjMr5/jvTJ7ys6zysU2vLPHE0sb2g==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.41.3" + } + }, + "node_modules/@img/colour": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", + "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.2.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mdx-js/mdx": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.1.tgz", + "integrity": "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdx": "^2.0.0", + "acorn": "^8.0.0", + "collapse-white-space": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-util-scope": "^1.0.0", + "estree-walker": "^3.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "markdown-extensions": "^2.0.0", + "recma-build-jsx": "^1.0.0", + "recma-jsx": "^1.0.0", + "recma-stringify": "^1.0.0", + "rehype-recma": "^1.0.0", + "remark-mdx": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "source-map": "^0.7.0", + "unified": "^11.0.0", + "unist-util-position-from-estree": "^2.0.0", + "unist-util-stringify-position": "^4.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@oslojs/encoding": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-1.1.0.tgz", + "integrity": "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==", + "license": "MIT" + }, + "node_modules/@pagefind/darwin-arm64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-arm64/-/darwin-arm64-1.4.0.tgz", + "integrity": "sha512-2vMqkbv3lbx1Awea90gTaBsvpzgRs7MuSgKDxW0m9oV1GPZCZbZBJg/qL83GIUEN2BFlY46dtUZi54pwH+/pTQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/darwin-x64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-x64/-/darwin-x64-1.4.0.tgz", + "integrity": "sha512-e7JPIS6L9/cJfow+/IAqknsGqEPjJnVXGjpGm25bnq+NPdoD3c/7fAwr1OXkG4Ocjx6ZGSCijXEV4ryMcH2E3A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/default-ui": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/default-ui/-/default-ui-1.4.0.tgz", + "integrity": "sha512-wie82VWn3cnGEdIjh4YwNESyS1G6vRHwL6cNjy9CFgNnWW/PGRjsLq300xjVH5sfPFK3iK36UxvIBymtQIEiSQ==", + "license": "MIT" + }, + "node_modules/@pagefind/freebsd-x64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/freebsd-x64/-/freebsd-x64-1.4.0.tgz", + "integrity": "sha512-WcJVypXSZ+9HpiqZjFXMUobfFfZZ6NzIYtkhQ9eOhZrQpeY5uQFqNWLCk7w9RkMUwBv1HAMDW3YJQl/8OqsV0Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@pagefind/linux-arm64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/linux-arm64/-/linux-arm64-1.4.0.tgz", + "integrity": "sha512-PIt8dkqt4W06KGmQjONw7EZbhDF+uXI7i0XtRLN1vjCUxM9vGPdtJc2mUyVPevjomrGz5M86M8bqTr6cgDp1Uw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/linux-x64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/linux-x64/-/linux-x64-1.4.0.tgz", + "integrity": "sha512-z4oddcWwQ0UHrTHR8psLnVlz6USGJ/eOlDPTDYZ4cI8TK8PgwRUPQZp9D2iJPNIPcS6Qx/E4TebjuGJOyK8Mmg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/windows-x64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/windows-x64/-/windows-x64-1.4.0.tgz", + "integrity": "sha512-NkT+YAdgS2FPCn8mIA9bQhiBs+xmniMGq1LFPDhcFn0+2yIUEiIG06t7bsZlhdjknEQRTSdT7YitP6fC5qwP0g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "license": "MIT" + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", + "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", + "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", + "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", + "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", + "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", + "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", + "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", + "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", + "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", + "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", + "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", + "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", + "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", + "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", + "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", + "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", + "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", + "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", + "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", + "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", + "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", + "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@shikijs/core": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.15.0.tgz", + "integrity": "sha512-8TOG6yG557q+fMsSVa8nkEDOZNTSxjbbR8l6lF2gyr6Np+jrPlslqDxQkN6rMXCECQ3isNPZAGszAfYoJOPGlg==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.15.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.5" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.15.0.tgz", + "integrity": "sha512-ZedbOFpopibdLmvTz2sJPJgns8Xvyabe2QbmqMTz07kt1pTzfEvKZc5IqPVO/XFiEbbNyaOpjPBkkr1vlwS+qg==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.15.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.3" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.15.0.tgz", + "integrity": "sha512-HnqFsV11skAHvOArMZdLBZZApRSYS4LSztk2K3016Y9VCyZISnlYUYsL2hzlS7tPqKHvNqmI5JSUJZprXloMvA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.15.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.15.0.tgz", + "integrity": "sha512-WpRvEFvkVvO65uKYW4Rzxs+IG0gToyM8SARQMtGGsH4GDMNZrr60qdggXrFOsdfOVssG/QQGEl3FnJ3EZ+8w8A==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.15.0" + } + }, + "node_modules/@shikijs/themes": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.15.0.tgz", + "integrity": "sha512-8ow2zWb1IDvCKjYb0KiLNrK4offFdkfNVPXb1OZykpLCzRU6j+efkY+Y7VQjNlNFXonSw+4AOdGYtmqykDbRiQ==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.15.0" + } + }, + "node_modules/@shikijs/types": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.15.0.tgz", + "integrity": "sha512-BnP+y/EQnhihgHy4oIAN+6FFtmfTekwOLsQbRw9hOKwqgNy8Bdsjq8B05oAt/ZgvIWWFrshV71ytOrlPfYjIJw==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "license": "MIT" + }, + "node_modules/@swc/helpers": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/fontkit": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@types/fontkit/-/fontkit-2.0.8.tgz", + "integrity": "sha512-wN+8bYxIpJf+5oZdrdtaX04qUuWHcKxcDEgRS9Qm9ZClSHjzEn13SxUC+5eRM+4yXIeTYk8mTzLAWGF64847ew==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", + "license": "MIT" + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdx": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", + "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==", + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/nlcst": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/nlcst/-/nlcst-2.0.3.tgz", + "integrity": "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/picomatch": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@types/picomatch/-/picomatch-2.3.3.tgz", + "integrity": "sha512-Yll76ZHikRFCyz/pffKGjrCwe/le2CDwOP5F210KQo27kpRE46U2rDnzikNlVn6/ezH3Mhn46bJMTfeVTtcYMg==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.6.tgz", + "integrity": "sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w==", + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "license": "MIT", + "peer": true, + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/sax": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", + "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/@volar/kit": { + "version": "2.4.23", + "resolved": "https://registry.npmjs.org/@volar/kit/-/kit-2.4.23.tgz", + "integrity": "sha512-YuUIzo9zwC2IkN7FStIcVl1YS9w5vkSFEZfPvnu0IbIMaR9WHhc9ZxvlT+91vrcSoRY469H2jwbrGqpG7m1KaQ==", + "license": "MIT", + "dependencies": { + "@volar/language-service": "2.4.23", + "@volar/typescript": "2.4.23", + "typesafe-path": "^0.2.2", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "typescript": "*" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.23", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.23.tgz", + "integrity": "sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ==", + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.23" + } + }, + "node_modules/@volar/language-server": { + "version": "2.4.23", + "resolved": "https://registry.npmjs.org/@volar/language-server/-/language-server-2.4.23.tgz", + "integrity": "sha512-k0iO+tybMGMMyrNdWOxgFkP0XJTdbH0w+WZlM54RzJU3WZSjHEupwL30klpM7ep4FO6qyQa03h+VcGHD4Q8gEg==", + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.23", + "@volar/language-service": "2.4.23", + "@volar/typescript": "2.4.23", + "path-browserify": "^1.0.1", + "request-light": "^0.7.0", + "vscode-languageserver": "^9.0.1", + "vscode-languageserver-protocol": "^3.17.5", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@volar/language-service": { + "version": "2.4.23", + "resolved": "https://registry.npmjs.org/@volar/language-service/-/language-service-2.4.23.tgz", + "integrity": "sha512-h5mU9DZ/6u3LCB9xomJtorNG6awBNnk9VuCioGsp6UtFiM8amvS5FcsaC3dabdL9zO0z+Gq9vIEMb/5u9K6jGQ==", + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.23", + "vscode-languageserver-protocol": "^3.17.5", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.23", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.23.tgz", + "integrity": "sha512-Z1Uc8IB57Lm6k7q6KIDu/p+JWtf3xsXJqAX/5r18hYOTpJyBn0KXUR8oTJ4WFYOcDzWC9n3IflGgHowx6U6z9Q==", + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.23", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.23.tgz", + "integrity": "sha512-lAB5zJghWxVPqfcStmAP1ZqQacMpe90UrP5RJ3arDyrhy4aCUQqmxPPLB2PWDKugvylmO41ljK7vZ+t6INMTag==", + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.23", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vscode/emmet-helper": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@vscode/emmet-helper/-/emmet-helper-2.11.0.tgz", + "integrity": "sha512-QLxjQR3imPZPQltfbWRnHU6JecWTF1QSWhx3GAKQpslx7y3Dp6sIIXhKjiUJ/BR9FX8PVthjr9PD6pNwOJfAzw==", + "license": "MIT", + "dependencies": { + "emmet": "^2.4.3", + "jsonc-parser": "^2.3.0", + "vscode-languageserver-textdocument": "^1.0.1", + "vscode-languageserver-types": "^3.15.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vscode/l10n": { + "version": "0.0.18", + "resolved": "https://registry.npmjs.org/@vscode/l10n/-/l10n-0.0.18.tgz", + "integrity": "sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==", + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-draft-04": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", + "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", + "license": "MIT", + "peerDependencies": { + "ajv": "^8.5.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "license": "ISC", + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-align/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/ansi-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-iterate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/array-iterate/-/array-iterate-2.0.1.tgz", + "integrity": "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/astring": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", + "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", + "license": "MIT", + "bin": { + "astring": "bin/astring" + } + }, + "node_modules/astro": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/astro/-/astro-5.16.0.tgz", + "integrity": "sha512-GaDRs2Mngpw3dr2vc085GnORh98NiXxwIjg/EoQQQl/icZt3Z7s0BRsYHDZ8swkZbOA6wZsqWJdrNirl+iKcDg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@astrojs/compiler": "^2.13.0", + "@astrojs/internal-helpers": "0.7.5", + "@astrojs/markdown-remark": "6.3.9", + "@astrojs/telemetry": "3.3.0", + "@capsizecss/unpack": "^3.0.1", + "@oslojs/encoding": "^1.1.0", + "@rollup/pluginutils": "^5.3.0", + "acorn": "^8.15.0", + "aria-query": "^5.3.2", + "axobject-query": "^4.1.0", + "boxen": "8.0.1", + "ci-info": "^4.3.1", + "clsx": "^2.1.1", + "common-ancestor-path": "^1.0.1", + "cookie": "^1.0.2", + "cssesc": "^3.0.0", + "debug": "^4.4.3", + "deterministic-object-hash": "^2.0.2", + "devalue": "^5.5.0", + "diff": "^5.2.0", + "dlv": "^1.1.3", + "dset": "^3.1.4", + "es-module-lexer": "^1.7.0", + "esbuild": "^0.25.0", + "estree-walker": "^3.0.3", + "flattie": "^1.1.1", + "fontace": "~0.3.1", + "github-slugger": "^2.0.0", + "html-escaper": "3.0.3", + "http-cache-semantics": "^4.2.0", + "import-meta-resolve": "^4.2.0", + "js-yaml": "^4.1.1", + "magic-string": "^0.30.21", + "magicast": "^0.5.1", + "mrmime": "^2.0.1", + "neotraverse": "^0.6.18", + "p-limit": "^6.2.0", + "p-queue": "^8.1.1", + "package-manager-detector": "^1.5.0", + "piccolore": "^0.1.3", + "picomatch": "^4.0.3", + "prompts": "^2.4.2", + "rehype": "^13.0.2", + "semver": "^7.7.3", + "shiki": "^3.15.0", + "smol-toml": "^1.5.0", + "svgo": "^4.0.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tsconfck": "^3.1.6", + "ultrahtml": "^1.6.0", + "unifont": "~0.6.0", + "unist-util-visit": "^5.0.0", + "unstorage": "^1.17.2", + "vfile": "^6.0.3", + "vite": "^6.4.1", + "vitefu": "^1.1.1", + "xxhash-wasm": "^1.1.0", + "yargs-parser": "^21.1.1", + "yocto-spinner": "^0.2.3", + "zod": "^3.25.76", + "zod-to-json-schema": "^3.24.6", + "zod-to-ts": "^1.2.0" + }, + "bin": { + "astro": "astro.js" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/astrodotbuild" + }, + "optionalDependencies": { + "sharp": "^0.34.0" + } + }, + "node_modules/astro-d2": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/astro-d2/-/astro-d2-0.5.2.tgz", + "integrity": "sha512-JN5kHalh/dQIZD6JG8y+WXY/j+K9NigyW7dLa+VbPeQnoNkRbvVms6847gqU5czojR7uzpArL7ug27vKae4lQg==", + "license": "MIT", + "dependencies": { + "unist-util-visit": "5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "astro": ">=4.0.0" + } + }, + "node_modules/astro-expressive-code": { + "version": "0.41.3", + "resolved": "https://registry.npmjs.org/astro-expressive-code/-/astro-expressive-code-0.41.3.tgz", + "integrity": "sha512-u+zHMqo/QNLE2eqYRCrK3+XMlKakv33Bzuz+56V1gs8H0y6TZ0hIi3VNbIxeTn51NLn+mJfUV/A0kMNfE4rANw==", + "license": "MIT", + "dependencies": { + "rehype-expressive-code": "^0.41.3" + }, + "peerDependencies": { + "astro": "^4.0.0-beta || ^5.0.0-beta || ^3.3.0" + } + }, + "node_modules/astro-remote": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/astro-remote/-/astro-remote-0.3.4.tgz", + "integrity": "sha512-jL5skNQLA0YBc1R3bVGXyHew3FqGqsT7AgLzWAVeTLzFkwVMUYvs4/lKJSmS7ygcF1GnHnoKG6++8GL9VtWwGQ==", + "license": "MIT", + "dependencies": { + "entities": "^4.5.0", + "marked": "^12.0.0", + "marked-footnote": "^1.2.2", + "marked-smartypants": "^1.1.6", + "ultrahtml": "^1.5.3" + }, + "engines": { + "node": ">=18.14.1" + } + }, + "node_modules/astro-remote/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/astro-remote/node_modules/marked": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-12.0.2.tgz", + "integrity": "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/astro/node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/astro/node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/astro/node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/astro/node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/astro/node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/astro/node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/astro/node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/astro/node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/astro/node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/astro/node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/base-64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", + "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==", + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.30", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.30.tgz", + "integrity": "sha512-aTUKW4ptQhS64+v2d6IkPzymEzzhw+G0bA1g3uBRV3+ntkH+svttKseW5IOR4Ed6NUVKqnY7qT3dKvzQ7io4AA==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/bcp-47": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-2.1.0.tgz", + "integrity": "sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/bcp-47-match": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz", + "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/boxen": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz", + "integrity": "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==", + "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^8.0.0", + "chalk": "^5.3.0", + "cli-boxes": "^3.0.0", + "string-width": "^7.2.0", + "type-fest": "^4.21.0", + "widest-line": "^5.0.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brotli": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", + "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.1.2" + } + }, + "node_modules/browserslist": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", + "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.8.25", + "caniuse-lite": "^1.0.30001754", + "electron-to-chromium": "^1.5.249", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.1.4" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/camelcase": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", + "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001756", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001756.tgz", + "integrity": "sha512-4HnCNKbMLkLdhJz3TToeVWHSnfJvPaq6vu/eRP0Ahub/07n484XHhBF5AJoSGHdVrS8tKFauUQz8Bp9P7LVx7A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ci-info": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", + "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/collapse-white-space": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", + "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/common-ancestor-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", + "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==", + "license": "ISC" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/cookie-es": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.2.tgz", + "integrity": "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==", + "license": "MIT" + }, + "node_modules/crossws": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.5.tgz", + "integrity": "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==", + "license": "MIT", + "dependencies": { + "uncrypto": "^0.1.3" + } + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-selector-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-3.2.0.tgz", + "integrity": "sha512-L1bdkNKUP5WYxiW5dW6vA2hd3sL8BdRNLy2FCX0rLVise4eNw9nBdeBuJHxlELieSE2H1f6bYQFfwVUwWCV9rQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, + "node_modules/css-tree": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.12.2", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "license": "MIT", + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "license": "CC0-1.0" + }, + "node_modules/cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", + "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "license": "MIT" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/deterministic-object-hash": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/deterministic-object-hash/-/deterministic-object-hash-2.0.2.tgz", + "integrity": "sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==", + "license": "MIT", + "dependencies": { + "base-64": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/devalue": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.5.0.tgz", + "integrity": "sha512-69sM5yrHfFLJt0AZ9QqZXGCPfJ7fQjvpln3Rq5+PS03LD32Ost1Q9N+eEnaQwGRIriKkMImXD56ocjQmfjbV3w==", + "license": "MIT" + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dfa": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz", + "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==", + "license": "MIT" + }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/direction": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/direction/-/direction-2.0.1.tgz", + "integrity": "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==", + "license": "MIT", + "bin": { + "direction": "cli.js" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "license": "MIT" + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dset": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.4.tgz", + "integrity": "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.259", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.259.tgz", + "integrity": "sha512-I+oLXgpEJzD6Cwuwt1gYjxsDmu/S/Kd41mmLA3O+/uH2pFRO/DvOjUyGozL8j3KeLV6WyZ7ssPwELMsXCcsJAQ==", + "license": "ISC" + }, + "node_modules/emmet": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/emmet/-/emmet-2.4.11.tgz", + "integrity": "sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ==", + "license": "MIT", + "workspaces": [ + "./packages/scanner", + "./packages/abbreviation", + "./packages/css-abbreviation", + "./" + ], + "dependencies": { + "@emmetio/abbreviation": "^2.3.3", + "@emmetio/css-abbreviation": "^2.1.8" + } + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "license": "MIT" + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "license": "MIT" + }, + "node_modules/esast-util-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", + "integrity": "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esast-util-from-js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", + "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "acorn": "^8.0.0", + "esast-util-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-util-attach-comments": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", + "integrity": "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-build-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz", + "integrity": "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-walker": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-scope": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.0.tgz", + "integrity": "sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-to-js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", + "integrity": "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "astring": "^1.8.0", + "source-map": "^0.7.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-visit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", + "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, + "node_modules/expressive-code": { + "version": "0.41.3", + "resolved": "https://registry.npmjs.org/expressive-code/-/expressive-code-0.41.3.tgz", + "integrity": "sha512-YLnD62jfgBZYrXIPQcJ0a51Afv9h8VlWqEGK9uU2T5nL/5rb8SnA86+7+mgCZe5D34Tff5RNEA5hjNVJYHzrFg==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.41.3", + "@expressive-code/plugin-frames": "^0.41.3", + "@expressive-code/plugin-shiki": "^0.41.3", + "@expressive-code/plugin-text-markers": "^0.41.3" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fast-xml-parser": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.2.tgz", + "integrity": "sha512-n8v8b6p4Z1sMgqRmqLJm3awW4NX7NkaKPfb3uJIBTSH7Pdvufi3PQ3/lJLQrvxcMYl7JI2jnDO90siPEpD8JBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flattie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flattie/-/flattie-1.1.1.tgz", + "integrity": "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/fontace": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/fontace/-/fontace-0.3.1.tgz", + "integrity": "sha512-9f5g4feWT1jWT8+SbL85aLIRLIXUaDygaM2xPXRmzPYxrOMNok79Lr3FGJoKVNKibE0WCunNiEVG2mwuE+2qEg==", + "license": "MIT", + "dependencies": { + "@types/fontkit": "^2.0.8", + "fontkit": "^2.0.4" + } + }, + "node_modules/fontkit": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-2.0.4.tgz", + "integrity": "sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==", + "license": "MIT", + "dependencies": { + "@swc/helpers": "^0.5.12", + "brotli": "^1.3.2", + "clone": "^2.1.2", + "dfa": "^1.2.0", + "fast-deep-equal": "^3.1.3", + "restructure": "^3.0.0", + "tiny-inflate": "^1.0.3", + "unicode-properties": "^1.4.0", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/framer-motion": { + "version": "11.18.2", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.18.2.tgz", + "integrity": "sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w==", + "license": "MIT", + "dependencies": { + "motion-dom": "^11.18.1", + "motion-utils": "^11.18.1", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", + "license": "ISC" + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/h3": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.4.tgz", + "integrity": "sha512-z5cFQWDffyOe4vQ9xIqNfCZdV4p//vy6fBnr8Q1AWnVZ0teurKMG66rLj++TKwKPUP3u7iMUvrvKaEUiQw2QWQ==", + "license": "MIT", + "dependencies": { + "cookie-es": "^1.2.2", + "crossws": "^0.3.5", + "defu": "^6.1.4", + "destr": "^2.0.5", + "iron-webcrypto": "^1.2.1", + "node-mock-http": "^1.0.2", + "radix3": "^1.1.2", + "ufo": "^1.6.1", + "uncrypto": "^0.1.3" + } + }, + "node_modules/hast-util-embedded": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-embedded/-/hast-util-embedded-3.0.0.tgz", + "integrity": "sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-is-element": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-format": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hast-util-format/-/hast-util-format-1.1.0.tgz", + "integrity": "sha512-yY1UDz6bC9rDvCWHpx12aIBGRG7krurX0p0Fm6pT547LwDIZZiNr8a+IHDogorAdreULSEzP82Nlv5SZkHZcjA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-minify-whitespace": "^1.0.0", + "hast-util-phrasing": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "html-whitespace-sensitive-tag-names": "^3.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", + "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", + "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^9.0.0", + "property-information": "^7.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-has-property": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz", + "integrity": "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-body-ok-link": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-is-body-ok-link/-/hast-util-is-body-ok-link-3.0.1.tgz", + "integrity": "sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-minify-whitespace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hast-util-minify-whitespace/-/hast-util-minify-whitespace-1.0.1.tgz", + "integrity": "sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-phrasing": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-phrasing/-/hast-util-phrasing-3.0.1.tgz", + "integrity": "sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-is-body-ok-link": "^3.0.0", + "hast-util-is-element": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", + "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-select": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/hast-util-select/-/hast-util-select-6.0.4.tgz", + "integrity": "sha512-RqGS1ZgI0MwxLaKLDxjprynNzINEkRHY2i8ln4DDjgv9ZhcYVIHN9rlpiYsqtFwrgpYU361SyWDQcGNIBVu3lw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "bcp-47-match": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "css-selector-parser": "^3.0.0", + "devlop": "^1.0.0", + "direction": "^2.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-to-string": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "nth-check": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-estree": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz", + "integrity": "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-attach-comments": "^3.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz", + "integrity": "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5/node_modules/property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/hast-util-to-string": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz", + "integrity": "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-text": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", + "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/html-escaper": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", + "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", + "license": "MIT" + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/html-whitespace-sensitive-tag-names": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-whitespace-sensitive-tag-names/-/html-whitespace-sensitive-tag-names-3.0.1.tgz", + "integrity": "sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/htmlparser2": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz", + "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.1", + "entities": "^6.0.0" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "license": "BSD-2-Clause" + }, + "node_modules/i18next": { + "version": "23.16.8", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.8.tgz", + "integrity": "sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, + "node_modules/iron-webcrypto": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", + "integrity": "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/brc-dd" + } + }, + "node_modules/is-absolute-url": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-4.0.1.tgz", + "integrity": "sha512-/51/TKE88Lmm7Gc4/8btclNXWS+g50wXhYJq8HWIBAGUBnoAdRu1aXeh364t/O7wXDAcTJDP8PNuNKWUDWie+A==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", + "license": "MIT" + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.3.1.tgz", + "integrity": "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==", + "license": "MIT" + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/linkedom": { + "version": "0.18.12", + "resolved": "https://registry.npmjs.org/linkedom/-/linkedom-0.18.12.tgz", + "integrity": "sha512-jalJsOwIKuQJSeTvsgzPe9iJzyfVaEJiEXl+25EkKevsULHvMJzpNqwvj1jOESWdmgKDiXObyjOYwlUqG7wo1Q==", + "license": "ISC", + "dependencies": { + "css-select": "^5.1.0", + "cssom": "^0.5.0", + "html-escaper": "^3.0.3", + "htmlparser2": "^10.0.0", + "uhyphen": "^0.2.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "canvas": ">= 2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/lite-youtube-embed": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/lite-youtube-embed/-/lite-youtube-embed-0.3.4.tgz", + "integrity": "sha512-aXgxpwK7AIW58GEbRzA8EYaY4LWvF3FKak6B9OtSJmuNyLhX2ouD4cMTxz/yR5HFInhknaYd2jLWOTRTvT8oAw==", + "license": "Apache-2.0" + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.1.tgz", + "integrity": "sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "source-map-js": "^1.2.1" + } + }, + "node_modules/markdown-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", + "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/marked": { + "version": "15.0.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", + "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", + "license": "MIT", + "peer": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/marked-footnote": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/marked-footnote/-/marked-footnote-1.4.0.tgz", + "integrity": "sha512-fZTxAhI1TcLEs5UOjCfYfTHpyKGaWQevbxaGTEA68B51l7i87SctPFtHETYqPkEN0ka5opvy4Dy1l/yXVC+hmg==", + "license": "MIT", + "peerDependencies": { + "marked": ">=7.0.0" + } + }, + "node_modules/marked-plaintify": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/marked-plaintify/-/marked-plaintify-1.1.1.tgz", + "integrity": "sha512-r3kMKArhfo2H3lD4ctFq/OJTzM0uNvXHh7FBTI1hMDpf4Ac1djjtq4g8NfTBWMxWLmaEz3KL1jCkLygik3gExA==", + "license": "MIT", + "peerDependencies": { + "marked": ">=13.0.0" + } + }, + "node_modules/marked-smartypants": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/marked-smartypants/-/marked-smartypants-1.1.11.tgz", + "integrity": "sha512-Jt0eq/6rf9oXDfEKPzQ0z7UzVWcEAK3L6QBBQzbwV8bT304OvPVLTqpH3yvkSung9foOM4s120TMHEHP76Metg==", + "license": "MIT", + "dependencies": { + "smartypants": "^0.2.2" + }, + "peerDependencies": { + "marked": ">=4 <18" + } + }, + "node_modules/mdast-util-definitions": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz", + "integrity": "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-directive": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.1.0.tgz", + "integrity": "sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", + "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdn-data": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "license": "CC0-1.0" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-directive": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz", + "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-expression": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz", + "integrity": "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-jsx": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz", + "integrity": "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-md": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", + "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", + "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", + "license": "MIT", + "dependencies": { + "acorn": "^8.0.0", + "acorn-jsx": "^5.0.0", + "micromark-extension-mdx-expression": "^3.0.0", + "micromark-extension-mdx-jsx": "^3.0.0", + "micromark-extension-mdx-md": "^2.0.0", + "micromark-extension-mdxjs-esm": "^3.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs-esm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", + "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-mdx-expression": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz", + "integrity": "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-events-to-acorn": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz", + "integrity": "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/motion": { + "version": "11.18.2", + "resolved": "https://registry.npmjs.org/motion/-/motion-11.18.2.tgz", + "integrity": "sha512-JLjvFDuFr42NFtcVoMAyC2sEjnpA8xpy6qWPyzQvCloznAyQ8FIXioxWfHiLtgYhoVpfUqSWpn1h9++skj9+Wg==", + "license": "MIT", + "dependencies": { + "framer-motion": "^11.18.2", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/motion-dom": { + "version": "11.18.1", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.18.1.tgz", + "integrity": "sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==", + "license": "MIT", + "dependencies": { + "motion-utils": "^11.18.1" + } + }, + "node_modules/motion-utils": { + "version": "11.18.1", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-11.18.1.tgz", + "integrity": "sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==", + "license": "MIT" + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/neotraverse": { + "version": "0.6.18", + "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-0.6.18.tgz", + "integrity": "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/nlcst-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-4.0.0.tgz", + "integrity": "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "license": "MIT" + }, + "node_modules/node-mock-http": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.3.tgz", + "integrity": "sha512-jN8dK25fsfnMrVsEhluUTPkBFY+6ybu7jSB1n+ri/vOGjJxU8J9CZhpSGkHXSkFjtUhbmoncG/YG9ta5Ludqog==", + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/ofetch": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.5.1.tgz", + "integrity": "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==", + "license": "MIT", + "dependencies": { + "destr": "^2.0.5", + "node-fetch-native": "^1.6.7", + "ufo": "^1.6.1" + } + }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "license": "MIT" + }, + "node_modules/oniguruma-parser": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz", + "integrity": "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==", + "license": "MIT" + }, + "node_modules/oniguruma-to-es": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.4.tgz", + "integrity": "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==", + "license": "MIT", + "dependencies": { + "oniguruma-parser": "^0.12.1", + "regex": "^6.0.1", + "regex-recursion": "^6.0.2" + } + }, + "node_modules/p-limit": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.2.0.tgz", + "integrity": "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-8.1.1.tgz", + "integrity": "sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.1", + "p-timeout": "^6.1.2" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz", + "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-manager-detector": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.5.0.tgz", + "integrity": "sha512-uBj69dVlYe/+wxj8JOpr97XfsxH/eumMt6HqjNTmJDf/6NO9s+0uxeOneIz3AsPt2m6y9PqzDzd3ATcU17MNfw==", + "license": "MIT" + }, + "node_modules/pagefind": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/pagefind/-/pagefind-1.4.0.tgz", + "integrity": "sha512-z2kY1mQlL4J8q5EIsQkLzQjilovKzfNVhX8De6oyE6uHpfFtyBaqUpcl/XzJC/4fjD8vBDyh1zolimIcVrCn9g==", + "license": "MIT", + "bin": { + "pagefind": "lib/runner/bin.cjs" + }, + "optionalDependencies": { + "@pagefind/darwin-arm64": "1.4.0", + "@pagefind/darwin-x64": "1.4.0", + "@pagefind/freebsd-x64": "1.4.0", + "@pagefind/linux-arm64": "1.4.0", + "@pagefind/linux-x64": "1.4.0", + "@pagefind/windows-x64": "1.4.0" + } + }, + "node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", + "license": "MIT" + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/parse-latin": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse-latin/-/parse-latin-7.0.0.tgz", + "integrity": "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "@types/unist": "^3.0.0", + "nlcst-to-string": "^4.0.0", + "unist-util-modify-children": "^4.0.0", + "unist-util-visit-children": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "license": "MIT" + }, + "node_modules/piccolore": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/piccolore/-/piccolore-0.1.3.tgz", + "integrity": "sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw==", + "license": "ISC" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "license": "MIT", + "peer": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prompts/node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/radix3": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", + "integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==", + "license": "MIT" + }, + "node_modules/react": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", + "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", + "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.0" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/recma-build-jsx": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz", + "integrity": "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-build-jsx": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-jsx": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.1.tgz", + "integrity": "sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==", + "license": "MIT", + "dependencies": { + "acorn-jsx": "^5.0.0", + "estree-util-to-js": "^2.0.0", + "recma-parse": "^1.0.0", + "recma-stringify": "^1.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/recma-parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-parse/-/recma-parse-1.0.0.tgz", + "integrity": "sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "esast-util-from-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-stringify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-stringify/-/recma-stringify-1.0.0.tgz", + "integrity": "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-to-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.0.1.tgz", + "integrity": "sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "license": "MIT" + }, + "node_modules/rehype": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/rehype/-/rehype-13.0.2.tgz", + "integrity": "sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "rehype-parse": "^9.0.0", + "rehype-stringify": "^10.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-expressive-code": { + "version": "0.41.3", + "resolved": "https://registry.npmjs.org/rehype-expressive-code/-/rehype-expressive-code-0.41.3.tgz", + "integrity": "sha512-8d9Py4c/V6I/Od2VIXFAdpiO2kc0SV2qTJsRAaqSIcM9aruW4ASLNe2kOEo1inXAAkIhpFzAHTc358HKbvpNUg==", + "license": "MIT", + "dependencies": { + "expressive-code": "^0.41.3" + } + }, + "node_modules/rehype-format": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/rehype-format/-/rehype-format-5.0.1.tgz", + "integrity": "sha512-zvmVru9uB0josBVpr946OR8ui7nJEdzZobwLOOqHb/OOD88W0Vk2SqLwoVOj0fM6IPCCO6TaV9CvQvJMWwukFQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-format": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-parse": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.1.tgz", + "integrity": "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-html": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-raw": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", + "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-raw": "^9.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-recma": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz", + "integrity": "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "hast-util-to-estree": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-stringify": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.1.tgz", + "integrity": "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-to-html": "^9.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-directive": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/remark-directive/-/remark-directive-3.0.1.tgz", + "integrity": "sha512-gwglrEQEZcZYgVyG1tQuA+h58EZfq5CSULw7J90AFuCTyib1thgHPoqQ+h9iFvU6R+vnZ5oNFQR5QKgGpk741A==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-directive": "^3.0.0", + "micromark-extension-directive": "^3.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-mdx": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.1.tgz", + "integrity": "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==", + "license": "MIT", + "dependencies": { + "mdast-util-mdx": "^3.0.0", + "micromark-extension-mdxjs": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-smartypants": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/remark-smartypants/-/remark-smartypants-3.0.2.tgz", + "integrity": "sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA==", + "license": "MIT", + "dependencies": { + "retext": "^9.0.0", + "retext-smartypants": "^6.0.0", + "unified": "^11.0.4", + "unist-util-visit": "^5.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/request-light": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/request-light/-/request-light-0.7.0.tgz", + "integrity": "sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q==", + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/restructure": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/restructure/-/restructure-3.0.2.tgz", + "integrity": "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==", + "license": "MIT" + }, + "node_modules/retext": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/retext/-/retext-9.0.0.tgz", + "integrity": "sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "retext-latin": "^4.0.0", + "retext-stringify": "^4.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-latin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/retext-latin/-/retext-latin-4.0.0.tgz", + "integrity": "sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "parse-latin": "^7.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-smartypants": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/retext-smartypants/-/retext-smartypants-6.2.0.tgz", + "integrity": "sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "nlcst-to-string": "^4.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-stringify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/retext-stringify/-/retext-stringify-4.0.0.tgz", + "integrity": "sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "nlcst-to-string": "^4.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", + "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.53.3", + "@rollup/rollup-android-arm64": "4.53.3", + "@rollup/rollup-darwin-arm64": "4.53.3", + "@rollup/rollup-darwin-x64": "4.53.3", + "@rollup/rollup-freebsd-arm64": "4.53.3", + "@rollup/rollup-freebsd-x64": "4.53.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", + "@rollup/rollup-linux-arm-musleabihf": "4.53.3", + "@rollup/rollup-linux-arm64-gnu": "4.53.3", + "@rollup/rollup-linux-arm64-musl": "4.53.3", + "@rollup/rollup-linux-loong64-gnu": "4.53.3", + "@rollup/rollup-linux-ppc64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-musl": "4.53.3", + "@rollup/rollup-linux-s390x-gnu": "4.53.3", + "@rollup/rollup-linux-x64-gnu": "4.53.3", + "@rollup/rollup-linux-x64-musl": "4.53.3", + "@rollup/rollup-openharmony-arm64": "4.53.3", + "@rollup/rollup-win32-arm64-msvc": "4.53.3", + "@rollup/rollup-win32-ia32-msvc": "4.53.3", + "@rollup/rollup-win32-x64-gnu": "4.53.3", + "@rollup/rollup-win32-x64-msvc": "4.53.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/sax": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz", + "integrity": "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==", + "license": "BlueOak-1.0.0" + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/sharp": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5" + } + }, + "node_modules/sharp/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shiki": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.15.0.tgz", + "integrity": "sha512-kLdkY6iV3dYbtPwS9KXU7mjfmDm25f5m0IPNFnaXO7TBPcvbUOY72PYXSuSqDzwp+vlH/d7MXpHlKO/x+QoLXw==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "3.15.0", + "@shikijs/engine-javascript": "3.15.0", + "@shikijs/engine-oniguruma": "3.15.0", + "@shikijs/langs": "3.15.0", + "@shikijs/themes": "3.15.0", + "@shikijs/types": "3.15.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + }, + "node_modules/sitemap": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-8.0.2.tgz", + "integrity": "sha512-LwktpJcyZDoa0IL6KT++lQ53pbSrx2c9ge41/SeLTyqy2XUNA6uR4+P9u5IVo5lPeL2arAcOKn1aZAxoYbCKlQ==", + "license": "MIT", + "dependencies": { + "@types/node": "^17.0.5", + "@types/sax": "^1.2.1", + "arg": "^5.0.0", + "sax": "^1.4.1" + }, + "bin": { + "sitemap": "dist/cli.js" + }, + "engines": { + "node": ">=14.0.0", + "npm": ">=6.0.0" + } + }, + "node_modules/sitemap/node_modules/@types/node": { + "version": "17.0.45", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", + "license": "MIT" + }, + "node_modules/smartypants": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/smartypants/-/smartypants-0.2.2.tgz", + "integrity": "sha512-TzobUYoEft/xBtb2voRPryAUIvYguG0V7Tt3de79I1WfXgCwelqVsGuZSnu3GFGRZhXR90AeEYIM+icuB/S06Q==", + "license": "BSD-3-Clause", + "bin": { + "smartypants": "bin/smartypants.js", + "smartypantsu": "bin/smartypantsu.js" + } + }, + "node_modules/smol-toml": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.5.2.tgz", + "integrity": "sha512-QlaZEqcAH3/RtNyet1IPIYPsEWAaYyXXv1Krsi+1L/QHppjX4Ifm8MQsBISz9vE8cHicIq3clogsheili5vhaQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/starlight-blog": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/starlight-blog/-/starlight-blog-0.25.1.tgz", + "integrity": "sha512-T/4x+9brr0r2QbhxQIdesvsSuOdqLNFtE/MGhoDyGt73KdBTT3EPUq/CgKY+yTWq1QhKtw/GzMox6qbsqEBHXA==", + "license": "MIT", + "dependencies": { + "@astrojs/markdown-remark": "^6.3.1", + "@astrojs/mdx": "^4.0.8", + "@astrojs/rss": "^4.0.11", + "astro-remote": "^0.3.3", + "github-slugger": "^2.0.0", + "hast-util-from-html": "^2.0.3", + "hast-util-to-html": "^9.0.5", + "hast-util-to-string": "^3.0.1", + "marked": "^15.0.4", + "marked-plaintify": "^1.1.1", + "mdast-util-mdx-expression": "^2.0.1", + "unist-util-is": "^6.0.0", + "unist-util-remove": "^4.0.0", + "unist-util-visit": "^5.0.0" + }, + "engines": { + "node": ">=18.20.8" + }, + "peerDependencies": { + "@astrojs/starlight": ">=0.33.0" + } + }, + "node_modules/starlight-image-zoom": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/starlight-image-zoom/-/starlight-image-zoom-0.9.0.tgz", + "integrity": "sha512-XG87T80g5hsT6dvtNk9OKx0gD+M8lsloVTApQYnxdc3JD8lQBfu2kCsrwkyrwXZRtV7JRyd0PDHwx1UFmGmI1g==", + "license": "MIT", + "dependencies": { + "rehype-raw": "7.0.0", + "unist-util-visit": "5.0.0", + "unist-util-visit-parents": "6.0.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@astrojs/starlight": ">=0.22.0" + } + }, + "node_modules/starlight-image-zoom/node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/starlight-links-validator": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/starlight-links-validator/-/starlight-links-validator-0.13.4.tgz", + "integrity": "sha512-LdmLbJyPHVrSUhcuxiP3pJNnW8zRcOg/32C996Ic0LOCKbB8vylqHLvAMdIhT67FvEV4eAROun+2wTVU2J156A==", + "license": "MIT", + "dependencies": { + "@types/picomatch": "2.3.3", + "github-slugger": "2.0.0", + "hast-util-from-html": "2.0.1", + "hast-util-has-property": "3.0.0", + "is-absolute-url": "4.0.1", + "kleur": "4.1.5", + "mdast-util-to-string": "4.0.0", + "picomatch": "4.0.2", + "unist-util-visit": "5.0.0" + }, + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "@astrojs/starlight": ">=0.15.0", + "astro": ">=4.0.0" + } + }, + "node_modules/starlight-links-validator/node_modules/hast-util-from-html": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.1.tgz", + "integrity": "sha512-RXQBLMl9kjKVNkJTIO6bZyb2n+cUH8LFaSSzo82jiLT6Tfc+Pt7VQCS+/h3YwG4jaNE2TA2sdJisGWR+aJrp0g==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/starlight-links-validator/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/starlight-showcases": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/starlight-showcases/-/starlight-showcases-0.2.0.tgz", + "integrity": "sha512-YWJuTqArkUdVJV85VKZJ0BvKCQRu1SKtH/Cr5t6G/oIfI4IptWc92E7BmiuNnpuQ2U7TczTRidCYurPrbgQQVA==", + "license": "MIT", + "dependencies": { + "@astro-community/astro-embed-twitter": "^0.5.4", + "@astro-community/astro-embed-youtube": "^0.5.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@astrojs/starlight": ">=0.23.0" + } + }, + "node_modules/stream-replace-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/stream-replace-string/-/stream-replace-string-2.0.0.tgz", + "integrity": "sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==", + "license": "MIT" + }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strnum": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", + "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, + "node_modules/svgo": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.0.tgz", + "integrity": "sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==", + "license": "MIT", + "dependencies": { + "commander": "^11.1.0", + "css-select": "^5.1.0", + "css-tree": "^3.0.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.1.1", + "sax": "^1.4.1" + }, + "bin": { + "svgo": "bin/svgo.js" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/tsconfck": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", + "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==", + "license": "MIT", + "bin": { + "tsconfck": "bin/tsconfck.js" + }, + "engines": { + "node": "^18 || >=20" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typesafe-path": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/typesafe-path/-/typesafe-path-0.2.2.tgz", + "integrity": "sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA==", + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-auto-import-cache": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/typescript-auto-import-cache/-/typescript-auto-import-cache-0.3.6.tgz", + "integrity": "sha512-RpuHXrknHdVdK7wv/8ug3Fr0WNsNi5l5aB8MYYuXhq2UH5lnEB1htJ1smhtD5VeCsGr2p8mUDtd83LCQDFVgjQ==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.8" + } + }, + "node_modules/typescript-auto-import-cache/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ufo": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "license": "MIT" + }, + "node_modules/uhyphen": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/uhyphen/-/uhyphen-0.2.0.tgz", + "integrity": "sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA==", + "license": "ISC" + }, + "node_modules/ultrahtml": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.6.0.tgz", + "integrity": "sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw==", + "license": "MIT" + }, + "node_modules/uncrypto": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz", + "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT" + }, + "node_modules/unicode-properties": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", + "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.0", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/unicode-trie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", + "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", + "license": "MIT", + "dependencies": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unifont": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/unifont/-/unifont-0.6.0.tgz", + "integrity": "sha512-5Fx50fFQMQL5aeHyWnZX9122sSLckcDvcfFiBf3QYeHa7a1MKJooUy52b67moi2MJYkrfo/TWY+CoLdr/w0tTA==", + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0", + "ofetch": "^1.4.1", + "ohash": "^2.0.0" + } + }, + "node_modules/unist-util-find-after": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-modify-children": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-modify-children/-/unist-util-modify-children-4.0.0.tgz", + "integrity": "sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "array-iterate": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", + "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-remove/-/unist-util-remove-4.0.0.tgz", + "integrity": "sha512-b4gokeGId57UVRX/eVKej5gXqGlc9+trkORhFJpu9raqZkZhU0zm8Doi05+HaiBsMEIJowL+2WtQ5ItjsngPXg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", + "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-children": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit-children/-/unist-util-visit-children-3.0.0.tgz", + "integrity": "sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unstorage": { + "version": "1.17.3", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.3.tgz", + "integrity": "sha512-i+JYyy0DoKmQ3FximTHbGadmIYb8JEpq7lxUjnjeB702bCPum0vzo6oy5Mfu0lpqISw7hCyMW2yj4nWC8bqJ3Q==", + "license": "MIT", + "dependencies": { + "anymatch": "^3.1.3", + "chokidar": "^4.0.3", + "destr": "^2.0.5", + "h3": "^1.15.4", + "lru-cache": "^10.4.3", + "node-fetch-native": "^1.6.7", + "ofetch": "^1.5.1", + "ufo": "^1.6.1" + }, + "peerDependencies": { + "@azure/app-configuration": "^1.8.0", + "@azure/cosmos": "^4.2.0", + "@azure/data-tables": "^13.3.0", + "@azure/identity": "^4.6.0", + "@azure/keyvault-secrets": "^4.9.0", + "@azure/storage-blob": "^12.26.0", + "@capacitor/preferences": "^6.0.3 || ^7.0.0", + "@deno/kv": ">=0.9.0", + "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", + "@planetscale/database": "^1.19.0", + "@upstash/redis": "^1.34.3", + "@vercel/blob": ">=0.27.1", + "@vercel/functions": "^2.2.12 || ^3.0.0", + "@vercel/kv": "^1.0.1", + "aws4fetch": "^1.0.20", + "db0": ">=0.2.1", + "idb-keyval": "^6.2.1", + "ioredis": "^5.4.2", + "uploadthing": "^7.4.4" + }, + "peerDependenciesMeta": { + "@azure/app-configuration": { + "optional": true + }, + "@azure/cosmos": { + "optional": true + }, + "@azure/data-tables": { + "optional": true + }, + "@azure/identity": { + "optional": true + }, + "@azure/keyvault-secrets": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@capacitor/preferences": { + "optional": true + }, + "@deno/kv": { + "optional": true + }, + "@netlify/blobs": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/blob": { + "optional": true + }, + "@vercel/functions": { + "optional": true + }, + "@vercel/kv": { + "optional": true + }, + "aws4fetch": { + "optional": true + }, + "db0": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "uploadthing": { + "optional": true + } + } + }, + "node_modules/unstorage/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/update-browserslist-db": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", + "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitefu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz", + "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/volar-service-css": { + "version": "0.0.66", + "resolved": "https://registry.npmjs.org/volar-service-css/-/volar-service-css-0.0.66.tgz", + "integrity": "sha512-XrL1V9LEAHnunglYdDf/7shJbQXqKsHB+P69zPmJTqHx6hqvM9GWNbn2h7M0P/oElW8p/MTVHdfjl6C8cxdsBQ==", + "license": "MIT", + "dependencies": { + "vscode-css-languageservice": "^6.3.0", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "@volar/language-service": "~2.4.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + } + } + }, + "node_modules/volar-service-emmet": { + "version": "0.0.66", + "resolved": "https://registry.npmjs.org/volar-service-emmet/-/volar-service-emmet-0.0.66.tgz", + "integrity": "sha512-BMPSpm6mk0DAEVdI2haxYIOt1Z2oaIZvCGtXuRu95x50a5pOSRPjdeHv2uGp1rQsq1Izigx+VR/bZUf2HcSnVQ==", + "license": "MIT", + "dependencies": { + "@emmetio/css-parser": "github:ramya-rao-a/css-parser#vscode", + "@emmetio/html-matcher": "^1.3.0", + "@vscode/emmet-helper": "^2.9.3", + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "@volar/language-service": "~2.4.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + } + } + }, + "node_modules/volar-service-html": { + "version": "0.0.66", + "resolved": "https://registry.npmjs.org/volar-service-html/-/volar-service-html-0.0.66.tgz", + "integrity": "sha512-MKKD2qM8qVZvBKBIugt00+Bm8j1ehgeX7Cm5XwgeEgdW/3PhUEEe/aeTxQGon1WJIGf2MM/cHPjZxPJOQN4WfQ==", + "license": "MIT", + "dependencies": { + "vscode-html-languageservice": "^5.3.0", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "@volar/language-service": "~2.4.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + } + } + }, + "node_modules/volar-service-prettier": { + "version": "0.0.66", + "resolved": "https://registry.npmjs.org/volar-service-prettier/-/volar-service-prettier-0.0.66.tgz", + "integrity": "sha512-CVaQEyfmFWoq3NhNVExoyDKonPqdacmb/07w7OfTZljxLgZpDRygiHAvzBKIcenb7rKtJNHqfQJv99ULOinJBA==", + "license": "MIT", + "dependencies": { + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "@volar/language-service": "~2.4.0", + "prettier": "^2.2 || ^3.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + }, + "prettier": { + "optional": true + } + } + }, + "node_modules/volar-service-typescript": { + "version": "0.0.66", + "resolved": "https://registry.npmjs.org/volar-service-typescript/-/volar-service-typescript-0.0.66.tgz", + "integrity": "sha512-8irsfCEf86R1RqPijrU6p5NCqKDNzyJNWKM6ZXmCcJqhebtl7Hr/a0bnlr59AzqkS3Ym4PbbJZs1K/92CXTDsw==", + "license": "MIT", + "dependencies": { + "path-browserify": "^1.0.1", + "semver": "^7.6.2", + "typescript-auto-import-cache": "^0.3.5", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-nls": "^5.2.0", + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "@volar/language-service": "~2.4.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + } + } + }, + "node_modules/volar-service-typescript-twoslash-queries": { + "version": "0.0.66", + "resolved": "https://registry.npmjs.org/volar-service-typescript-twoslash-queries/-/volar-service-typescript-twoslash-queries-0.0.66.tgz", + "integrity": "sha512-PA3CyvEaBrkxJcBq+HFdks1TF1oJ8H+jTOTQUurLDRkVjmUFg8bfdya6U/dWfTsPaDSRM4m/2chwgew5zoQXfg==", + "license": "MIT", + "dependencies": { + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "@volar/language-service": "~2.4.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + } + } + }, + "node_modules/volar-service-typescript/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/volar-service-yaml": { + "version": "0.0.66", + "resolved": "https://registry.npmjs.org/volar-service-yaml/-/volar-service-yaml-0.0.66.tgz", + "integrity": "sha512-q6oTKD6EMEu1ws1FDjRw+cfCF69Gu51IEGM9jVbtmSZS1qQHKxMqlt2+wBInKl2D+xILtjzkWbfkjQyBYQMw7g==", + "license": "MIT", + "dependencies": { + "vscode-uri": "^3.0.8", + "yaml-language-server": "~1.19.2" + }, + "peerDependencies": { + "@volar/language-service": "~2.4.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + } + } + }, + "node_modules/vscode-css-languageservice": { + "version": "6.3.8", + "resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-6.3.8.tgz", + "integrity": "sha512-dBk/9ullEjIMbfSYAohGpDOisOVU1x2MQHOeU12ohGJQI7+r0PCimBwaa/pWpxl/vH4f7ibrBfxIZY3anGmHKQ==", + "license": "MIT", + "dependencies": { + "@vscode/l10n": "^0.0.18", + "vscode-languageserver-textdocument": "^1.0.12", + "vscode-languageserver-types": "3.17.5", + "vscode-uri": "^3.1.0" + } + }, + "node_modules/vscode-html-languageservice": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/vscode-html-languageservice/-/vscode-html-languageservice-5.6.0.tgz", + "integrity": "sha512-FIVz83oGw2tBkOr8gQPeiREInnineCKGCz3ZD1Pi6opOuX3nSRkc4y4zLLWsuop+6ttYX//XZCI6SLzGhRzLmA==", + "license": "MIT", + "dependencies": { + "@vscode/l10n": "^0.0.18", + "vscode-languageserver-textdocument": "^1.0.12", + "vscode-languageserver-types": "^3.17.5", + "vscode-uri": "^3.1.0" + } + }, + "node_modules/vscode-json-languageservice": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-4.1.8.tgz", + "integrity": "sha512-0vSpg6Xd9hfV+eZAaYN63xVVMOTmJ4GgHxXnkLCh+9RsQBkWKIghzLhW2B9ebfG+LQQg8uLtsQ2aUKjTgE+QOg==", + "license": "MIT", + "dependencies": { + "jsonc-parser": "^3.0.0", + "vscode-languageserver-textdocument": "^1.0.1", + "vscode-languageserver-types": "^3.16.0", + "vscode-nls": "^5.0.0", + "vscode-uri": "^3.0.2" + }, + "engines": { + "npm": ">=7.0.0" + } + }, + "node_modules/vscode-json-languageservice/node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "license": "MIT" + }, + "node_modules/vscode-jsonrpc": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageserver": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", + "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", + "license": "MIT", + "dependencies": { + "vscode-languageserver-protocol": "3.17.5" + }, + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", + "license": "MIT" + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "license": "MIT" + }, + "node_modules/vscode-nls": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-5.2.0.tgz", + "integrity": "sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng==", + "license": "MIT" + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "license": "MIT" + }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/which-pm-runs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz", + "integrity": "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/widest-line": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz", + "integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==", + "license": "MIT", + "dependencies": { + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/xxhash-wasm": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz", + "integrity": "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==", + "license": "MIT" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, + "node_modules/yaml-language-server": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/yaml-language-server/-/yaml-language-server-1.19.2.tgz", + "integrity": "sha512-9F3myNmJzUN/679jycdMxqtydPSDRAarSj3wPiF7pchEPnO9Dg07Oc+gIYLqXR4L+g+FSEVXXv2+mr54StLFOg==", + "license": "MIT", + "dependencies": { + "@vscode/l10n": "^0.0.18", + "ajv": "^8.17.1", + "ajv-draft-04": "^1.0.0", + "lodash": "4.17.21", + "prettier": "^3.5.0", + "request-light": "^0.5.7", + "vscode-json-languageservice": "4.1.8", + "vscode-languageserver": "^9.0.0", + "vscode-languageserver-textdocument": "^1.0.1", + "vscode-languageserver-types": "^3.16.0", + "vscode-uri": "^3.0.2", + "yaml": "2.7.1" + }, + "bin": { + "yaml-language-server": "bin/yaml-language-server" + } + }, + "node_modules/yaml-language-server/node_modules/request-light": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/request-light/-/request-light-0.5.8.tgz", + "integrity": "sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg==", + "license": "MIT" + }, + "node_modules/yaml-language-server/node_modules/yaml": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz", + "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yocto-spinner": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/yocto-spinner/-/yocto-spinner-0.2.3.tgz", + "integrity": "sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ==", + "license": "MIT", + "dependencies": { + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": ">=18.19" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.0", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.0.tgz", + "integrity": "sha512-HvWtU2UG41LALjajJrML6uQejQhNJx+JBO9IflpSja4R03iNWfKXrj6W2h7ljuLyc1nKS+9yDyL/9tD1U/yBnQ==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25 || ^4" + } + }, + "node_modules/zod-to-ts": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/zod-to-ts/-/zod-to-ts-1.2.0.tgz", + "integrity": "sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA==", + "peerDependencies": { + "typescript": "^4.9.4 || ^5.0.2", + "zod": "^3" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 000000000..773d51419 --- /dev/null +++ b/docs/package.json @@ -0,0 +1,31 @@ +{ + "name": "wails-docs", + "type": "module", + "version": "0.0.1", + "scripts": { + "dev": "astro dev", + "start": "astro dev", + "build": "astro check && astro build", + "preview": "astro preview", + "astro": "astro" + }, + "dependencies": { + "@astrojs/check": "^0.9.4", + "@astrojs/react": "^4.1.0", + "@astrojs/starlight": "0.36.2", + "@types/react": "^19.0.1", + "@types/react-dom": "^19.0.2", + "astro": "^5.0.0", + "astro-d2": "^0.5.0", + "framer-motion": "^11.14.4", + "motion": "^11.14.4", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "sharp": "^0.33.5", + "starlight-blog": "0.25.1", + "starlight-image-zoom": "^0.9.0", + "starlight-links-validator": "^0.13.4", + "starlight-showcases": "^0.2.0", + "typescript": "^5.7.2" + } +} diff --git a/docs/public/d2/docs/DEVELOPER_GUIDE-0.svg b/docs/public/d2/docs/DEVELOPER_GUIDE-0.svg new file mode 100644 index 000000000..a9b379378 --- /dev/null +++ b/docs/public/d2/docs/DEVELOPER_GUIDE-0.svg @@ -0,0 +1,188 @@ +DeveloperCLI Layer(cmd/wails3)Command Handlers(internal/commands)Task Runner(internal/commands/task.go)Taskfile(v3/Taskfile.yaml)Build/Packaging(internal/*)Templates(internal/templates)Runtime Assets(internal/runtime)Application Core(pkg/application)Asset Server(internal/assetserver)Message Processor(pkg/application/messageprocessor*.go)WebView Implementations(pkg/application/webview_window_*.go)Services & Bindings(pkg/services/*)Platform Layers(pkg/mac, pkg/w32, pkg/events, pkg/ui) runs wails3registers subcommandswraps build/package/devexecutes tasksinvokes packagers & generatorsrenders scaffoldsbuilds runtime JSembedded assetsserves HTTProutes runtime callsbridge over WebViewbinds Go servicesdispatches to OSfeeds frontendexpose methods + + + + + + + + + + + + + + + + + diff --git a/docs/public/d2/docs/DEVELOPER_GUIDE-1.svg b/docs/public/d2/docs/DEVELOPER_GUIDE-1.svg new file mode 100644 index 000000000..ae97cb156 --- /dev/null +++ b/docs/public/d2/docs/DEVELOPER_GUIDE-1.svg @@ -0,0 +1,181 @@ +App.Run dispatch hubapplicationEventsEventManagerwindowEventsWindowswebviewRequestsAssetServerwindowMessagesMessageHandlerswindowKeyEventsKeyBindingdragDropBuffermenuItemClickedMenuManager goroutineEvent.handleApplicationEventgoroutinehandleWindowEventServeWebViewRequestHandleMessage / RawMessageHandlerHandleKeyEventHandleDragAndDrop + + + + + + + + + + diff --git a/docs/public/favicon.svg b/docs/public/favicon.svg new file mode 100644 index 000000000..e682e4453 --- /dev/null +++ b/docs/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/public/missing.png b/docs/public/missing.png new file mode 100644 index 000000000..05d2bafc3 Binary files /dev/null and b/docs/public/missing.png differ diff --git a/docs/public/showcase-images/bboard.webp b/docs/public/showcase-images/bboard.webp new file mode 100644 index 000000000..463d25de0 Binary files /dev/null and b/docs/public/showcase-images/bboard.webp differ diff --git a/docs/public/showcase-images/cfntracker.webp b/docs/public/showcase-images/cfntracker.webp new file mode 100644 index 000000000..6a2288a5c Binary files /dev/null and b/docs/public/showcase-images/cfntracker.webp differ diff --git a/docs/public/showcase-images/edex-ui.webp b/docs/public/showcase-images/edex-ui.webp new file mode 100644 index 000000000..110ca1acf Binary files /dev/null and b/docs/public/showcase-images/edex-ui.webp differ diff --git a/docs/public/showcase-images/emailit.webp b/docs/public/showcase-images/emailit.webp new file mode 100644 index 000000000..fc1b9a51a Binary files /dev/null and b/docs/public/showcase-images/emailit.webp differ diff --git a/docs/public/showcase-images/encrypteasy.webp b/docs/public/showcase-images/encrypteasy.webp new file mode 100644 index 000000000..c0789a3e3 Binary files /dev/null and b/docs/public/showcase-images/encrypteasy.webp differ diff --git a/docs/public/showcase-images/filehound.webp b/docs/public/showcase-images/filehound.webp new file mode 100644 index 000000000..92769ca8e Binary files /dev/null and b/docs/public/showcase-images/filehound.webp differ diff --git a/docs/public/showcase-images/gamestacker.webp b/docs/public/showcase-images/gamestacker.webp new file mode 100644 index 000000000..432e6eaed Binary files /dev/null and b/docs/public/showcase-images/gamestacker.webp differ diff --git a/docs/public/showcase-images/hiposter.webp b/docs/public/showcase-images/hiposter.webp new file mode 100644 index 000000000..7c7510ea1 Binary files /dev/null and b/docs/public/showcase-images/hiposter.webp differ diff --git a/docs/public/showcase-images/mchat.webp b/docs/public/showcase-images/mchat.webp new file mode 100644 index 000000000..393b6f77b Binary files /dev/null and b/docs/public/showcase-images/mchat.webp differ diff --git a/docs/public/showcase-images/minecraft-mod-updater.webp b/docs/public/showcase-images/minecraft-mod-updater.webp new file mode 100644 index 000000000..c8e011cf3 Binary files /dev/null and b/docs/public/showcase-images/minecraft-mod-updater.webp differ diff --git a/docs/public/showcase-images/minesweeper-xp.webp b/docs/public/showcase-images/minesweeper-xp.webp new file mode 100644 index 000000000..b3c5ca26b Binary files /dev/null and b/docs/public/showcase-images/minesweeper-xp.webp differ diff --git a/docs/public/showcase-images/modalfilemanager.webp b/docs/public/showcase-images/modalfilemanager.webp new file mode 100644 index 000000000..2fdf219fc Binary files /dev/null and b/docs/public/showcase-images/modalfilemanager.webp differ diff --git a/docs/public/showcase-images/mollywallet.webp b/docs/public/showcase-images/mollywallet.webp new file mode 100644 index 000000000..11641f8ca Binary files /dev/null and b/docs/public/showcase-images/mollywallet.webp differ diff --git a/docs/public/showcase-images/october.webp b/docs/public/showcase-images/october.webp new file mode 100644 index 000000000..ceec1c573 Binary files /dev/null and b/docs/public/showcase-images/october.webp differ diff --git a/docs/public/showcase-images/optimus.webp b/docs/public/showcase-images/optimus.webp new file mode 100644 index 000000000..0aac84058 Binary files /dev/null and b/docs/public/showcase-images/optimus.webp differ diff --git a/docs/public/showcase-images/portfall.webp b/docs/public/showcase-images/portfall.webp new file mode 100644 index 000000000..12f8d6e5b Binary files /dev/null and b/docs/public/showcase-images/portfall.webp differ diff --git a/docs/public/showcase-images/resizem.webp b/docs/public/showcase-images/resizem.webp new file mode 100644 index 000000000..aaee1c806 Binary files /dev/null and b/docs/public/showcase-images/resizem.webp differ diff --git a/docs/public/showcase-images/riftshare-main.webp b/docs/public/showcase-images/riftshare-main.webp new file mode 100644 index 000000000..2d6a8fb3a Binary files /dev/null and b/docs/public/showcase-images/riftshare-main.webp differ diff --git a/docs/public/showcase-images/scriptbar.webp b/docs/public/showcase-images/scriptbar.webp new file mode 100644 index 000000000..92463fdb9 Binary files /dev/null and b/docs/public/showcase-images/scriptbar.webp differ diff --git a/docs/public/showcase-images/tiny-rdm1.webp b/docs/public/showcase-images/tiny-rdm1.webp new file mode 100644 index 000000000..11b375580 Binary files /dev/null and b/docs/public/showcase-images/tiny-rdm1.webp differ diff --git a/docs/public/showcase-images/tiny-rdm2.webp b/docs/public/showcase-images/tiny-rdm2.webp new file mode 100644 index 000000000..9de730fb4 Binary files /dev/null and b/docs/public/showcase-images/tiny-rdm2.webp differ diff --git a/docs/public/showcase-images/varly2.webp b/docs/public/showcase-images/varly2.webp new file mode 100644 index 000000000..6dbe0c9bf Binary files /dev/null and b/docs/public/showcase-images/varly2.webp differ diff --git a/docs/public/showcase-images/wailsterm.webp b/docs/public/showcase-images/wailsterm.webp new file mode 100644 index 000000000..6d4251a75 Binary files /dev/null and b/docs/public/showcase-images/wailsterm.webp differ diff --git a/docs/public/showcase-images/wally.webp b/docs/public/showcase-images/wally.webp new file mode 100644 index 000000000..150c98c74 Binary files /dev/null and b/docs/public/showcase-images/wally.webp differ diff --git a/docs/public/showcase-images/wombat.webp b/docs/public/showcase-images/wombat.webp new file mode 100644 index 000000000..97f965834 Binary files /dev/null and b/docs/public/showcase-images/wombat.webp differ diff --git a/docs/public/showcase-images/ytd.webp b/docs/public/showcase-images/ytd.webp new file mode 100644 index 000000000..c7988cee5 Binary files /dev/null and b/docs/public/showcase-images/ytd.webp differ diff --git a/docs/public/sponsors/jetbrains-grayscale.webp b/docs/public/sponsors/jetbrains-grayscale.webp new file mode 100644 index 000000000..be39c4856 Binary files /dev/null and b/docs/public/sponsors/jetbrains-grayscale.webp differ diff --git a/docs/public/sponsors/sponsors.svg b/docs/public/sponsors/sponsors.svg new file mode 100644 index 000000000..1a9985e59 --- /dev/null +++ b/docs/public/sponsors/sponsors.svg @@ -0,0 +1,197 @@ + + + + +Champion + Masato Miura + +Bronze Sponsors + Cody Bentley + + + + Kazuya Gokita + + + + Simon Thomas + + + + CodeRabbit + +Covering Costs + Nick + + + + Marcus + + + + John + + + + Matt Holt + + + + Iain + + + + Julien + + + + Andrei + + + + Michael + +Buying Breakfast + tc-hib + + + + Tai Groot + + + + Tom Wu + + + + Arden + + + + igops + + + + vaaski + +Buying Coffee + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Helpers + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/public/sponsors/zsa.png b/docs/public/sponsors/zsa.png new file mode 100644 index 000000000..507e983fa Binary files /dev/null and b/docs/public/sponsors/zsa.png differ diff --git a/docs/src/assets/blog-images/browser.webp b/docs/src/assets/blog-images/browser.webp new file mode 100644 index 000000000..a19d5b036 Binary files /dev/null and b/docs/src/assets/blog-images/browser.webp differ diff --git a/docs/src/assets/blog-images/build-cross-windows.webp b/docs/src/assets/blog-images/build-cross-windows.webp new file mode 100644 index 000000000..ba1a538f6 Binary files /dev/null and b/docs/src/assets/blog-images/build-cross-windows.webp differ diff --git a/docs/src/assets/blog-images/build-darwin-amd.webp b/docs/src/assets/blog-images/build-darwin-amd.webp new file mode 100644 index 000000000..6126129ca Binary files /dev/null and b/docs/src/assets/blog-images/build-darwin-amd.webp differ diff --git a/docs/src/assets/blog-images/build-darwin-arm.webp b/docs/src/assets/blog-images/build-darwin-arm.webp new file mode 100644 index 000000000..47fcf4583 Binary files /dev/null and b/docs/src/assets/blog-images/build-darwin-arm.webp differ diff --git a/docs/src/assets/blog-images/build-darwin-universal.webp b/docs/src/assets/blog-images/build-darwin-universal.webp new file mode 100644 index 000000000..c95d92ccb Binary files /dev/null and b/docs/src/assets/blog-images/build-darwin-universal.webp differ diff --git a/docs/src/assets/blog-images/devtools.png b/docs/src/assets/blog-images/devtools.png new file mode 100644 index 000000000..e56a0d304 Binary files /dev/null and b/docs/src/assets/blog-images/devtools.png differ diff --git a/docs/src/assets/blog-images/linux-build-cross-windows.webp b/docs/src/assets/blog-images/linux-build-cross-windows.webp new file mode 100644 index 000000000..cbed7585b Binary files /dev/null and b/docs/src/assets/blog-images/linux-build-cross-windows.webp differ diff --git a/docs/src/assets/blog-images/montage.png b/docs/src/assets/blog-images/montage.png new file mode 100644 index 000000000..ddd771851 Binary files /dev/null and b/docs/src/assets/blog-images/montage.png differ diff --git a/docs/src/assets/blog-images/multiwindow.webp b/docs/src/assets/blog-images/multiwindow.webp new file mode 100644 index 000000000..746a1d7f2 Binary files /dev/null and b/docs/src/assets/blog-images/multiwindow.webp differ diff --git a/docs/src/assets/blog-images/remote-linux.webp b/docs/src/assets/blog-images/remote-linux.webp new file mode 100644 index 000000000..25ad11ea3 Binary files /dev/null and b/docs/src/assets/blog-images/remote-linux.webp differ diff --git a/docs/src/assets/blog-images/remote-mac.webp b/docs/src/assets/blog-images/remote-mac.webp new file mode 100644 index 000000000..bf4758e1a Binary files /dev/null and b/docs/src/assets/blog-images/remote-mac.webp differ diff --git a/docs/src/assets/blog-images/remote.webp b/docs/src/assets/blog-images/remote.webp new file mode 100644 index 000000000..3968c5ddc Binary files /dev/null and b/docs/src/assets/blog-images/remote.webp differ diff --git a/docs/src/assets/blog-images/vscode.webp b/docs/src/assets/blog-images/vscode.webp new file mode 100644 index 000000000..156fa3078 Binary files /dev/null and b/docs/src/assets/blog-images/vscode.webp differ diff --git a/docs/src/assets/blog-images/wails-linux.webp b/docs/src/assets/blog-images/wails-linux.webp new file mode 100644 index 000000000..45c76ed0e Binary files /dev/null and b/docs/src/assets/blog-images/wails-linux.webp differ diff --git a/docs/src/assets/blog-images/wails-mac.webp b/docs/src/assets/blog-images/wails-mac.webp new file mode 100644 index 000000000..26ee0be13 Binary files /dev/null and b/docs/src/assets/blog-images/wails-mac.webp differ diff --git a/docs/src/assets/blog-images/wails-menus-linux.webp b/docs/src/assets/blog-images/wails-menus-linux.webp new file mode 100644 index 000000000..391225a35 Binary files /dev/null and b/docs/src/assets/blog-images/wails-menus-linux.webp differ diff --git a/docs/src/assets/blog-images/wails-menus-mac.webp b/docs/src/assets/blog-images/wails-menus-mac.webp new file mode 100644 index 000000000..033f68d35 Binary files /dev/null and b/docs/src/assets/blog-images/wails-menus-mac.webp differ diff --git a/docs/src/assets/blog-images/wails-menus.webp b/docs/src/assets/blog-images/wails-menus.webp new file mode 100644 index 000000000..ba64152b4 Binary files /dev/null and b/docs/src/assets/blog-images/wails-menus.webp differ diff --git a/docs/src/assets/blog-images/wails.webp b/docs/src/assets/blog-images/wails.webp new file mode 100644 index 000000000..777f4d05c Binary files /dev/null and b/docs/src/assets/blog-images/wails.webp differ diff --git a/docs/src/assets/contributors.html b/docs/src/assets/contributors.html new file mode 100644 index 000000000..465a64801 --- /dev/null +++ b/docs/src/assets/contributors.html @@ -0,0 +1,246 @@ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Lea Anthony
Lea Anthony

💻 🤔 🎨 🖋 💡 🧑‍🏫 📆 🔧 🐛 📝 🚧 📦 👀 💬 🔬 ⚠️ 📢 👀 📖
stffabi
stffabi

💻 🤔 🎨 🐛 🚧 📦 👀 💬 🔬 👀 📖 ⚠️
Travis McLane
Travis McLane

💻 🔬 📦 🤔 🐛 👀 ⚠️ 💬 📖
Misite Bao
Misite Bao

📖 🌍 🔬 🚧
Byron Chris
Byron Chris

💻 🔬 🚧 🐛 👀 ⚠️ 💬 🤔 🎨 📦 🚇
konez2k
konez2k

💻 📦 🤔
Dario Emerson
Dario Emerson

💻 🐛 🤔 ⚠️
Ian M. Jones
Ian M. Jones

💻 🐛 🤔 ⚠️ 👀 📦
marktohark
marktohark

💻
Ryan H
Ryan H

💻
Cody Bentley
Cody Bentley

💻 📦 🤔 💵
Florent
Florent

💻 🐛
Alexander Hudek
Alexander Hudek

💻 💵
Tim Kipp
Tim Kipp

💻
Altynbek Kaliakbarov
Altynbek Kaliakbarov

💻
Nikolai Zimmermann
Nikolai Zimmermann

💻
k-muchmore
k-muchmore

💻
Snider
Snider

💻 🤔 📖 💵
Albert Sun
Albert Sun

💻 ⚠️
Ariel
Ariel

💻 🐛
Ilgıt Yıldırım
Ilgıt Yıldırım

💻 🐛 💵
Toyam Cox
Toyam Cox

💻 📦 🐛
hi019
hi019

💻 🐛
Arthur Wiebe
Arthur Wiebe

💻 🐛
Balakrishna Prasad Ganne
Balakrishna Prasad Ganne

💻
BillBuilt
BillBuilt

💻 📦 🤔 💬 💵
Eng Zer Jun
Eng Zer Jun

🚧 💻
LGiki
LGiki

📖
Lontten
Lontten

📖
Lukas Crepaz
Lukas Crepaz

💻 🐛
Marcus Crane
Marcus Crane

🐛 📖 💵
Qais Patankar
Qais Patankar

📖
Wakeful-Cloud
Wakeful-Cloud

💻 🐛
Zámbó, Levente
Zámbó, Levente

💻 📦 🐛 ⚠️
Ironpark
Ironpark

💻 🤔
mondy
mondy

💻 📖
Benjamin Ryan
Benjamin Ryan

🐛
fallendusk
fallendusk

📦 💻
Mat Ryer
Mat Ryer

💻 🤔 🐛
Abtin
Abtin

💻 🐛
Adrian Lanzafame
Adrian Lanzafame

📦 💻
Aleksey Polyakov
Aleksey Polyakov

🐛 💻
Alexander Matviychuk
Alexander Matviychuk

💻 📦
AlienRecall
AlienRecall

💻 📦
Aman
Aman

📖
Amaury Tobias Quiroz
Amaury Tobias Quiroz

💻 🐛
Andreas Wenk
Andreas Wenk

📖
Antonio Stanković
Antonio Stanković

💻 📦
Arpit Jain
Arpit Jain

📖
Austin Schey
Austin Schey

💻 🐛
Benjamin Thomas
Benjamin Thomas

💻 📦 🤔
Bertram Truong
Bertram Truong

💻 🐛
Blake Bourque
Blake Bourque

📖
Denis
Denis

📖
diogox
diogox

💻 📦
Dmitry Gomzyakov
Dmitry Gomzyakov

💻 📦
Edward Browncross
Edward Browncross

💻
Elie Grenon
Elie Grenon

💻
Florian Didron
Florian Didron

💻 🐛 🤔 ⚠️ 👀 📦
GargantuaX
GargantuaX

💵
Igor Minin
Igor Minin

💻 🐛
Jae-Sung Lee
Jae-Sung Lee

💻 🤔
Jarek
Jarek

💻 📦
Junker
Junker

📖
Kris Raney
Kris Raney

💻 🐛
Luken
Luken

📖
Mark Stenglein
Mark Stenglein

💻 🐛
buddyabaddon
buddyabaddon

💻
MikeSchaap
MikeSchaap

💻 🐛
NYSSEN Michaël
NYSSEN Michaël

💻 🐛
Nan0
Nan0

💻 🤔 ⚠️ 👀
oskar
oskar

📖
Pierre Joye
Pierre Joye

💻 🐛 🤔 ⚠️
Reuben Thomas-Davis
Reuben Thomas-Davis

💻 🐛
Robin
Robin

💻 🐛
Sebastian Bauer
Sebastian Bauer

💻 🤔 ⚠️ 👀 💬
Sidharth Rathi
Sidharth Rathi

📖 🐛
Sithembiso Khumalo
Sithembiso Khumalo

💻 🐛
Soheib El-Harrache
Soheib El-Harrache

💻 🐛 💵
Sophie Au
Sophie Au

💻 🐛
Stefanos Papadakis
Stefanos Papadakis

💻 🐛
Steve Chung
Steve Chung

💻 🐛
Timm Ortloff
Timm Ortloff

📖
Tom
Tom

💻
Valentin Trinqué
Valentin Trinqué

💻 🐛
mattn
mattn

💻 🐛
bearsh
bearsh

💻 🤔 📖
chenxiao
chenxiao

💻 🤔 📖
fengweiqiang
fengweiqiang

💻 📦
flin7
flin7

📖
fred21O4
fred21O4

📖
gardc
gardc

📖
rayshoo
rayshoo

📖
Ishiyama Yuzuki
Ishiyama Yuzuki

💻 🐛
佰阅
佰阅

💻
刀刀
刀刀

📖 🐛
归位
归位

💻 🐛
skamensky
skamensky

💻 🤔 📖
dependabot[bot]
dependabot[bot]

💻 🚧
Damian Sieradzki
Damian Sieradzki

💵
John Dorman
John Dorman

💵
Ian Sinnott
Ian Sinnott

💵
Arden Shackelford
Arden Shackelford

💵
Bironou
Bironou

💵
CharlieGo_
CharlieGo_

💵
overnet
overnet

💵
jugglingjsons
jugglingjsons

💵
Selvin Ortiz
Selvin Ortiz

💵
ZanderCodes
ZanderCodes

💵
Michael Voronov
Michael Voronov

💵
letheanVPN
letheanVPN

💵
Tai Groot
Tai Groot

💵
easy-web-it
easy-web-it

💵
Michael Olson
Michael Olson

💵
EdenNetwork Italia
EdenNetwork Italia

💵
ondoki
ondoki

💵
QuEST Rail LLC
QuEST Rail LLC

💵
Gilgameš
Gilgameš

💵
Bernt-Johan Bergshaven
Bernt-Johan Bergshaven

💵
Liam Bigelow
Liam Bigelow

💵
Nick Arellano
Nick Arellano

💵
Frank Chiarulli Jr.
Frank Chiarulli Jr.

💵
Tyler
Tyler

💵
Trea Hauet
Trea Hauet

💵
Kent 'picat' Gruber
Kent 'picat' Gruber

💵
tc-hib
tc-hib

💵
Antonio
Antonio

📖
MyNameIsAres
MyNameIsAres

📖
Maicarons J
Maicarons J

📖
kiddov
kiddov

📖 💵 ⚠️ 🤔
Nicolas Coutin
Nicolas Coutin

💵
Parvin Eyvazov
Parvin Eyvazov

📖
github-actions[bot]
github-actions[bot]

💻
Oleg Gulevskyy
Oleg Gulevskyy

💻 📖 🚧 📦
Richard Guay
Richard Guay

📖
Adam Tenderholt
Adam Tenderholt

💻
JulioDRF
JulioDRF

💻
Scott Opell
Scott Opell

💻
Vadim Shchepotev
Vadim Shchepotev

💻
Will Andrews
Will Andrews

💻
Gwyn
Gwyn

💻 👀 💬 🔬
希嘉嘉
希嘉嘉

💻
ALMAS
ALMAS

💻
Alex
Alex

💻
Arif Ali
Arif Ali

💻
Artur Siarohau
Artur Siarohau

💻
Binyamin Aron Green
Binyamin Aron Green

💻
Brian Dwyer
Brian Dwyer

💻
Christian Kilb
Christian Kilb

💻
David Florness
David Florness

📖
David Walton
David Walton

💻
Debdut Karmakar
Debdut Karmakar

💻
Dieter Zhu
Dieter Zhu

💻
Fredrik Holmqvist
Fredrik Holmqvist

💻
Giovanni Palma
Giovanni Palma

💻
Hao
Hao

💻
Igor Sementsov
Igor Sementsov

💻
Johannes Haseitl
Johannes Haseitl

💻
Joshua Hull
Joshua Hull

💻
Joshua Mangiola
Joshua Mangiola

📖
Kevin MacMartin
Kevin MacMartin

💻
Liang Li
Liang Li

💻
Marvin Collins Hosea
Marvin Collins Hosea

💻
Matt Holt
Matt Holt

💻
Niklas
Niklas

💻
Andy Hsu
Andy Hsu

💻
NullCode
NullCode

💻
Oussama Sethoum
Oussama Sethoum

💻
ParkourLiu
ParkourLiu

💻
Rachel Chen
Rachel Chen

💻
Rob Nice
Rob Nice

💻
Ryo TAGAMI
Ryo TAGAMI

💻
Sam Hennessy
Sam Hennessy

💻
Sean
Sean

💻
Sean Gosiaco
Sean Gosiaco

💻
Eric P Sheets
Eric P Sheets

💻
Supian M
Supian M

💻
Watson-Sei
Watson-Sei

💻 📖
Yuki Shindo
Yuki Shindo

💻
cuigege
cuigege

💻
cybertramp
cybertramp

💻
hiroki yagi
hiroki yagi

💻
imgbot[bot]
imgbot[bot]

💻
juju
juju

💻
Michael Eatherly
Michael Eatherly

💻
tk
tk

💻
allcontributors[bot]
allcontributors[bot]

📖
wander
wander

📖
+ + +
\ No newline at end of file diff --git a/docs/src/assets/menus/context-menu.png b/docs/src/assets/menus/context-menu.png new file mode 100644 index 000000000..dd43c0c7f --- /dev/null +++ b/docs/src/assets/menus/context-menu.png @@ -0,0 +1 @@ +iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAYAAAB5fY51AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MTIzODU1NjI4RjU1MTFFQjg5QzVCNTY1QjY1NjY1QjYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MTIzODU1NjM4RjU1MTFFQjg5QzVCNTY1QjY1NjY1QjYiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoxMjM4NTU2MDhGNTUxMUVCODlDNUI1NjVCNjU2NjVCNiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoxMjM4NTU2MThGNTUxMUVCODlDNUI1NjVCNjU2NjVCNiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAEAAAAALAAAAAAsASwBAAL/hI+py+0Po5y02ouz3rz7D4biSJbmiabqyrbuC8fyTNf2jef6zvf+DwwKh8Si8YhMKpfMpvMJjUqn1Kr1is1qt9yu9wsOi8fksvmMTqvX7Lb7DY/L5/S6/Y7P6/f8vv8PGCg4SFhoeIiYqLjI2Oj4CBkpOUlZaXmJmam5ydnp+QkaKjpKWmp6ipqqusra6voKGys7S1tre4ubq7vL2+v7CxwsPExcbHyMnKy8zNzs/AwdLT1NXW19jZ2tvc3d7f0NHi4+Tl5ufo6err7O3u7+Dh8vP09fb3+Pn6+/z9/v/w8woMCBBAsaPIgwocKFDBs6fAgxosSJFCtavIgxo8aN/xw7evwIMqTIkSRLmjyJMqXKlSxbunwJM6bMmTRr2ryJM6fOnTx7+vwJNKjQoUSLGj2KNKnSpUybOn0KNarUqVSrWr2KNavWrVy7ev0KNqzYsWTLmj2LNq3atWzbun0LN67cuXTr2r2LN6/evXz7+v0LOLDgwYQLGz6MOLHixYwbO34MObLkyZQrW76MObPmzZw7e/4MOrTo0aRLmz6NOrXq1axbu34NO7bs2bRr276NO7fu3bx7+/4NPLjw4cSLGz+OPLny5cybO38OPbr06dSrW7+OPbv27dy7e/8OPrz48eTLmz+PPr369ezbu38PP778+fTr27+PP7/+/fz7+9OPX7//fwAGKOCABBZo4IEIJqjgggw26OCDEEYo4YQUVmjhhRhmqOGGHHbo4YcghijiiCSWaOKJKKao4oostujiizDGKOOMNNZo44045qjjjjz26OOPQAYp5JBEFmnkkUgmqeSSTDbp5JNQRinllFRWaeWVWGap5ZZcdunll2CGKeaYZJZp5plopqnmmmy26eabcMYp55x01mnnnXjmqeeefPbp55+ABirooIQWauihiCaq6KKMNuroo5BGKumklFZq6aWYZqrpppx26umnoIYq6qiklmrqqaimquqqrLbq6quwxirrqxEAADs= diff --git a/docs/src/assets/notes-app.png b/docs/src/assets/notes-app.png new file mode 100644 index 000000000..4e4953204 Binary files /dev/null and b/docs/src/assets/notes-app.png differ diff --git a/docs/src/assets/qr1.png b/docs/src/assets/qr1.png new file mode 100644 index 000000000..69885acbe Binary files /dev/null and b/docs/src/assets/qr1.png differ diff --git a/docs/src/assets/qr2.png b/docs/src/assets/qr2.png new file mode 100644 index 000000000..e2ce2a5f1 Binary files /dev/null and b/docs/src/assets/qr2.png differ diff --git a/docs/src/assets/showcase-images/bboard.webp b/docs/src/assets/showcase-images/bboard.webp new file mode 100644 index 000000000..463d25de0 Binary files /dev/null and b/docs/src/assets/showcase-images/bboard.webp differ diff --git a/docs/src/assets/showcase-images/cfntracker.webp b/docs/src/assets/showcase-images/cfntracker.webp new file mode 100644 index 000000000..6a2288a5c Binary files /dev/null and b/docs/src/assets/showcase-images/cfntracker.webp differ diff --git a/docs/src/assets/showcase-images/clave.png b/docs/src/assets/showcase-images/clave.png new file mode 100644 index 000000000..3ee29c303 Binary files /dev/null and b/docs/src/assets/showcase-images/clave.png differ diff --git a/docs/src/assets/showcase-images/emailit.webp b/docs/src/assets/showcase-images/emailit.webp new file mode 100644 index 000000000..fc1b9a51a Binary files /dev/null and b/docs/src/assets/showcase-images/emailit.webp differ diff --git a/docs/src/assets/showcase-images/encrypteasy.webp b/docs/src/assets/showcase-images/encrypteasy.webp new file mode 100644 index 000000000..c0789a3e3 Binary files /dev/null and b/docs/src/assets/showcase-images/encrypteasy.webp differ diff --git a/docs/src/assets/showcase-images/esp-studio.png b/docs/src/assets/showcase-images/esp-studio.png new file mode 100644 index 000000000..9cb7d39fa Binary files /dev/null and b/docs/src/assets/showcase-images/esp-studio.png differ diff --git a/docs/src/assets/showcase-images/filehound.webp b/docs/src/assets/showcase-images/filehound.webp new file mode 100644 index 000000000..92769ca8e Binary files /dev/null and b/docs/src/assets/showcase-images/filehound.webp differ diff --git a/docs/src/assets/showcase-images/hiposter.webp b/docs/src/assets/showcase-images/hiposter.webp new file mode 100644 index 000000000..7c7510ea1 Binary files /dev/null and b/docs/src/assets/showcase-images/hiposter.webp differ diff --git a/docs/src/assets/showcase-images/mac-app.png b/docs/src/assets/showcase-images/mac-app.png new file mode 100644 index 000000000..1f4c85304 Binary files /dev/null and b/docs/src/assets/showcase-images/mac-app.png differ diff --git a/docs/src/assets/showcase-images/mchat.png b/docs/src/assets/showcase-images/mchat.png new file mode 100644 index 000000000..0942c482b Binary files /dev/null and b/docs/src/assets/showcase-images/mchat.png differ diff --git a/docs/src/assets/showcase-images/minecraft-mod-updater.webp b/docs/src/assets/showcase-images/minecraft-mod-updater.webp new file mode 100644 index 000000000..c8e011cf3 Binary files /dev/null and b/docs/src/assets/showcase-images/minecraft-mod-updater.webp differ diff --git a/docs/src/assets/showcase-images/minesweeper-xp.webp b/docs/src/assets/showcase-images/minesweeper-xp.webp new file mode 100644 index 000000000..b3c5ca26b Binary files /dev/null and b/docs/src/assets/showcase-images/minesweeper-xp.webp differ diff --git a/docs/src/assets/showcase-images/modalfilemanager.webp b/docs/src/assets/showcase-images/modalfilemanager.webp new file mode 100644 index 000000000..2fdf219fc Binary files /dev/null and b/docs/src/assets/showcase-images/modalfilemanager.webp differ diff --git a/docs/src/assets/showcase-images/mollywallet.webp b/docs/src/assets/showcase-images/mollywallet.webp new file mode 100644 index 000000000..11641f8ca Binary files /dev/null and b/docs/src/assets/showcase-images/mollywallet.webp differ diff --git a/docs/src/assets/showcase-images/october.webp b/docs/src/assets/showcase-images/october.webp new file mode 100644 index 000000000..ceec1c573 Binary files /dev/null and b/docs/src/assets/showcase-images/october.webp differ diff --git a/docs/src/assets/showcase-images/optimus.webp b/docs/src/assets/showcase-images/optimus.webp new file mode 100644 index 000000000..0aac84058 Binary files /dev/null and b/docs/src/assets/showcase-images/optimus.webp differ diff --git a/docs/src/assets/showcase-images/portfall.webp b/docs/src/assets/showcase-images/portfall.webp new file mode 100644 index 000000000..12f8d6e5b Binary files /dev/null and b/docs/src/assets/showcase-images/portfall.webp differ diff --git a/docs/src/assets/showcase-images/resizem.webp b/docs/src/assets/showcase-images/resizem.webp new file mode 100644 index 000000000..aaee1c806 Binary files /dev/null and b/docs/src/assets/showcase-images/resizem.webp differ diff --git a/docs/src/assets/showcase-images/restic-browser-2.png b/docs/src/assets/showcase-images/restic-browser-2.png new file mode 100644 index 000000000..b986bfe68 Binary files /dev/null and b/docs/src/assets/showcase-images/restic-browser-2.png differ diff --git a/docs/src/assets/showcase-images/riftshare-main.webp b/docs/src/assets/showcase-images/riftshare-main.webp new file mode 100644 index 000000000..2d6a8fb3a Binary files /dev/null and b/docs/src/assets/showcase-images/riftshare-main.webp differ diff --git a/docs/src/assets/showcase-images/scriptbar.webp b/docs/src/assets/showcase-images/scriptbar.webp new file mode 100644 index 000000000..92463fdb9 Binary files /dev/null and b/docs/src/assets/showcase-images/scriptbar.webp differ diff --git a/docs/src/assets/showcase-images/snippetexpandergui-add-snippet.png b/docs/src/assets/showcase-images/snippetexpandergui-add-snippet.png new file mode 100644 index 000000000..0f34e4f53 Binary files /dev/null and b/docs/src/assets/showcase-images/snippetexpandergui-add-snippet.png differ diff --git a/docs/src/assets/showcase-images/snippetexpandergui-search-and-paste.png b/docs/src/assets/showcase-images/snippetexpandergui-search-and-paste.png new file mode 100644 index 000000000..d87878c29 Binary files /dev/null and b/docs/src/assets/showcase-images/snippetexpandergui-search-and-paste.png differ diff --git a/docs/src/assets/showcase-images/snippetexpandergui-select-snippet.png b/docs/src/assets/showcase-images/snippetexpandergui-select-snippet.png new file mode 100644 index 000000000..b82468c80 Binary files /dev/null and b/docs/src/assets/showcase-images/snippetexpandergui-select-snippet.png differ diff --git a/docs/src/assets/showcase-images/surge.png b/docs/src/assets/showcase-images/surge.png new file mode 100644 index 000000000..d732fabe8 Binary files /dev/null and b/docs/src/assets/showcase-images/surge.png differ diff --git a/docs/src/assets/showcase-images/tiny-rdm1.webp b/docs/src/assets/showcase-images/tiny-rdm1.webp new file mode 100644 index 000000000..11b375580 Binary files /dev/null and b/docs/src/assets/showcase-images/tiny-rdm1.webp differ diff --git a/docs/src/assets/showcase-images/tiny-rdm2.webp b/docs/src/assets/showcase-images/tiny-rdm2.webp new file mode 100644 index 000000000..9de730fb4 Binary files /dev/null and b/docs/src/assets/showcase-images/tiny-rdm2.webp differ diff --git a/docs/src/assets/showcase-images/varly2.webp b/docs/src/assets/showcase-images/varly2.webp new file mode 100644 index 000000000..6dbe0c9bf Binary files /dev/null and b/docs/src/assets/showcase-images/varly2.webp differ diff --git a/docs/src/assets/showcase-images/wailsterm.webp b/docs/src/assets/showcase-images/wailsterm.webp new file mode 100644 index 000000000..6d4251a75 Binary files /dev/null and b/docs/src/assets/showcase-images/wailsterm.webp differ diff --git a/docs/src/assets/showcase-images/wally.webp b/docs/src/assets/showcase-images/wally.webp new file mode 100644 index 000000000..150c98c74 Binary files /dev/null and b/docs/src/assets/showcase-images/wally.webp differ diff --git a/docs/src/assets/showcase-images/warmine1.png b/docs/src/assets/showcase-images/warmine1.png new file mode 100644 index 000000000..38441d99d Binary files /dev/null and b/docs/src/assets/showcase-images/warmine1.png differ diff --git a/docs/src/assets/showcase-images/warmine2.png b/docs/src/assets/showcase-images/warmine2.png new file mode 100644 index 000000000..4713462da Binary files /dev/null and b/docs/src/assets/showcase-images/warmine2.png differ diff --git a/docs/src/assets/showcase-images/wombat.webp b/docs/src/assets/showcase-images/wombat.webp new file mode 100644 index 000000000..97f965834 Binary files /dev/null and b/docs/src/assets/showcase-images/wombat.webp differ diff --git a/docs/src/assets/showcase-images/ytd.webp b/docs/src/assets/showcase-images/ytd.webp new file mode 100644 index 000000000..c7988cee5 Binary files /dev/null and b/docs/src/assets/showcase-images/ytd.webp differ diff --git a/docs/src/assets/todo-app.png b/docs/src/assets/todo-app.png new file mode 100644 index 000000000..319cc8fbf Binary files /dev/null and b/docs/src/assets/todo-app.png differ diff --git a/docs/src/assets/wails-logo-dark.svg b/docs/src/assets/wails-logo-dark.svg new file mode 100644 index 000000000..dfc54485c --- /dev/null +++ b/docs/src/assets/wails-logo-dark.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/assets/wails-logo-horizontal-dark.svg b/docs/src/assets/wails-logo-horizontal-dark.svg new file mode 100644 index 000000000..01808dca5 --- /dev/null +++ b/docs/src/assets/wails-logo-horizontal-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/src/assets/wails-logo-horizontal-light.svg b/docs/src/assets/wails-logo-horizontal-light.svg new file mode 100644 index 000000000..465bc5cf9 --- /dev/null +++ b/docs/src/assets/wails-logo-horizontal-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/src/assets/wails-logo-light.svg b/docs/src/assets/wails-logo-light.svg new file mode 100644 index 000000000..ab02c827a --- /dev/null +++ b/docs/src/assets/wails-logo-light.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/assets/wails_build.mp4 b/docs/src/assets/wails_build.mp4 new file mode 100644 index 000000000..2d1ee783b Binary files /dev/null and b/docs/src/assets/wails_build.mp4 differ diff --git a/docs/src/assets/wails_dev.mp4 b/docs/src/assets/wails_dev.mp4 new file mode 100644 index 000000000..29c2a45a4 Binary files /dev/null and b/docs/src/assets/wails_dev.mp4 differ diff --git a/docs/src/assets/wails_init.mp4 b/docs/src/assets/wails_init.mp4 new file mode 100644 index 000000000..337bc0f5a Binary files /dev/null and b/docs/src/assets/wails_init.mp4 differ diff --git a/docs/src/components/CardAnimation.astro b/docs/src/components/CardAnimation.astro new file mode 100644 index 000000000..5d3de111b --- /dev/null +++ b/docs/src/components/CardAnimation.astro @@ -0,0 +1,62 @@ +--- +--- + diff --git a/docs/src/content/authors.ts b/docs/src/content/authors.ts new file mode 100644 index 000000000..0500490f3 --- /dev/null +++ b/docs/src/content/authors.ts @@ -0,0 +1,11 @@ +import type { StarlightBlogUserConfig } from "starlight-blog"; + +type Authors = NonNullable["authors"]; +export const authors: Authors = { + leaanthony: { + name: "Lea Anthony", + title: "Maintainer of Wails", + url: "https://github.com/leaanthony", + picture: "https://github.com/leaanthony.png", + }, +}; diff --git a/docs/src/content/config.ts b/docs/src/content/config.ts new file mode 100644 index 000000000..dae3197fe --- /dev/null +++ b/docs/src/content/config.ts @@ -0,0 +1,10 @@ +import { defineCollection } from "astro:content"; +import { docsSchema, i18nSchema } from "@astrojs/starlight/schema"; +import { blogSchema } from "starlight-blog/schema"; + +export const collections = { + i18n: defineCollection({ type: "data", schema: i18nSchema() }), + docs: defineCollection({ + schema: docsSchema({ extend: (context) => blogSchema(context) }), + }), +}; diff --git a/docs/src/content/docs/blog/2021-09-27-v2-beta1-release-notes.md b/docs/src/content/docs/blog/2021-09-27-v2-beta1-release-notes.md new file mode 100644 index 000000000..dbc8d9776 --- /dev/null +++ b/docs/src/content/docs/blog/2021-09-27-v2-beta1-release-notes.md @@ -0,0 +1,206 @@ +--- +slug: blog/wails-v2-beta-for-windows +title: Wails v2 Beta for Windows +authors: [leaanthony] +tags: [wails, v2] +date: 2021-09-27 +--- + +![wails screenshot](../../../assets/blog-images/wails.webp) + +When I first announced Wails on Reddit, just over 2 years ago from a train in +Sydney, I did not expect it to get much attention. A few days later, a prolific +tech vlogger released a tutorial video, gave it a positive review and from that +point on, interest in the project has skyrocketed. + +It was clear that people were excited about adding web frontends to their Go +projects, and almost immediately pushed the project beyond the proof of concept +that I had created. At the time, Wails used the +[webview](https://github.com/webview/webview) project to handle the frontend, +and the only option for Windows was the IE11 renderer. Many bug reports were +rooted in this limitation: poor JavaScript/CSS support and no dev tools to debug +it. This was a frustrating development experience but there wasn't much that +could have been done to rectify it. + +For a long time, I'd firmly believed that Microsoft would eventually have to +sort out their browser situation. The world was moving on, frontend development +was booming and IE wasn't cutting it. When Microsoft announced the move to using +Chromium as the basis for their new browser direction, I knew it was only a +matter of time until Wails could use it, and move the Windows developer +experience to the next level. + +Today, I am pleased to announce: **Wails v2 Beta for Windows**! There's a huge +amount to unpack in this release, so grab a drink, take a seat and we'll +begin... + +### No CGO Dependency! + +No, I'm not joking: _No_ _CGO_ _dependency_ 🤯! The thing about Windows is that, +unlike MacOS and Linux, it doesn't come with a default compiler. In addition, +CGO requires a mingw compiler and there's a ton of different installation +options. Removing the CGO requirement has massively simplified setup, as well as +making debugging an awful lot easier. Whilst I have put a fair bit of effort in +getting this working, the majority of the credit should go to +[John Chadwick](https://github.com/jchv) for not only starting a couple of +projects to make this possible, but also being open to someone taking those +projects and building on them. Credit also to +[Tad Vizbaras](https://github.com/tadvi) whose +[winc](https://github.com/tadvi/winc) project started me down this path. + +### WebView2 Chromium Renderer + +![devtools screenshot](../../../assets/blog-images/devtools.png) + +Finally, Windows developers get a first class rendering engine for their +applications! Gone are the days of contorting your frontend code to work on +Windows. On top of that, you get a first-class developer tools experience! + +The WebView2 component does, however, have a requirement to have the +`WebView2Loader.dll` sitting alongside the binary. This makes distribution just +that little bit more painful than we gophers are used to. All solutions and +libraries (that I know of) that use WebView2 have this dependency. + +However, I'm really excited to announce that Wails applications _have no such +requirement_! Thanks to the wizardry of +[John Chadwick](https://github.com/jchv), we are able to bundle this dll inside +the binary and get Windows to load it as if it were present on disk. + +Gophers rejoice! The single binary dream lives on! + +### New Features + +![wails-menus screenshot](../../../assets/blog-images/wails-menus.webp) + +There were a lot of requests for native menu support. Wails has finally got you +covered. Application menus are now available and include support for most native +menu features. This includes standard menu items, checkboxes, radio groups, +submenus and separators. + +There were a huge number of requests in v1 for the ability to have greater +control of the window itself. I'm happy to announce that there's new runtime +APIs specifically for this. It's feature-rich and supports multi-monitor +configurations. There is also an improved dialogs API: Now, you can have modern, +native dialogs with rich configuration to cater for all your dialog needs. + +There is now the option to generate IDE configuration along with your project. +This means that if you open your project in a supported IDE, it will already be +configured for building and debugging your application. Currently VSCode is +supported but we hope to support other IDEs such as Goland soon. + +![vscode screenshot](../../../assets/blog-images/vscode.webp) + +### No requirement to bundle assets + +A huge pain-point of v1 was the need to condense your entire application down to +single JS & CSS files. I'm happy to announce that for v2, there is no +requirement to bundle assets, in any way, shape or form. Want to load a local +image? Use an `` tag with a local src path. Want to use a cool font? Copy +it in and add the path to it in your CSS. + +> Wow, that sounds like a webserver... + +Yes, it works just like a webserver, except it isn't. + +> So how do I include my assets? + +You just pass a single `embed.FS` that contains all your assets into your +application configuration. They don't even need to be in the top directory - +Wails will just work it out for you. + +### New Development Experience + +![browser screenshot](../../../assets/blog-images/browser.webp) + +Now that assets don't need to be bundled, it's enabled a whole new development +experience. The new `wails dev` command will build and run your application, but +instead of using the assets in the `embed.FS`, it loads them directly from disk. + +It also provides the additional features: + +- Hot reload - Any changes to frontend assets will trigger and auto reload of + the application frontend +- Auto rebuild - Any changes to your Go code will rebuild and relaunch your + application + +In addition to this, a webserver will start on port 34115. This will serve your +application to any browser that connects to it. All connected web browsers will +respond to system events like hot reload on asset change. + +In Go, we are used to dealing with structs in our applications. It's often +useful to send structs to our frontend and use them as state in our application. +In v1, this was a very manual process and a bit of a burden on the developer. +I'm happy to announce that in v2, any application run in dev mode will +automatically generate TypeScript models for all structs that are input or +output parameters to bound methods. This enables seamless interchange of data +models between the two worlds. + +In addition to this, another JS module is dynamically generated wrapping all +your bound methods. This provides JSDoc for your methods, providing code +completion and hinting in your IDE. It's really cool when you get data models +auto-imported when hitting tab in an auto-generated module wrapping your Go +code! + +### Remote Templates + +![remote screenshot](../../../assets/blog-images/remote.webp) + +Getting an application up and running quickly was always a key goal for the +Wails project. When we launched, we tried to cover a lot of the modern +frameworks at the time: react, vue and angular. The world of frontend +development is very opinionated, fast moving and hard to keep on top of! As a +result, we found our base templates getting out of date pretty quickly and this +caused a maintenance headache. It also meant that we didn't have cool modern +templates for the latest and greatest tech stacks. + +With v2, I wanted to empower the community by giving you the ability to create +and host templates yourselves, rather than rely on the Wails project. So now you +can create projects using community supported templates! I hope this will +inspire developers to create a vibrant ecosystem of project templates. I'm +really quite excited about what our developer community can create! + +### In Conclusion + +Wails v2 represents a new foundation for the project. The aim of this release is +to get feedback on the new approach, and to iron out any bugs before a full +release. Your input would be most welcome. Please direct any feedback to the +[v2 Beta](https://github.com/wailsapp/wails/discussions/828) discussion board. + +There were many twists and turns, pivots and u-turns to get to this point. This +was due partly to early technical decisions that needed changing, and partly +because some core problems we had spent time building workarounds for were fixed +upstream: Go’s embed feature is a good example. Fortunately, everything came +together at the right time, and today we have the very best solution that we can +have. I believe the wait has been worth it - this would not have been possible +even 2 months ago. + +I also need to give a huge thank you :pray: to the following people because +without them, this release just wouldn't exist: + +- [Misite Bao](https://github.com/misitebao) - An absolute workhorse on the + Chinese translations and an incredible bug finder. +- [John Chadwick](https://github.com/jchv) - His amazing work on + [go-webview2](https://github.com/jchv/go-webview2) and + [go-winloader](https://github.com/jchv/go-winloader) have made the Windows + version we have today possible. +- [Tad Vizbaras](https://github.com/tadvi) - Experimenting with his + [winc](https://github.com/tadvi/winc) project was the first step down the path + to a pure Go Wails. +- [Mat Ryer](https://github.com/matryer) - His support, encouragement and + feedback has really helped drive the project forward. + +And finally, I'd like to give a special thank you to all the +[project sponsors](/credits#sponsors), including +[JetBrains](https://www.jetbrains.com?from=Wails), whose support drives the +project in many ways behind the scenes. + +I look forward to seeing what people build with Wails in this next exciting +phase of the project! + +Lea. + +PS: MacOS and Linux users need not feel left out - porting to this new +foundation is actively under way and most of the hard work has already been +done. Hang in there! + +PPS: If you or your company find Wails useful, please consider +[sponsoring the project](https://github.com/sponsors/leaanthony). Thanks! diff --git a/docs/src/content/docs/blog/2021-11-08-v2-beta2-release-notes.md b/docs/src/content/docs/blog/2021-11-08-v2-beta2-release-notes.md new file mode 100644 index 000000000..31d53e1bc --- /dev/null +++ b/docs/src/content/docs/blog/2021-11-08-v2-beta2-release-notes.md @@ -0,0 +1,166 @@ +--- +slug: blog/wails-v2-beta-for-mac +title: Wails v2 Beta for MacOS +authors: [leaanthony] +tags: [wails, v2] +date: 2021-11-08 +--- + +![wails-mac screenshot](../../../assets/blog-images/wails-mac.webp) + +Today marks the first beta release of Wails v2 for Mac! It's taken quite a while +to get to this point and I'm hoping that today's release will give you something +that's reasonably useful. There have been a number of twists and turns to get to +this point and I'm hoping, with your help, to iron out the crinkles and get the +Mac port polished for the final v2 release. + +You mean this isn't ready for production? For your use case, it may well be +ready, but there are still a number of known issues so keep your eye on +[this project board](https://github.com/wailsapp/wails/projects/7) and if you +would like to contribute, you'd be very welcome! + +So what's new for Wails v2 for Mac vs v1? Hint: It's pretty similar to the +Windows Beta :wink: + +### New Features + +![wails-menus-mac screenshot](../../../assets/blog-images/wails-menus-mac.webp) + +There were a lot of requests for native menu support. Wails has finally got you +covered. Application menus are now available and include support for most native +menu features. This includes standard menu items, checkboxes, radio groups, +submenus and separators. + +There were a huge number of requests in v1 for the ability to have greater +control of the window itself. I'm happy to announce that there's new runtime +APIs specifically for this. It's feature-rich and supports multi-monitor +configurations. There is also an improved dialogs API: Now, you can have modern, +native dialogs with rich configuration to cater for all your dialog needs. + +### Mac Specific Options + +In addition to the normal application options, Wails v2 for Mac also brings some +Mac extras: + +- Make your window all funky and translucent, like all the pretty swift apps! +- Highly customisable titlebar +- We support the NSAppearance options for the application +- Simple config to auto-create an "About" menu + +### No requirement to bundle assets + +A huge pain-point of v1 was the need to condense your entire application down to +single JS & CSS files. I'm happy to announce that for v2, there is no +requirement to bundle assets, in any way, shape or form. Want to load a local +image? Use an `` tag with a local src path. Want to use a cool font? Copy +it in and add the path to it in your CSS. + +> Wow, that sounds like a webserver... + +Yes, it works just like a webserver, except it isn't. + +> So how do I include my assets? + +You just pass a single `embed.FS` that contains all your assets into your +application configuration. They don't even need to be in the top directory - +Wails will just work it out for you. + +### New Development Experience + +Now that assets don't need to be bundled, it's enabled a whole new development +experience. The new `wails dev` command will build and run your application, but +instead of using the assets in the `embed.FS`, it loads them directly from disk. + +It also provides the additional features: + +- Hot reload - Any changes to frontend assets will trigger and auto reload of + the application frontend +- Auto rebuild - Any changes to your Go code will rebuild and relaunch your + application + +In addition to this, a webserver will start on port 34115. This will serve your +application to any browser that connects to it. All connected web browsers will +respond to system events like hot reload on asset change. + +In Go, we are used to dealing with structs in our applications. It's often +useful to send structs to our frontend and use them as state in our application. +In v1, this was a very manual process and a bit of a burden on the developer. +I'm happy to announce that in v2, any application run in dev mode will +automatically generate TypeScript models for all structs that are input or +output parameters to bound methods. This enables seamless interchange of data +models between the two worlds. + +In addition to this, another JS module is dynamically generated wrapping all +your bound methods. This provides JSDoc for your methods, providing code +completion and hinting in your IDE. It's really cool when you get data models +auto-imported when hitting tab in an auto-generated module wrapping your Go +code! + +### Remote Templates + +![remote-mac screenshot](../../../assets/blog-images/remote-mac.webp) + +Getting an application up and running quickly was always a key goal for the +Wails project. When we launched, we tried to cover a lot of the modern +frameworks at the time: react, vue and angular. The world of frontend +development is very opinionated, fast moving and hard to keep on top of! As a +result, we found our base templates getting out of date pretty quickly and this +caused a maintenance headache. It also meant that we didn't have cool modern +templates for the latest and greatest tech stacks. + +With v2, I wanted to empower the community by giving you the ability to create +and host templates yourselves, rather than rely on the Wails project. So now you +can create projects using community supported templates! I hope this will +inspire developers to create a vibrant ecosystem of project templates. I'm +really quite excited about what our developer community can create! + +### Native M1 Support + +Thanks to the amazing support of [Mat Ryer](https://github.com/matryer/), the +Wails project now supports M1 native builds: + +![build-darwin-arm screenshot](../../../assets/blog-images/build-darwin-arm.webp) + +You can also specify `darwin/amd64` as a target too: + +![build-darwin-amd screenshot](../../../assets/blog-images/build-darwin-amd.webp) + +Oh, I almost forgot.... you can also do `darwin/universal`.... :wink: + +![build-darwin-universal screenshot](../../../assets/blog-images/build-darwin-universal.webp) + +### Cross Compilation to Windows + +Because Wails v2 for Windows is pure Go, you can target Windows builds without +docker. + +![build-cross-windows screenshot](../../../assets/blog-images/build-cross-windows.webp) +bu + +### WKWebView Renderer + +V1 relied on a (now deprecated) WebView component. V2 uses the most recent +WKWebKit component so expect the latest and greatest from Apple. + +### In Conclusion + +As I'd said in the Windows release notes, Wails v2 represents a new foundation +for the project. The aim of this release is to get feedback on the new approach, +and to iron out any bugs before a full release. Your input would be most +welcome! Please direct any feedback to the +[v2 Beta](https://github.com/wailsapp/wails/discussions/828) discussion board. + +And finally, I'd like to give a special thank you to all the +[project sponsors](/credits#sponsors), including +[JetBrains](https://www.jetbrains.com?from=Wails), whose support drive the +project in many ways behind the scenes. + +I look forward to seeing what people build with Wails in this next exciting +phase of the project! + +Lea. + +PS: Linux users, you're next! + +PPS: If you or your company find Wails useful, please consider +[sponsoring the project](https://github.com/sponsors/leaanthony). Thanks! diff --git a/docs/src/content/docs/blog/2022-02-22-v2-beta3-release-notes.md b/docs/src/content/docs/blog/2022-02-22-v2-beta3-release-notes.md new file mode 100644 index 000000000..047236258 --- /dev/null +++ b/docs/src/content/docs/blog/2022-02-22-v2-beta3-release-notes.md @@ -0,0 +1,129 @@ +--- +slug: blog/wails-v2-beta-for-linux +title: Wails v2 Beta for Linux +authors: [leaanthony] +tags: [wails, v2] +date: 2022-02-22 +--- + +![wails-linux screenshot](../../../assets/blog-images/wails-linux.webp) + +I'm pleased to finally announce that Wails v2 is now in beta for Linux! It is +somewhat ironic that the very first experiments with v2 was on Linux and yet it +has ended up as the last release. That being said, the v2 we have today is very +different from those first experiments. So without further ado, let's go over +the new features: + +### New Features + +![wails-menus-linux screenshot](../../../assets/blog-images/wails-menus-linux.webp) + +There were a lot of requests for native menu support. Wails has finally got you +covered. Application menus are now available and include support for most native +menu features. This includes standard menu items, checkboxes, radio groups, +submenus and separators. + +There were a huge number of requests in v1 for the ability to have greater +control of the window itself. I'm happy to announce that there's new runtime +APIs specifically for this. It's feature-rich and supports multi-monitor +configurations. There is also an improved dialogs API: Now, you can have modern, +native dialogs with rich configuration to cater for all your dialog needs. + +### No requirement to bundle assets + +A huge pain-point of v1 was the need to condense your entire application down to +single JS & CSS files. I'm happy to announce that for v2, there is no +requirement to bundle assets, in any way, shape or form. Want to load a local +image? Use an `<../../../assets/blog-images>` tag with a local src path. Want to +use a cool font? Copy it in and add the path to it in your CSS. + +> Wow, that sounds like a webserver... + +Yes, it works just like a webserver, except it isn't. + +> So how do I include my assets? + +You just pass a single `embed.FS` that contains all your assets into your +application configuration. They don't even need to be in the top directory - +Wails will just work it out for you. + +### New Development Experience + +Now that assets don't need to be bundled, it's enabled a whole new development +experience. The new `wails dev` command will build and run your application, but +instead of using the assets in the `embed.FS`, it loads them directly from disk. + +It also provides the additional features: + +- Hot reload - Any changes to frontend assets will trigger an auto reload of the + application frontend +- Auto rebuild - Any changes to your Go code will rebuild and relaunch your + application + +In addition to this, a webserver will start on port 34115. This will serve your +application to any browser that connects to it. All connected web browsers will +respond to system events like hot reload on asset change. + +In Go, we are used to dealing with structs in our applications. It's often +useful to send structs to our frontend and use them as state in our application. +In v1, this was a very manual process and a bit of a burden on the developer. +I'm happy to announce that in v2, any application run in dev mode will +automatically generate TypeScript models for all structs that are input or +output parameters to bound methods. This enables seamless interchange of data +models between the two worlds. + +In addition to this, another JS module is dynamically generated wrapping all +your bound methods. This provides JSDoc for your methods, providing code +completion and hinting in your IDE. It's really cool when you get data models +auto-imported when hitting tab in an auto-generated module wrapping your Go +code! + +### Remote Templates + +![remote-linux screenshot](../../../assets/blog-images/remote-linux.webp) + +Getting an application up and running quickly was always a key goal for the +Wails project. When we launched, we tried to cover a lot of the modern +frameworks at the time: react, vue and angular. The world of frontend +development is very opinionated, fast moving and hard to keep on top of! As a +result, we found our base templates getting out of date pretty quickly and this +caused a maintenance headache. It also meant that we didn't have cool modern +templates for the latest and greatest tech stacks. + +With v2, I wanted to empower the community by giving you the ability to create +and host templates yourselves, rather than rely on the Wails project. So now you +can create projects using community supported templates! I hope this will +inspire developers to create a vibrant ecosystem of project templates. I'm +really quite excited about what our developer community can create! + +### Cross Compilation to Windows + +Because Wails v2 for Windows is pure Go, you can target Windows builds without +docker. + +![build-cross-windows screenshot](../../../assets/blog-images/linux-build-cross-windows.webp) + +### In Conclusion + +As I'd said in the Windows release notes, Wails v2 represents a new foundation +for the project. The aim of this release is to get feedback on the new approach, +and to iron out any bugs before a full release. Your input would be most +welcome! Please direct any feedback to the +[v2 Beta](https://github.com/wailsapp/wails/discussions/828) discussion board. + +Linux is **hard** to support. We expect there to be a number of quirks with the +beta. Please help us to help you by filing detailed bug reports! + +Finally, I'd like to give a special thank you to all the +[project sponsors](/credits#sponsors) whose support drives the project in many +ways behind the scenes. + +I look forward to seeing what people build with Wails in this next exciting +phase of the project! + +Lea. + +PS: The v2 release isn't far off now! + +PPS: If you or your company find Wails useful, please consider +[sponsoring the project](https://github.com/sponsors/leaanthony). Thanks! diff --git a/docs/src/content/docs/blog/2022-09-22-v2-release-notes.md b/docs/src/content/docs/blog/2022-09-22-v2-release-notes.md new file mode 100644 index 000000000..c72d7f566 --- /dev/null +++ b/docs/src/content/docs/blog/2022-09-22-v2-release-notes.md @@ -0,0 +1,198 @@ +--- +slug: blog/wails-v2-released +title: Wails v2 Released +authors: [leaanthony] +tags: [wails, v2] +date: 2022-09-22 +--- + +![montage screenshot](../../../assets/blog-images/montage.png) + +# It's here! + +Today marks the release of [Wails](https://wails.io) v2. It's been about 18 +months since the first v2 alpha and about a year from the first beta release. +I'm truly grateful to everyone involved in the evolution of the project. + +Part of the reason it took that long was due to wanting to get to some +definition of completeness before officially calling it v2. The truth is, +there's never a perfect time to tag a release - there's always outstanding +issues or "just one more" feature to squeeze in. What tagging an imperfect major +release does do, however, is to provide a bit of stability for users of the +project, as well as a bit of a reset for the developers. + +This release is more than I'd ever expected it to be. I hope it gives you as +much pleasure as it has given us to develop it. + +# What _is_ Wails? + +If you are unfamiliar with Wails, it is a project that enables Go programmers to +provide rich frontends for their Go programs using familiar web technologies. +It's a lightweight, Go alternative to Electron. Much more information can be +found on the [official site](https://wails.io/docs/introduction). + +# What's new? + +The v2 release is a huge leap forward for the project, addressing many of the +pain points of v1. If you have not read any of the blog posts on the Beta +releases for [macOS](/blog/wails-v2-beta-for-mac), +[Windows](/blog/wails-v2-beta-for-windows) or +[Linux](/blog/wails-v2-beta-for-linux), then I encourage you to do so as it +covers all the major changes in more detail. In summary: + +- Webview2 component for Windows that supports modern web standards and + debugging capabilities. +- [Dark / Light theme](https://wails.io/docs/reference/options#theme) + + [custom theming](https://wails.io/docs/reference/options#customtheme) on Windows. +- Windows now has no CGO requirements. +- Out-of-the-box support for Svelte, Vue, React, Preact, Lit & Vanilla project + templates. +- [Vite](https://vitejs.dev/) integration providing a hot-reload development + environment for your application. +- Native application + [menus](https://wails.io/docs/guides/application-development#application-menu) and + [dialogs](https://wails.io/docs/reference/runtime/dialog). +- Native window translucency effects for + [Windows](https://wails.io/docs/reference/options#windowistranslucent) and + [macOS](https://wails.io/docs/reference/options#windowistranslucent-1). Support for Mica & + Acrylic backdrops. +- Easily generate an [NSIS installer](https://wails.io/docs/guides/windows-installer) for + Windows deployments. +- A rich [runtime library](https://wails.io/docs/reference/runtime/intro) providing utility + methods for window manipulation, eventing, dialogs, menus and logging. +- Support for [obfuscating](https://wails.io/docs/guides/obfuscated) your application using + [garble](https://github.com/burrowers/garble). +- Support for compressing your application using [UPX](https://upx.github.io/). +- Automatic TypeScript generation of Go structs. More info + [here](https://wails.io/docs/howdoesitwork#calling-bound-go-methods). +- No extra libraries or DLLs are required to be shipped with your application. + For any platform. +- No requirement to bundle frontend assets. Just develop your application like + any other web application. + +# Credit & Thanks + +Getting to v2 has been a huge effort. There have been ~2.2K commits by 89 +contributors between the initial alpha and the release today, and many, many +more that have provided translations, testing, feedback and help on the +discussion forums as well as the issue tracker. I'm so unbelievably grateful to +each one of you. I'd also like to give an extra special thank you to all the +project sponsors who have provided guidance, advice and feedback. Everything you +do is hugely appreciated. + +There are a few people I'd like to give special mention to: + +Firstly, a **huge** thank you to [@stffabi](https://github.com/stffabi) who has +provided so many contributions which we all benefit from, as well as providing a +lot of support on many issues. He has provided some key features such as the +external dev server support which transformed our dev mode offering by allowing +us to hook into [Vite](https://vitejs.dev/)'s superpowers. It's fair to say that +Wails v2 would be a far less exciting release without his +[incredible contributions](https://github.com/wailsapp/wails/commits?author=stffabi&since=2020-01-04). +Thank you so much @stffabi! + +I'd also like to give a huge shout-out to +[@misitebao](https://github.com/misitebao) who has tirelessly been maintaining +the website, as well as providing Chinese translations, managing Crowdin and +helping new translators get up to speed. This is a hugely important task, and +I'm extremely grateful for all the time and effort put into this! You rock! + +Last, but not least, a huge thank you to Mat Ryer who has provided advice and +support during the development of v2. Writing xBar together using an early Alpha +of v2 was helpful in shaping the direction of v2, as well as give me an +understanding of some design flaws in the early releases. I'm happy to announce +that as of today, we will start to port xBar to Wails v2, and it will become the +flagship application for the project. Cheers Mat! + +# Lessons Learnt + +There are a number of lessons learnt in getting to v2 that will shape +development moving forward. + +## Smaller, Quicker, Focused Releases + +In the course of developing v2, there were many features and bug fixes that were +developed on an ad-hoc basis. This led to longer release cycles and were harder +to debug. Moving forward, we are going to create releases more often that will +include a reduced number of features. A release will involve updates to +documentation as well as thorough testing. Hopefully, these smaller, quicker, +focussed releases will lead to fewer regressions and better quality +documentation. + +## Encourage Engagement + +When starting this project, I wanted to immediately help everyone who had a +problem. Issues were "personal" and I wanted them resolved as quickly as +possible. This is unsustainable and ultimately works against the longevity of +the project. Moving forward, I will be giving more space for people to get +involved in answering questions and triaging issues. It would be good to get +some tooling to help with this so if you have any suggestions, please join in +the discussion [here](https://github.com/wailsapp/wails/discussions/1855). + +## Learning to say No + +The more people that engage with an Open Source project, the more requests there +will be for additional features that may or may not be useful to the majority of +people. These features will take an initial amount of time to develop and debug, +and incur an ongoing maintenance cost from that point on. I myself am the most +guilty of this, often wanting to "boil the sea" rather than provide the minimum +viable feature. Moving forward, we will need to say "No" a bit more to adding +core features and focus our energies on a way to empower developers to provide +that functionality themselves. We are looking seriously into plugins for this +scenario. This will allow anyone to extend the project as they see fit, as well +as providing an easy way to contribute towards the project. + +# Looking to the Future + +There are so many core features we are looking at to add to Wails in the next +major development cycle already. The +[roadmap](https://github.com/wailsapp/wails/discussions/1484) is full of +interesting ideas, and I'm keen to start work on them. One of the big asks has +been for multiple window support. It's a tricky one and to do it right, and we +may need to look at providing an alternative API, as the current one was not +designed with this in mind. Based on some preliminary ideas and feedback, I +think you'll like where we're looking to go with it. + +I'm personally very excited at the prospect of getting Wails apps running on +mobile. We already have a demo project showing that it is possible to run a +Wails app on Android, so I'm really keen to explore where we can go with this! + +A final point I'd like to raise is that of feature parity. It has long been a +core principle that we wouldn't add anything to the project without there being +full cross-platform support for it. Whilst this has proven to be (mainly) +achievable so far, it has really held the project back in releasing new +features. Moving forward, we will be adopting a slightly different approach: any +new feature that cannot be immediately released for all platforms will be +released under an experimental configuration or API. This allows early adopters +on certain platforms to try the feature and provide feedback that will feed into +the final design of the feature. This, of course, means that there are no +guarantees of API stability until it is fully supported by all the platforms it +can be supported on, but at least it will unblock development. + +# Final Words + +I'm really proud of what we've been able to achieve with the V2 release. It's +amazing to see what people have already been able to build using the beta +releases so far. Quality applications like [Varly](https://varly.app/), +[Surge](https://getsurge.io/) and [October](https://october.utf9k.net/). I +encourage you to check them out. + +This release was achieved through the hard work of many contributors. Whilst it +is free to download and use, it has not come about through zero cost. Make no +mistakes, this project has come at considerable cost. It has not only been my +time and the time of each and every contributor, but also the cost of absence +from friends and families of each of those people too. That's why I'm extremely +grateful for every second that has been dedicated to making this project happen. +The more contributors we have, the more this effort can be spread out and the +more we can achieve together. I'd like to encourage you all to pick one thing +that you can contribute, whether it is confirming someone's bug, suggesting a +fix, making a documentation change or helping out someone who needs it. All of +these small things have such a huge impact! It would be so awesome if you too +were part of the story in getting to v3. + +Enjoy! + +‐ Lea + +PS: If you or your company find Wails useful, please consider +[sponsoring the project](https://github.com/sponsors/leaanthony). Thanks! diff --git a/docs/src/content/docs/blog/2023-01-17-v3-roadmap.md b/docs/src/content/docs/blog/2023-01-17-v3-roadmap.md new file mode 100644 index 000000000..a7d0d3b94 --- /dev/null +++ b/docs/src/content/docs/blog/2023-01-17-v3-roadmap.md @@ -0,0 +1,256 @@ +--- +slug: blog/the-road-to-wails-v3 +title: The Road to Wails v3 +authors: [leaanthony] +tags: [wails, v3] +date: 2023-01-17 +--- + +![multiwindow screenshot](../../../assets/blog-images/multiwindow.webp) + +# Introduction + +Wails is a project that simplifies the ability to write cross-platform desktop +applications using Go. It uses native webview components for the frontend (not +embedded browsers), bringing the power of the world's most popular UI system to +Go, whilst remaining lightweight. + +Version 2 was released on the 22nd of September 2022 and brought with it a lot +of enhancements including: + +- Live development, leveraging the popular Vite project +- Rich functionality for managing windows and creating menus +- Microsoft's WebView2 component +- Generation of Typescript models that mirror your Go structs +- Creating of NSIS Installer +- Obfuscated builds + +Right now, Wails v2 provides powerful tooling for creating rich, cross-platform +desktop applications. + +This blog post aims to look at where the project is at right now and what we can +improve on moving forward. + +# Where are we now? + +It's been incredible to see the popularity of Wails rising since the v2 release. +I'm constantly amazed by the creativity of the community and the wonderful +things that are being built with it. With more popularity, comes more eyes on +the project. And with that, more feature requests and bug reports. + +Over time, I've been able to identify some of the most pressing issues facing +the project. I've also been able to identify some of the things that are holding +the project back. + +## Current issues + +I've identified the following areas that I feel are holding the project back: + +- The API +- Bindings generation +- The Build System + +### The API + +The API to build a Wails application currently consists of 2 parts: + +- The Application API +- The Runtime API + +The Application API famously has only 1 function: `Run()` which takes a heap of +options which govern how the application will work. Whilst this is very simple +to use, it is also very limiting. It is a "declarative" approach which hides a +lot of the underlying complexity. For instance, there is no handle to the main +window, so you can't interact with it directly. For that, you need to use the +Runtime API. This is a problem when you start to want to do more complex things +like create multiple windows. + +The Runtime API provides a lot of utility functions for the developer. This +includes: + +- Window management +- Dialogs +- Menus +- Events +- Logs + +There are a number of things I am not happy with the Runtime API. The first is +that it requires a "context" to be passed around. This is both frustrating and +confusing for new developers who pass in a context and then get a runtime error. + +The biggest issue with the Runtime API is that it was designed for applications +that only use a single window. Over time, the demand for multiple windows has +grown and the API is not well suited to this. + +### Thoughts on the v3 API + +Wouldn't it be great if we could do something like this? + +```go +func main() { + app := wails.NewApplication(options.App{}) + myWindow := app.NewWindow(options.Window{}) + myWindow.SetTitle("My Window") + myWindow.On(events.Window.Close, func() { + app.Quit() + }) + app.Run() +} +``` + +This programmatic approach is far more intuitive and allows the developer to +interact with the application elements directly. All current runtime methods for +windows would simply be methods on the window object. For the other runtime +methods, we could move them to the application object like so: + +```go +app := wails.NewApplication(options.App{}) +app.NewInfoDialog(options.InfoDialog{}) +app.Log.Info("Hello World") +``` + +This is a much more powerful API which will allow for more complex applications +to be built. It also allows for the creation of multiple windows, +[the most up-voted feature on GitHub](https://github.com/wailsapp/wails/issues/1480): + +```go +func main() { + app := wails.NewApplication(options.App{}) + myWindow := app.NewWindow(options.Window{}) + myWindow.SetTitle("My Window") + myWindow.On(events.Window.Close, func() { + app.Quit() + }) + myWindow2 := app.NewWindow(options.Window{}) + myWindow2.SetTitle("My Window 2") + myWindow2.On(events.Window.Close, func() { + app.Quit() + }) + app.Run() +} +``` + +### Bindings generation + +One of the key features of Wails is generating bindings for your Go methods so +they may be called from Javascript. The current method for doing this is a bit +of a hack. It involves building the application with a special flag and then +running the resultant binary which uses reflection to determine what has been +bound. This leads to a bit of a chicken and egg situation: You can't build the +application without the bindings and you can't generate the bindings without +building the application. There are many ways around this but the best one would +be not to use this approach at all. + +There were a number of attempts at writing a static analyser for Wails projects +but they didn't get very far. In more recent times, it has become slightly +easier to do this with more material available on the subject. + +Compared to reflection, the AST approach is much faster however it is +significantly more complicated. To start with, we may need to impose certain +constraints on how to specify bindings in the code. The goal is to support the +most common use cases and then expand it later on. + +### The Build System + +Like the declarative approach to the API, the build system was created to hide +the complexities of building a desktop application. When you run `wails build`, +it does a lot of things behind the scenes: + +- Builds the backend binary for bindings and generates the bindings +- Installs the frontend dependencies +- Builds the frontend assets +- Determines if the application icon is present and if so, embeds it +- Builds the final binary +- If the build is for `darwin/universal` it builds 2 binaries, one for + `darwin/amd64` and one for `darwin/arm64` and then creates a fat binary using + `lipo` +- If compression is required, it compresses the binary with UPX +- Determines if this binary is to be packaged and if so: + - Ensures the icon and application manifest are compiled into the binary + (Windows) + - Builds out the application bundle, generates the icon bundle and copies it, + the binary and Info.plist to the application bundle (Mac) +- If an NSIS installer is required, it builds it + +This entire process, whilst very powerful, is also very opaque. It is very +difficult to customise it and it is very difficult to debug. + +To address this in v3, I would like to move to a build system that exists +outside of Wails. After using [Task](https://taskfile.dev/) for a while, I am a +big fan of it. It is a great tool for configuring build systems and should be +reasonably familiar to anyone who has used Makefiles. + +The build system would be configured using a `Taskfile.yml` file which would be +generated by default with any of the supported templates. This would have all of +the steps required to do all the current tasks, such as building or packaging +the application, allowing for easy customisation. + +There will be no external requirement for this tooling as it would form part of +the Wails CLI. This means that you can still use `wails build` and it will do +all the things it does today. However, if you want to customise the build +process, you can do so by editing the `Taskfile.yml` file. It also means you can +easily understand the build steps and use your own build system if you wish. + +The missing piece in the build puzzle is the atomic operations in the build +process, such as icon generation, compression and packaging. To require a bunch +of external tooling would not be a great experience for the developer. To +address this, the Wails CLI will provide all these capabilities as part of the +CLI. This means that the builds still work as expected, with no extra external +tooling, however you can replace any step of the build with any tool you like. + +This will be a much more transparent build system which will allow for easier +customisation and address a lot of the issues that have been raised around it. + +## The Payoff + +These positive changes will be a huge benefit to the project: + +- The new API will be much more intuitive and will allow for more complex + applications to be built. +- Using static analysis for bindings generation will be much faster and reduce a + lot of the complexity around the current process. +- Using an established, external build system will make the build process + completely transparent, allowing for powerful customisation. + +Benefits to the project maintainers are: + +- The new API will be much easier to maintain and adapt to new features and + platforms. +- The new build system will be much easier to maintain and extend. I hope this + will lead to a new ecosystem of community driven build pipelines. +- Better separation of concerns within the project. This will make it easier to + add new features and platforms. + +## The Plan + +A lot of the experimentation for this has already been done and it's looking +good. There is no current timeline for this work but I'm hoping by the end of Q1 +2023, there will be an alpha release for Mac to allow the community to test, +experiment with and provide feedback. + +## Summary + +- The v2 API is declarative, hides a lot from the developer and not suitable for + features such as multiple windows. A new API will be created which will be + simpler, intuitive and more powerful. +- The build system is opaque and difficult to customise so we will move to an + external build system which will open it all up. +- The bindings generation is slow and complex so we will move to static analysis + which will remove a lot of the complexity the current method has. + +There has been a lot of work put into the guts of v2 and it's solid. It's now +time to address the layer on top of it and make it a much better experience for +the developer. + +I hope you are as excited about this as I am. I'm looking forward to hearing +your thoughts and feedback. + +Regards, + +‐ Lea + +PS: If you or your company find Wails useful, please consider +[sponsoring the project](https://github.com/sponsors/leaanthony). Thanks! + +PPS: Yes, that's a genuine screenshot of a multi-window application built with +Wails. It's not a mockup. It's real. It's awesome. It's coming soon. diff --git a/docs/src/content/docs/blog/2024-12-03-alpha10-and-new-release-strategy.md b/docs/src/content/docs/blog/2024-12-03-alpha10-and-new-release-strategy.md new file mode 100644 index 000000000..0cba0baa7 --- /dev/null +++ b/docs/src/content/docs/blog/2024-12-03-alpha10-and-new-release-strategy.md @@ -0,0 +1,52 @@ +--- +title: Alpha 10 Released - A New Chapter for Wails +description: Announcing Wails v3 Alpha 10 and our new daily release strategy +date: 2024-12-03 +author: The Wails Team +--- + +# Alpha 10 Released - A New Chapter for Wails + +We're thrilled to announce the release of **Wails v3 Alpha 10** - and it's big! While our release cadence may have seemed slow recently, there's been an incredible amount of work happening behind the scenes. Today marks not just another release, but a shift in how we approach the development of Wails. + +## Why the Wait? + +Like many development teams, we fell into the trap of trying to achieve perfection before each release. We wanted to squash every bug, polish every feature, and ensure everything was just right. But here's the thing - software is never truly bug-free, and waiting for that mythical state only delays getting improvements into your hands. + +## Our New Release Strategy + +Starting today, we're adopting a **daily release strategy**. Any new changes merged during the day will be released at the end of that day. This approach will: + +- Get bug fixes and features to you faster +- Provide more frequent feedback loops +- Speed up our journey to Beta +- Make the development process more transparent + +This means you might see more frequent, smaller releases rather than occasional large ones. We believe this will benefit everyone in the Wails community. + +## How You Can Help + +We've been overwhelmed by the number of people wanting to contribute to Wails! To make it easier for contributors to get involved, we're implementing a new system: + +- Firstly, we are opening up bug reporting for alpha releases. The frequent releases allow us to do this. +- Issues marked with **"Ready for Work"** are open for community contributions +- These issues have clear requirements and are ready to be tackled +- Most importantly, **we need people testing PRs** - this is one of the most valuable contributions you can make + +Testing doesn't require deep knowledge of the codebase, but it provides immense value by ensuring changes work across different environments and use cases. + +## What's Next? + +With our new daily release strategy, expect to see rapid progress toward Beta. We're committed to maintaining momentum and getting Wails v3 to a stable release as quickly as possible without sacrificing quality. + +And who knows? There might be a few surprises along the way... 😉 + +## Thank You + +To everyone who has contributed code, tested releases, reported bugs, or simply used Wails - thank you. Your support and feedback drive this project forward. + +Here's to more frequent releases, faster iteration, and an exciting journey ahead! + +--- + +*Want to get involved? Check out our [GitHub repository](https://github.com/wailsapp/wails) for issues marked "ready for work", or join our community to help test the latest changes.* \ No newline at end of file diff --git a/docs/src/content/docs/changelog.mdx b/docs/src/content/docs/changelog.mdx new file mode 100644 index 000000000..45289ddf4 --- /dev/null +++ b/docs/src/content/docs/changelog.mdx @@ -0,0 +1,1167 @@ +--- +title: Changelog +--- + + +Legend: +-  - macOS +- ⊞ - Windows +- 🐧 - Linux + +/*-- +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +- `Added` for new features. +- `Changed` for changes in existing functionality. +- `Deprecated` for soon-to-be removed features. +- `Removed` for now removed features. +- `Fixed` for any bug fixes. +- `Security` in case of vulnerabilities. + +*/ + +/* + ** PLEASE DO NOT UPDATE THIS FILE ** + Updates should be added to `v3/UNRELEASED_CHANGELOG.md` + Thank you! +*/ +## [Unreleased] + +## v3.0.0-alpha.69 - 2026-02-08 + +## Added +- Add `-tags` flag to `wails3 build` command for passing custom Go build tags (e.g., `wails3 build -tags gtk4`) (#4957) + +## Fixed +- Fix file drag-and-drop on Windows not working at non-100% display scaling +- Fix HTML5 internal drag-and-drop being broken when file drop was enabled on Windows +- Fix file drop coordinates being in wrong pixel space on Windows (physical vs CSS pixels) +- Fix file drag-and-drop on Linux not working reliably with hover effects +- Fix HTML5 internal drag-and-drop being broken when file drop was enabled on Linux +- Fix window show/hide on Linux/GTK4 sometimes restoring to minimized state by using `gtk_window_present()` (#4957) +- Fix window position get/set on Linux/GTK4 always returning 0,0 by adding X11-conditional support via `XTranslateCoordinates`/`XMoveWindow` (#4957) +- Fix max window size not being enforced on Linux/GTK4 by adding signal-based size clamping to replace removed `gtk_window_set_geometry_hints` (#4957) +- Fix DPI scaling on Linux/GTK4 by implementing proper PhysicalBounds calculation and fractional scaling support via `gdk_monitor_get_scale` (GTK 4.14+) +- Fix menu items duplicating when creating new windows on Linux/GTK4 +- Fix generation of mapped types with enum keys in JS/TS bindings (#4437) by @fbbdev + +## v3.0.0-alpha.68 - 2026-02-07 + +## Added +- Add Web API examples in `v3/examples/web-apis/` demonstrating 41 browser APIs including Storage (localStorage, sessionStorage, IndexedDB, Cache API), Network (Fetch, WebSocket, XMLHttpRequest, EventSource, Beacon), Media (Canvas, WebGL, Web Audio, MediaDevices, MediaRecorder, Speech Synthesis), Device (Geolocation, Clipboard, Fullscreen, Device Orientation, Vibration, Gamepad), Performance (Performance API, Mutation Observer, Intersection/Resize Observer), UI (Web Components, Pointer Events, Selection, Dialog, Drag and Drop), and more +- Add WebView API compatibility checker example (`v3/examples/webview-api-check/`) that tests 200+ browser APIs across platforms +- Add `internal/libpath` package for finding native library paths on Linux with parallel search, caching, and support for Flatpak/Snap/Nix +- **WIP:** Add experimental WebKitGTK 6.0 / GTK4 support for Linux, available via `-tags gtk4` (GTK3/WebKit2GTK 4.1 remains the default) + - Note: On tiling window managers (e.g., Hyprland, Sway), Minimize/Maximize operations may not work as expected since the WM controls window geometry + +## Changed +- **BREAKING**: Map keys in generated JS/TS bindings are now marked optional to accurately reflect Go map semantics. Map value access in Typescript now returns `T | undefined` instead of `T`, requiring null checks or assertions (#4943) by `@fbbdev` + +## Fixed +- Fix file drag-and-drop on Windows not working at non-100% display scaling +- Fix HTML5 internal drag-and-drop being broken when file drop was enabled on Windows +- Fix file drop coordinates being in wrong pixel space on Windows (physical vs CSS pixels) +- Fix file drag-and-drop on Linux not working reliably with hover effects +- Fix HTML5 internal drag-and-drop being broken when file drop was enabled on Linux +- Fix DPI scaling on Linux/GTK4 by implementing proper PhysicalBounds calculation and fractional scaling support via `gdk_monitor_get_scale` (GTK 4.14+) +- Fix menu items duplicating when creating new windows on Linux/GTK4 +- Fix generation of mapped types with enum keys in JS/TS bindings (#4437) by @fbbdev + +## v3.0.0-alpha.67 - 2026-02-04 + +## Added +- Added how to do `One Time Handlers` in the docs for `Listening to Events in JavaScript` by @AbdelhadiSeddar + +## Changed +- Changed the use of `Event` into `Events` according to changes in `@wailsio/runtime` and appropriate function calls in the docs in `Features/Events/Event System` by @AbdelhadiSeddar + +## Fixed +- Fix "ghost windows" issue on macOS caused by not accessing AppKit APIs from the Main Thread in App.Window.Current() (#4947) by @wimaha +- Fix HTML `` not working on macOS by implementing WKUIDelegate runOpenPanelWithParameters (#4862) +- Fix native file drag-and-drop not working when using `@wailsio/runtime` npm module on macOS/Linux (#4953) by @leaanthony +- Fix binding generation for cross-package type aliases (#4578) by @fbbdev + +## v3.0.0-alpha.66 - 2026-02-03 + +## Added +- Add `UseApplicationMenu` option to `WebviewWindowOptions` allowing windows on Windows/Linux to inherit the application menu set via `app.Menu.Set()` by @leaanthony + +## Changed +- Move `EnabledFeatures`, `DisabledFeatures`, and `AdditionalBrowserArgs` from per-window options to application-level `Options.Windows` (#4559) by @leaanthony + +## Fixed +- Fix OpenFileDialog crash on Linux due to GTK thread safety violation (#3683) by @ddmoney420 +- Fix SIGSEGV crash when calling `Focus()` on a hidden or destroyed window (#4890) by @ddmoney420 +- Fix potential panic when setting empty icon or bitmap on Linux (#4923) by @ddmoney420 +- Fix ErrorDialog crash when called from service binding on macOS (#3631) by @leaanthony +- Make menus to be displayed on Windows OS in `v3\examples\dialogs` by @ndianabasi +- Fix race condition causing TypeError during page reload (#4872) by @ddmoney420 +- Fix incorrect output from binding generator tests by removing global state in the `Collector.IsVoidAlias()` method (#4941) by @fbbdev +- Fix `` file picker not working on macOS (#4862) by @leaanthony + +## Removed +- **BREAKING**: Remove `EnabledFeatures`, `DisabledFeatures`, and `AdditionalLaunchArgs` from per-window `WindowsWindow` options. Use application-level `Options.Windows.EnabledFeatures`, `Options.Windows.DisabledFeatures`, and `Options.Windows.AdditionalBrowserArgs` instead. These flags apply globally to the shared WebView2 environment (#4559) by @leaanthony + +## v3.0.0-alpha.65 - 2026-02-01 + +## Added +- Add support for using `.icon` files (Apple Icon Composer format) for generating Liquid Glass icons and asset catalogs (macOS) (#4934) by @wimaha + +## v3.0.0-alpha.64 - 2026-01-26 + +## Added +- Add experimental server mode for headless/web deployments (`-tags server`). Enables running Wails apps as HTTP servers without native GUI dependencies. Build with `wails3 task build:server`. See `examples/server` for details. + +## v3.0.0-alpha.63 - 2026-01-25 + +## Fixed +- Fix `Position()` and `SetPosition()` using inconsistent coordinate systems on macOS, causing window position drift when saving/restoring state (#4816) by @leaanthony + +## v3.0.0-alpha.62 - 2026-01-22 + +## Fixed +- Fix SetProcessDpiAwarenessContext "Access is denied" error when DPI awareness is already set via application manifest (#4803) + +## v3.0.0-alpha.61 - 2026-01-20 + +## Fixed +- Update the docs page for keyboard shortcuts and corrects the type of the callback parameter for `KeyBinding.Add` by @ndianabasi +- Fix documentation regarding generating custom binding, must use `-d String` instead of `-o String` + +## v3.0.0-alpha.60 - 2026-01-14 + +## Fixed +- Fix menu not clearing children on `menu.Update()` + +## v3.0.0-alpha.59 - 2026-01-11 + +## Changed +- Update the README for the `Drag N Drop` example and highlights that `Internal Drag and Drop` is demonstrated with the example @ndianabasi + +## v3.0.0-alpha.58 - 2026-01-09 + +## Fixed +- Fix outdated Manager API references in documentation (31 files updated to use new pattern like `app.Window.New()`, `app.Event.Emit()`, etc.) by @leaanthony +- Fix Linux crash on panic in JS-bound Go methods due to WebKit overriding signal handlers (#3965) by @leaanthony + +## v3.0.0-alpha.57 - 2026-01-05 + +## Changed +- Replace various debug logs from Info to Debug (by @mbaklor) + +## Fixed +- Fix SaveFileDialog.SetFilename() having no effect on Linux (#4841) by @samstanier +- Fix drop coordinates showing as undefined in drag-n-drop example +- Fix macOS app bundle creation failing when APP_NAME contains spaces (brace expansion issue) +- Fix index out of bounds panic on Windows when calling service methods (revert goccy/go-json) + +## v3.0.0-alpha.56 - 2026-01-04 + +## Added +- Add `internal/libpath` package for finding native library paths on Linux with parallel search, caching, and support for Flatpak/Snap/Nix + +## Changed +- **BREAKING:** Rename `EnableDragAndDrop` to `EnableFileDrop` in window options +- **BREAKING:** Rename `DropZoneDetails` to `DropTargetDetails` in event context +- **BREAKING:** Rename `DropZoneDetails()` method to `DropTargetDetails()` on `WindowEventContext` +- **BREAKING:** Remove `WindowDropZoneFilesDropped` event, use `WindowFilesDropped` instead +- **BREAKING:** Change HTML attribute from `data-wails-dropzone` to `data-file-drop-target` +- **BREAKING:** Change CSS hover class from `wails-dropzone-hover` to `file-drop-target-active` +- **BREAKING:** Remove `DragEffect`, `OnEnterEffect`, `OnOverEffect` options from Windows (were part of removed IDropTarget) + +## Fixed +- Fix file drag-and-drop on Windows not working at non-100% display scaling +- Fix HTML5 internal drag-and-drop being broken when file drop was enabled on Windows +- Fix file drop coordinates being in wrong pixel space on Windows (physical vs CSS pixels) +- Fix file drag-and-drop on Linux not working reliably with hover effects +- Fix HTML5 internal drag-and-drop being broken when file drop was enabled on Linux + +## Removed +- Remove native `IDropTarget` implementation on Windows in favor of JavaScript-based approach (matches v2 behavior) + +## v3.0.0-alpha.55 - 2026-01-02 + +## Changed +- Switch to goccy/go-json for all runtime JSON processing (method bindings, events, webview requests, notifications, kvstore), improving performance by 21-63% and reducing memory allocations by 40-60% +- Optimize BoundMethod struct layout and cache isVariadic flag to reduce per-call overhead +- Use stack-allocated argument buffer for methods with `<=8` arguments to avoid heap allocations +- Optimize result collection in method calls to avoid slice allocation for single return values +- Use sync.Map for MIME type cache to improve concurrent performance +- Use buffer pool for HTTP transport request body reading +- Lazily allocate CloseNotify channel in content type sniffer to reduce per-request allocations +- Remove debug CSS logging from asset server +- Expand MIME type extension map to cover 50+ common web formats (fonts, audio, video, etc.) + +## Fixed +- Update all commands in Taskfile.yml files for all operating systems to accommodate spaces in variables such as `APP_NAME` by @ndianabasi + +## Removed +- Remove github.com/wailsapp/mimetype dependency in favor of expanded extension map + stdlib http.DetectContentType, reducing binary size by ~1.2MB +- Remove gopkg.in/ini.v1 dependency by implementing minimal .desktop file parser for Linux file explorer, saving ~45KB +- Remove samber/lo from runtime code by using Go 1.21+ stdlib slices package and minimal internal helpers, saving ~310KB + +## v3.0.0-alpha.54 - 2025-12-29 + +## Added +- Add `CollectionBehavior` option to `MacWindow` for controlling window behavior across macOS Spaces and fullscreen (#4756) by @leaanthony + +## Fixed +- Fix command argument error when executing 'build:universal:lipo:go' task on Linux by @wux1an +- Fix Docker error "undefined symbol: ___ubsan_handle_xxxxxxx" when running 'wails3 build GOOS=darwin GOARCH=arm64' on Linux by @wux1an + +## Removed +- Remove debug printf statements from Darwin URL scheme handler (#4834) + +## v3.0.0-alpha.53 - 2025-12-27 + +## Added +- Add unit tests for pkg/application by @leaanthony +- Add custom protocol support to MSIX packaging by @leaanthony + +## Fixed +- Consolidate custom protocol documentation and add Universal Links sections by @leaanthony + +## v3.0.0-alpha.52 - 2025-12-26 + +## Fixed +- Fix Windows systray menu crash when clicking icon repeatedly by adding guard against concurrent TrackPopupMenuEx calls (#4151) by @leaanthony + +## v3.0.0-alpha.51 - 2025-12-23 + +## Fixed +- Prevent app crashing when calling systray.Run() before app.Run() by @leaanthony + +## v3.0.0-alpha.50 - 2025-12-21 + +## Changed +- Update the documentation for Window `X/Y` options @ruhuang2001 + +## v3.0.0-alpha.49 - 2025-12-18 + +## Changed +- Update the `Frontend Runtime` documentation by adding more options for generating frontend bindings by @ndianabasi + +## v3.0.0-alpha.48 - 2025-12-16 + +## Added +- Add desktop environment detection on linux [PR #4797](https://github.com/wailsapp/wails/pull/4797) + +## Changed +- Update the documentation page for Wails v3 Asset Server by @ndianabasi +- **BREAKING**: Remove package-level dialog functions (`application.InfoDialog()`, `application.QuestionDialog()`, etc.). Use the `app.Dialog` manager instead: `app.Dialog.Info()`, `app.Dialog.Question()`, `app.Dialog.Warning()`, `app.Dialog.Error()`, `app.Dialog.OpenFile()`, `app.Dialog.SaveFile()` +- Update dialogs documentation to match actual API: use `app.Dialog.*`, `AddButton()` with callbacks (not `SetButtons()`), `SetDefaultButton(*Button)` (not string), `AddFilter()` (not `SetFilters()`), `SetFilename()` (not `SetDefaultFilename()`), and `app.Dialog.OpenFile().CanChooseDirectories(true)` for folder selection + +## Fixed +- Fix crash on macOS when toggling window visibility via Hide()/Show() with ApplicationShouldTerminateAfterLastWindowClosed enabled (#4389) by @leaanthony +- Fix memory leak in context menus on macOS and Windows when repeatedly opening menus (#4012) by @leaanthony +- Fix context menu native resources not being reused on macOS, causing fresh menu creation on each display (#4012) by @leaanthony + +## v3.0.0-alpha.47 - 2025-12-15 + +## Added +- Add `Window.Print()` method to JavaScript runtime for triggering print dialog from frontend (#4290) by @leaanthony + +## Fixed +- Fix macOS dock icon click not showing hidden windows when app started with `Hidden: true` (#4583) by @leaanthony +- Fix macOS print dialog not opening due to incorrect window pointer type in CGO call (#4290) by @leaanthony + +## v3.0.0-alpha.46 - 2025-12-14 + +## Added +- Add `XDG_SESSION_TYPE` to `wails3 doctor` output on Linux by @leaanthony +- Add additional WebKit2 load-change events for Linux: `WindowLoadStarted`, `WindowLoadRedirected`, `WindowLoadCommitted`, `WindowLoadFinished` (#3896) by @leaanthony + +## Removed +- **BREAKING**: Remove `linux:WindowLoadChanged` event - use `linux:WindowLoadFinished` instead for detecting when WebView has finished loading (#3896) by @leaanthony + +## v3.0.0-alpha.45 - 2025-12-13 + +## Added +- Add `XDG_SESSION_TYPE` to `wails3 doctor` output on Linux by @leaanthony +- Generate `.desktop` file during Linux build, not just packaging (#4575) +- Add Linux runtime dependencies documentation with distro-specific package names and nfpm packaging examples (#4339) by @leaanthony + +## Fixed +- Fix window menu crash on Wayland caused by appmenu-gtk-module accessing unrealized window (#4769) by @leaanthony +- Fix GTK application crash when app name contains invalid characters (spaces, parentheses, etc.) by @leaanthony +- Fix "not enough memory" error when initializing drag and drop on Windows (#4701) by @overlordtm +- Fix file explorer opening wrong directory on Linux due to incorrect URI escaping (#4397) by @leaanthony +- Fix AppImage build failure on modern Linux distributions (Arch, Fedora 39+, Ubuntu 24.04+) by auto-detecting `.relr.dyn` ELF sections and disabling stripping (#4642) by @leaanthony +- Fix `wails doctor` falsely reporting webkit packages as installed on Fedora/DNF-based systems (#4457) by @leaanthony + +## v3.0.0-alpha.44 - 2025-12-12 + +## Added +- Add NVIDIA driver version info to `wails3 doctor` output on Linux by @leaanthony + +## Changed +- **BREAKING**: Production builds are now the default. To create dev builds, set `DEV=true` in your Taskfiles. Generate a new project for examples by @leaanthony + +## Fixed +- Fix default `config.yml` would run `wails3 dev` with a production build by @mbaklor +- Fix iOS service stubs causing build failures due to non-existent package import by @leaanthony +- Fix structured logging in debug/info methods causing "no formatting directives" errors by @leaanthony +- Remove temporary debug print statements accidentally included from mobile platform merge by @leaanthony +- Fix WebKitGTK crash on Wayland with NVIDIA GPUs (Error 71 Protocol error) by auto-disabling DMA-BUF renderer by @leaanthony + +## v3.0.0-alpha.43 - 2025-12-11 + +## Added +- Add origin to raw message handler by @APshenkin in [PR](https://github.com/wailsapp/wails/pull/4710) +- Add universal link support for macOS by @APshenkin in [PR](https://github.com/wailsapp/wails/pull/4712) +- Refactor binding transport layer by @APshenkin in [PR](https://github.com/wailsapp/wails/pull/4702) +- Add aria-label identifiers to the helloworld templates so that the example app can be easily tested by Appium test clients by @chinenual in [PR](https://github.com/wailsapp/wails/pull/4760) + +## v3.0.0-alpha.42 - 2025-12-10 + +## Added +- Add origin to raw message handler by @APshenkin in [PR](https://github.com/wailsapp/wails/pull/4710) +- Add universal link support for macOS by @APshenkin in [PR](https://github.com/wailsapp/wails/pull/4712) +- Refactor binding transport layer by @APshenkin in [PR](https://github.com/wailsapp/wails/pull/4702) + +## v3.0.0-alpha.41 - 2025-11-23 + +## Fixed +- Resolve alpha value being ignored in `application.WebviewWindowOptions.BackgroundColour` on Linux ([#4722](https://github.com/wailsapp/wails/pull/4722), @BradHacker) + +## v3.0.0-alpha.40 - 2025-11-13 + +## Fixed +- Fix Windows systray icon not defaulting to application icon when no custom icon is provided (#4704) + +## v3.0.0-alpha.39 - 2025-11-12 + +## Added +- Typed Events by @fbbdev and @ianvs in [#4633](https://github.com/wailsapp/wails/pull/4633) + +## Changed +- When emitting a custom event with zero or one data argument, the data value will be assigned directly to the Data field without wrapping it in a slice by [@fbbdev](https://github.com/fbbdev) in [#4633](https://github.com/wailsapp/wails/pull/4633) + +## v3.0.0-alpha.38 - 2025-11-04 + +## Added +- Add `systray-clock` example showing a headless tray with live tooltip updates (#4653). + +## Changed +- Windows trays now honor `SystemTray.Show()`/`Hide()` by toggling `NIS_HIDDEN`, so apps can truly disappear and return (#4653). +- Tray registration reuses resolved icons, sets `NOTIFYICON_VERSION_4` once, and enables `NIF_SHOWTIP` so tooltips recover after Explorer restarts (#4653). + +## Fixed +- Track `HICON` ownership so only user-created handles are destroyed, preventing Explorer recycling crashes (#4653). +- Release the Windows system-theme listener and retained tray icons during destroy to stop leaking goroutines and device contexts (#4653). +- Truncate tray tooltips at 127 UTF-16 units to avoid corrupting surrogate pairs and multi-byte glyphs (#4653). + +## v3.0.0-alpha.37 - 2025-11-02 + +## Fixed +- Fix Windows package task failure (#4667) + +## v3.0.0-alpha.36 - 2025-10-15 + +## Fixed +- Fix Linux appimage appicon variable in Linux taskfile [PR #4644](https://github.com/wailsapp/wails/pull/4644) +- Fix Windows build error caused by go-webview2 v1.0.22 signature change (#4513, #4645) + +## v3.0.0-alpha.35 - 2025-10-14 + +## Fixed +- Fix Linux appimage appicon variable in Linux taskfile [PR #4644](https://github.com/wailsapp/wails/pull/4644) + +## v3.0.0-alpha.34 - 2025-10-06 + +## Added +- Added NSIS Protocol template for Windows by @Tolfx in #4510 +- Added tests for build-assets by @Tolfx in #4510 + +## Fixed +- Fixed linux desktop.tmpl protocol range, by removing `<.Info.Protocol>` to `<.Protocol>` by @Tolfx in #4510 +- Fixed redefinition error for liquid glass demo in [#4542](https://github.com/wailsapp/wails/pull/4542) by @Etesam913 + +## v3.0.0-alpha.33 - 2025-10-04 + +## Fixed +- Fixed systray menu updates on Linux [#4604](https://github.com/wailsapp/wails/issues/4604) by [@JackDoan](https://github.com/JackDoan) + +## v3.0.0-alpha.32 - 2025-10-02 + +## Fixed +- Fix the white window appearing on Windows when creating a hidden window by @leaanthony in [#4612](https://github.com/wailsapp/wails/pull/4612) +- Fix notifications package import path in documentation by @rxliuli in [#4617](https://github.com/wailsapp/wails/pull/4617) +- Fix drag-and-drop not working when using npm package @wailsio/runtime (#4489) by @leaanthony in #4616 + +## v3.0.0-alpha.31 - 2025-09-27 + +## Fixed +- Windows: Flicker of window at start and hidden windows being shown incorrectly in [PR](https://github.com/wailsapp/wails/pull/4600) by @leaanthony. +- Fixed Wayland window size maximising issues (https://github.com/wailsapp/wails/issues/4429) by [@samstanier](https://github.com/samstanier) + +## v3.0.0-alpha.30 - 2025-09-26 + +## Fixed +- Fixed Wayland window size maximising issues (https://github.com/wailsapp/wails/issues/4429) by [@samstanier](https://github.com/samstanier) + +## v3.0.0-alpha.29 - 2025-09-25 + +## Added +- macOS: Shows native window controls in the menu bar in [#4588](https://github.com/wailsapp/wails/pull/4588) by @nidib +- Add macOS Dock service to hide/show app icon in the dock @popaprozac in [PR](https://github.com/wailsapp/wails/pull/4451) + +## Changed +- macOS: Use `visibleFrame` instead of `frame` for window centering to exclude menu bar and dock areas + +## Fixed +- Fixed redefinition error for liquid glass demo in [#4542](https://github.com/wailsapp/wails/pull/4542) by @Etesam913 +- Fixed issue where AssetServer can crash on MacOS in [#4576](https://github.com/wailsapp/wails/pull/4576) by @jghiloni +- Fixed compilation issue when building with NextJs. Fixed in [#4585](https://github.com/wailsapp/wails/pull/4585) by @rev42 +- Fixed pipelines for nightly release in [#4597](https://github.com/wailsapp/wails/pull/4597) by @riadafridishibly + +## v3.0.0-alpha.29 - 2025-09-25 + +## Added +- Add macOS Dock service to hide/show app icon in the dock @popaprozac in [PR](https://github.com/wailsapp/wails/pull/4451) + +## Changed +- macOS: Use `visibleFrame` instead of `frame` for window centering to exclude menu bar and dock areas + +## Fixed +- Fixed redefinition error for liquid glass demo in [#4542](https://github.com/wailsapp/wails/pull/4542) by @Etesam913 +- Fixed issue where AssetServer can crash on MacOS in [#4576](https://github.com/wailsapp/wails/pull/4576) by @jghiloni +- Fixed compilation issue when building with NextJs. Fixed in [#4585](https://github.com/wailsapp/wails/pull/4585) by @rev42 +- Fixed pipelines for nightly release in [#4597](https://github.com/wailsapp/wails/pull/4597) by @riadafridishibly + +## v3.0.0-alpha.27 - 2025-09-07 + +## Fixed +- Fixed redefinition error for liquid glass demo in [#4542](https://github.com/wailsapp/wails/pull/4542) by @Etesam913 + + +## v3.0.0-alpha.26 - 2025-08-24 + +## Added +- Add native Liquid Glass effect support for macOS with NSGlassEffectView (macOS 15.0+) and NSVisualEffectView fallback, including comprehensive material customization options by @leaanthony in [#4534](https://github.com/wailsapp/wails/pull/4534) + +## v3.0.0-alpha.25 - 2025-08-16 + +## Changed +- When running `wails3 update build-assets` with the `-config` parameter, values set via the `-product*` parameters are + no longer ignored, and override the config value. + +## v3.0.0-alpha.24 - 2025-08-13 + +## Added +- Browser URL Sanitisation by @leaanthony in [#4500](https://github.dev/wailsapp/wails/pull/4500). Based on [#4484](https://github.com/wailsapp/wails/pull/4484) by @APShenkin. + +## v3.0.0-alpha.23 - 2025-08-11 + +## Fixed +- Fix SetBackgroundColour on Windows by @PPTGamer in [PR](https://github.com/wailsapp/wails/pull/4492) + +## v3.0.0-alpha.22 - 2025-08-10 + +## Added +- Add Content Protection on Windows/Mac by [@leaanthony](https://github.com/leaanthony) based on the original work of [@Taiterbase](https://github.com/Taiterbase) in this [PR](https://github.com/wailsapp/wails/pull/4241) +- Add support for passing CLI variables to Task commands through `wails3 build` and `wails3 package` aliases (#4422) by @leaanthony in [PR](https://github.com/wailsapp/wails/pull/4488) + +## Changed +- `window.NativeWindowHandle()` -> `window.NativeWindow()` by @leaanthony in [#4471](https://github.com/wailsapp/wails/pull/4471) +- Refactor internal window handling by @leaanthony in [#4471](https://github.com/wailsapp/wails/pull/4471) ++ Fix extra-broad Linux package dependencies, fix outdated RPM dependencies. + +## v3.0.0-alpha.21 - 2025-08-07 + +## Fixed +- Update docs to reflect changes from Manager API Refactoring by @yulesxoxo in [PR #4476](https://github.com/wailsapp/wails/pull/4476) +- Fix Linux .desktop file appicon variable in Linux taskfile [PR #4477](https://github.com/wailsapp/wails/pull/4477) + +## v3.0.0-alpha.20 - 2025-08-06 + +## Fixed +- Update docs to reflect changes from Manager API Refactoring by @yulesxoxo in [PR #4476](https://github.com/wailsapp/wails/pull/4476) + +## v3.0.0-alpha.19 - 2025-08-05 + +## Added +- Support for dropzones with event sourcing dropped element data [@atterpac](https://github.com/atterpac) in [#4318](https://github.com/wailsapp/wails/pull/4318) +- Added `AdditionalLaunchArgs` to `WindowsWindow` options to allow for additional command line arguments to be passed to the WebView2 browser. in [PR](https://github.com/wailsapp/wails/pull/4467) +- Added Run go mod tidy automatically after wails init [@triadmoko](https://github.com/triadmoko) in [PR](https://github.com/wailsapp/wails/pull/4286) +- Windows Snapassist feature by @leaanthony in [PR](https://github.dev/wailsapp/wails/pull/4463) + +## Fixed +- Fix Windows nil pointer dereference bug reported in [#4456](https://github.com/wailsapp/wails/issues/4456) by @leaanthony in [#4460](https://github.com/wailsapp/wails/pull/4460) +- Add support for `allowsBackForwardNavigationGestures` in macOS WKWebView to enable two-finger swipe navigation gestures (#1857) +- Fixes issue where onClick didn't work for menu items initially set as disabled by @leaanthony in [PR #4469](https://github.com/wailsapp/wails/pull/4469). Thanks to @IanVS for the initial investigation. +- Fix Vite server not being cleaned up when build fails (#4403) +- Fixed panic when closing or cancelling a `SaveFileDialog` on windows. Fixed in [PR](https://github.com/wailsapp/wails/pull/4284) by @hkhere +- Fixed HTML level drag and drop on Windows by [@mbaklor](https://github.com/mbaklor) in [#4259](https://github.com/wailsapp/wails/pull/4259) + +## v3.0.0-alpha.18 - 2025-08-03 + +## Added +- Added `AdditionalLaunchArgs` to `WindowsWindow` options to allow for additional command line arguments to be passed to the WebView2 browser. in [PR](https://github.com/wailsapp/wails/pull/4467) +- Added Run go mod tidy automatically after wails init [@triadmoko](https://github.com/triadmoko) in [PR](https://github.com/wailsapp/wails/pull/4286) +- Windows Snapassist feature by @leaanthony in [PR](https://github.dev/wailsapp/wails/pull/4463) + +## Fixed +- Add support for `allowsBackForwardNavigationGestures` in macOS WKWebView to enable two-finger swipe navigation gestures (#1857) +- Fixes issue where onClick didn't work for menu items initially set as disabled by @leaanthony in [PR #4469](https://github.com/wailsapp/wails/pull/4469). Thanks to @IanVS for the initial investigation. +- Fix Vite server not being cleaned up when build fails (#4403) + +## v3.0.0-alpha.17 - 2025-07-31 + +## Fixed +- Fixed notification parsing on Windows @popaprozac in [PR](https://github.com/wailsapp/wails/pull/4450) + +## v3.0.0-alpha.16 - 2025-07-25 + +## Added +- Add Windows `getAccentColor` implementation by [@almas-x](https://github.com/almas-x) in [PR](https://github.com/wailsapp/wails/pull/4427) + +## v3.0.0-alpha.15 - 2025-07-25 + +## Added +- Add Windows `getAccentColor` implementation by [@almas-x](https://github.com/almas-x) in [PR](https://github.com/wailsapp/wails/pull/4427) + +## v3.0.0-alpha.14 - 2025-07-25 + +## Added +- Windows dark theme menus + menubar. By @leaanthony in [a29b4f0861b1d0a700e9eb213c6f1076ec40efd5](https://github.com/wailsapp/wails/commit/a29b4f0861b1d0a700e9eb213c6f1076ec40efd5) +- Rename built-in services for clearer JS/TS bindings by @popaprozac in [PR](https://github.com/wailsapp/wails/pull/4405) + +## v3.0.0-alpha.12 - 2025-07-15 + +### Added +- `app.Env.GetAccentColor` to get the accent color of a user's system. Works on MacOS. by [@etesam913](https://github.com/etesam913) +- Add `window.ToggleFrameless()` api by [@atterpac](https://github.com/atterpac) in [#4137](https://github.com/wailsapp/wails/pull/4137) + +### Fixed +- Fixed doctor command to check for Windows SDK dependencies by [@kodumulo](https://github.com/kodumulo) in [#4390](https://github.com/wailsapp/wails/issues/4390) + + +## v3.0.0-alpha.11 - 2025-07-12 + +## Added +- Add distribution-specific build dependencies for Linux by @leaanthony in [PR](https://github.com/wailsapp/wails/pull/4345) +- Added bindings guide by @atterpac in [PR](https://github.com/wailsapp/wails/pull/4404) + +## v3.0.0-alpha.10 - 2025-07-06 + +### Breaking Changes +- **Manager API Refactoring**: Reorganized application API from flat structure to organized managers for better code organization and discoverability by [@leaanthony](https://github.com/leaanthony) in [#4359](https://github.com/wailsapp/wails/pull/4359) + - `app.NewWebviewWindow()` → `app.Window.New()` + - `app.CurrentWindow()` → `app.Window.Current()` + - `app.GetAllWindows()` → `app.Window.GetAll()` + - `app.WindowByName()` → `app.Window.GetByName()` + - `app.EmitEvent()` → `app.Event.Emit()` + - `app.OnApplicationEvent()` → `app.Event.OnApplicationEvent()` + - `app.OnWindowEvent()` → `app.Event.OnWindowEvent()` + - `app.SetApplicationMenu()` → `app.Menu.SetApplicationMenu()` + - `app.OpenFileDialog()` → `app.Dialog.OpenFile()` + - `app.SaveFileDialog()` → `app.Dialog.SaveFile()` + - `app.MessageDialog()` → `app.Dialog.Message()` + - `app.InfoDialog()` → `app.Dialog.Info()` + - `app.WarningDialog()` → `app.Dialog.Warning()` + - `app.ErrorDialog()` → `app.Dialog.Error()` + - `app.QuestionDialog()` → `app.Dialog.Question()` + - `app.NewSystemTray()` → `app.SystemTray.New()` + - `app.GetSystemTray()` → `app.SystemTray.Get()` + - `app.ShowContextMenu()` → `app.ContextMenu.Show()` + - `app.RegisterKeybinding()` → `app.KeyBinding.Register()` + - `app.UnregisterKeybinding()` → `app.KeyBinding.Unregister()` + - `app.GetPrimaryScreen()` → `app.Screen.GetPrimary()` + - `app.GetAllScreens()` → `app.Screen.GetAll()` + - `app.BrowserOpenURL()` → `app.Browser.OpenURL()` + - `app.Environment()` → `app.Env.GetAll()` + - `app.ClipboardGetText()` → `app.Clipboard.Text()` + - `app.ClipboardSetText()` → `app.Clipboard.SetText()` +- Renamed Service methods: `Name` -> `ServiceName`, `OnStartup` -> `ServiceStartup`, `OnShutdown` -> `ServiceShutdown` by [@leaanthony](https://github.com/leaanthony) +- Moved `Path` and `Paths` methods to `application` package by [@leaanthony](https://github.com/leaanthony) +- The application menu is now macOS only by [@leaanthony](https://github.com/leaanthony) + +### Added + +- **Organized Testing Infrastructure**: Moved Docker test files to dedicated `test/docker/` directory with optimized images and enhanced build reliability by [@leaanthony](https://github.com/leaanthony) in [#4359](https://github.com/wailsapp/wails/pull/4359) +- **Improved Resource Management Patterns**: Added proper event handler cleanup and context-aware goroutine management in examples by [@leaanthony](https://github.com/leaanthony) in [#4359](https://github.com/wailsapp/wails/pull/4359) +- Support aarch64 AppImage builds by [@AkshayKalose](https://github.com/AkshayKalose) in [#3981](https://github.com/wailsapp/wails/pull/3981) +- Add diagnostics section to `wails doctor` by [@leaanthony](https://github.com/leaanthony) +- Add window to context when calling a service method by [@leaanthony](https://github.com/leaanthony) +- Add `window-call` example to demonstrate how to know which window is calling a service by [@leaanthony](https://github.com/leaanthony) +- New Menu guide by [@leaanthony](https://github.com/leaanthony) +- Better panic handling by [@leaanthony](https://github.com/leaanthony) +- New Menu guide by [@leaanthony](https://github.com/leaanthony) +- Add doc comments for Service API by [@fbbdev](https://github.com/fbbdev) in [#4024](https://github.com/wailsapp/wails/pull/4024) +- Add function `application.NewServiceWithOptions` to initialise services with additional configuration by [@leaanthony](https://github.com/leaanthony) in [#4024](https://github.com/wailsapp/wails/pull/4024) +- Improved menu control by [@FalcoG](https://github.com/FalcoG) and [@leaanthony](https://github.com/leaanthony) in [#4031](https://github.com/wailsapp/wails/pull/4031) +- More documentation by [@leaanthony](https://github.com/leaanthony) +- Support cancellation of events in standard event listeners by [@leaanthony](https://github.com/leaanthony) +- Systray `Hide`, `Show` and `Destroy` support by [@leaanthony](https://github.com/leaanthony) +- Systray `SetTooltip` support by [@leaanthony](https://github.com/leaanthony). Original idea by [@lujihong](https://github.com/wailsapp/wails/issues/3487#issuecomment-2633242304) +- Report package path in binding generator warnings about unsupported types by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Add binding generator support for generic aliases by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Add binding generator support for `omitzero` JSON flag by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Add `//wails:ignore` directive to prevent binding generation for chosen service methods by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Add `//wails:internal` directive on services and models to allow for types that are exported in Go but not in JS/TS by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Add binding generator support for constants of alias type to allow for weakly typed enums by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Add binding generator tests for Go 1.24 features by [@fbbdev](https://github.com/fbbdev) in [#4068](https://github.com/wailsapp/wails/pull/4068) +- Add support for macOS 15 "Sequoia" to `OSInfo.Branding` for improved OS version detection in [#4065](https://github.com/wailsapp/wails/pull/4065) +- Add `PostShutdown` hook for running custom code after the shutdown process completes by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066) +- Add `FatalError` struct to support detection of fatal errors in custom error handlers by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066) +- Standardise and document service startup and shutdown order by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066) +- Add test harness for application startup/shutdown sequence and service startup/shutdown tests by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066) +- Add `RegisterService` method for registering services after the application has been created by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066) +- Add `MarshalError` field in application and service options for custom error handling in binding calls by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066) +- Add cancellable promise wrapper that propagates cancellation requests through promise chains by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100) +- Add the ability to tie binding call cancellation to an `AbortSignal` by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100) +- Support `data-wml-*` attributes for WML alongside the usual `wml-*` attributes by [@leaanthony](https://github.com/leaanthony) +- Add `Configure` method on all services for late configuration/dynamic reconfiguration by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) +- `fileserver` service sends a 503 Service Unavailable response when unconfigured by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) +- `kvstore` service provides an in-memory key-value store by default when unconfigured by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) +- Add `Load` method on `kvstore` service to reload data from file after config changes by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) +- Add `Clear` method on `kvstore` service to delete all keys by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) +- Add type `Level` in `log` service to provide JS-side log-level constants by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) +- Add `Log` method on `log` service to specify log-level dynamically by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) +- `sqlite` service provides an in-memory DB by default when unconfigured by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) +- Add method `Close` on `sqlite` service to close the DB manually by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) +- Add cancellation support for query methods on `sqlite` service by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) +- Add prepared statement support to `sqlite` service with JS bindings by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) +- Gin support by [Lea Anthony](https://github.com/leaanthony) in [PR](https://github.com/wailsapp/wails/pull/3537) based on the original work of [@AnalogJ](https://github.com/AnalogJ) in this [PR](https://github.com/wailsapp/wails/pull/3537) +- Fix auto save and password auto save always enabled by [@oSethoum](https://github.com/osethoum) in [#4134](https://github.com/wailsapp/wails/pull/4134) +- Add `SetMenu()` on window to allow for setting a menu on a window by [@leaanthony](https://github.com/leaanthony) +- Add Notification support by [@popaprozac](https://github.com/popaprozac) in [#4098](https://github.com/wailsapp/wails/pull/4098) +-  Add File Association support for mac by [@wimaha](https://github.com/wimaha) in [#4177](https://github.com/wailsapp/wails/pull/4177) +- Add `wails3 tool version` for semantic version bumping by [@leaanthony](https://github.com/leaanthony) +- Add badging support for macOS and Windows by [@popaprozac](https://github.com/popaprozac) in [#](https://github.com/wailsapp/wails/pull/4234) +- Add support for registered/strictly-typed events by [@fbbdev](https://github.com/fbbdev) and [@IanVS](https://github.com/IanVS) in [#4161](https://github.com/wailsapp/wails/pull/4161) +- Add the ability to register hooks for custom events by [@fbbdev](https://github.com/fbbdev) and [@IanVS](https://github.com/IanVS) in [#4161](https://github.com/wailsapp/wails/pull/4161) + +### Fixed + +- Fixed nil pointer dereference in processURLRequest for Mac by [@etesam913](https://github.com/etesam913) in [#4366](https://github.com/wailsapp/wails/pull/4366) +- Fixed a linux bug preventing filtered dialogs by [@bh90210](https://github.com/bh90210) in [#4287](https://github.com/wailsapp/wails/pull/4287) +- Fixed Windows+Linux Edit Menu issues by [@leaanthony](https://github.com/leaanthony) in [#3f78a3a](https://github.com/wailsapp/wails/commit/3f78a3a8ce7837e8b32242c8edbbed431c68c062) +- Updated the minimum system version in macOS .plist files from 10.13.0 to 10.15.0 by [@AkshayKalose](https://github.com/AkshayKalose) in [#3981](https://github.com/wailsapp/wails/pull/3981) +- Window ID skip issue by [@leaanthony](https://github.com/leaanthony) +- Fix nil menu issue when calling RegisterContextMenu by [@leaanthony](https://github.com/leaanthony) +- Fixed dependency cycles in binding generator output by [@fbbdev](https://github.com/fbbdev) in [#4001](https://github.com/wailsapp/wails/pull/4001) +- Fixed use-before-define errors in binding generator output by [@fbbdev](https://github.com/fbbdev) in [#4001](https://github.com/wailsapp/wails/pull/4001) +- Pass build flags to binding generator by [@fbbdev](https://github.com/fbbdev) in [#4023](https://github.com/wailsapp/wails/pull/4023) +- Change paths in windows Taskfile to forward slashes to ensure it works on non-Windows platforms by [@leaanthony](https://github.com/leaanthony) +- Mac + Mac JS events now fixed by [@leaanthony](https://github.com/leaanthony) +- Fixed event deadlock for macOS by [@leaanthony](https://github.com/leaanthony) +- Fixed a `Parameter incorrect` error in Window initialisation on Windows when HTML provided but no JS by [@leaanthony](https://github.com/leaanthony) +- Fixed size of response prefix used for content type sniffing in asset server by [@fbbdev](https://github.com/fbbdev) in [#4049](https://github.com/wailsapp/wails/pull/4049) +- Fixed handling of non-404 responses on root index path in asset server by [@fbbdev](https://github.com/fbbdev) in [#4049](https://github.com/wailsapp/wails/pull/4049) +- Fixed undefined behaviour in binding generator when testing properties of generic types by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Fixed binding generator output for models when underlying type has not the same properties as named wrapper by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Fixed binding generator output for map key types and preprocessing by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Fixed binding generator output for structs that implement marshaler interfaces by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Fixed detection of type cycles involving generic types in binding generator by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Fixed invalid references to unexported models in binding generator output by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Moved injected code to the end of service files by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Fixed handling of errors from file close operations in binding generator by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Suppressed warnings for services that define lifecycle or http methods but no other bound methods by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Fixed non-React templates failing to display Hello World footer when using light system colour scheme by [@marcus-crane](https://github.com/marcus-crane) in [#4056](https://github.com/wailsapp/wails/pull/4056) +- Fixed hidden menu items on macOS by [@leaanthony](https://github.com/leaanthony) +- Fixed handling and formatting of errors in message processors by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066) +-  Fixed skipped service shutdown when quitting application by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066) +-  Ensure menu updates occur on the main thread by [@leaanthony](https://github.com/leaanthony) +- The dragging and resizing mechanism is now more robust and matches expected platform behaviour more closely by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100) +- Fixed [#4097](https://github.com/wailsapp/wails/issues/4097) Webpack/angular discards runtime init code by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100) +- Fixed initially-hidden menu items by [@IanVS](https://github.com/IanVS) in [#4116](https://github.com/wailsapp/wails/pull/4116) +- Fixed assetFileServer not serving `.html` files when non-extension request when `[request]` doesn't exist but `[request].html` does +- Fixed icon generation paths by [@robin-samuel](https://github.com/robin-samuel) in [#4125](https://github.com/wailsapp/wails/pull/4125) +- Fixed `fullscreen`, `unfullscreen`, `unminimise` and `unmaximise` events not being emitted by [@oSethoum](https://github.com/osethoum) in [#4130](https://github.com/wailsapp/wails/pull/4130) +- Fixed NSIS Error because of incorrect prefix on default version in config by [@robin-samuel](https://github.com/robin-samuel) in [#4126](https://github.com/wailsapp/wails/pull/4126) +- Fixed Dialogs runtime function returning escaped paths on Windows by [TheGB0077](https://github.com/TheGB0077) in [#4188](https://github.com/wailsapp/wails/pull/4188) +- Fixed Webview2 detection path in HKCU by [@leaanthony](https://github.com/leaanthony). +- Fixed input issue with macOS by [@leaanthony](https://github.com/leaanthony). +- Fixed Windows icon generation task file name by [@yulesxoxo](https://github.com/yulesxoxo) in [#4219](https://github.com/wailsapp/wails/pull/4219). +- Fixed transparency issue for frameless windows by [@leaanthony](https://github.com/leaanthony) based on work by @kron. +- Fixed focus calls when window is disabled or minimised by [@leaanthony](https://github.com/leaanthony) based on work by @kron. +- Fixed system trays not showing after taskbar restarts by [@leaanthony](https://github.com/leaanthony) based on work by @kron. +- Fixed fallbackResponseWriter not implementing Flush() in [#4245](https://github.com/wailsapp/wails/pull/4245) +- Fixed fallbackResponseWriter not implementing Flush() by [@superDingda] in [#4236](https://github.com/wailsapp/wails/issues/4236) +- Fixed macOS window close with pending async Go-bound function call crashes by [@joshhardy](https://github.com/joshhardy) in [#4354](https://github.com/wailsapp/wails/pull/4354) +- Fixed Windows Efficiency mode startup race condition by [@leaanthony](https://github.com/leaanthony) +- Fixed Windows icon handle cleanup by [@leaanthony](https://github.com/leaanthony). +- Fixed `OpenFileManager` on Windows by [@PPTGamer](https://github.com/PPTGamer) in [#4375](https://github.com/wailsapp/wails/pull/4375). + +### Changed + +- Removed `application.WindowIDKey` and `application.WindowNameKey` (replaced by `application.WindowKey`) by [@leaanthony](https://github.com/leaanthony) +- ContextMenuData now returns a string instead of any by [@leaanthony](https://github.com/leaanthony) +- In JS/TS bindings, class fields of fixed-length array types are now initialized with their expected length instead of being empty by [@fbbdev](https://github.com/fbbdev) in [#4001](https://github.com/wailsapp/wails/pull/4001) +- ContextMenuData now returns a string instead of any by [@leaanthony](https://github.com/leaanthony) +- `application.NewService` does not accept options as an optional parameter anymore (use `application.NewServiceWithOptions` instead) by [@leaanthony](https://github.com/leaanthony) in [#4024](https://github.com/wailsapp/wails/pull/4024) +- Removed `nanoid` dependency by [@leaanthony](https://github.com/leaanthony) +- Updated Window example for mica/acrylic/tabbed window styles by [@leaanthony](https://github.com/leaanthony) +- In JS/TS bindings, `internal.js/ts` model files have been removed; all models can now be found in `models.js/ts` by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- In JS/TS bindings, named types are never rendered as aliases for other named types; the old behaviour is now restricted to aliases by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- In JS/TS bindings, in class mode, struct fields whose type is a type parameter are marked optional and never initialised automatically by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Remove ESLint from templates by by [@IanVS](https://github.com/IanVS) in [#4059](https://github.com/wailsapp/wails/pull/4059) +- Update copyright date to 2025 by [@IanVS](https://github.com/IanVS) in [#4037](https://github.com/wailsapp/wails/pull/4037) +- Add docs for event.Sender by [@IanVS](https://github.com/IanVS) in [#4075](https://github.com/wailsapp/wails/pull/4075) +- Go 1.24 support by [@leaanthony](https://github.com/leaanthony) +- `ServiceStartup` hooks are now invoked when `App.Run` is called, not in `application.New` by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066) +- `ServiceStartup` errors are now returned from `App.Run` instead of terminating the process by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066) +- Binding and dialog calls from JS now reject with error objects instead of strings by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066) +- Improved systray menu positioning on Windows by [@leaanthony](https://github.com/leaanthony) +- The JS runtime has been ported to TypeScript by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100) +- The runtime initialises as soon as it is imported, no need to wait for the window to load by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100) +- The runtime does not export an init method anymore. A side effects import can be used to initialise it by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100) +- Bound methods now return a `CancellablePromise` that rejects with a `CancelError` if cancelled. The actual result of the call is discarded by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100) +- Built-in service types are now consistently called `Service` by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) +- Built-in service creation functions with options are now consistently called `NewWithConfig` by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) +- `Select` method on `sqlite` service is now named `Query` for consistency with Go APIs by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) +- Templates: moved runtime to "dependencies", organized package.json files by [@IanVS](https://github.com/IanVS) in [#4133](https://github.com/wailsapp/wails/pull/4133) +- Creates and ad-hoc signs app bundles in dev to enable certain macOS APIs by [@popaprozac](https://github.com/popaprozac) in [#4171](https://github.com/wailsapp/wails/pull/4171) + +## v3.0.0-alpha.9 - 2025-01-13 + +### Added + +- `app.OpenFileManager(path string, selectFile bool)` to open the system file manager to the path `path` with optional highlighting via `selectFile` by [@Krzysztofz01](https://github.com/Krzysztofz01) [@rcalixte](https://github.com/rcalixte) +- New `-git` flag for `wails3 init` command by [@leaanthony](https://github.com/leaanthony) +- New `wails3 generate webview2bootstrapper` command by [@leaanthony](https://github.com/leaanthony) +- Added `init()` method in runtime to allow manual initialisation of the runtime by [@leaanthony](https://github.com/leaanthony) +- Added `WindowDidMoveDebounceMS` option to Window's WindowOptions by [@leaanthony](https://github.com/leaanthony) +- Added Single Instance feature by [@leaanthony](https://github.com/leaanthony). Based on the [v2 PR](https://github.com/wailsapp/wails/pull/2951) by @APshenkin. +- `wails3 generate template` command by [@leaanthony](https://github.com/leaanthony) +- `wails3 releasenotes` command by [@leaanthony](https://github.com/leaanthony) +- `wails3 update cli` command by [@leaanthony](https://github.com/leaanthony) +- `-clean` option for `wails3 generate bindings` command by [@leaanthony](https://github.com/leaanthony) +- Allow for aarch64 (arm64) AppImage Linux builds by [@AkshayKalose](https://github.com/AkshayKalose) in [#3981](https://github.com/wailsapp/wails/pull/3981) + +### Fixed + +- Fixed min/max width options for linux by @atterpac in [#3979](https://github.com/wailsapp/wails/pull/3979) +- Typescript templates types definitions via npm version bump by @atterpac in [#3966](https://github.com/wailsapp/wails/pull/3966) +- Fix Sveltekit template CSS referance by @atterpac in [#3945](https://github.com/wailsapp/wails/pull/3945) +- Ensure key callbacks in window run() are called on the main thread by [@leaanthony](https://github.com/leaanthony) +- Fix dialog directory chooser examples by [@leaanthony](https://github.com/leaanthony) +- Created new Chinese error page when index.html is missing by [@leaanthony](https://github.com/leaanthony) +-  Ensure `windowDidBecomeKey` callback is running on main thread by [@leaanthony](https://github.com/leaanthony) +-  Support fullscreen for frameless windows by [@leaanthony](https://github.com/leaanthony) +-  Improved window destroying logic by [@leaanthony](https://github.com/leaanthony) +-  Fix window position logic when attached to system trays by [@leaanthony](https://github.com/leaanthony) +-  Support fullscreen for frameless windows by [@leaanthony](https://github.com/leaanthony) +- Fix event handling by [@leaanthony](https://github.com/leaanthony) +- Fixed window shutdown logic by [@leaanthony](https://github.com/leaanthony) +- Common taskfile now defaults to generating Typescript bindings for Typescript templates by [@leaanthony](https://github.com/leaanthony) +- Fix Close application on WM_CLOSE message when no windows are open/systray only by [@mmalcek](https://github.com/mmalcek) in [#3990](https://github.com/wailsapp/wails/pull/3990) +- Fixed garble build by @5aaee9 in [#3192](https://github.com/wailsapp/wails/pull/3192) +- Fixed windows nsis builds by [@leaanthony](https://github.com/leaanthony) + +### Changed + +- Moved build assets to platform specific directories by [@leaanthony](https://github.com/leaanthony) +- Moved and renamed Taskfiles to platform specific directories by [@leaanthony](https://github.com/leaanthony) +- Created a much better experience when `index.html` is missing by [@leaanthony](https://github.com/leaanthony) +- [Windows] Improved performance of minimise and restore by [@leaanthony](https://github.com/leaanthony). Based on original [PR](https://github.com/wailsapp/wails/pull/3955) by [562589540](https://github.com/562589540) +- Removed `ShouldClose` option (Register a hook for events.Common.WindowClosing instead) by [@leaanthony](https://github.com/leaanthony) +- [Windows] Reduced flicker when opening a window by [@leaanthony](https://github.com/leaanthony) +- Removed `Window.Destroy` as this was intended to be an internal function by [@leaanthony](https://github.com/leaanthony) +- Renamed `WindowClose` events to `WindowClosing` by [@leaanthony](https://github.com/leaanthony) +- Frontend builds now use vite environment "development" or "production" depending on build type by [@leaanthony](https://github.com/leaanthony) +- Update to go-webview2 v1.19 by [@leaanthony](https://github.com/leaanthony) + +## v3.0.0-alpha.8.3 - 2024-12-07 + +### Changed + +- Ensure fork of taskfile is used by @leaanthony + +## v3.0.0-alpha.8.2 - 2024-12-07 + +### Changed + +- Update fork of Taskfile to fix version issues when installing using + `go install` by @leaanthony + +## v3.0.0-alpha.8.1 - 2024-12-07 + +### Changed + +- Using fork of Taskfile to fix version issues when installing using + `go install` by @leaanthony + +## v3.0.0-alpha.8 - 2024-12-06 + +### Added + +- Added hyperlink for sponsor by @ansxuman in [#3958](https://github.com/wailsapp/wails/pull/3958) +- Support of linux packaging of deb,rpm, and arch linux packager builds by + @atterpac in [#3909](https://github.com/wailsapp/wails/3909) +- Added Support for darwin universal builds and packages by + [ansxuman](https://github.com/ansxuman) in + [#3902](https://github.com/wailsapp/wails/pull/3902) +- Events documentation to the website by + [atterpac](https://github.com/atterpac) in + [#3867](https://github.com/wailsapp/wails/pull/3867) +- Templates for sveltekit and sveltekit-ts that are set for non-SSR development + by [atterpac](https://github.com/atterpac) in + [#3829](https://github.com/wailsapp/wails/pull/3829) +- Update build assets using new `wails3 update build-assets` command by + [leaanthony](https://github.com/leaanthony) +- Example to test the HTML Drag and Drop API by + [FerroO2000](https://github.com/FerroO2000) in + [#3856](https://github.com/wailsapp/wails/pull/3856) +- File Association support by [leaanthony](https://github.com/leaanthony) in + [#3873](https://github.com/wailsapp/wails/pull/3873) +- New `wails3 generate runtime` command by + [leaanthony](https://github.com/leaanthony) +- New `InitialPosition` option to specify if the window should be centered or + positioned at the given X/Y location by + [leaanthony](https://github.com/leaanthony) in + [#3885](https://github.com/wailsapp/wails/pull/3885) +- Add `Path` & `Paths` methods to `application` package by + [ansxuman](https://github.com/ansxuman) and + [leaanthony](https://github.com/leaanthony) in + [#3823](https://github.com/wailsapp/wails/pull/3823) +- Added `GeneralAutofillEnabled` and `PasswordAutosaveEnabled` Windows options + by [leaanthony](https://github.com/leaanthony) in + [#3766](https://github.com/wailsapp/wails/pull/3766) +- Added the ability to retrieve the window calling a service method by + [leaanthony](https://github.com/leaanthony) in + [#3888](https://github.com/wailsapp/wails/pull/3888) +- Added `EnabledFeatures` and `DisabledFeatures` options for Webview2 by + [leaanthony](https://github.com/leaanthony). +- + +### Changed + +- `service.OnStartup` now shutdowns the application on error and runs + `service.OnShutdown`for any prior services that started by @atterpac in + [#3920](https://github.com/wailsapp/wails/pull/3920) +- Refactored systray click messaging to better align with user interactions by + @atterpac in [#3907](https://github.com/wailsapp/wails/pull/3907) +- Asset embed to include `all:frontend/dist` to support frameworks that generate + subfolders by @atterpac in + [#3887](https://github.com/wailsapp/wails/pull/3887) +- Taskfile refactor by [leaanthony](https://github.com/leaanthony) in + [#3748](https://github.com/wailsapp/wails/pull/3748) +- Upgrade to `go-webview2` v1.0.16 by + [leaanthony](https://github.com/leaanthony) +- Fixed `Screen` type to include `ID` not `Id` by + [etesam913](https://github.com/etesam913) in + [#3778](https://github.com/wailsapp/wails/pull/3778) +- Update `go.mod.tmpl` wails version to support `application.ServiceOptions` by + [northes](https://github.com/northes) in + [#3836](https://github.com/wailsapp/wails/pull/3836) +- Fixed service name determination by [windom](https://github.com/windom/) in + [#3827](https://github.com/wailsapp/wails/pull/3827) +- mkdocs serve now uses docker by [leaanthony](https://github.com/leaanthony) +- Consolidated dev config into `config.yml` by + [leaanthony](https://github.com/leaanthony) +- Systray dialog now defaults to the application icon if available (Windows) by + [@leaanthony](https://github.com/leaanthony) +- Better reporting of GPU + Memory for macOS by + [@leaanthony](https://github.com/leaanthony) +- Removed `WebviewGpuIsDisabled` and `EnableFraudulentWebsiteWarnings` + (superseded by `EnabledFeatures` and `DisabledFeatures` options) by + [leaanthony](https://github.com/leaanthony) + +### Fixed + +- Fixed deadlock in Linux dialog for multiple selections caused by unclosed + channel variable by @michael-freling in + [#3925](https://github.com/wailsapp/wails/pull/3925) +- Fixed cross-platform cleanup for .syso files during Windows build by + [ansxuman](https://github.com/ansxuman) in + [#3924](https://github.com/wailsapp/wails/pull/3924) +- Fixed amd64 appimage compile by @atterpac in + [#3898](https://github.com/wailsapp/wails/pull/3898) +- Fixed build assets update by @ansxuman in + [#3901](https://github.com/wailsapp/wails/pull/3901) +- Fixed Linux systray `OnClick` and `OnRightClick` implementation by @atterpac + in [#3886](https://github.com/wailsapp/wails/pull/3886) +- Fixed `AlwaysOnTop` not working on Mac by + [leaanthony](https://github.com/leaanthony) in + [#3841](https://github.com/wailsapp/wails/pull/3841) +-  Fixed `application.NewEditMenu` including a duplicate + `PasteAndMatchStyle` role in the edit menu on Darwin by + [johnmccabe](https://github.com/johnmccabe) in + [#3839](https://github.com/wailsapp/wails/pull/3839) +- 🐧 Fixed aarch64 compilation + [#3840](https://github.com/wailsapp/wails/issues/3840) in + [#3854](https://github.com/wailsapp/wails/pull/3854) by + [kodflow](https://github.com/kodflow) +- ⊞ Fixed radio group menu items by + [@leaanthony](https://github.com/leaanthony) +- Fix error on building runnable .app on MacOS when 'name' and 'outputfilename' + are different. by @nickisworking in + [#3789](https://github.com/wailsapp/wails/pull/3789) + +## v3.0.0-alpha.7 - 2024-09-18 + +### Added + +- ⊞ New DIP system for Enhanced High DPI Monitor Support by + [mmghv](https://github.com/mmghv) in + [#3665](https://github.com/wailsapp/wails/pull/3665) +- ⊞ Window class name option by [windom](https://github.com/windom/) in + [#3682](https://github.com/wailsapp/wails/pull/3682) +- Services have been expanded to provide plugin functionality. By + [atterpac](https://github.com/atterpac) and + [leaanthony](https://github.com/leaanthony) in + [#3570](https://github.com/wailsapp/wails/pull/3570) + +### Changed + +- Events API change: `On`/`Emit` -> user events, `OnApplicationEvent` -> + Application Events `OnWindowEvent` -> Window Events, by + [leaanthony](https://github.com/leaanthony) +- Fix for Events API on Linux by [TheGB0077](https://github.com/TheGB0077) in + [#3734](https://github.com/wailsapp/wails/pull/3734) +- [CI] improvements to actions & enable to run actions also in forks and + branches prefixed with `v3/` or `v3-` by + [stendler](https://github.com/stendler) in + [#3747](https://github.com/wailsapp/wails/pull/3747) + +### Fixed + +- Fixed bug with usage of customEventProcessor in drag-n-drop example by + [etesam913](https://github.com/etesam913) in + [#3742](https://github.com/wailsapp/wails/pull/3742) +- 🐧 Fixed linux compile error introduced by IgnoreMouseEvents addition by + [atterpac](https://github.com/atterpac) in + [#3721](https://github.com/wailsapp/wails/pull/3721) +- ⊞ Fixed syso icon file generation bug by + [atterpac](https://github.com/atterpac) in + [#3675](https://github.com/wailsapp/wails/pull/3675) +- 🐧 Fix to run natively in wayland incorporated from + [#1811](https://github.com/wailsapp/wails/pull/1811) in + [#3614](https://github.com/wailsapp/wails/pull/3614) by + [@stendler](https://github.com/stendler) +- Do not bind internal service methods in + [#3720](https://github.com/wailsapp/wails/pull/3720) by + [leaanthony](https://github.com/leaanthony) +- ⊞ Fixed system tray startup panic in + [#3693](https://github.com/wailsapp/wails/issues/3693) by + [@DeltaLaboratory](https://github.com/DeltaLaboratory) +- Do not bind internal service methods in + [#3720](https://github.com/wailsapp/wails/pull/3720) by + [leaanthony](https://github.com/leaanthony) +- ⊞ Fixed system tray startup panic in + [#3693](https://github.com/wailsapp/wails/issues/3693) by + [@DeltaLaboratory](https://github.com/DeltaLaboratory) +- Major menu item refactor and event handling. Mainly improves macOS for now. By + [leaanthony](https://github.com/leaanthony) +- Fix tests after plugins and event refactor in + [#3746](https://github.com/wailsapp/wails/pull/3746) by + [@stendler](https://github.com/stendler) +- ⊞ Fixed `Failed to unregister class Chrome_WidgetWin_0` warning. By + [leaanthony](https://github.com/leaanthony) + +## v3.0.0-alpha.6 - 2024-07-30 + +### Fixed + +- Module issues + +## v3.0.0-alpha.5 - 2024-07-30 + +### Added + +- 🐧 WindowDidMove / WindowDidResize events in + [#3580](https://github.com/wailsapp/wails/pull/3580) +- ⊞ WindowDidResize event in + [#3580](https://github.com/wailsapp/wails/pull/3580) +-  add Event ApplicationShouldHandleReopen to be able to handle dock + icon click by @5aaee9 in [#2991](https://github.com/wailsapp/wails/pull/2991) +-  add getPrimaryScreen/getScreens to impl by @tmclane in + [#2618](https://github.com/wailsapp/wails/pull/2618) +-  add option for showing the toolbar in fullscreen mode on macOS by + [@fbbdev](https://github.com/fbbdev) in + [#3282](https://github.com/wailsapp/wails/pull/3282) +- 🐧 add onKeyPress logic to convert linux keypress into an accelerator + @[Atterpac](https://github.com/Atterpac) + in[#3022](https://github.com/wailsapp/wails/pull/3022]) +- 🐧 add task `run:linux` by + [@marcus-crane](https://github.com/marcus-crane) in + [#3146](https://github.com/wailsapp/wails/pull/3146) +- Export `SetIcon` method by [@almas-x](https://github.com/almas-x) in + [PR](https://github.com/wailsapp/wails/pull/3147) +- Improve `OnShutdown` by [@almas-x](https://github.com/almas-x) in + [PR](https://github.com/wailsapp/wails/pull/3189) +- Restore `ToggleMaximise` method in `Window` interface by + [@fbbdev](https://github.com/fbbdev) in + [#3281](https://github.com/wailsapp/wails/pull/3281) +- Added more information to `Environment()`. By @leaanthony in + [aba82cc](https://github.com/wailsapp/wails/commit/aba82cc52787c97fb99afa58b8b63a0004b7ff6c) + based on [PR](https://github.com/wailsapp/wails/pull/2044) by @Mai-Lapyst +- Expose the `WebviewWindow.IsFocused` method on the `Window` interface by + [@fbbdev](https://github.com/fbbdev) in + [#3295](https://github.com/wailsapp/wails/pull/3295) +- Support multiple space-separated trigger events in the WML system by + [@fbbdev](https://github.com/fbbdev) in + [#3295](https://github.com/wailsapp/wails/pull/3295) +- Add ESM exports from the bundled JS runtime script by + [@fbbdev](https://github.com/fbbdev) in + [#3295](https://github.com/wailsapp/wails/pull/3295) +- Add binding generator flag for using the bundled JS runtime script instead of + the npm package by [@fbbdev](https://github.com/fbbdev) in + [#3334](https://github.com/wailsapp/wails/pull/3334) +- Implement `setIcon` on linux by [@abichinger](https://github.com/abichinger) + in [#3354](https://github.com/wailsapp/wails/pull/3354) +- Add flag `-port` to dev command and support environment variable + `WAILS_VITE_PORT` by [@abichinger](https://github.com/abichinger) in + [#3429](https://github.com/wailsapp/wails/pull/3429) +- Add tests for bound method calls by + [@abichinger](https://github.com/abichinger) in + [#3431](https://github.com/wailsapp/wails/pull/3431) +- ⊞ add `SetIgnoreMouseEvents` for already created window by + [@bruxaodev](https://github.com/bruxaodev) in + [#3667](https://github.com/wailsapp/wails/pull/3667) +-  Add ability to set a window's stacking level (order) by + [@OlegGulevskyy](https://github.com/OlegGulevskyy) in + [#3674](https://github.com/wailsapp/wails/pull/3674) + +### Fixed + +- Fixed resize event messaging by [atterpac](https://github.com/atterpac) in + [#3606](https://github.com/wailsapp/wails/pull/3606) +- 🐧Fixed theme handling error on NixOS by + [tmclane](https://github.com/tmclane) in + [#3515](https://github.com/wailsapp/wails/pull/3515) +- Fixed cross volume project install for windows by + [atterpac](https://github.com/atterac) in + [#3512](https://github.com/wailsapp/wails/pull/3512) +- Fixed react template css to show footer by + [atterpac](https://github.com/atterpac) in + [#3477](https://github.com/wailsapp/wails/pull/3477) +- Fixed zombie processes when working in devmode by updating to latest refresh + by [Atterpac](https://github.com/atterpac) in + [#3320](https://github.com/wailsapp/wails/pull/3320). +- Fixed appimage webkit file sourcing by [Atterpac](https://github.com/atterpac) + in [#3306](https://github.com/wailsapp/wails/pull/3306). +- Fixed Doctor apt package verify by [Atterpac](https://github.com/Atterpac) in + [#2972](https://github.com/wailsapp/wails/pull/2972). +- Fixed application frozen when quit (Darwin) by @5aaee9 in + [#2982](https://github.com/wailsapp/wails/pull/2982) +- Fixed background colours of examples on Windows by + [mmghv](https://github.com/mmghv) in + [#2750](https://github.com/wailsapp/wails/pull/2750). +- Fixed default context menus by [mmghv](https://github.com/mmghv) in + [#2753](https://github.com/wailsapp/wails/pull/2753). +- Fixed hex values for arrow keys on Darwin by + [jaybeecave](https://github.com/jaybeecave) in + [#3052](https://github.com/wailsapp/wails/pull/3052). +- Set drag-n-drop for windows to working. Added by + [@pylotlight](https://github.com/pylotlight) in + [PR](https://github.com/wailsapp/wails/pull/3039) +- Fixed bug for linux in doctor in the event user doesn't have proper drivers + installed. Added by [@pylotlight](https://github.com/pylotlight) in + [PR](https://github.com/wailsapp/wails/pull/3032) +- Fix dpi scaling on start up (windows). Changed by [@almas-x](https://github.com/almas-x) in + [PR](https://github.com/wailsapp/wails/pull/3145) +- Fix replace line in `go.mod` to use relative paths. Fixes Windows paths with + spaces - @leaanthony. +- Fix MacOS systray click handling when no attached window by + [thomas-senechal](https://github.com/thomas-senechal) in PR + [#3207](https://github.com/wailsapp/wails/pull/3207) +- Fix failing Windows build due to unknown option by + [thomas-senechal](https://github.com/thomas-senechal) in PR + [#3208](https://github.com/wailsapp/wails/pull/3208) +- Fix crash on windows left clicking the systray icon when not having an + attached window [tw1nk](https://github.com/tw1nk) in PR + [#3271](https://github.com/wailsapp/wails/pull/3271) +- Fix wrong baseURL when open window twice by @5aaee9 in PR + [#3273](https://github.com/wailsapp/wails/pull/3273) +- Fix ordering of if branches in `WebviewWindow.Restore` method by + [@fbbdev](https://github.com/fbbdev) in + [#3279](https://github.com/wailsapp/wails/pull/3279) +- Correctly compute `startURL` across multiple `GetStartURL` invocations when + `FRONTEND_DEVSERVER_URL` is present. + [#3299](https://github.com/wailsapp/wails/pull/3299) +- Fix the JS type of the `Screen` struct to match its Go counterpart by + [@fbbdev](https://github.com/fbbdev) in + [#3295](https://github.com/wailsapp/wails/pull/3295) +- Fix the `WML.Reload` method to ensure proper cleanup of registered event + listeners by [@fbbdev](https://github.com/fbbdev) in + [#3295](https://github.com/wailsapp/wails/pull/3295) +- Fix custom context menu closing immediately on linux by + [@abichinger](https://github.com/abichinger) in + [#3330](https://github.com/wailsapp/wails/pull/3330) +- Fix the output path and extension of model files produced by the binding + generator by [@fbbdev](https://github.com/fbbdev) in + [#3334](https://github.com/wailsapp/wails/pull/3334) +- Fix the import paths of model files in JS code produced by the binding + generator by [@fbbdev](https://github.com/fbbdev) in + [#3334](https://github.com/wailsapp/wails/pull/3334) +- Fix drag-n-drop on some linux distros by + [@abichinger](https://github.com/abichinger) in + [#3346](https://github.com/wailsapp/wails/pull/3346) +- Fix missing task for macOS when using `wails3 task dev` by + [@hfoxy](https://github.com/hfoxy) in + [#3417](https://github.com/wailsapp/wails/pull/3417) +- Fix registering events causing a nil map assignment by + [@hfoxy](https://github.com/hfoxy) in + [#3426](https://github.com/wailsapp/wails/pull/3426) +- Fix unmarshaling of bound method parameters by + [@fbbdev](https://github.com/fbbdev) in + [#3431](https://github.com/wailsapp/wails/pull/3431) +- Fix handling of multiple return values from bound methods by + [@fbbdev](https://github.com/fbbdev) in + [#3431](https://github.com/wailsapp/wails/pull/3431) +- Fix doctor detection of npm that is not installed with system package manager + by [@pekim](https://github.com/pekim) in + [#3458](https://github.com/wailsapp/wails/pull/3458) +- Fix missing MicrosoftEdgeWebview2Setup.exe. Thanks to + [@robin-samuel](https://github.com/robin-samuel). +- Fix random crash on linux due to window ID handling by @leaanthony. Based on + PR [#3466](https://github.com/wailsapp/wails/pull/3622) by + [@5aaee9](https://github.com/5aaee9). +- Fix systemTray.setIcon crashing on Linux by + [@windom](https://github.com/windom/) in + [#3636](https://github.com/wailsapp/wails/pull/3636). +- Fix Ensure Window Frame is Applied on First Call in `setFrameless` Function on + Windows by [@bruxaodev](https://github.com/bruxaodev/) in + [#3691](https://github.com/wailsapp/wails/pull/3691). + +### Changed + +- Renamed `AbsolutePosition()` to `Position()` by + [mmghv](https://github.com/mmghv) in + [#3611](https://github.com/wailsapp/wails/pull/3611) +- Update linux webkit dependency to webkit2gtk-4.1 over webkitgtk2-4.0 to + support Ubuntu 24.04 LTS by [atterpac](https://github.com/atterpac) in + [#3461](https://github.com/wailsapp/wails/pull/3461) +- The bundled JS runtime script is now an ESM module: script tags importing it + must have the `type="module"` attribute. By + [@fbbdev](https://github.com/fbbdev) in + [#3295](https://github.com/wailsapp/wails/pull/3295) +- The `@wailsio/runtime` package does not publish its API on the `window.wails` + object, and does not start the WML system. This has been done to improve + encapsulation. The WML system can be started manually if desired by calling + the new `WML.Enable` method. The bundled JS runtime script still performs both + operations automatically. By [@fbbdev](https://github.com/fbbdev) in + [#3295](https://github.com/wailsapp/wails/pull/3295) +- The Window API module `@wailsio/runtime/src/window` now exposes the containing + window object as a default export. It is not possible anymore to import + individual methods through ESM named or namespace import syntax. +- The JS window API has been updated to match the current Go `WebviewWindow` + API. Some methods have changed name or prototype, specifically: `Screen` + becomes `GetScreen`; `GetZoomLevel`/`SetZoomLevel` become `GetZoom`/`SetZoom`; + `GetZoom`, `Width` and `Height` now return values directly instead of wrapping + them within objects. By [@fbbdev](https://github.com/fbbdev) in + [#3295](https://github.com/wailsapp/wails/pull/3295) +- The binding generator now uses calls by ID by default. The `-id` CLI option + has been removed. Use the `-names` CLI option to switch back to calls by name. + By [@fbbdev](https://github.com/fbbdev) in + [#3468](https://github.com/wailsapp/wails/pull/3468) +- New binding code layout: output files were previously organised in folders + named after their containing package; now full Go import paths are used, + including the module path. By [@fbbdev](https://github.com/fbbdev) in + [#3468](https://github.com/wailsapp/wails/pull/3468) +- The struct field `application.Options.Bind` has been renamed to + `application.Options.Services`. By [@fbbdev](https://github.com/fbbdev) in + [#3468](https://github.com/wailsapp/wails/pull/3468) +- New syntax for binding services: service instances must now be wrapped in a + call to `application.NewService`. By [@fbbdev](https://github.com/fbbdev) in + [#3468](https://github.com/wailsapp/wails/pull/3468) +- Disable spinner on Non-Terminal or CI Environment by + [@DeltaLaboratory](https://github.com/DeltaLaboratory) in + [#3574](https://github.com/wailsapp/wails/pull/3574) diff --git a/docs/src/content/docs/community/links.md b/docs/src/content/docs/community/links.md new file mode 100644 index 000000000..9507991dd --- /dev/null +++ b/docs/src/content/docs/community/links.md @@ -0,0 +1,27 @@ +--- +title: Links +--- + +This page serves as a list for community related links. + +:::tip[How to Submit a Link] + +You can click the `Edit page` at the bottom of this page to submit a PR. + +::: + +## Awesome Wails + +The [definitive list](https://github.com/wailsapp/awesome-wails) of links +related to Wails. + +## Support Channels + +- [Wails Discord Server](https://discord.gg/bdj28QNHmT) +- [Github Issues](https://github.com/wailsapp/wails/issues) + +## Social Media + +- [Twitter](https://x.com/wailsapp) +- [Wails Chinese Community QQ Group](https://qm.qq.com/cgi-bin/qm/qr?k=PmIURne5hFGNd7QWzW5qd6FV-INEjNJv&jump_from=webapi) - + Group number: 1067173054 diff --git a/docs/src/content/docs/community/showcase/_template.md b/docs/src/content/docs/community/showcase/_template.md new file mode 100644 index 000000000..f98a2ef06 --- /dev/null +++ b/docs/src/content/docs/community/showcase/_template.md @@ -0,0 +1,25 @@ +--- +title: My Project +draft: true +--- + + + +![My Project Screenshot](../../../../assets/showcase-images/your-project.webp) + + + +Your project description goes here. Explain what it does, what makes it special, and why you built it with Wails. + + + +[Visit Project Website](https://your-project.com) | [View on GitHub](https://github.com/yourusername/your-project) diff --git a/docs/src/content/docs/community/showcase/bulletinboard.md b/docs/src/content/docs/community/showcase/bulletinboard.md new file mode 100644 index 000000000..58d24b97f --- /dev/null +++ b/docs/src/content/docs/community/showcase/bulletinboard.md @@ -0,0 +1,16 @@ +--- +title: BulletinBoard +--- + +![BulletinBoard](../../../../assets/showcase-images/bboard.webp) + +The [BulletinBoard](https://github.com/raguay/BulletinBoard) application is a +versital message board for static messages or dialogs to get information from +the user for a script. It has a TUI for creating new dialogs that can latter be +used to get information from the user. It's design is to stay running on your +system and show the information as needed and then hide away. I have a process +for watching a file on my system and sending the contents to BulletinBoard when +changed. It works great with my workflows. T here is also an +[Alfred workflow](https://github.com/raguay/MyAlfred/blob/master/Alfred%205/EmailIt.alfredworkflow) +for sending information to the program. The workflow is also for working with +[EmailIt](https://github.com/raguay/EmailIt). diff --git a/docs/src/content/docs/community/showcase/cfntracker.md b/docs/src/content/docs/community/showcase/cfntracker.md new file mode 100644 index 000000000..dc9c030f3 --- /dev/null +++ b/docs/src/content/docs/community/showcase/cfntracker.md @@ -0,0 +1,36 @@ +--- +title: CFN Tracker +--- + +![CFN Tracker](../../../../assets/showcase-images/cfntracker.webp) + +[CFN Tracker](https://github.com/williamsjokvist/cfn-tracker) - Track any Street +Fighter 6 or V CFN profile's live matches. Check +[the website](https://cfn.williamsjokvist.se/) to get started. + +## Features + +- Real-time match tracking +- Storing match logs and statistics +- Support for displaying live stats to OBS via Browser Source +- Support for both SF6 and SFV +- Ability for users to create their own OBS Browser themes with CSS + +### Major tech used alongside Wails + +- [Task](https://github.com/go-task/task) - wrapping the Wails CLI to make + common commands easy to use +- [React](https://github.com/facebook/react) - chosen for its rich ecosystem + (radix, framer-motion) +- [Bun](https://github.com/oven-sh/bun) - used for its fast dependency + resolution and build-time +- [Rod](https://github.com/go-rod/rod) - headless browser automation for + authentication and polling changes +- [SQLite](https://github.com/mattn/go-sqlite3) - used for storing matches, + sessions and profiles +- [Server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) - + a http stream to send tracking updates to OBS browser sources +- [i18next](https://github.com/i18next/) - with backend connector to serve + localization objects from the Go layer +- [xstate](https://github.com/statelyai/xstate) - state machines for auth + process and tracking diff --git a/docs/src/content/docs/community/showcase/clave.md b/docs/src/content/docs/community/showcase/clave.md new file mode 100644 index 000000000..9c405e616 --- /dev/null +++ b/docs/src/content/docs/community/showcase/clave.md @@ -0,0 +1,18 @@ +--- +title: Clave +--- + +![Clave](../../../../assets/showcase-images/clave.png) + +Key Features + +- 🎨 Simple, intuitive design for hassle-free experience +- ✅ Add accounts easily via manual entry or QR code image import. +- 🔒 End-to-end encryption with PIN or Touch ID(macOS only). +- 💻 Available for macOS, Windows, and Linux +- ⚡ Quick access from system tray for convenience +- 📂 Easily backup and restore your profiles + +Try Clave Today! + +💻 [https://clave.rocks](https://clave.rocks) diff --git a/docs/src/content/docs/community/showcase/emailit.md b/docs/src/content/docs/community/showcase/emailit.md new file mode 100644 index 000000000..6afc1da5e --- /dev/null +++ b/docs/src/content/docs/community/showcase/emailit.md @@ -0,0 +1,14 @@ +--- +title: EmailIt +--- + +![EmailIt](../../../../assets/showcase-images/emailit.webp) + +[EmailIt](https://github.com/raguay/EmailIt/) is a Wails 2 program that is a +markdown based email sender only with nine notepads, scripts to manipulate the +text, and templates. It also has a scripts terminal to run scripts in EmailIt on +files in your system. The scripts and templates can be used from the commandline +itself or with the Alfred, Keyboard Maestro, Dropzone, or PopClip extensions. It +also supports scripts and themes downloaded form GitHub. Documentation is not +complete, but the programs works. It’s built using Wails2 and Svelte, and the +download is a universal macOS application. diff --git a/docs/src/content/docs/community/showcase/encrypteasy.md b/docs/src/content/docs/community/showcase/encrypteasy.md new file mode 100644 index 000000000..3a098fac9 --- /dev/null +++ b/docs/src/content/docs/community/showcase/encrypteasy.md @@ -0,0 +1,16 @@ +--- +title: EncryptEasy +--- + +![EncryptEasy](../../../../assets/showcase-images/encrypteasy.webp) + +**[EncryptEasy](https://www.encrypteasy.app) is a simple and easy to use PGP +encryption tool, managing all your and your contacts keys. Encryption should be +simple. Developed with Wails.** + +Encrypting messages using PGP is the industry standard. Everyone has a private +and a public key. Your private key, well, needs to be kept private so only you +can read messages. Your public key is distributed to anyone who wants to send +you secret, encrypted messages. Managing keys, encrypting messages and +decrypting messages should be a smooth experience. EncryptEasy is all about +making it easy. diff --git a/docs/src/content/docs/community/showcase/espstudio.md b/docs/src/content/docs/community/showcase/espstudio.md new file mode 100644 index 000000000..0ac90a827 --- /dev/null +++ b/docs/src/content/docs/community/showcase/espstudio.md @@ -0,0 +1,9 @@ +--- +title: ESP Studio +--- + +![ESP Studio](../../../../assets/showcase-images/esp-studio.png) + +[ESP Studio](https://github.com/torabian/esp-studio) - Cross platform, Desktop, +Cloud, and Embedded software for controlling ESP/Arduino devices, and building +complex IOT workflows and control systems diff --git a/docs/src/content/docs/community/showcase/filehound.md b/docs/src/content/docs/community/showcase/filehound.md new file mode 100644 index 000000000..627007f97 --- /dev/null +++ b/docs/src/content/docs/community/showcase/filehound.md @@ -0,0 +1,29 @@ +--- +title: FileHound Export Utility +--- + +![FileHound Export Utility](../../../../assets/showcase-images/filehound.webp) + +[FileHound Export Utility](https://www.filehound.co.uk/) FileHound is a cloud +document management platform made for secure file retention, business process +automation and SmartCapture capabilities. + +The FileHound Export Utility allows FileHound Administrators the ability to run +a secure document and data extraction tasks for alternative back-up and recovery +purposes. This application will download all documents and/or meta data saved in +FileHound based on the filters you choose. The metadata will be exported in both +JSON and XML formats. + +Backend built with: + +- Go 1.15 +- Wails 1.11.0 +- go-sqlite3 1.14.6 +- go-linq 3.2 + +Frontend with: + +- Vue 2.6.11 +- Vuex 3.4.0 +- TypeScript +- Tailwind 1.9.6 diff --git a/docs/src/content/docs/community/showcase/hiposter.md b/docs/src/content/docs/community/showcase/hiposter.md new file mode 100644 index 000000000..08acfa832 --- /dev/null +++ b/docs/src/content/docs/community/showcase/hiposter.md @@ -0,0 +1,8 @@ +--- +title: hiposter +--- + +![hiposter](../../../../assets/showcase-images/hiposter.webp) + +[hiposter](https://github.com/obity/hiposter) is a simple and efficient http API +testing client tool. Based on Wails, Go and sveltejs. diff --git a/docs/src/content/docs/community/showcase/index.mdx b/docs/src/content/docs/community/showcase/index.mdx new file mode 100644 index 000000000..e1da3f75d --- /dev/null +++ b/docs/src/content/docs/community/showcase/index.mdx @@ -0,0 +1,189 @@ +--- +title: Overview +sidebar: + order: 1 +--- + +import { ShowcaseImage } from "starlight-showcases"; +import { Steps } from "@astrojs/starlight/components"; + +:::tip[See how to add your project] + +Check out the +[How to add my project in showcase](#how-to-add-my-project-in-showcase) section. + +::: + + + +## How to add my project in showcase + + + +1. Make a fork of the repository. +2. Add the image(s) under `docs/src/assets/showcase-images` folder. +3. Make a copy of the `_template.md` file under + `docs/src/content/docs/community/showcase` folder. +4. Rename the copied file to the name of your project. (Name should not start + with `_`)) +5. Update the title, image, link and content of the file. +6. Add it on the above list in + `docs/src/content/docs/community/showcase/index.mdx`. +7. Submit a PR. + + diff --git a/docs/src/content/docs/community/showcase/mchat.md b/docs/src/content/docs/community/showcase/mchat.md new file mode 100644 index 000000000..444aed343 --- /dev/null +++ b/docs/src/content/docs/community/showcase/mchat.md @@ -0,0 +1,8 @@ +--- +title: Mchat +--- + +![Mchat](../../../../assets/showcase-images/mchat.png) + +[Official page](https://marcio199226.github.io/mchat-site/public/) Fully +anonymous end2end encrypted chat. diff --git a/docs/src/content/docs/community/showcase/minecraftupdater.md b/docs/src/content/docs/community/showcase/minecraftupdater.md new file mode 100644 index 000000000..57cd80e30 --- /dev/null +++ b/docs/src/content/docs/community/showcase/minecraftupdater.md @@ -0,0 +1,10 @@ +--- +title: Minecraft Updater +--- + +![Minecraft Updater](../../../../assets/showcase-images/minecraft-mod-updater.webp) + +[Minecraft Updater](https://github.com/Gurkengewuerz/MinecraftModUpdater) is a +utility tool to update and synchronize Minecraft mods for your userbase. It’s +built using Wails2 and React with [antd](https://ant.design/) as frontend +framework. diff --git a/docs/src/content/docs/community/showcase/minesweeper-xp.md b/docs/src/content/docs/community/showcase/minesweeper-xp.md new file mode 100644 index 000000000..9ca30ed55 --- /dev/null +++ b/docs/src/content/docs/community/showcase/minesweeper-xp.md @@ -0,0 +1,8 @@ +--- +title: Minesweeper XP +--- + +![Minesweeper XP](../../../../assets/showcase-images/minesweeper-xp.webp) + +[Minesweeper-XP](https://git.new/Minesweeper-XP) allows you to experience the +classic Minesweeper XP (+ 98 and 3.1) on macOS, Windows, and Linux! diff --git a/docs/src/content/docs/community/showcase/modalfilemanager.md b/docs/src/content/docs/community/showcase/modalfilemanager.md new file mode 100644 index 000000000..7a7748d4d --- /dev/null +++ b/docs/src/content/docs/community/showcase/modalfilemanager.md @@ -0,0 +1,21 @@ +--- +title: Modal File Manager +--- + +![Modal File Manager](../../../../assets/showcase-images/modalfilemanager.webp) + +[Modal File Manager](https://github.com/raguay/ModalFileManager) is a dual pane +file manager using web technologies. My original design was based on NW.js and +can be found [here](https://github.com/raguay/ModalFileManager-NWjs). This +version uses the same Svelte based frontend code (but it has be greatly modified +since the departure from NW.js), but the backend is a +[Wails 2](https://wails.io/) implementation. By using this implementation, I no +longer use command line `rm`, `cp`, etc. commands, but a git install has to be +on the system to download themes and extensions. It is fully coded using Go and +runs much faster than the previous versions. + +This file manager is designed around the same principle as Vim: a state +controlled keyboard actions. The number of states isn't fixed, but very +programmable. Therefore, an infinite number of keyboard configurations can be +created and used. This is the main difference from other file managers. There +are themes and extensions available to download from GitHub. diff --git a/docs/src/content/docs/community/showcase/mollywallet.md b/docs/src/content/docs/community/showcase/mollywallet.md new file mode 100644 index 000000000..930aebd75 --- /dev/null +++ b/docs/src/content/docs/community/showcase/mollywallet.md @@ -0,0 +1,9 @@ +--- +title: Molley Wallet +--- + +![Molly Wallet](../../../../assets/showcase-images/mollywallet.webp) + +[Molly Wallet](https://github.com/grvlle/constellation_wallet/) the official +$DAG wallet of the Constellation Network. It'll let users interact with the +Hypergraph Network in various ways, not limited to producing $DAG transactions. diff --git a/docs/src/content/docs/community/showcase/october.md b/docs/src/content/docs/community/showcase/october.md new file mode 100644 index 000000000..60a29e91b --- /dev/null +++ b/docs/src/content/docs/community/showcase/october.md @@ -0,0 +1,16 @@ +--- +title: October +--- + +![October](../../../../assets/showcase-images/october.webp) + +[October](https://october.utf9k.net) is a small Wails application that makes it +really easy to extract highlights from +[Kobo eReaders](https://en.wikipedia.org/wiki/Kobo_eReader) and then forward +them to [Readwise](https://readwise.io). + +It has a relatively small scope with all platform versions weighing in under +10MB, and that's without enabling [UPX compression](https://upx.github.io/)! + +In contrast, the author's previous attempts with Electron quickly bloated to +several hundred megabytes. diff --git a/docs/src/content/docs/community/showcase/optimus.md b/docs/src/content/docs/community/showcase/optimus.md new file mode 100644 index 000000000..6128258b2 --- /dev/null +++ b/docs/src/content/docs/community/showcase/optimus.md @@ -0,0 +1,9 @@ +--- +title: Optimus +--- + +![Optimus](../../../../assets/showcase-images/optimus.webp) + +[Optimus](https://github.com/splode/optimus) is a desktop image optimization +application. It supports conversion and compression between WebP, JPEG, and PNG +image formats. diff --git a/docs/src/content/docs/community/showcase/portfall.md b/docs/src/content/docs/community/showcase/portfall.md new file mode 100644 index 000000000..c1ffc76f9 --- /dev/null +++ b/docs/src/content/docs/community/showcase/portfall.md @@ -0,0 +1,8 @@ +--- +title: Portfall +--- + +![Portfall](../../../../assets/showcase-images/portfall.webp) + +[Portfall](https://github.com/rekon-oss/portfall) - A desktop k8s +port-forwarding portal for easy access to all your cluster UIs diff --git a/docs/src/content/docs/community/showcase/resizem.md b/docs/src/content/docs/community/showcase/resizem.md new file mode 100644 index 000000000..3e8761dbc --- /dev/null +++ b/docs/src/content/docs/community/showcase/resizem.md @@ -0,0 +1,9 @@ +--- +title: Resizem +--- + +![Resizem Screenshot](../../../../assets/showcase-images/resizem.webp) + +[Resizem](https://github.com/barats/resizem) - is an app designed for bulk image +process. It is particularly useful for users who need to resize, convert, and +manage large numbers of image files at once. diff --git a/docs/src/content/docs/community/showcase/restic-browser.md b/docs/src/content/docs/community/showcase/restic-browser.md new file mode 100644 index 000000000..5a68cb02e --- /dev/null +++ b/docs/src/content/docs/community/showcase/restic-browser.md @@ -0,0 +1,9 @@ +--- +title: Restic Browser +--- + +![Restic Browser](../../../../assets/showcase-images/restic-browser-2.png) + +[Restic-Browser](https://github.com/emuell/restic-browser) - A simple, +cross-platform [restic](https://github.com/restic/restic) backup GUI for +browsing and restoring restic repositories. diff --git a/docs/src/content/docs/community/showcase/riftshare.md b/docs/src/content/docs/community/showcase/riftshare.md new file mode 100644 index 000000000..b6ed8f464 --- /dev/null +++ b/docs/src/content/docs/community/showcase/riftshare.md @@ -0,0 +1,23 @@ +--- +title: RiftShare +--- + +![RiftShare](../../../../assets/showcase-images/riftshare-main.webp) + +Easy, Secure, and Free file sharing for everyone. Learn more at +[Riftshare.app](https://riftshare.app) + +## Features + +- Easy secure file sharing between computers both in the local network and + through the internet +- Supports sending files or directories securely through the + [magic wormhole protocol](https://magic-wormhole.readthedocs.io/en/latest/) +- Compatible with all other apps using magic wormhole (magic-wormhole or + wormhole-william CLI, wormhole-gui, etc.) +- Automatic zipping of multiple selected files to send at once +- Full animations, progress bar, and cancellation support for sending and + receiving +- Native OS File Selection +- Open files in one click once received +- Auto Update - don't worry about having the latest release! diff --git a/docs/src/content/docs/community/showcase/scriptbar.md b/docs/src/content/docs/community/showcase/scriptbar.md new file mode 100644 index 000000000..1e03ab528 --- /dev/null +++ b/docs/src/content/docs/community/showcase/scriptbar.md @@ -0,0 +1,14 @@ +--- +title: ScriptBar +--- + +![ScriptBar](../../../../assets/showcase-images/scriptbar.webp) + +[ScriptBar](https://GitHub.com/raguay/ScriptBarApp) is a program to show the +output of scripts or [Node-Red](https://nodered.org) server. It runs scripts +defined in EmailIt program and shows the output. Scripts from xBar or TextBar +can be used, but currently on the TextBar scripts work well. It also displays +the output of scripts on your system. ScriptBar doesn't put them in the menubar, +but has them all in a convient window for easy viewing. You can have multiple +tabs to have many different things show. You can also keep the links to your +most visited web sites. diff --git a/docs/src/content/docs/community/showcase/snippetexpander.md b/docs/src/content/docs/community/showcase/snippetexpander.md new file mode 100644 index 000000000..0b5a88801 --- /dev/null +++ b/docs/src/content/docs/community/showcase/snippetexpander.md @@ -0,0 +1,34 @@ +--- +title: Snippet Expander +--- + +![Snippet Expander Screenshot](../../../../assets/showcase-images/snippetexpandergui-select-snippet.png) + +Screenshot of Snippet Expander's Select Snippet window + +![Snippet Expander Screenshot](../../../../assets/showcase-images/snippetexpandergui-add-snippet.png) + +Screenshot of Snippet Expander's Add Snippet screen + +![Snippet Expander Screenshot](../../../../assets/showcase-images/snippetexpandergui-search-and-paste.png) + +Screenshot of Snippet Expander's Search & Paste window + +[Snippet Expander](https://snippetexpander.org) is "Your little expandable text +snippets helper", for Linux. + +Snippet Expander comprises of a GUI application built with Wails for managing +snippets and settings, with a Search & Paste window mode for quickly selecting +and pasting a snippet. + +The Wails based GUI, go-lang CLI and vala-lang auto expander daemon all +communicate with a go-lang daemon via D-Bus. The daemon does the majority of the +work, managing the database of snippets and common settings, and providing +services for expanding and pasting snippets etc. + +Check out the +[source code](https://git.sr.ht/~ianmjones/snippetexpander/tree/trunk/item/cmd/snippetexpandergui/app.go#L38) +to see how the Wails app sends messages from the UI to the backend that are then +sent to the daemon, and subscribes to a D-Bus event to monitor changes to +snippets via another instance of the app or CLI and show them instantly in the +UI via a Wails event. diff --git a/docs/src/content/docs/community/showcase/surge.md b/docs/src/content/docs/community/showcase/surge.md new file mode 100644 index 000000000..3ab4444dd --- /dev/null +++ b/docs/src/content/docs/community/showcase/surge.md @@ -0,0 +1,9 @@ +--- +title: Surge +--- + +![Surge Screenshot](../../../../assets/showcase-images/surge.png) + +[Surge](https://getsurge.io/) is a p2p filesharing app designed to utilize +blockchain technologies to enable 100% anonymous file transfers. Surge is +end-to-end encrypted, decentralized and open source. diff --git a/docs/src/content/docs/community/showcase/tinyrdm.md b/docs/src/content/docs/community/showcase/tinyrdm.md new file mode 100644 index 000000000..f9551e046 --- /dev/null +++ b/docs/src/content/docs/community/showcase/tinyrdm.md @@ -0,0 +1,12 @@ +--- +title: Tiny RDM +--- + +![Tiny RDM Screenshot](../../../../assets/showcase-images/tiny-rdm1.webp) +![Tiny RDM Screenshot](../../../../assets/showcase-images/tiny-rdm2.webp) + +The [Tiny RDM](https://redis.tinycraft.cc/) application is an open-source, +modern lightweight Redis GUI. It has a beautful UI, intuitive Redis database +management, and compatible with Windows, Mac, and Linux. It provides visual +key-value data operations, supports various data decoding and viewing options, +built-in console for executing commands, slow log queries and more. diff --git a/docs/src/content/docs/community/showcase/wailsterm.md b/docs/src/content/docs/community/showcase/wailsterm.md new file mode 100644 index 000000000..0521aac70 --- /dev/null +++ b/docs/src/content/docs/community/showcase/wailsterm.md @@ -0,0 +1,8 @@ +--- +title: WailsTerm +--- + +![WailsTerm Screenshot](../../../../assets/showcase-images/wailsterm.webp) + +[WailsTerm](https://github.com/rlshukhov/wailsterm) is a simple translucent +terminal app powered by Wails and Xterm.js. diff --git a/docs/src/content/docs/community/showcase/wally.md b/docs/src/content/docs/community/showcase/wally.md new file mode 100644 index 000000000..3f4ddbfa2 --- /dev/null +++ b/docs/src/content/docs/community/showcase/wally.md @@ -0,0 +1,10 @@ +--- +title: Wally +--- + +![Wally Screenshot](../../../../assets/showcase-images/wally.webp) + +[Wally](https://ergodox-ez.com/pages/wally) is the official firmware flasher for +[Ergodox](https://ergodox-ez.com/) keyboards. It looks great and is a fantastic +example of what you can achieve with Wails: the ability to combine the power of +Go and the rich graphical tools of the web development world. diff --git a/docs/src/content/docs/community/showcase/warmine.md b/docs/src/content/docs/community/showcase/warmine.md new file mode 100644 index 000000000..19de97f2a --- /dev/null +++ b/docs/src/content/docs/community/showcase/warmine.md @@ -0,0 +1,17 @@ +--- +title: Minecraft launcher for WarMine +--- + +![WarMine Screenshot](../../../../assets/showcase-images/warmine1.png) +![WarMine Screenshot](../../../../assets/showcase-images/warmine2.png) + +[Minecraft launcher for WarMine](https://warmine.ru/) is a Wails application, +that allows you to easily join modded game servers and manage your game +accounts. + +The Launcher downloads the game files, checks their integrity and launches the +game with a wide range of customization options for the launch arguments from +the backend. + +Frontend is written in Svelte, whole launcher fits in 9MB and supports Windows +7-11. diff --git a/docs/src/content/docs/community/showcase/wombat.md b/docs/src/content/docs/community/showcase/wombat.md new file mode 100644 index 000000000..d61bdd6c9 --- /dev/null +++ b/docs/src/content/docs/community/showcase/wombat.md @@ -0,0 +1,7 @@ +--- +title: Wombat +--- + +![Wombat Screenshot](../../../../assets/showcase-images/wombat.webp) + +[Wombat](https://github.com/rogchap/wombat) is a cross platform gRPC client. diff --git a/docs/src/content/docs/community/showcase/ytd.md b/docs/src/content/docs/community/showcase/ytd.md new file mode 100644 index 000000000..bbcd6979f --- /dev/null +++ b/docs/src/content/docs/community/showcase/ytd.md @@ -0,0 +1,10 @@ +--- +title: Ytd +--- + +![Ytd Screenshot](../../../../assets/showcase-images/ytd.webp) + +[Ytd](https://github.com/marcio199226/ytd/tree/v2-wails) is an app for +downloading tracks from youtube, creating offline playlists and share them with +your friends, your friends will be able to playback your playlists or download +them for offline listening, has an built-in player. diff --git a/docs/src/content/docs/community/templates.md b/docs/src/content/docs/community/templates.md new file mode 100644 index 000000000..855b70c23 --- /dev/null +++ b/docs/src/content/docs/community/templates.md @@ -0,0 +1,132 @@ +--- +title: Templates +--- + +:::caution + +This page might be outdated for Wails v3. + +::: + + + +This page serves as a list for community supported templates. To build your own +template, please see the [Templates](https://wails.io/docs/guides/templates) +guide. + +:::tip[How to Submit a Template] + +You can click `Edit this page` at the bottom to include your templates. + +::: + +To use these templates, run +`wails init -n "Your Project Name" -t [the link below[@version]]` + +If there is no version suffix, the main branch code template is used by default. +If there is a version suffix, the code template corresponding to the tag of this +version is used. + +Example: +`wails init -n "Your Project Name" -t https://github.com/misitebao/wails-template-vue` + +:::danger[Attention] + +**The Wails project does not maintain, is not responsible nor liable for 3rd +party templates!** + +If you are unsure about a template, inspect `package.json` and `wails.json` for +what scripts are run and what packages are installed. + +::: + +## Vue + +- [wails-template-vue](https://github.com/misitebao/wails-template-vue) - Wails + template based on Vue ecology (Integrated TypeScript, Dark theme, + Internationalization, Single page routing, TailwindCSS) +- [wails-template-quasar-js](https://github.com/sgosiaco/wails-template-quasar-js) - + A template using JavaScript + Quasar V2 (Vue 3, Vite, Sass, Pinia, ESLint, + Prettier) +- [wails-template-quasar-ts](https://github.com/sgosiaco/wails-template-quasar-ts) - + A template using TypeScript + Quasar V2 (Vue 3, Vite, Sass, Pinia, ESLint, + Prettier, Composition API with <script setup>) +- [wails-template-naive](https://github.com/tk103331/wails-template-naive) - + Wails template based on Naive UI (A Vue 3 Component Library) +- [wails-template-nuxt](https://github.com/gornius/wails-template-nuxt) - Wails + template using clean Nuxt3 and TypeScript with auto-imports for wails js + runtime +- [Wails-Tool-Template](https://github.com/xisuo67/Wails-Tool-Template) - Wails + template using Vue+TypeScript+Vite+Element-plus(仿网易云) + +## Angular + +- [wails-template-angular](https://github.com/mateothegreat/wails-template-angular) - + Angular 15+ action packed & ready to roll to production. +- [wails-angular-template](https://github.com/TAINCER/wails-angular-template) - + Angular with TypeScript, Sass, Hot-Reload, Code-Splitting and i18n + +## React + +- [wails-react-template](https://github.com/AlienRecall/wails-react-template) - + A template using reactjs +- [wails-react-template](https://github.com/flin7/wails-react-template) - A + minimal template for React that supports live development +- [wails-template-nextjs](https://github.com/LGiki/wails-template-nextjs) - A + template using Next.js and TypeScript +- [wails-template-nextjs-app-router](https://github.com/thisisvk-in/wails-template-nextjs-app-router) - + A template using Next.js and TypeScript with App router +- [wails-template-nextjs-app-router-src](https://github.com/edai-git/wails-template-nextjs-app-router) - + A template using Next.js and TypeScript with App router src + example +- [wails-vite-react-ts-tailwind-template](https://github.com/hotafrika/wails-vite-react-ts-tailwind-template) - + A template for React + TypeScript + Vite + TailwindCSS +- [wails-vite-react-ts-tailwind-shadcnui-template](https://github.com/Mahcks/wails-vite-react-tailwind-shadcnui-ts) - + A template with Vite, React, TypeScript, TailwindCSS, and shadcn/ui + +## Svelte + +- [wails-svelte-template](https://github.com/raitonoberu/wails-svelte-template) - + A template using Svelte +- [wails-vite-svelte-template](https://github.com/BillBuilt/wails-vite-svelte-template) - + A template using Svelte and Vite +- [wails-vite-svelte-tailwind-template](https://github.com/BillBuilt/wails-vite-svelte-tailwind-template) - + A template using Svelte and Vite with TailwindCSS v3 +- [wails-svelte-tailwind-vite-template](https://github.com/PylotLight/wails-vite-svelte-tailwind-template/tree/master) - + An updated template using Svelte v4.2.0 and Vite with TailwindCSS v3.3.3 +- [wails-sveltekit-template](https://github.com/h8gi/wails-sveltekit-template) - + A template using SvelteKit +- [wails-template-shadcn-svelte](https://github.com/xijaja/wails-template-shadcn-svelte) - + A template using Sveltekit and Shadcn-Svelte + +## Solid + +- [wails-template-vite-solid-ts](https://github.com/xijaja/wails-template-solid-ts) - + A template using Solid + Ts + Vite +- [wails-template-vite-solid-js](https://github.com/xijaja/wails-template-solid-js) - + A template using Solid + Js + Vite + +## Elm + +- [wails-elm-template](https://github.com/benjamin-thomas/wails-elm-template) - + Develop your GUI app with functional programming and a **snappy** hot-reload + setup :tada: :rocket: +- [wails-template-elm-tailwind](https://github.com/rnice01/wails-template-elm-tailwind) - + Combine the powers :muscle: of Elm + Tailwind CSS + Wails! Hot reloading + supported. + +## HTMX + +- [wails-htmx-templ-chi-tailwind](https://github.com/PylotLight/wails-hmtx-templ-template) - + Use a unique combination of pure htmx for interactivity plus templ for + creating components and forms + +## Pure JavaScript (Vanilla) + +- [wails-pure-js-template](https://github.com/KiddoV/wails-pure-js-template) - A + template with nothing but just basic JavaScript, HTML, and CSS + +## Lit (web components) + +- [wails-lit-shoelace-esbuild-template](https://github.com/Braincompiler/wails-lit-shoelace-esbuild-template) - + Wails template providing frontend with lit, Shoelace component library + + pre-configured prettier and typescript. diff --git a/docs/src/content/docs/concepts/architecture.mdx b/docs/src/content/docs/concepts/architecture.mdx new file mode 100644 index 000000000..d72affe3b --- /dev/null +++ b/docs/src/content/docs/concepts/architecture.mdx @@ -0,0 +1,655 @@ +--- +title: How Wails Works +description: Understanding the Wails architecture and how it achieves native performance +sidebar: + order: 1 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +Wails is a framework for building desktop applications using **Go for the backend** and **web technologies for the frontend**. But unlike Electron, Wails doesn't bundle a browser—it uses the **operating system's native WebView**. + +```d2 +direction: right + +User: "User" { + shape: person + style.fill: "#3B82F6" +} + +Application: "Your Wails Application" { + Frontend: "Frontend\n(HTML/CSS/JS)" { + shape: rectangle + style.fill: "#8B5CF6" + } + + Runtime: "Wails Runtime" { + Bridge: "Message Bridge" { + shape: diamond + style.fill: "#10B981" + } + + Bindings: "Type-Safe Bindings" { + shape: rectangle + style.fill: "#10B981" + } + } + + Backend: "Go Backend" { + Services: "Your Services" { + shape: rectangle + style.fill: "#00ADD8" + } + + NativeAPIs: "OS APIs" { + shape: rectangle + style.fill: "#00ADD8" + } + } +} + +OS: "Operating System" { + WebView: "Native WebView\n(WebKit/WebView2/WebKitGTK)" { + shape: rectangle + style.fill: "#6B7280" + } + + SystemAPIs: "System APIs\n(Windows/macOS/Linux)" { + shape: rectangle + style.fill: "#6B7280" + } +} + +User -> Application.Frontend: "Interacts with UI" +Application.Frontend <-> Application.Runtime.Bridge: "JSON messages" +Application.Runtime.Bridge <-> Application.Backend.Services: "Direct function calls" +Application.Runtime.Bindings -> Application.Frontend: "TypeScript definitions" +Application.Frontend -> OS.WebView: "Renders in" +Application.Backend.NativeAPIs -> OS.SystemAPIs: "Native calls" +``` + +**Key differences from Electron:** + +| Aspect | Wails | Electron | +|--------|-------|----------| +| **Browser** | OS-provided WebView | Bundled Chromium (~100MB) | +| **Backend** | Go (compiled) | Node.js (interpreted) | +| **Communication** | In-memory bridge | IPC (inter-process) | +| **Bundle Size** | ~15MB | ~150MB | +| **Memory** | ~10MB | ~100MB+ | +| **Startup** | <0.5s | 2-3s | + +## Core Components + +### 1. Native WebView + +Wails uses the operating system's built-in web rendering engine: + + + + **WebView2** (Microsoft Edge WebView2) + - Based on Chromium (same as Edge browser) + - Pre-installed on Windows 10/11 + - Automatic updates via Windows Update + - Full modern web standards support + + + + **WebKit** (Safari's rendering engine) + - Built into macOS + - Same engine as Safari browser + - Excellent performance and battery life + - Full modern web standards support + + + + **WebKitGTK** (GTK port of WebKit) + - Installed via package manager + - Same engine as GNOME Web (Epiphany) + - Good standards support + - Lightweight and performant + + + +**Why this matters:** +- **No bundled browser** → Smaller app size +- **OS-native** → Better integration and performance +- **Auto-updates** → Security patches from OS updates +- **Familiar rendering** → Same as system browser + +### 2. The Wails Bridge + +The bridge is the heart of Wails—it enables **direct communication** between Go and JavaScript. + +```d2 +direction: down + +Frontend: "Frontend (JavaScript)" { + shape: rectangle + style.fill: "#8B5CF6" +} + +Bridge: "Wails Bridge" { + Encoder: "JSON Encoder" { + shape: rectangle + } + + Router: "Method Router" { + shape: diamond + style.fill: "#10B981" + } + + Decoder: "JSON Decoder" { + shape: rectangle + } +} + +Backend: "Backend (Go)" { + Services: "Registered Services" { + shape: rectangle + style.fill: "#00ADD8" + } +} + +Frontend -> Bridge.Encoder: "1. Call Go method\nGreet('Alice')" +Bridge.Encoder -> Bridge.Router: "2. Encode to JSON\n{method: 'Greet', args: ['Alice']}" +Bridge.Router -> Backend.Services: "3. Route to service\nGreetService.Greet('Alice')" +Backend.Services -> Bridge.Decoder: "4. Return result\n'Hello, Alice!'" +Bridge.Decoder -> Frontend: "5. Decode to JS\nPromise resolves" +``` + +**How it works:** + +1. **Frontend calls a Go method** (via auto-generated binding) +2. **Bridge encodes the call** to JSON (method name + arguments) +3. **Router finds the Go method** in registered services +4. **Go method executes** and returns a value +5. **Bridge decodes the result** and sends back to frontend +6. **Promise resolves** in JavaScript with the result + +**Performance characteristics:** +- **In-memory**: No network overhead, no HTTP +- **Zero-copy** where possible (for large data) +- **Async by default**: Non-blocking on both sides +- **Type-safe**: TypeScript definitions auto-generated + +### 3. Service System + +Services are the recommended way to expose Go functionality to the frontend. + +```go +// Define a service (just a regular Go struct) +type GreetService struct { + prefix string +} + +// Methods with exported names are automatically available +func (g *GreetService) Greet(name string) string { + return g.prefix + name + "!" +} + +func (g *GreetService) GetTime() time.Time { + return time.Now() +} + +// Register the service +app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&GreetService{prefix: "Hello, "}), + }, +}) +``` + +**Service discovery:** +- Wails **scans your struct** at startup +- **Exported methods** become callable from frontend +- **Type information** is extracted for TypeScript bindings +- **Error handling** is automatic (Go errors → JS exceptions) + +**Generated TypeScript binding:** +```typescript +// Auto-generated in frontend/bindings/GreetService.ts +export function Greet(name: string): Promise +export function GetTime(): Promise +``` + +**Why services?** +- **Type-safe**: Full TypeScript support +- **Auto-discovery**: No manual registration of methods +- **Organised**: Group related functionality +- **Testable**: Services are just Go structs + +[Learn more about services →](/features/bindings/services) + +### 4. Event System + +Events enable **pub/sub communication** between components. + +```d2 +direction: right + +GoService: "Go Service" { + shape: rectangle + style.fill: "#00ADD8" +} + +EventBus: "Event Bus" { + shape: cylinder + style.fill: "#10B981" +} + +Frontend1: "Window 1" { + shape: rectangle + style.fill: "#8B5CF6" +} + +Frontend2: "Window 2" { + shape: rectangle + style.fill: "#8B5CF6" +} + +GoService -> EventBus: "Emit('data-updated', data)" +EventBus -> Frontend1: "Notify subscribers" +EventBus -> Frontend2: "Notify subscribers" +Frontend1 -> EventBus: "On('data-updated', handler)" +Frontend2 -> EventBus: "On('data-updated', handler)" +``` + +**Use cases:** +- **Window communication**: One window notifies others +- **Background tasks**: Go service notifies UI of progress +- **State synchronisation**: Keep multiple windows in sync +- **Loose coupling**: Components don't need direct references + +**Example:** +```go +// Go: Emit an event +app.Event.Emit("user-logged-in", user) +``` + +```javascript +// JavaScript: Listen for event +import { On } from '@wailsio/runtime' + +On('user-logged-in', (user) => { + console.log('User logged in:', user) +}) +``` + +[Learn more about events →](/features/events/system) + +## Application Lifecycle + +Understanding the lifecycle helps you know when to initialise resources and clean up. + +```d2 +direction: down + +Start: "Application Start" { + shape: oval + style.fill: "#10B981" +} + +Init: "Initialisation" { + Create: "Create Application" { + shape: rectangle + } + + Register: "Register Services" { + shape: rectangle + } + + Setup: "Setup Windows/Menus" { + shape: rectangle + } +} + +Run: "Event Loop" { + Events: "Process Events" { + shape: rectangle + } + + Messages: "Handle Messages" { + shape: rectangle + } + + Render: "Update UI" { + shape: rectangle + } +} + +Shutdown: "Shutdown" { + Cleanup: "Cleanup Resources" { + shape: rectangle + } + + Save: "Save State" { + shape: rectangle + } +} + +End: "Application End" { + shape: oval + style.fill: "#EF4444" +} + +Start -> Init.Create +Init.Create -> Init.Register +Init.Register -> Init.Setup +Init.Setup -> Run.Events +Run.Events -> Run.Messages +Run.Messages -> Run.Render +Run.Render -> Run.Events: "Loop" +Run.Events -> Shutdown.Cleanup: "Quit signal" +Shutdown.Cleanup -> Shutdown.Save +Shutdown.Save -> End +``` + +**Lifecycle hooks:** + +```go +app := application.New(application.Options{ + Name: "My App", + + // Called before windows are created + OnStartup: func(ctx context.Context) { + // Initialise database, load config, etc. + }, + + // Called when app is about to quit + OnShutdown: func() { + // Save state, close connections, etc. + }, +}) +``` + +[Learn more about lifecycle →](/concepts/lifecycle) + +## Build Process + +Understanding how Wails builds your application: + +```d2 +direction: down + +Source: "Source Code" { + Go: "Go Code\n(main.go, services)" { + shape: rectangle + style.fill: "#00ADD8" + } + + Frontend: "Frontend Code\n(HTML/CSS/JS)" { + shape: rectangle + style.fill: "#8B5CF6" + } +} + +Build: "Build Process" { + AnalyseGo: "Analyse Go Code" { + shape: rectangle + } + + GenerateBindings: "Generate Bindings" { + shape: rectangle + } + + BuildFrontend: "Build Frontend" { + shape: rectangle + } + + CompileGo: "Compile Go" { + shape: rectangle + } + + Embed: "Embed Assets" { + shape: rectangle + } +} + +Output: "Output" { + Binary: "Native Binary\n(myapp.exe/.app)" { + shape: rectangle + style.fill: "#10B981" + } +} + +Source.Go -> Build.AnalyseGo +Build.AnalyseGo -> Build.GenerateBindings: "Extract types" +Build.GenerateBindings -> Source.Frontend: "TypeScript bindings" +Source.Frontend -> Build.BuildFrontend: "Compile (Vite/webpack)" +Build.BuildFrontend -> Build.Embed: "Bundled assets" +Source.Go -> Build.CompileGo +Build.CompileGo -> Build.Embed +Build.Embed -> Output.Binary +``` + +**Build steps:** + +1. **Analyse Go code** + - Scan services for exported methods + - Extract parameter and return types + - Generate method signatures + +2. **Generate TypeScript bindings** + - Create `.ts` files for each service + - Include full type definitions + - Add JSDoc comments + +3. **Build frontend** + - Run your bundler (Vite, webpack, etc.) + - Minify and optimise + - Output to `frontend/dist/` + +4. **Compile Go** + - Compile with optimisations (`-ldflags="-s -w"`) + - Include build metadata + - Platform-specific compilation + +5. **Embed assets** + - Embed frontend files into Go binary + - Compress assets + - Create single executable + +**Result:** A single native executable with everything embedded. + +[Learn more about building →](/guides/build/building) + +## Development vs Production + +Wails behaves differently in development and production: + + + + **Characteristics:** + - **Hot reload**: Frontend changes reload instantly + - **Source maps**: Debug with original source + - **DevTools**: Browser DevTools available + - **Logging**: Verbose logging enabled + - **External frontend**: Served from dev server (Vite) + + **How it works:** + ```d2 + direction: right + + WailsApp: "Wails App" { + shape: rectangle + style.fill: "#00ADD8" + } + + DevServer: "Vite Dev Server\n(localhost:5173)" { + shape: rectangle + style.fill: "#8B5CF6" + } + + WebView: "WebView" { + shape: rectangle + style.fill: "#6B7280" + } + + WailsApp -> DevServer: "Proxy requests" + DevServer -> WebView: "Serve with HMR" + WebView -> WailsApp: "Call Go methods" + ``` + + **Benefits:** + - Instant feedback on changes + - Full debugging capabilities + - Faster iteration + + + + **Characteristics:** + - **Embedded assets**: Frontend built into binary + - **Optimised**: Minified, compressed + - **No DevTools**: Disabled by default + - **Minimal logging**: Errors only + - **Single file**: Everything in one executable + + **How it works:** + ```d2 + direction: right + + Binary: "Single Binary\n(myapp.exe)" { + GoCode: "Compiled Go" { + shape: rectangle + style.fill: "#00ADD8" + } + + Assets: "Embedded Assets\n(HTML/CSS/JS)" { + shape: rectangle + style.fill: "#8B5CF6" + } + } + + WebView: "WebView" { + shape: rectangle + style.fill: "#6B7280" + } + + Binary.Assets -> WebView: "Serve from memory" + WebView -> Binary.GoCode: "Call Go methods" + ``` + + **Benefits:** + - Single file distribution + - Smaller size (minified) + - Better performance + - No external dependencies + + + +## Memory Model + +Understanding memory usage helps you build efficient applications. + +{/* VISUAL PLACEHOLDER: Memory Diagram +Description: Memory layout diagram showing: +1. Go Heap (services, application state) +2. WebView Memory (DOM, JavaScript heap) +3. Shared Memory (bridge communication) +4. Arrows showing data flow between regions +5. Annotations for zero-copy optimisations +Style: Technical diagram with memory regions as boxes, clear labels, size indicators +*/} + +**Memory regions:** + +1. **Go Heap** + - Your services and application state + - Managed by Go garbage collector + - Typically 5-10MB for simple apps + +2. **WebView Memory** + - DOM, JavaScript heap, CSS + - Managed by WebView's engine + - Typically 10-20MB for simple apps + +3. **Bridge Memory** + - Message buffers for communication + - Minimal overhead (\<1MB) + - Zero-copy for large data where possible + +**Optimisation tips:** +- **Avoid large data transfers**: Pass IDs, fetch details on demand +- **Use events for updates**: Don't poll from frontend +- **Stream large files**: Don't load entirely into memory +- **Clean up listeners**: Remove event listeners when done + +[Learn more about performance →](/guides/advanced/performance) + +## Security Model + +Wails provides a secure-by-default architecture: + +```d2 +direction: down + +Frontend: "Frontend (Untrusted)" { + shape: rectangle + style.fill: "#EF4444" +} + +Bridge: "Wails Bridge (Validation)" { + shape: diamond + style.fill: "#F59E0B" +} + +Backend: "Backend (Trusted)" { + shape: rectangle + style.fill: "#10B981" +} + +Frontend -> Bridge: "Call method" +Bridge -> Bridge: "Validate:\n- Method exists?\n- Types correct?\n- Access allowed?" +Bridge -> Backend: "Execute if valid" +Backend -> Bridge: "Return result" +Bridge -> Frontend: "Send response" +``` + +**Security features:** + +1. **Method whitelisting** + - Only exported methods are callable + - Private methods are inaccessible + - Explicit service registration required + +2. **Type validation** + - Arguments checked against Go types + - Invalid types rejected + - Prevents injection attacks + +3. **No eval()** + - Frontend can't execute arbitrary Go code + - Only predefined methods callable + - No dynamic code execution + +4. **Context isolation** + - Each window has its own context + - Services can check caller context + - Permissions per window possible + +**Best practices:** +- **Validate user input** in Go (don't trust frontend) +- **Use context** for authentication/authorisation +- **Sanitise file paths** before file operations +- **Rate limit** expensive operations + +[Learn more about security →](/guides/advanced/security) + +## Next Steps + +**Application Lifecycle** - Understand startup, shutdown, and lifecycle hooks +[Learn More →](/concepts/lifecycle) + +**Go-Frontend Bridge** - Deep dive into how the bridge works +[Learn More →](/concepts/bridge) + +**Build System** - Understand how Wails builds your application +[Learn More →](/concepts/build-system) + +**Start Building** - Apply what you've learned in a tutorial +[Tutorials →](/tutorials/03-notes-vanilla) + +--- + +**Questions about architecture?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [API reference](/reference/overview). diff --git a/docs/src/content/docs/concepts/bridge.mdx b/docs/src/content/docs/concepts/bridge.mdx new file mode 100644 index 000000000..7593d896a --- /dev/null +++ b/docs/src/content/docs/concepts/bridge.mdx @@ -0,0 +1,700 @@ +--- +title: Go-Frontend Bridge +description: Deep dive into how Wails enables direct communication between Go and JavaScript +sidebar: + order: 3 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +## Direct Go-JavaScript Communication + +Wails provides a **direct, in-memory bridge** between Go and JavaScript, enabling seamless communication without HTTP overhead, process boundaries, or serialisation bottlenecks. + +## The Big Picture + +```d2 +direction: right + +Frontend: "Frontend (JavaScript)" { + UI: "React/Vue/Vanilla" { + shape: rectangle + style.fill: "#8B5CF6" + } + + Bindings: "Auto-Generated Bindings" { + shape: rectangle + style.fill: "#A78BFA" + } +} + +Bridge: "Wails Bridge" { + Encoder: "JSON Encoder" { + shape: rectangle + style.fill: "#10B981" + } + + Router: "Method Router" { + shape: diamond + style.fill: "#10B981" + } + + Decoder: "JSON Decoder" { + shape: rectangle + style.fill: "#10B981" + } + + TypeGen: "Type Generator" { + shape: rectangle + style.fill: "#10B981" + } +} + +Backend: "Backend (Go)" { + Services: "Your Services" { + shape: rectangle + style.fill: "#00ADD8" + } + + Registry: "Service Registry" { + shape: rectangle + style.fill: "#00ADD8" + } +} + +Frontend.UI -> Frontend.Bindings: "import { Method }" +Frontend.Bindings -> Bridge.Encoder: "Call Method('arg')" +Bridge.Encoder -> Bridge.Router: "Encode to JSON" +Bridge.Router -> Backend.Registry: "Find service" +Backend.Registry -> Backend.Services: "Invoke method" +Backend.Services -> Bridge.Decoder: "Return result" +Bridge.Decoder -> Frontend.Bindings: "Decode to JS" +Frontend.Bindings -> Frontend.UI: "Promise resolves" +Bridge.TypeGen -> Frontend.Bindings: "Generate types" +``` + +**Key insight:** No HTTP, no IPC, no process boundaries. Just **direct function calls** with **type safety**. + +## How It Works: Step by Step + +### 1. Service Registration (Startup) + +When your application starts, Wails scans your services: + +```go +type GreetService struct { + prefix string +} + +func (g *GreetService) Greet(name string) string { + return g.prefix + name + "!" +} + +func (g *GreetService) Add(a, b int) int { + return a + b +} + +// Register service +app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&GreetService{prefix: "Hello, "}), + }, +}) +``` + +**What Wails does:** +1. **Scans the struct** for exported methods +2. **Extracts type information** (parameters, return types) +3. **Builds a registry** mapping method names to functions +4. **Generates TypeScript bindings** with full type definitions + +### 2. Binding Generation (Build Time) + +Wails generates TypeScript bindings automatically: + +```typescript +// Auto-generated: frontend/bindings/GreetService.ts +export function Greet(name: string): Promise +export function Add(a: number, b: number): Promise +``` + +**Type mapping:** + +| Go Type | TypeScript Type | +|---------|-----------------| +| `string` | `string` | +| `int`, `int32`, `int64` | `number` | +| `float32`, `float64` | `number` | +| `bool` | `boolean` | +| `[]T` | `T[]` | +| `map[string]T` | `Record` | +| `struct` | `interface` | +| `time.Time` | `Date` | +| `error` | Exception (thrown) | + +### 3. Frontend Call (Runtime) + +Developer calls the Go method from JavaScript: + +```javascript +import { Greet, Add } from './bindings/GreetService' + +// Call Go from JavaScript +const greeting = await Greet("World") +console.log(greeting) // "Hello, World!" + +const sum = await Add(5, 3) +console.log(sum) // 8 +``` + +**What happens:** +1. **Binding function called** - `Greet("World")` +2. **Message created** - `{ service: "GreetService", method: "Greet", args: ["World"] }` +3. **Sent to bridge** - Via WebView's JavaScript bridge +4. **Promise returned** - Awaits response + +### 4. Bridge Processing (Runtime) + +The bridge receives the message and processes it: + +```d2 +direction: down + +Receive: "Receive Message" { + shape: rectangle + style.fill: "#10B981" +} + +Parse: "Parse JSON" { + shape: rectangle +} + +Validate: "Validate" { + Check: "Service exists?" { + shape: diamond + } + + CheckMethod: "Method exists?" { + shape: diamond + } + + CheckTypes: "Types correct?" { + shape: diamond + } +} + +Invoke: "Invoke Go Method" { + shape: rectangle + style.fill: "#00ADD8" +} + +Encode: "Encode Result" { + shape: rectangle +} + +Send: "Send Response" { + shape: rectangle + style.fill: "#10B981" +} + +Error: "Send Error" { + shape: rectangle + style.fill: "#EF4444" +} + +Receive -> Parse +Parse -> Validate.Check +Validate.Check -> Validate.CheckMethod: "Yes" +Validate.Check -> Error: "No" +Validate.CheckMethod -> Validate.CheckTypes: "Yes" +Validate.CheckMethod -> Error: "No" +Validate.CheckTypes -> Invoke: "Yes" +Validate.CheckTypes -> Error: "No" +Invoke -> Encode: "Success" +Invoke -> Error: "Error" +Encode -> Send +``` + +**Security:** Only registered services and exported methods are callable. + +### 5. Go Execution (Runtime) + +The Go method executes: + +```go +func (g *GreetService) Greet(name string) string { + // This runs in Go + return g.prefix + name + "!" +} +``` + +**Execution context:** +- Runs in a **goroutine** (non-blocking) +- Has access to **all Go features** (file system, network, databases) +- Can call **other Go code** freely +- Returns result or error + +### 6. Response (Runtime) + +Result is sent back to JavaScript: + +```javascript +// Promise resolves with result +const greeting = await Greet("World") +// greeting = "Hello, World!" +``` + +**Error handling:** + +```go +func (g *GreetService) Divide(a, b float64) (float64, error) { + if b == 0 { + return 0, errors.New("division by zero") + } + return a / b, nil +} +``` + +```javascript +try { + const result = await Divide(10, 0) +} catch (error) { + console.error("Go error:", error) // "division by zero" +} +``` + +## Performance Characteristics + +### Speed + +**Typical call overhead:** <1ms + +``` +Frontend Call → Bridge → Go Execution → Bridge → Frontend Response + ↓ ↓ ↓ ↓ ↓ + <0.1ms <0.1ms [varies] <0.1ms <0.1ms +``` + +**Compared to alternatives:** +- **HTTP/REST:** 5-50ms (network stack, serialisation) +- **IPC:** 1-10ms (process boundaries, marshalling) +- **Wails Bridge:** <1ms (in-memory, direct call) + +### Memory + +**Per-call overhead:** ~1KB (message buffer) + +**Zero-copy optimisation:** Large data (>1MB) uses shared memory where possible. + +### Concurrency + +**Calls are concurrent:** +- Each call runs in its own goroutine +- Multiple calls can execute simultaneously +- No blocking between calls + +```javascript +// These run concurrently +const [result1, result2, result3] = await Promise.all([ + SlowOperation1(), + SlowOperation2(), + SlowOperation3(), +]) +``` + +## Type System + +### Supported Types + +#### Primitives + +```go +// Go +func Example( + s string, + i int, + f float64, + b bool, +) (string, int, float64, bool) { + return s, i, f, b +} +``` + +```typescript +// TypeScript (auto-generated) +function Example( + s: string, + i: number, + f: number, + b: boolean, +): Promise<[string, number, number, boolean]> +``` + +#### Slices and Arrays + +```go +// Go +func Sum(numbers []int) int { + total := 0 + for _, n := range numbers { + total += n + } + return total +} +``` + +```typescript +// TypeScript +function Sum(numbers: number[]): Promise + +// Usage +const total = await Sum([1, 2, 3, 4, 5]) // 15 +``` + +#### Maps + +```go +// Go +func GetConfig() map[string]interface{} { + return map[string]interface{}{ + "theme": "dark", + "fontSize": 14, + "enabled": true, + } +} +``` + +```typescript +// TypeScript +function GetConfig(): Promise> + +// Usage +const config = await GetConfig() +console.log(config.theme) // "dark" +``` + +#### Structs + +```go +// Go +type User struct { + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` +} + +func GetUser(id int) (*User, error) { + return &User{ + ID: id, + Name: "Alice", + Email: "alice@example.com", + }, nil +} +``` + +```typescript +// TypeScript (auto-generated) +interface User { + id: number + name: string + email: string +} + +function GetUser(id: number): Promise + +// Usage +const user = await GetUser(1) +console.log(user.name) // "Alice" +``` + +**JSON tags:** Use `json:` tags to control field names in TypeScript. + +#### Time + +```go +// Go +func GetTimestamp() time.Time { + return time.Now() +} +``` + +```typescript +// TypeScript +function GetTimestamp(): Promise + +// Usage +const timestamp = await GetTimestamp() +console.log(timestamp.toISOString()) +``` + +#### Errors + +```go +// Go +func Validate(input string) error { + if input == "" { + return errors.New("input cannot be empty") + } + return nil +} +``` + +```typescript +// TypeScript +function Validate(input: string): Promise + +// Usage +try { + await Validate("") +} catch (error) { + console.error(error) // "input cannot be empty" +} +``` + +### Unsupported Types + +These types **cannot** be passed across the bridge: + +- **Channels** (`chan T`) +- **Functions** (`func()`) +- **Interfaces** (except `interface{}` / `any`) +- **Pointers** (except to structs) +- **Unexported fields** (lowercase) + +**Workaround:** Use IDs or handles: + +```go +// ❌ Can't pass file handle +func OpenFile(path string) (*os.File, error) { + return os.Open(path) +} + +// ✅ Return file ID instead +var files = make(map[string]*os.File) + +func OpenFile(path string) (string, error) { + file, err := os.Open(path) + if err != nil { + return "", err + } + id := generateID() + files[id] = file + return id, nil +} + +func ReadFile(id string) ([]byte, error) { + file := files[id] + return io.ReadAll(file) +} + +func CloseFile(id string) error { + file := files[id] + delete(files, id) + return file.Close() +} +``` + +## Advanced Patterns + +### Context Passing + +Services can access the call context: + +```go +type UserService struct{} + +func (s *UserService) GetCurrentUser(ctx context.Context) (*User, error) { + // Access window that made the call + window := application.ContextWindow(ctx) + + // Access application + app := application.ContextApplication(ctx) + + // Your logic + return getCurrentUser(), nil +} +``` + +**Context provides:** +- Window that made the call +- Application instance +- Request metadata + +### Streaming Data + +For large data, use events instead of return values: + +```go +func ProcessLargeFile(path string) error { + file, err := os.Open(path) + if err != nil { + return err + } + defer file.Close() + + scanner := bufio.NewScanner(file) + lineNum := 0 + + for scanner.Scan() { + lineNum++ + // Emit progress events + app.Event.Emit("file-progress", map[string]interface{}{ + "line": lineNum, + "text": scanner.Text(), + }) + } + + return scanner.Err() +} +``` + +```javascript +import { On } from '@wailsio/runtime' +import { ProcessLargeFile } from './bindings/FileService' + +// Listen for progress +On('file-progress', (data) => { + console.log(`Line ${data.line}: ${data.text}`) +}) + +// Start processing +await ProcessLargeFile('/path/to/large/file.txt') +``` + +### Cancellation + +Use context for cancellable operations: + +```go +func LongRunningTask(ctx context.Context) error { + for i := 0; i < 1000; i++ { + // Check if cancelled + select { + case <-ctx.Done(): + return ctx.Err() + default: + // Continue work + time.Sleep(100 * time.Millisecond) + } + } + return nil +} +``` + +**Note:** Context cancellation on frontend disconnect is automatic. + +### Batch Operations + +Reduce bridge overhead by batching: + +```go +// ❌ Inefficient: N bridge calls +for _, item := range items { + await ProcessItem(item) +} + +// ✅ Efficient: 1 bridge call +await ProcessItems(items) +``` + +```go +func ProcessItems(items []Item) ([]Result, error) { + results := make([]Result, len(items)) + for i, item := range items { + results[i] = processItem(item) + } + return results, nil +} +``` + +## Debugging the Bridge + +### Enable Debug Logging + +```go +app := application.New(application.Options{ + Name: "My App", + Logger: application.NewDefaultLogger(), + LogLevel: logger.DEBUG, +}) +``` + +**Output shows:** +- Method calls +- Parameters +- Return values +- Errors +- Timing information + +### Inspect Generated Bindings + +Check `frontend/bindings/` to see generated TypeScript: + +```typescript +// frontend/bindings/MyService.ts +export function MyMethod(arg: string): Promise { + return window.wails.Call('MyService.MyMethod', arg) +} +``` + +### Test Services Directly + +Test Go services without the frontend: + +```go +func TestGreetService(t *testing.T) { + service := &GreetService{prefix: "Hello, "} + result := service.Greet("Test") + if result != "Hello, Test!" { + t.Errorf("Expected 'Hello, Test!', got '%s'", result) + } +} +``` + +## Performance Tips + +### ✅ Do + +- **Batch operations** - Reduce bridge calls +- **Use events for streaming** - Don't return large arrays +- **Keep methods fast** - <100ms ideal +- **Use goroutines** - For long operations +- **Cache on Go side** - Avoid repeated calculations + +### ❌ Don't + +- **Don't make excessive calls** - Batch when possible +- **Don't return huge data** - Use pagination or streaming +- **Don't block** - Use goroutines for long operations +- **Don't pass complex types** - Keep it simple +- **Don't ignore errors** - Always handle them + +## Security + +The bridge is secure by default: + +1. **Whitelist only** - Only registered services callable +2. **Type validation** - Arguments checked against Go types +3. **No eval()** - Frontend can't execute arbitrary Go code +4. **No reflection abuse** - Only exported methods accessible + +**Best practices:** +- **Validate input** in Go (don't trust frontend) +- **Use context** for authentication/authorisation +- **Rate limit** expensive operations +- **Sanitise** file paths and user input + +## Next Steps + +**Build System** - Learn how Wails builds and bundles your application +[Learn More →](/concepts/build-system) + +**Services** - Deep dive into the service system +[Learn More →](/features/bindings/services) + +**Events** - Use events for pub/sub communication +[Learn More →](/features/events/system) + +--- + +**Questions about the bridge?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [binding examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/binding). diff --git a/docs/src/content/docs/concepts/build-system.mdx b/docs/src/content/docs/concepts/build-system.mdx new file mode 100644 index 000000000..f9de52bc5 --- /dev/null +++ b/docs/src/content/docs/concepts/build-system.mdx @@ -0,0 +1,700 @@ +--- +title: Build System +description: Understanding how Wails builds and packages your application +sidebar: + order: 4 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +## Unified Build System + +Wails provides a **unified build system** that compiles Go code, bundles frontend assets, embeds everything into a single executable, and handles platform-specific builds—all with one command. + +```bash +wails3 build +``` + +**Output:** Native executable with everything embedded. + +## Build Process Overview + +{/* +TODO: Fix D2 diagram generation or embed as image. +The previous D2 code block was causing MDX parsing errors in the build pipeline. +*/} + +**[Build Process Diagram Placeholder]** + + +## Build Phases + +### 1. Analysis Phase + +Wails scans your Go code to understand your services: + +```go +type GreetService struct { + prefix string +} + +func (g *GreetService) Greet(name string) string { + return g.prefix + name + "!" +} +``` + +**What Wails extracts:** +- Service name: `GreetService` +- Method name: `Greet` +- Parameter types: `string` +- Return types: `string` + +**Used for:** Generating TypeScript bindings + +### 2. Generation Phase + +#### TypeScript Bindings + +Wails generates type-safe bindings: + +```typescript +// Auto-generated: frontend/bindings/GreetService.ts +export function Greet(name: string): Promise { + return window.wails.Call('GreetService.Greet', name) +} +``` + +**Benefits:** +- Full type safety +- IDE autocomplete +- Compile-time errors +- JSDoc comments + +#### Frontend Build + +Your frontend bundler runs (Vite, webpack, etc.): + +```bash +# Vite example +vite build --outDir dist +``` + +**What happens:** +- JavaScript/TypeScript compiled +- CSS processed and minified +- Assets optimised +- Source maps generated (dev only) +- Output to `frontend/dist/` + +### 3. Compilation Phase + +#### Go Compilation + +Go code is compiled with optimisations: + +```bash +go build -ldflags="-s -w" -o myapp.exe +``` + +**Flags:** +- `-s`: Strip symbol table +- `-w`: Strip DWARF debugging info +- Result: Smaller binary (~30% reduction) + +**Platform-specific:** +- Windows: `.exe` with icon embedded +- macOS: `.app` bundle structure +- Linux: ELF binary + +#### Asset Embedding + +Frontend assets are embedded into the Go binary: + +```go +//go:embed frontend/dist +var assets embed.FS +``` + +**Result:** Single executable with everything inside. + +### 4. Output + +**Single native binary:** +- Windows: `myapp.exe` (~15MB) +- macOS: `myapp.app` (~15MB) +- Linux: `myapp` (~15MB) + +**No dependencies** (except system WebView). + +## Development vs Production + + + + **Optimised for speed:** + + ```bash + wails3 dev + ``` + + **What happens:** + 1. Starts frontend dev server (Vite on port 5173) + 2. Compiles Go without optimisations + 3. Launches app pointing to dev server + 4. Enables hot reload + 5. Includes source maps + + **Characteristics:** + - **Fast rebuilds** (<1s for frontend changes) + - **No asset embedding** (served from dev server) + - **Debug symbols** included + - **Source maps** enabled + - **Verbose logging** + + **File size:** Larger (~50MB with debug symbols) + + + + **Optimised for size and performance:** + + ```bash + wails3 build + ``` + + **What happens:** + 1. Builds frontend for production (minified) + 2. Compiles Go with optimisations + 3. Strips debug symbols + 4. Embeds assets + 5. Creates single binary + + **Characteristics:** + - **Optimised code** (minified, tree-shaken) + - **Assets embedded** (no external files) + - **Debug symbols stripped** + - **No source maps** + - **Minimal logging** + + **File size:** Smaller (~15MB) + + + +## Build Commands + +### Basic Build + +```bash +wails3 build +``` + +**Output:** `build/bin/myapp[.exe]` + +### Build for Specific Platform + +```bash +# Build for Windows (from any OS) +wails3 build -platform windows/amd64 + +# Build for macOS +wails3 build -platform darwin/amd64 +wails3 build -platform darwin/arm64 + +# Build for Linux +wails3 build -platform linux/amd64 +``` + +**Cross-compilation:** Build for any platform from any platform. + +### Build with Options + +```bash +# Custom output directory +wails3 build -o ./dist/myapp + +# Skip frontend build (use existing) +wails3 build -skipbindings + +# Clean build (remove cache) +wails3 build -clean + +# Verbose output +wails3 build -v +``` + +### Build Modes + +```bash +# Debug build (includes symbols) +wails3 build -debug + +# Production build (default, optimised) +wails3 build + +# Development build (fast, unoptimised) +wails3 build -devbuild +``` + +## Build Configuration + +### Taskfile.yml + +Wails uses [Taskfile](https://taskfile.dev/) for build configuration: + +```yaml +# Taskfile.yml +version: '3' + +tasks: + build: + desc: Build the application + cmds: + - wails3 build + + build:windows: + desc: Build for Windows + cmds: + - wails3 build -platform windows/amd64 + + build:macos: + desc: Build for macOS (Universal) + cmds: + - wails3 build -platform darwin/amd64 + - wails3 build -platform darwin/arm64 + - lipo -create -output build/bin/myapp.app build/bin/myapp-amd64.app build/bin/myapp-arm64.app + + build:linux: + desc: Build for Linux + cmds: + - wails3 build -platform linux/amd64 +``` + +**Run tasks:** + +```bash +task build:windows +task build:macos +task build:linux +``` + +### Build Options File + +Create `build/build.json` for persistent configuration: + +```json +{ + "name": "My Application", + "version": "1.0.0", + "author": "Your Name", + "description": "Application description", + "icon": "build/appicon.png", + "outputFilename": "myapp", + "platforms": ["windows/amd64", "darwin/amd64", "linux/amd64"], + "frontend": { + "dir": "./frontend", + "install": "npm install", + "build": "npm run build", + "dev": "npm run dev" + }, + "go": { + "ldflags": "-s -w -X main.version={{.Version}}" + } +} +``` + +## Asset Embedding + +### How It Works + +Wails uses Go's `embed` package: + +```go +package main + +import ( + "embed" + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed frontend/dist +var assets embed.FS + +func main() { + app := application.New(application.Options{ + Name: "My App", + Assets: application.AssetOptions{ + Handler: application.AssetFileServerFS(assets), + }, + }) + + app.Window.New() + app.Run() +} +``` + +**At build time:** +1. Frontend built to `frontend/dist/` +2. `//go:embed` directive includes files +3. Files compiled into binary +4. Binary contains everything + +**At runtime:** +1. App starts +2. Assets served from memory +3. No disk I/O for assets +4. Fast loading + +### Custom Assets + +Embed additional files: + +```go +//go:embed frontend/dist +var frontendAssets embed.FS + +//go:embed data/*.json +var dataAssets embed.FS + +//go:embed templates/*.html +var templateAssets embed.FS +``` + +## Build Optimisations + +### Frontend Optimisations + +**Vite (default):** + +```javascript +// vite.config.js +export default { + build: { + minify: 'terser', + terserOptions: { + compress: { + drop_console: true, // Remove console.log + drop_debugger: true, + }, + }, + rollupOptions: { + output: { + manualChunks: { + vendor: ['react', 'react-dom'], // Separate vendor bundle + }, + }, + }, + }, +} +``` + +**Results:** +- JavaScript minified (~70% reduction) +- CSS minified (~60% reduction) +- Images optimised +- Tree-shaking applied + +### Go Optimisations + +**Compiler flags:** + +```bash +-ldflags="-s -w" +``` + +- `-s`: Strip symbol table (~10% reduction) +- `-w`: Strip DWARF debug info (~20% reduction) + +**Additional optimisations:** + +```bash +-ldflags="-s -w -X main.version=1.0.0" +``` + +- `-X`: Set variable values at build time +- Useful for version numbers, build dates + +### Binary Compression + +**UPX (optional):** + +```bash +# After building +upx --best build/bin/myapp.exe +``` + +**Results:** +- ~50% size reduction +- Slightly slower startup (~100ms) +- Not recommended for macOS (code signing issues) + +## Platform-Specific Builds + +### Windows + +**Output:** `myapp.exe` + +**Includes:** +- Application icon +- Version information +- Manifest (UAC settings) + +**Icon:** + +```bash +# Specify icon +wails3 build -icon build/appicon.png +``` + +Wails converts PNG to `.ico` automatically. + +**Manifest:** + +```xml + + + + + + + + + + + + +``` + +### macOS + +**Output:** `myapp.app` (application bundle) + +**Structure:** + +``` +myapp.app/ +├── Contents/ +│ ├── Info.plist # App metadata +│ ├── MacOS/ +│ │ └── myapp # Binary +│ ├── Resources/ +│ │ └── icon.icns # Icon +│ └── _CodeSignature/ # Code signature (if signed) +``` + +**Info.plist:** + +```xml + + + + + CFBundleName + My App + CFBundleIdentifier + com.example.myapp + CFBundleVersion + 1.0.0 + + +``` + +**Universal Binary:** + +```bash +# Build for both architectures +wails3 build -platform darwin/amd64 +wails3 build -platform darwin/arm64 + +# Combine into universal binary +lipo -create -output myapp-universal \ + build/bin/myapp-amd64 \ + build/bin/myapp-arm64 +``` + +### Linux + +**Output:** `myapp` (ELF binary) + +**Dependencies:** +- GTK3 +- WebKitGTK + +**Desktop file:** + +```ini +# myapp.desktop +[Desktop Entry] +Name=My App +Exec=/usr/bin/myapp +Icon=myapp +Type=Application +Categories=Utility; +``` + +**Installation:** + +```bash +# Copy binary +sudo cp myapp /usr/bin/ + +# Copy desktop file +sudo cp myapp.desktop /usr/share/applications/ + +# Copy icon +sudo cp icon.png /usr/share/icons/hicolor/256x256/apps/myapp.png +``` + +## Build Performance + +### Typical Build Times + +| Phase | Time | Notes | +|-------|------|-------| +| Analysis | <1s | Go code scanning | +| Binding Generation | <1s | TypeScript generation | +| Frontend Build | 5-30s | Depends on project size | +| Go Compilation | 2-10s | Depends on code size | +| Asset Embedding | <1s | Embedding frontend | +| **Total** | **10-45s** | First build | +| **Incremental** | **5-15s** | Subsequent builds | + +### Speeding Up Builds + +**1. Use build cache:** + +```bash +# Go build cache is automatic +# Frontend cache (Vite) +npm run build # Uses cache by default +``` + +**2. Skip unchanged steps:** + +```bash +# Skip frontend if unchanged +wails3 build -skipbindings +``` + +**3. Parallel builds:** + +```bash +# Build multiple platforms in parallel +wails3 build -platform windows/amd64 & +wails3 build -platform darwin/amd64 & +wails3 build -platform linux/amd64 & +wait +``` + +**4. Use faster tools:** + +```bash +# Use esbuild instead of webpack +# (Vite uses esbuild by default) +``` + +## Troubleshooting + +### Build Fails + +**Symptom:** `wails3 build` exits with error + +**Common causes:** + +1. **Go compilation error** + ```bash + # Check Go code compiles + go build + ``` + +2. **Frontend build error** + ```bash + # Check frontend builds + cd frontend + npm run build + ``` + +3. **Missing dependencies** + ```bash + # Install dependencies + npm install + go mod download + ``` + +### Binary Too Large + +**Symptom:** Binary is >50MB + +**Solutions:** + +1. **Strip debug symbols** (should be automatic) + ```bash + wails3 build # Already includes -ldflags="-s -w" + ``` + +2. **Check embedded assets** + ```bash + # Remove unnecessary files from frontend/dist/ + # Check for large images, videos, etc. + ``` + +3. **Use UPX compression** + ```bash + upx --best build/bin/myapp.exe + ``` + +### Slow Builds + +**Symptom:** Builds take >1 minute + +**Solutions:** + +1. **Use build cache** + - Go cache is automatic + - Frontend cache (Vite) is automatic + +2. **Skip unchanged steps** + ```bash + wails3 build -skipbindings + ``` + +3. **Optimise frontend build** + ```javascript + // vite.config.js + export default { + build: { + minify: 'esbuild', // Faster than terser + }, + } + ``` + +## Best Practices + +### ✅ Do + +- **Use `wails3 dev` during development** - Fast iteration +- **Use `wails3 build` for releases** - Optimised output +- **Version your builds** - Use `-ldflags` to embed version +- **Test builds on target platforms** - Cross-compilation isn't perfect +- **Keep frontend builds fast** - Optimise bundler config +- **Use build cache** - Speeds up subsequent builds + +### ❌ Don't + +- **Don't commit `build/` directory** - Add to `.gitignore` +- **Don't skip testing builds** - Always test before release +- **Don't embed unnecessary assets** - Keep binaries small +- **Don't use debug builds for production** - Use optimised builds +- **Don't forget code signing** - Required for distribution + +## Next Steps + +**Building Applications** - Detailed guide to building and packaging +[Learn More →](/guides/building) + +**Cross-Platform Builds** - Build for all platforms from one machine +[Learn More →](/guides/cross-platform) + +**Creating Installers** - Create installers for end users +[Learn More →](/guides/installers) + +--- + +**Questions about building?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [build examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/build). diff --git a/docs/src/content/docs/concepts/lifecycle.mdx b/docs/src/content/docs/concepts/lifecycle.mdx new file mode 100644 index 000000000..c7f6c0805 --- /dev/null +++ b/docs/src/content/docs/concepts/lifecycle.mdx @@ -0,0 +1,807 @@ +--- +title: Application Lifecycle +description: Understanding the Wails application lifecycle from startup to shutdown +sidebar: + order: 2 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +## Understanding Application Lifecycle + +Desktop applications have a lifecycle from startup to shutdown. Wails v3 provides **services**, **events**, and **hooks** to manage this lifecycle effectively. + +## The Lifecycle Stages + +```d2 +direction: down + +Start: "Application Start" { + shape: oval + style.fill: "#10B981" +} + +Init: "Initialisation" { + Parse: "Parse Options" { + shape: rectangle + } + Register: "Register Services" { + shape: rectangle + } + Setup: "Setup Runtime" { + shape: rectangle + } +} + +AppRun: "app.Run()" { + shape: rectangle + style.fill: "#3B82F6" +} + +ServiceStartup: "Service Startup" { + shape: rectangle + style.fill: "#8B5CF6" +} + +EventLoop: "Event Loop" { + Process: "Process Events" { + shape: rectangle + } + Handle: "Handle Messages" { + shape: rectangle + } + Update: "Update UI" { + shape: rectangle + } +} + +QuitSignal: "Quit Signal" { + shape: diamond + style.fill: "#F59E0B" +} + +ShouldQuit: "ShouldQuit Check" { + shape: rectangle + style.fill: "#3B82F6" +} + +OnShutdown: "OnShutdown Callbacks" { + shape: rectangle + style.fill: "#3B82F6" +} + +ServiceShutdown: "Service Shutdown" { + shape: rectangle + style.fill: "#8B5CF6" +} + +Cleanup: "Cleanup" { + Close: "Close Windows" { + shape: rectangle + } + Release: "Release Resources" { + shape: rectangle + } +} + +End: "Application End" { + shape: oval + style.fill: "#EF4444" +} + +Start -> Init.Parse +Init.Parse -> Init.Register +Init.Register -> Init.Setup +Init.Setup -> AppRun +AppRun -> ServiceStartup +ServiceStartup -> EventLoop.Process +EventLoop.Process -> EventLoop.Handle +EventLoop.Handle -> EventLoop.Update +EventLoop.Update -> EventLoop.Process: "Loop" +EventLoop.Process -> QuitSignal: "User quits" +QuitSignal -> ShouldQuit: "Check allowed?" +ShouldQuit -> EventLoop.Process: "Denied" +ShouldQuit -> OnShutdown: "Allowed" +OnShutdown -> ServiceShutdown +ServiceShutdown -> Cleanup.Close +Cleanup.Close -> Cleanup.Release +Cleanup.Release -> End +``` + +### 1. Application Creation + +Create your application with `application.New()`: + +```go +app := application.New(application.Options{ + Name: "My App", + Description: "An application built with Wails", + Services: []application.Service{ + application.NewService(&MyService{}), + }, + Assets: application.AssetOptions{ + Handler: application.BundledAssetFileServer(assets), + }, +}) +``` + +**What happens:** +1. Options are parsed and validated +2. Services are registered (but not started yet) +3. Asset server is configured +4. Runtime is set up + +### 2. Running the Application + +Call `app.Run()` to start the application: + +```go +err := app.Run() // Blocks until quit +if err != nil { + log.Fatal(err) +} +``` + +**What happens:** +1. Services are started in registration order +2. Event listeners are activated +3. Windows can be created +4. Event loop begins + +### 3. Event Loop + +The application enters the event loop where it spends most of its time: + +- OS events processed (mouse, keyboard, window events) +- Go-to-JS messages handled +- JS-to-Go calls executed +- UI updates rendered + +### 4. Shutdown + +When the application quits: + +1. `ShouldQuit` callback is checked (if set) +2. `OnShutdown` callbacks are executed +3. Services are shut down in reverse order +4. Windows are closed +5. Resources are released + +## Services Lifecycle + +Services are the primary way to manage lifecycle in Wails v3. They provide startup and shutdown hooks through interfaces. For complete documentation on services, see the [Services guide](/features/bindings/services). + +### Creating a Service + +```go +type MyService struct { + db *sql.DB +} + +// ServiceStartup is called when the application starts +func (s *MyService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + var err error + s.db, err = sql.Open("sqlite3", "app.db") + if err != nil { + return err // Startup aborts if error returned + } + + // Run migrations + if err := s.runMigrations(); err != nil { + return err + } + + return nil +} + +// ServiceShutdown is called when the application shuts down +func (s *MyService) ServiceShutdown() error { + if s.db != nil { + return s.db.Close() + } + return nil +} +``` + +### Registering Services + +```go +app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&MyService{}), + application.NewService(&AnotherService{}), + }, +}) +``` + +**Key points:** +- Services start in registration order +- Services shut down in **reverse** registration order +- If a service's `ServiceStartup` returns an error, the application aborts +- The `ctx` passed to `ServiceStartup` is cancelled when shutdown begins + +### Using the Application Context + +The context passed to `ServiceStartup` is valid for the application's lifetime: + +```go +func (s *MyService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + // Start a background task that respects shutdown + go func() { + ticker := time.NewTicker(5 * time.Minute) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + s.performBackgroundSync() + case <-ctx.Done(): + // Application is shutting down + return + } + } + }() + + return nil +} +``` + +You can also access the context from the application instance: + +```go +app := application.Get() +ctx := app.Context() +``` + +## Application-Level Hooks + +These are convenience callbacks in `application.Options` that let you hook into the application lifecycle without creating a full service. They're useful for simple cleanup tasks, quit confirmation, or when you need to run code at specific points in the shutdown sequence. + +For more complex lifecycle management with startup logic, dependency injection, or stateful resources, use [Services](#services-lifecycle) instead. + +### ShouldQuit + +The `ShouldQuit` callback is called whenever a quit is requested—whether by the user closing the last window, pressing Cmd+Q (macOS) / Alt+F4 (Windows), or calling `app.Quit()` programmatically. + +**Return value:** +- Return `true` to allow the quit to proceed (application will shut down) +- Return `false` to cancel the quit (application continues running) + +This is your opportunity to intercept quit requests and optionally prevent them, for example to prompt the user about unsaved changes: + +```go +app := application.New(application.Options{ + ShouldQuit: func() bool { + if !hasUnsavedChanges() { + return true // No unsaved changes, allow quit + } + + // Prompt the user + result, _ := application.QuestionDialog(). + SetTitle("Unsaved Changes"). + SetMessage("You have unsaved changes. Quit anyway?"). + AddButton("Quit", "quit"). + AddButton("Cancel", "cancel"). + Show() + + // Only quit if user clicked "Quit" + return result == "quit" + }, +}) +``` + +If `ShouldQuit` is not set, the application will quit immediately when requested. + +**When ShouldQuit is called:** +- User closes the last window (unless `DisableQuitOnLastWindowClosed` is set) +- User presses Cmd+Q on macOS +- User presses Alt+F4 on Windows (when focused on last window) +- Code calls `app.Quit()` + +**When ShouldQuit is NOT called:** +- The process is killed (SIGKILL, Task Manager force-quit) +- `os.Exit()` is called directly + +### OnShutdown + +The `OnShutdown` callback is called when the application is confirmed to be quitting (after `ShouldQuit` returns `true`, if set). Use this for cleanup tasks like saving state, closing database connections, or releasing resources. + +```go +app := application.New(application.Options{ + OnShutdown: func() { + // Save application state + saveState() + + // Close connections + cleanup() + }, +}) +``` + +You can also register additional shutdown callbacks programmatically at any time during the application's lifecycle: + +```go +app.OnShutdown(func() { + log.Println("Application shutting down...") +}) +``` + +Multiple callbacks are executed in the order they were registered. The shutdown process blocks until all callbacks complete. + +**Important:** Keep shutdown callbacks fast (under 1 second). The operating system may force-terminate applications that take too long to quit, which could interrupt your cleanup and cause data loss. + +### PostShutdown + +The `PostShutdown` callback is called after all shutdown tasks have completed, just before the process terminates. At this point, the application instance is no longer usable—windows are closed, services are shut down, and resources are released. + +This is primarily useful for: +- Final logging that must happen after all other cleanup +- Testing and debugging shutdown behaviour +- Platforms where `app.Run()` doesn't return (the callback ensures your code runs) + +```go +app := application.New(application.Options{ + PostShutdown: func() { + // Final logging + log.Println("Application terminated cleanly") + + // Flush any buffered logs + logger.Sync() + }, +}) +``` + +**Note:** Do not attempt to use application features (windows, dialogs, etc.) in `PostShutdown`—they are no longer available. + +## Event-Based Lifecycle + +Wails provides an event system that notifies you when things happen in your application—windows opening, the application starting, theme changes, and more. You can listen to these events to react to lifecycle changes without blocking or intercepting them. + +For window events, you can also use `RegisterHook` instead of `OnWindowEvent` to intercept and cancel actions—for example, preventing a window from closing. See [Window Hooks](#window-hooks-cancellable-events) below. + +For full documentation on the event system, see the [Events guide](/features/events/system). + +### Application Events + +Listen to application lifecycle events: + +```go +app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(event *application.ApplicationEvent) { + app.Logger.Info("Application has started!") +}) +``` + +Platform-specific events are also available: + +```go +// macOS +app.Event.OnApplicationEvent(events.Mac.ApplicationDidFinishLaunching, func(event *application.ApplicationEvent) { + // Handle macOS launch +}) + +app.Event.OnApplicationEvent(events.Mac.ApplicationWillTerminate, func(event *application.ApplicationEvent) { + // Handle macOS termination +}) + +// Windows +app.Event.OnApplicationEvent(events.Windows.ApplicationStarted, func(event *application.ApplicationEvent) { + // Handle Windows start +}) +``` + +### Window Events + +Listen to window lifecycle events: + +```go +window := app.Window.New() + +window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) { + app.Logger.Info("Window gained focus") +}) + +window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) { + app.Logger.Info("Window is closing") +}) +``` + +### Window Hooks (Cancellable Events) + +Use `RegisterHook` instead of `OnWindowEvent` when you need to **cancel** an event: + +```go +window := app.Window.New() + +var countdown = 3 + +window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { + countdown-- + if countdown > 0 { + app.Logger.Info("Not closing yet!", "remaining", countdown) + e.Cancel() // Prevent the window from closing + return + } + app.Logger.Info("Window closing now") +}) +``` + +**Difference between OnWindowEvent and RegisterHook:** +- `OnWindowEvent`: Notifies you when an event happens (cannot cancel) +- `RegisterHook`: Lets you intercept and potentially cancel the event + +## Window Lifecycle + +Windows have their own lifecycle, from creation through to destruction. Each window loads its frontend content independently and can be shown, hidden, or closed at any time. When a user attempts to close a window, you can intercept this with a `RegisterHook` to prompt for confirmation or hide the window instead of destroying it. + +For complete window documentation, see the [Windows guide](/features/windows/basics). + +```d2 +direction: down + +Create: "Create Window" { + shape: oval + style.fill: "#10B981" +} + +Load: "Load Frontend" { + shape: rectangle +} + +Show: "Show Window" { + shape: rectangle +} + +Active: "Window Active" { + Events: "Handle Events" { + shape: rectangle + } +} + +CloseRequest: "Close Request" { + shape: diamond + style.fill: "#F59E0B" +} + +Hook: "WindowClosing Hook" { + shape: rectangle + style.fill: "#3B82F6" +} + +Destroy: "Destroy Window" { + shape: rectangle +} + +End: "Window Closed" { + shape: oval + style.fill: "#EF4444" +} + +Create -> Load +Load -> Show +Show -> Active.Events +Active.Events -> Active.Events: "Loop" +Active.Events -> CloseRequest: "User closes" +CloseRequest -> Hook +Hook -> Active.Events: "Cancelled" +Hook -> Destroy: "Allowed" +Destroy -> End +``` + +### Creating Windows + +```go +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "My Window", + Width: 800, + Height: 600, +}) +``` + +### Preventing Window Close + +```go +window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { + if hasUnsavedChanges() { + // Show dialog + result, _ := application.QuestionDialog(). + SetTitle("Unsaved Changes"). + SetMessage("Save before closing?"). + AddButton("Save", "save"). + AddButton("Discard", "discard"). + AddButton("Cancel", "cancel"). + Show() + + switch result { + case "save": + saveChanges() + // Allow close + case "cancel": + e.Cancel() // Prevent close + } + // "discard" falls through and allows close + } +}) +``` + +### Hide Instead of Close + +A common pattern for system tray apps: + +```go +window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { + window.Hide() // Hide instead of destroy + e.Cancel() // Prevent actual close +}) +``` + +## Multi-Window Lifecycle + +With multiple windows: + +```go +mainWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Main Window", +}) + +settingsWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Settings", + Width: 400, + Height: 600, + Hidden: true, // Start hidden +}) +``` + +**Default behaviour varies by platform:** + +| Platform | Default when last window closes | +|----------|--------------------------------| +| macOS | App stays running (menu bar remains) | +| Windows | App quits | +| Linux | App quits | + +macOS follows native platform conventions where applications typically remain active in the menu bar even with no windows. Windows and Linux quit by default. + +**Make all platforms quit when last window closes:** + +```go +app := application.New(application.Options{ + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, +}) +``` + +**Make all platforms stay running when last window closes:** + +This is useful for system tray applications or apps that should remain running in the background. + +```go +app := application.New(application.Options{ + Windows: application.WindowsOptions{ + DisableQuitOnLastWindowClosed: true, + }, + Linux: application.LinuxOptions{ + DisableQuitOnLastWindowClosed: true, + }, +}) +``` + +## Common Patterns + +### Pattern 1: Database Service + +```go +type DatabaseService struct { + db *sql.DB +} + +func (s *DatabaseService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + var err error + s.db, err = sql.Open("sqlite3", "app.db") + if err != nil { + return fmt.Errorf("failed to open database: %w", err) + } + + if err := s.db.PingContext(ctx); err != nil { + return fmt.Errorf("failed to connect to database: %w", err) + } + + return nil +} + +func (s *DatabaseService) ServiceShutdown() error { + if s.db != nil { + return s.db.Close() + } + return nil +} + +// Exported methods are available to the frontend +func (s *DatabaseService) GetUsers() ([]User, error) { + // Query implementation +} +``` + +### Pattern 2: Configuration Service + +```go +type ConfigService struct { + config *Config + path string +} + +func (s *ConfigService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + s.path = "config.json" + + data, err := os.ReadFile(s.path) + if err != nil { + if os.IsNotExist(err) { + s.config = &Config{} // Default config + return nil + } + return err + } + + return json.Unmarshal(data, &s.config) +} + +func (s *ConfigService) ServiceShutdown() error { + data, err := json.MarshalIndent(s.config, "", " ") + if err != nil { + return err + } + return os.WriteFile(s.path, data, 0644) +} +``` + +### Pattern 3: Background Worker + +```go +type WorkerService struct { + cancel context.CancelFunc +} + +func (s *WorkerService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + workerCtx, cancel := context.WithCancel(ctx) + s.cancel = cancel + + go s.runWorker(workerCtx) + + return nil +} + +func (s *WorkerService) ServiceShutdown() error { + if s.cancel != nil { + s.cancel() + } + return nil +} + +func (s *WorkerService) runWorker(ctx context.Context) { + ticker := time.NewTicker(time.Minute) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + s.doWork() + case <-ctx.Done(): + return + } + } +} +``` + +## Lifecycle Reference + +| Hook/Interface | When Called | Can Cancel? | Use For | +|----------------|-------------|-------------|---------| +| `ServiceStartup` | During `app.Run()`, before event loop | No (return error to abort) | Initialisation | +| `ServiceShutdown` | During shutdown, after `OnShutdown` | No | Cleanup | +| `OnShutdown` | When quit confirmed | No | Application cleanup | +| `ShouldQuit` | When quit requested | Yes (return false) | Confirm quit | +| `RegisterHook(WindowClosing)` | When window close requested | Yes (`e.Cancel()`) | Prevent window close | +| `OnWindowEvent` | When event occurs | No | React to events | +| `OnApplicationEvent` | When event occurs | No | React to events | + +## Platform Differences + +### macOS + +- **Application menu** persists even with no windows +- **Cmd+Q** triggers quit (goes through `ShouldQuit`) +- **Dock icon** remains unless hidden +- Use `ApplicationShouldTerminateAfterLastWindowClosed` to control quit behaviour + +### Windows + +- **No application menu** without a window +- **Alt+F4** closes window (can be prevented with `RegisterHook`) +- **System tray** can keep app running + +### Linux + +- **Behaviour varies** by desktop environment +- **Generally similar to Windows** + +## Debugging Lifecycle Issues + +### Problem: Application Won't Quit + +**Causes:** +1. `ShouldQuit` returning `false` +2. `OnShutdown` taking too long +3. Background goroutines not stopping + +**Solution:** + +```go +// 1. Check ShouldQuit logic +ShouldQuit: func() bool { + log.Println("ShouldQuit called") + return true +} + +// 2. Keep OnShutdown fast +OnShutdown: func() { + log.Println("OnShutdown started") + // Fast cleanup only + log.Println("OnShutdown finished") +} + +// 3. Use context for background tasks +func (s *MyService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + go func() { + <-ctx.Done() + log.Println("Context cancelled, stopping background work") + }() + return nil +} +``` + +### Problem: Service Startup Fails + +**Solution:** Return descriptive errors: + +```go +func (s *MyService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + if err := s.init(); err != nil { + return fmt.Errorf("failed to initialise: %w", err) + } + return nil +} +``` + +The error will be logged and the application will not start. + +## Best Practices + +### Do + +- **Use services for lifecycle management** - They provide proper startup/shutdown hooks +- **Keep shutdown fast** - Target under 1 second for all cleanup +- **Use context for cancellation** - Stop background tasks properly +- **Handle errors in startup** - Return errors to abort cleanly +- **Log lifecycle events** - Helps with debugging + +### Don't + +- **Don't block in service startup** - Keep initialisation fast (under 2 seconds) +- **Don't show dialogs in shutdown** - App is quitting, UI may not work +- **Don't ignore the context** - Always check `ctx.Done()` in goroutines +- **Don't leak resources** - Always implement `ServiceShutdown` + +## Next Steps + +**Services** - Learn more about the service system +[Learn More →](/features/services) + +**Events System** - Use events for communication +[Learn More →](/features/events/system) + +**Window Management** - Create and manage windows +[Learn More →](/features/windows/basics) + +--- + +**Questions about lifecycle?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples). diff --git a/docs/src/content/docs/concepts/manager-api.mdx b/docs/src/content/docs/concepts/manager-api.mdx new file mode 100644 index 000000000..26f6de918 --- /dev/null +++ b/docs/src/content/docs/concepts/manager-api.mdx @@ -0,0 +1,266 @@ +--- +title: Manager API +description: Organized API structure with focused manager interfaces +sidebar: + order: 2 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +The Wails v3 Manager API provides an organized and discoverable way to access application functionality through focused manager structs. This new API structure groups related methods together while maintaining full backward compatibility with the traditional App API. + +## Overview + +The Manager API organizes application functionality into eleven focused areas: + +- **`app.Window`** - Window creation, management, and callbacks +- **`app.ContextMenu`** - Context menu registration and management +- **`app.KeyBinding`** - Global key binding management +- **`app.Browser`** - Browser integration (opening URLs and files) +- **`app.Env`** - Environment information and system state +- **`app.Dialog`** - File and message dialog operations +- **`app.Event`** - Custom event handling and application events +- **`app.Menu`** - Application menu management +- **`app.Screen`** - Screen management and coordinate transformations +- **`app.Clipboard`** - Clipboard text operations +- **`app.SystemTray`** - System tray icon creation and management + +## Benefits + +- **Better discoverability** - IDE autocomplete shows organized API surface +- **Improved code organization** - Related methods grouped together +- **Enhanced maintainability** - Separation of concerns across managers +- **Future extensibility** - Easier to add new features to specific areas + +## Usage + +The Manager API provides organized access to all application functionality: + +```go +// Events and custom event handling +app.Event.Emit("custom", data) +app.Event.On("custom", func(e *CustomEvent) { ... }) + +// Window management +window, _ := app.Window.GetByName("main") +app.Window.OnCreate(func(window Window) { ... }) + +// Browser integration +app.Browser.OpenURL("https://wails.io") + +// Menu management +menu := app.Menu.New() +app.Menu.Set(menu) + +// System tray +systray := app.SystemTray.New() +``` + +## Manager Reference + +### Window Manager + +Manages window creation, retrieval, and lifecycle callbacks. + +```go +// Create windows +window := app.Window.New() +window := app.Window.NewWithOptions(options) +current := app.Window.Current() + +// Find windows +window, exists := app.Window.GetByName("main") +windows := app.Window.GetAll() + +// Window callbacks +app.Window.OnCreate(func(window Window) { + // Handle window creation +}) +``` + +### Event Manager + +Handles custom events and application event listening. + +```go +// Custom events +app.Event.Emit("userAction", data) +cancelFunc := app.Event.On("userAction", func(e *CustomEvent) { + // Handle event +}) +app.Event.Off("userAction") +app.Event.Reset() // Remove all listeners + +// Application events +app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(e *ApplicationEvent) { + // Handle system theme change +}) +``` + +### Browser Manager + +Provides browser integration for opening URLs and files. + +```go +// Open URLs and files in default browser +err := app.Browser.OpenURL("https://wails.io") +err := app.Browser.OpenFile("/path/to/document.pdf") +``` + +### Environment Manager + +Access to system environment information. + +```go +// Get environment info +env := app.Env.Info() +fmt.Printf("OS: %s, Arch: %s\n", env.OS, env.Arch) + +// Check system theme +if app.Env.IsDarkMode() { + // Dark mode is active +} + +// Open file manager +err := app.Env.OpenFileManager("/path/to/folder", false) +``` + +### Dialog Manager + +Organized access to file and message dialogs. + +```go +// File dialogs +result, err := app.Dialog.OpenFile(). + AddFilter("Text Files", "*.txt"). + PromptForSingleSelection() + +result, err = app.Dialog.SaveFile(). + SetDefaultFilename("document.txt"). + PromptForSingleSelection() + +// Message dialogs +app.Dialog.Info(). + SetTitle("Information"). + SetMessage("Operation completed successfully"). + Show() + +app.Dialog.Error(). + SetTitle("Error"). + SetMessage("An error occurred"). + Show() +``` + +### Menu Manager + +Application menu creation and management. + +```go +// Create and set application menu +menu := app.Menu.New() +fileMenu := menu.AddSubmenu("File") +fileMenu.Add("New").OnClick(func(ctx *Context) { + // Handle menu click +}) + +app.Menu.Set(menu) + +// Show about dialog +app.Menu.ShowAbout() +``` + +### Key Binding Manager + +Dynamic management of global key bindings. + +```go +// Add key bindings +app.KeyBinding.Add("ctrl+n", func(window *WebviewWindow) { + // Handle Ctrl+N +}) + +app.KeyBinding.Add("ctrl+q", func(window *WebviewWindow) { + app.Quit() +}) + +// Remove key bindings +app.KeyBinding.Remove("ctrl+n") + +// Get all bindings +bindings := app.KeyBinding.GetAll() +``` + +### Context Menu Manager + +Advanced context menu management (for library authors). + +```go +// Create and register context menu +menu := app.ContextMenu.New() +app.ContextMenu.Add("myMenu", menu) + +// Retrieve context menu +menu, exists := app.ContextMenu.Get("myMenu") + +// Remove context menu +app.ContextMenu.Remove("myMenu") +``` + +### Screen Manager + +Screen management and coordinate transformations for multi-monitor setups. + +```go +// Get screen information +screens := app.Screen.GetAll() +primary := app.Screen.GetPrimary() + +// Coordinate transformations +physicalPoint := app.Screen.DipToPhysicalPoint(logicalPoint) +logicalPoint := app.Screen.PhysicalToDipPoint(physicalPoint) + +// Screen detection +screen := app.Screen.ScreenNearestDipPoint(point) +screen = app.Screen.ScreenNearestDipRect(rect) +``` + +### Clipboard Manager + +Clipboard operations for reading and writing text. + +```go +// Set text to clipboard +success := app.Clipboard.SetText("Hello World") +if !success { + // Handle error +} + +// Get text from clipboard +text, ok := app.Clipboard.Text() +if !ok { + // Handle error +} else { + // Use the text +} +``` + +### SystemTray Manager + +System tray icon creation and management. + +```go +// Create system tray +systray := app.SystemTray.New() +systray.SetLabel("My App") +systray.SetIcon(iconBytes) + +// Add menu to system tray +menu := app.Menu.New() +menu.Add("Open").OnClick(func(ctx *Context) { + // Handle click +}) +systray.SetMenu(menu) + +// Destroy system tray when done +systray.Destroy() +``` \ No newline at end of file diff --git a/docs/src/content/docs/contributing.mdx b/docs/src/content/docs/contributing.mdx new file mode 100644 index 000000000..841c30282 --- /dev/null +++ b/docs/src/content/docs/contributing.mdx @@ -0,0 +1,275 @@ +--- +title: Contributing +description: Contribute to Wails +sidebar: + order: 100 +--- + +import { Card, CardGrid } from "@astrojs/starlight/components"; + +## Welcome Contributors! + +We welcome contributions to Wails! Whether you're fixing bugs, adding features, or improving documentation, your help is appreciated. + +## Ways to Contribute + +### 1. Report Issues + +Found a bug? [Open an issue](https://github.com/wailsapp/wails/issues/new) with: +- Clear description +- Steps to reproduce +- Expected vs actual behaviour +- System information +- Code samples + +### 2. Improve Documentation + +Documentation improvements are always welcome: +- Fix typos and errors +- Add examples +- Clarify explanations +- Translate content + +### 3. Submit Code + +Contribute code through pull requests: +- Bug fixes +- New features +- Performance improvements +- Tests + +## Getting Started + +### Fork and Clone + +```bash +# Fork the repository on GitHub +# Then clone your fork +git clone https://github.com/YOUR_USERNAME/wails.git +cd wails + +# Add upstream remote +git remote add upstream https://github.com/wailsapp/wails.git +``` + +### Build from Source + +```bash +# Install dependencies +go mod download + +# Build Wails CLI +cd v3/cmd/wails3 +go build + +# Test your build +./wails3 version +``` + +### Run Tests + +```bash +# Run all tests +go test ./... + +# Run specific package tests +go test ./v3/pkg/application + +# Run with coverage +go test -cover ./... +``` + +## Making Changes + +### Create a Branch + +```bash +# Update main +git checkout main +git pull upstream main + +# Create feature branch +git checkout -b feature/my-feature +``` + +### Make Your Changes + +1. **Write code** following Go conventions +2. **Add tests** for new functionality +3. **Update documentation** if needed +4. **Run tests** to ensure nothing breaks +5. **Commit changes** with clear messages + +### Commit Guidelines + +```bash +# Good commit messages +git commit -m "fix: resolve window focus issue on macOS" +git commit -m "feat: add support for custom window chrome" +git commit -m "docs: improve bindings documentation" + +# Use conventional commits: +# - feat: New feature +# - fix: Bug fix +# - docs: Documentation +# - test: Tests +# - refactor: Code refactoring +# - chore: Maintenance +``` + +### Submit Pull Request + +```bash +# Push to your fork +git push origin feature/my-feature + +# Open pull request on GitHub +# Provide clear description +# Reference related issues +``` + +## Pull Request Guidelines + +### Good PR Description + +```markdown +## Description +Brief description of changes + +## Changes +- Added feature X +- Fixed bug Y +- Updated documentation + +## Testing +- Tested on macOS 14 +- Tested on Windows 11 +- All tests passing + +## Related Issues +Fixes #123 +``` + +### PR Checklist + +- [ ] Code follows Go conventions +- [ ] Tests added/updated +- [ ] Documentation updated +- [ ] All tests passing +- [ ] No breaking changes (or documented) +- [ ] Commit messages clear + +## Code Guidelines + +### Go Code Style + +```go +// ✅ Good: Clear, documented, tested +// ProcessData processes the input data and returns the result. +// It returns an error if the data is invalid. +func ProcessData(data string) (string, error) { + if data == "" { + return "", errors.New("data cannot be empty") + } + + result := process(data) + return result, nil +} + +// ❌ Bad: No docs, no error handling +func ProcessData(data string) string { + return process(data) +} +``` + +### Testing + +```go +func TestProcessData(t *testing.T) { + tests := []struct { + name string + input string + want string + wantErr bool + }{ + {"valid input", "test", "processed", false}, + {"empty input", "", "", true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ProcessData(tt.input) + if (err != nil) != tt.wantErr { + t.Errorf("ProcessData() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("ProcessData() = %v, want %v", got, tt.want) + } + }) + } +} +``` + +## Documentation + +### Writing Docs + +Documentation uses Starlight (Astro): + +```bash +cd docs +npm install +npm run dev +``` + +### Documentation Style + +- Use International English spelling +- Start with the problem +- Provide working examples +- Include troubleshooting +- Cross-reference related content + +## Community + +### Get Help + +- **Discord:** [Join our community](https://discord.gg/JDdSxwjhGf) +- **GitHub Discussions:** Ask questions +- **GitHub Issues:** Report bugs + +### Code of Conduct + +Be respectful, inclusive, and professional. We're all here to build great software together. + +## Recognition + +Contributors are recognised in: +- Release notes +- Contributors list +- GitHub insights + +Thank you for contributing to Wails! 🎉 + +## Next Steps + + + + Visit the Wails repository. + + [View on GitHub →](https://github.com/wailsapp/wails) + + + + Join the community. + + [Join Discord →](https://discord.gg/JDdSxwjhGf) + + + + Read the docs. + + [Browse Docs →](/quick-start/why-wails) + + diff --git a/docs/src/content/docs/contributing/_category_.json b/docs/src/content/docs/contributing/_category_.json new file mode 100644 index 000000000..5f76b677d --- /dev/null +++ b/docs/src/content/docs/contributing/_category_.json @@ -0,0 +1,9 @@ +{ + "label": "Technical Documentation", + "link": { + "type": "generated-index", + "description": "Deep dive into Wails v3 internals for developers who want to understand or contribute to the codebase." + }, + "order": 50, + "collapsed": false +} diff --git a/docs/src/content/docs/contributing/architecture.mdx b/docs/src/content/docs/contributing/architecture.mdx new file mode 100644 index 000000000..ed4dab2d0 --- /dev/null +++ b/docs/src/content/docs/contributing/architecture.mdx @@ -0,0 +1,99 @@ +--- +title: Wails v3 Architecture +description: Deep-dive diagrams and explanations of every moving part inside Wails v3 +sidebar: + order: 1 +--- + + +Wails v3 is a **full-stack desktop framework** consisting of a Go runtime, +a JavaScript bridge, a task-driven tool-chain and a collection of templates that +let you ship native applications powered by modern web tech. + +This page presents the *big picture* in four diagrams: + +1. **Overall Architecture** – how every subsystem connects +2. **Runtime Flow** – what happens when JS calls Go and vice-versa +3. **Development vs Production** – two modes of the asset server +4. **Platform Implementations** – where OS-specific code lives + +--- + +## 1 · Overall Architecture + +**Wails v3 – High-Level Stack** + +{/* +TODO: Fix D2 diagram generation (triple quotes for multi-line strings) or embed as image. +The previous D2 code block was causing MDX parsing errors in the build pipeline. +*/} + +**[High-Level Stack Diagram Placeholder]** + +--- + +## 2 · Runtime Call Flow + +**Runtime – JavaScript ⇄ Go Calling Path** + +{/* +TODO: Fix D2 diagram generation (triple quotes for multi-line strings) or embed as image. +The previous D2 code block was causing MDX parsing errors in the build pipeline. +*/} + +**[Runtime Call Flow Diagram Placeholder]** + +Key points: + +* **No HTTP / IPC** – the bridge uses the native WebView’s in-memory channel +* **Method IDs** – deterministic FNV-hash enables O(1) lookup in Go +* **Promises** – errors propagate as rejections with stack & code + +--- + +## 3 · Development vs Production Asset Flow + +**Dev ↔ Prod Asset Server** + +{/* +TODO: Fix D2 diagram generation (triple quotes for multi-line strings) or embed as image. +The previous D2 code block was causing MDX parsing errors in the build pipeline. +*/} + +**[Asset Flow Diagram Placeholder]** + +* In **dev** the server proxies unknown paths to the framework’s live-reload + server and serves static assets from disk. +* In **prod** the same API is backed by `go:embed`, producing a zero-dependency + binary. + +--- + +## 4 · Platform-Specific Runtime Split + +**Per-OS Runtime Files** + +{/* +TODO: Fix D2 diagram generation (triple quotes for multi-line strings) or embed as image. +The previous D2 code block was causing MDX parsing errors in the build pipeline. +*/} + +**[Platform Split Diagram Placeholder]** + +Every feature follows this pattern: + +1. **Common interface** in `pkg/application` +2. **Message processor** entry in `pkg/application/messageprocessor_*.go` +3. **Implementation** per OS under `internal/runtime/*.go` guarded by build tags + +Missing functionality on an OS should return `ErrCapability` and register +availability via `internal/capabilities`. + +--- + +## Summary + +These diagrams outline **where the code lives**, **how data moves**, and +**which layers own which responsibilities**. +Keep them handy while exploring the detailed pages that follow – they are your +map to the Wails v3 source tree. diff --git a/docs/src/content/docs/contributing/architecture/bindings.mdx b/docs/src/content/docs/contributing/architecture/bindings.mdx new file mode 100644 index 000000000..9cfcfa2b6 --- /dev/null +++ b/docs/src/content/docs/contributing/architecture/bindings.mdx @@ -0,0 +1,156 @@ +--- +title: Binding System +description: How the binding system collects, processes, and generates JavaScript/TypeScript code +sidebar: + order: 1 +--- + +import { FileTree } from "@astrojs/starlight/components"; + +This guide explains how the Wails binding system works internally, providing insights for developers who want to understand the mechanics behind the automatic code generation. + +## Architecture Overview + +The Wails binding system consists of three main components: + +1. **Collection**: Analyzes Go code to extract information about services, models, and other declarations +2. **Configuration**: Manages settings and options for the binding generation process +3. **Rendering**: Generates JavaScript/TypeScript code based on the collected information + + +- internal/generator/ + - collect/ # Package analysis and information extraction + - config/ # Configuration structures and interfaces + - render/ # Code generation for JS/TS + + +## Collection Process + +The collection process is responsible for analyzing Go packages and extracting information about services, models, and other declarations. This is handled by the `collect` package. + +### Key Components + +- **Collector**: Manages package information and caches collected data +- **Package**: Represents a Go package being analyzed and stores collected services, models, and directives +- **Service**: Collects information about service types and their methods +- **Model**: Collects detailed information about model types, including fields, values, and type parameters +- **Directive**: Parses and interprets `//wails:` directives in Go source code + +### Collection Flow + +1. The collector scans the Go packages specified in the project +2. It identifies service types (structs with methods that will be exposed to the frontend) +3. For each service, it collects information about its methods +4. It identifies model types (structs used as parameters or return values in service methods) +5. For each model, it collects information about its fields and type parameters +6. It processes any `//wails:` directives found in the code + +## Rendering Process + +The rendering process is responsible for generating JavaScript/TypeScript code based on the collected information. This is handled by the `render` package. + +### Key Components + +- **Renderer**: Orchestrates the rendering of service, model, and index files +- **Module**: Represents a single generated JavaScript/TypeScript module +- **Templates**: Text templates used for code generation + +### Rendering Flow + +1. For each service, the renderer generates a JavaScript/TypeScript file with functions that mirror the service methods +2. For each model, the renderer generates a JavaScript/TypeScript class that mirrors the model struct +3. The renderer generates index files that re-export all services and models +4. The renderer applies any custom code injections specified by `//wails:inject` directives + +## Type Mapping + +One of the most important aspects of the binding system is how Go types are mapped to JavaScript/TypeScript types. Here's a summary of the mapping: + +| Go Type | JavaScript Type | TypeScript Type | +|---------|----------------|----------------| +| `bool` | `boolean` | `boolean` | +| `int`, `int8`, `int16`, `int32`, `int64`, `uint`, `uint8`, `uint16`, `uint32`, `uint64`, `float32`, `float64` | `number` | `number` | +| `string` | `string` | `string` | +| `[]byte` | `Uint8Array` | `Uint8Array` | +| `[]T` | `Array` | `T[]` | +| `map[K]V` | `Object` | `Record` | +| `struct` | `Object` | Custom class | +| `interface{}` | `any` | `any` | +| `*T` | `T \| null` | `T \| null` | +| `func` | Not supported | Not supported | +| `chan` | Not supported | Not supported | + +## Directives System + +The binding system supports several directives that can be used to customize the generated code. These directives are added as comments in your Go code. + +### Available Directives + +- `//wails:inject`: Injects custom JavaScript/TypeScript code into the generated bindings +- `//wails:include`: Includes additional files with the generated bindings +- `//wails:internal`: Marks a type or method as internal, preventing it from being exported to the frontend +- `//wails:ignore`: Completely ignores a method during binding generation +- `//wails:id`: Specifies a custom ID for a method, overriding the default hash-based ID + +### Directive Processing + +1. During the collection phase, the collector identifies and parses directives in the Go code +2. The directives are stored with the corresponding declarations (services, methods, models, etc.) +3. During the rendering phase, the renderer applies the directives to customize the generated code + +## Advanced Features + +### Conditional Code Generation + +The binding system supports conditional code generation using a two-character condition prefix for `include` and `inject` directives: + +``` + + + +
+

Application Report

+

Generated: {{.Timestamp}}

+
+
+ + {{range .Items}} +

{{.}}

+ {{end}} +
+ +` + + // Write report to file + file, err := os.Create(reportPath) + if err != nil { + return err + } + defer file.Close() + + t, err := template.New("report").Parse(tmpl) + if err != nil { + return err + } + + err = t.Execute(file, data) + if err != nil { + return err + } + + // Open in browser + return app.Browser.OpenFile(reportPath) +} +``` + +### Development Tools + +Open development resources during development: + +```go +func setupDevelopmentMenu(app *application.App) { + if !app.Env.Info().Debug { + return // Only show in debug mode + } + + menu := app.Menu.New() + devMenu := menu.AddSubmenu("Development") + + devMenu.Add("Open DevTools").OnClick(func(ctx *application.Context) { + // This would open browser devtools if available + window := app.Window.Current() + if window != nil { + window.OpenDevTools() + } + }) + + devMenu.Add("View Source").OnClick(func(ctx *application.Context) { + // Open source code repository + app.Browser.OpenURL("https://github.com/youruser/yourapp") + }) + + devMenu.Add("API Documentation").OnClick(func(ctx *application.Context) { + // Open local API docs + app.Browser.OpenURL("http://localhost:8080/docs") + }) +} +``` + +## Error Handling + +### Graceful Error Handling + +Always handle potential errors when opening URLs or files: + +```go +func openURLWithFallback(app *application.App, url string, fallbackMessage string) { + err := app.Browser.OpenURL(url) + if err != nil { + app.Logger.Error("Failed to open URL", "url", url, "error", err) + + // Show fallback dialog with URL + dialog := app.Dialog.Info() + dialog.SetTitle("Unable to Open Link") + dialog.SetMessage(fmt.Sprintf("%s\n\nURL: %s", fallbackMessage, url)) + dialog.Show() + } +} + +// Usage +openURLWithFallback(app, + "https://docs.example.com", + "Please open the following URL manually in your browser:") +``` + +### User Feedback + +Provide feedback when operations succeed or fail: + +```go +func openURLWithFeedback(app *application.App, url string) { + err := app.Browser.OpenURL(url) + if err != nil { + // Show error dialog + app.Dialog.Error(). + SetTitle("Browser Error"). + SetMessage(fmt.Sprintf("Could not open URL: %s", err.Error())). + Show() + } else { + // Optionally show success notification + app.Logger.Info("URL opened successfully", "url", url) + } +} +``` + +## Platform Considerations + + + + + On macOS: + + - Uses the `open` command to launch the default browser + - Respects user's default browser setting in System Preferences + - May prompt for permission if the application is sandboxed + - Handles `file://` URLs correctly for local files + + + + + + On Windows: + + - Uses Windows Shell API to open URLs + - Respects default browser setting in Windows Settings + - Handles Windows path formats correctly + - May show security warnings for untrusted URLs + + + + + + On Linux: + + - Attempts to use `xdg-open` first, falls back to other methods + - Behavior varies by desktop environment + - Respects `BROWSER` environment variable if set + - May require additional packages in minimal installations + + + + +## Best Practices + +1. **Always Handle Errors**: Browser operations can fail for various reasons: + ```go + if err := app.Browser.OpenURL(url); err != nil { + app.Logger.Error("Failed to open browser", "error", err) + // Provide fallback or user notification + } + ``` + +2. **Validate URLs**: Ensure URLs are well-formed before opening: + ```go + func isValidHTTPURL(str string) bool { + u, err := url.Parse(str) + return err == nil && (u.Scheme == "http" || u.Scheme == "https") + } + ``` + +3. **User Confirmation**: For external links, consider asking user permission: + ```go + // Show confirmation dialog before opening external links + confirmAndOpen(app, "https://external-site.com") + ``` + +4. **Secure File Paths**: When opening files, ensure paths are safe: + ```go + func openSafeFile(app *application.App, filename string) error { + // Ensure file exists and is readable + if _, err := os.Stat(filename); err != nil { + return err + } + return app.Browser.OpenFile(filename) + } + ``` + +## Complete Example + +Here's a complete example showing various browser integration patterns: + +```go +package main + +import ( + "fmt" + "os" + "path/filepath" + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Name: "Browser Integration Demo", + }) + + // Setup menu with browser actions + setupMenu(app) + + // Create main window + window := app.Window.New() + window.SetTitle("Browser Integration") + + err := app.Run() + if err != nil { + panic(err) + } +} + +func setupMenu(app *application.App) { + menu := app.Menu.New() + + // File menu + fileMenu := menu.AddSubmenu("File") + fileMenu.Add("Generate Report").OnClick(func(ctx *application.Context) { + generateHTMLReport(app) + }) + + // Help menu + helpMenu := menu.AddSubmenu("Help") + helpMenu.Add("Documentation").OnClick(func(ctx *application.Context) { + openWithConfirmation(app, "https://docs.example.com") + }) + helpMenu.Add("Support").OnClick(func(ctx *application.Context) { + openWithConfirmation(app, "https://support.example.com") + }) + + app.Menu.Set(menu) +} + +func openWithConfirmation(app *application.App, url string) { + dialog := app.Dialog.Question() + dialog.SetTitle("Open External Link") + dialog.SetMessage(fmt.Sprintf("Open %s in your browser?", url)) + + dialog.AddButton("Open").OnClick(func() { + if err := app.Browser.OpenURL(url); err != nil { + showError(app, "Failed to open URL", err) + } + }) + + dialog.AddButton("Cancel") + dialog.Show() +} + +func generateHTMLReport(app *application.App) { + // Create temporary HTML file + tmpDir := os.TempDir() + reportPath := filepath.Join(tmpDir, "demo_report.html") + + html := ` + + + + Demo Report + + + +
+

Application Report

+

This is a sample report generated by the application.

+
+
+

Report Details

+

This report was generated to demonstrate browser integration.

+
+ +` + + err := os.WriteFile(reportPath, []byte(html), 0644) + if err != nil { + showError(app, "Failed to create report", err) + return + } + + // Open in browser + err = app.Browser.OpenFile(reportPath) + if err != nil { + showError(app, "Failed to open report", err) + } +} + +func showError(app *application.App, message string, err error) { + app.Dialog.Error(). + SetTitle("Error"). + SetMessage(fmt.Sprintf("%s: %v", message, err)). + Show() +} +``` + +:::tip[Pro Tip] +Consider providing fallback mechanisms for when browser operations fail, such as copying URLs to clipboard or showing them in a dialog for manual opening. +::: + +:::danger[Warning] +Always validate URLs and file paths before opening them to prevent security issues. Be cautious about opening user-provided URLs without validation. +::: \ No newline at end of file diff --git a/docs/src/content/docs/features/clipboard/basics.mdx b/docs/src/content/docs/features/clipboard/basics.mdx new file mode 100644 index 000000000..b75570aca --- /dev/null +++ b/docs/src/content/docs/features/clipboard/basics.mdx @@ -0,0 +1,493 @@ +--- +title: Clipboard Operations +description: Copy and paste text with the system clipboard +sidebar: + order: 1 +--- + +import { Card, CardGrid } from "@astrojs/starlight/components"; + +## Clipboard Operations + +Wails provides a **unified clipboard API** that works across all platforms. Copy and paste text with simple, consistent methods on Windows, macOS, and Linux. + +## Quick Start + +```go +// Copy text to clipboard +app.Clipboard.SetText("Hello, World!") + +// Get text from clipboard +text, ok := app.Clipboard.Text() +if ok { + fmt.Println("Clipboard:", text) +} +``` + +**That's it!** Cross-platform clipboard access. + +## Copying Text + +### Basic Copy + +```go +success := app.Clipboard.SetText("Text to copy") +if !success { + app.Logger.Error("Failed to copy to clipboard") +} +``` + +**Returns:** `bool` - `true` if successful, `false` otherwise + +### Copy from Service + +```go +type ClipboardService struct { + app *application.Application +} + +func (c *ClipboardService) CopyToClipboard(text string) bool { + return c.app.Clipboard.SetText(text) +} +``` + +**Call from JavaScript:** + +```javascript +import { CopyToClipboard } from './bindings/myapp/clipboardservice' + +await CopyToClipboard("Text to copy") +``` + +### Copy with Feedback + +```go +func copyWithFeedback(text string) { + if app.Clipboard.SetText(text) { + app.Dialog.Info(). + SetTitle("Copied"). + SetMessage("Text copied to clipboard!"). + Show() + } else { + app.Dialog.Error(). + SetTitle("Copy Failed"). + SetMessage("Failed to copy to clipboard."). + Show() + } +} +``` + +## Pasting Text + +### Basic Paste + +```go +text, ok := app.Clipboard.Text() +if !ok { + app.Logger.Error("Failed to read clipboard") + return +} + +fmt.Println("Clipboard text:", text) +``` + +**Returns:** `(string, bool)` - Text and success flag + +### Paste from Service + +```go +func (c *ClipboardService) PasteFromClipboard() string { + text, ok := c.app.Clipboard.Text() + if !ok { + return "" + } + return text +} +``` + +**Call from JavaScript:** + +```javascript +import { PasteFromClipboard } from './bindings/myapp/clipboardservice' + +const text = await PasteFromClipboard() +console.log("Pasted:", text) +``` + +### Paste with Validation + +```go +func pasteText() (string, error) { + text, ok := app.Clipboard.Text() + if !ok { + return "", errors.New("clipboard empty or unavailable") + } + + // Validate + if len(text) == 0 { + return "", errors.New("clipboard is empty") + } + + if len(text) > 10000 { + return "", errors.New("clipboard text too large") + } + + return text, nil +} +``` + +## Complete Examples + +### Copy Button + +**Go:** + +```go +type TextService struct { + app *application.Application +} + +func (t *TextService) CopyText(text string) error { + if !t.app.Clipboard.SetText(text) { + return errors.New("failed to copy") + } + return nil +} +``` + +**JavaScript:** + +```javascript +import { CopyText } from './bindings/myapp/textservice' + +async function copyToClipboard(text) { + try { + await CopyText(text) + showNotification("Copied to clipboard!") + } catch (error) { + showError("Failed to copy: " + error) + } +} + +// Usage +document.getElementById('copy-btn').addEventListener('click', () => { + const text = document.getElementById('text').value + copyToClipboard(text) +}) +``` + +### Paste and Process + +**Go:** + +```go +type DataService struct { + app *application.Application +} + +func (d *DataService) PasteAndProcess() (string, error) { + // Get clipboard text + text, ok := d.app.Clipboard.Text() + if !ok { + return "", errors.New("clipboard unavailable") + } + + // Process text + processed := strings.TrimSpace(text) + processed = strings.ToUpper(processed) + + return processed, nil +} +``` + +**JavaScript:** + +```javascript +import { PasteAndProcess } from './bindings/myapp/dataservice' + +async function pasteAndProcess() { + try { + const result = await PasteAndProcess() + document.getElementById('output').value = result + } catch (error) { + showError("Failed to paste: " + error) + } +} +``` + +### Copy Multiple Formats + +```go +type CopyService struct { + app *application.Application +} + +func (c *CopyService) CopyAsPlainText(text string) bool { + return c.app.Clipboard.SetText(text) +} + +func (c *CopyService) CopyAsJSON(data interface{}) bool { + jsonBytes, err := json.MarshalIndent(data, "", " ") + if err != nil { + return false + } + return c.app.Clipboard.SetText(string(jsonBytes)) +} + +func (c *CopyService) CopyAsCSV(rows [][]string) bool { + var buf bytes.Buffer + writer := csv.NewWriter(&buf) + + for _, row := range rows { + if err := writer.Write(row); err != nil { + return false + } + } + + writer.Flush() + return c.app.Clipboard.SetText(buf.String()) +} +``` + +### Clipboard Monitor + +```go +type ClipboardMonitor struct { + app *application.Application + lastText string + ticker *time.Ticker + stopChan chan bool +} + +func NewClipboardMonitor(app *application.Application) *ClipboardMonitor { + return &ClipboardMonitor{ + app: app, + stopChan: make(chan bool), + } +} + +func (cm *ClipboardMonitor) Start() { + cm.ticker = time.NewTicker(1 * time.Second) + + go func() { + for { + select { + case <-cm.ticker.C: + cm.checkClipboard() + case <-cm.stopChan: + return + } + } + }() +} + +func (cm *ClipboardMonitor) Stop() { + if cm.ticker != nil { + cm.ticker.Stop() + } + cm.stopChan <- true +} + +func (cm *ClipboardMonitor) checkClipboard() { + text, ok := cm.app.Clipboard.Text() + if !ok { + return + } + + if text != cm.lastText { + cm.lastText = text + cm.app.Event.Emit("clipboard-changed", text) + } +} +``` + +### Copy with History + +```go +type ClipboardHistory struct { + app *application.Application + history []string + maxSize int +} + +func NewClipboardHistory(app *application.Application) *ClipboardHistory { + return &ClipboardHistory{ + app: app, + history: make([]string, 0), + maxSize: 10, + } +} + +func (ch *ClipboardHistory) Copy(text string) bool { + if !ch.app.Clipboard.SetText(text) { + return false + } + + // Add to history + ch.history = append([]string{text}, ch.history...) + + // Limit size + if len(ch.history) > ch.maxSize { + ch.history = ch.history[:ch.maxSize] + } + + return true +} + +func (ch *ClipboardHistory) GetHistory() []string { + return ch.history +} + +func (ch *ClipboardHistory) RestoreFromHistory(index int) bool { + if index < 0 || index >= len(ch.history) { + return false + } + + return ch.app.Clipboard.SetText(ch.history[index]) +} +``` + +## Frontend Integration + +### Using Browser Clipboard API + +For simple text, you can use the browser's clipboard API: + +```javascript +// Copy +async function copyText(text) { + try { + await navigator.clipboard.writeText(text) + console.log("Copied!") + } catch (error) { + console.error("Copy failed:", error) + } +} + +// Paste +async function pasteText() { + try { + const text = await navigator.clipboard.readText() + return text + } catch (error) { + console.error("Paste failed:", error) + return "" + } +} +``` + +**Note:** Browser clipboard API requires HTTPS or localhost, and user permission. + +### Using Wails Clipboard + +For system-wide clipboard access: + +```javascript +import { CopyToClipboard, PasteFromClipboard } from './bindings/myapp/clipboardservice' + +// Copy +async function copy(text) { + const success = await CopyToClipboard(text) + if (success) { + console.log("Copied!") + } +} + +// Paste +async function paste() { + const text = await PasteFromClipboard() + return text +} +``` + +## Best Practices + +### ✅ Do + +- **Check return values** - Handle failures gracefully +- **Provide feedback** - Let users know copy succeeded +- **Validate pasted text** - Check format and size +- **Use appropriate method** - Browser API vs Wails API +- **Handle empty clipboard** - Check before using +- **Trim whitespace** - Clean pasted text + +### ❌ Don't + +- **Don't ignore failures** - Always check success +- **Don't copy sensitive data** - Clipboard is shared +- **Don't assume format** - Validate pasted data +- **Don't poll too frequently** - If monitoring clipboard +- **Don't copy large data** - Use files instead +- **Don't forget security** - Sanitise pasted content + +## Platform Differences + +### macOS + +- Uses NSPasteboard +- Supports rich text (future) +- System-wide clipboard +- Clipboard history (system feature) + +### Windows + +- Uses Windows Clipboard API +- Supports multiple formats (future) +- System-wide clipboard +- Clipboard history (Windows 10+) + +### Linux + +- Uses X11/Wayland clipboard +- Primary and clipboard selections +- Varies by desktop environment +- May require clipboard manager + +## Limitations + +### Current Version + +- **Text only** - Images not yet supported +- **No format detection** - Plain text only +- **No clipboard events** - Must poll for changes +- **No clipboard history** - Implement yourself + +### Future Features + +- Image support +- Rich text support +- Multiple formats +- Clipboard change events +- Clipboard history API + +## Next Steps + + + + Call Go functions from JavaScript. + + [Learn More →](/features/bindings/methods) + + + + Use events for clipboard notifications. + + [Learn More →](/features/events/system) + + + + Show copy/paste feedback. + + [Learn More →](/features/dialogs/message) + + + + Organise clipboard code. + + [Learn More →](/features/bindings/services) + + + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [clipboard examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples). diff --git a/docs/src/content/docs/features/dialogs/custom.mdx b/docs/src/content/docs/features/dialogs/custom.mdx new file mode 100644 index 000000000..591d68db2 --- /dev/null +++ b/docs/src/content/docs/features/dialogs/custom.mdx @@ -0,0 +1,626 @@ +--- +title: Custom dialogs +sidebar: + order: 4 +--- + +import { Card, CardGrid } from "@astrojs/starlight/components"; + +## Custom dialogs + +Create **custom dialog windows** using regular Wails windows with dialog-like behaviour. Build custom forms, complex input validation, branded appearance, and rich content (images, videos) whilst maintaining familiar dialog patterns. + +## Quick Start + +```go +// Create custom dialog window +dialog := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Custom dialog", + Width: 400, + Height: 300, + AlwaysOnTop: true, + Frameless: true, + Hidden: true, +}) + +// Load custom UI +dialog.SetURL("http://wails.localhost/dialog.html") + +// Show as modal +dialog.Show() +dialog.SetFocus() +``` + +**That's it!** Custom UI with dialog behaviour. + +## Creating Custom dialogs + +### Basic Custom dialog + +```go +type Customdialog struct { + window *application.WebviewWindow + result chan string +} + +func NewCustomdialog(app *application.Application) *Customdialog { + dialog := &Customdialog{ + result: make(chan string, 1), + } + + dialog.window = app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Custom dialog", + Width: 400, + Height: 300, + AlwaysOnTop: true, + Resizable: false, + Hidden: true, + }) + + return dialog +} + +func (d *Customdialog) Show() string { + d.window.Show() + d.window.SetFocus() + + // Wait for result + return <-d.result +} + +func (d *Customdialog) Close(result string) { + d.result <- result + d.window.Close() +} +``` + +### Modal dialog + +```go +func ShowModaldialog(parent *application.WebviewWindow, title string) string { + // Create dialog + dialog := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: title, + Width: 400, + Height: 200, + Parent: parent, + AlwaysOnTop: true, + Resizable: false, + }) + + // Disable parent + parent.SetEnabled(false) + + // Re-enable parent on close + dialog.OnClose(func() bool { + parent.SetEnabled(true) + parent.SetFocus() + return true + }) + + dialog.Show() + + return waitForResult(dialog) +} +``` + +### Form dialog + +```go +type Formdialog struct { + window *application.WebviewWindow + data map[string]interface{} + done chan bool +} + +func NewFormdialog(app *application.Application) *Formdialog { + fd := &Formdialog{ + data: make(map[string]interface{}), + done: make(chan bool, 1), + } + + fd.window = app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Enter Information", + Width: 500, + Height: 400, + Frameless: true, + Hidden: true, + }) + + return fd +} + +func (fd *Formdialog) Show() (map[string]interface{}, bool) { + fd.window.Show() + fd.window.SetFocus() + + ok := <-fd.done + return fd.data, ok +} + +func (fd *Formdialog) Submit(data map[string]interface{}) { + fd.data = data + fd.done <- true + fd.window.Close() +} + +func (fd *Formdialog) Cancel() { + fd.done <- false + fd.window.Close() +} +``` + +## dialog Patterns + +### Confirmation dialog + +```go +func ShowConfirmdialog(message string) bool { + dialog := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Confirm", + Width: 400, + Height: 150, + AlwaysOnTop: true, + Frameless: true, + }) + + // Pass message to dialog + dialog.OnReady(func() { + dialog.EmitEvent("set-message", message) + }) + + result := make(chan bool, 1) + + // Handle responses + app.Event.On("confirm-yes", func(e *application.CustomEvent) { + result <- true + dialog.Close() + }) + + app.Event.On("confirm-no", func(e *application.CustomEvent) { + result <- false + dialog.Close() + }) + + dialog.Show() + return <-result +} +``` + +**Frontend (HTML/JS):** + +```html +
+

+
+ + +
+
+ + +``` + +### Input dialog + +```go +func ShowInputdialog(prompt string, defaultValue string) (string, bool) { + dialog := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Input", + Width: 400, + Height: 150, + Frameless: true, + }) + + result := make(chan struct { + value string + ok bool + }, 1) + + dialog.OnReady(func() { + dialog.EmitEvent("set-prompt", map[string]string{ + "prompt": prompt, + "default": defaultValue, + }) + }) + + app.Event.On("input-submit", func(e *application.CustomEvent) { + result <- struct { + value string + ok bool + }{e.Data.(string), true} + dialog.Close() + }) + + app.Event.On("input-cancel", func(e *application.CustomEvent) { + result <- struct { + value string + ok bool + }{"", false} + dialog.Close() + }) + + dialog.Show() + r := <-result + return r.value, r.ok +} +``` + +### Progress dialog + +```go +type Progressdialog struct { + window *application.WebviewWindow +} + +func NewProgressdialog(title string) *Progressdialog { + pd := &Progressdialog{} + + pd.window = app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: title, + Width: 400, + Height: 150, + Frameless: true, + }) + + return pd +} + +func (pd *Progressdialog) Show() { + pd.window.Show() +} + +func (pd *Progressdialog) UpdateProgress(current, total int, message string) { + pd.window.EmitEvent("progress-update", map[string]interface{}{ + "current": current, + "total": total, + "message": message, + }) +} + +func (pd *Progressdialog) Close() { + pd.window.Close() +} +``` + +**Usage:** + +```go +func processFiles(files []string) { + progress := NewProgressdialog("Processing Files") + progress.Show() + + for i, file := range files { + progress.UpdateProgress(i+1, len(files), + fmt.Sprintf("Processing %s...", filepath.Base(file))) + + processFile(file) + } + + progress.Close() +} +``` + +## Complete Examples + +### Login dialog + +**Go:** + +```go +type Logindialog struct { + window *application.WebviewWindow + result chan struct { + username string + password string + ok bool + } +} + +func NewLogindialog(app *application.Application) *Logindialog { + ld := &Logindialog{ + result: make(chan struct { + username string + password string + ok bool + }, 1), + } + + ld.window = app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Login", + Width: 400, + Height: 250, + Frameless: true, + }) + + return ld +} + +func (ld *Logindialog) Show() (string, string, bool) { + ld.window.Show() + ld.window.SetFocus() + + r := <-ld.result + return r.username, r.password, r.ok +} + +func (ld *Logindialog) Submit(username, password string) { + ld.result <- struct { + username string + password string + ok bool + }{username, password, true} + ld.window.Close() +} + +func (ld *Logindialog) Cancel() { + ld.result <- struct { + username string + password string + ok bool + }{"", "", false} + ld.window.Close() +} +``` + +**Frontend:** + +```html + + + +``` + +### Settings dialog + +**Go:** + +```go +type Settingsdialog struct { + window *application.WebviewWindow + settings map[string]interface{} + done chan bool +} + +func NewSettingsdialog(app *application.Application, current map[string]interface{}) *Settingsdialog { + sd := &Settingsdialog{ + settings: current, + done: make(chan bool, 1), + } + + sd.window = app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Settings", + Width: 600, + Height: 500, + }) + + sd.window.OnReady(func() { + sd.window.EmitEvent("load-settings", current) + }) + + return sd +} + +func (sd *Settingsdialog) Show() (map[string]interface{}, bool) { + sd.window.Show() + + ok := <-sd.done + return sd.settings, ok +} + +func (sd *Settingsdialog) Save(settings map[string]interface{}) { + sd.settings = settings + sd.done <- true + sd.window.Close() +} + +func (sd *Settingsdialog) Cancel() { + sd.done <- false + sd.window.Close() +} +``` + +### Wizard dialog + +```go +type Wizarddialog struct { + window *application.WebviewWindow + currentStep int + data map[string]interface{} + done chan bool +} + +func NewWizarddialog(app *application.Application) *Wizarddialog { + wd := &Wizarddialog{ + currentStep: 0, + data: make(map[string]interface{}), + done: make(chan bool, 1), + } + + wd.window = app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Setup Wizard", + Width: 600, + Height: 400, + Resizable: false, + }) + + return wd +} + +func (wd *Wizarddialog) Show() (map[string]interface{}, bool) { + wd.window.Show() + + ok := <-wd.done + return wd.data, ok +} + +func (wd *Wizarddialog) NextStep(stepData map[string]interface{}) { + // Merge step data + for k, v := range stepData { + wd.data[k] = v + } + + wd.currentStep++ + wd.window.EmitEvent("next-step", wd.currentStep) +} + +func (wd *Wizarddialog) PreviousStep() { + if wd.currentStep > 0 { + wd.currentStep-- + wd.window.EmitEvent("previous-step", wd.currentStep) + } +} + +func (wd *Wizarddialog) Finish(finalData map[string]interface{}) { + for k, v := range finalData { + wd.data[k] = v + } + + wd.done <- true + wd.window.Close() +} + +func (wd *Wizarddialog) Cancel() { + wd.done <- false + wd.window.Close() +} +``` + +## Best Practices + +### ✅ Do + +- **Use appropriate window options** - AlwaysOnTop, Frameless, etc. +- **Handle cancellation** - Always provide a way to cancel +- **Validate input** - Check data before accepting +- **Provide feedback** - Loading states, errors +- **Use events for communication** - Clean separation +- **Clean up resources** - Close windows, remove listeners + +### ❌ Don't + +- **Don't block the main thread** - Use channels for results +- **Don't forget to close** - Memory leaks +- **Don't skip validation** - Always validate input +- **Don't ignore errors** - Handle all error cases +- **Don't make it too complex** - Keep dialogs simple +- **Don't forget accessibility** - Keyboard navigation + +## Styling Custom dialogs + +### Modern dialog Style + +```css +.dialog { + display: flex; + flex-direction: column; + height: 100vh; + background: white; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; +} + +.dialog-header { + --wails-draggable: drag; + padding: 16px; + background: #f5f5f5; + border-bottom: 1px solid #e0e0e0; +} + +.dialog-content { + flex: 1; + padding: 24px; + overflow: auto; +} + +.dialog-footer { + padding: 16px; + background: #f5f5f5; + border-top: 1px solid #e0e0e0; + display: flex; + justify-content: flex-end; + gap: 8px; +} + +button { + padding: 8px 16px; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 14px; +} + +button.primary { + background: #007aff; + color: white; +} + +button.secondary { + background: #e0e0e0; + color: #333; +} +``` + +## Next Steps + + + + Standard info, warning, error dialogs. + + [Learn More →](/features/dialogs/message) + + + + Open, save, folder selection. + + [Learn More →](/features/dialogs/file) + + + + Learn about window management. + + [Learn More →](/features/windows/basics) + + + + Use events for dialog communication. + + [Learn More →](/features/events/system) + + + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [custom dialog examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/dialogs). diff --git a/docs/src/content/docs/features/dialogs/file.mdx b/docs/src/content/docs/features/dialogs/file.mdx new file mode 100644 index 000000000..bba6e3f03 --- /dev/null +++ b/docs/src/content/docs/features/dialogs/file.mdx @@ -0,0 +1,568 @@ +--- +title: File dialogs +description: Open, save, and folder selection dialogs +sidebar: + order: 3 +--- + +import { Card, CardGrid } from "@astrojs/starlight/components"; + +## File dialogs + +Wails provides **native file dialogs** with platform-appropriate appearance for opening files, saving files, and selecting folders. Simple API with file type filtering, multiple selection support, and default locations. + +## Creating File Dialogs + +File dialogs are accessed through the `app.Dialog` manager: + +```go +app.Dialog.OpenFile() +app.Dialog.SaveFile() +``` + +## Open File dialog + +Select files to open: + +```go +path, err := app.Dialog.OpenFile(). + SetTitle("Select Image"). + AddFilter("Images", "*.png;*.jpg;*.gif"). + AddFilter("All Files", "*.*"). + PromptForSingleSelection() + +if err != nil || path == "" { + return +} + +openFile(path) +``` + +**Use cases:** +- Open documents +- Import files +- Load images +- Select configuration files + +### Single File Selection + +```go +path, err := app.Dialog.OpenFile(). + SetTitle("Open Document"). + AddFilter("Text Files", "*.txt"). + AddFilter("All Files", "*.*"). + PromptForSingleSelection() + +if err != nil || path == "" { + // User cancelled or error occurred + return +} + +// Use selected file +data, _ := os.ReadFile(path) +``` + +### Multiple File Selection + +```go +paths, err := app.Dialog.OpenFile(). + SetTitle("Select Images"). + AddFilter("Images", "*.png;*.jpg;*.jpeg;*.gif"). + PromptForMultipleSelection() + +if err != nil { + return +} + +// Process all selected files +for _, path := range paths { + processFile(path) +} +``` + +### With Default Directory + +```go +path, err := app.Dialog.OpenFile(). + SetTitle("Open File"). + SetDirectory("/Users/me/Documents"). + PromptForSingleSelection() +``` + +## Save File dialog + +Choose where to save: + +```go +path, err := app.Dialog.SaveFile(). + SetFilename("document.txt"). + AddFilter("Text Files", "*.txt"). + AddFilter("All Files", "*.*"). + PromptForSingleSelection() + +if err != nil || path == "" { + return +} + +saveFile(path, data) +``` + +**Use cases:** +- Save documents +- Export data +- Create new files +- Save as... + +### With Default Filename + +```go +path, err := app.Dialog.SaveFile(). + SetFilename("export.csv"). + AddFilter("CSV Files", "*.csv"). + PromptForSingleSelection() +``` + +### With Default Directory + +```go +path, err := app.Dialog.SaveFile(). + SetDirectory("/Users/me/Documents"). + SetFilename("untitled.txt"). + PromptForSingleSelection() +``` + +### Overwrite Confirmation + +```go +path, err := app.Dialog.SaveFile(). + SetFilename("document.txt"). + PromptForSingleSelection() + +if err != nil || path == "" { + return +} + +// Check if file exists +if _, err := os.Stat(path); err == nil { + dialog := app.Dialog.Question(). + SetTitle("Confirm Overwrite"). + SetMessage("File already exists. Overwrite?") + + overwriteBtn := dialog.AddButton("Overwrite") + overwriteBtn.OnClick(func() { + saveFile(path, data) + }) + + cancelBtn := dialog.AddButton("Cancel") + dialog.SetDefaultButton(cancelBtn) + dialog.SetCancelButton(cancelBtn) + dialog.Show() + return +} + +saveFile(path, data) +``` + +## Select Folder dialog + +Choose a directory using the open file dialog with directory selection enabled: + +```go +path, err := app.Dialog.OpenFile(). + SetTitle("Select Output Folder"). + CanChooseDirectories(true). + CanChooseFiles(false). + PromptForSingleSelection() + +if err != nil || path == "" { + return +} + +exportToFolder(path) +``` + +**Use cases:** +- Choose output directory +- Select workspace +- Pick backup location +- Choose installation directory + +### With Default Directory + +```go +path, err := app.Dialog.OpenFile(). + SetTitle("Select Folder"). + SetDirectory("/Users/me/Documents"). + CanChooseDirectories(true). + CanChooseFiles(false). + PromptForSingleSelection() +``` + +## File Filters + +Use the `AddFilter()` method to add file type filters to dialogs. Each call adds a new filter option. + +### Basic Filters + +```go +path, _ := app.Dialog.OpenFile(). + AddFilter("Text Files", "*.txt"). + AddFilter("All Files", "*.*"). + PromptForSingleSelection() +``` + +### Multiple Extensions + +Use semicolons to specify multiple extensions in a single filter: + +```go +dialog := app.Dialog.OpenFile(). + AddFilter("Images", "*.png;*.jpg;*.jpeg;*.gif;*.bmp"). + AddFilter("Documents", "*.txt;*.doc;*.docx;*.pdf"). + AddFilter("All Files", "*.*") +``` + +### Pattern Format + +Use **semicolons** to separate multiple extensions in a single filter: + +```go +// Multiple extensions separated by semicolons +AddFilter("Images", "*.png;*.jpg;*.gif") +``` + +## Complete Examples + +### Open Image File + +```go +func openImage(app *application.App) (image.Image, error) { + path, err := app.Dialog.OpenFile(). + SetTitle("Select Image"). + AddFilter("Images", "*.png;*.jpg;*.jpeg;*.gif;*.bmp"). + PromptForSingleSelection() + + if err != nil { + return nil, err + } + + if path == "" { + return nil, errors.New("no file selected") + } + + // Open and decode image + file, err := os.Open(path) + if err != nil { + app.Dialog.Error(). + SetTitle("Open Failed"). + SetMessage(err.Error()). + Show() + return nil, err + } + defer file.Close() + + img, _, err := image.Decode(file) + if err != nil { + app.Dialog.Error(). + SetTitle("Invalid Image"). + SetMessage("Could not decode image file."). + Show() + return nil, err + } + + return img, nil +} +``` + +### Save Document with Validation + +```go +func saveDocument(app *application.App, content string) { + path, err := app.Dialog.SaveFile(). + SetFilename("document.txt"). + AddFilter("Text Files", "*.txt"). + AddFilter("Markdown Files", "*.md"). + AddFilter("All Files", "*.*"). + PromptForSingleSelection() + + if err != nil || path == "" { + return + } + + // Validate extension + ext := filepath.Ext(path) + if ext != ".txt" && ext != ".md" { + dialog := app.Dialog.Question(). + SetTitle("Confirm Extension"). + SetMessage(fmt.Sprintf("Save as %s file?", ext)) + + saveBtn := dialog.AddButton("Save") + saveBtn.OnClick(func() { + doSave(app, path, content) + }) + + cancelBtn := dialog.AddButton("Cancel") + dialog.SetDefaultButton(cancelBtn) + dialog.SetCancelButton(cancelBtn) + dialog.Show() + return + } + + doSave(app, path, content) +} + +func doSave(app *application.App, path, content string) { + if err := os.WriteFile(path, []byte(content), 0644); err != nil { + app.Dialog.Error(). + SetTitle("Save Failed"). + SetMessage(err.Error()). + Show() + return + } + + app.Dialog.Info(). + SetTitle("Saved"). + SetMessage("Document saved successfully!"). + Show() +} +``` + +### Batch File Processing + +```go +func processMultipleFiles(app *application.App) { + paths, err := app.Dialog.OpenFile(). + SetTitle("Select Files to Process"). + AddFilter("Images", "*.png;*.jpg"). + PromptForMultipleSelection() + + if err != nil || len(paths) == 0 { + return + } + + // Confirm processing + dialog := app.Dialog.Question(). + SetTitle("Confirm Processing"). + SetMessage(fmt.Sprintf("Process %d file(s)?", len(paths))) + + processBtn := dialog.AddButton("Process") + processBtn.OnClick(func() { + // Process files + var errs []error + for i, path := range paths { + if err := processFile(path); err != nil { + errs = append(errs, err) + } + + // Update progress + // app.Event.Emit("progress", map[string]interface{}{ + // "current": i + 1, + // "total": len(paths), + // }) + _ = i // suppress unused variable warning in example + } + + // Show results + if len(errs) > 0 { + app.Dialog.Warning(). + SetTitle("Processing Complete"). + SetMessage(fmt.Sprintf("Processed %d files with %d errors.", + len(paths), len(errs))). + Show() + } else { + app.Dialog.Info(). + SetTitle("Success"). + SetMessage(fmt.Sprintf("Processed %d files successfully!", len(paths))). + Show() + } + }) + + cancelBtn := dialog.AddButton("Cancel") + dialog.SetCancelButton(cancelBtn) + dialog.Show() +} +``` + +### Export with Folder Selection + +```go +func exportData(app *application.App, data []byte) { + // Select output folder + folder, err := app.Dialog.OpenFile(). + SetTitle("Select Export Folder"). + SetDirectory(getDefaultExportFolder()). + CanChooseDirectories(true). + CanChooseFiles(false). + PromptForSingleSelection() + + if err != nil || folder == "" { + return + } + + // Generate filename + filename := fmt.Sprintf("export_%s.csv", + time.Now().Format("2006-01-02_15-04-05")) + path := filepath.Join(folder, filename) + + // Save file + if err := os.WriteFile(path, data, 0644); err != nil { + app.Dialog.Error(). + SetTitle("Export Failed"). + SetMessage(err.Error()). + Show() + return + } + + // Show success with option to open folder + dialog := app.Dialog.Question(). + SetTitle("Export Complete"). + SetMessage(fmt.Sprintf("Exported to %s", filename)) + + openBtn := dialog.AddButton("Open Folder") + openBtn.OnClick(func() { + openFolder(folder) + }) + + dialog.AddButton("OK") + dialog.Show() +} +``` + +### Import with Validation + +```go +func importConfiguration(app *application.App) { + path, err := app.Dialog.OpenFile(). + SetTitle("Import Configuration"). + AddFilter("JSON Files", "*.json"). + AddFilter("YAML Files", "*.yaml;*.yml"). + PromptForSingleSelection() + + if err != nil || path == "" { + return + } + + // Read file + data, err := os.ReadFile(path) + if err != nil { + app.Dialog.Error(). + SetTitle("Read Failed"). + SetMessage(err.Error()). + Show() + return + } + + // Validate configuration + config, err := parseConfig(data) + if err != nil { + app.Dialog.Error(). + SetTitle("Invalid Configuration"). + SetMessage("File is not a valid configuration."). + Show() + return + } + + // Confirm import + dialog := app.Dialog.Question(). + SetTitle("Confirm Import"). + SetMessage("Import this configuration?") + + importBtn := dialog.AddButton("Import") + importBtn.OnClick(func() { + // Apply configuration + if err := applyConfig(config); err != nil { + app.Dialog.Error(). + SetTitle("Import Failed"). + SetMessage(err.Error()). + Show() + return + } + + app.Dialog.Info(). + SetTitle("Success"). + SetMessage("Configuration imported successfully!"). + Show() + }) + + cancelBtn := dialog.AddButton("Cancel") + dialog.SetCancelButton(cancelBtn) + dialog.Show() +} +``` + +## Best Practices + +### ✅ Do + +- **Provide file filters** - Help users find files +- **Set appropriate titles** - Clear context +- **Use default directories** - Start in logical location +- **Validate selections** - Check file types +- **Handle cancellation** - User might cancel +- **Show confirmation** - For destructive actions +- **Provide feedback** - Success/error messages + +### ❌ Don't + +- **Don't skip validation** - Check file types +- **Don't ignore errors** - Handle cancellation +- **Don't use generic filters** - Be specific +- **Don't forget "All Files"** - Always include as option +- **Don't hardcode paths** - Use user's home directory +- **Don't assume file exists** - Check before opening + +## Platform Differences + +### macOS + +- Native NSOpenPanel/NSSavePanel +- Sheet-style when attached to window +- Follows system theme +- Supports Quick Look preview +- Tags and favourites integration + +### Windows + +- Native File Open/Save dialogs +- Follows system theme +- Recent files integration +- Network location support + +### Linux + +- GTK file chooser +- Varies by desktop environment +- Follows desktop theme +- Recent files support + +## Next Steps + + + + Info, warning, and error dialogs. + + [Learn More →](/features/dialogs/message) + + + + Create custom dialog windows. + + [Learn More →](/features/dialogs/custom) + + + + Call Go functions from JavaScript. + + [Learn More →](/features/bindings/methods) + + + + Use events for progress updates. + + [Learn More →](/features/events/system) + + + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [file dialog examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/dialogs). diff --git a/docs/src/content/docs/features/dialogs/message.mdx b/docs/src/content/docs/features/dialogs/message.mdx new file mode 100644 index 000000000..4b718aaa6 --- /dev/null +++ b/docs/src/content/docs/features/dialogs/message.mdx @@ -0,0 +1,516 @@ +--- +title: Message dialogs +description: Display information, warnings, errors, and questions +sidebar: + order: 2 +--- + +import { Card, CardGrid } from "@astrojs/starlight/components"; + +## Message dialogs + +Wails provides **native message dialogs** with platform-appropriate appearance: info, warning, error, and question dialogs with customisable titles, messages, and buttons. Simple API, native behaviour, accessible by default. + +## Creating Dialogs + +Message dialogs are accessed through the `app.Dialog` manager: + +```go +app.Dialog.Info() +app.Dialog.Question() +app.Dialog.Warning() +app.Dialog.Error() +``` + +All methods return a `*MessageDialog` that can be configured using method chaining. + +## Information dialog + +Display informational messages: + +```go +app.Dialog.Info(). + SetTitle("Success"). + SetMessage("File saved successfully!"). + Show() +``` + +**Use cases:** +- Success confirmations +- Completion notices +- Informational messages +- Status updates + +**Example - Save confirmation:** + +```go +func saveFile(app *application.App, path string, data []byte) error { + if err := os.WriteFile(path, data, 0644); err != nil { + return err + } + + app.Dialog.Info(). + SetTitle("File Saved"). + SetMessage(fmt.Sprintf("Saved to %s", filepath.Base(path))). + Show() + + return nil +} +``` + +## Warning dialog + +Show warnings: + +```go +app.Dialog.Warning(). + SetTitle("Warning"). + SetMessage("This action cannot be undone."). + Show() +``` + +**Use cases:** +- Non-critical warnings +- Deprecation notices +- Caution messages +- Potential issues + +**Example - Disk space warning:** + +```go +func checkDiskSpace(app *application.App) { + available := getDiskSpace() + + if available < 100*1024*1024 { // Less than 100MB + app.Dialog.Warning(). + SetTitle("Low Disk Space"). + SetMessage(fmt.Sprintf("Only %d MB available.", available/(1024*1024))). + Show() + } +} +``` + +## Error dialog + +Display errors: + +```go +app.Dialog.Error(). + SetTitle("Error"). + SetMessage("Failed to connect to server."). + Show() +``` + +**Use cases:** +- Error messages +- Failure notifications +- Exception handling +- Critical issues + +**Example - Network error:** + +```go +func fetchData(app *application.App, url string) ([]byte, error) { + resp, err := http.Get(url) + if err != nil { + app.Dialog.Error(). + SetTitle("Network Error"). + SetMessage(fmt.Sprintf("Failed to connect: %v", err)). + Show() + return nil, err + } + defer resp.Body.Close() + + return io.ReadAll(resp.Body) +} +``` + +## Question dialog + +Ask users questions and handle responses via button callbacks: + +```go +dialog := app.Dialog.Question(). + SetTitle("Confirm"). + SetMessage("Save changes before closing?") + +save := dialog.AddButton("Save") +save.OnClick(func() { + saveChanges() +}) + +dontSave := dialog.AddButton("Don't Save") +dontSave.OnClick(func() { + // Continue without saving +}) + +cancel := dialog.AddButton("Cancel") +cancel.OnClick(func() { + // Don't close +}) + +dialog.SetDefaultButton(save) +dialog.SetCancelButton(cancel) +dialog.Show() +``` + +**Use cases:** +- Confirm actions +- Yes/No questions +- Multiple choice +- User decisions + +**Example - Unsaved changes:** + +```go +func closeDocument(app *application.App) { + if !hasUnsavedChanges() { + doClose() + return + } + + dialog := app.Dialog.Question(). + SetTitle("Unsaved Changes"). + SetMessage("Do you want to save your changes?") + + save := dialog.AddButton("Save") + save.OnClick(func() { + if saveDocument() { + doClose() + } + }) + + dontSave := dialog.AddButton("Don't Save") + dontSave.OnClick(func() { + doClose() + }) + + cancel := dialog.AddButton("Cancel") + // Cancel button has no callback - just closes the dialog + + dialog.SetDefaultButton(save) + dialog.SetCancelButton(cancel) + dialog.Show() +} +``` + +## dialog Options + +### Title and Message + +```go +dialog := app.Dialog.Info(). + SetTitle("Operation Complete"). + SetMessage("All files have been processed successfully.") +``` + +**Best practices:** +- **Title:** Short, descriptive (2-5 words) +- **Message:** Clear, specific, actionable +- **Avoid jargon:** Use plain language + +### Buttons + +**Single button (Info/Warning/Error):** + +Info, warning, and error dialogs display a default "OK" button: + +```go +app.Dialog.Info(). + SetMessage("Done!"). + Show() +``` + +You can also add custom buttons: + +```go +dialog := app.Dialog.Info(). + SetMessage("Done!") +ok := dialog.AddButton("Got it!") +dialog.SetDefaultButton(ok) +dialog.Show() +``` + +**Multiple buttons (Question):** + +Use `AddButton()` to add buttons, which returns a `*Button` you can configure: + +```go +dialog := app.Dialog.Question(). + SetMessage("Choose an action") + +option1 := dialog.AddButton("Option 1") +option1.OnClick(func() { + handleOption1() +}) + +option2 := dialog.AddButton("Option 2") +option2.OnClick(func() { + handleOption2() +}) + +option3 := dialog.AddButton("Option 3") +option3.OnClick(func() { + handleOption3() +}) + +dialog.Show() +``` + +**Default and Cancel buttons:** + +Use `SetDefaultButton()` to specify which button is highlighted and triggered by Enter. +Use `SetCancelButton()` to specify which button is triggered by Escape. + +```go +dialog := app.Dialog.Question(). + SetMessage("Delete file?") + +deleteBtn := dialog.AddButton("Delete") +deleteBtn.OnClick(func() { + performDelete() +}) + +cancelBtn := dialog.AddButton("Cancel") +// No callback needed - just dismisses dialog + +dialog.SetDefaultButton(cancelBtn) // Safe option as default +dialog.SetCancelButton(cancelBtn) // Escape triggers Cancel +dialog.Show() +``` + +You can also use the fluent `SetAsDefault()` and `SetAsCancel()` methods on buttons: + +```go +dialog := app.Dialog.Question(). + SetMessage("Delete file?") + +dialog.AddButton("Delete").OnClick(func() { + performDelete() +}) + +dialog.AddButton("Cancel").SetAsDefault().SetAsCancel() + +dialog.Show() +``` + +**Best practices:** +- **1-3 buttons:** Don't overwhelm users +- **Clear labels:** "Save" not "OK" +- **Safe default:** Non-destructive action +- **Order matters:** Most likely action first (except Cancel) + +### Custom Icon + +Set a custom icon for the dialog: + +```go +app.Dialog.Info(). + SetTitle("Custom Icon Example"). + SetMessage("Using a custom icon"). + SetIcon(myIconBytes). + Show() +``` + +### Window Attachment + +Attach to specific window: + +```go +dialog := app.Dialog.Question(). + SetMessage("Window-specific question"). + AttachToWindow(window) + +dialog.AddButton("OK") +dialog.Show() +``` + +**Benefits:** +- dialog appears on correct window +- Parent window disabled whilst shown +- Better multi-window UX + +## Complete Examples + +### Confirm Destructive Action + +```go +func deleteFiles(app *application.App, paths []string) { + // Confirm deletion + message := fmt.Sprintf("Delete %d file(s)?", len(paths)) + if len(paths) == 1 { + message = fmt.Sprintf("Delete %s?", filepath.Base(paths[0])) + } + + dialog := app.Dialog.Question(). + SetTitle("Confirm Delete"). + SetMessage(message) + + deleteBtn := dialog.AddButton("Delete") + deleteBtn.OnClick(func() { + // Perform deletion + var errs []error + for _, path := range paths { + if err := os.Remove(path); err != nil { + errs = append(errs, err) + } + } + + // Show result + if len(errs) > 0 { + app.Dialog.Error(). + SetTitle("Delete Failed"). + SetMessage(fmt.Sprintf("Failed to delete %d file(s)", len(errs))). + Show() + } else { + app.Dialog.Info(). + SetTitle("Delete Complete"). + SetMessage(fmt.Sprintf("Deleted %d file(s)", len(paths))). + Show() + } + }) + + cancelBtn := dialog.AddButton("Cancel") + dialog.SetDefaultButton(cancelBtn) + dialog.SetCancelButton(cancelBtn) + dialog.Show() +} +``` + +### Quit Confirmation + +```go +func confirmQuit(app *application.App) { + dialog := app.Dialog.Question(). + SetTitle("Quit"). + SetMessage("You have unsaved work. Are you sure you want to quit?") + + yes := dialog.AddButton("Yes") + yes.OnClick(func() { + app.Quit() + }) + + no := dialog.AddButton("No") + dialog.SetDefaultButton(no) + dialog.Show() +} +``` + +### Update Dialog with Download Option + +```go +func showUpdateDialog(app *application.App) { + dialog := app.Dialog.Question(). + SetTitle("Update"). + SetMessage("A new version is available. The cancel button is selected when pressing escape.") + + download := dialog.AddButton("📥 Download") + download.OnClick(func() { + app.Dialog.Info().SetMessage("Downloading...").Show() + }) + + cancel := dialog.AddButton("Cancel") + + dialog.SetDefaultButton(download) + dialog.SetCancelButton(cancel) + dialog.Show() +} +``` + +### Custom Icon Question + +```go +func showCustomIconQuestion(app *application.App, iconBytes []byte) { + dialog := app.Dialog.Question(). + SetTitle("Custom Icon Example"). + SetMessage("Using a custom icon"). + SetIcon(iconBytes) + + likeIt := dialog.AddButton("I like it!") + likeIt.OnClick(func() { + app.Dialog.Info().SetMessage("Thanks!").Show() + }) + + notKeen := dialog.AddButton("Not so keen...") + notKeen.OnClick(func() { + app.Dialog.Info().SetMessage("Too bad!").Show() + }) + + dialog.SetDefaultButton(likeIt) + dialog.Show() +} + +## Best Practices + +### ✅ Do + +- **Be specific** - "File saved to Documents" not "Success" +- **Use appropriate type** - Error for errors, Warning for warnings +- **Provide context** - Include relevant details +- **Use clear button labels** - "Delete" not "OK" +- **Set safe defaults** - Non-destructive action +- **Handle cancellation** - User might close dialog + +### ❌ Don't + +- **Don't overuse** - Interrupts workflow +- **Don't use for frequent updates** - Use notifications instead +- **Don't use generic messages** - "Error" tells nothing +- **Don't ignore errors** - Handle dialog.Show() errors +- **Don't block unnecessarily** - Consider async alternatives +- **Don't use technical jargon** - Plain language + +## Platform Differences + +### macOS + +- Sheet-style when attached to window +- Standard keyboard shortcuts (⌘. for Cancel) +- Follows system theme automatically +- Accessibility built-in + +### Windows + +- Modal dialogs +- TaskDialog appearance +- Esc for Cancel +- Follows system theme + +### Linux + +- GTK dialogs +- Varies by desktop environment +- Follows desktop theme +- Standard keyboard navigation + +## Next Steps + + + + Open, save, and folder selection. + + [Learn More →](/features/dialogs/file) + + + + Create custom dialog windows. + + [Learn More →](/features/dialogs/custom) + + + + Non-intrusive notifications. + + [Learn More →](/features/notifications) + + + + Use events for non-blocking communication. + + [Learn More →](/features/events/system) + + + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [dialog examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/dialogs). diff --git a/docs/src/content/docs/features/dialogs/overview.mdx b/docs/src/content/docs/features/dialogs/overview.mdx new file mode 100644 index 000000000..25dcaf901 --- /dev/null +++ b/docs/src/content/docs/features/dialogs/overview.mdx @@ -0,0 +1,518 @@ +--- +title: dialogs Overview +description: Display native system dialogs in your application +sidebar: + order: 1 +--- + +import { Tabs, TabItem, Card, CardGrid } from "@astrojs/starlight/components"; + +## Native dialogs + +Wails provides **native system dialogs** that work across all platforms: message dialogs (info, warning, error, question), file dialogs (open, save, folder), and custom dialog windows with platform-native appearance and behaviour. + +## Quick Start + +```go +// Information dialog +app.Dialog.Info(). + SetTitle("Success"). + SetMessage("File saved successfully!"). + Show() + +// Question dialog with button callbacks +dialog := app.Dialog.Question(). + SetTitle("Confirm"). + SetMessage("Delete this file?") + +deleteBtn := dialog.AddButton("Delete") +deleteBtn.OnClick(func() { + deleteFile() +}) + +cancelBtn := dialog.AddButton("Cancel") +dialog.SetDefaultButton(cancelBtn) +dialog.SetCancelButton(cancelBtn) +dialog.Show() + +// File open dialog +path, _ := app.Dialog.OpenFile(). + SetTitle("Select Image"). + AddFilter("Images", "*.png;*.jpg"). + PromptForSingleSelection() +``` + +**That's it!** Native dialogs with minimal code. + +## Accessing Dialogs + +Dialogs are accessed through the `app.Dialog` manager: + +```go +app.Dialog.Info() +app.Dialog.Question() +app.Dialog.Warning() +app.Dialog.Error() +app.Dialog.OpenFile() +app.Dialog.SaveFile() +``` + +## dialog Types + +### Information dialog + +Display simple messages: + +```go +app.Dialog.Info(). + SetTitle("Welcome"). + SetMessage("Welcome to our application!"). + Show() +``` + +**Use cases:** +- Success messages +- Informational notices +- Completion confirmations + +### Warning dialog + +Show warnings: + +```go +app.Dialog.Warning(). + SetTitle("Warning"). + SetMessage("This action cannot be undone."). + Show() +``` + +**Use cases:** +- Non-critical warnings +- Deprecation notices +- Caution messages + +### Error dialog + +Display errors: + +```go +app.Dialog.Error(). + SetTitle("Error"). + SetMessage("Failed to save file: " + err.Error()). + Show() +``` + +**Use cases:** +- Error messages +- Failure notifications +- Exception handling + +### Question dialog + +Ask users questions and handle responses via button callbacks: + +```go +dialog := app.Dialog.Question(). + SetTitle("Confirm Delete"). + SetMessage("Are you sure you want to delete this file?") + +deleteBtn := dialog.AddButton("Delete") +deleteBtn.OnClick(func() { + deleteFile() +}) + +cancelBtn := dialog.AddButton("Cancel") +dialog.SetDefaultButton(cancelBtn) +dialog.SetCancelButton(cancelBtn) +dialog.Show() +``` + +**Use cases:** +- Confirm actions +- Yes/No questions +- Multiple choice + +## File dialogs + +### Open File dialog + +Select files to open: + +```go +path, err := app.Dialog.OpenFile(). + SetTitle("Select Image"). + AddFilter("Images", "*.png;*.jpg;*.gif"). + AddFilter("All Files", "*.*"). + PromptForSingleSelection() + +if err == nil && path != "" { + openFile(path) +} +``` + +**Multiple selection:** + +```go +paths, err := app.Dialog.OpenFile(). + SetTitle("Select Images"). + AddFilter("Images", "*.png;*.jpg"). + PromptForMultipleSelection() + +if err == nil { + for _, path := range paths { + processFile(path) + } +} +``` + +### Save File dialog + +Choose where to save: + +```go +path, err := app.Dialog.SaveFile(). + SetFilename("document.txt"). + AddFilter("Text Files", "*.txt"). + AddFilter("All Files", "*.*"). + PromptForSingleSelection() + +if err == nil && path != "" { + saveFile(path) +} +``` + +### Select Folder dialog + +Choose a directory using the open file dialog with directory selection enabled: + +```go +path, err := app.Dialog.OpenFile(). + SetTitle("Select Output Folder"). + CanChooseDirectories(true). + CanChooseFiles(false). + PromptForSingleSelection() + +if err == nil && path != "" { + exportToFolder(path) +} +``` + +## dialog Options + +### Title and Message + +```go +dialog := app.Dialog.Info(). + SetTitle("Success"). + SetMessage("Operation completed successfully!") +``` + +### Buttons + +**Default button for simple dialogs:** + +Info, warning, and error dialogs display a default "OK" button: + +```go +app.Dialog.Info(). + SetMessage("Done!"). + Show() +``` + +**Custom buttons for question dialogs:** + +Use `AddButton()` to add buttons, which returns a `*Button` you can configure with callbacks: + +```go +dialog := app.Dialog.Question(). + SetMessage("Choose action") + +save := dialog.AddButton("Save") +save.OnClick(func() { + saveDocument() +}) + +dontSave := dialog.AddButton("Don't Save") +dontSave.OnClick(func() { + discardChanges() +}) + +cancel := dialog.AddButton("Cancel") +// No callback needed - just dismisses dialog + +dialog.SetDefaultButton(save) +dialog.SetCancelButton(cancel) +dialog.Show() +``` + +**Default and Cancel buttons:** + +Use `SetDefaultButton()` to specify which button is highlighted and triggered by Enter. +Use `SetCancelButton()` to specify which button is triggered by Escape. + +```go +dialog := app.Dialog.Question(). + SetMessage("Delete file?") + +deleteBtn := dialog.AddButton("Delete") +deleteBtn.OnClick(func() { + performDelete() +}) + +cancelBtn := dialog.AddButton("Cancel") +dialog.SetDefaultButton(cancelBtn) // Safe option highlighted by default +dialog.SetCancelButton(cancelBtn) // Escape triggers Cancel +dialog.Show() +``` + +### Window Attachment + +Attach dialog to specific window: + +```go +dialog := app.Dialog.Info(). + SetMessage("Window-specific message"). + AttachToWindow(window) + +dialog.Show() +``` + +**Behaviour:** +- dialog appears centred on parent window +- Parent window disabled whilst dialog shown +- dialog moves with parent window (macOS) + +## Platform Behaviour + + + + **macOS dialogs:** + + - Native NSAlert appearance + - Follow system theme (light/dark) + - Support keyboard navigation + - Standard shortcuts (⌘. for Cancel) + - Accessibility features built-in + - Sheet-style when attached to window + + **Example:** + ```go + // Appears as sheet on macOS + dialog := app.Dialog.Question(). + SetMessage("Save changes?"). + AttachToWindow(window) + dialog.AddButton("Yes") + dialog.AddButton("No") + dialog.Show() + ``` + + + + **Windows dialogs:** + + - Native TaskDialog appearance + - Follow system theme + - Support keyboard navigation + - Standard shortcuts (Esc for Cancel) + - Accessibility features built-in + - Modal to parent window + + **Example:** + ```go + // Modal dialog on Windows + app.Dialog.Error(). + SetTitle("Error"). + SetMessage("Operation failed"). + Show() + ``` + + + + **Linux dialogs:** + + - GTK dialog appearance + - Follow desktop theme + - Support keyboard navigation + - Desktop environment integration + - Varies by DE (GNOME, KDE, etc.) + + **Example:** + ```go + // GTK dialog on Linux + app.Dialog.Info(). + SetMessage("Update complete"). + Show() + ``` + + + +## Common Patterns + +### Confirm Before Destructive Action + +```go +func deleteFile(app *application.App, path string) { + dialog := app.Dialog.Question(). + SetTitle("Confirm Delete"). + SetMessage(fmt.Sprintf("Delete %s?", filepath.Base(path))) + + deleteBtn := dialog.AddButton("Delete") + deleteBtn.OnClick(func() { + if err := os.Remove(path); err != nil { + app.Dialog.Error(). + SetTitle("Delete Failed"). + SetMessage(err.Error()). + Show() + } + }) + + cancelBtn := dialog.AddButton("Cancel") + dialog.SetDefaultButton(cancelBtn) + dialog.SetCancelButton(cancelBtn) + dialog.Show() +} +``` + +### Error Handling with dialog + +```go +func saveDocument(app *application.App, path string, data []byte) { + if err := os.WriteFile(path, data, 0644); err != nil { + app.Dialog.Error(). + SetTitle("Save Failed"). + SetMessage(fmt.Sprintf("Could not save file: %v", err)). + Show() + return + } + + app.Dialog.Info(). + SetTitle("Success"). + SetMessage("File saved successfully!"). + Show() +} +``` + +### File Selection with Validation + +```go +func selectImageFile(app *application.App) (string, error) { + path, err := app.Dialog.OpenFile(). + SetTitle("Select Image"). + AddFilter("Images", "*.png;*.jpg;*.jpeg;*.gif"). + PromptForSingleSelection() + + if err != nil { + return "", err + } + + if path == "" { + return "", errors.New("no file selected") + } + + // Validate file + if !isValidImage(path) { + app.Dialog.Error(). + SetTitle("Invalid File"). + SetMessage("Selected file is not a valid image."). + Show() + return "", errors.New("invalid image") + } + + return path, nil +} +``` + +### Multi-Step dialog Flow + +```go +func exportData(app *application.App) { + // Step 1: Confirm export + dialog := app.Dialog.Question(). + SetTitle("Export Data"). + SetMessage("Export all data to CSV?") + + exportBtn := dialog.AddButton("Export") + exportBtn.OnClick(func() { + // Step 2: Select destination + path, err := app.Dialog.SaveFile(). + SetFilename("export.csv"). + AddFilter("CSV Files", "*.csv"). + PromptForSingleSelection() + + if err != nil || path == "" { + return + } + + // Step 3: Perform export + if err := performExport(path); err != nil { + app.Dialog.Error(). + SetTitle("Export Failed"). + SetMessage(err.Error()). + Show() + return + } + + // Step 4: Success + app.Dialog.Info(). + SetTitle("Export Complete"). + SetMessage("Data exported successfully!"). + Show() + }) + + cancelBtn := dialog.AddButton("Cancel") + dialog.SetCancelButton(cancelBtn) + dialog.Show() +} +``` + +## Best Practices + +### ✅ Do + +- **Use native dialogs** - Better UX than custom +- **Provide clear messages** - Be specific +- **Set appropriate titles** - Context matters +- **Use default buttons wisely** - Safe option as default +- **Handle cancellation** - User might cancel +- **Validate file selections** - Check file types + +### ❌ Don't + +- **Don't overuse dialogs** - Interrupts workflow +- **Don't use for frequent messages** - Use notifications +- **Don't forget error handling** - User might cancel +- **Don't block unnecessarily** - Consider alternatives +- **Don't use generic messages** - Be specific +- **Don't ignore platform differences** - Test on all platforms + +## Next Steps + + + + Info, warning, and error dialogs. + + [Learn More →](/features/dialogs/message) + + + + Open, save, and folder selection. + + [Learn More →](/features/dialogs/file) + + + + Create custom dialog windows. + + [Learn More →](/features/dialogs/custom) + + + + Learn about window management. + + [Learn More →](/features/windows/basics) + + + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [dialog examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/dialogs). diff --git a/docs/src/content/docs/features/drag-and-drop/files.mdx b/docs/src/content/docs/features/drag-and-drop/files.mdx new file mode 100644 index 000000000..ffc75093a --- /dev/null +++ b/docs/src/content/docs/features/drag-and-drop/files.mdx @@ -0,0 +1,212 @@ +--- +title: File Drop +description: Accept files dragged from the operating system into your application +sidebar: + order: 1 +--- + +Wails lets users drag files from the operating system (file manager, desktop) into your application. Unlike HTML5 drag-and-drop which only works within the browser, this gives you access to actual file paths on disk. + +## Enable File Drop + +File drop is disabled by default. To enable it, set `EnableFileDrop: true` in your window options: + +```go +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "My App", + Width: 800, + Height: 600, + EnableFileDrop: true, +}) +``` + +When `EnableFileDrop` is `false` (the default), files dragged from the OS are blocked - they won't open in the webview or trigger any events. This prevents accidental navigation when users drag files over your app. + +## Define Drop Zones + +Drop zones tell Wails which elements should accept files. Files dropped outside a drop zone are ignored. + +Add the `data-file-drop-target` attribute to any element: + +```html +
+ Drop files here +
+``` + +You can have multiple drop zones. The element's `id` and CSS classes are passed to your Go code, so you can handle drops differently depending on where files land. + +## Style Drag Hover + +When files are dragged over a drop zone, Wails adds the `file-drop-target-active` class. This lets you provide visual feedback so users know where they can drop: + +```css +.drop-zone { + border: 2px dashed #ccc; + padding: 40px; + text-align: center; + transition: all 0.2s ease; +} + +.drop-zone.file-drop-target-active { + border-color: #007bff; + background-color: rgba(0, 123, 255, 0.1); +} +``` + +The class is removed automatically when files leave the zone or are dropped. + +## Detect Dropped Files + +When files are dropped on a valid drop zone, Wails fires a `WindowFilesDropped` event. The event context contains the full filesystem paths of all dropped files: + +```go +import "github.com/wailsapp/wails/v3/pkg/events" + +window.OnWindowEvent(events.Common.WindowFilesDropped, func(event *application.WindowEvent) { + files := event.Context().DroppedFiles() + + for _, file := range files { + fmt.Println("Dropped:", file) + } +}) +``` + +The paths are absolute, like `/home/user/documents/report.pdf` or `C:\Users\Name\Documents\report.pdf`. + +## Get Drop Target Info + +When you have multiple drop zones, you can find out which one received the files using `DropTargetDetails()`: + +```go +window.OnWindowEvent(events.Common.WindowFilesDropped, func(event *application.WindowEvent) { + files := event.Context().DroppedFiles() + details := event.Context().DropTargetDetails() + + fmt.Printf("Dropped on element: id=%s, classes=%v\n", + details.ElementID, details.ClassList) + fmt.Printf("Position: x=%d, y=%d\n", details.X, details.Y) +}) +``` + +This lets you route files to different handlers: + +```go +switch details.ElementID { +case "images": + handleImageUpload(files) +case "documents": + handleDocumentUpload(files) +} +``` + +## Complete Example + +**Go:** + +```go +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "File Uploader", + EnableFileDrop: true, +}) + +window.OnWindowEvent(events.Common.WindowFilesDropped, func(event *application.WindowEvent) { + files := event.Context().DroppedFiles() + details := event.Context().DropTargetDetails() + + // Send to frontend + app.Event.Emit("files-dropped", map[string]any{ + "files": files, + "target": details.ElementID, + }) +}) +``` + +**HTML:** + +```html +
+ Drop images here +
+ +
+ Drop documents here +
+ + +``` + +## Full Window Drop + +If you want files to be droppable anywhere in your app, add the attribute to the body element: + +```html + + + +``` + +You can use a CSS overlay to indicate the entire window is a drop target: + +```css +body.file-drop-target-active::after { + content: "Drop files anywhere"; + position: fixed; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + font-size: 24px; + color: #007bff; + background: rgba(255, 255, 255, 0.9); + pointer-events: none; +} +``` + +## Combining with HTML Drag & Drop + +You can use both external file drops and internal HTML drag-and-drop in the same application. When `EnableFileDrop` is `true`, Wails intercepts external file drags but lets internal HTML5 drags pass through normally. + +To distinguish between them in your HTML drop zone handlers, check if the drag contains files: + +```javascript +zone.addEventListener('dragenter', (e) => { + // Skip external file drags - Wails handles these + if (e.dataTransfer?.types.includes('Files')) { + return; + } + // Handle internal HTML5 drags + zone.classList.add('drag-over'); +}); + +zone.addEventListener('drop', (e) => { + // Skip external file drops - Wails handles these + if (e.dataTransfer?.types.includes('Files')) { + return; + } + e.preventDefault(); + zone.classList.remove('drag-over'); + // Handle internal drop +}); +``` + +This ensures your HTML drop handlers only respond to internal drags (like moving list items), while Wails handles external file drops separately via the `WindowFilesDropped` event. + +## Next Steps + +- [HTML Drag & Drop](./html) - Drag elements within your app +- [Window Options](/features/windows/options) - All window configuration options diff --git a/docs/src/content/docs/features/drag-and-drop/html.mdx b/docs/src/content/docs/features/drag-and-drop/html.mdx new file mode 100644 index 000000000..7eeebc7be --- /dev/null +++ b/docs/src/content/docs/features/drag-and-drop/html.mdx @@ -0,0 +1,232 @@ +--- +title: HTML Drag & Drop +description: Drag and drop elements within your application +sidebar: + order: 2 +--- + +HTML5 drag-and-drop lets users drag elements within your app's UI - for example, reordering a list or moving items between columns. This is standard web functionality that works in Wails without any special setup. + +## Make an Element Draggable + +By default, most elements can't be dragged. To make an element draggable, add `draggable="true"`: + +```html +
Drag me
+``` + +The element will now show a drag preview when the user clicks and drags it. + +## Define a Drop Zone + +Elements don't accept drops by default. To make an element accept drops, you need to cancel the default behaviour on `dragover`: + +```html +
Drop here
+ + +``` + +Calling `preventDefault()` on `dragover` is required - it signals that this element accepts drops. Without it, the drop event won't fire. + +## Style Drag Hover + +To show users where they can drop, add visual feedback when dragging over a drop zone. The `dragenter` event fires when something enters the zone, and `dragleave` fires when it leaves: + +```css +.drop-zone { + border: 2px dashed #ccc; + padding: 40px; + transition: all 0.2s ease; +} + +.drop-zone.drag-over { + border-color: #007bff; + background-color: rgba(0, 123, 255, 0.1); +} +``` + +```javascript +const target = document.getElementById('target'); + +target.addEventListener('dragenter', () => { + target.classList.add('drag-over'); +}); + +target.addEventListener('dragleave', () => { + target.classList.remove('drag-over'); +}); + +target.addEventListener('drop', (e) => { + e.preventDefault(); + target.classList.remove('drag-over'); + // Handle the drop +}); +``` + +Note: `dragleave` also fires when entering a child element, which can cause flickering. The complete example below shows how to handle this. + +## Complete Example + +A task list where items can be dragged between priority columns. This tracks the dragged element in a variable, which is the simplest approach when everything is on the same page: + +```html +
+
Fix login bug
+
Update docs
+
Add dark mode
+
+ +
+
+

High Priority

+
    +
    +
    +

    Low Priority

    +
      +
      +
      + + + + +``` + +## Combining with File Drop + +If your app uses both HTML drag-and-drop and [File Drop](./files), your HTML drop zones will also receive events when users drag files from the operating system. To prevent confusion, filter out file drags in your handlers: + +```javascript +zone.addEventListener('dragenter', (e) => { + // Ignore external file drags + if (e.dataTransfer?.types.includes('Files')) return; + + zone.classList.add('drag-over'); +}); + +zone.addEventListener('dragover', (e) => { + // Ignore external file drags + if (e.dataTransfer?.types.includes('Files')) return; + + e.preventDefault(); +}); + +zone.addEventListener('drop', (e) => { + // Ignore external file drags + if (e.dataTransfer?.types.includes('Files')) return; + + e.preventDefault(); + zone.classList.remove('drag-over'); + // Handle the internal drop +}); +``` + +The `dataTransfer.types` array contains `'Files'` when the user is dragging files from the OS, but contains types like `'text/plain'` for internal HTML drags. This lets you distinguish between the two. + +## Passing Data with dataTransfer + +The example above tracks the dragged element in a JavaScript variable. This works well when everything is on the same page. But if you need to drag between iframes or pass data that isn't tied to a DOM element, use the `dataTransfer` API: + +```javascript +// When drag starts, store data +item.addEventListener('dragstart', (e) => { + e.dataTransfer.setData('text/plain', item.id); +}); + +// When dropped, retrieve the data +target.addEventListener('drop', (e) => { + e.preventDefault(); + const itemId = e.dataTransfer.getData('text/plain'); + const item = document.getElementById(itemId); + // Move or copy the item +}); +``` + +The data is stored as strings, so you'll need to serialize objects with `JSON.stringify()` if needed. + +## Next Steps + +- [File Drop](./files) - Accept files from the operating system +- [MDN Drag and Drop API](https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API) - Full browser API reference diff --git a/docs/src/content/docs/features/environment/info.mdx b/docs/src/content/docs/features/environment/info.mdx new file mode 100644 index 000000000..d03fb5f70 --- /dev/null +++ b/docs/src/content/docs/features/environment/info.mdx @@ -0,0 +1,620 @@ +--- +title: Environment +sidebar: + order: 59 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +Wails provides comprehensive environment information through the EnvironmentManager API. This allows your application to detect system properties, theme preferences, and integrate with the operating system's file manager. + +## Accessing the Environment Manager + +The environment manager is accessed through the `Env` property on your application instance: + +```go +app := application.New(application.Options{ + Name: "Environment Demo", +}) + +// Access the environment manager +env := app.Env +``` + +## System Information + +### Get Environment Information + +Retrieve comprehensive information about the runtime environment: + +```go +envInfo := app.Env.Info() + +app.Logger.Info("Environment information", + "os", envInfo.OS, // "windows", "darwin", "linux" + "arch", envInfo.Arch, // "amd64", "arm64", etc. + "debug", envInfo.Debug, // Debug mode flag +) + +// Operating system details +if envInfo.OSInfo != nil { + app.Logger.Info("OS details", + "name", envInfo.OSInfo.Name, + "version", envInfo.OSInfo.Version, + ) +} + +// Platform-specific information +for key, value := range envInfo.PlatformInfo { + app.Logger.Info("Platform info", "key", key, "value", value) +} +``` + +### Environment Structure + +The environment information includes several important fields: + +```go +type EnvironmentInfo struct { + OS string // Operating system: "windows", "darwin", "linux" + Arch string // Architecture: "amd64", "arm64", "386", etc. + Debug bool // Whether running in debug mode + OSInfo *operatingsystem.OS // Detailed OS information + PlatformInfo map[string]any // Platform-specific details +} +``` + +## Theme Detection + +### Dark Mode Detection + +Detect whether the system is using dark mode: + +```go +if app.Env.IsDarkMode() { + app.Logger.Info("System is in dark mode") + // Apply dark theme to your application + applyDarkTheme() +} else { + app.Logger.Info("System is in light mode") + // Apply light theme to your application + applyLightTheme() +} +``` + +### Theme Change Monitoring + +Listen for theme changes to update your application dynamically: + +```go +import "github.com/wailsapp/wails/v3/pkg/events" + +// Listen for theme changes +app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(event *application.ApplicationEvent) { + if app.Env.IsDarkMode() { + app.Logger.Info("Switched to dark mode") + updateApplicationTheme("dark") + } else { + app.Logger.Info("Switched to light mode") + updateApplicationTheme("light") + } +}) + +func updateApplicationTheme(theme string) { + // Update your application's theme + // This could emit an event to the frontend + app.Event.Emit("theme:changed", theme) +} +``` + +## File Manager Integration + +### Open File Manager + +Open the system's file manager at a specific location: + +```go +// Open file manager at a directory +err := app.Env.OpenFileManager("/Users/username/Documents", false) +if err != nil { + app.Logger.Error("Failed to open file manager", "error", err) +} + +// Open file manager and select a specific file +err = app.Env.OpenFileManager("/Users/username/Documents/report.pdf", true) +if err != nil { + app.Logger.Error("Failed to open file manager with selection", "error", err) +} +``` + +### Common Use Cases + +Show files or folders in the file manager from your application: + +```go +func showInFileManager(app *application.App, path string) { + err := app.Env.OpenFileManager(path, true) + if err != nil { + // Fallback: try opening just the directory + dir := filepath.Dir(path) + err = app.Env.OpenFileManager(dir, false) + if err != nil { + app.Logger.Error("Could not open file manager", "path", path, "error", err) + + // Show error to user + app.Dialog.Error(). + SetTitle("File Manager Error"). + SetMessage("Could not open file manager"). + Show() + } + } +} + +// Usage examples +func setupFileMenu(app *application.App) { + menu := app.Menu.New() + fileMenu := menu.AddSubmenu("File") + + fileMenu.Add("Show Downloads Folder").OnClick(func(ctx *application.Context) { + homeDir, _ := os.UserHomeDir() + downloadsDir := filepath.Join(homeDir, "Downloads") + showInFileManager(app, downloadsDir) + }) + + fileMenu.Add("Show Application Data").OnClick(func(ctx *application.Context) { + configDir, _ := os.UserConfigDir() + appDir := filepath.Join(configDir, "MyApp") + showInFileManager(app, appDir) + }) +} +``` + +## Platform-Specific Behavior + +### Adaptive Application Behavior + +Use environment information to adapt your application's behavior: + +```go +func configureForPlatform(app *application.App) { + envInfo := app.Env.Info() + + switch envInfo.OS { + case "darwin": + configureMacOS(app) + case "windows": + configureWindows(app) + case "linux": + configureLinux(app) + } + + // Adapt to architecture + if envInfo.Arch == "arm64" { + app.Logger.Info("Running on ARM architecture") + // Potentially optimize for ARM + } +} + +func configureMacOS(app *application.App) { + app.Logger.Info("Configuring for macOS") + + // macOS-specific configuration + menu := app.Menu.New() + menu.AddRole(application.AppMenu) // Add standard macOS app menu + + // Handle dark mode + if app.Env.IsDarkMode() { + setMacOSDarkTheme() + } +} + +func configureWindows(app *application.App) { + app.Logger.Info("Configuring for Windows") + + // Windows-specific configuration + // Set up Windows-style menus, key bindings, etc. +} + +func configureLinux(app *application.App) { + app.Logger.Info("Configuring for Linux") + + // Linux-specific configuration + // Adapt to different desktop environments +} +``` + +## Debug Mode Handling + +### Development vs Production + +Use debug mode information to enable development features: + +```go +func setupApplicationMode(app *application.App) { + envInfo := app.Env.Info() + + if envInfo.Debug { + app.Logger.Info("Running in debug mode") + setupDevelopmentFeatures(app) + } else { + app.Logger.Info("Running in production mode") + setupProductionFeatures(app) + } +} + +func setupDevelopmentFeatures(app *application.App) { + // Enable development-only features + menu := app.Menu.New() + + // Add development menu + devMenu := menu.AddSubmenu("Development") + devMenu.Add("Reload Application").OnClick(func(ctx *application.Context) { + // Reload the application + window := app.Window.Current() + if window != nil { + window.Reload() + } + }) + + devMenu.Add("Open DevTools").OnClick(func(ctx *application.Context) { + window := app.Window.Current() + if window != nil { + window.OpenDevTools() + } + }) + + devMenu.Add("Show Environment").OnClick(func(ctx *application.Context) { + showEnvironmentDialog(app) + }) +} + +func setupProductionFeatures(app *application.App) { + // Production-only features + // Disable debug logging, enable analytics, etc. +} +``` + +## Environment Information Dialog + +### Display System Information + +Create a dialog showing environment information: + +```go +func showEnvironmentDialog(app *application.App) { + envInfo := app.Env.Info() + + details := fmt.Sprintf(`Environment Information: + +Operating System: %s +Architecture: %s +Debug Mode: %t + +Dark Mode: %t + +Platform Information:`, + envInfo.OS, + envInfo.Arch, + envInfo.Debug, + app.Env.IsDarkMode()) + + // Add platform-specific details + for key, value := range envInfo.PlatformInfo { + details += fmt.Sprintf("\n%s: %v", key, value) + } + + if envInfo.OSInfo != nil { + details += fmt.Sprintf("\n\nOS Details:\nName: %s\nVersion: %s", + envInfo.OSInfo.Name, + envInfo.OSInfo.Version) + } + + dialog := app.Dialog.Info() + dialog.SetTitle("Environment Information") + dialog.SetMessage(details) + dialog.Show() +} +``` + +## Platform Considerations + + + + + On macOS: + + - Dark mode detection uses system appearance settings + - File manager operations use Finder + - Platform info includes macOS version details + - Architecture may be "arm64" on Apple Silicon Macs + + ```go + if envInfo.OS == "darwin" { + // macOS-specific handling + if envInfo.Arch == "arm64" { + app.Logger.Info("Running on Apple Silicon") + } + } + ``` + + + + + + On Windows: + + - Dark mode detection uses Windows theme settings + - File manager operations use Windows Explorer + - Platform info includes Windows version details + - May include additional Windows-specific information + + ```go + if envInfo.OS == "windows" { + // Windows-specific handling + for key, value := range envInfo.PlatformInfo { + if key == "windows_version" { + app.Logger.Info("Windows version", "version", value) + } + } + } + ``` + + + + + + On Linux: + + - Dark mode detection varies by desktop environment + - File manager operations use system default file manager + - Platform info includes distribution details + - Behavior may vary between different Linux distributions + + ```go + if envInfo.OS == "linux" { + // Linux-specific handling + if distro, ok := envInfo.PlatformInfo["distribution"]; ok { + app.Logger.Info("Linux distribution", "distro", distro) + } + } + ``` + + + + +## Best Practices + +1. **Cache Environment Information**: Environment info rarely changes during runtime: + ```go + type App struct { + envInfo *application.EnvironmentInfo + } + + func (a *App) getEnvInfo() application.EnvironmentInfo { + if a.envInfo == nil { + info := a.app.Env.Info() + a.envInfo = &info + } + return *a.envInfo + } + ``` + +2. **Handle Theme Changes**: Listen for system theme changes: + ```go + app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(event *application.ApplicationEvent) { + updateTheme(app.Env.IsDarkMode()) + }) + ``` + +3. **Graceful File Manager Failures**: Always handle file manager errors: + ```go + func openFileManagerSafely(app *application.App, path string) { + err := app.Env.OpenFileManager(path, false) + if err != nil { + // Provide fallback or user notification + app.Logger.Warn("Could not open file manager", "path", path) + } + } + ``` + +4. **Platform-Specific Features**: Use environment info to enable platform features: + ```go + envInfo := app.Env.Info() + if envInfo.OS == "darwin" { + // Enable macOS-specific features + } + ``` + +## Complete Example + +Here's a complete example demonstrating environment management: + +```go +package main + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/events" +) + +func main() { + app := application.New(application.Options{ + Name: "Environment Demo", + }) + + // Setup application based on environment + setupForEnvironment(app) + + // Monitor theme changes + monitorThemeChanges(app) + + // Create menu with environment features + setupEnvironmentMenu(app) + + // Create main window + window := app.Window.New() + window.SetTitle("Environment Demo") + + err := app.Run() + if err != nil { + panic(err) + } +} + +func setupForEnvironment(app *application.App) { + envInfo := app.Env.Info() + + app.Logger.Info("Application environment", + "os", envInfo.OS, + "arch", envInfo.Arch, + "debug", envInfo.Debug, + "darkMode", app.Env.IsDarkMode(), + ) + + // Configure for platform + switch envInfo.OS { + case "darwin": + app.Logger.Info("Configuring for macOS") + // macOS-specific setup + case "windows": + app.Logger.Info("Configuring for Windows") + // Windows-specific setup + case "linux": + app.Logger.Info("Configuring for Linux") + // Linux-specific setup + } + + // Apply initial theme + if app.Env.IsDarkMode() { + applyDarkTheme(app) + } else { + applyLightTheme(app) + } +} + +func monitorThemeChanges(app *application.App) { + app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(event *application.ApplicationEvent) { + if app.Env.IsDarkMode() { + app.Logger.Info("System switched to dark mode") + applyDarkTheme(app) + } else { + app.Logger.Info("System switched to light mode") + applyLightTheme(app) + } + }) +} + +func setupEnvironmentMenu(app *application.App) { + menu := app.Menu.New() + + // Add platform-specific app menu + if runtime.GOOS == "darwin" { + menu.AddRole(application.AppMenu) + } + + // Tools menu + toolsMenu := menu.AddSubmenu("Tools") + + toolsMenu.Add("Show Environment Info").OnClick(func(ctx *application.Context) { + showEnvironmentInfo(app) + }) + + toolsMenu.Add("Open Downloads Folder").OnClick(func(ctx *application.Context) { + openDownloadsFolder(app) + }) + + toolsMenu.Add("Toggle Theme").OnClick(func(ctx *application.Context) { + // This would typically be handled by the system + // but shown here for demonstration + toggleTheme(app) + }) + + app.Menu.Set(menu) +} + +func showEnvironmentInfo(app *application.App) { + envInfo := app.Env.Info() + + message := fmt.Sprintf(`Environment Information: + +Operating System: %s +Architecture: %s +Debug Mode: %t +Dark Mode: %t + +Platform Details:`, + envInfo.OS, + envInfo.Arch, + envInfo.Debug, + app.Env.IsDarkMode()) + + for key, value := range envInfo.PlatformInfo { + message += fmt.Sprintf("\n%s: %v", key, value) + } + + if envInfo.OSInfo != nil { + message += fmt.Sprintf("\n\nOS Information:\nName: %s\nVersion: %s", + envInfo.OSInfo.Name, + envInfo.OSInfo.Version) + } + + dialog := app.Dialog.Info() + dialog.SetTitle("Environment Information") + dialog.SetMessage(message) + dialog.Show() +} + +func openDownloadsFolder(app *application.App) { + homeDir, err := os.UserHomeDir() + if err != nil { + app.Logger.Error("Could not get home directory", "error", err) + return + } + + downloadsDir := filepath.Join(homeDir, "Downloads") + err = app.Env.OpenFileManager(downloadsDir, false) + if err != nil { + app.Logger.Error("Could not open Downloads folder", "error", err) + + app.Dialog.Error(). + SetTitle("File Manager Error"). + SetMessage("Could not open Downloads folder"). + Show() + } +} + +func applyDarkTheme(app *application.App) { + app.Logger.Info("Applying dark theme") + // Emit theme change to frontend + app.Event.Emit("theme:apply", "dark") +} + +func applyLightTheme(app *application.App) { + app.Logger.Info("Applying light theme") + // Emit theme change to frontend + app.Event.Emit("theme:apply", "light") +} + +func toggleTheme(app *application.App) { + // This is just for demonstration + // Real theme changes should come from the system + currentlyDark := app.Env.IsDarkMode() + if currentlyDark { + applyLightTheme(app) + } else { + applyDarkTheme(app) + } +} +``` + +:::tip[Pro Tip] +Use environment information to provide platform-appropriate user experiences. For example, use Command key shortcuts on macOS and Control key shortcuts on Windows/Linux. +::: + +:::danger[Warning] +Environment information is generally stable during application runtime, but theme preferences can change. Always listen for theme change events to keep your UI synchronized. +::: \ No newline at end of file diff --git a/docs/src/content/docs/features/events/system.mdx b/docs/src/content/docs/features/events/system.mdx new file mode 100644 index 000000000..678abff2f --- /dev/null +++ b/docs/src/content/docs/features/events/system.mdx @@ -0,0 +1,591 @@ +--- +title: Event System +description: Communicate between components with the event system +sidebar: + order: 1 +--- + +import { Tabs, TabItem, Card, CardGrid } from "@astrojs/starlight/components"; + +## Event System + +Wails provides a **unified event system** for pub/sub communication. Emit events from anywhere, listen from anywhere—Go to JavaScript, JavaScript to Go, window to window—enabling decoupled architecture with typed events and lifecycle hooks. + +## Quick Start + +**Go (emit):** + +```go +app.Event.Emit("user-logged-in", map[string]interface{}{ + "userId": 123, + "name": "Alice", +}) +``` + +**JavaScript (listen):** + +```javascript +import { Events } from '@wailsio/runtime' + +Events.On("user-logged-in", (event) => { + console.log(`User ${event.data.name} logged in`) +}) +``` + +**That's it!** Cross-language pub/sub. + +## Event Types + +### Custom Events + +Your application-specific events: + +```go +// Emit from Go +app.Event.Emit("order-created", order) +app.Event.Emit("payment-processed", payment) +app.Event.Emit("notification", message) +``` + +```javascript +// Listen in JavaScript +Events.On("order-created", handleOrder) +Events.On("payment-processed", handlePayment) +Events.On("notification", showNotification) +``` + +### System Events + +Built-in OS and application events: + +```go +import "github.com/wailsapp/wails/v3/pkg/events" + +// Theme changes +app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.ApplicationEvent) { + if e.Context().IsDarkMode() { + app.Logger.Info("Dark mode enabled") + } +}) + +// Application lifecycle +app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(e *application.ApplicationEvent) { + app.Logger.Info("Application started") +}) +``` + +### Window Events + +Window-specific events: + +```go +window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) { + app.Logger.Info("Window focused") +}) + +window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) { + app.Logger.Info("Window closing") +}) +``` + +## Emitting Events + +### From Go + +**Basic emit:** + +```go +app.Event.Emit("event-name", data) +``` + +**With different data types:** + +```go +// String +app.Event.Emit("message", "Hello") + +// Number +app.Event.Emit("count", 42) + +// Struct +app.Event.Emit("user", User{ID: 1, Name: "Alice"}) + +// Map +app.Event.Emit("config", map[string]interface{}{ + "theme": "dark", + "fontSize": 14, +}) + +// Array +app.Event.Emit("items", []string{"a", "b", "c"}) +``` + +**To specific window:** + +```go +window.EmitEvent("window-specific-event", data) +``` + +### From JavaScript + +```javascript +import { Emit } from '@wailsio/runtime' + +// Emit to Go +Emit("button-clicked", { buttonId: "submit" }) + +// Emit to all windows +Emit("broadcast-message", "Hello everyone") +``` + +## Listening to Events + +### In Go + +**Application events:** + +```go +app.Event.On("custom-event", func(e *application.CustomEvent) { + data := e.Data + // Handle event +}) +``` + +**With type assertion:** + +```go +app.Event.On("user-updated", func(e *application.CustomEvent) { + user := e.Data.(User) + app.Logger.Info("User updated", "name", user.Name) +}) +``` + +**Multiple handlers:** + +```go +// All handlers will be called +app.Event.On("order-created", logOrder) +app.Event.On("order-created", sendEmail) +app.Event.On("order-created", updateInventory) +``` + +### In JavaScript + +**Basic listener:** + +```javascript +import { Events } from '@wailsio/runtime' + +Events.On("event-name", (event) => { + console.log("Event received:", event.data) +}) +``` + +**With cleanup:** + +```javascript +const unsubscribe = Events.On("event-name", handleEvent) + +// Later, stop listening +unsubscribe() +``` + +**Multiple handlers:** + +```javascript +Events.On("data-updated", updateUI) +Events.On("data-updated", saveToCache) +Events.On("data-updated", logChange) +``` + +**One Time handlers:** + +```javascript +Events.Once("data-updated", updateVariable) +``` + +## System Events + +### Application Events + +**Common events (cross-platform):** + +```go +import "github.com/wailsapp/wails/v3/pkg/events" + +// Application started +app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(e *application.ApplicationEvent) { + app.Logger.Info("App started") +}) + +// Theme changed +app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.ApplicationEvent) { + isDark := e.Context().IsDarkMode() + app.Event.Emit("theme-changed", isDark) +}) + +// File opened +app.Event.OnApplicationEvent(events.Common.ApplicationOpenedWithFile, func(e *application.ApplicationEvent) { + filePath := e.Context().OpenedFile() + openFile(filePath) +}) +``` + +**Platform-specific events:** + + + + ```go + // Application became active + app.Event.OnApplicationEvent(events.Mac.ApplicationDidBecomeActive, func(e *application.ApplicationEvent) { + app.Logger.Info("App became active") + }) + + // Application will terminate + app.Event.OnApplicationEvent(events.Mac.ApplicationWillTerminate, func(e *application.ApplicationEvent) { + cleanup() + }) + ``` + + + + ```go + // Power status changed + app.Event.OnApplicationEvent(events.Windows.APMPowerStatusChange, func(e *application.ApplicationEvent) { + app.Logger.Info("Power status changed") + }) + + // System suspending + app.Event.OnApplicationEvent(events.Windows.APMSuspend, func(e *application.ApplicationEvent) { + saveState() + }) + ``` + + + + ```go + // Application startup + app.Event.OnApplicationEvent(events.Linux.ApplicationStartup, func(e *application.ApplicationEvent) { + app.Logger.Info("App starting") + }) + + // Theme changed + app.Event.OnApplicationEvent(events.Linux.SystemThemeChanged, func(e *application.ApplicationEvent) { + updateTheme() + }) + ``` + + + +### Window Events + +**Common window events:** + +```go +// Window focus +window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) { + app.Logger.Info("Window focused") +}) + +// Window blur +window.OnWindowEvent(events.Common.WindowBlur, func(e *application.WindowEvent) { + app.Logger.Info("Window blurred") +}) + +// Window closing +window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) { + if hasUnsavedChanges() { + e.Cancel() // Prevent close + } +}) + +// Window closed +window.OnWindowEvent(events.Common.WindowClosed, func(e *application.WindowEvent) { + cleanup() +}) +``` + +## Event Hooks + +Hooks run **before** standard listeners and can **cancel** events: + +```go +// Hook - runs first, can cancel +window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { + if hasUnsavedChanges() { + result := showConfirmdialog("Unsaved changes. Close anyway?") + if result != "yes" { + e.Cancel() // Prevent window close + } + } +}) + +// Standard listener - runs after hooks +window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) { + app.Logger.Info("Window closing") +}) +``` + +**Key differences:** + +| Feature | Hooks | Standard Listeners | +|---------|-------|-------------------| +| Execution order | First, in registration order | After hooks, no guaranteed order | +| Blocking | Synchronous, blocks next hook | Asynchronous, non-blocking | +| Can cancel | Yes | No (already propagated) | +| Use case | Control flow, validation | Logging, side effects | + +## Event Patterns + +### Pub/Sub Pattern + +```go +// Publisher (service) +type OrderService struct { + app *application.Application +} + +func (o *OrderService) CreateOrder(items []Item) (*Order, error) { + order := &Order{Items: items} + + if err := o.saveOrder(order); err != nil { + return nil, err + } + + // Publish event + o.app.Event.Emit("order-created", order) + + return order, nil +} + +// Subscribers +app.Event.On("order-created", func(e *application.CustomEvent) { + order := e.Data.(*Order) + sendConfirmationEmail(order) +}) + +app.Event.On("order-created", func(e *application.CustomEvent) { + order := e.Data.(*Order) + updateInventory(order) +}) + +app.Event.On("order-created", func(e *application.CustomEvent) { + order := e.Data.(*Order) + logOrder(order) +}) +``` + +### Request/Response Pattern + +```go +// Frontend requests data +Emit("get-user-data", { userId: 123 }) + +// Backend responds +app.Event.On("get-user-data", func(e *application.CustomEvent) { + data := e.Data.(map[string]interface{}) + userId := int(data["userId"].(float64)) + + user := getUserFromDB(userId) + + // Send response + app.Event.Emit("user-data-response", user) +}) +``` + +```javascript +// Frontend receives response +Events.On("user-data-response", (event) => { + const user = event.data + displayUser(user) +}) +``` + +**Note:** For request/response, **bindings are better**. Use events for notifications. + +### Broadcast Pattern + +```go +// Broadcast to all windows +app.Event.Emit("global-notification", "System update available") + +// Each window handles it +Events.On("global-notification", (event) => { + const message = event.data + showNotification(message) +}) +``` + +### Event Aggregation + +```go +type EventAggregator struct { + events []Event + mu sync.Mutex +} + +func (ea *EventAggregator) Add(event Event) { + ea.mu.Lock() + defer ea.mu.Unlock() + + ea.events = append(ea.events, event) + + // Emit batch every 100 events + if len(ea.events) >= 100 { + app.Event.Emit("event-batch", ea.events) + ea.events = nil + } +} +``` + +## Complete Example + +**Go:** + +```go +package main + +import ( + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/events" +) + +type NotificationService struct { + app *application.Application +} + +func (n *NotificationService) Notify(message string) { + // Emit to all windows + n.app.Event.Emit("notification", map[string]interface{}{ + "message": message, + "timestamp": time.Now(), + }) +} + +func main() { + app := application.New(application.Options{ + Name: "Event Demo", + }) + + notifService := &NotificationService{app: app} + + // System events + app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.ApplicationEvent) { + isDark := e.Context().IsDarkMode() + app.Event.Emit("theme-changed", isDark) + }) + + // Custom events from frontend + app.Event.On("user-action", func(e *application.CustomEvent) { + data := e.Data.(map[string]interface{}) + action := data["action"].(string) + + app.Logger.Info("User action", "action", action) + + // Respond + notifService.Notify("Action completed: " + action) + }) + + // Window events + window := app.Window.New() + + window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) { + app.Event.Emit("window-focused", window.Name()) + }) + + window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { + // Confirm before close + app.Event.Emit("confirm-close", nil) + e.Cancel() // Wait for confirmation + }) + + app.Run() +} +``` + +**JavaScript:** + +```javascript +import { Events, Emit } from '@wailsio/runtime' + +// Listen for notifications +Events.On("notification", (event) => { + showNotification(event.data.message) +}) + +// Listen for theme changes +Events.On("theme-changed", (event) => { + const isDark = event.data + document.body.classList.toggle('dark', isDark) +}) + +// Listen for window focus +Events.On("window-focused", (event) => { + const windowName = event.data + console.log(`Window ${windowName} focused`) +}) + +// Handle close confirmation +Events.On("confirm-close", (event) => { + if (confirm("Close window?")) { + Emit("close-confirmed", true) + } +}) + +// Emit user actions +document.getElementById('button').addEventListener('click', () => { + Emit("user-action", { action: "button-clicked" }) +}) +``` + +## Best Practices + +### ✅ Do + +- **Use events for notifications** - One-way communication +- **Use bindings for requests** - Two-way communication +- **Keep event names consistent** - Use kebab-case +- **Document event data** - What fields are included? +- **Unsubscribe when done** - Prevent memory leaks +- **Use hooks for validation** - Control event flow + +### ❌ Don't + +- **Don't use events for RPC** - Use bindings instead +- **Don't emit too frequently** - Batch if needed +- **Don't block in handlers** - Keep them fast +- **Don't forget to unsubscribe** - Memory leaks +- **Don't use events for large data** - Use bindings +- **Don't create event loops** - A emits B, B emits A + +## Next Steps + + + + Create your own event types. + + [Learn More →](/features/events/custom) + + + + Common event patterns and best practices. + + [Learn More →](/features/events/patterns) + + + + Use bindings for request/response. + + [Learn More →](/features/bindings/methods) + + + + Handle window lifecycle events. + + [Learn More →](/features/windows/events) + + + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [event examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/events). diff --git a/docs/src/content/docs/features/keyboard/shortcuts.mdx b/docs/src/content/docs/features/keyboard/shortcuts.mdx new file mode 100644 index 000000000..ba83603f8 --- /dev/null +++ b/docs/src/content/docs/features/keyboard/shortcuts.mdx @@ -0,0 +1,438 @@ +--- +title: Keyboard Shortcuts +description: Register global keyboard shortcuts for quick access to functionality +sidebar: + order: 1 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +Wails provides a powerful key binding system that allows you to register global keyboard shortcuts that work across all windows in your application. This enables users to quickly access functionality without navigating through menus. + +## Accessing the Key Binding Manager + +The key binding manager is accessed through the `KeyBindings` property on your application instance: + +```go +app := application.New(application.Options{ + Name: "Keyboard Shortcuts Demo", +}) + +// Access the key binding manager +keyBindings := app.KeyBinding +``` + +## Adding Key Bindings + +### Basic Key Binding + +Register a simple keyboard shortcut: + +```go +app.KeyBinding.Add("Ctrl+S", func(window application.Window) { + // Handle save action + app.Logger.Info("Save shortcut triggered") + // Perform save operation... +}) +``` + +### Multiple Key Bindings + +Register multiple shortcuts for common operations: + +```go +// File operations +app.KeyBinding.Add("Ctrl+N", func(window application.Window) { + // New file + window.EmitEvent("file:new", nil) +}) + +app.KeyBinding.Add("Ctrl+O", func(window application.Window) { + // Open file + dialog := app.Dialog.OpenFile() + if file, err := dialog.PromptForSingleSelection(); err == nil { + window.EmitEvent("file:open", file) + } +}) + +app.KeyBinding.Add("Ctrl+S", func(window application.Window) { + // Save file + window.EmitEvent("file:save", nil) +}) + +// Edit operations +app.KeyBinding.Add("Ctrl+Z", func(window application.Window) { + // Undo + window.EmitEvent("edit:undo", nil) +}) + +app.KeyBinding.Add("Ctrl+Y", func(window application.Window) { + // Redo (Windows/Linux) + window.EmitEvent("edit:redo", nil) +}) + +app.KeyBinding.Add("Cmd+Shift+Z", func(window application.Window) { + // Redo (macOS) + window.EmitEvent("edit:redo", nil) +}) +``` + +## Key Binding Accelerators + +### Accelerator Format + +Key bindings use a standard accelerator format with modifiers and keys: + +```go +// Modifier keys +"Ctrl+S" // Control + S +"Cmd+S" // Command + S (macOS) +"Alt+F4" // Alt + F4 +"Shift+Ctrl+Z" // Shift + Control + Z + +// Function keys +"F1" // F1 key +"Ctrl+F5" // Control + F5 + +// Special keys +"Escape" // Escape key +"Enter" // Enter key +"Space" // Spacebar +"Tab" // Tab key +"Backspace" // Backspace key +"Delete" // Delete key + +// Arrow keys +"Up" // Up arrow +"Down" // Down arrow +"Left" // Left arrow +"Right" // Right arrow +``` + +### Platform-Specific Accelerators + +Handle platform differences for common shortcuts: + +```go +import "runtime" + +// Cross-platform save shortcut +if runtime.GOOS == "darwin" { + app.KeyBinding.Add("Cmd+S", saveHandler) +} else { + app.KeyBinding.Add("Ctrl+S", saveHandler) +} + +// Or register both +app.KeyBinding.Add("Ctrl+S", saveHandler) +app.KeyBinding.Add("Cmd+S", saveHandler) +``` + +## Managing Key Bindings + +### Removing Key Bindings + +Remove key bindings when they're no longer needed: + +```go +// Remove a specific key binding +app.KeyBinding.Remove("Ctrl+S") + +// Example: Temporary key binding for a modal +app.KeyBinding.Add("Escape", func(window application.Window) { + // Close modal + window.EmitEvent("modal:close", nil) + // Remove this temporary binding + app.KeyBinding.Remove("Escape") +}) +``` + +### Getting All Key Bindings + +Retrieve all registered key bindings: + +```go +allBindings := app.KeyBinding.GetAll() +for _, binding := range allBindings { + app.Logger.Info("Key binding", "accelerator", binding.Accelerator) +} +``` + +## Advanced Usage + +### Context-Aware Key Bindings + +Make key bindings context-aware by checking application state: + +```go +app.KeyBinding.Add("Ctrl+S", func(window application.Window) { + // Check current application state + if isEditMode() { + // Save document + saveDocument() + } else if isInSettings() { + // Save settings + saveSettings() + } else { + app.Logger.Info("Save not available in current context") + } +}) +``` + +### Window-Specific Actions + +Key bindings receive the active window, allowing window-specific behavior: + +```go +app.KeyBinding.Add("F11", func(window application.Window) { + // Toggle fullscreen for the active window + if window.Fullscreen() { + window.SetFullscreen(false) + } else { + window.SetFullscreen(true) + } +}) + +app.KeyBinding.Add("Ctrl+W", func(window application.Window) { + // Close the active window + window.Close() +}) +``` + +### Dynamic Key Binding Management + +Dynamically add and remove key bindings based on application state: + +```go +func enableEditMode() { + // Add edit-specific key bindings + app.KeyBinding.Add("Ctrl+B", func(window application.Window) { + window.EmitEvent("format:bold", nil) + }) + + app.KeyBinding.Add("Ctrl+I", func(window application.Window) { + window.EmitEvent("format:italic", nil) + }) + + app.KeyBinding.Add("Ctrl+U", func(window application.Window) { + window.EmitEvent("format:underline", nil) + }) +} + +func disableEditMode() { + // Remove edit-specific key bindings + app.KeyBinding.Remove("Ctrl+B") + app.KeyBinding.Remove("Ctrl+I") + app.KeyBinding.Remove("Ctrl+U") +} +``` + +## Platform Considerations + + + + + On macOS: + + - Use `Cmd` instead of `Ctrl` for standard shortcuts + - `Cmd+Q` is typically reserved for quitting the application + - `Cmd+H` hides the application + - `Cmd+M` minimizes windows + - Consider standard macOS keyboard shortcuts + + Common macOS patterns: + ```go + app.KeyBinding.Add("Cmd+N", newFileHandler) // New + app.KeyBinding.Add("Cmd+O", openFileHandler) // Open + app.KeyBinding.Add("Cmd+S", saveFileHandler) // Save + app.KeyBinding.Add("Cmd+Z", undoHandler) // Undo + app.KeyBinding.Add("Cmd+Shift+Z", redoHandler) // Redo + app.KeyBinding.Add("Cmd+C", copyHandler) // Copy + app.KeyBinding.Add("Cmd+V", pasteHandler) // Paste + ``` + + + + + + On Windows: + + - Use `Ctrl` for standard shortcuts + - `Alt+F4` closes applications + - `F1` typically opens help + - Consider Windows keyboard conventions + + Common Windows patterns: + ```go + app.KeyBinding.Add("Ctrl+N", newFileHandler) // New + app.KeyBinding.Add("Ctrl+O", openFileHandler) // Open + app.KeyBinding.Add("Ctrl+S", saveFileHandler) // Save + app.KeyBinding.Add("Ctrl+Z", undoHandler) // Undo + app.KeyBinding.Add("Ctrl+Y", redoHandler) // Redo + app.KeyBinding.Add("Ctrl+C", copyHandler) // Copy + app.KeyBinding.Add("Ctrl+V", pasteHandler) // Paste + app.KeyBinding.Add("F1", helpHandler) // Help + ``` + + + + + + On Linux: + + - Generally follows Windows conventions with `Ctrl` + - May vary by desktop environment + - Consider GNOME/KDE standard shortcuts + - Some desktop environments reserve certain shortcuts + + Common Linux patterns: + ```go + app.KeyBinding.Add("Ctrl+N", newFileHandler) // New + app.KeyBinding.Add("Ctrl+O", openFileHandler) // Open + app.KeyBinding.Add("Ctrl+S", saveFileHandler) // Save + app.KeyBinding.Add("Ctrl+Z", undoHandler) // Undo + app.KeyBinding.Add("Ctrl+Shift+Z", redoHandler) // Redo + app.KeyBinding.Add("Ctrl+C", copyHandler) // Copy + app.KeyBinding.Add("Ctrl+V", pasteHandler) // Paste + ``` + + + + +## Best Practices + +1. **Use Standard Shortcuts**: Follow platform conventions for common operations: + ```go + // Cross-platform save + if runtime.GOOS == "darwin" { + app.KeyBinding.Add("Cmd+S", saveHandler) + } else { + app.KeyBinding.Add("Ctrl+S", saveHandler) + } + ``` + +2. **Provide Visual Feedback**: Let users know when shortcuts are triggered: + ```go + app.KeyBinding.Add("Ctrl+S", func(window application.Window) { + saveDocument() + // Show brief notification + window.EmitEvent("notification:show", "Document saved") + }) + ``` + +3. **Handle Conflicts**: Be careful not to override important system shortcuts: + ```go + // Avoid overriding system shortcuts like: + // Ctrl+Alt+Del (Windows) + // Cmd+Space (macOS Spotlight) + // Alt+Tab (Window switching) + ``` + +4. **Document Shortcuts**: Provide help or documentation for available shortcuts: + ```go + app.KeyBinding.Add("F1", func(window application.Window) { + // Show help dialog with available shortcuts + showKeyboardShortcutsHelp() + }) + ``` + +5. **Clean Up**: Remove temporary key bindings when they're no longer needed: + ```go + func enterEditMode() { + app.KeyBinding.Add("Escape", exitEditModeHandler) + } + + func exitEditModeHandler(window application.Window) { + exitEditMode() + app.KeyBinding.Remove("Escape") // Clean up temporary binding + } + ``` + +## Complete Example + +Here's a complete example of a text editor with keyboard shortcuts: + +```go +package main + +import ( + "runtime" + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Name: "Text Editor with Shortcuts", + }) + + // File operations + if runtime.GOOS == "darwin" { + app.KeyBinding.Add("Cmd+N", func(window application.Window) { + window.EmitEvent("file:new", nil) + }) + app.KeyBinding.Add("Cmd+O", func(window application.Window) { + openFile(app, window) + }) + app.KeyBinding.Add("Cmd+S", func(window application.Window) { + window.EmitEvent("file:save", nil) + }) + } else { + app.KeyBinding.Add("Ctrl+N", func(window application.Window) { + window.EmitEvent("file:new", nil) + }) + app.KeyBinding.Add("Ctrl+O", func(window application.Window) { + openFile(app, window) + }) + app.KeyBinding.Add("Ctrl+S", func(window application.Window) { + window.EmitEvent("file:save", nil) + }) + } + + // View operations + app.KeyBinding.Add("F11", func(window application.Window) { + window.SetFullscreen(!window.Fullscreen()) + }) + + app.KeyBinding.Add("F1", func(window application.Window) { + showKeyboardShortcuts(window) + }) + + // Create main window + window := app.Window.New() + window.SetTitle("Text Editor") + + err := app.Run() + if err != nil { + panic(err) + } +} + +func openFile(app *application.App, window application.Window) { + dialog := app.Dialog.OpenFile() + dialog.AddFilter("Text Files", "*.txt;*.md") + + if file, err := dialog.PromptForSingleSelection(); err == nil { + window.EmitEvent("file:open", file) + } +} + +func showKeyboardShortcuts(window application.Window) { + shortcuts := ` +Keyboard Shortcuts: +- Ctrl/Cmd+N: New file +- Ctrl/Cmd+O: Open file +- Ctrl/Cmd+S: Save file +- F11: Toggle fullscreen +- F1: Show this help +` + window.EmitEvent("help:show", shortcuts) +} +``` + +:::tip[Pro Tip] +Test your key bindings on all target platforms to ensure they work correctly and don't conflict with system shortcuts. +::: + +:::danger[Warning] +Be careful not to override critical system shortcuts. Some key combinations are reserved by the operating system and cannot be captured by applications. +::: diff --git a/docs/src/content/docs/features/menus/application.mdx b/docs/src/content/docs/features/menus/application.mdx new file mode 100644 index 000000000..26b7e71a0 --- /dev/null +++ b/docs/src/content/docs/features/menus/application.mdx @@ -0,0 +1,683 @@ +--- +title: Application Menus +description: Create native menu bars for your desktop application +sidebar: + order: 1 +--- + +import { Card, CardGrid, Tabs, TabItem } from "@astrojs/starlight/components"; + +## The Problem + +Professional desktop applications need menu bars—File, Edit, View, Help. But menus work differently on each platform: +- **macOS**: Global menu bar at top of screen +- **Windows**: Menu bar in window title bar +- **Linux**: Varies by desktop environment + +Building platform-appropriate menus manually is tedious and error-prone. + +## The Wails Solution + +Wails provides a **unified API** that creates platform-native menus automatically. Write once, get native behaviour on all platforms. + +{/* VISUAL PLACEHOLDER: Menu Bar Comparison +Description: Three screenshots side-by-side showing the same Wails menu on: +1. macOS - Global menu bar at top of screen with app name +2. Windows - Menu bar in window title bar +3. Linux (GNOME) - Menu bar in window +All showing identical menu structure: File, Edit, View, Tools, Help +Style: Clean screenshots with subtle borders, labels indicating platform +*/} + +## Quick Start + +```go +package main + +import ( + "runtime" + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Name: "My App", + }) + + // Create menu + menu := app.NewMenu() + + // Add standard menus (platform-appropriate) + if runtime.GOOS == "darwin" { + menu.AddRole(application.AppMenu) // macOS only + } + menu.AddRole(application.FileMenu) + menu.AddRole(application.EditMenu) + menu.AddRole(application.WindowMenu) + menu.AddRole(application.HelpMenu) + + // Set the application menu + app.Menu.Set(menu) + + // Create window with UseApplicationMenu to inherit the menu on Windows/Linux + app.Window.NewWithOptions(application.WebviewWindowOptions{ + UseApplicationMenu: true, + }) + + app.Run() +} +``` + +**That's it!** You now have platform-native menus with standard items. The `UseApplicationMenu` option ensures Windows and Linux windows display the menu without additional code. + +## Creating Menus + +### Basic Menu Creation + +```go +// Create a new menu +menu := app.NewMenu() + +// Add a top-level menu +fileMenu := menu.AddSubmenu("File") + +// Add menu items +fileMenu.Add("New").OnClick(func(ctx *application.Context) { + // Handle New +}) + +fileMenu.Add("Open").OnClick(func(ctx *application.Context) { + // Handle Open +}) + +fileMenu.AddSeparator() + +fileMenu.Add("Quit").OnClick(func(ctx *application.Context) { + app.Quit() +}) +``` + +### Setting the Menu + +**Recommended approach** — Use `UseApplicationMenu` for cross-platform consistency: + +```go +// Set the application menu once +app.Menu.Set(menu) + +// Create windows that inherit the menu on Windows/Linux +app.Window.NewWithOptions(application.WebviewWindowOptions{ + UseApplicationMenu: true, // Window uses the app menu +}) +``` + +This approach: +- On **macOS**: The menu appears at the top of screen (standard behaviour) +- On **Windows/Linux**: Each window with `UseApplicationMenu: true` displays the app menu + +**Platform-specific details:** + + + + **Global menu bar** (one per application): + + ```go + app.Menu.Set(menu) + ``` + + The menu appears at the top of the screen and persists even when all windows are closed. The `UseApplicationMenu` option has no effect on macOS since all apps use the global menu. + + + + **Per-window menu bar**: + + ```go + // Option 1: Use application menu (recommended) + app.Menu.Set(menu) + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + UseApplicationMenu: true, + }) + + // Option 2: Set menu directly on window + window.SetMenu(menu) + ``` + + Each window can have its own menu, or inherit the application menu. The menu appears in the window's title bar. + + + + **Per-window menu bar** (usually): + + ```go + // Option 1: Use application menu (recommended) + app.Menu.Set(menu) + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + UseApplicationMenu: true, + }) + + // Option 2: Set menu directly on window + window.SetMenu(menu) + ``` + + Behaviour varies by desktop environment. Some (like Unity) support global menus. + + + +:::tip[Simplify Cross-Platform Menus] +Using `UseApplicationMenu: true` eliminates the need for platform-specific code like: +```go +// Old approach - no longer needed +if runtime.GOOS == "darwin" { + app.Menu.Set(menu) +} else { + window.SetMenu(menu) +} +``` +::: + +**Per-window custom menus:** + +If a window needs a different menu than the application menu, set it directly: + +```go +window.SetMenu(customMenu) // Overrides UseApplicationMenu +``` + +## Menu Roles + +Wails provides **predefined menu roles** that create platform-appropriate menu structures automatically. + +### Available Roles + +| Role | Description | Platform Notes | +|------|-------------|----------------| +| `AppMenu` | Application menu with About, Preferences, Quit | **macOS only** | +| `FileMenu` | File operations (New, Open, Save, etc.) | All platforms | +| `EditMenu` | Text editing (Undo, Redo, Cut, Copy, Paste) | All platforms | +| `WindowMenu` | Window management (Minimise, Zoom, etc.) | All platforms | +| `HelpMenu` | Help and information | All platforms | + +### Using Roles + +```go +menu := app.NewMenu() + +// macOS: Add application menu +if runtime.GOOS == "darwin" { + menu.AddRole(application.AppMenu) +} + +// All platforms: Add standard menus +menu.AddRole(application.FileMenu) +menu.AddRole(application.EditMenu) +menu.AddRole(application.WindowMenu) +menu.AddRole(application.HelpMenu) +``` + +**What you get:** + + + + **AppMenu** (with app name): + - About [App Name] + - Preferences... (⌘,) + - --- + - Services + - --- + - Hide [App Name] (⌘H) + - Hide Others (⌥⌘H) + - Show All + - --- + - Quit [App Name] (⌘Q) + + **FileMenu**: + - New (⌘N) + - Open... (⌘O) + - --- + - Close Window (⌘W) + + **EditMenu**: + - Undo (⌘Z) + - Redo (⇧⌘Z) + - --- + - Cut (⌘X) + - Copy (⌘C) + - Paste (⌘V) + - Select All (⌘A) + + **WindowMenu**: + - Minimise (⌘M) + - Zoom + - --- + - Bring All to Front + + **HelpMenu**: + - [App Name] Help + + + + **FileMenu**: + - New (Ctrl+N) + - Open... (Ctrl+O) + - --- + - Exit (Alt+F4) + + **EditMenu**: + - Undo (Ctrl+Z) + - Redo (Ctrl+Y) + - --- + - Cut (Ctrl+X) + - Copy (Ctrl+C) + - Paste (Ctrl+V) + - Select All (Ctrl+A) + + **WindowMenu**: + - Minimise + - Maximise + + **HelpMenu**: + - About [App Name] + + + + Similar to Windows, but keyboard shortcuts may vary by desktop environment. + + + +### Customising Role Menus + +Add items to role menus: + +```go +fileMenu := menu.AddRole(application.FileMenu) + +// Add custom items +fileMenu.Add("Import...").OnClick(handleImport) +fileMenu.Add("Export...").OnClick(handleExport) +``` + +## Custom Menus + +Create your own menus for application-specific features: + +```go +// Add a custom top-level menu +toolsMenu := menu.AddSubmenu("Tools") + +// Add items +toolsMenu.Add("Settings").OnClick(func(ctx *application.Context) { + showSettingsWindow() +}) + +toolsMenu.AddSeparator() + +// Add checkbox +toolsMenu.AddCheckbox("Dark Mode", false).OnClick(func(ctx *application.Context) { + isDark := ctx.ClickedMenuItem().Checked() + setTheme(isDark) +}) + +// Add radio group +toolsMenu.AddRadio("Small", true).OnClick(handleFontSize) +toolsMenu.AddRadio("Medium", false).OnClick(handleFontSize) +toolsMenu.AddRadio("Large", false).OnClick(handleFontSize) + +// Add submenu +advancedMenu := toolsMenu.AddSubmenu("Advanced") +advancedMenu.Add("Configure...").OnClick(showAdvancedSettings) +``` + +**For more menu item types**, see [Menu Reference](/features/menus/reference). + +## Dynamic Menus + +Update menus based on application state: + +### Enable/Disable Items + +```go +var saveMenuItem *application.MenuItem + +func createMenu() { + menu := app.NewMenu() + fileMenu := menu.AddSubmenu("File") + + saveMenuItem = fileMenu.Add("Save") + saveMenuItem.SetEnabled(false) // Initially disabled + saveMenuItem.OnClick(handleSave) + + app.SetMenu(menu) +} + +func onDocumentChanged() { + saveMenuItem.SetEnabled(hasUnsavedChanges()) + menu.Update() // Important! +} +``` + +:::caution[Always Call menu.Update()] +After changing menu state (enable/disable, label, checked), **always call `menu.Update()`**. This is especially critical on Windows where menus are reconstructed. + +See [Menu Reference](/features/menus/reference#enabled-state) for details. +::: + +### Change Labels + +```go +updateMenuItem := menu.Add("Check for Updates") + +updateMenuItem.OnClick(func(ctx *application.Context) { + updateMenuItem.SetLabel("Checking...") + menu.Update() + + checkForUpdates() + + updateMenuItem.SetLabel("Check for Updates") + menu.Update() +}) +``` + +### Rebuild Menus + +For major changes, rebuild the entire menu: + +```go +func rebuildFileMenu() { + menu := app.NewMenu() + fileMenu := menu.AddSubmenu("File") + + fileMenu.Add("New").OnClick(handleNew) + fileMenu.Add("Open").OnClick(handleOpen) + + // Add recent files dynamically + if hasRecentFiles() { + recentMenu := fileMenu.AddSubmenu("Open Recent") + for _, file := range getRecentFiles() { + filePath := file // Capture for closure + recentMenu.Add(filepath.Base(file)).OnClick(func(ctx *application.Context) { + openFile(filePath) + }) + } + recentMenu.AddSeparator() + recentMenu.Add("Clear Recent").OnClick(clearRecentFiles) + } + + fileMenu.AddSeparator() + fileMenu.Add("Quit").OnClick(func(ctx *application.Context) { + app.Quit() + }) + + app.SetMenu(menu) +} +``` + +## Window Control from Menus + +Menu items can control windows: + +```go +viewMenu := menu.AddSubmenu("View") + +// Toggle fullscreen +viewMenu.Add("Toggle Fullscreen").OnClick(func(ctx *application.Context) { + window := app.GetWindowByName("main") + window.SetFullscreen(!window.IsFullscreen()) +}) + +// Zoom controls +viewMenu.Add("Zoom In").SetAccelerator("CmdOrCtrl++").OnClick(func(ctx *application.Context) { + // Increase zoom +}) + +viewMenu.Add("Zoom Out").SetAccelerator("CmdOrCtrl+-").OnClick(func(ctx *application.Context) { + // Decrease zoom +}) + +viewMenu.Add("Reset Zoom").SetAccelerator("CmdOrCtrl+0").OnClick(func(ctx *application.Context) { + // Reset zoom +}) +``` + +**Get the active window:** + +```go +menuItem.OnClick(func(ctx *application.Context) { + window := application.ContextWindow(ctx) + // Use window +}) +``` + +## Platform-Specific Considerations + +### macOS + +**Menu bar behaviour:** +- Appears at **top of screen** (global) +- Persists when all windows closed +- First menu is **always the application menu** +- Use `menu.AddRole(application.AppMenu)` for standard items + +**Standard locations:** +- **About**: Application menu +- **Preferences**: Application menu (⌘,) +- **Quit**: Application menu (⌘Q) +- **Help**: Help menu + +**Example:** + +```go +if runtime.GOOS == "darwin" { + menu.AddRole(application.AppMenu) // Adds About, Preferences, Quit + + // Don't add Quit to File menu on macOS + // Don't add About to Help menu on macOS +} +``` + +### Windows + +**Menu bar behaviour:** +- Appears in **window title bar** +- Each window has its own menu +- No application menu + +**Standard locations:** +- **Exit**: File menu (Alt+F4) +- **Settings**: Tools or Edit menu +- **About**: Help menu + +**Example:** + +```go +if runtime.GOOS == "windows" { + fileMenu := menu.AddRole(application.FileMenu) + // Exit is added automatically + + helpMenu := menu.AddRole(application.HelpMenu) + // About is added automatically +} +``` + +### Linux + +**Menu bar behaviour:** +- Usually per-window (like Windows) +- Some DEs support global menus (Unity, GNOME with extension) +- Appearance varies by desktop environment + +**Best practice:** Follow Windows conventions, test on target DEs. + +## Complete Example + +Here's a production-ready menu structure: + +```go +package main + +import ( + "runtime" + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Name: "My Application", + }) + + // Create and set menu + createMenu(app) + + // Create main window with UseApplicationMenu for cross-platform menu support + app.Window.NewWithOptions(application.WebviewWindowOptions{ + UseApplicationMenu: true, + }) + + app.Run() +} + +func createMenu(app *application.Application) { + menu := app.NewMenu() + + // Platform-specific application menu (macOS only) + if runtime.GOOS == "darwin" { + menu.AddRole(application.AppMenu) + } + + // File menu + fileMenu := menu.AddRole(application.FileMenu) + fileMenu.Add("Import...").SetAccelerator("CmdOrCtrl+I").OnClick(handleImport) + fileMenu.Add("Export...").SetAccelerator("CmdOrCtrl+E").OnClick(handleExport) + + // Edit menu + menu.AddRole(application.EditMenu) + + // View menu + viewMenu := menu.AddSubmenu("View") + viewMenu.Add("Toggle Fullscreen").SetAccelerator("F11").OnClick(toggleFullscreen) + viewMenu.AddSeparator() + viewMenu.AddCheckbox("Show Sidebar", true).OnClick(toggleSidebar) + viewMenu.AddCheckbox("Show Toolbar", true).OnClick(toggleToolbar) + + // Tools menu + toolsMenu := menu.AddSubmenu("Tools") + + // Settings location varies by platform + if runtime.GOOS == "darwin" { + // On macOS, Preferences is in Application menu (added by AppMenu role) + } else { + toolsMenu.Add("Settings").SetAccelerator("CmdOrCtrl+,").OnClick(showSettings) + } + + toolsMenu.AddSeparator() + toolsMenu.AddCheckbox("Dark Mode", false).OnClick(toggleDarkMode) + + // Window menu + menu.AddRole(application.WindowMenu) + + // Help menu + helpMenu := menu.AddRole(application.HelpMenu) + helpMenu.Add("Documentation").OnClick(openDocumentation) + + // About location varies by platform + if runtime.GOOS == "darwin" { + // On macOS, About is in Application menu (added by AppMenu role) + } else { + helpMenu.AddSeparator() + helpMenu.Add("About").OnClick(showAbout) + } + + // Set the application menu + app.Menu.Set(menu) +} + +func handleImport(ctx *application.Context) { + // Implementation +} + +func handleExport(ctx *application.Context) { + // Implementation +} + +func toggleFullscreen(ctx *application.Context) { + window := application.ContextWindow(ctx) + window.SetFullscreen(!window.IsFullscreen()) +} + +func toggleSidebar(ctx *application.Context) { + // Implementation +} + +func toggleToolbar(ctx *application.Context) { + // Implementation +} + +func showSettings(ctx *application.Context) { + // Implementation +} + +func toggleDarkMode(ctx *application.Context) { + isDark := ctx.ClickedMenuItem().Checked() + // Apply theme +} + +func openDocumentation(ctx *application.Context) { + // Open browser +} + +func showAbout(ctx *application.Context) { + // Show about dialog +} +``` + +## Best Practices + +### ✅ Do + +- **Use menu roles** for standard menus (File, Edit, etc.) +- **Follow platform conventions** for menu structure +- **Add keyboard shortcuts** to common actions +- **Call menu.Update()** after changing menu state +- **Test on all platforms** - behaviour varies +- **Keep menus shallow** - 2-3 levels maximum +- **Use clear labels** - "Save Project" not "Save" + +### ❌ Don't + +- **Don't hardcode platform shortcuts** - Use `CmdOrCtrl` +- **Don't put Quit in File menu on macOS** - It's in Application menu +- **Don't put About in Help menu on macOS** - It's in Application menu +- **Don't forget menu.Update()** - Menus won't work properly +- **Don't nest too deeply** - Users get lost +- **Don't use jargon** - Keep labels user-friendly + +## Next Steps + + + + Complete reference for menu item types and properties. + + [Learn More →](/features/menus/reference) + + + + Create right-click context menus. + + [Learn More →](/features/menus/context) + + + + Add system tray/menu bar integration. + + [Learn More →](/features/menus/systray) + + + + Common menu patterns and best practices. + + [Learn More →](/guides/patterns/menus) + + + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [menu example](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/menu). diff --git a/docs/src/content/docs/features/menus/context.mdx b/docs/src/content/docs/features/menus/context.mdx new file mode 100644 index 000000000..ab7eb630a --- /dev/null +++ b/docs/src/content/docs/features/menus/context.mdx @@ -0,0 +1,728 @@ +--- +title: Context Menus +description: Create right-click context menus for your application +sidebar: + order: 2 +--- + +import { Card, CardGrid, Tabs, TabItem } from "@astrojs/starlight/components"; + +## The Problem + +Users expect right-click menus with context-specific actions. Different elements need different menus: +- **Text**: Cut, Copy, Paste +- **Images**: Save, Copy, Open +- **Custom elements**: Application-specific actions + +Building context menus manually means handling mouse events, positioning, and platform differences. + +## The Wails Solution + +Wails provides **declarative context menus** using CSS properties. Associate menus with HTML elements, pass data, and handle clicks—all with native platform behaviour. + +## Quick Start + +**Go code:** + +```go +// Create context menu +contextMenu := app.NewContextMenu() +contextMenu.Add("Cut").OnClick(handleCut) +contextMenu.Add("Copy").OnClick(handleCopy) +contextMenu.Add("Paste").OnClick(handlePaste) + +// Register with ID +app.RegisterContextMenu("editor-menu", contextMenu) +``` + +**HTML:** + +```html + +``` + +**That's it!** Right-clicking the textarea shows your custom menu. + +## Creating Context Menus + +### Basic Context Menu + +```go +// Create menu +contextMenu := app.NewContextMenu() + +// Add items +contextMenu.Add("Cut").SetAccelerator("CmdOrCtrl+X").OnClick(func(ctx *application.Context) { + // Handle cut +}) + +contextMenu.Add("Copy").SetAccelerator("CmdOrCtrl+C").OnClick(func(ctx *application.Context) { + // Handle copy +}) + +contextMenu.Add("Paste").SetAccelerator("CmdOrCtrl+V").OnClick(func(ctx *application.Context) { + // Handle paste +}) + +// Register with unique ID +app.RegisterContextMenu("text-menu", contextMenu) +``` + +**Menu ID:** Must be unique. Used to associate menu with HTML elements. + +### With Submenus + +```go +contextMenu := app.NewContextMenu() + +// Add regular items +contextMenu.Add("Open").OnClick(handleOpen) +contextMenu.Add("Delete").OnClick(handleDelete) + +contextMenu.AddSeparator() + +// Add submenu +exportMenu := contextMenu.AddSubmenu("Export As") +exportMenu.Add("PNG").OnClick(exportPNG) +exportMenu.Add("JPEG").OnClick(exportJPEG) +exportMenu.Add("SVG").OnClick(exportSVG) + +app.RegisterContextMenu("image-menu", contextMenu) +``` + +### With Checkboxes and Radio Groups + +```go +contextMenu := app.NewContextMenu() + +// Checkbox +contextMenu.AddCheckbox("Show Grid", true).OnClick(func(ctx *application.Context) { + showGrid := ctx.ClickedMenuItem().Checked() + // Toggle grid +}) + +contextMenu.AddSeparator() + +// Radio group +contextMenu.AddRadio("Small", false).OnClick(handleSize) +contextMenu.AddRadio("Medium", true).OnClick(handleSize) +contextMenu.AddRadio("Large", false).OnClick(handleSize) + +app.RegisterContextMenu("view-menu", contextMenu) +``` + +**For all menu item types**, see [Menu Reference](/features/menus/reference). + +## Associating with HTML Elements + +Use CSS custom properties to attach context menus: + +### Basic Association + +```html +
      + Right-click me! +
      +``` + +**CSS property:** `--custom-contextmenu: ` + +### With Context Data + +Pass data from HTML to Go: + +```html +
      + Right-click this file +
      +``` + +**Go handler:** + +```go +contextMenu := app.NewContextMenu() +contextMenu.Add("Open").OnClick(func(ctx *application.Context) { + fileID := ctx.ContextMenuData() // "file-123" + openFile(fileID) +}) + +app.RegisterContextMenu("file-menu", contextMenu) +``` + +**CSS properties:** +- `--custom-contextmenu: ` - Which menu to show +- `--custom-contextmenu-data: ` - Data to pass to handlers + +### Dynamic Data + +Generate data dynamically in JavaScript: + +```html +
      + File.txt +
      + + +``` + +### Multiple Elements, Same Menu + +```html +
      + Document.pdf +
      + +
      + Image.png +
      + +
      + Video.mp4 +
      +``` + +**One menu, different data for each element.** + +## Context Data + +### Accessing Context Data + +```go +contextMenu.Add("Process").OnClick(func(ctx *application.Context) { + data := ctx.ContextMenuData() // Get data from HTML + + // Use the data + processItem(data) +}) +``` + +**Data type:** Always `string`. Parse as needed. + +### Passing Complex Data + +Use JSON for complex data: + +```html +
      + Image.png +
      +``` + +**Go handler:** + +```go +import "encoding/json" + +type ItemData struct { + ID int `json:"id"` + Type string `json:"type"` +} + +contextMenu.Add("Process").OnClick(func(ctx *application.Context) { + dataStr := ctx.ContextMenuData() + + var data ItemData + if err := json.Unmarshal([]byte(dataStr), &data); err != nil { + log.Printf("Invalid data: %v", err) + return + } + + processItem(data.ID, data.Type) +}) +``` + +:::caution[Security] +**Always validate context data** from the frontend. Users can manipulate CSS properties, so treat data as untrusted input. +::: + +### Validation Example + +```go +contextMenu.Add("Delete").OnClick(func(ctx *application.Context) { + fileID := ctx.ContextMenuData() + + // Validate + if !isValidFileID(fileID) { + log.Printf("Invalid file ID: %s", fileID) + return + } + + // Check permissions + if !canDeleteFile(fileID) { + showError("Permission denied") + return + } + + // Safe to proceed + deleteFile(fileID) +}) +``` + +## Default Context Menu + +The WebView provides a built-in context menu for standard operations (copy, paste, inspect). Control it with `--default-contextmenu`: + +### Hide Default Menu + +```html +
      + No default menu here +
      +``` + +**Use case:** Custom UI elements where default menu doesn't make sense. + +### Show Default Menu + +```html +
      + Default menu always shown +
      +``` + +**Use case:** Text areas, input fields, editable content. + +### Auto (Smart) Mode + +```html +
      + Smart context menu +
      +``` + +**Default behaviour.** Shows default menu when: +- Text is selected +- In text input fields +- In editable content (`contenteditable`) + +Hides default menu otherwise. + +### Combining Custom and Default + +```html + + +``` + +**Behaviour:** +1. Custom menu shows first +2. If custom menu is empty or not found, default menu shows +3. Both can coexist (platform-dependent) + +## Dynamic Context Menus + +Update menus based on application state: + +### Enable/Disable Items + +```go +var cutMenuItem *application.MenuItem +var copyMenuItem *application.MenuItem + +func createContextMenu() { + contextMenu := app.NewContextMenu() + + cutMenuItem = contextMenu.Add("Cut") + cutMenuItem.SetEnabled(false) // Initially disabled + cutMenuItem.OnClick(handleCut) + + copyMenuItem = contextMenu.Add("Copy") + copyMenuItem.SetEnabled(false) + copyMenuItem.OnClick(handleCopy) + + app.RegisterContextMenu("editor-menu", contextMenu) +} + +func onSelectionChanged(hasSelection bool) { + cutMenuItem.SetEnabled(hasSelection) + copyMenuItem.SetEnabled(hasSelection) + contextMenu.Update() // Important! +} +``` + +:::caution[Always Call Update()] +After changing menu state, **call `contextMenu.Update()`**. This is critical on Windows. + +See [Menu Reference](/features/menus/reference#enabled-state) for details. +::: + +### Change Labels + +```go +playMenuItem := contextMenu.Add("Play") + +playMenuItem.OnClick(func(ctx *application.Context) { + if isPlaying { + playMenuItem.SetLabel("Pause") + } else { + playMenuItem.SetLabel("Play") + } + contextMenu.Update() +}) +``` + +### Rebuild Menus + +For major changes, rebuild the entire menu: + +```go +func rebuildContextMenu(fileType string) { + contextMenu := app.NewContextMenu() + + // Common items + contextMenu.Add("Open").OnClick(handleOpen) + contextMenu.Add("Delete").OnClick(handleDelete) + + contextMenu.AddSeparator() + + // Type-specific items + switch fileType { + case "image": + contextMenu.Add("Edit Image").OnClick(editImage) + contextMenu.Add("Set as Wallpaper").OnClick(setWallpaper) + case "video": + contextMenu.Add("Play").OnClick(playVideo) + contextMenu.Add("Extract Audio").OnClick(extractAudio) + case "document": + contextMenu.Add("Print").OnClick(printDocument) + contextMenu.Add("Export PDF").OnClick(exportPDF) + } + + app.RegisterContextMenu("file-menu", contextMenu) +} +``` + +## Platform Behaviour + +Context menus are **platform-native**: + + + + **Native macOS context menus:** + - System animations and transitions + - Right-click = Control+Click (automatic) + - Adapts to system appearance (light/dark) + - Standard text operations in default menu + - Native scrolling for long menus + + **macOS conventions:** + - Use sentence case for menu items + - Use ellipsis (...) for items that open dialogs + - Common shortcuts: ⌘C (Copy), ⌘V (Paste) + + + + **Native Windows context menus:** + - Windows native style + - Follows Windows theme + - Standard Windows operations in default menu + - Touch and pen input support + + **Windows conventions:** + - Use title case for menu items + - Use ellipsis (...) for items that open dialogs + - Common shortcuts: Ctrl+C (Copy), Ctrl+V (Paste) + + + + **Desktop environment integration:** + - Adapts to desktop theme (GTK, Qt, etc.) + - Right-click behaviour follows system settings + - Default menu content varies by environment + - Positioning follows DE conventions + + **Linux considerations:** + - Test on target desktop environments + - GTK and Qt have different behaviours + - Some DEs customise context menus + + + +## Complete Example + +**Go code:** + +```go +package main + +import ( + "encoding/json" + "log" + "github.com/wailsapp/wails/v3/pkg/application" +) + +type FileData struct { + ID string `json:"id"` + Type string `json:"type"` + Name string `json:"name"` +} + +func main() { + app := application.New(application.Options{ + Name: "Context Menu Demo", + }) + + // Create file context menu + fileMenu := createFileMenu(app) + app.RegisterContextMenu("file-menu", fileMenu) + + // Create image context menu + imageMenu := createImageMenu(app) + app.RegisterContextMenu("image-menu", imageMenu) + + // Create text context menu + textMenu := createTextMenu(app) + app.RegisterContextMenu("text-menu", textMenu) + + app.Window.New() + app.Run() +} + +func createFileMenu(app *application.Application) *application.ContextMenu { + menu := app.NewContextMenu() + + menu.Add("Open").OnClick(func(ctx *application.Context) { + data := parseFileData(ctx.ContextMenuData()) + openFile(data.ID) + }) + + menu.Add("Rename").OnClick(func(ctx *application.Context) { + data := parseFileData(ctx.ContextMenuData()) + renameFile(data.ID) + }) + + menu.AddSeparator() + + menu.Add("Delete").OnClick(func(ctx *application.Context) { + data := parseFileData(ctx.ContextMenuData()) + deleteFile(data.ID) + }) + + return menu +} + +func createImageMenu(app *application.Application) *application.ContextMenu { + menu := app.NewContextMenu() + + menu.Add("View").OnClick(func(ctx *application.Context) { + data := parseFileData(ctx.ContextMenuData()) + viewImage(data.ID) + }) + + menu.Add("Edit").OnClick(func(ctx *application.Context) { + data := parseFileData(ctx.ContextMenuData()) + editImage(data.ID) + }) + + menu.AddSeparator() + + exportMenu := menu.AddSubmenu("Export As") + exportMenu.Add("PNG").OnClick(exportPNG) + exportMenu.Add("JPEG").OnClick(exportJPEG) + exportMenu.Add("WebP").OnClick(exportWebP) + + return menu +} + +func createTextMenu(app *application.Application) *application.ContextMenu { + menu := app.NewContextMenu() + + menu.Add("Cut").SetAccelerator("CmdOrCtrl+X").OnClick(handleCut) + menu.Add("Copy").SetAccelerator("CmdOrCtrl+C").OnClick(handleCopy) + menu.Add("Paste").SetAccelerator("CmdOrCtrl+V").OnClick(handlePaste) + + return menu +} + +func parseFileData(dataStr string) FileData { + var data FileData + if err := json.Unmarshal([]byte(dataStr), &data); err != nil { + log.Printf("Invalid file data: %v", err) + } + return data +} + +// Handler implementations... +func openFile(id string) { /* ... */ } +func renameFile(id string) { /* ... */ } +func deleteFile(id string) { /* ... */ } +func viewImage(id string) { /* ... */ } +func editImage(id string) { /* ... */ } +func exportPNG(ctx *application.Context) { /* ... */ } +func exportJPEG(ctx *application.Context) { /* ... */ } +func exportWebP(ctx *application.Context) { /* ... */ } +func handleCut(ctx *application.Context) { /* ... */ } +func handleCopy(ctx *application.Context) { /* ... */ } +func handlePaste(ctx *application.Context) { /* ... */ } +``` + +**HTML:** + +```html + + + + + + +

      Files

      + + +
      + 📄 Report.pdf +
      + + +
      + 🖼️ Photo.jpg +
      + +

      Text Editor

      + + + + +

      No Context Menu

      + + +
      + Right-click here - no menu appears +
      + + +``` + +## Best Practices + +### ✅ Do + +- **Keep menus focused** - Only relevant actions for the element +- **Validate context data** - Treat as untrusted input +- **Use clear labels** - "Delete File" not "Delete" +- **Call menu.Update()** - After changing menu state +- **Test on all platforms** - Behaviour varies +- **Provide keyboard shortcuts** - For common actions +- **Group related items** - Use separators + +### ❌ Don't + +- **Don't trust context data** - Always validate +- **Don't make menus too long** - 7-10 items maximum +- **Don't forget menu.Update()** - Menus won't work properly +- **Don't nest too deeply** - 2 levels maximum +- **Don't use jargon** - Keep labels user-friendly +- **Don't block handlers** - Keep them fast + +## Troubleshooting + +### Context Menu Not Appearing + +**Possible causes:** +1. Menu ID mismatch +2. CSS property typo +3. Runtime not initialised + +**Solution:** + +```go +// Check menu is registered +app.RegisterContextMenu("my-menu", contextMenu) +``` + +```html + +
      +``` + +### Context Data Not Received + +**Possible causes:** +1. CSS property not set +2. Data contains special characters + +**Solution:** + +```html + +
      +``` + +Or use JavaScript: + +```javascript +element.style.setProperty('--custom-contextmenu-data', JSON.stringify(data)) +``` + +### Menu Items Not Responding + +**Cause:** Forgot to call `menu.Update()` after enabling + +**Solution:** + +```go +menuItem.SetEnabled(true) +contextMenu.Update() // Add this! +``` + +## Next Steps + + + + Complete reference for menu item types and properties. + + [Learn More →](/features/menus/reference) + + + + Create application menu bars. + + [Learn More →](/features/menus/application) + + + + Add system tray/menu bar integration. + + [Learn More →](/features/menus/systray) + + + + Common menu patterns and best practices. + + [Learn More →](/guides/patterns/menus) + + + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [context menu example](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/contextmenus). diff --git a/docs/src/content/docs/features/menus/reference.mdx b/docs/src/content/docs/features/menus/reference.mdx new file mode 100644 index 000000000..93cc6b45a --- /dev/null +++ b/docs/src/content/docs/features/menus/reference.mdx @@ -0,0 +1,562 @@ +--- +title: Menu Reference +description: Complete reference for menu item types, properties, and methods +sidebar: + order: 4 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +## Menu Reference + +Complete reference for menu item types, properties, and dynamic behaviour. Build professional, responsive menus with checkboxes, radio groups, separators, and dynamic updates. + +## Menu Item Types + +### Regular Menu Items + +The most common type—displays text and triggers an action: + +```go +menuItem := menu.Add("Click Me") +menuItem.OnClick(func(ctx *application.Context) { + fmt.Println("Menu item clicked!") +}) +``` + +**Use for:** Commands, actions, opening windows + +### Checkboxes + +Toggle-able menu items with checked/unchecked state: + +```go +checkbox := menu.AddCheckbox("Enable Feature", true) // true = initially checked +checkbox.OnClick(func(ctx *application.Context) { + isChecked := ctx.ClickedMenuItem().Checked() + fmt.Printf("Feature is now: %v\n", isChecked) +}) +``` + +**Use for:** Boolean settings, feature toggles, view options + +**Important:** The checked state toggles automatically when clicked. + +### Radio Groups + +Mutually exclusive options—only one can be selected: + +```go +menu.AddRadio("Small", true) // true = initially selected +menu.AddRadio("Medium", false) +menu.AddRadio("Large", false) +``` + +**Use for:** Mutually exclusive choices (size, theme, mode) + +**How grouping works:** +- Adjacent radio items form a group automatically +- Selecting one deselects others in the group +- Separate groups with a separator or regular item + +**Example with multiple groups:** + +```go +// Group 1: Size +menu.AddRadio("Small", true) +menu.AddRadio("Medium", false) +menu.AddRadio("Large", false) + +menu.AddSeparator() + +// Group 2: Theme +menu.AddRadio("Light", true) +menu.AddRadio("Dark", false) +``` + +### Submenus + +Nested menu structures for organisation: + +```go +submenu := menu.AddSubmenu("More Options") +submenu.Add("Submenu Item 1").OnClick(func(ctx *application.Context) { + // Handle click +}) +submenu.Add("Submenu Item 2") +``` + +**Use for:** Grouping related items, reducing clutter + +**Nesting limit:** Most platforms support 2-3 levels. Avoid deeper nesting. + +### Separators + +Visual dividers between menu items: + +```go +menu.Add("Item 1") +menu.AddSeparator() +menu.Add("Item 2") +``` + +**Use for:** Grouping related items visually + +**Best practice:** Don't start or end menus with separators. + +## Menu Item Properties + +### Label + +The text displayed for the menu item: + +```go +menuItem := menu.Add("Initial Label") +menuItem.SetLabel("New Label") + +// Get current label +label := menuItem.Label() +``` + +**Dynamic labels:** + +```go +updateMenuItem := menu.Add("Check for Updates") +updateMenuItem.OnClick(func(ctx *application.Context) { + updateMenuItem.SetLabel("Checking...") + menu.Update() // Important on Windows! + + // Perform update check + checkForUpdates() + + updateMenuItem.SetLabel("Check for Updates") + menu.Update() +}) +``` + +### Enabled State + +Control whether the menu item can be interacted with: + +```go +menuItem := menu.Add("Save") +menuItem.SetEnabled(false) // Greyed out, can't click + +// Enable it later +menuItem.SetEnabled(true) +menu.Update() // Important: Call this after changing enabled state! + +// Check current state +isEnabled := menuItem.Enabled() +``` + +:::caution[Windows Menu Behaviour] +On Windows, menus need to be reconstructed when their state changes. **Always call `menu.Update()` after enabling/disabling menu items**, especially if the item was created whilst disabled. + +**Why:** Windows menus are rebuilt from scratch when updated. If you don't call `Update()`, click handlers won't fire properly. +::: + +**Example: Dynamic enable/disable** + +```go +var hasSelection bool + +cutMenuItem := menu.Add("Cut") +cutMenuItem.SetEnabled(false) // Initially disabled + +copyMenuItem := menu.Add("Copy") +copyMenuItem.SetEnabled(false) + +// When selection changes +func onSelectionChanged(selected bool) { + hasSelection = selected + cutMenuItem.SetEnabled(hasSelection) + copyMenuItem.SetEnabled(hasSelection) + menu.Update() // Critical on Windows! +} +``` + +**Common pattern: Enable on condition** + +```go +saveMenuItem := menu.Add("Save") + +func updateSaveMenuItem() { + canSave := hasUnsavedChanges() && !isSaving() + saveMenuItem.SetEnabled(canSave) + menu.Update() +} + +// Call whenever state changes +onDocumentChanged(func() { + updateSaveMenuItem() +}) +``` + +### Checked State + +For checkbox and radio items, control or query their checked state: + +```go +checkbox := menu.AddCheckbox("Feature", false) +checkbox.SetChecked(true) +menu.Update() + +// Query state +isChecked := checkbox.Checked() +``` + +**Auto-toggle:** Checkboxes toggle automatically when clicked. You don't need to call `SetChecked()` in the click handler. + +**Manual control:** + +```go +checkbox := menu.AddCheckbox("Auto-save", false) + +// Sync with external state +func syncAutoSave(enabled bool) { + checkbox.SetChecked(enabled) + menu.Update() +} +``` + +### Accelerators (Keyboard Shortcuts) + +Add keyboard shortcuts to menu items: + +```go +saveMenuItem := menu.Add("Save") +saveMenuItem.SetAccelerator("CmdOrCtrl+S") + +quitMenuItem := menu.Add("Quit") +quitMenuItem.SetAccelerator("CmdOrCtrl+Q") +``` + +**Accelerator format:** +- `CmdOrCtrl` - Cmd on macOS, Ctrl on Windows/Linux +- `Shift`, `Alt`, `Option` - Modifier keys +- `A-Z`, `0-9` - Letter/number keys +- `F1-F12` - Function keys +- `Enter`, `Space`, `Backspace`, etc. - Special keys + +**Examples:** + +```go +"CmdOrCtrl+S" // Save +"CmdOrCtrl+Shift+S" // Save As +"CmdOrCtrl+W" // Close Window +"CmdOrCtrl+Q" // Quit +"F5" // Refresh +"CmdOrCtrl+," // Preferences (macOS convention) +"Alt+F4" // Close (Windows convention) +``` + +**Platform-specific accelerators:** + +```go +if runtime.GOOS == "darwin" { + prefsMenuItem.SetAccelerator("Cmd+,") +} else { + prefsMenuItem.SetAccelerator("Ctrl+P") +} +``` + +### Tooltip + +Add hover text to menu items (platform support varies): + +```go +menuItem := menu.Add("Advanced Options") +menuItem.SetTooltip("Configure advanced settings") +``` + +**Platform support:** +- **Windows:** ✅ Supported +- **macOS:** ❌ Not supported (tooltips not standard for menus) +- **Linux:** ⚠️ Varies by desktop environment + +### Hidden State + +Hide menu items without removing them: + +```go +debugMenuItem := menu.Add("Debug Mode") +debugMenuItem.SetHidden(true) // Hidden + +// Show in debug builds +if isDebugBuild { + debugMenuItem.SetHidden(false) + menu.Update() +} +``` + +**Use for:** Debug options, feature flags, conditional features + +## Event Handling + +### OnClick Handler + +Execute code when menu item is clicked: + +```go +menuItem := menu.Add("Click Me") +menuItem.OnClick(func(ctx *application.Context) { + // Handle click + fmt.Println("Clicked!") +}) +``` + +**Context provides:** +- `ctx.ClickedMenuItem()` - The menu item that was clicked +- Window context (if from window menu) +- Application context + +**Example: Access menu item in handler** + +```go +checkbox := menu.AddCheckbox("Feature", false) +checkbox.OnClick(func(ctx *application.Context) { + item := ctx.ClickedMenuItem() + isChecked := item.Checked() + fmt.Printf("Feature is now: %v\n", isChecked) +}) +``` + +### Multiple Handlers + +You can set multiple handlers (last one wins): + +```go +menuItem := menu.Add("Action") +menuItem.OnClick(func(ctx *application.Context) { + fmt.Println("First handler") +}) + +// This replaces the first handler +menuItem.OnClick(func(ctx *application.Context) { + fmt.Println("Second handler - this one runs") +}) +``` + +**Best practice:** Set handler once, use conditional logic inside if needed. + +## Dynamic Menus + +### Updating Menu Items + +**The golden rule:** Always call `menu.Update()` after changing menu state. + +```go +// ✅ Correct +menuItem.SetEnabled(true) +menu.Update() + +// ❌ Wrong (especially on Windows) +menuItem.SetEnabled(true) +// Forgot to call Update() - click handlers may not work! +``` + +**Why this matters:** +- **Windows:** Menus are reconstructed when updated +- **macOS/Linux:** Less critical but still recommended +- **Click handlers:** Won't fire properly without Update() + +### Rebuilding Menus + +For major changes, rebuild the entire menu: + +```go +func rebuildFileMenu() { + menu := app.Menu.New() + + menu.Add("New").OnClick(handleNew) + menu.Add("Open").OnClick(handleOpen) + + if hasRecentFiles() { + recentMenu := menu.AddSubmenu("Open Recent") + for _, file := range getRecentFiles() { + recentMenu.Add(file).OnClick(func(ctx *application.Context) { + openFile(file) + }) + } + } + + menu.AddSeparator() + menu.Add("Quit").OnClick(handleQuit) + + // Set the new menu + window.SetMenu(menu) +} +``` + +**When to rebuild:** +- Recent files list changes +- Plugin menus change +- Major state transitions + +**When to update:** +- Enable/disable items +- Change labels +- Toggle checkboxes + +### Context-Sensitive Menus + +Adjust menus based on application state: + +```go +func updateEditMenu() { + cutMenuItem.SetEnabled(hasSelection()) + copyMenuItem.SetEnabled(hasSelection()) + pasteMenuItem.SetEnabled(hasClipboardContent()) + undoMenuItem.SetEnabled(canUndo()) + redoMenuItem.SetEnabled(canRedo()) + menu.Update() +} + +// Call whenever state changes +onSelectionChanged(updateEditMenu) +onClipboardChanged(updateEditMenu) +onUndoStackChanged(updateEditMenu) +``` + +## Platform Differences + +### Menu Bar Location + +| Platform | Location | Notes | +|----------|----------|-------| +| **macOS** | Top of screen | Global menu bar | +| **Windows** | Top of window | Per-window menu | +| **Linux** | Top of window | Per-window (usually) | + +### Standard Menus + +**macOS:** +- Has "Application" menu (with app name) +- "Preferences" in Application menu +- "Quit" in Application menu + +**Windows/Linux:** +- No Application menu +- "Preferences" in Edit or Tools menu +- "Exit" in File menu + +**Example: Platform-appropriate structure** + +```go +menu := app.Menu.New() + +// macOS gets Application menu +if runtime.GOOS == "darwin" { + menu.AddRole(application.AppMenu) +} + +// File menu +fileMenu := menu.AddSubmenu("File") +fileMenu.Add("New") +fileMenu.Add("Open") + +// Preferences location varies +if runtime.GOOS == "darwin" { + // On macOS, preferences are in Application menu (added by AppMenu role) +} else { + // On Windows/Linux, add to Edit or Tools menu + editMenu := menu.AddSubmenu("Edit") + editMenu.Add("Preferences") +} +``` + +### Accelerator Conventions + +**macOS:** +- `Cmd+` for most shortcuts +- `Cmd+,` for Preferences +- `Cmd+Q` for Quit + +**Windows:** +- `Ctrl+` for most shortcuts +- `Ctrl+P` or `Ctrl+,` for Preferences +- `Alt+F4` for Exit (or `Ctrl+Q`) + +**Linux:** +- Generally follows Windows conventions +- Desktop environment may override + +## Best Practices + +### ✅ Do + +- **Call menu.Update()** after changing menu state (especially on Windows) +- **Use radio groups** for mutually exclusive options +- **Use checkboxes** for toggleable features +- **Add accelerators** to common actions +- **Group related items** with separators +- **Test on all platforms** - behaviour varies + +### ❌ Don't + +- **Don't forget menu.Update()** - Click handlers won't work properly +- **Don't nest too deeply** - 2-3 levels maximum +- **Don't start/end with separators** - Looks unprofessional +- **Don't use tooltips on macOS** - Not supported +- **Don't hardcode platform shortcuts** - Use `CmdOrCtrl` + +## Troubleshooting + +### Menu Items Not Responding + +**Symptom:** Click handlers don't fire + +**Cause:** Forgot to call `menu.Update()` after enabling item + +**Solution:** + +```go +menuItem.SetEnabled(true) +menu.Update() // Add this! +``` + +### Menu Items Greyed Out + +**Symptom:** Can't click menu items + +**Cause:** Items are disabled + +**Solution:** + +```go +menuItem.SetEnabled(true) +menu.Update() +``` + +### Accelerators Not Working + +**Symptom:** Keyboard shortcuts don't trigger menu items + +**Causes:** +1. Accelerator format incorrect +2. Conflict with system shortcuts +3. Window doesn't have focus + +**Solution:** + +```go +// Check format +menuItem.SetAccelerator("CmdOrCtrl+S") // ✅ Correct +menuItem.SetAccelerator("Ctrl+S") // ❌ Wrong (macOS uses Cmd) + +// Avoid conflicts +// ❌ Cmd+H (Hide Window on macOS - system shortcut) +// ✅ Cmd+Shift+H (Custom shortcut) +``` + +## Next Steps + +- [Application Menus](/features/menus/application) - Create application menu bars +- [Context Menus](/features/menus/context) - Right-click context menus +- [System Tray Menus](/features/menus/systray) - System tray/menu bar menus +- [Menu Patterns](/guides/patterns/menus) - Common menu patterns and best practices + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [menu examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/menu). diff --git a/docs/src/content/docs/features/menus/systray.mdx b/docs/src/content/docs/features/menus/systray.mdx new file mode 100644 index 000000000..a0c8a722b --- /dev/null +++ b/docs/src/content/docs/features/menus/systray.mdx @@ -0,0 +1,713 @@ +--- +title: System Tray Menus +description: Add system tray (notification area) integration to your application +sidebar: + order: 3 +--- + +import { Tabs, TabItem, Card, CardGrid } from "@astrojs/starlight/components"; + +## System Tray Menus + +Wails provides **unified system tray APIs** that work across all platforms. Create tray icons with menus, attach windows, and handle clicks with native platform behaviour for background applications, services, and quick-access utilities. +{/* VISUAL PLACEHOLDER: System Tray Comparison +Description: Three screenshots showing the same Wails system tray icon on: +1. Windows - Notification area (bottom-right) +2. macOS - Menu bar (top-right) with label +3. Linux (GNOME) - Top bar +All showing the same icon and menu structure +Style: Clean screenshots with arrows pointing to tray icon, menu expanded +*/} + +## Quick Start + +```go +package main + +import ( + _ "embed" + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed assets/icon.png +var icon []byte + +func main() { + app := application.New(application.Options{ + Name: "Tray App", + }) + + // Create system tray + systray := app.SystemTray.New() + systray.SetIcon(icon) + systray.SetLabel("My App") + + // Add menu + menu := app.NewMenu() + menu.Add("Show").OnClick(func(ctx *application.Context) { + // Show main window + }) + menu.Add("Quit").OnClick(func(ctx *application.Context) { + app.Quit() + }) + systray.SetMenu(menu) + + // Create hidden window + window := app.Window.New() + window.Hide() + + app.Run() +} +``` + +**Result:** System tray icon with menu on all platforms. + +## Creating a System Tray + +### Basic System Tray + +```go +// Create system tray +systray := app.SystemTray.New() + +// Set icon +systray.SetIcon(iconBytes) + +// Set label (macOS) / tooltip (Windows) +systray.SetLabel("My Application") +``` + +### With Icon + +Icons should be embedded: + +```go +import _ "embed" + +//go:embed assets/icon.png +var icon []byte + +//go:embed assets/icon-dark.png +var iconDark []byte + +func main() { + app := application.New(application.Options{ + Name: "My App", + }) + + systray := app.SystemTray.New() + systray.SetIcon(icon) + systray.SetDarkModeIcon(iconDark) // macOS dark mode + + app.Run() +} +``` + +**Icon requirements:** + +| Platform | Size | Format | Notes | +|----------|------|--------|-------| +| **Windows** | 16x16 or 32x32 | PNG, ICO | Notification area | +| **macOS** | 18x18 to 22x22 | PNG | Menu bar, template recommended | +| **Linux** | 22x22 to 48x48 | PNG, SVG | Varies by DE | + +### Template Icons (macOS) + +Template icons adapt to light/dark mode automatically: + +```go +systray.SetTemplateIcon(iconBytes) +``` + +**Template icon guidelines:** +- Use black and clear (transparent) colours only +- Black becomes white in dark mode +- Name file with `Template` suffix: `iconTemplate.png` +- [Design guide](https://bjango.com/articles/designingmenubarextras/) + +## Adding Menus + +System tray menus work like application menus: + +```go +menu := app.NewMenu() + +// Add items +menu.Add("Open").OnClick(func(ctx *application.Context) { + showMainWindow() +}) + +menu.AddSeparator() + +menu.AddCheckbox("Start at Login", false).OnClick(func(ctx *application.Context) { + enabled := ctx.ClickedMenuItem().Checked() + setStartAtLogin(enabled) +}) + +menu.AddSeparator() + +menu.Add("Quit").OnClick(func(ctx *application.Context) { + app.Quit() +}) + +// Set menu +systray.SetMenu(menu) +``` + +**For all menu item types**, see [Menu Reference](/features/menus/reference). + +## Attaching Windows + +Attach a window to the tray icon for automatic show/hide: + +```go +// Create window +window := app.Window.New() + +// Attach to tray +systray.AttachWindow(window) + +// Configure behaviour +systray.SetWindowOffset(10) // Pixels from tray icon +systray.SetWindowDebounce(200 * time.Millisecond) // Click debounce +``` + +**Behaviour:** +- Window starts hidden +- **Left-click tray icon** → Toggle window visibility +- **Right-click tray icon** → Show menu (if set) +- Window positioned near tray icon + +**Example: Popup window** + +```go +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Quick Access", + Width: 300, + Height: 400, + Frameless: true, // No title bar + AlwaysOnTop: true, // Stay on top +}) + +systray.AttachWindow(window) +systray.SetWindowOffset(5) +``` + +## Click Handlers + +Handle tray icon clicks: + +```go +systray := app.SystemTray.New() + +// Left click +systray.OnClick(func() { + fmt.Println("Tray icon clicked") +}) + +// Right click +systray.OnRightClick(func() { + fmt.Println("Tray icon right-clicked") +}) + +// Double click +systray.OnDoubleClick(func() { + fmt.Println("Tray icon double-clicked") +}) + +// Mouse enter/leave +systray.OnMouseEnter(func() { + fmt.Println("Mouse entered tray icon") +}) + +systray.OnMouseLeave(func() { + fmt.Println("Mouse left tray icon") +}) +``` + +**Platform support:** + +| Event | Windows | macOS | Linux | +|-------|---------|-------|-------| +| OnClick | ✅ | ✅ | ✅ | +| OnRightClick | ✅ | ✅ | ✅ | +| OnDoubleClick | ✅ | ✅ | ⚠️ Varies | +| OnMouseEnter | ✅ | ✅ | ⚠️ Varies | +| OnMouseLeave | ✅ | ✅ | ⚠️ Varies | + +## Dynamic Updates + +Update tray icon and menu dynamically: + +### Change Icon + +```go +var isActive bool + +func updateTrayIcon() { + if isActive { + systray.SetIcon(activeIcon) + systray.SetLabel("Active") + } else { + systray.SetIcon(inactiveIcon) + systray.SetLabel("Inactive") + } +} +``` + +### Update Menu + +```go +var isPaused bool + +pauseMenuItem := menu.Add("Pause") + +pauseMenuItem.OnClick(func(ctx *application.Context) { + isPaused = !isPaused + + if isPaused { + pauseMenuItem.SetLabel("Resume") + } else { + pauseMenuItem.SetLabel("Pause") + } + + menu.Update() // Important! +}) +``` + +:::caution[Always Call Update()] +After changing menu state, **call `menu.Update()`**. See [Menu Reference](/features/menus/reference#enabled-state). +::: + +### Rebuild Menu + +For major changes, rebuild the entire menu: + +```go +func rebuildTrayMenu(status string) { + menu := app.NewMenu() + + // Status-specific items + switch status { + case "syncing": + menu.Add("Syncing...").SetEnabled(false) + menu.Add("Pause Sync").OnClick(pauseSync) + case "synced": + menu.Add("Up to date ✓").SetEnabled(false) + menu.Add("Sync Now").OnClick(startSync) + case "error": + menu.Add("Sync Error").SetEnabled(false) + menu.Add("Retry").OnClick(retrySync) + } + + menu.AddSeparator() + menu.Add("Quit").OnClick(func(ctx *application.Context) { + app.Quit() + }) + + systray.SetMenu(menu) +} +``` + +## Platform-Specific Features + + + + **Menu bar integration:** + + ```go + // Set label (appears next to icon) + systray.SetLabel("My App") + + // Use template icon (adapts to dark mode) + systray.SetTemplateIcon(iconBytes) + + // Set icon position + systray.SetIconPosition(application.IconPositionRight) + ``` + + **Icon positions:** + - `IconPositionLeft` - Icon left of label + - `IconPositionRight` - Icon right of label + - `IconPositionOnly` - Icon only, no label + - `IconPositionNone` - Label only, no icon + + **Best practices:** + - Use template icons (black + transparent) + - Keep labels short (3-5 characters) + - 18x18 to 22x22 pixels for Retina displays + - Test in both light and dark modes + + + + **Notification area integration:** + + ```go + // Set tooltip (appears on hover) + systray.SetTooltip("My Application") + + // Or use SetLabel (same as tooltip on Windows) + systray.SetLabel("My Application") + + // Show/Hide functionality (fully functional) + systray.Show() // Show tray icon + systray.Hide() // Hide tray icon + ``` + + **Icon requirements:** + - 16x16 or 32x32 pixels + - PNG or ICO format + - Transparent background + + **Tooltip limits:** + - Maximum 127 UTF-16 characters + - Longer tooltips will be truncated + - Keep concise for best experience + + **Platform features:** + - Tray icon survives Windows Explorer restarts + - Show() and Hide() methods fully functional + - Proper lifecycle management + + **Best practices:** + - Use 32x32 for high-DPI displays + - Keep tooltips under 127 characters + - Test on different Windows versions + - Consider notification area overflow + - Use Show/Hide for conditional tray visibility + + + + **System tray integration:** + + Uses StatusNotifierItem specification (most modern DEs). + + ```go + systray.SetIcon(iconBytes) + systray.SetLabel("My App") + ``` + + **Desktop environment support:** + - **GNOME**: Top bar (with extension) + - **KDE Plasma**: System tray + - **XFCE**: Notification area + - **Others**: Varies + + **Best practices:** + - Use 22x22 or 24x24 pixels + - SVG icons scale better + - Test on target desktop environments + - Provide fallback for unsupported DEs + + + +## Complete Example + +Here's a production-ready system tray application: + +```go +package main + +import ( + _ "embed" + "fmt" + "time" + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed assets/icon.png +var icon []byte + +//go:embed assets/icon-active.png +var iconActive []byte + +type TrayApp struct { + app *application.Application + systray *application.SystemTray + window *application.WebviewWindow + menu *application.Menu + isActive bool +} + +func main() { + app := application.New(application.Options{ + Name: "Tray Application", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: false, + }, + }) + + trayApp := &TrayApp{app: app} + trayApp.setup() + + app.Run() +} + +func (t *TrayApp) setup() { + // Create system tray + t.systray = t.app.SystemTray.New() + t.systray.SetIcon(icon) + t.systray.SetLabel("Inactive") + + // Create menu + t.createMenu() + + // Create window (hidden by default) + t.window = t.app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Tray Application", + Width: 400, + Height: 600, + Hidden: true, + }) + + // Attach window to tray + t.systray.AttachWindow(t.window) + t.systray.SetWindowOffset(10) + + // Handle tray clicks + t.systray.OnRightClick(func() { + t.systray.OpenMenu() + }) + + // Start background task + go t.backgroundTask() +} + +func (t *TrayApp) createMenu() { + t.menu = t.app.NewMenu() + + // Status item (disabled) + statusItem := t.menu.Add("Status: Inactive") + statusItem.SetEnabled(false) + + t.menu.AddSeparator() + + // Toggle active + t.menu.Add("Start").OnClick(func(ctx *application.Context) { + t.toggleActive() + }) + + // Show window + t.menu.Add("Show Window").OnClick(func(ctx *application.Context) { + t.window.Show() + t.window.SetFocus() + }) + + t.menu.AddSeparator() + + // Settings + t.menu.AddCheckbox("Start at Login", false).OnClick(func(ctx *application.Context) { + enabled := ctx.ClickedMenuItem().Checked() + t.setStartAtLogin(enabled) + }) + + t.menu.AddSeparator() + + // Quit + t.menu.Add("Quit").OnClick(func(ctx *application.Context) { + t.app.Quit() + }) + + t.systray.SetMenu(t.menu) +} + +func (t *TrayApp) toggleActive() { + t.isActive = !t.isActive + t.updateTray() +} + +func (t *TrayApp) updateTray() { + if t.isActive { + t.systray.SetIcon(iconActive) + t.systray.SetLabel("Active") + } else { + t.systray.SetIcon(icon) + t.systray.SetLabel("Inactive") + } + + // Rebuild menu with new status + t.createMenu() +} + +func (t *TrayApp) backgroundTask() { + ticker := time.NewTicker(5 * time.Second) + defer ticker.Stop() + + for range ticker.C { + if t.isActive { + fmt.Println("Background task running...") + // Do work + } + } +} + +func (t *TrayApp) setStartAtLogin(enabled bool) { + // Implementation varies by platform + fmt.Printf("Start at login: %v\n", enabled) +} +``` + +## Visibility Control + +Show/hide the tray icon dynamically: + +```go +// Hide tray icon +systray.Hide() + +// Show tray icon +systray.Show() + +// Check visibility +if systray.IsVisible() { + fmt.Println("Tray icon is visible") +} +``` + +**Platform Support:** + +| Platform | Hide() | Show() | Notes | +|----------|--------|--------|-------| +| **Windows** | ✅ | ✅ | Fully functional - icon appears/disappears from notification area | +| **macOS** | ✅ | ✅ | Menu bar item shows/hides | +| **Linux** | ✅ | ✅ | Varies by desktop environment | + +**Use cases:** +- Temporarily hide tray icon based on user preference +- Headless mode with tray icon appearing only when needed +- Toggle visibility based on application state + +**Example - Conditional Tray Visibility:** + +```go +func (t *TrayApp) setTrayVisibility(visible bool) { + if visible { + t.systray.Show() + } else { + t.systray.Hide() + } +} + +// Show tray only when updates are available +func (t *TrayApp) checkForUpdates() { + if hasUpdates { + t.systray.Show() + t.systray.SetLabel("Update Available") + } else { + t.systray.Hide() + } +} +``` + +## Cleanup + +Destroy the tray icon when done: + +```go +// In OnShutdown +app := application.New(application.Options{ + OnShutdown: func() { + if systray != nil { + systray.Destroy() + } + }, +}) +``` + +**Important:** Always destroy system tray on shutdown to release resources. + +## Best Practices + +### ✅ Do + +- **Use template icons on macOS** - Adapts to dark mode +- **Keep labels short** - 3-5 characters maximum +- **Provide tooltips on Windows** - Helps users identify your app +- **Test on all platforms** - Behaviour varies +- **Handle clicks appropriately** - Left-click for main action, right-click for menu +- **Update icon for status** - Visual feedback is important +- **Destroy on shutdown** - Release resources + +### ❌ Don't + +- **Don't use large icons** - Follow platform guidelines +- **Don't use long labels** - Gets truncated +- **Don't forget dark mode** - Test on macOS dark mode +- **Don't block click handlers** - Keep them fast +- **Don't forget menu.Update()** - After changing menu state +- **Don't assume tray support** - Some Linux DEs don't support it + +## Troubleshooting + +### Tray Icon Not Appearing + +**Possible causes:** +1. Icon format not supported +2. Icon size too large/small +3. System tray not supported (Linux) + +**Solution:** + +```go +// Check if system tray is supported +if !application.SystemTraySupported() { + fmt.Println("System tray not supported") + // Fallback to window-only mode +} +``` + +### Icon Looks Wrong on macOS + +**Cause:** Not using template icon + +**Solution:** + +```go +// Use template icon +systray.SetTemplateIcon(iconBytes) + +// Or design icon as template (black + transparent) +``` + +### Menu Not Updating + +**Cause:** Forgot to call `menu.Update()` + +**Solution:** + +```go +menuItem.SetLabel("New Label") +menu.Update() // Add this! +``` + +## Next Steps + + + + Complete reference for menu item types and properties. + + [Learn More →](/features/menus/reference) + + + + Create application menu bars. + + [Learn More →](/features/menus/application) + + + + Create right-click context menus. + + [Learn More →](/features/menus/context) + + + + Build a complete system tray application. + + [Learn More →](/tutorials/system-tray) + + + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [system tray examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/systray-basic). diff --git a/docs/src/content/docs/features/notifications/overview.mdx b/docs/src/content/docs/features/notifications/overview.mdx new file mode 100644 index 000000000..efacdd74f --- /dev/null +++ b/docs/src/content/docs/features/notifications/overview.mdx @@ -0,0 +1,307 @@ +--- +title: Notifications +description: Display native system notifications with action buttons and text input +sidebar: + order: 1 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +## Introduction + +Wails provides a comprehensive cross-platform notification system for desktop applications. This service allows you to display native system notifications, with support for interactive elements like action buttons and text input fields. + +## Basic Usage + +### Creating the Service + +First, initialize the notifications service: + +```go +import "github.com/wailsapp/wails/v3/pkg/application" +import "github.com/wailsapp/wails/v3/pkg/services/notifications" + +// Create a new notification service +notifier := notifications.New() + +//Register the service with the application +app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(notifier), + }, +}) +``` + +## Notification Authorization + +Notifications on macOS require user authorization. Request and check authorization: + +```go +authorized, err := notifier.CheckNotificationAuthorization() +if err != nil { + // Handle authorization error +} +if authorized { + // Send notifications +} else { + // Request authorization + authorized, err = notifier.RequestNotificationAuthorization() +} +``` +On Windows and Linux this always returns `true`. + +## Notification Types + +### Basic Notifications + +Send a basic notification with a unique id, title, optional subtitle (macOS and Linux), and body text to users: + +```go +notifier.SendNotification(notifications.NotificationOptions{ + ID: "unique-id", + Title: "New Calendar Invite", + Subtitle: "From: Jane Doe", // Optional + Body: "Tap to view the event", +}) + +``` + +### Interactive Notifications +Send a notification with action buttons and text inputs. These notifications require a notification category to be resgistered first: + +```go +// Define a unique category id +categoryID := "unique-category-id" + +// Define a category with actions +category := notifications.NotificationCategory{ + ID: categoryID, + Actions: []notifications.NotificationAction{ + { + ID: "OPEN", + Title: "Open", + }, + { + ID: "ARCHIVE", + Title: "Archive", + Destructive: true, /* macOS-specific */ + }, + }, + HasReplyField: true, + ReplyPlaceholder: "message...", + ReplyButtonTitle: "Reply", +} + +// Register the category +notifier.RegisterNotificationCategory(category) + +// Send an interactive notification with the actions registered in the provided category +notifier.SendNotificationWithActions(notifications.NotificationOptions{ + ID: "unique-id", + Title: "New Message", + Subtitle: "From: Jane Doe", + Body: "Are you able to make it?", + CategoryID: categoryID, +}) +``` + +## Notification Responses + +Process user interactions with notifications: + +```go +notifier.OnNotificationResponse(func(result notifications.NotificationResult) { + response := result.Response + fmt.Printf("Notification %s was actioned with: %s\n", response.ID, response.ActionIdentifier) + + if response.ActionIdentifier == "TEXT_REPLY" { + fmt.Printf("User replied: %s\n", response.UserText) + } + + if data, ok := response.UserInfo["sender"].(string); ok { + fmt.Printf("Original sender: %s\n", data) + } + + // Emit an event to the frontend + app.Event.Emit("notification", result.Response) +}) +``` + +## Notification Customisation + +### Custom Metadata + +Basic and interactive notifications can include custom data: + +```go +notifier.SendNotification(notifications.NotificationOptions{ + ID: "unique-id", + Title: "New Calendar Invite", + Subtitle: "From: Jane Doe", // Optional + Body: "Tap to view the event", + Data: map[string]interface{}{ + "sender": "jane.doe@example.com", + "timestamp": "2025-03-10T15:30:00Z", + } +}) + +``` + +## Platform Considerations + + + + + On macOS, notifications: + + - Require user authorization + - Require the app to be notorized for distribution + - Use system-standard notification appearances + - Support `subtitle` + - Support user text input + - Support the `Destructive` action option + - Automatically handle dark/light mode + + + + + + On Windows, notifications: + + - Use Windows system toast styles + - Adapt to Windows theme settings + - Support user text input + - Support high DPI displays + - Do not support `subtitle` + + + + + + On Linux, dialog behaviour depends on the desktop environment: + + - Use native notifications when available + - Follow desktop environment theme + - Position according to desktop environment rules + - Support `subtitle` + - Do not support user text input + + + + +## Best Practices + +1. Check and request for authorization: + - macOS requires user authorization + +2. Provide clear and concise notifications: + - Use descriptive titles, subtitles, text, and action titles + +3. Handle dialog responses appropriately: + - Check for errors in notification responses + - Provide feedback for user actions + +4. Consider platform conventions: + - Follow platform-specific notification patterns + - Respect system settings + +## Examples + +Explore this example: + +- [Notifications](/examples/notifications) + +## API Reference + +### Service Management +| Method | Description | +|--------------------------------------------|-------------------------------------------------------| +| `New()` | Creates a new notifications service | + +### Notification Authorization +| Method | Description | +|----------------------------------------------|------------------------------------------------------------| +| `RequestNotificationAuthorization()` | Requests permission to display notifications (macOS) | +| `CheckNotificationAuthorization()` | Checks current notification authorization status (macOS) | + +### Sending Notifications +| Method | Description | +|------------------------------------------------------------|---------------------------------------------------| +| `SendNotification(options NotificationOptions)` | Sends a basic notification | +| `SendNotificationWithActions(options NotificationOptions)` | Sends an interactive notification with actions | + +### Notification Categories +| Method | Description | +|---------------------------------------------------------------|---------------------------------------------------| +| `RegisterNotificationCategory(category NotificationCategory)` | Registers a reusable notification category | +| `RemoveNotificationCategory(categoryID string)` | Removes a previously registered category | + +### Managing Notifications +| Method | Description | +|-------------------------------------------------|---------------------------------------------------------------------| +| `RemoveAllPendingNotifications()` | Removes all pending notifications (macOS and Linux only) | +| `RemovePendingNotification(identifier string)` | Removes a specific pending notification (macOS and Linux only) | +| `RemoveAllDeliveredNotifications()` | Removes all delivered notifications (macOS and Linux only) | +| `RemoveDeliveredNotification(identifier string)`| Removes a specific delivered notification (macOS and Linux only) | +| `RemoveNotification(identifier string)` | Removes a notification (Linux-specific) | + +### Event Handling +| Method | Description | +|--------------------------------------------------------------------|-------------------------------------------------| +| `OnNotificationResponse(callback func(result NotificationResult))` | Registers a callback for notification responses | + +### Structs and Types + +#### NotificationOptions +```go +type NotificationOptions struct { + ID string // Unique identifier for the notification + Title string // Main notification title + Subtitle string // Subtitle text (macOS and Linux only) + Body string // Main notification content + CategoryID string // Category identifier for interactive notifications + Data map[string]interface{} // Custom data to associate with the notification +} +``` + +#### NotificationCategory +```go +type NotificationCategory struct { + ID string // Unique identifier for the category + Actions []NotificationAction // Button actions for the notification + HasReplyField bool // Whether to include a text input field + ReplyPlaceholder string // Placeholder text for the input field + ReplyButtonTitle string // Text for the reply button +} +``` + +#### NotificationAction +```go +type NotificationAction struct { + ID string // Unique identifier for the action + Title string // Button text + Destructive bool // Whether the action is destructive (macOS-specific) +} +``` + +#### NotificationResponse +```go +type NotificationResponse struct { + ID string // Notification identifier + ActionIdentifier string // Action that was triggered + CategoryID string // Category of the notification + Title string // Title of the notification + Subtitle string // Subtitle of the notification + Body string // Body text of the notification + UserText string // Text entered by the user + UserInfo map[string]interface{} // Custom data from the notification +} +``` + +#### NotificationResult +```go +type NotificationResult struct { + Response NotificationResponse // Response data + Error error // Any error that occurred +} +``` \ No newline at end of file diff --git a/docs/src/content/docs/features/platform/dock.mdx b/docs/src/content/docs/features/platform/dock.mdx new file mode 100644 index 000000000..2638f7bf2 --- /dev/null +++ b/docs/src/content/docs/features/platform/dock.mdx @@ -0,0 +1,235 @@ +--- +title: Dock & Taskbar +description: Manage dock icon visibility and display badges on macOS and Windows +sidebar: + order: 1 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +## Introduction + +Wails provides a cross-platform Dock service for desktop applications. This service allows you to: + +- Hide and show the application icon in the macOS Dock +- Display badges on your application tile or dock/taskbar icon (macOS and Windows) + +## Basic Usage + +### Creating the Service + +First, initialize the dock service: + +```go +import "github.com/wailsapp/wails/v3/pkg/application" +import "github.com/wailsapp/wails/v3/pkg/services/dock" + +// Create a new Dock service +dockService := dock.New() + +// Register the service with the application +app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(dockService), + }, +}) +``` + +### Creating the Service with Custom Badge Options (Windows Only) + +On Windows, you can customize the badge appearance with various options: + +```go +import "github.com/wailsapp/wails/v3/pkg/application" +import "github.com/wailsapp/wails/v3/pkg/services/dock" +import "image/color" + +// Create a dock service with custom badge options +options := dock.BadgeOptions{ + TextColour: color.RGBA{255, 255, 255, 255}, // White text + BackgroundColour: color.RGBA{0, 0, 255, 255}, // Blue background + FontName: "consolab.ttf", // Bold Consolas font + FontSize: 20, // Font size for single character + SmallFontSize: 14, // Font size for multiple characters +} + +dockService := dock.NewWithOptions(options) + +// Register the service with the application +app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(dockService), + }, +}) +``` + +## Dock Operations + +### Hiding the dock app icon + +Hide the app icon from the macOS Dock: + +```go +// Hide the app icon +dockService.HideAppIcon() +``` + +### Showing the dock app icon + +Show the app icon in the macOS Dock: + +```go +// Show the app icon +dockService.ShowAppIcon() +``` + +## Badge Operations + +### Setting a Badge + +Set a badge on the application tile/dock icon: + +```go +// Set a default badge +dockService.SetBadge("") + +// Set a numeric badge +dockService.SetBadge("3") + +// Set a text badge +dockService.SetBadge("New") +``` + +### Setting a Custom Badge (Windows Only) + +Set a badge with one-off options applied: + +```go +options := dock.BadgeOptions{ + BackgroundColour: color.RGBA{0, 255, 255, 255}, + FontName: "arialb.ttf", // System font + FontSize: 16, + SmallFontSize: 10, + TextColour: color.RGBA{0, 0, 0, 255}, +} + +// Set a default badge +dockService.SetCustomBadge("", options) + +// Set a numeric badge +dockService.SetCustomBadge("3", options) + +// Set a text badge +dockService.SetCustomBadge("New", options) +``` + +### Removing a Badge + +Remove the badge from the application icon: + +```go +dockService.RemoveBadge() +``` + +## Platform Considerations + + + + + On macOS: + + - The dock icon can be **hidden** and **shown** + - Badges are displayed directly on the dock icon + - Badge options are **not customizable** (any options passed to `NewWithOptions`/`SetCustomBadge` are ignored) + - The standard macOS dock badge styling is used and automatically adapts to appearance + - Label overflow is handled by the system + - Providing an empty label displays a default badge of "●" + + + + + + On Windows: + + - Hiding/showing the taskbar icon is not currently supported by this service + - Badges are displayed as an overlay icon in the taskbar + - Badges support text values + - Badge appearance can be customized via `BadgeOptions` + - The application must have a window for badges to display + - A smaller font size is automatically used for multi-character labels + - Label overflow is not handled + - Customization options: + - **TextColour**: Text color (default: white) + - **BackgroundColour**: Badge background color (default: red) + - **FontName**: Font file name (default: "segoeuib.ttf") + - **FontSize**: Font size for single character (default: 18) + - **SmallFontSize**: Font size for multiple characters (default: 14) + + + + + + On Linux: + + - Dock icon visibility and badge functionality are not available + + + + +## Best Practices + +1. **When hiding the dock icon (macOS):** + - Ensure users can still access your app (e.g., via [system tray](https://v3alpha.wails.io/learn/systray/)) + - Include a "Quit" option in your alternative UI + - The app won't appear in Command+Tab switcher + - Open windows remain visible and functional + - Closing all windows may not quit the app (macOS behavior varies) + - Users lose the standard way to quit via Dock right-click + +2. **Use badges sparingly:** + - Too many badge updates can distract users + - Reserve badges for important notifications + +3. **Keep badge text short:** + - Numeric badges are most effective + - On macOS, text badges should be brief + +4. **For Windows badge customization:** + - Ensure high contrast between text and background colors + - Test with different text lengths as font size decreases with length + - Use common system fonts to ensure availability + + +## API Reference + +### Service Management +| Method | Description | +|--------------------------------------------|-------------------------------------------------------| +| `New()` | Creates a new dock service | +| `NewWithOptions(options BadgeOptions)` | Creates a new dock service with custom badge options (Windows only; options are ignored on macOS and Linux) | + +### Dock Operations +| Method | Description | +|--------------------------------|-------------------------------------------------------------| +| `HideAppIcon()` | Hides the app icon from the macOS Dock (macOS only) | +| `ShowAppIcon()` | Shows the app icon in the macOS Dock (macOS only) | + +### Badge Operations +| Method | Description | +|---------------------------------------------------|------------------------------------------------------------| +| `SetBadge(label string) error` | Sets a badge with the specified label | +| `SetCustomBadge(label string, options BadgeOptions) error` | Sets a badge with the specified label and custom styling options (Windows only) | +| `RemoveBadge() error` | Removes the badge from the application icon | + +### Structs and Types + +```go +// Options for customizing badge appearance (Windows only) +type BadgeOptions struct { + TextColour color.RGBA // Color of the badge text + BackgroundColour color.RGBA // Color of the badge background + FontName string // Font file name (e.g., "segoeuib.ttf") + FontSize int // Font size for single character + SmallFontSize int // Font size for multiple characters +} +``` diff --git a/docs/src/content/docs/features/screens/info.mdx b/docs/src/content/docs/features/screens/info.mdx new file mode 100644 index 000000000..a1ade0a27 --- /dev/null +++ b/docs/src/content/docs/features/screens/info.mdx @@ -0,0 +1,466 @@ +--- +title: Screen Information +description: Get information about displays and monitors +sidebar: + order: 1 +--- + +import { Card, CardGrid } from "@astrojs/starlight/components"; + +## Screen Information + +Wails provides a **unified screen API** that works across all platforms. Get screen information, detect multiple monitors, query screen properties (size, position, DPI), identify the primary display, and handle DPI scaling with consistent code. + +## Quick Start + +```go +// Get all screens +screens := app.Screen.GetAll() + +for _, screen := range screens { + fmt.Printf("Screen: %s (%dx%d)\n", + screen.Name, screen.Width, screen.Height) +} + +// Get primary screen +primary := app.Screens.GetPrimary() +fmt.Printf("Primary: %s\n", primary.Name) +``` + +**That's it!** Cross-platform screen information. + +## Getting Screen Information + +### All Screens + +```go +screens := app.Screen.GetAll() + +for _, screen := range screens { + fmt.Printf("ID: %s\n", screen.ID) + fmt.Printf("Name: %s\n", screen.Name) + fmt.Printf("Size: %dx%d\n", screen.Width, screen.Height) + fmt.Printf("Position: %d,%d\n", screen.X, screen.Y) + fmt.Printf("Scale: %.2f\n", screen.ScaleFactor) + fmt.Printf("Primary: %v\n", screen.IsPrimary) + fmt.Println("---") +} +``` + +### Primary Screen + +```go +primary := app.Screens.GetPrimary() + +fmt.Printf("Primary screen: %s\n", primary.Name) +fmt.Printf("Resolution: %dx%d\n", primary.Width, primary.Height) +fmt.Printf("Scale factor: %.2f\n", primary.ScaleFactor) +``` + +### Current Screen + +Get the screen containing a window: + +```go +screen := app.Screens.GetCurrent(window) + +fmt.Printf("Window is on: %s\n", screen.Name) +``` + +### Screen by ID + +```go +screen := app.Screens.GetByID("screen-id") +if screen != nil { + fmt.Printf("Found screen: %s\n", screen.Name) +} +``` + +## Screen Properties + +### Screen Structure + +```go +type Screen struct { + ID string // Unique identifier + Name string // Display name + X int // X position + Y int // Y position + Width int // Width in pixels + Height int // Height in pixels + ScaleFactor float32 // DPI scale (1.0, 1.5, 2.0, etc.) + IsPrimary bool // Is this the primary screen? +} +``` + +### Physical vs Logical Pixels + +```go +screen := app.Screens.GetPrimary() + +// Logical pixels (what you use) +logicalWidth := screen.Width +logicalHeight := screen.Height + +// Physical pixels (actual display) +physicalWidth := int(float32(screen.Width) * screen.ScaleFactor) +physicalHeight := int(float32(screen.Height) * screen.ScaleFactor) + +fmt.Printf("Logical: %dx%d\n", logicalWidth, logicalHeight) +fmt.Printf("Physical: %dx%d\n", physicalWidth, physicalHeight) +fmt.Printf("Scale: %.2f\n", screen.ScaleFactor) +``` + +**Common scale factors:** +- `1.0` - Standard DPI (96 DPI) +- `1.25` - 125% scaling (120 DPI) +- `1.5` - 150% scaling (144 DPI) +- `2.0` - 200% scaling (192 DPI) - Retina +- `3.0` - 300% scaling (288 DPI) - 4K/5K + +## Window Positioning + +### Centre on Screen + +```go +func centreOnScreen(window *application.WebviewWindow, screen *Screen) { + windowWidth, windowHeight := window.Size() + + x := screen.X + (screen.Width-windowWidth)/2 + y := screen.Y + (screen.Height-windowHeight)/2 + + window.SetPosition(x, y) +} +``` + +### Position on Specific Screen + +```go +func moveToScreen(window *application.WebviewWindow, screenIndex int) { + screens := app.Screen.GetAll() + + if screenIndex < 0 || screenIndex >= len(screens) { + return + } + + screen := screens[screenIndex] + + // Centre on target screen + centreOnScreen(window, screen) +} +``` + +### Position Relative to Screen + +```go +// Top-left corner +func positionTopLeft(window *application.WebviewWindow, screen *Screen) { + window.SetPosition(screen.X+10, screen.Y+10) +} + +// Top-right corner +func positionTopRight(window *application.WebviewWindow, screen *Screen) { + windowWidth, _ := window.Size() + window.SetPosition(screen.X+screen.Width-windowWidth-10, screen.Y+10) +} + +// Bottom-right corner +func positionBottomRight(window *application.WebviewWindow, screen *Screen) { + windowWidth, windowHeight := window.Size() + window.SetPosition( + screen.X+screen.Width-windowWidth-10, + screen.Y+screen.Height-windowHeight-10, + ) +} +``` + +## Multi-Monitor Support + +### Detect Multiple Monitors + +```go +func hasMultipleMonitors() bool { + return len(app.Screen.GetAll()) > 1 +} + +func getMonitorCount() int { + return len(app.Screen.GetAll()) +} +``` + +### List All Monitors + +```go +func listMonitors() { + screens := app.Screen.GetAll() + + fmt.Printf("Found %d monitor(s):\n", len(screens)) + + for i, screen := range screens { + primary := "" + if screen.IsPrimary { + primary = " (Primary)" + } + + fmt.Printf("%d. %s%s\n", i+1, screen.Name, primary) + fmt.Printf(" Resolution: %dx%d\n", screen.Width, screen.Height) + fmt.Printf(" Position: %d,%d\n", screen.X, screen.Y) + fmt.Printf(" Scale: %.2fx\n", screen.ScaleFactor) + } +} +``` + +### Choose Monitor + +```go +func chooseMonitor() (*Screen, error) { + screens := app.Screen.GetAll() + + if len(screens) == 1 { + return screens[0], nil + } + + // Show dialog to choose + var options []string + for i, screen := range screens { + primary := "" + if screen.IsPrimary { + primary = " (Primary)" + } + options = append(options, + fmt.Sprintf("%d. %s%s - %dx%d", + i+1, screen.Name, primary, screen.Width, screen.Height)) + } + + // Use dialog to select + // (Implementation depends on your dialog system) + + return screens[0], nil +} +``` + +## Complete Examples + +### Multi-Monitor Window Manager + +```go +type MultiMonitorManager struct { + app *application.Application + windows map[int]*application.WebviewWindow +} + +func NewMultiMonitorManager(app *application.Application) *MultiMonitorManager { + return &MultiMonitorManager{ + app: app, + windows: make(map[int]*application.WebviewWindow), + } +} + +func (m *MultiMonitorManager) CreateWindowOnScreen(screenIndex int) error { + screens := m.app.Screen.GetAll() + + if screenIndex < 0 || screenIndex >= len(screens) { + return errors.New("invalid screen index") + } + + screen := screens[screenIndex] + + // Create window + window := m.app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: fmt.Sprintf("Window on %s", screen.Name), + Width: 800, + Height: 600, + }) + + // Centre on screen + x := screen.X + (screen.Width-800)/2 + y := screen.Y + (screen.Height-600)/2 + window.SetPosition(x, y) + + window.Show() + + m.windows[screenIndex] = window + return nil +} + +func (m *MultiMonitorManager) CreateWindowOnEachScreen() { + screens := m.app.Screen.GetAll() + + for i := range screens { + m.CreateWindowOnScreen(i) + } +} +``` + +### Screen Change Detection + +```go +type ScreenMonitor struct { + app *application.Application + lastScreens []*Screen + changeHandler func([]*Screen) +} + +func NewScreenMonitor(app *application.Application) *ScreenMonitor { + return &ScreenMonitor{ + app: app, + lastScreens: app.Screen.GetAll(), + } +} + +func (sm *ScreenMonitor) OnScreenChange(handler func([]*Screen)) { + sm.changeHandler = handler +} + +func (sm *ScreenMonitor) Start() { + ticker := time.NewTicker(2 * time.Second) + + go func() { + for range ticker.C { + sm.checkScreens() + } + }() +} + +func (sm *ScreenMonitor) checkScreens() { + current := sm.app.Screen.GetAll() + + if len(current) != len(sm.lastScreens) { + sm.lastScreens = current + if sm.changeHandler != nil { + sm.changeHandler(current) + } + } +} +``` + +### DPI-Aware Window Sizing + +```go +func createDPIAwareWindow(screen *Screen) *application.WebviewWindow { + // Base size at 1.0 scale + baseWidth := 800 + baseHeight := 600 + + // Adjust for DPI + width := int(float32(baseWidth) * screen.ScaleFactor) + height := int(float32(baseHeight) * screen.ScaleFactor) + + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "DPI-Aware Window", + Width: width, + Height: height, + }) + + // Centre on screen + x := screen.X + (screen.Width-width)/2 + y := screen.Y + (screen.Height-height)/2 + window.SetPosition(x, y) + + return window +} +``` + +### Screen Layout Visualiser + +```go +func visualiseScreenLayout() string { + screens := app.Screen.GetAll() + + var layout strings.Builder + layout.WriteString("Screen Layout:\n\n") + + for i, screen := range screens { + primary := "" + if screen.IsPrimary { + primary = " [PRIMARY]" + } + + layout.WriteString(fmt.Sprintf("Screen %d: %s%s\n", i+1, screen.Name, primary)) + layout.WriteString(fmt.Sprintf(" Position: (%d, %d)\n", screen.X, screen.Y)) + layout.WriteString(fmt.Sprintf(" Size: %dx%d\n", screen.Width, screen.Height)) + layout.WriteString(fmt.Sprintf(" Scale: %.2fx\n", screen.ScaleFactor)) + layout.WriteString(fmt.Sprintf(" Physical: %dx%d\n", + int(float32(screen.Width)*screen.ScaleFactor), + int(float32(screen.Height)*screen.ScaleFactor))) + layout.WriteString("\n") + } + + return layout.String() +} +``` + +## Best Practices + +### ✅ Do + +- **Check screen count** - Handle single and multiple monitors +- **Use logical pixels** - Wails handles DPI automatically +- **Centre windows** - Better UX than fixed positions +- **Validate positions** - Ensure windows are visible +- **Handle screen changes** - Monitors can be added/removed +- **Test on different DPI** - 100%, 125%, 150%, 200% + +### ❌ Don't + +- **Don't hardcode positions** - Use screen dimensions +- **Don't assume primary screen** - User might have multiple +- **Don't ignore scale factor** - Important for DPI awareness +- **Don't position off-screen** - Validate coordinates +- **Don't forget screen changes** - Laptops dock/undock +- **Don't use physical pixels** - Use logical pixels + +## Platform Differences + +### macOS + +- Retina displays (2x scale factor) +- Multiple displays common +- Coordinate system: (0,0) at bottom-left +- Spaces (virtual desktops) affect positioning + +### Windows + +- Various DPI scaling (100%, 125%, 150%, 200%) +- Multiple displays common +- Coordinate system: (0,0) at top-left +- Per-monitor DPI awareness + +### Linux + +- Varies by desktop environment +- X11 vs Wayland differences +- DPI scaling support varies +- Multiple displays supported + +## Next Steps + + + + Learn about window management. + + [Learn More →](/features/windows/basics) + + + + Configure window appearance. + + [Learn More →](/features/windows/options) + + + + Multi-window patterns. + + [Learn More →](/features/windows/multiple) + + + + Call Go functions from JavaScript. + + [Learn More →](/features/bindings/methods) + + + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [screen examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples). diff --git a/docs/src/content/docs/features/windows/basics.mdx b/docs/src/content/docs/features/windows/basics.mdx new file mode 100644 index 000000000..deed35602 --- /dev/null +++ b/docs/src/content/docs/features/windows/basics.mdx @@ -0,0 +1,616 @@ +--- +title: Window Basics +description: Creating and managing application windows in Wails +sidebar: + order: 1 +--- + +import { Tabs, TabItem, Card, CardGrid } from "@astrojs/starlight/components"; + +## Window Management + +Wails provides a **unified window management API** that works across all platforms. Create windows, control their behaviour, and manage multiple windows with full control over creation, appearance, behaviour, and lifecycle. + +## Quick Start + +```go +package main + +import "github.com/wailsapp/wails/v3/pkg/application" + +func main() { + app := application.New(application.Options{ + Name: "My App", + }) + + // Create a window + window := app.Window.New() + + // Configure it + window.SetTitle("Hello Wails") + window.SetSize(800, 600) + window.Center() + + // Show it + window.Show() + + app.Run() +} +``` + +**That's it!** You have a cross-platform window. + +## Creating Windows + +### Basic Window + +The simplest way to create a window: + +```go +window := app.Window.New() +``` + +**What you get:** +- Default size (800x600) +- Default title (application name) +- WebView ready for your frontend +- Platform-native appearance + +### Window with Options + +Create a window with custom configuration: + +```go +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "My Application", + Width: 1200, + Height: 800, + X: 100, // Position from left + Y: 100, // Position from top + AlwaysOnTop: false, + Frameless: false, + Hidden: false, + MinWidth: 400, + MinHeight: 300, + MaxWidth: 1920, + MaxHeight: 1080, +}) +``` + +**Common options:** + +| Option | Type | Description | +|--------|------|-------------| +| `Title` | `string` | Window title | +| `Width` | `int` | Window width in pixels | +| `Height` | `int` | Window height in pixels | +| `X` | `int` | X position (from left) | +| `Y` | `int` | Y position (from top) | +| `AlwaysOnTop` | `bool` | Keep window above others | +| `Frameless` | `bool` | Remove title bar and borders | +| `Hidden` | `bool` | Start hidden | +| `MinWidth` | `int` | Minimum width | +| `MinHeight` | `int` | Minimum height | +| `MaxWidth` | `int` | Maximum width | +| `MaxHeight` | `int` | Maximum height | + +**See [Window Options](/features/windows/options) for complete list.** + +### Named Windows + +Give windows names for easy retrieval: + +```go +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Name: "main-window", + Title: "Main Application", +}) + +// Later, find it by name +mainWindow := app.GetWindowByName("main-window") +if mainWindow != nil { + mainWindow.Show() +} +``` + +**Use cases:** +- Multiple windows (main, settings, about) +- Finding windows from different parts of your code +- Window communication + +## Controlling Windows + +### Show and Hide + +```go +// Show window +window.Show() + +// Hide window +window.Hide() + +// Check if visible +if window.IsVisible() { + fmt.Println("Window is visible") +} +``` + +**Use cases:** +- Splash screens (show, then hide) +- Settings windows (hide when not needed) +- Popup windows (show on demand) + +### Position and Size + +```go +// Set size +window.SetSize(1024, 768) + +// Set position +window.SetPosition(100, 100) + +// Centre on screen +window.Center() + +// Get current size +width, height := window.Size() + +// Get current position +x, y := window.Position() +``` + +**Coordinate system:** +- (0, 0) is top-left of primary screen +- Positive X goes right +- Positive Y goes down + +### Window State + +```go +// Minimise +window.Minimise() + +// Maximise +window.Maximise() + +// Fullscreen +window.Fullscreen() + +// Restore to normal +window.Restore() + +// Check state +if window.IsMinimised() { + fmt.Println("Window is minimised") +} + +if window.IsMaximised() { + fmt.Println("Window is maximised") +} + +if window.IsFullscreen() { + fmt.Println("Window is fullscreen") +} +``` + +**State transitions:** + +``` +Normal ←→ Minimised +Normal ←→ Maximised +Normal ←→ Fullscreen +``` + +### Title and Appearance + +```go +// Set title +window.SetTitle("My Application - Document.txt") + +// Set background colour +window.SetBackgroundColour(0, 0, 0, 255) // RGBA + +// Set always on top +window.SetAlwaysOnTop(true) + +// Set resizable +window.SetResizable(false) +``` + +### Closing Windows + +```go +// Close window +window.Close() + +// Destroy window (force close) +window.Destroy() +``` + +**Difference:** +- `Close()` - Triggers close event, can be cancelled +- `Destroy()` - Immediate destruction, cannot be cancelled + +## Finding Windows + +### By Name + +```go +window := app.GetWindowByName("settings") +if window != nil { + window.Show() +} +``` + +### By ID + +Every window has a unique ID: + +```go +id := window.ID() +fmt.Printf("Window ID: %d\n", id) + +// Find by ID +foundWindow := app.GetWindowByID(id) +``` + +### Current Window + +Get the currently focused window: + +```go +current := app.Window.Current() +if current != nil { + current.SetTitle("Active Window") +} +``` + +### All Windows + +Get all windows: + +```go +windows := app.Window.GetAll() +fmt.Printf("Total windows: %d\n", len(windows)) + +for _, w := range windows { + fmt.Printf("Window: %s (ID: %d)\n", w.Name(), w.ID()) +} +``` + +## Window Lifecycle + +### Creation + +```go +app.OnWindowCreation(func(window *application.WebviewWindow) { + fmt.Printf("Window created: %s\n", window.Name()) + + // Configure new windows + window.SetMinSize(400, 300) +}) +``` + +### Closing + +```go +window.OnClose(func() bool { + // Return false to cancel close + // Return true to allow close + + if hasUnsavedChanges() { + result := showConfirmdialog("Unsaved changes. Close anyway?") + return result == "yes" + } + return true +}) +``` + +**Important:** `OnClose` only works for user-initiated closes (clicking X button). It doesn't prevent `window.Destroy()`. + +### Destruction + +```go +window.OnDestroy(func() { + fmt.Println("Window destroyed") + // Cleanup resources +}) +``` + +## Multiple Windows + +### Creating Multiple Windows + +```go +// Main window +mainWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Name: "main", + Title: "Main Application", + Width: 1200, + Height: 800, +}) + +// Settings window +settingsWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Name: "settings", + Title: "Settings", + Width: 600, + Height: 400, + Hidden: true, // Start hidden +}) + +// Show settings when needed +settingsWindow.Show() +``` + +### Window Communication + +Windows can communicate via events: + +```go +// In main window +app.Event.Emit("data-updated", map[string]interface{}{ + "value": 42, +}) + +// In settings window +app.Event.On("data-updated", func(event *application.WailsEvent) { + data := event.Data.(map[string]interface{}) + value := data["value"].(int) + fmt.Printf("Received: %d\n", value) +}) +``` + +**See [Events](/features/events/system) for more.** + +### Parent-Child Windows + +```go +// Create child window +childWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Child Window", + Parent: mainWindow, // Set parent +}) +``` + +**Behaviour:** +- Child closes when parent closes +- Child stays above parent (on some platforms) +- Child minimises with parent (on some platforms) + +**Platform support:** +- **macOS:** Full support +- **Windows:** Partial support +- **Linux:** Varies by desktop environment + +## Platform-Specific Features + + + + **Windows-specific features:** + + ```go + // Flash taskbar button + window.Flash(true) // Start flashing + window.Flash(false) // Stop flashing + + // Trigger Windows 11 Snap Assist (Win+Z) + window.SnapAssist() + + // Set window icon + window.SetIcon(iconBytes) + ``` + + **Snap Assist:** + Shows Windows 11 snap layout options for the window. + + **Taskbar flashing:** + Useful for notifications when window is minimised. + + + + **macOS-specific features:** + + ```go + // Transparent title bar + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Mac: application.MacWindow{ + TitleBar: application.MacTitleBar{ + AppearsTransparent: true, + }, + Backdrop: application.MacBackdropTranslucent, + }, + }) + ``` + + **Backdrop types:** + - `MacBackdropNormal` - Standard window + - `MacBackdropTranslucent` - Translucent background + - `MacBackdropTransparent` - Fully transparent + + **Collection behavior:** + Control how windows behave across Spaces: + - `MacWindowCollectionBehaviorCanJoinAllSpaces` - Visible on all Spaces + - `MacWindowCollectionBehaviorFullScreenAuxiliary` - Can overlay fullscreen apps + + **Native fullscreen:** + macOS fullscreen creates a new Space (virtual desktop). + + + + **Linux-specific features:** + + ```go + // Set window icon + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Linux: application.LinuxOptions{ + Icon: iconBytes, + }, + }) + ``` + + **Desktop environment notes:** + - GNOME: Full support + - KDE Plasma: Full support + - XFCE: Partial support + - Others: Varies + + **Tiling window managers (Hyprland, Sway, i3, etc.):** + - `Minimise()` and `Maximise()` may not work as expected - the WM controls window geometry + - `SetSize()` and `SetPosition()` requests are advisory and may be ignored + - `Fullscreen()` typically works as expected + - Some WMs don't support always-on-top + + + +## Common Patterns + +### Splash Screen + +```go +// Create splash screen +splash := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Loading...", + Width: 400, + Height: 300, + Frameless: true, + AlwaysOnTop: true, +}) + +// Show splash +splash.Show() + +// Initialise application +time.Sleep(2 * time.Second) + +// Hide splash, show main window +splash.Close() +mainWindow.Show() +``` + +### Settings Window + +```go +var settingsWindow *application.WebviewWindow + +func showSettings() { + if settingsWindow == nil { + settingsWindow = app.Window.NewWithOptions(application.WebviewWindowOptions{ + Name: "settings", + Title: "Settings", + Width: 600, + Height: 400, + }) + } + + settingsWindow.Show() + settingsWindow.SetFocus() +} +``` + +### Confirm Before Close + +```go +window.OnClose(func() bool { + if hasUnsavedChanges() { + // Show dialog + result := showConfirmdialog("Unsaved changes. Close anyway?") + return result == "yes" + } + return true +}) +``` + +## Best Practices + +### ✅ Do + +- **Name important windows** - Easier to find later +- **Set minimum size** - Prevent unusable layouts +- **Centre windows** - Better UX than random position +- **Handle close events** - Prevent data loss +- **Test on all platforms** - Behaviour varies +- **Use appropriate sizes** - Consider different screen sizes + +### ❌ Don't + +- **Don't create too many windows** - Confusing for users +- **Don't forget to close windows** - Memory leaks +- **Don't hardcode positions** - Different screen sizes +- **Don't ignore platform differences** - Test thoroughly +- **Don't block the UI thread** - Use goroutines for long operations + +## Troubleshooting + +### Window Not Showing + +**Possible causes:** +1. Window created as hidden +2. Window off-screen +3. Window behind other windows + +**Solution:** + +```go +window.Show() +window.Center() +window.SetFocus() +``` + +### Window Wrong Size + +**Cause:** DPI scaling on Windows/Linux + +**Solution:** + +```go +// Wails handles DPI automatically +// Just use logical pixels +window.SetSize(800, 600) +``` + +### Window Closes Immediately + +**Cause:** Application exits when last window closes + +**Solution:** + +```go +app := application.New(application.Options{ + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: false, + }, +}) +``` + +## Next Steps + + + + Complete reference for all window options. + + [Learn More →](/features/windows/options) + + + + Patterns for multi-window applications. + + [Learn More →](/features/windows/multiple) + + + + Create custom window chrome. + + [Learn More →](/features/windows/frameless) + + + + Handle window lifecycle events. + + [Learn More →](/features/windows/events) + + + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [window examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples). diff --git a/docs/src/content/docs/features/windows/events.mdx b/docs/src/content/docs/features/windows/events.mdx new file mode 100644 index 000000000..b45c6596c --- /dev/null +++ b/docs/src/content/docs/features/windows/events.mdx @@ -0,0 +1,693 @@ +--- +title: Window Events +description: Handle window lifecycle and state change events +sidebar: + order: 5 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +## Window Events + +Wails provides **comprehensive event hooks** for window lifecycle and state changes: creation, focus/blur, resize/move, minimise/maximise, and close events. Register callbacks, handle events, and coordinate between windows with simple, consistent APIs. + +## Lifecycle Events + +### OnCreate + +Called when a window is created: + +```go +app.OnWindowCreation(func(window *application.WebviewWindow) { + fmt.Printf("Window created: %s (ID: %d)\n", window.Name(), window.ID()) + + // Configure all new windows + window.SetMinSize(400, 300) + + // Register window-specific handlers + window.OnClose(func() bool { + return confirmClose() + }) +}) +``` + +**Use cases:** +- Configure all windows consistently +- Register event handlers +- Track window creation +- Initialise window-specific resources + +### OnClose + +Called when user attempts to close window: + +```go +window.OnClose(func() bool { + // Return false to cancel close + // Return true to allow close + + if hasUnsavedChanges() { + result := showConfirmdialog("Unsaved changes. Close anyway?") + return result == "yes" + } + return true +}) +``` + +**Important:** +- Only triggered by user actions (clicking X button) +- NOT triggered by `window.Destroy()` +- Can cancel the close by returning `false` + +**Use cases:** +- Confirm before closing +- Save state +- Prevent accidental closure +- Cleanup before close + +**Example with dialog:** + +```go +window.OnClose(func() bool { + if !hasUnsavedChanges() { + return true + } + + // Show confirmation dialog + dialog := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Confirm Close", + Width: 400, + Height: 150, + Parent: window, + AlwaysOnTop: true, + }) + + // Wait for user response + result := waitFordialogResult(dialog) + + return result == "yes" +}) +``` + +### OnDestroy + +Called when window is destroyed: + +```go +window.OnDestroy(func() { + fmt.Printf("Window destroyed: %s\n", window.Name()) + + // Cleanup resources + closeDatabase() + + // Remove from tracking + removeWindowFromRegistry(window.ID()) + + // Update application state + updateWindowCount() +}) +``` + +**Important:** +- Always called when window is destroyed +- Cannot be cancelled +- Last chance to cleanup + +**Use cases:** +- Release resources +- Close connections +- Update application state +- Remove from tracking + +**Example with resource cleanup:** + +```go +type ManagedWindow struct { + window *application.WebviewWindow + db *sql.DB + listeners []func() +} + +func (mw *ManagedWindow) Setup() { + mw.window.OnDestroy(func() { + // Close database + if mw.db != nil { + mw.db.Close() + } + + // Remove event listeners + for _, listener := range mw.listeners { + listener() + } + + // Clear references + mw.db = nil + mw.listeners = nil + }) +} +``` + +## Focus Events + +### OnFocus + +Called when window gains focus: + +```go +window.OnFocus(func() { + fmt.Println("Window gained focus") + + // Update UI + updateTitleBar(true) + + // Refresh data + refreshContent() + + // Notify other windows + app.Event.Emit("window-focused", window.ID()) +}) +``` + +**Use cases:** +- Update UI appearance +- Refresh data +- Resume operations +- Coordinate with other windows + +### OnBlur + +Called when window loses focus: + +```go +window.OnBlur(func() { + fmt.Println("Window lost focus") + + // Update UI + updateTitleBar(false) + + // Pause operations + pauseAnimations() + + // Save state + saveCurrentState() +}) +``` + +**Use cases:** +- Update UI appearance +- Pause operations +- Save state +- Reduce resource usage + +**Example: Focus-aware UI:** + +```go +type FocusAwareWindow struct { + window *application.WebviewWindow + focused bool +} + +func (fw *FocusAwareWindow) Setup() { + fw.window.OnFocus(func() { + fw.focused = true + fw.updateAppearance() + }) + + fw.window.OnBlur(func() { + fw.focused = false + fw.updateAppearance() + }) +} + +func (fw *FocusAwareWindow) updateAppearance() { + if fw.focused { + fw.window.EmitEvent("update-theme", "active") + } else { + fw.window.EmitEvent("update-theme", "inactive") + } +} +``` + +## State Change Events + +### OnMinimise / OnUnMinimise + +Called when window is minimised or restored: + +```go +window.OnMinimise(func() { + fmt.Println("Window minimised") + + // Pause expensive operations + pauseRendering() + + // Save state + saveWindowState() +}) + +window.OnUnMinimise(func() { + fmt.Println("Window restored from minimised") + + // Resume operations + resumeRendering() + + // Refresh data + refreshContent() +}) +``` + +**Use cases:** +- Pause/resume operations +- Save/restore state +- Reduce resource usage +- Update UI + +### OnMaximise / OnUnMaximise + +Called when window is maximised or restored: + +```go +window.OnMaximise(func() { + fmt.Println("Window maximised") + + // Adjust layout + window.EmitEvent("layout-mode", "maximised") + + // Update button icon + updateMaximiseButton("restore") +}) + +window.OnUnMaximise(func() { + fmt.Println("Window restored from maximised") + + // Adjust layout + window.EmitEvent("layout-mode", "normal") + + // Update button icon + updateMaximiseButton("maximise") +}) +``` + +**Use cases:** +- Adjust layout +- Update UI +- Save window state +- Coordinate with other windows + +### OnFullscreen / OnUnFullscreen + +Called when window enters or exits fullscreen: + +```go +window.OnFullscreen(func() { + fmt.Println("Window entered fullscreen") + + // Hide UI chrome + window.EmitEvent("chrome-visibility", false) + + // Adjust layout + window.EmitEvent("layout-mode", "fullscreen") +}) + +window.OnUnFullscreen(func() { + fmt.Println("Window exited fullscreen") + + // Show UI chrome + window.EmitEvent("chrome-visibility", true) + + // Restore layout + window.EmitEvent("layout-mode", "normal") +}) +``` + +**Use cases:** +- Show/hide UI elements +- Adjust layout +- Update controls +- Save preferences + +## Position and Size Events + +### OnMove + +Called when window is moved: + +```go +window.OnMove(func(x, y int) { + fmt.Printf("Window moved to: %d, %d\n", x, y) + + // Save position + saveWindowPosition(x, y) + + // Update related windows + updateRelatedWindowPositions(x, y) +}) +``` + +**Use cases:** +- Save window position +- Update related windows +- Snap to edges +- Multi-monitor handling + +### OnResize + +Called when window is resized: + +```go +window.OnResize(func(width, height int) { + fmt.Printf("Window resized to: %dx%d\n", width, height) + + // Save size + saveWindowSize(width, height) + + // Adjust layout + window.EmitEvent("window-size", map[string]int{ + "width": width, + "height": height, + }) +}) +``` + +**Use cases:** +- Save window size +- Adjust layout +- Update UI +- Responsive design + +**Example: Responsive layout:** + +```go +window.OnResize(func(width, height int) { + var layout string + + if width < 600 { + layout = "compact" + } else if width < 1200 { + layout = "normal" + } else { + layout = "wide" + } + + window.EmitEvent("layout-changed", layout) +}) +``` + +## Complete Example + +Here's a production-ready window with full event handling: + +```go +package main + +import ( + "encoding/json" + "fmt" + "os" + "github.com/wailsapp/wails/v3/pkg/application" +) + +type WindowState struct { + X int `json:"x"` + Y int `json:"y"` + Width int `json:"width"` + Height int `json:"height"` + Maximised bool `json:"maximised"` + Fullscreen bool `json:"fullscreen"` +} + +type ManagedWindow struct { + app *application.Application + window *application.WebviewWindow + state WindowState + dirty bool +} + +func main() { + app := application.New(application.Options{ + Name: "Event Demo", + }) + + mw := &ManagedWindow{app: app} + mw.CreateWindow() + mw.LoadState() + mw.SetupEventHandlers() + + app.Run() +} + +func (mw *ManagedWindow) CreateWindow() { + mw.window = mw.app.Window.NewWithOptions(application.WebviewWindowOptions{ + Name: "main", + Title: "Event Demo", + Width: 800, + Height: 600, + }) +} + +func (mw *ManagedWindow) SetupEventHandlers() { + // Focus events + mw.window.OnFocus(func() { + fmt.Println("Window focused") + mw.window.EmitEvent("focus-state", true) + }) + + mw.window.OnBlur(func() { + fmt.Println("Window blurred") + mw.window.EmitEvent("focus-state", false) + }) + + // State change events + mw.window.OnMinimise(func() { + fmt.Println("Window minimised") + mw.SaveState() + }) + + mw.window.OnUnMinimise(func() { + fmt.Println("Window restored") + }) + + mw.window.OnMaximise(func() { + fmt.Println("Window maximised") + mw.state.Maximised = true + mw.dirty = true + }) + + mw.window.OnUnMaximise(func() { + fmt.Println("Window restored from maximised") + mw.state.Maximised = false + mw.dirty = true + }) + + mw.window.OnFullscreen(func() { + fmt.Println("Window fullscreen") + mw.state.Fullscreen = true + mw.dirty = true + }) + + mw.window.OnUnFullscreen(func() { + fmt.Println("Window exited fullscreen") + mw.state.Fullscreen = false + mw.dirty = true + }) + + // Position and size events + mw.window.OnMove(func(x, y int) { + mw.state.X = x + mw.state.Y = y + mw.dirty = true + }) + + mw.window.OnResize(func(width, height int) { + mw.state.Width = width + mw.state.Height = height + mw.dirty = true + }) + + // Lifecycle events + mw.window.OnClose(func() bool { + if mw.dirty { + mw.SaveState() + } + return true + }) + + mw.window.OnDestroy(func() { + fmt.Println("Window destroyed") + if mw.dirty { + mw.SaveState() + } + }) +} + +func (mw *ManagedWindow) LoadState() { + data, err := os.ReadFile("window-state.json") + if err != nil { + return + } + + if err := json.Unmarshal(data, &mw.state); err != nil { + return + } + + // Restore window state + mw.window.SetPosition(mw.state.X, mw.state.Y) + mw.window.SetSize(mw.state.Width, mw.state.Height) + + if mw.state.Maximised { + mw.window.Maximise() + } + + if mw.state.Fullscreen { + mw.window.Fullscreen() + } +} + +func (mw *ManagedWindow) SaveState() { + data, err := json.Marshal(mw.state) + if err != nil { + return + } + + os.WriteFile("window-state.json", data, 0644) + mw.dirty = false + + fmt.Println("Window state saved") +} +``` + +## Event Coordination + +### Cross-Window Events + +Coordinate between multiple windows: + +```go +// In main window +mainWindow.OnFocus(func() { + // Notify all windows + app.Event.Emit("main-window-focused", nil) +}) + +// In other windows +app.Event.On("main-window-focused", func(event *application.WailsEvent) { + // Update UI + updateRelativeToMain() +}) +``` + +### Event Chains + +Chain events together: + +```go +window.OnMaximise(func() { + // Save state + saveWindowState() + + // Update layout + window.EmitEvent("layout-changed", "maximised") + + // Notify other windows + app.Event.Emit("window-maximised", window.ID()) +}) +``` + +### Debounced Events + +Debounce frequent events: + +```go +var resizeTimer *time.Timer + +window.OnResize(func(width, height int) { + if resizeTimer != nil { + resizeTimer.Stop() + } + + resizeTimer = time.AfterFunc(500*time.Millisecond, func() { + // Save after resize stops + saveWindowSize(width, height) + }) +}) +``` + +## Best Practices + +### ✅ Do + +- **Save state on close** - Restore window position/size +- **Cleanup on destroy** - Release resources +- **Debounce frequent events** - Resize, move +- **Handle focus changes** - Update UI appropriately +- **Coordinate windows** - Use events for communication +- **Test all events** - Ensure handlers work correctly + +### ❌ Don't + +- **Don't block event handlers** - Keep them fast +- **Don't forget cleanup** - Memory leaks +- **Don't ignore errors** - Log or handle them +- **Don't save on every event** - Debounce first +- **Don't create circular events** - Infinite loops +- **Don't forget platform differences** - Test thoroughly + +## Troubleshooting + +### OnClose Not Firing + +**Cause:** Using `window.Destroy()` instead of `window.Close()` + +**Solution:** + +```go +// ✅ Triggers OnClose +window.Close() + +// ❌ Doesn't trigger OnClose +window.Destroy() +``` + +### Events Not Firing + +**Cause:** Handler registered after event occurred + +**Solution:** + +```go +// Register handlers immediately after creation +window := app.Window.New() +window.OnClose(func() bool { return true }) +``` + +### Memory Leaks + +**Cause:** Not cleaning up in OnDestroy + +**Solution:** + +```go +window.OnDestroy(func() { + // Always cleanup + closeResources() + removeReferences() +}) +``` + +## Next Steps + +**Window Basics** - Learn the fundamentals of window management +[Learn More →](/features/windows/basics) + +**Multiple Windows** - Patterns for multi-window applications +[Learn More →](/features/windows/multiple) + +**Events System** - Deep dive into the event system +[Learn More →](/features/events/system) + +**Application Lifecycle** - Understand the application lifecycle +[Learn More →](/concepts/lifecycle) + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples). diff --git a/docs/src/content/docs/features/windows/frameless.mdx b/docs/src/content/docs/features/windows/frameless.mdx new file mode 100644 index 000000000..c13cb9d4d --- /dev/null +++ b/docs/src/content/docs/features/windows/frameless.mdx @@ -0,0 +1,870 @@ +--- +title: Frameless Windows +description: Create custom window chrome with frameless windows +sidebar: + order: 4 +--- + +import { Tabs, TabItem, Card, CardGrid } from "@astrojs/starlight/components"; + +## Frameless Windows + +Wails provides **frameless window support** with CSS-based drag regions and platform-native behaviour. Remove the platform-native title bar for complete control over window chrome, custom designs, and unique user experiences whilst maintaining essential functionality like dragging, resizing, and system controls. + +## Quick Start + +```go +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Frameless App", + Width: 800, + Height: 600, + Frameless: true, +}) +``` + +**CSS for draggable title bar:** + +```css +.titlebar { + --wails-draggable: drag; + height: 40px; + background: #333; +} + +.titlebar button { + --wails-draggable: no-drag; +} +``` + +**HTML:** + +```html +
      + My Application + +
      +``` + +**That's it!** You have a custom title bar. + +## Creating Frameless Windows + +### Basic Frameless Window + +```go +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Frameless: true, + Width: 800, + Height: 600, +}) +``` + +**What you get:** +- No title bar +- No window borders +- No system buttons +- Transparent background (optional) + +**What you need to implement:** +- Draggable area +- Close/minimise/maximise buttons +- Resize handles (if resizable) + +### With Transparent Background + +```go +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Frameless: true, + BackgroundType: application.BackgroundTypeTransparent, +}) +``` + +**Use cases:** +- Rounded corners +- Custom shapes +- Overlay windows +- Splash screens + +## Drag Regions + +### CSS-Based Dragging + +Use the `--wails-draggable` CSS property: + +```css +/* Draggable area */ +.titlebar { + --wails-draggable: drag; +} + +/* Non-draggable elements within draggable area */ +.titlebar button { + --wails-draggable: no-drag; +} +``` + +**Values:** +- `drag` - Area is draggable +- `no-drag` - Area is not draggable (even if parent is) + +### Complete Title Bar Example + +```html +
      +
      My Application
      +
      + + + +
      +
      +``` + +```css +.titlebar { + --wails-draggable: drag; + display: flex; + justify-content: space-between; + align-items: center; + height: 40px; + background: #2c2c2c; + color: white; + padding: 0 16px; +} + +.title { + font-size: 14px; + user-select: none; +} + +.controls { + display: flex; + gap: 8px; +} + +.controls button { + --wails-draggable: no-drag; + width: 32px; + height: 32px; + border: none; + background: transparent; + color: white; + font-size: 16px; + cursor: pointer; + border-radius: 4px; +} + +.controls button:hover { + background: rgba(255, 255, 255, 0.1); +} + +.controls .close:hover { + background: #e81123; +} +``` + +**JavaScript for buttons:** + +```javascript +import { WindowMinimise, WindowMaximise, WindowClose } from '@wailsio/runtime' + +document.querySelector('.minimize').addEventListener('click', WindowMinimise) +document.querySelector('.maximize').addEventListener('click', WindowMaximise) +document.querySelector('.close').addEventListener('click', WindowClose) +``` + +## System Buttons + +### Implementing Close/Minimise/Maximise + +**Go side:** + +```go +type WindowControls struct { + window *application.WebviewWindow +} + +func (wc *WindowControls) Minimise() { + wc.window.Minimise() +} + +func (wc *WindowControls) Maximise() { + if wc.window.IsMaximised() { + wc.window.UnMaximise() + } else { + wc.window.Maximise() + } +} + +func (wc *WindowControls) Close() { + wc.window.Close() +} +``` + +**JavaScript side:** + +```javascript +import { Minimise, Maximise, Close } from './bindings/WindowControls' + +document.querySelector('.minimize').addEventListener('click', Minimise) +document.querySelector('.maximize').addEventListener('click', Maximise) +document.querySelector('.close').addEventListener('click', Close) +``` + +**Or use runtime methods:** + +```javascript +import { + WindowMinimise, + WindowMaximise, + WindowClose +} from '@wailsio/runtime' + +document.querySelector('.minimize').addEventListener('click', WindowMinimise) +document.querySelector('.maximize').addEventListener('click', WindowMaximise) +document.querySelector('.close').addEventListener('click', WindowClose) +``` + +### Toggle Maximise State + +Track maximise state for button icon: + +```javascript +import { WindowIsMaximised, WindowMaximise, WindowUnMaximise } from '@wailsio/runtime' + +async function toggleMaximise() { + const isMaximised = await WindowIsMaximised() + + if (isMaximised) { + await WindowUnMaximise() + } else { + await WindowMaximise() + } + + updateMaximiseButton() +} + +async function updateMaximiseButton() { + const isMaximised = await WindowIsMaximised() + const button = document.querySelector('.maximize') + button.textContent = isMaximised ? '❐' : '□' +} +``` + +## Resize Handles + +### CSS-Based Resize + +Wails provides automatic resize handles for frameless windows: + +```css +/* Enable resize on all edges */ +body { + --wails-resize: all; +} + +/* Or specific edges */ +.resize-top { + --wails-resize: top; +} + +.resize-bottom { + --wails-resize: bottom; +} + +.resize-left { + --wails-resize: left; +} + +.resize-right { + --wails-resize: right; +} + +/* Corners */ +.resize-top-left { + --wails-resize: top-left; +} + +.resize-top-right { + --wails-resize: top-right; +} + +.resize-bottom-left { + --wails-resize: bottom-left; +} + +.resize-bottom-right { + --wails-resize: bottom-right; +} +``` + +**Values:** +- `all` - Resize from all edges +- `top`, `bottom`, `left`, `right` - Specific edges +- `top-left`, `top-right`, `bottom-left`, `bottom-right` - Corners +- `none` - No resize + +### Resize Handle Example + +```html +
      +
      ...
      +
      ...
      +
      +
      +``` + +```css +.resize-handle { + position: absolute; + width: 16px; + height: 16px; +} + +.resize-bottom-right { + --wails-resize: bottom-right; + bottom: 0; + right: 0; + cursor: nwse-resize; +} +``` + +## Platform-Specific Behaviour + + + + **Windows frameless windows:** + + ```go + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Frameless: true, + Windows: application.WindowsOptions{ + DisableFramelessWindowDecorations: false, + }, + }) + ``` + + **Features:** + - Automatic drop shadow + - Snap layouts support (Windows 11) + - Aero Snap support + - DPI scaling + + **Disable decorations:** + ```go + Windows: application.WindowsOptions{ + DisableFramelessWindowDecorations: true, + }, + ``` + + **Snap Assist:** + ```go + // Trigger Windows 11 Snap Assist + window.SnapAssist() + ``` + + **Custom title bar height:** + Windows automatically detects drag regions from CSS. + + + + **macOS frameless windows:** + + ```go + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Frameless: true, + Mac: application.MacOptions{ + TitleBarAppearsTransparent: true, + InvisibleTitleBarHeight: 40, + }, + }) + ``` + + **Features:** + - Native fullscreen support + - Traffic light buttons (optional) + - Vibrancy effects + - Transparent title bar + + **Hide traffic lights:** + ```go + Mac: application.MacOptions{ + TitleBarStyle: application.MacTitleBarStyleHidden, + }, + ``` + + **Invisible title bar:** + Allows dragging whilst hiding the title bar: + ```go + Mac: application.MacOptions{ + InvisibleTitleBarHeight: 40, + }, + ``` + + + + **Linux frameless windows:** + + ```go + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Frameless: true, + }) + ``` + + **Features:** + - Basic frameless support + - CSS drag regions + - Varies by desktop environment + + **Desktop environment notes:** + - **GNOME:** Good support + - **KDE Plasma:** Good support + - **XFCE:** Basic support + - **Tiling WMs:** Limited support + + **Compositor required:** + Transparency requires a compositor (most modern DEs have one). + + + +## Common Patterns + +### Pattern 1: Modern Title Bar + +```html +
      +
      + App Icon +
      +
      My Application
      +
      + + + +
      +
      +``` + +```css +.modern-titlebar { + --wails-draggable: drag; + display: flex; + align-items: center; + height: 40px; + background: linear-gradient(to bottom, #3a3a3a, #2c2c2c); + border-bottom: 1px solid #1a1a1a; + padding: 0 16px; +} + +.app-icon { + --wails-draggable: no-drag; + width: 24px; + height: 24px; + margin-right: 12px; +} + +.title { + flex: 1; + font-size: 13px; + color: #e0e0e0; + user-select: none; +} + +.controls { + display: flex; + gap: 1px; +} + +.controls button { + --wails-draggable: no-drag; + width: 46px; + height: 32px; + border: none; + background: transparent; + color: #e0e0e0; + font-size: 14px; + cursor: pointer; + transition: background 0.2s; +} + +.controls button:hover { + background: rgba(255, 255, 255, 0.1); +} + +.controls .close:hover { + background: #e81123; + color: white; +} +``` + +### Pattern 2: Splash Screen + +```go +splash := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Loading...", + Width: 400, + Height: 300, + Frameless: true, + AlwaysOnTop: true, + BackgroundType: application.BackgroundTypeTransparent, +}) +``` + +```css +body { + background: transparent; + display: flex; + justify-content: center; + align-items: center; +} + +.splash { + background: white; + border-radius: 12px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); + padding: 40px; + text-align: center; +} +``` + +### Pattern 3: Rounded Window + +```go +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Frameless: true, + BackgroundType: application.BackgroundTypeTransparent, +}) +``` + +```css +body { + background: transparent; + margin: 8px; +} + +.window { + background: white; + border-radius: 16px; + box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15); + overflow: hidden; + height: calc(100vh - 16px); +} + +.titlebar { + --wails-draggable: drag; + background: #f5f5f5; + border-bottom: 1px solid #e0e0e0; +} +``` + +### Pattern 4: Overlay Window + +```go +overlay := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Frameless: true, + AlwaysOnTop: true, + BackgroundType: application.BackgroundTypeTransparent, +}) +``` + +```css +body { + background: transparent; +} + +.overlay { + background: rgba(0, 0, 0, 0.8); + backdrop-filter: blur(10px); + border-radius: 8px; + padding: 20px; +} +``` + +## Complete Example + +Here's a production-ready frameless window: + +**Go:** + +```go +package main + +import ( + _ "embed" + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed frontend/dist +var assets embed.FS + +func main() { + app := application.New(application.Options{ + Name: "Frameless App", + }) + + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Frameless Application", + Width: 1000, + Height: 700, + MinWidth: 800, + MinHeight: 600, + Frameless: true, + + Assets: application.AssetOptions{ + Handler: application.AssetFileServerFS(assets), + }, + + Mac: application.MacOptions{ + TitleBarAppearsTransparent: true, + InvisibleTitleBarHeight: 40, + }, + + Windows: application.WindowsOptions{ + DisableFramelessWindowDecorations: false, + }, + }) + + window.Center() + window.Show() + + app.Run() +} +``` + +**HTML:** + +```html + + + + + + +
      +
      +
      Frameless Application
      +
      + + + +
      +
      +
      +

      Hello from Frameless Window!

      +
      +
      + + + +``` + +**CSS:** + +```css +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + background: #f5f5f5; +} + +.window { + height: 100vh; + display: flex; + flex-direction: column; +} + +.titlebar { + --wails-draggable: drag; + display: flex; + justify-content: space-between; + align-items: center; + height: 40px; + background: #ffffff; + border-bottom: 1px solid #e0e0e0; + padding: 0 16px; +} + +.title { + font-size: 13px; + font-weight: 500; + color: #333; + user-select: none; +} + +.controls { + display: flex; + gap: 8px; +} + +.controls button { + --wails-draggable: no-drag; + width: 32px; + height: 32px; + border: none; + background: transparent; + color: #666; + font-size: 16px; + cursor: pointer; + border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s; +} + +.controls button:hover { + background: #f0f0f0; + color: #333; +} + +.controls .close:hover { + background: #e81123; + color: white; +} + +.content { + flex: 1; + padding: 40px; + overflow: auto; +} +``` + +**JavaScript:** + +```javascript +import { + WindowMinimise, + WindowMaximise, + WindowUnMaximise, + WindowIsMaximised, + WindowClose +} from '@wailsio/runtime' + +// Minimise button +document.querySelector('.minimize').addEventListener('click', () => { + WindowMinimise() +}) + +// Maximise/restore button +const maximiseBtn = document.querySelector('.maximize') +maximiseBtn.addEventListener('click', async () => { + const isMaximised = await WindowIsMaximised() + + if (isMaximised) { + await WindowUnMaximise() + } else { + await WindowMaximise() + } + + updateMaximiseButton() +}) + +// Close button +document.querySelector('.close').addEventListener('click', () => { + WindowClose() +}) + +// Update maximise button icon +async function updateMaximiseButton() { + const isMaximised = await WindowIsMaximised() + maximiseBtn.textContent = isMaximised ? '❐' : '□' + maximiseBtn.title = isMaximised ? 'Restore' : 'Maximise' +} + +// Initial state +updateMaximiseButton() +``` + +## Best Practices + +### ✅ Do + +- **Provide draggable area** - Users need to move the window +- **Implement system buttons** - Close, minimise, maximise +- **Set minimum size** - Prevent unusable layouts +- **Test on all platforms** - Behaviour varies +- **Use CSS for drag regions** - Flexible and maintainable +- **Provide visual feedback** - Hover states on buttons + +### ❌ Don't + +- **Don't forget resize handles** - If window is resizable +- **Don't make entire window draggable** - Prevents interaction +- **Don't forget no-drag on buttons** - They won't work +- **Don't use tiny drag areas** - Hard to grab +- **Don't forget platform differences** - Test thoroughly + +## Troubleshooting + +### Window Won't Drag + +**Cause:** Missing `--wails-draggable: drag` + +**Solution:** + +```css +.titlebar { + --wails-draggable: drag; +} +``` + +### Buttons Don't Work + +**Cause:** Buttons are in draggable area + +**Solution:** + +```css +.titlebar button { + --wails-draggable: no-drag; +} +``` + +### Can't Resize Window + +**Cause:** Missing resize handles + +**Solution:** + +```css +body { + --wails-resize: all; +} +``` + +## Next Steps + + + + Learn the fundamentals of window management. + + [Learn More →](/features/windows/basics) + + + + Complete reference for window options. + + [Learn More →](/features/windows/options) + + + + Handle window lifecycle events. + + [Learn More →](/features/windows/events) + + + + Patterns for multi-window applications. + + [Learn More →](/features/windows/multiple) + + + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [frameless example](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/frameless). diff --git a/docs/src/content/docs/features/windows/multiple.mdx b/docs/src/content/docs/features/windows/multiple.mdx new file mode 100644 index 000000000..41d5a18fe --- /dev/null +++ b/docs/src/content/docs/features/windows/multiple.mdx @@ -0,0 +1,814 @@ +--- +title: Multiple Windows +description: Patterns and best practices for multi-window applications +sidebar: + order: 3 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +## Multi-Window Applications + +Wails v3 provides **native multi-window support** for creating settings windows, document windows, tool palettes, and inspector windows. Track windows, enable communication between them, and manage their lifecycle with simple, consistent APIs. + +### Main + Settings Window + +```go +package main + +import "github.com/wailsapp/wails/v3/pkg/application" + +type App struct { + app *application.Application + mainWindow *application.WebviewWindow + settingsWindow *application.WebviewWindow +} + +func main() { + app := &App{} + + app.app = application.New(application.Options{ + Name: "Multi-Window App", + }) + + // Create main window + app.mainWindow = app.app.Window.NewWithOptions(application.WebviewWindowOptions{ + Name: "main", + Title: "Main Application", + Width: 1200, + Height: 800, + }) + + // Create settings window (hidden initially) + app.settingsWindow = app.app.Window.NewWithOptions(application.WebviewWindowOptions{ + Name: "settings", + Title: "Settings", + Width: 600, + Height: 400, + Hidden: true, + }) + + app.app.Run() +} + +// Show settings from main window +func (a *App) ShowSettings() { + if a.settingsWindow != nil { + a.settingsWindow.Show() + a.settingsWindow.SetFocus() + } +} +``` + +**Key points:** +- Main window always visible +- Settings window created but hidden +- Show settings on demand +- Reuse same window (don't create multiple) + +## Window Tracking + +### Get All Windows + +```go +windows := app.Window.GetAll() +fmt.Printf("Total windows: %d\n", len(windows)) + +for _, window := range windows { + fmt.Printf("- %s (ID: %d)\n", window.Name(), window.ID()) +} +``` + +### Find Specific Window + +```go +// By name +settings := app.GetWindowByName("settings") +if settings != nil { + settings.Show() +} + +// By ID +window := app.GetWindowByID(123) + +// Current (focused) window +current := app.Window.Current() +``` + +### Window Registry Pattern + +Track windows in your application: + +```go +type WindowManager struct { + windows map[string]*application.WebviewWindow + mu sync.RWMutex +} + +func (wm *WindowManager) Register(name string, window *application.WebviewWindow) { + wm.mu.Lock() + defer wm.mu.Unlock() + wm.windows[name] = window +} + +func (wm *WindowManager) Get(name string) *application.WebviewWindow { + wm.mu.RLock() + defer wm.mu.RUnlock() + return wm.windows[name] +} + +func (wm *WindowManager) Remove(name string) { + wm.mu.Lock() + defer wm.mu.Unlock() + delete(wm.windows, name) +} +``` + +## Window Communication + +### Using Events + +Windows communicate via the event system: + +```go +// In main window - emit event +app.Event.Emit("settings-changed", map[string]interface{}{ + "theme": "dark", + "fontSize": 14, +}) + +// In settings window - listen for event +app.Event.On("settings-changed", func(event *application.WailsEvent) { + data := event.Data.(map[string]interface{}) + theme := data["theme"].(string) + fontSize := data["fontSize"].(int) + + // Update UI + updateSettings(theme, fontSize) +}) +``` + +### Shared State Pattern + +Use a shared state manager: + +```go +type AppState struct { + theme string + fontSize int + mu sync.RWMutex +} + +var state = &AppState{ + theme: "light", + fontSize: 12, +} + +func (s *AppState) SetTheme(theme string) { + s.mu.Lock() + s.theme = theme + s.mu.Unlock() + + // Notify all windows + app.Event.Emit("theme-changed", theme) +} + +func (s *AppState) GetTheme() string { + s.mu.RLock() + defer s.mu.RUnlock() + return s.theme +} +``` + +### Window-to-Window Messages + +Send messages between specific windows: + +```go +// Get target window +targetWindow := app.GetWindowByName("preview") + +// Emit event to specific window +targetWindow.EmitEvent("update-preview", previewData) +``` + +## Common Patterns + +### Pattern 1: Singleton Windows + +Ensure only one instance of a window exists: + +```go +var settingsWindow *application.WebviewWindow + +func ShowSettings(app *application.Application) { + // Create if doesn't exist + if settingsWindow == nil { + settingsWindow = app.Window.NewWithOptions(application.WebviewWindowOptions{ + Name: "settings", + Title: "Settings", + Width: 600, + Height: 400, + }) + + // Cleanup on close + settingsWindow.OnDestroy(func() { + settingsWindow = nil + }) + } + + // Show and focus + settingsWindow.Show() + settingsWindow.SetFocus() +} +``` + +### Pattern 2: Document Windows + +Multiple instances of the same window type: + +```go +type DocumentWindow struct { + window *application.WebviewWindow + filePath string + modified bool +} + +var documents = make(map[string]*DocumentWindow) + +func OpenDocument(app *application.Application, filePath string) { + // Check if already open + if doc, exists := documents[filePath]; exists { + doc.window.Show() + doc.window.SetFocus() + return + } + + // Create new document window + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: filepath.Base(filePath), + Width: 800, + Height: 600, + }) + + doc := &DocumentWindow{ + window: window, + filePath: filePath, + modified: false, + } + + documents[filePath] = doc + + // Cleanup on close + window.OnDestroy(func() { + delete(documents, filePath) + }) + + // Load document + loadDocument(window, filePath) +} +``` + +### Pattern 3: Tool Palettes + +Floating windows that stay on top: + +```go +func CreateToolPalette(app *application.Application) *application.WebviewWindow { + palette := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Name: "tools", + Title: "Tools", + Width: 200, + Height: 400, + AlwaysOnTop: true, + Resizable: false, + }) + + return palette +} +``` + +### Pattern 4: Modal dialogs + +Child windows that block parent: + +```go +func ShowModaldialog(parent *application.WebviewWindow, title string) { + dialog := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: title, + Width: 400, + Height: 200, + Parent: parent, + AlwaysOnTop: true, + Resizable: false, + }) + + // Disable parent (platform-specific) + parent.SetEnabled(false) + + // Re-enable parent on close + dialog.OnDestroy(func() { + parent.SetEnabled(true) + parent.SetFocus() + }) +} +``` + +### Pattern 5: Inspector/Preview Windows + +Linked windows that update together: + +```go +type EditorApp struct { + editor *application.WebviewWindow + preview *application.WebviewWindow +} + +func (e *EditorApp) UpdatePreview(content string) { + if e.preview != nil && e.preview.IsVisible() { + e.preview.EmitEvent("content-changed", content) + } +} + +func (e *EditorApp) TogglePreview() { + if e.preview == nil { + e.preview = app.Window.NewWithOptions(application.WebviewWindowOptions{ + Name: "preview", + Title: "Preview", + Width: 600, + Height: 800, + }) + + e.preview.OnDestroy(func() { + e.preview = nil + }) + } + + if e.preview.IsVisible() { + e.preview.Hide() + } else { + e.preview.Show() + } +} +``` + +## Parent-Child Relationships + +### Creating Child Windows + +```go +childWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Child Window", + Parent: parentWindow, +}) +``` + +**Behaviour:** +- Child closes when parent closes +- Child stays above parent (on some platforms) +- Child minimises with parent (on some platforms) + +**Platform support:** + +| Feature | macOS | Windows | Linux | +|---------|-------|---------|-------| +| Auto-close | ✅ | ✅ | ⚠️ Varies | +| Stay above | ✅ | ⚠️ Partial | ⚠️ Varies | +| Minimise together | ✅ | ❌ | ⚠️ Varies | + +### Modal Behaviour + +Create modal-like behaviour: + +```go +func ShowModal(parent *application.WebviewWindow) { + modal := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Modal dialog", + Width: 400, + Height: 200, + Parent: parent, + AlwaysOnTop: true, + }) + + // Disable parent interaction + parent.SetEnabled(false) + + // Re-enable on close + modal.OnClose(func() bool { + parent.SetEnabled(true) + parent.SetFocus() + return true + }) +} +``` + +**Note:** True modal behaviour (blocking) varies by platform. + +## Window Lifecycle Management + +### Creation Callbacks + +Be notified when windows are created: + +```go +app.OnWindowCreation(func(window *application.WebviewWindow) { + fmt.Printf("Window created: %s\n", window.Name()) + + // Configure all new windows + window.SetMinSize(400, 300) +}) +``` + +### Destruction Callbacks + +Cleanup when windows are destroyed: + +```go +window.OnDestroy(func() { + fmt.Printf("Window %s destroyed\n", window.Name()) + + // Cleanup resources + cleanup(window.ID()) + + // Remove from tracking + removeFromRegistry(window.Name()) +}) +``` + +### Application Quit Behaviour + +Control when application quits: + +```go +app := application.New(application.Options{ + Mac: application.MacOptions{ + // Don't quit when last window closes + ApplicationShouldTerminateAfterLastWindowClosed: false, + }, +}) +``` + +**Use cases:** +- System tray applications +- Background services +- Menu bar applications (macOS) + +## Memory Management + +### Preventing Leaks + +Always clean up window references: + +```go +var windows = make(map[string]*application.WebviewWindow) + +func CreateWindow(name string) { + window := app.Window.New() + windows[name] = window + + // IMPORTANT: Clean up on destroy + window.OnDestroy(func() { + delete(windows, name) + }) +} +``` + +### Closing vs Destroying + +```go +// Close - triggers OnClose, can be cancelled +window.Close() + +// Destroy - immediate, cannot be cancelled +window.Destroy() +``` + +**Best practice:** Use `Close()` for user-initiated closes, `Destroy()` for cleanup. + +### Resource Cleanup + +```go +type ManagedWindow struct { + window *application.WebviewWindow + resources []io.Closer +} + +func (mw *ManagedWindow) Destroy() { + // Close all resources + for _, resource := range mw.resources { + resource.Close() + } + + // Destroy window + mw.window.Destroy() +} +``` + +## Advanced Patterns + +### Window Pool + +Reuse windows instead of creating new ones: + +```go +type WindowPool struct { + available []*application.WebviewWindow + inUse map[uint]*application.WebviewWindow + mu sync.Mutex +} + +func (wp *WindowPool) Acquire() *application.WebviewWindow { + wp.mu.Lock() + defer wp.mu.Unlock() + + // Reuse available window + if len(wp.available) > 0 { + window := wp.available[0] + wp.available = wp.available[1:] + wp.inUse[window.ID()] = window + return window + } + + // Create new window + window := app.Window.New() + wp.inUse[window.ID()] = window + return window +} + +func (wp *WindowPool) Release(window *application.WebviewWindow) { + wp.mu.Lock() + defer wp.mu.Unlock() + + delete(wp.inUse, window.ID()) + window.Hide() + wp.available = append(wp.available, window) +} +``` + +### Window Groups + +Manage related windows together: + +```go +type WindowGroup struct { + name string + windows []*application.WebviewWindow +} + +func (wg *WindowGroup) Add(window *application.WebviewWindow) { + wg.windows = append(wg.windows, window) +} + +func (wg *WindowGroup) ShowAll() { + for _, window := range wg.windows { + window.Show() + } +} + +func (wg *WindowGroup) HideAll() { + for _, window := range wg.windows { + window.Hide() + } +} + +func (wg *WindowGroup) CloseAll() { + for _, window := range wg.windows { + window.Close() + } +} +``` + +### Workspace Management + +Save and restore window layouts: + +```go +type WindowLayout struct { + Windows []WindowState `json:"windows"` +} + +type WindowState struct { + Name string `json:"name"` + X int `json:"x"` + Y int `json:"y"` + Width int `json:"width"` + Height int `json:"height"` +} + +func SaveLayout() *WindowLayout { + layout := &WindowLayout{} + + for _, window := range app.Window.GetAll() { + x, y := window.Position() + width, height := window.Size() + + layout.Windows = append(layout.Windows, WindowState{ + Name: window.Name(), + X: x, + Y: y, + Width: width, + Height: height, + }) + } + + return layout +} + +func RestoreLayout(layout *WindowLayout) { + for _, state := range layout.Windows { + window := app.GetWindowByName(state.Name) + if window != nil { + window.SetPosition(state.X, state.Y) + window.SetSize(state.Width, state.Height) + } + } +} +``` + +## Complete Example + +Here's a production-ready multi-window application: + +```go +package main + +import ( + "encoding/json" + "os" + "sync" + "github.com/wailsapp/wails/v3/pkg/application" +) + +type MultiWindowApp struct { + app *application.Application + windows map[string]*application.WebviewWindow + mu sync.RWMutex +} + +func main() { + mwa := &MultiWindowApp{ + windows: make(map[string]*application.WebviewWindow), + } + + mwa.app = application.New(application.Options{ + Name: "Multi-Window Application", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: false, + }, + }) + + // Create main window + mwa.CreateMainWindow() + + // Load saved layout + mwa.LoadLayout() + + mwa.app.Run() +} + +func (mwa *MultiWindowApp) CreateMainWindow() { + window := mwa.app.Window.NewWithOptions(application.WebviewWindowOptions{ + Name: "main", + Title: "Main Application", + Width: 1200, + Height: 800, + }) + + mwa.RegisterWindow("main", window) +} + +func (mwa *MultiWindowApp) ShowSettings() { + if window := mwa.GetWindow("settings"); window != nil { + window.Show() + window.SetFocus() + return + } + + window := mwa.app.Window.NewWithOptions(application.WebviewWindowOptions{ + Name: "settings", + Title: "Settings", + Width: 600, + Height: 400, + }) + + mwa.RegisterWindow("settings", window) +} + +func (mwa *MultiWindowApp) OpenDocument(path string) { + name := "doc-" + path + + if window := mwa.GetWindow(name); window != nil { + window.Show() + window.SetFocus() + return + } + + window := mwa.app.Window.NewWithOptions(application.WebviewWindowOptions{ + Name: name, + Title: path, + Width: 800, + Height: 600, + }) + + mwa.RegisterWindow(name, window) +} + +func (mwa *MultiWindowApp) RegisterWindow(name string, window *application.WebviewWindow) { + mwa.mu.Lock() + mwa.windows[name] = window + mwa.mu.Unlock() + + window.OnDestroy(func() { + mwa.UnregisterWindow(name) + }) +} + +func (mwa *MultiWindowApp) UnregisterWindow(name string) { + mwa.mu.Lock() + delete(mwa.windows, name) + mwa.mu.Unlock() +} + +func (mwa *MultiWindowApp) GetWindow(name string) *application.WebviewWindow { + mwa.mu.RLock() + defer mwa.mu.RUnlock() + return mwa.windows[name] +} + +func (mwa *MultiWindowApp) SaveLayout() { + layout := make(map[string]WindowState) + + mwa.mu.RLock() + for name, window := range mwa.windows { + x, y := window.Position() + width, height := window.Size() + + layout[name] = WindowState{ + X: x, + Y: y, + Width: width, + Height: height, + } + } + mwa.mu.RUnlock() + + data, _ := json.Marshal(layout) + os.WriteFile("layout.json", data, 0644) +} + +func (mwa *MultiWindowApp) LoadLayout() { + data, err := os.ReadFile("layout.json") + if err != nil { + return + } + + var layout map[string]WindowState + if err := json.Unmarshal(data, &layout); err != nil { + return + } + + for name, state := range layout { + if window := mwa.GetWindow(name); window != nil { + window.SetPosition(state.X, state.Y) + window.SetSize(state.Width, state.Height) + } + } +} + +type WindowState struct { + X int `json:"x"` + Y int `json:"y"` + Width int `json:"width"` + Height int `json:"height"` +} +``` + +## Best Practices + +### ✅ Do + +- **Track windows** - Keep references for easy access +- **Clean up on destroy** - Prevent memory leaks +- **Use events for communication** - Decoupled architecture +- **Reuse windows** - Don't create duplicates +- **Save/restore layouts** - Better UX +- **Handle window close** - Confirm before closing with unsaved data + +### ❌ Don't + +- **Don't create unlimited windows** - Memory and performance issues +- **Don't forget to clean up** - Memory leaks +- **Don't use global variables carelessly** - Thread-safety issues +- **Don't block window creation** - Create asynchronously if needed +- **Don't ignore platform differences** - Test on all platforms + +## Next Steps + +- [Window Basics](/features/windows/basics) - Learn the fundamentals of window management +- [Window Events](/features/windows/events) - Handle window lifecycle events +- [Events System](/features/events/system) - Deep dive into the event system +- [Frameless Windows](/features/windows/frameless) - Create custom window chrome + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [multi-window example](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/multi-window). diff --git a/docs/src/content/docs/features/windows/options.mdx b/docs/src/content/docs/features/windows/options.mdx new file mode 100644 index 000000000..730e62045 --- /dev/null +++ b/docs/src/content/docs/features/windows/options.mdx @@ -0,0 +1,1094 @@ +--- +title: Window Options +description: Complete reference for WebviewWindowOptions +sidebar: + order: 2 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +## Window Configuration Options + +Wails provides comprehensive window configuration with dozens of options for size, position, appearance, and behaviour. This reference covers all available options across Windows, macOS, and Linux as the **complete reference** for `WebviewWindowOptions`. Every option, every platform, with examples and constraints. + +## WebviewWindowOptions Structure + +```go +type WebviewWindowOptions struct { + // Identity + Name string + Title string + + // Size and Position + Width int + Height int + X int + Y int + MinWidth int + MinHeight int + MaxWidth int + MaxHeight int + + // Initial State + Hidden bool + Frameless bool + Resizable bool + AlwaysOnTop bool + Fullscreen bool + Minimised bool + Maximised bool + WindowState WindowState + + // Appearance + BackgroundColour RGBA + BackgroundType BackgroundType + + // Content + URL string + HTML string + + // Assets + Assets application.AssetOptions + + // Security + ContentProtectionEnabled bool + + // Menu + UseApplicationMenu bool + + // Lifecycle + OnClose func() bool + OnDestroy func() + + // Platform-Specific + Mac MacOptions + Windows WindowsOptions + Linux LinuxOptions +} +``` + +## Core Options + +### Name + +**Type:** `string` +**Default:** Auto-generated UUID +**Platform:** All + +```go +Name: "main-window" +``` + +**Purpose:** Unique identifier for finding windows later. + +**Best practices:** +- Use descriptive names: `"main"`, `"settings"`, `"about"` +- Use kebab-case: `"file-browser"`, `"color-picker"` +- Keep it short and memorable + +**Example:** + +```go +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Name: "settings-window", +}) + +// Later... +settings := app.GetWindowByName("settings-window") +``` + +### Title + +**Type:** `string` +**Default:** Application name +**Platform:** All + +```go +Title: "My Application" +``` + +**Purpose:** Text shown in title bar and taskbar. + +**Dynamic updates:** + +```go +window.SetTitle("My Application - Document.txt") +``` + +### Width / Height + +**Type:** `int` (pixels) +**Default:** 800 x 600 +**Platform:** All +**Constraints:** Must be positive + +```go +Width: 1200, +Height: 800, +``` + +**Purpose:** Initial window size in logical pixels. + +**Notes:** +- Wails handles DPI scaling automatically +- Use logical pixels, not physical pixels +- Consider minimum screen resolution (1024x768) + +**Example sizes:** + +| Use Case | Width | Height | +|----------|-------|--------| +| Small utility | 400 | 300 | +| Standard app | 1024 | 768 | +| Large app | 1440 | 900 | +| Full HD | 1920 | 1080 | + +### X / Y + +**Type:** `int` (pixels) +**Default:** Centred on screen +**Platform:** All + +```go +X: 100, // 100px from left edge +Y: 100, // 100px from top edge +``` + +**Purpose:** Initial window position. + +**Coordinate system:** +- (0, 0) is top-left of primary screen +- Positive X goes right +- Positive Y goes down + +**Example:** + +```go +settings := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Name: "coordinate-window", + InitialPosition: application.WindowXY, // use coordinate system + X: 100, + Y: 100, +}) + +**Best practice:** Use `Center()` to center the window: + +```go +window := app.Window.New() +window.Center() +``` + +### MinWidth / MinHeight + +**Type:** `int` (pixels) +**Default:** 0 (no minimum) +**Platform:** All + +```go +MinWidth: 400, +MinHeight: 300, +``` + +**Purpose:** Prevent window from being too small. + +**Use cases:** +- Prevent broken layouts +- Ensure usability +- Maintain aspect ratio + +**Example:** + +```go +// Prevent window smaller than 400x300 +MinWidth: 400, +MinHeight: 300, +``` + +### MaxWidth / MaxHeight + +**Type:** `int` (pixels) +**Default:** 0 (no maximum) +**Platform:** All + +```go +MaxWidth: 1920, +MaxHeight: 1080, +``` + +**Purpose:** Prevent window from being too large. + +**Use cases:** +- Fixed-size applications +- Prevent excessive resource usage +- Maintain design constraints + +## State Options + +### Hidden + +**Type:** `bool` +**Default:** `false` +**Platform:** All + +```go +Hidden: true, +``` + +**Purpose:** Create window without showing it. + +**Use cases:** +- Background windows +- Windows shown on demand +- Splash screens (create, load, then show) +- Prevent white flash while loading content + +**Platform improvements:** +- **Windows:** Fixed white window flash - window stays invisible until `Show()` is called +- **macOS:** Full support +- **Linux:** Full support + +**Recommended pattern for smooth loading:** + +```go +// Create hidden window +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Name: "main-window", + Hidden: true, + BackgroundColour: application.RGBA{R: 30, G: 30, B: 30, A: 255}, // Match your theme +}) + +// Load content while hidden +// ... content loads ... + +// Show when ready (no flash!) +window.Show() +``` + +**Example:** + +```go +settings := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Name: "settings", + Hidden: true, +}) + +// Show when needed +settings.Show() +``` + +### Frameless + +**Type:** `bool` +**Default:** `false` +**Platform:** All + +```go +Frameless: true, +``` + +**Purpose:** Remove title bar and window borders. + +**Use cases:** +- Custom window chrome +- Splash screens +- Kiosk applications +- Custom-designed windows + +**Important:** You'll need to implement: +- Window dragging +- Close/minimise/maximise buttons +- Resize handles (if resizable) + +**See [Frameless Windows](/features/windows/frameless) for details.** + +### Resizable + +**Type:** `bool` +**Default:** `true` +**Platform:** All + +```go +Resizable: false, +``` + +**Purpose:** Allow/prevent window resizing. + +**Use cases:** +- Fixed-size applications +- Splash screens +- dialogs + +**Note:** Users can still maximise/fullscreen unless you prevent those too. + +### AlwaysOnTop + +**Type:** `bool` +**Default:** `false` +**Platform:** All + +```go +AlwaysOnTop: true, +``` + +**Purpose:** Keep window above all other windows. + +**Use cases:** +- Floating toolbars +- Notifications +- Picture-in-picture +- Timers + +**Platform notes:** +- **macOS:** Full support +- **Windows:** Full support +- **Linux:** Depends on window manager + +### Fullscreen + +**Type:** `bool` +**Default:** `false` +**Platform:** All + +```go +Fullscreen: true, +``` + +**Purpose:** Start in fullscreen mode. + +**Platform behaviour:** +- **macOS:** Creates new Space (virtual desktop) +- **Windows:** Covers taskbar +- **Linux:** Varies by desktop environment + +**Toggle at runtime:** + +```go +window.Fullscreen() +window.UnFullscreen() +``` + +### Minimised / Maximised + +**Type:** `bool` +**Default:** `false` +**Platform:** All + +```go +Minimised: true, // Start minimised +Maximised: true, // Start maximised +``` + +**Purpose:** Set initial window state. + +**Note:** Don't set both to `true` - behaviour is undefined. + +### WindowState + +**Type:** `WindowState` enum +**Default:** `WindowStateNormal` +**Platform:** All + +```go +WindowState: application.WindowStateMaximised, +``` + +**Values:** +- `WindowStateNormal` - Normal window +- `WindowStateMinimised` - Minimised +- `WindowStateMaximised` - Maximised +- `WindowStateFullscreen` - Fullscreen +- `WindowStateHidden` - Hidden + +**Preferred over individual flags:** + +```go +// ✅ Preferred +WindowState: application.WindowStateMaximised, + +// ❌ Avoid +Maximised: true, +``` + +## Appearance Options + +### BackgroundColour + +**Type:** `RGBA` struct +**Default:** White (255, 255, 255, 255) +**Platform:** All + +```go +BackgroundColour: application.RGBA{R: 0, G: 0, H: 0, A: 255}, +``` + +**Purpose:** Window background colour before content loads. + +**Use cases:** +- Match your app's theme +- Prevent white flash on dark themes +- Smooth loading experience + +**Example:** + +```go +// Dark theme +BackgroundColour: application.RGBA{R: 30, G: 30, B: 30, A: 255}, + +// Light theme +BackgroundColour: application.RGBA{R: 255, G: 255, B: 255, A: 255}, +``` + +**Helper method:** + +```go +window.SetBackgroundColour(30, 30, 30, 255) +``` + +### BackgroundType + +**Type:** `BackgroundType` enum +**Default:** `BackgroundTypeSolid` +**Platform:** macOS, Windows (partial) + +```go +BackgroundType: application.BackgroundTypeTranslucent, +``` + +**Values:** +- `BackgroundTypeSolid` - Solid colour +- `BackgroundTypeTransparent` - Fully transparent +- `BackgroundTypeTranslucent` - Semi-transparent blur + +**Platform support:** +- **macOS:** All types supported +- **Windows:** Transparent and Translucent (Windows 11+) +- **Linux:** Solid only + +**Example (macOS):** + +```go +BackgroundType: application.BackgroundTypeTranslucent, +Mac: application.MacOptions{ + Backdrop: application.MacBackdropTranslucent, +}, +``` + +## Content Options + +### URL + +**Type:** `string` +**Default:** Empty (loads from Assets) +**Platform:** All + +```go +URL: "https://example.com", +``` + +**Purpose:** Load external URL instead of embedded assets. + +**Use cases:** +- Development (load from dev server) +- Web-based applications +- Hybrid applications + +**Example:** + +```go +// Development +URL: "http://localhost:5173", + +// Production +Assets: application.AssetOptions{ + Handler: application.AssetFileServerFS(assets), +}, +``` + +### HTML + +**Type:** `string` +**Default:** Empty +**Platform:** All + +```go +HTML: "

      Hello World

      ", +``` + +**Purpose:** Load HTML string directly. + +**Use cases:** +- Simple windows +- Generated content +- Testing + +**Example:** + +```go +HTML: ` + + +Simple Window +

      Hello from Wails!

      + +`, +``` + +### Assets + +**Type:** `AssetOptions` +**Default:** Inherited from application +**Platform:** All + +```go +Assets: application.AssetOptions{ + Handler: application.AssetFileServerFS(assets), +}, +``` + +**Purpose:** Serve embedded frontend assets. + +**See [Build System](/concepts/build-system) for details.** + +### UseApplicationMenu + +**Type:** `bool` +**Default:** `false` +**Platform:** Windows, Linux (no effect on macOS) + +```go +UseApplicationMenu: true, +``` + +**Purpose:** Use the application menu (set via `app.Menu.Set()`) for this window. + +On **macOS**, this option has no effect because macOS always uses a global application menu at the top of the screen. + +On **Windows** and **Linux**, windows don't display a menu by default. Setting `UseApplicationMenu: true` tells the window to use the application-level menu, providing a simple cross-platform solution. + +**Example:** + +```go +// Set the application menu once +menu := app.NewMenu() +menu.AddRole(application.FileMenu) +menu.AddRole(application.EditMenu) +app.Menu.Set(menu) + +// All windows with UseApplicationMenu will display this menu +app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Main Window", + UseApplicationMenu: true, +}) + +app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Second Window", + UseApplicationMenu: true, // Also gets the app menu +}) +``` + +**Notes:** +- If both `UseApplicationMenu` and a window-specific menu are set, the window-specific menu takes priority +- This simplifies cross-platform code by eliminating the need for runtime OS checks +- See [Application Menus](/features/menus/application) for complete menu documentation + +## Input Options + +### EnableFileDrop + +**Type:** `bool` +**Default:** `false` +**Platform:** All + +```go +EnableFileDrop: true, +``` + +**Purpose:** Enable drag-and-drop of files from the operating system into the window. + +When enabled: +- Files dragged from file managers can be dropped into your application +- The `WindowFilesDropped` event fires with the dropped file paths +- Elements with `data-file-drop-target` attribute provide detailed drop information + +**Use cases:** +- File upload interfaces +- Document editors +- Media importers +- Any app that accepts files + +**Example:** + +```go +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "File Uploader", + EnableFileDrop: true, +}) + +// Handle dropped files +window.OnWindowEvent(events.Common.WindowFilesDropped, func(event *application.WindowEvent) { + files := event.Context().DroppedFiles() + details := event.Context().DropTargetDetails() + + for _, file := range files { + fmt.Println("Dropped:", file) + } +}) +``` + +**HTML drop zones:** + +```html + +
      + Drop files here +
      +``` + +**See [File Drop](/features/drag-and-drop/files) for complete documentation.** + +## Security Options + +### ContentProtectionEnabled + +**Type:** `bool` +**Default:** `false` +**Platform:** Windows (10+), macOS + +```go +ContentProtectionEnabled: true, +``` + +**Purpose:** Prevent screen capture of window contents. + +**Platform support:** +- **Windows:** Windows 10 build 19041+ (full), older versions (partial) +- **macOS:** Full support +- **Linux:** Not supported + +**Use cases:** +- Banking applications +- Password managers +- Medical records +- Confidential documents + +**Important notes:** +1. Doesn't prevent physical photography +2. Some tools may bypass protection +3. Part of comprehensive security, not sole protection +4. DevTools windows not protected automatically + +**Example:** + +```go +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Secure Window", + ContentProtectionEnabled: true, +}) + +// Toggle at runtime +window.SetContentProtection(true) +``` + +## Lifecycle Options + +### OnClose + +**Type:** `func() bool` +**Default:** `nil` (always allow close) +**Platform:** All + +```go +OnClose: func() bool { + // Return false to cancel close + // Return true to allow close + return true +}, +``` + +**Purpose:** Handle window close events, optionally prevent closing. + +**Use cases:** +- Confirm before closing with unsaved changes +- Save state before closing +- Prevent accidental closure + +**Example:** + +```go +OnClose: func() bool { + if hasUnsavedChanges() { + result := showConfirmdialog("Unsaved changes. Close anyway?") + return result == "yes" + } + return true +}, +``` + +**Important:** Only triggered by user actions (clicking X), not `window.Destroy()`. + +### OnDestroy + +**Type:** `func()` +**Default:** `nil` +**Platform:** All + +```go +OnDestroy: func() { + // Cleanup code + fmt.Println("Window destroyed") +}, +``` + +**Purpose:** Cleanup when window is destroyed. + +**Use cases:** +- Release resources +- Close connections +- Update application state + +**Example:** + +```go +OnDestroy: func() { + // Close database connection + if db != nil { + db.Close() + } + + // Remove from window list + removeWindow(window.ID()) +}, +``` + +## Platform-Specific Options + +### Mac Options + +```go +Mac: application.MacWindow{ + TitleBar: application.MacTitleBar{ + AppearsTransparent: true, + Hide: false, + HideTitle: true, + FullSizeContent: true, + }, + Backdrop: application.MacBackdropTranslucent, + InvisibleTitleBarHeight: 50, + WindowLevel: application.MacWindowLevelNormal, + CollectionBehavior: application.MacWindowCollectionBehaviorDefault, +}, +``` + +**TitleBar** (`MacTitleBar`) +- `AppearsTransparent` - Makes title bar transparent, content extends into title bar area +- `Hide` - Hides the title bar completely +- `HideTitle` - Hides only the title text +- `FullSizeContent` - Extends content to full window size + +**Backdrop** (`MacBackdrop`) +- `MacBackdropNormal` - Standard opaque background +- `MacBackdropTranslucent` - Blurred translucent background +- `MacBackdropTransparent` - Fully transparent background + +**InvisibleTitleBarHeight** (`int`) +- Height of invisible title bar area (for dragging) +- Useful when title bar is hidden + +**WindowLevel** (`MacWindowLevel`) +- `MacWindowLevelNormal` - Standard window level (default) +- `MacWindowLevelFloating` - Floats above normal windows +- `MacWindowLevelTornOffMenu` - Torn-off menu level +- `MacWindowLevelModalPanel` - Modal panel level +- `MacWindowLevelMainMenu` - Main menu level +- `MacWindowLevelStatus` - Status window level +- `MacWindowLevelPopUpMenu` - Pop-up menu level +- `MacWindowLevelScreenSaver` - Screen saver level + +**CollectionBehavior** (`MacWindowCollectionBehavior`) + +Controls how the window behaves across macOS Spaces and fullscreen. These are bitmask values that can be combined using bitwise OR (`|`). + +**Space behavior:** +- `MacWindowCollectionBehaviorDefault` - Uses FullScreenPrimary (default, backwards compatible) +- `MacWindowCollectionBehaviorCanJoinAllSpaces` - Window appears on all Spaces +- `MacWindowCollectionBehaviorMoveToActiveSpace` - Moves to active Space when shown +- `MacWindowCollectionBehaviorManaged` - Default managed window behavior +- `MacWindowCollectionBehaviorTransient` - Temporary/transient window +- `MacWindowCollectionBehaviorStationary` - Stays stationary during Space switches + +**Window cycling:** +- `MacWindowCollectionBehaviorParticipatesInCycle` - Included in Cmd+` cycling +- `MacWindowCollectionBehaviorIgnoresCycle` - Excluded from Cmd+` cycling + +**Fullscreen behavior:** +- `MacWindowCollectionBehaviorFullScreenPrimary` - Can enter fullscreen mode +- `MacWindowCollectionBehaviorFullScreenAuxiliary` - Can overlay fullscreen apps +- `MacWindowCollectionBehaviorFullScreenNone` - Disables fullscreen capability +- `MacWindowCollectionBehaviorFullScreenAllowsTiling` - Allows side-by-side tiling (macOS 10.11+) +- `MacWindowCollectionBehaviorFullScreenDisallowsTiling` - Prevents tiling (macOS 10.11+) + +**Example - Spotlight-like window:** + +```go +// Window that appears on all Spaces AND can overlay fullscreen apps +Mac: application.MacWindow{ + CollectionBehavior: application.MacWindowCollectionBehaviorCanJoinAllSpaces | + application.MacWindowCollectionBehaviorFullScreenAuxiliary, + WindowLevel: application.MacWindowLevelFloating, +}, +``` + +**Example - Single behavior:** + +```go +// Window that can appear over fullscreen applications +Mac: application.MacWindow{ + CollectionBehavior: application.MacWindowCollectionBehaviorFullScreenAuxiliary, +}, +``` + +### Windows Options + +```go +Windows: application.WindowsOptions{ + DisableWindowIcon: false, + WindowBackdropType: application.WindowsBackdropTypeAuto, + CustomTheme: nil, + DisableFramelessWindowDecorations: false, +}, +``` + +**DisableWindowIcon** (`bool`) +- Remove icon from title bar +- Cleaner appearance + +**WindowBackdropType** (`WindowsBackdropType`) +- `WindowsBackdropTypeAuto` - System default +- `WindowsBackdropTypeNone` - No backdrop +- `WindowsBackdropTypeMica` - Mica material (Windows 11) +- `WindowsBackdropTypeAcrylic` - Acrylic material (Windows 11) +- `WindowsBackdropTypeTabbed` - Tabbed material (Windows 11) + +**CustomTheme** (`*WindowsTheme`) +- Custom colour theme +- Override system colours + +**DisableFramelessWindowDecorations** (`bool`) +- Disable default frameless decorations +- For custom window chrome + +**Example:** + +```go +Windows: application.WindowsOptions{ + WindowBackdropType: application.WindowsBackdropTypeMica, + DisableWindowIcon: true, +}, +``` + +### Linux Options + +```go +Linux: application.LinuxOptions{ + Icon: []byte{/* PNG data */}, + WindowIsTranslucent: false, +}, +``` + +**Icon** (`[]byte`) +- Window icon (PNG format) +- Shown in title bar and taskbar + +**WindowIsTranslucent** (`bool`) +- Enable window translucency +- Requires compositor support + +**Example:** + +```go +//go:embed icon.png +var icon []byte + +Linux: application.LinuxOptions{ + Icon: icon, +}, +``` + +## Application-Level Windows Options + +Some Windows-specific options must be configured at the application level rather than per-window. This is because WebView2 shares a single browser environment per user data path. + +### Browser Flags + +WebView2 browser flags control experimental features and behavior across **all windows** in your application. These must be set in `application.Options.Windows`: + +```go +app := application.New(application.Options{ + Name: "My App", + Windows: application.WindowsOptions{ + // Enable experimental WebView2 features + EnabledFeatures: []string{ + "msWebView2EnableDraggableRegions", + }, + + // Disable specific features + DisabledFeatures: []string{ + "msSmartScreenProtection", // Always disabled by Wails + }, + + // Additional Chromium command-line arguments + AdditionalBrowserArgs: []string{ + "--disable-gpu", + "--remote-debugging-port=9222", + }, + }, +}) +``` + +**EnabledFeatures** (`[]string`) +- List of WebView2 feature flags to enable +- See [WebView2 browser flags](https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/webview-features-flags) for available flags +- Example: `"msWebView2EnableDraggableRegions"` + +**DisabledFeatures** (`[]string`) +- List of WebView2 feature flags to disable +- Wails automatically disables `msSmartScreenProtection` +- Example: `"msExperimentalFeature"` + +**AdditionalBrowserArgs** (`[]string`) +- Chromium command-line arguments passed to the browser process +- Must include the `--` prefix (e.g., `"--remote-debugging-port=9222"`) +- See [Chromium command line switches](https://peter.sh/experiments/chromium-command-line-switches/) for available arguments + +:::caution[Important] +These flags apply globally to ALL windows because WebView2 shares a single browser environment per user data path. You cannot have different browser flags for different windows. +::: + +**Complete Example:** + +```go +package main + +import ( + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Name: "My App", + Windows: application.WindowsOptions{ + // Enable draggable regions feature + EnabledFeatures: []string{ + "msWebView2EnableDraggableRegions", + }, + // Enable remote debugging + AdditionalBrowserArgs: []string{ + "--remote-debugging-port=9222", + }, + }, + }) + + // All windows will use the browser flags configured above + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Main Window", + Width: 1024, + Height: 768, + }) + + window.Show() + app.Run() +} +``` + +## Complete Example + +Here's a production-ready window configuration: + +```go +package main + +import ( + _ "embed" + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed frontend/dist +var assets embed.FS + +//go:embed icon.png +var icon []byte + +func main() { + app := application.New(application.Options{ + Name: "My Application", + }) + + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + // Identity + Name: "main-window", + Title: "My Application", + + // Size and Position + Width: 1200, + Height: 800, + MinWidth: 800, + MinHeight: 600, + + // Initial State + WindowState: application.WindowStateNormal, + Resizable: true, + + // Appearance + BackgroundColour: application.RGBA{R: 255, G: 255, B: 255, A: 255}, + + // Content + Assets: application.AssetOptions{ + Handler: application.AssetFileServerFS(assets), + }, + + // Lifecycle + OnClose: func() bool { + if hasUnsavedChanges() { + result := showConfirmdialog("Unsaved changes. Close anyway?") + return result == "yes" + } + return true + }, + + OnDestroy: func() { + cleanup() + }, + + // Platform-Specific + Mac: application.MacOptions{ + TitleBarAppearsTransparent: true, + Backdrop: application.MacBackdropTranslucent, + }, + + Windows: application.WindowsOptions{ + WindowBackdropType: application.WindowsBackdropTypeMica, + DisableWindowIcon: false, + }, + + Linux: application.LinuxOptions{ + Icon: icon, + }, + }) + + window.Center() + window.Show() + + app.Run() +} +``` + +## Next Steps + +- [Window Basics](/features/windows/basics) - Creating and controlling windows +- [Multiple Windows](/features/windows/multiple) - Multi-window patterns +- [Frameless Windows](/features/windows/frameless) - Custom window chrome +- [Window Events](/features/windows/events) - Lifecycle events + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples). diff --git a/docs/src/content/docs/feedback.mdx b/docs/src/content/docs/feedback.mdx new file mode 100644 index 000000000..afe4a8bfb --- /dev/null +++ b/docs/src/content/docs/feedback.mdx @@ -0,0 +1,86 @@ +--- +title: v3 Alpha Feedback +sidebar: + order: 40 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +We welcome (and encourage) your feedback! Please search for existing tickets or +posts before creating new ones. Here are the different ways to provide feedback: + + + + + If you find a bug, please let us know by posting into the [v3 Alpha Feedback](https://discord.gg/bdj28QNHmT) channel on Discord. + + - The post should clearly state what the bug is and have a simple reproducible example. If the docs are unclear what *should* happen, please include that in the post. + - The post should be given the `Bug` tag. + - Please include the output of `wails doctor` in your post. + - If the bug is behavior that does not align with current documentation, e.g. a window does not resize properly, please do the following: + - Update an existing example in the `v3/example` directory or create a new example in the `v3/examples` folder that clearly shows the issue. + - Open a [PR](https://github.com/wailsapp/wails/pulls) with the title `[v3 alpha test] `. + - Please include a link to the PR in your post. + + :::caution + + _Remember_, unexpected behavior isn't necessarily a bug - it might just not do + what you expect it to do. Use `Suggestions` for this. + + ::: + + + + + + If you have a fix for a bug or an update for documentation, please do the following: + + - Open a pull request on the [Wails repository](https://github.com/wailsapp/wails). The title of the PR should start with `[v3 alpha]`. + - Create a post in the [v3 Alpha Feedback](https://discord.gg/bdj28QNHmT) channel. + - The post should be given the `PR` tag. + - Please include a link to the PR in your post. + + + + + + If you have a suggestion, please let us know by posting into the [v3 Alpha Feedback](https://discord.gg/bdj28QNHmT) channel on Discord: + + - The post should be given the `Suggestion` tag. + + Please feel free to reach out to us on [Discord](https://discord.gg/bdj28QNHmT) if you have any questions. + + + + + + - Posts can be "upvoted" by using the :thumbsup: emoji. Please apply to any posts that are a priority for you. + - Please *don't* just add comments like "+1" or "me too". + - Please feel free to comment if there is more to add to the post, such as "this bug also affect ARM builds" or "Another option would be to ....." + + + + + +There is a list of known issues & work in progress can be found +[here](https://github.com/orgs/wailsapp/projects/6). + +## Things we are looking for feedback on + +- The API + - Is it easy to use? + - Does it do what you expect? + - Is it missing anything? + - Is there anything that should be removed? + - Is it consistent between Go and JS? +- The build system + - Is it easy to use? + - Can we improve it? +- The examples + - Are they clear? + - Do they cover the basics? +- Features + - What features are missing? + - What features are not needed? +- Documentation + - What could be clearer? diff --git a/docs/src/content/docs/getting-started/installation.mdx b/docs/src/content/docs/getting-started/installation.mdx new file mode 100644 index 000000000..9c9d87885 --- /dev/null +++ b/docs/src/content/docs/getting-started/installation.mdx @@ -0,0 +1,114 @@ +--- +title: Installation +sidebar: + order: 10 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +## Supported Platforms + +- Windows 10/11 AMD64/ARM64 +- macOS 10.15+ AMD64 (Can deploy to macOS 10.13+) +- macOS 11.0+ ARM64 +- Ubuntu 24.04 AMD64/ARM64 (other Linux may work too!) + +## Dependencies + +Wails has a number of common dependencies that are required before installation: + + + + + Download Go from the [Go Downloads Page](https://go.dev/dl/). + + Ensure that you follow the official [Go installation instructions](https://go.dev/doc/install). You will also need to ensure that your `PATH` environment variable also includes the path to your `~/go/bin` directory. Restart your terminal and do the following checks: + + - Check Go is installed correctly: `go version` + - Check `~/go/bin` is in your PATH variable + - Mac / Linux: `echo $PATH | grep go/bin` + - Windows: `$env:PATH -split ';' | Where-Object { $_ -like '*\go\bin' }` + + + + + + Although Wails doesn't require npm to be installed, it is needed by most of the bundled templates. + + Download the latest node installer from the [Node Downloads Page](https://nodejs.org/en/download/). It is best to use the latest release as that is what we generally test against. + + Run `npm --version` to verify. + + :::note + If you prefer a different package manager to npm, feel free to use it. You will need to update the project Taskfiles to use it. + ::: + + + + + +## Platform Specific Dependencies + +You will also need to install platform specific dependencies: + + + + + Wails requires that the xcode command line tools are installed. This can be + done by running: + + ```sh + xcode-select --install + ``` + + + + + Wails requires that the [WebView2 Runtime](https://developer.microsoft.com/en-us/microsoft-edge/webview2/) is installed. Almost all Windows installations will already have this installed. You can check using the `wails doctor` command. + + + + + Linux requires the standard `gcc` build tools plus `gtk3` and `webkit2gtk`. Run wails doctor after installation to be shown how to install the dependencies. If your distro/package manager is not supported, please let us know on discord. + + + + + +## Installation + +To install the Wails CLI using Go Modules, run the following commands: + +```shell +go install -v github.com/wailsapp/wails/v3/cmd/wails3@latest +``` + +If you would like to install the latest development version, run the following +commands: + +```shell +git clone https://github.com/wailsapp/wails.git +cd wails +git checkout v3-alpha +cd v3/cmd/wails3 +go install +``` + +When using the development version, all generated projects will use Go's +[replace](https://go.dev/ref/mod#go-mod-file-replace) directive to ensure +projects use the development version of Wails. + +## System Check + +Running `wails3 doctor` will check if you have the correct dependencies +installed. If not, it will advise on what is missing and help on how to rectify +any problems. + +## The `wails3` command appears to be missing? + +If your system is reporting that the `wails3` command is missing, check the +following: + +- Make sure you have followed the above `Go installation guide` correctly and + that the `go/bin` directory is in the `PATH` environment variable. +- Close/Reopen current terminals to pick up the new `PATH` variable. diff --git a/docs/src/content/docs/getting-started/your-first-app.mdx b/docs/src/content/docs/getting-started/your-first-app.mdx new file mode 100644 index 000000000..143e66256 --- /dev/null +++ b/docs/src/content/docs/getting-started/your-first-app.mdx @@ -0,0 +1,272 @@ +--- +title: Your First Application +sidebar: + order: 20 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; +import { Badge } from '@astrojs/starlight/components'; +import { Steps } from "@astrojs/starlight/components"; +import { FileTree } from '@astrojs/starlight/components'; + +import wails_init from '../../../assets/wails_init.mp4'; +import wails_build from '../../../assets/wails_build.mp4'; +import wails_dev from '../../../assets/wails_dev.mp4'; + + +This guide shows you how to create your first Wails v3 application, covering project setup, building, and development workflow. + +
      +
      + + + +1. ## Creating a New Project + + Open your terminal and run the following command to create a new Wails + project: + + ```bash + wails3 init -n myfirstapp + ``` + + This command creates a new directory called `myfirstapp` with all the + necessary files. + + + +2. ## Exploring the Project Structure + + Navigate to the `myfirstapp` directory. You'll find several files and + folders: + + + - build/ Contains files used by the build process + - appicon.png The application icon + - config.yml Build configuration + - Taskfile.yml Build tasks + - darwin/ macOS specific build files + - Info.dev.plist Development configuration + - Info.plist Production configuration + - Taskfile.yml macOS build tasks + - icons.icns macOS application icon + - linux/ Linux specific build files + - Taskfile.yml Linux build tasks + - appimage/ AppImage packaging + - build.sh AppImage build script + - nfpm/ NFPM packaging + - nfpm.yaml Package configuration + - scripts/ Build scripts + - windows/ Windows specific build files + - Taskfile.yml Windows build tasks + - icon.ico Windows application icon + - info.json Application metadata + - wails.exe.manifest Windows manifest file + - nsis/ NSIS installer files + - project.nsi NSIS project file + - wails_tools.nsh NSIS helper scripts + - frontend/ Frontend application files + - index.html Main HTML file + - main.js Main JavaScript file + - package.json NPM package configuration + - public/ Static assets + - Inter Font License.txt Font license + - .gitignore Git ignore file + - README.md Project documentation + - Taskfile.yml Project tasks + - go.mod Go module file + - go.sum Go module checksums + - greetservice.go Greeting service + - main.go Main application code + + + Take a moment to explore these files and familiarize yourself with the + structure. + + :::note + Although Wails v3 uses [Task](https://taskfile.dev/) as its + default build system, there is nothing stopping you from using `make` or any other + alternative build system. + ::: + +3. ## Building Your Application + + To build your application, execute: + + ```bash + wails3 build + ``` + + This command compiles a debug version of your application and saves it in a + new `bin` directory. + + :::note + `wails3 build` is shorthand for `wails3 task build` and will run the `build` task in `Taskfile.yml`. + ::: + + + + + Once built, you can run this like you would any normal application: + + + + + + + ```sh + ./bin/myfirstapp + ``` + + + + + + ```sh + bin\myfirstapp.exe + ``` + + + + + + ```sh + ./bin/myfirstapp + ``` + + + + + + You'll see a simple UI, the starting point for your application. As it is + the debug version, you'll also see logs in the console window. This is + useful for debugging purposes. + +4. ## Dev Mode + + We can also run the application in development mode. This mode allows you to + make changes to your frontend code and see the changes reflected in the + running application without having to rebuild the entire application. + + 1. Open a new terminal window. + 2. Run `wails3 dev`. The application will compile and run in debug mode. + 3. Open `frontend/index.html` in your editor of choice. + 4. Edit the code and change `Please enter your name below` to + `Please enter your name below!!!`. + 5. Save the file. + + This change will reflect in your application immediately. + + Any changes to backend code will trigger a rebuild: + + 1. Open `greetservice.go`. + 2. Change the line that has `return "Hello " + name + "!"` to + `return "Hello there " + name + "!"`. + 3. Save the file. + + The application will update within a matter of seconds. + + + +5. ## Packaging Your Application + + Once your application is ready for distribution, you can create + platform-specific packages: + + + + + + To create a `.app` bundle: + + ```bash + wails3 package + ``` + + This will create a production build and package it into a `.app` bundle in the `bin` directory. + + + + + To create an NSIS installer: + + ```bash + wails3 package + ``` + + This will create a production build and package it into an NSIS installer in the `bin` directory. + + + + + Wails supports multiple package formats for Linux distribution: + + ```bash + # Create all package types (AppImage, deb, rpm, and Arch Linux) + wails3 package + + # Or create specific package types + wails3 task linux:create:appimage # AppImage format + wails3 task linux:create:deb # Debian package + wails3 task linux:create:rpm # Red Hat package + wails3 task linux:create:aur # Arch Linux package + ``` + + + + + + For more detailed information about packaging options and configuration, + check out our [Packaging Guide](/guides/packaging). + +6. ## Setting up Version Control and Module Name + + Your project is created with the placeholder module name `changeme`. It's recommended to update this to match your repository URL: + + 1. Create a new repository on GitHub (or your preferred Git host) + 2. Initialize git in your project directory: + ```bash + git init + git add . + git commit -m "Initial commit" + ``` + 3. Set your remote repository (replace with your repository URL): + ```bash + git remote add origin https://github.com/username/myfirstapp.git + ``` + 4. Update your module name in `go.mod` to match your repository URL: + ```bash + go mod edit -module github.com/username/myfirstapp + ``` + 5. Push your code: + ```bash + git push -u origin main + ``` + + This ensures your Go module name aligns with Go's module naming conventions and makes it easier to share your code. + + :::tip[Pro Tip] + You can automate all of the initialisation steps by using the `-git` flag when creating your project: + ```bash + wails3 init -n myfirstapp -git github.com/username/myfirstapp + ``` + This supports various Git URL formats: + - HTTPS: `https://github.com/username/project` + - SSH: `git@github.com:username/project` or `ssh://git@github.com/username/project` + - Git protocol: `git://github.com/username/project` + - Filesystem: `file:///path/to/project.git` + ::: + + + +## Congratulations! + +You've just created, developed and packaged your first Wails application. +This is just the beginning of what you can achieve with Wails v3. + +## Next Steps + +If you are new to Wails, we recommend reading through our Tutorials next which will be a practical guide through +the various features of Wails. The first tutorial is [Creating a Service](/tutorials/01-creating-a-service). + +If you are a more advanced user, check out our [Guides](/guides) for more detailed information on how to use Wails. diff --git a/docs/src/content/docs/guides/_category_.json b/docs/src/content/docs/guides/_category_.json new file mode 100644 index 000000000..edc4bf6e3 --- /dev/null +++ b/docs/src/content/docs/guides/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Guides", + "position": 3, + "link": { + "type": "generated-index", + "description": "Comprehensive guides for developing Wails applications" + } +} diff --git a/docs/src/content/docs/guides/architecture.mdx b/docs/src/content/docs/guides/architecture.mdx new file mode 100644 index 000000000..72678befd --- /dev/null +++ b/docs/src/content/docs/guides/architecture.mdx @@ -0,0 +1,199 @@ +--- +title: Architecture Patterns +description: Design patterns for Wails applications +sidebar: + order: 7 +--- + +## Overview + +Proven patterns for organising your Wails application. + +## Service Layer Pattern + +### Structure + +``` +app/ +├── main.go +├── services/ +│ ├── user_service.go +│ ├── data_service.go +│ └── file_service.go +└── models/ + └── user.go +``` + +### Implementation + +```go +// Service interface +type UserService interface { + Create(email, password string) (*User, error) + GetByID(id int) (*User, error) + Update(user *User) error + Delete(id int) error +} + +// Implementation +type userService struct { + app *application.Application + db *sql.DB +} + +func NewUserService(app *application.Application, db *sql.DB) UserService { + return &userService{app: app, db: db} +} +``` + +## Repository Pattern + +### Structure + +```go +// Repository interface +type UserRepository interface { + Create(user *User) error + FindByID(id int) (*User, error) + Update(user *User) error + Delete(id int) error +} + +// Service uses repository +type UserService struct { + repo UserRepository +} + +func (s *UserService) Create(email, password string) (*User, error) { + user := &User{Email: email} + return user, s.repo.Create(user) +} +``` + +## Event-Driven Architecture + +### Event Bus + +```go +type EventBus struct { + app *application.Application + listeners map[string][]func(interface{}) + mu sync.RWMutex +} + +func (eb *EventBus) Subscribe(event string, handler func(interface{})) { + eb.mu.Lock() + defer eb.mu.Unlock() + eb.listeners[event] = append(eb.listeners[event], handler) +} + +func (eb *EventBus) Publish(event string, data interface{}) { + eb.mu.RLock() + handlers := eb.listeners[event] + eb.mu.RUnlock() + + for _, handler := range handlers { + go handler(data) + } +} +``` + +### Usage + +```go +// Subscribe +eventBus.Subscribe("user.created", func(data interface{}) { + user := data.(*User) + sendWelcomeEmail(user) +}) + +// Publish +eventBus.Publish("user.created", user) +``` + +## Dependency Injection + +### Manual DI + +```go +type App struct { + userService *UserService + fileService *FileService + db *sql.DB +} + +func NewApp() *App { + db := openDatabase() + + return &App{ + db: db, + userService: NewUserService(db), + fileService: NewFileService(db), + } +} +``` + +### Using Wire + +```go +// wire.go +//go:build wireinject + +func InitializeApp() (*App, error) { + wire.Build( + openDatabase, + NewUserService, + NewFileService, + NewApp, + ) + return nil, nil +} +``` + +## State Management + +### Centralised State + +```go +type AppState struct { + currentUser *User + settings *Settings + mu sync.RWMutex +} + +func (s *AppState) SetUser(user *User) { + s.mu.Lock() + defer s.mu.Unlock() + s.currentUser = user +} + +func (s *AppState) GetUser() *User { + s.mu.RLock() + defer s.mu.RUnlock() + return s.currentUser +} +``` + +## Best Practices + +### ✅ Do + +- Separate concerns +- Use interfaces +- Inject dependencies +- Handle errors properly +- Keep services focused +- Document architecture + +### ❌ Don't + +- Don't create god objects +- Don't tightly couple components +- Don't skip error handling +- Don't ignore concurrency +- Don't over-engineer + +## Next Steps + +- [Security](/guides/security) - Security best practices +- [Best Practices](/features/bindings/best-practices) - Bindings best practices diff --git a/docs/src/content/docs/guides/build/building.mdx b/docs/src/content/docs/guides/build/building.mdx new file mode 100644 index 000000000..64a82b552 --- /dev/null +++ b/docs/src/content/docs/guides/build/building.mdx @@ -0,0 +1,104 @@ +--- +title: Building Applications +description: Build and package your Wails application +sidebar: + order: 1 +--- + +import { Tabs, TabItem, Aside } from "@astrojs/starlight/components"; + +Wails v3 uses [Task](https://taskfile.dev) as its build system. The `wails3 build` and `wails3 package` commands are convenient wrappers around Task. + +## Building + +Build for the current platform: + +```bash +wails3 build +``` + +Build for a specific platform: + +```bash +wails3 build GOOS=windows +wails3 build GOOS=darwin +wails3 build GOOS=linux + +# With architecture +wails3 build GOOS=darwin GOARCH=arm64 + +# Environment variable style works too +GOOS=windows wails3 build +``` + +Output goes to the `bin/` directory. + + + +## Development + +Run your application with hot reload: + +```bash +wails3 dev +``` + +This starts a file watcher that rebuilds and restarts your app on changes. The frontend dev server runs on port 9245 by default. + +```bash +# Custom port +wails3 dev -port 3000 + +# Enable HTTPS +wails3 dev -s +``` + +## Packaging + +Package your app for distribution: + +```bash +wails3 package +wails3 package GOOS=windows +wails3 package GOOS=darwin +wails3 package GOOS=linux +``` + +This creates platform-specific packages: +- **Windows**: NSIS installer — see [Windows Packaging](/guides/build/windows) +- **macOS**: Application bundle (`.app`) — see [macOS Packaging](/guides/build/macos) +- **Linux**: AppImage, deb, and rpm — see [Linux Packaging](/guides/build/linux) + +## Using Task Directly + +For more control, use Task directly: + +```bash +# List available tasks +wails3 task --list + +# Verbose output +wails3 task build -v + +# Dry run +wails3 task --dry + +# Force rebuild +wails3 task build -f + +# Pass variables +wails3 task darwin:build ARCH=amd64 +``` + +Platform-specific tasks like `linux:create:deb` or `darwin:build:universal` are only available through Task. + +## Generating Assets + +Regenerate icons or update build configuration: + +```bash +wails3 generate icons -input build/appicon.png +wails3 update build-assets -name "MyApp" -config build/config.yml -dir build +``` diff --git a/docs/src/content/docs/guides/build/cross-platform.mdx b/docs/src/content/docs/guides/build/cross-platform.mdx new file mode 100644 index 000000000..0685834d4 --- /dev/null +++ b/docs/src/content/docs/guides/build/cross-platform.mdx @@ -0,0 +1,298 @@ +--- +title: Cross-Platform Building +description: Build for multiple platforms from a single machine +sidebar: + order: 2 +--- + +import { Tabs, TabItem, Aside } from "@astrojs/starlight/components"; + +## Quick Start + +Wails v3 supports building for Windows, macOS, and Linux from any host operating system. The build system automatically detects your environment and chooses the right compilation method. + +**Want to cross-compile to macOS and Linux?** Run this once to set up the Docker images (~800MB download): + +```bash +wails3 task setup:docker +``` + +Then build for any platform: + +```bash +# Build for current platform (production by default) +wails3 build + +# Build for specific platforms +wails3 build GOOS=windows +wails3 build GOOS=darwin +wails3 build GOOS=linux + +# Build for ARM64 architecture +wails3 build GOOS=windows GOARCH=arm64 +wails3 build GOOS=darwin GOARCH=arm64 +wails3 build GOOS=linux GOARCH=arm64 + +# Environment variable style also works +GOOS=darwin GOARCH=arm64 wails3 build +``` + +### Windows + +Windows is the simplest cross-compilation target because it doesn't require CGO by default. + +```bash +wails3 build GOOS=windows +``` + +This works from any host OS with no additional setup. Go's built-in cross-compilation handles everything. + +**If your app requires CGO** (e.g., you're using a C library or CGO-dependent package), you'll need Docker when building from macOS or Linux: + +```bash +# One-time setup +wails3 task setup:docker + +# Build with CGO enabled +wails3 task windows:build CGO_ENABLED=1 +``` + +The Taskfile detects `CGO_ENABLED=1` on non-Windows hosts and automatically uses the Docker image. + +### macOS + +macOS builds require CGO for WebView integration, which means cross-compilation needs special tooling. + +```bash +# Build for Apple Silicon (arm64) - default +wails3 build GOOS=darwin + +# Build for Intel (amd64) +wails3 build GOOS=darwin GOARCH=amd64 + +# Build universal binary (both architectures) +wails3 task darwin:build:universal +``` + +**From Linux or Windows**, you'll need to set up Docker first: + +```bash +wails3 task setup:docker +``` + +Once the images are built, the build system detects that you're not on macOS and uses Docker automatically. You don't need to change your build commands. + +Note that cross-compiled macOS binaries are not code-signed. You'll need to sign them on macOS or in CI before distribution. + +### Linux + +Linux builds require CGO for WebView integration. + +```bash +wails3 build GOOS=linux + +# Build for specific architecture +wails3 build GOOS=linux GOARCH=amd64 +wails3 build GOOS=linux GOARCH=arm64 +``` + +**From macOS or Windows**, you'll need to set up Docker first: + +```bash +wails3 task setup:docker +``` + +The build system detects that you're not on Linux and uses Docker automatically. + +**On Linux without a C compiler**, the build system checks for `gcc` or `clang`. If neither is found, it falls back to Docker. This is useful for minimal containers or systems without build tools installed. You can either: + +1. Install a C compiler: `sudo apt install build-essential` (Debian/Ubuntu) or `sudo pacman -S base-devel` (Arch) +2. Build the Docker image and let it be used automatically + +### ARM Architecture + +All platforms support ARM64 cross-compilation using `GOARCH`: + +```bash +# Windows ARM64 (Surface Pro X, Windows on ARM) +wails3 build GOOS=windows GOARCH=arm64 + +# Linux ARM64 (Raspberry Pi 4/5, AWS Graviton) +wails3 build GOOS=linux GOARCH=arm64 + +# macOS ARM64 (Apple Silicon - this is the default on macOS) +wails3 build GOOS=darwin GOARCH=arm64 + +# macOS Intel (amd64) +wails3 build GOOS=darwin GOARCH=amd64 +``` + +The Docker image includes Zig cross-compiler targets for both amd64 and arm64 on all platforms, so ARM builds work from any host: + +| Build ARM64 for | From Windows | From macOS | From Linux | +|-----------------|--------------|------------|------------| +| **Windows ARM64** | Native Go | Native Go | Native Go | +| **macOS ARM64** | Docker | Native | Docker | +| **Linux ARM64** | Docker | Docker | Docker* | + +*Linux ARM64 from Linux x86_64 uses Docker because CGO cross-compilation requires a different toolchain. + +## How It Works + +### Cross-Compilation Matrix + +| Host → Target | Windows | macOS | Linux | +|---------------|---------|-------|-------| +| **Windows** | Native | Docker | Docker | +| **macOS** | Native Go | Native | Docker | +| **Linux** | Native Go | Docker | Native | + +- **Native** = Platform's native toolchain, no additional setup +- **Native Go** = Go's built-in cross-compilation (`CGO_ENABLED=0`) +- **Docker** = Docker image with Zig cross-compiler + +### CGO Requirements + +| Target | CGO Required | Cross-Compilation Method | +|--------|--------------|--------------------------| +| Windows | No (default) | Native Go. Docker only if `CGO_ENABLED=1` | +| macOS | Yes | Docker with macOS SDK | +| Linux | Yes | Docker, or native if C compiler available | + +### Auto-Detection + +The Taskfiles automatically choose the right build method based on your environment: + +- **Windows target:** Uses native Go cross-compilation by default. If you explicitly set `CGO_ENABLED=1` on a non-Windows host, it switches to Docker. +- **macOS target:** Uses Docker automatically when not on macOS. No manual intervention needed. +- **Linux target:** Checks for `gcc` or `clang`. Uses native compilation if found, otherwise falls back to Docker. + +### Docker Image + +Wails uses a single Docker image (`wails-cross`) that can build for all platforms. It uses [Zig](https://ziglang.org/) as the cross-compiler, which can target any platform from any host. The macOS SDK is included for darwin targets. + +```bash +wails3 task setup:docker +``` + +You can check if the image is ready by running `wails3 doctor`. + +### macOS SDK + +The Docker image downloads the macOS SDK from [joseluisq/macosx-sdks](https://github.com/joseluisq/macosx-sdks) during the image build process. This is required because macOS headers are needed for CGO compilation. + +By default, the image uses **macOS SDK 14.5** (Sonoma). To use a different version, rebuild with: + +```bash +docker build -t wails-cross \ + --build-arg MACOS_SDK_VERSION=15.0 \ + -f build/docker/Dockerfile.cross build/docker/ +``` + +See the [available SDK versions](https://github.com/joseluisq/macosx-sdks/releases) for options. + +**Important:** Wails does not distribute the macOS SDK. Users are responsible for reviewing Apple's SDK license terms before using this feature. + +## CI/CD Integration + +For production releases, we recommend using CI/CD with native runners for each platform. This avoids cross-compilation entirely and ensures you get properly signed binaries. + +```yaml +name: Build + +on: + push: + branches: [main] + +jobs: + build: + strategy: + matrix: + include: + - os: ubuntu-latest + goos: linux + - os: macos-latest + goos: darwin + - os: windows-latest + goos: windows + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version: '1.24' + + - uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install Wails CLI + run: go install github.com/wailsapp/wails/v3/cmd/wails3@latest + + - name: Install Task + uses: arduino/setup-task@v2 + + - name: Build + run: wails3 build + + - uses: actions/upload-artifact@v4 + with: + name: app-${{ matrix.goos }} + path: bin/ +``` + +## Troubleshooting + +### Docker image not found + +``` +Docker image 'wails-cross' not found. +``` + +Run `wails3 task setup:docker` to build the Docker image. You only need to do this once. + +### Docker daemon not running + +``` +Docker is required for cross-compilation. Please install Docker. +``` + +Start Docker Desktop or the Docker daemon. On Linux, you may need to run `sudo systemctl start docker`. + +### No C compiler on Linux + +If you see CGO-related errors when building on Linux, you have two options: + +1. **Install a C compiler:** + - Debian/Ubuntu: `sudo apt install build-essential` + - Arch Linux: `sudo pacman -S base-devel` + - Fedora: `sudo dnf install gcc` + +2. **Use Docker instead:** Run `wails3 task setup:docker` and the Taskfile will use it automatically when no compiler is detected. + +### macOS binaries not signed + +Cross-compiled macOS binaries are not code-signed. Apple requires code signing for distribution, so you'll need to: + +1. Sign the binary on a macOS machine, or +2. Sign in CI using a macOS runner + +See [Signing Applications](/guides/build/signing) for details. + +### Universal binary creation + +Universal binaries (arm64 + amd64 combined) can be built on any platform: + +```bash +wails3 task darwin:build:universal +``` + +On Linux and Windows, Wails uses its built-in `wails3 tool lipo` command (powered by [konoui/lipo](https://github.com/konoui/lipo)) to combine the binaries. This creates a single binary that runs natively on both Apple Silicon and Intel Macs. + +## Next Steps + +- [Building Applications](/guides/build/building) - Basic build commands and options +- [Signing Applications](/guides/build/signing) - Code signing for distribution diff --git a/docs/src/content/docs/guides/build/customization.mdx b/docs/src/content/docs/guides/build/customization.mdx new file mode 100644 index 000000000..993fb1ee5 --- /dev/null +++ b/docs/src/content/docs/guides/build/customization.mdx @@ -0,0 +1,282 @@ +--- +title: Build Customization +description: Customize your build process using Task and Taskfile.yml +sidebar: + order: 7 +--- + +import { FileTree } from "@astrojs/starlight/components"; + +## Overview + +The Wails build system is a flexible and powerful tool designed to streamline +the build process for your Wails applications. It leverages +[Task](https://taskfile.dev), a task runner that allows you to define and run +tasks easily. While the v3 build system is the default, Wails encourages a +"bring your own tooling" approach, allowing developers to customize their build +process as needed. + +Learn more about how to use Task in the +[official documentation](https://taskfile.dev/usage/). + +## Task: The Heart of the Build System + +[Task](https://taskfile.dev) is a modern alternative to Make, written in Go. It +uses a YAML file to define tasks and their dependencies. In the Wails build +system, [Task](https://taskfile.dev) plays a central role in orchestrating the +build process. + +The main `Taskfile.yml` is located in the project root, while platform-specific +tasks are defined in `build//Taskfile.yml` files. A common `Taskfile.yml` +file in the `build` directory contains common tasks that are shared across +platforms. + + + +- Project Root + - Taskfile.yml + - build + - windows/Taskfile.yml + - darwin/Taskfile.yml + - linux/Taskfile.yml + - Taskfile.yml + + + +## Taskfile.yml + +The `Taskfile.yml` file in the project root is the main entry point for the build system. It defines +the tasks and their dependencies. Here's the default `Taskfile.yml` file: + +```yaml +version: '3' + +includes: + common: ./build/Taskfile.yml + windows: ./build/windows/Taskfile.yml + darwin: ./build/darwin/Taskfile.yml + linux: ./build/linux/Taskfile.yml + +vars: + APP_NAME: "myproject" + BIN_DIR: "bin" + VITE_PORT: '{{.WAILS_VITE_PORT | default 9245}}' + +tasks: + build: + summary: Builds the application + cmds: + - task: "{{OS}}:build" + + package: + summary: Packages a production build of the application + cmds: + - task: "{{OS}}:package" + + run: + summary: Runs the application + cmds: + - task: "{{OS}}:run" + + dev: + summary: Runs the application in development mode + cmds: + - wails3 dev -config ./build/config.yml -port {{.VITE_PORT}} + + +``` + +## Platform-Specific Taskfiles + +Each platform has its own Taskfile, located in the platform directories beneath the `build` directory. These +files define the core tasks for that platform. Each taskfile includes common tasks from the `build/Taskfile.yml` file. + +### Windows + +Location: `build/windows/Taskfile.yml` + +The Windows-specific Taskfile includes tasks for building, packaging, and +running the application on Windows. Key features include: + +- Building with optional production flags +- Generating `.ico` icon file +- Generating Windows `.syso` file +- Creating an NSIS installer for packaging + +### Linux + +Location: `build/linux/Taskfile.yml` + +The Linux-specific Taskfile includes tasks for building, packaging, and running +the application on Linux. Key features include: + +- Building with optional production flags +- Creating an AppImage, deb, rpm, and Arch Linux packages +- Generating `.desktop` file for Linux applications + +### macOS + +Location: `build/darwin/Taskfile.yml` + +The macOS-specific Taskfile includes tasks for building, packaging, and running +the application on macOS. Key features include: + +- Building binaries for amd64, arm64 and universal (both) architectures +- Generating `.icns` icon file +- Creating an `.app` bundle for distributing +- Ad-hoc signing `.app` bundles +- Setting macOS-specific build flags and environment variables + +## Task Execution and Command Aliases + +The `wails3 task` command is an embedded version of [Taskfile](https://taskfile.dev), which executes +the tasks defined in your `Taskfile.yml`. + +The `wails3 build` and `wails3 package` commands are aliases for +`wails3 task build` and `wails3 task package` respectively. When you run these +commands, Wails internally translates them to the appropriate task execution: + +- `wails3 build` → `wails3 task build` +- `wails3 package` → `wails3 task package` + +### Passing Parameters to Tasks + +You can pass CLI variables to tasks using the `KEY=VALUE` format. These variables are forwarded through the alias commands: + +```bash +# These are equivalent: +wails3 build PLATFORM=linux CONFIG=production +wails3 task build PLATFORM=linux CONFIG=production + +# Package with custom version: +wails3 package VERSION=2.0.0 OUTPUT=myapp.pkg +``` + +In your `Taskfile.yml`, you can access these variables using Go template syntax: + +```yaml +tasks: + build: + cmds: + - echo "Building for {{.PLATFORM | default "darwin"}}" + - go build -tags {{.CONFIG | default "debug"}} -o myapp +``` + +## Common Build Process + +Across all platforms, the build process typically includes the following steps: + +1. Tidying Go modules +2. Building the frontend +3. Generating icons +4. Compiling the Go code with platform-specific flags +5. Packaging the application (platform-specific) + +## Customising the Build Process + +While the v3 build system provides a solid default configuration, you can easily +customise it to fit your project's needs. By modifying the `Taskfile.yml` and +platform-specific Taskfiles, you can: + +- Add new tasks +- Modify existing tasks +- Change the order of task execution +- Integrate with other tools and scripts + +This flexibility allows you to tailor the build process to your specific +requirements while still benefiting from the structure provided by the Wails +build system. + +:::tip[Learning Taskfile] +We highly recommend reading the [Taskfile](https://taskfile.dev) documentation to +understand how to use Taskfile effectively. +You can find out which version of Taskfile is embedded in the Wails CLI by running `wails3 task --version`. +::: + +## Development Mode + +The Wails build system includes a powerful development mode that enhances the +developer experience by providing live reloading and hot module replacement. +This mode is activated using the `wails3 dev` command. + +### How It Works + +When you run `wails3 dev`, the following process occurs: + +1. The command checks for an available port, defaulting to 9245 if not + specified. +2. It sets up the environment variables for the frontend dev server (Vite). +3. It starts the file watcher using the [refresh](https://github.com/atterpac/refresh) library. + +The [refresh](https://github.com/atterpac/refresh) library is responsible for +monitoring file changes and triggering rebuilds. It uses the configuration defined under the `dev_mode` key in the `./build/config.yaml` file. +It may be configured to ignore certain directories and files, to determine which files to watch and what actions to take when changes are detected. +The default configuration works pretty well, but feel free to customise it to your needs. + +### Configuration + +Here's an example of its structure: + +```yaml +dev_mode: + root_path: . + log_level: warn + debounce: 1000 + ignore: + dir: + - .git + - node_modules + - frontend + - bin + file: + - .DS_Store + - .gitignore + - .gitkeep + watched_extension: + - "*.go" + git_ignore: true + executes: + - cmd: wails3 task common:install:frontend:deps + type: once + - cmd: wails3 task common:dev:frontend + type: background + - cmd: go mod tidy + type: blocking + - cmd: wails3 task build + type: blocking + - cmd: wails3 task run + type: primary +``` + +This configuration file allows you to: + +- Set the root path for file watching +- Configure logging level +- Set a debounce time for file change events +- Ignore specific directories, files, or file extensions +- Define commands to execute on file changes + +### Customising Development Mode + +You can customise the development mode experience by modifying these values in the `config.yaml` file. + +Some ways to customise include: + +1. Changing the watched directories or files +2. Adjusting the debounce time to control how quickly the system responds to + changes +3. Adding or modifying the execute commands to fit your project's needs + +### Using a browser for development + +Whilst Wails v2 fully supported the use of a browser for development, it caused a lot +of confusion. Applications that would work in the browser would not necessarily +work in the desktop application, as not all browser APIs are available in webviews. + +For UI-focused development work, you still have the flexibility to use a browser +in v3, by accessing the Vite URL at `http://localhost:9245` in dev mode. This +gives you access to powerful browser dev tools while working on styling and +layout. Be aware that Go bindings *will not work* in this mode. +When you're ready to test functionality like bindings and events, simply +switch to the desktop view to ensure everything works perfectly in the +production environment. diff --git a/docs/src/content/docs/guides/build/linux.mdx b/docs/src/content/docs/guides/build/linux.mdx new file mode 100644 index 000000000..18666c2aa --- /dev/null +++ b/docs/src/content/docs/guides/build/linux.mdx @@ -0,0 +1,160 @@ +--- +title: Linux Packaging +description: Package your Wails application for Linux distribution +sidebar: + order: 5 +--- + +import { Aside } from '@astrojs/starlight/components'; + +## Package Formats + +Package your app for Linux distribution: + +```bash +wails3 package GOOS=linux +``` + +This creates multiple formats in the `bin/` directory: +- **AppImage**: Portable, runs on any Linux distribution +- **DEB**: For Debian, Ubuntu, and derivatives +- **RPM**: For Fedora, RHEL, and derivatives +- **Arch**: For Arch Linux and derivatives + +### Individual Formats + +Build specific formats: + +```bash +wails3 task linux:create:appimage +wails3 task linux:create:deb +wails3 task linux:create:rpm +wails3 task linux:create:aur +``` + +## Customizing Packages + +### Desktop Entry + +The `.desktop` file controls how your app appears in application menus. It's generated from values in `build/linux/Taskfile.yml`: + +```yaml +vars: + APP_NAME: 'MyApp' + EXEC: 'MyApp' + ICON: 'MyApp' + CATEGORIES: 'Development;' +``` + +### Package Metadata + +Edit `build/linux/nfpm/nfpm.yaml` to customize DEB and RPM packages: + +```yaml +name: myapp +version: 1.0.0 +maintainer: Your Name +description: My awesome Wails application +homepage: https://example.com +license: MIT +``` + +### AppImage + +AppImage configuration is in `build/linux/appimage/`. The app icon comes from `build/appicon.png`. + +## Signing Packages + +Sign DEB and RPM packages with a PGP key: + +```bash +# Using the wrapper (auto-detects platform) +wails3 sign GOOS=linux + +# Or using tasks directly +wails3 task linux:sign:deb +wails3 task linux:sign:rpm +wails3 task linux:sign:packages # Both +``` + +Configure signing in `build/linux/Taskfile.yml`: + +```yaml +vars: + PGP_KEY: "path/to/signing-key.asc" + SIGN_ROLE: "builder" # origin, maint, archive, or builder +``` + +Store your key password: + +```bash +wails3 setup signing +``` + +See [Signing Applications](/guides/build/signing) for details. + +## Building for ARM + +```bash +wails3 build GOOS=linux GOARCH=arm64 +wails3 package GOOS=linux GOARCH=arm64 +``` + + + +## Troubleshooting + +### AppImage won't run + +Make it executable: + +```bash +chmod +x MyApp-x86_64.AppImage +``` + +### Missing dependencies + +If the app fails to start, check for missing WebKit dependencies: + +```bash +# Debian/Ubuntu +sudo apt install libwebkit2gtk-4.1-0 + +# Fedora +sudo dnf install webkit2gtk4.1 + +# Arch +sudo pacman -S webkit2gtk-4.1 +``` + +### No C compiler found + +The build system needs GCC or Clang for CGO: + +```bash +# Debian/Ubuntu +sudo apt install build-essential + +# Fedora +sudo dnf install gcc + +# Arch +sudo pacman -S base-devel +``` + +Alternatively, run `wails3 task setup:docker` and the build system will use Docker automatically. + +### AppImage strip compatibility + +On modern Linux distributions (Arch Linux, Fedora 39+, Ubuntu 24.04+), system libraries are compiled with `.relr.dyn` ELF sections for more efficient relocations. The `linuxdeploy` tool used to create AppImages bundles an older `strip` binary that cannot process these modern sections. + +Wails automatically detects this situation by checking system GTK libraries before building the AppImage. When detected, stripping is disabled (`NO_STRIP=1`) to ensure compatibility. + +**What this means:** +- AppImages will be slightly larger (~20-40%) on affected systems +- The application functionality is not affected +- This is handled automatically—no action required + +If you need smaller AppImages on modern systems, you can install a newer `strip` binary and configure `linuxdeploy` to use it instead of its bundled version. diff --git a/docs/src/content/docs/guides/build/macos.mdx b/docs/src/content/docs/guides/build/macos.mdx new file mode 100644 index 000000000..3271b2faa --- /dev/null +++ b/docs/src/content/docs/guides/build/macos.mdx @@ -0,0 +1,145 @@ +--- +title: macOS Packaging +description: Package your Wails application for macOS distribution +sidebar: + order: 4 +--- + +import { Aside } from '@astrojs/starlight/components'; + +## Application Bundle + +Package your app as a standard macOS `.app` bundle: + +```bash +wails3 package GOOS=darwin +``` + +This creates `bin/.app` containing: +- The compiled binary in `Contents/MacOS/` +- App icon in `Contents/Resources/` (from `icons.icns` or, when present, from an asset catalog `Assets.car`) +- `Info.plist` with app metadata + +### Universal Binary + +Build for both Apple Silicon and Intel Macs: + +```bash +wails3 task darwin:package:universal +``` + +This creates a single `.app` that runs natively on both architectures. Universal binaries can be built on any platform — on Linux and Windows, `wails3 tool lipo` is used automatically. + +## Customizing the Bundle + +Edit `build/darwin/Info.plist` to customize: + +- Bundle identifier (`CFBundleIdentifier`) +- App name and version +- Minimum macOS version +- File associations +- URL schemes + +The app icon is generated from assets in the `build/` directory. Use the `generate:icons` task: + +```bash +wails3 task generate:icons +``` + +This uses `build/appicon.png` to produce `darwin/icons.icns` and `windows/icon.ico`. On macOS you can also provide `build/appicon.icon` (Icon Composer format): the task passes `-iconcomposerinput appicon.icon -macassetdir darwin`, which produces `Assets.car` and `darwin/icons.icns` from the `.icon` file (skipped on non-macOS platforms). When `Assets.car` is present, run the `update:build-assets` task so that `Info.plist` and `CFBundleIconName` are updated accordingly: + +```bash +wails3 task update:build-assets +``` + +To run the icon command manually from the `build/` directory: + +```bash +cd build +wails3 generate icons -input appicon.png -macfilename darwin/icons.icns -windowsfilename windows/icon.ico -iconcomposerinput appicon.icon -macassetdir darwin +``` + +## Code Signing + +Sign your app for distribution: + +```bash +# Using the wrapper (auto-detects platform) +wails3 sign GOOS=darwin + +# Or using the task directly +wails3 task darwin:sign +``` + +Configure signing in `build/darwin/Taskfile.yml`: + +```yaml +vars: + SIGN_IDENTITY: "Developer ID Application: Your Company (TEAMID)" + ENTITLEMENTS: "build/darwin/entitlements.plist" +``` + +### Notarization + +For apps distributed outside the Mac App Store, Apple requires notarization: + +```bash +wails3 task darwin:sign:notarize +``` + +First, store your credentials: + +```bash +wails3 signing credentials \ + --apple-id "you@email.com" \ + --team-id "TEAMID" \ + --password "app-specific-password" \ + --profile "my-notarize-profile" +``` + +Configure in `build/darwin/Taskfile.yml`: + +```yaml +vars: + SIGN_IDENTITY: "Developer ID Application: Your Company (TEAMID)" + KEYCHAIN_PROFILE: "my-notarize-profile" +``` + +See [Signing Applications](/guides/build/signing) for details. + +## DMG Installer + +Create a DMG disk image for distribution: + +```bash +wails3 task darwin:create:dmg +``` + + + +## Troubleshooting + +### "App is damaged and can't be opened" + +The app isn't signed. Either sign it with a Developer ID certificate, or users can bypass Gatekeeper: + +```bash +xattr -cr /path/to/YourApp.app +``` + +### Notarization fails + +Common issues: +- **Invalid credentials**: Re-run `wails3 signing credentials` +- **Hardened runtime required**: Ensure entitlements include `com.apple.security.cs.allow-unsigned-executable-memory` if needed +- **Missing timestamp**: The signing process should include a timestamp automatically + +### Cross-compiled app won't run + +Cross-compiled macOS binaries aren't signed. Transfer to a Mac and sign before testing: + +```bash +codesign --force --deep --sign - YourApp.app +``` diff --git a/docs/src/content/docs/guides/build/signing.mdx b/docs/src/content/docs/guides/build/signing.mdx new file mode 100644 index 000000000..3f6a03382 --- /dev/null +++ b/docs/src/content/docs/guides/build/signing.mdx @@ -0,0 +1,823 @@ +--- +title: Code Signing +description: Guide for signing your Wails applications on all platforms +sidebar: + order: 6 +--- + +import { Tabs, TabItem, Steps, Aside } from '@astrojs/starlight/components'; + +# Code Signing Your Application + +This guide covers how to sign your Wails applications for macOS, Windows, and Linux. Wails v3 provides built-in CLI tools for code signing, notarization, and PGP key management. + +- **macOS** - Sign and notarize your macOS applications +- **Windows** - Sign your Windows executables and packages +- **Linux** - Sign DEB and RPM packages with PGP keys + +## Cross-Platform Signing Matrix + +This matrix shows what you can sign from each source platform: + +| Target Format | From Windows | From macOS | From Linux | +|---------------|:------------:|:----------:|:----------:| +| Windows EXE/MSI | ✅ | ✅ | ✅ | +| macOS .app bundle | ❌ | ✅ | ❌ | +| macOS notarization | ❌ | ✅ | ❌ | +| Linux DEB | ✅ | ✅ | ✅ | +| Linux RPM | ✅ | ✅ | ✅ | + + + +### Signing Backends + +Wails automatically selects the best available signing backend: + +| Platform | Native Backend | Cross-Platform Backend | +|----------|----------------|------------------------| +| Windows | `signtool.exe` (Windows SDK) | Built-in | +| macOS | `codesign` (Xcode) | Not available | +| Linux | N/A | Built-in | + +When running on the native platform, Wails uses the native tools for maximum compatibility. When cross-compiling, it uses the built-in signing support. + +## Quick Start + +The easiest way to configure signing is using the interactive setup wizard: + +```bash +wails3 setup signing +``` + +This command: +- Walks you through configuring signing credentials for each platform +- On macOS, lists available Developer ID certificates from your keychain +- For Linux, can generate a new PGP key if you don't have one +- **Stores passwords securely in your system keychain** (not in Taskfiles) +- Updates the `vars` section in each platform's Taskfile with non-sensitive config + + + +To configure only specific platforms: + +```bash +wails3 setup signing --platform darwin +wails3 setup signing --platform windows --platform linux +``` + +### Manual Configuration + +Alternatively, you can manually edit the platform-specific Taskfiles. Edit the `vars` section at the top of each file: + + + +Edit `build/darwin/Taskfile.yml`: + +```yaml +vars: + SIGN_IDENTITY: "Developer ID Application: Your Company (TEAMID)" + KEYCHAIN_PROFILE: "my-notarize-profile" + # ENTITLEMENTS: "build/darwin/entitlements.plist" +``` + +Then run: + +```bash +wails3 task darwin:sign # Sign only +wails3 task darwin:sign:notarize # Sign and notarize +``` + + +Edit `build/windows/Taskfile.yml`: + +```yaml +vars: + SIGN_CERTIFICATE: "path/to/certificate.pfx" + # Or use thumbprint instead: + # SIGN_THUMBPRINT: "certificate-thumbprint" + # TIMESTAMP_SERVER: "http://timestamp.digicert.com" +``` + +Password is retrieved from system keychain (run `wails3 setup signing` to configure). + +Then run: + +```bash +wails3 task windows:sign # Sign executable +wails3 task windows:sign:installer # Sign NSIS installer +``` + + +Edit `build/linux/Taskfile.yml`: + +```yaml +vars: + PGP_KEY: "path/to/signing-key.asc" + # SIGN_ROLE: "builder" # Options: origin, maint, archive, builder +``` + +Password is retrieved from system keychain (run `wails3 setup signing` to configure). + +Then run: + +```bash +wails3 task linux:sign:deb # Sign DEB package +wails3 task linux:sign:rpm # Sign RPM package +wails3 task linux:sign:packages # Sign all packages +``` + + + +You can also use the CLI directly: + +```bash +# Check signing capabilities on your system +wails3 signing info + +# List available signing identities (macOS/Windows) +wails3 signing list +``` + +## macOS Code Signing + +### Prerequisites + +- Apple Developer Account ($99/year) +- Developer ID Application certificate +- Xcode Command Line Tools installed + +### Signing Identities + +Check available signing identities: + +```bash +wails3 signing list +``` + +Output: + +``` +Found 2 signing identities: + + Developer ID Application: Your Company (ABCD1234) [valid] + Hash: ABC123DEF456... + + Apple Development: your@email.com (XYZ789) [valid] + Hash: DEF789ABC123... +``` + + + +### Configuration + +Edit `build/darwin/Taskfile.yml` and set the signing variables: + +```yaml +vars: + SIGN_IDENTITY: "Developer ID Application: Your Company (TEAMID)" + KEYCHAIN_PROFILE: "my-notarize-profile" + ENTITLEMENTS: "build/darwin/entitlements.plist" +``` + +| Variable | Required | Description | +|----------|----------|-------------| +| `SIGN_IDENTITY` | Yes | Your Developer ID (e.g., "Developer ID Application: Your Company (TEAMID)") | +| `KEYCHAIN_PROFILE` | For notarization | Keychain profile name with stored credentials | +| `ENTITLEMENTS` | No | Path to entitlements file | + +Then run: + +```bash +wails3 task darwin:sign # Build, package, and sign +wails3 task darwin:sign:notarize # Build, package, sign, and notarize +``` + +### Entitlements + +Entitlements control what capabilities your app has access to. Wails apps typically need different entitlements for development vs production: + +- **Development**: Requires JIT, unsigned memory, and debugging entitlements +- **Production**: Minimal entitlements (just network access) + +Use the interactive setup wizard to generate both files: + +```bash +wails3 setup entitlements +``` + +This creates: +- `build/darwin/entitlements.dev.plist` - For development builds +- `build/darwin/entitlements.plist` - For production/signed builds + +**Presets available:** +| Preset | Description | +|--------|-------------| +| Development | JIT, unsigned memory, debugging, network | +| Production | Network only (minimal, most secure) | +| Both | Creates both dev and production files (recommended) | +| App Store | Sandbox enabled with network and file access | +| Custom | Choose individual entitlements | + + + +Then set `ENTITLEMENTS` in your Taskfile vars to point to the appropriate file. + +### Notarization + +Apple requires all distributed apps to be notarized. + + +1. **Store your credentials in the keychain** (one-time setup): + ```bash + wails3 signing credentials \ + --apple-id "your@email.com" \ + --team-id "ABCD1234" \ + --password "app-specific-password" \ + --profile "my-notarize-profile" + ``` + +2. **Set KEYCHAIN_PROFILE in your Taskfile** to match the profile name above. + +3. **Sign and notarize your app**: + ```bash + wails3 task darwin:sign:notarize + ``` + +4. **Verify notarization**: + ```bash + spctl --assess --verbose=2 bin/MyApp.app + ``` + + + + +## Windows Code Signing + +### Prerequisites + +- Code signing certificate (from DigiCert, Sectigo, etc.) +- For native signing: Windows SDK installed (for signtool.exe) +- For cross-platform: Just the certificate file + +### Configuration + +Edit `build/windows/Taskfile.yml` and set the signing variables: + +```yaml +vars: + SIGN_CERTIFICATE: "path/to/certificate.pfx" + # Or use thumbprint instead: + # SIGN_THUMBPRINT: "certificate-thumbprint" + # TIMESTAMP_SERVER: "http://timestamp.digicert.com" +``` + +| Variable | Required | Description | +|----------|----------|-------------| +| `SIGN_CERTIFICATE` | One of these | Path to .pfx/.p12 certificate file | +| `SIGN_THUMBPRINT` | One of these | Certificate thumbprint in Windows cert store | +| `TIMESTAMP_SERVER` | No | Timestamp server URL (default: http://timestamp.digicert.com) | + + + +Then run: + +```bash +wails3 task windows:sign # Build and sign executable +wails3 task windows:sign:installer # Build and sign NSIS installer +``` + +### Cross-Platform Signing + +Windows executables can be signed from any platform. The same Taskfile configuration and commands work on macOS and Linux. + +### Supported Windows Formats + +| Format | Extension | Notes | +|--------|-----------|-------| +| Executables | .exe | Standard PE signing | +| Installers | .msi | Windows Installer packages | +| App Packages | .msix, .appx | Modern Windows apps | + +## Linux Package Signing + +Linux packages (DEB and RPM) are signed using PGP/GPG keys. Unlike Windows and macOS code signing, Linux package signing proves the package came from a trusted source rather than that the code is trusted by the OS. + +### Prerequisites + +- PGP key pair (can be generated with Wails) + +### Generating a PGP Key + +If you don't have a PGP key, Wails can generate one for you: + +```bash +wails3 signing generate-key \ + --name "Your Name" \ + --email "your@email.com" \ + --comment "Package Signing Key" \ + --output-private signing-key.asc \ + --output-public signing-key.pub.asc +``` + +Options: +- `--bits`: Key size (default: 4096) +- `--expiry`: Key expiry duration (e.g., "1y", "6m", "0" for no expiry) +- `--password`: Password to protect the private key + + + +### Configuration + +Edit `build/linux/Taskfile.yml` and set the signing variables: + +```yaml +vars: + PGP_KEY: "path/to/signing-key.asc" + # SIGN_ROLE: "builder" # Options: origin, maint, archive, builder +``` + +| Variable | Required | Description | +|----------|----------|-------------| +| `PGP_KEY` | Yes | Path to PGP private key file | +| `SIGN_ROLE` | No | DEB signing role (default: builder) | + + + +Then run: + +```bash +wails3 task linux:sign:deb # Build and sign DEB package +wails3 task linux:sign:rpm # Build and sign RPM package +wails3 task linux:sign:packages # Build and sign all packages +``` + +### DEB Signing Roles + +For DEB packages, you can specify the signing role via `SIGN_ROLE`: + +- `origin`: Signature from the package origin +- `maint`: Signature from the package maintainer +- `archive`: Signature from the archive maintainer +- `builder`: Signature from the package builder (default) + +### Cross-Platform Signing + +Linux packages can be signed from any platform. The same Taskfile configuration and commands work on Windows and macOS. + +### Viewing Key Information + +```bash +wails3 signing key-info --key signing-key.asc +``` + +Output: + +``` +PGP Key Information: + Key ID: ABC123DEF456 + Fingerprint: 1234 5678 90AB CDEF ... + User IDs: Your Name + Created: 2024-01-15 + Expires: 2025-01-15 + Has Private: Yes + Encrypted: Yes +``` + +### Verifying Linux Packages + +```bash +# Verify DEB signature +dpkg-sig --verify myapp_1.0.0_amd64.deb + +# Verify RPM signature +rpm --checksig myapp-1.0.0.x86_64.rpm +``` + +### Distributing Your Public Key + +Users need your public key to verify packages: + +```bash +# Export public key for distribution +wails3 signing key-info --key signing-key.asc --export-public > myapp-signing.pub.asc + +# Users can import it: +# For DEB (apt): +sudo apt-key add myapp-signing.pub.asc +# Or for modern apt: +sudo cp myapp-signing.pub.asc /etc/apt/trusted.gpg.d/ + +# For RPM: +sudo rpm --import myapp-signing.pub.asc +``` + +## GitHub Actions Integration + +In CI environments, passwords are provided via environment variables instead of the system keychain: + +| Environment Variable | Description | +|---------------------|-------------| +| `WAILS_WINDOWS_CERT_PASSWORD` | Windows certificate password | +| `WAILS_PGP_PASSWORD` | PGP key password for Linux packages | + +You can also pass Taskfile variables directly: + +```bash +wails3 task darwin:sign SIGN_IDENTITY="$SIGN_IDENTITY" KEYCHAIN_PROFILE="$KEYCHAIN_PROFILE" +``` + +### macOS Workflow + +```yaml +name: Build and Sign macOS + +on: + push: + tags: ['v*'] + +jobs: + build: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: '1.23' + + - name: Install Wails + run: go install github.com/wailsapp/wails/v3/cmd/wails3@latest + + - name: Import Certificate + env: + CERTIFICATE_BASE64: ${{ secrets.MACOS_CERTIFICATE }} + CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }} + run: | + echo $CERTIFICATE_BASE64 | base64 --decode > certificate.p12 + security create-keychain -p "" build.keychain + security default-keychain -s build.keychain + security unlock-keychain -p "" build.keychain + security import certificate.p12 -k build.keychain -P "$CERTIFICATE_PASSWORD" -T /usr/bin/codesign + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "" build.keychain + + - name: Store Notarization Credentials + env: + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + APPLE_APP_PASSWORD: ${{ secrets.APPLE_APP_PASSWORD }} + run: | + wails3 signing credentials \ + --apple-id "$APPLE_ID" \ + --team-id "$APPLE_TEAM_ID" \ + --password "$APPLE_APP_PASSWORD" \ + --profile "notarize-profile" + + - name: Build, Sign, and Notarize + env: + SIGN_IDENTITY: ${{ secrets.MACOS_SIGN_IDENTITY }} + run: | + wails3 task darwin:sign:notarize \ + SIGN_IDENTITY="$SIGN_IDENTITY" \ + KEYCHAIN_PROFILE="notarize-profile" + + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: MyApp-macOS + path: bin/*.app +``` + +### Windows Workflow + +```yaml +name: Build and Sign Windows + +on: + push: + tags: ['v*'] + +jobs: + build: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: '1.23' + + - name: Install Wails + run: go install github.com/wailsapp/wails/v3/cmd/wails3@latest + + - name: Import Certificate + env: + CERTIFICATE_BASE64: ${{ secrets.WINDOWS_CERTIFICATE }} + run: | + $certBytes = [Convert]::FromBase64String($env:CERTIFICATE_BASE64) + [IO.File]::WriteAllBytes("certificate.pfx", $certBytes) + + - name: Build and Sign + env: + WAILS_WINDOWS_CERT_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }} + run: | + wails3 task windows:sign SIGN_CERTIFICATE=certificate.pfx + + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: MyApp-Windows + path: bin/*.exe +``` + +### Cross-Platform Workflow (Linux Runner) + +Sign Windows and Linux packages from a single Linux runner: + +```yaml +name: Build and Sign (Cross-Platform) + +on: + push: + tags: ['v*'] + +jobs: + build-and-sign: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: '1.23' + + - name: Install Wails + run: go install github.com/wailsapp/wails/v3/cmd/wails3@latest + + - name: Install Build Dependencies + run: | + sudo apt-get update + sudo apt-get install -y nsis rpm + + # Import certificates + - name: Import Certificates + env: + WINDOWS_CERT_BASE64: ${{ secrets.WINDOWS_CERTIFICATE }} + PGP_KEY_BASE64: ${{ secrets.PGP_PRIVATE_KEY }} + run: | + echo "$WINDOWS_CERT_BASE64" | base64 -d > certificate.pfx + echo "$PGP_KEY_BASE64" | base64 -d > signing-key.asc + + # Build and sign Windows + - name: Build and Sign Windows + env: + WAILS_WINDOWS_CERT_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }} + run: | + wails3 task windows:sign SIGN_CERTIFICATE=certificate.pfx + + # Build and sign Linux packages + - name: Build and Sign Linux Packages + env: + WAILS_PGP_PASSWORD: ${{ secrets.PGP_PASSWORD }} + run: | + wails3 task linux:sign:packages PGP_KEY=signing-key.asc + + # Cleanup secrets + - name: Cleanup + if: always() + run: rm -f certificate.pfx signing-key.asc + + - name: Upload Artifacts + uses: actions/upload-artifact@v4 + with: + name: signed-binaries + path: | + bin/*.exe + bin/*.deb + bin/*.rpm +``` + + + +## CLI Reference + +### wails3 setup signing + +Interactive wizard to configure signing for your project. + +```bash +wails3 setup signing [flags] + +Flags: + --platform Platform(s) to configure (darwin, windows, linux) + If not specified, configures all platforms +``` + +The wizard guides you through: +- **macOS**: Selecting a Developer ID certificate, configuring notarization profile +- **Windows**: Choosing between certificate file or thumbprint, setting password and timestamp server +- **Linux**: Using an existing PGP key or generating a new one, configuring signing role + +### wails3 setup entitlements + +Interactive wizard to configure macOS entitlements. + +```bash +wails3 setup entitlements [flags] + +Flags: + --output Output directory (default: build/darwin) +``` + +**Presets:** +- **Development**: Creates `entitlements.dev.plist` with JIT, debugging, and network +- **Production**: Creates `entitlements.plist` with minimal entitlements +- **Both**: Creates both files (recommended) +- **App Store**: Creates sandboxed entitlements for Mac App Store +- **Custom**: Choose individual entitlements and target file + +### wails3 sign + +Sign binaries and packages for the current or specified platform. This is a wrapper that calls the appropriate platform-specific signing task. + +```bash +wails3 sign +wails3 sign GOOS=darwin +wails3 sign GOOS=windows +wails3 sign GOOS=linux +``` + +This runs the corresponding `:sign` task which uses the signing configuration from your Taskfile. + +### wails3 tool sign + +Low-level command to sign a specific file directly. Used internally by the Taskfiles. + +```bash +wails3 tool sign [flags] +``` + +**Common Flags:** +| Flag | Description | +|------|-------------| +| `--input` | Path to the file to sign | +| `--output` | Output path (optional, defaults to in-place) | +| `--verbose` | Enable verbose output | + +**Windows/macOS Flags:** +| Flag | Description | +|------|-------------| +| `--certificate` | Path to PKCS#12 (.pfx/.p12) certificate | +| `--password` | Certificate password | +| `--timestamp` | Timestamp server URL | + +**macOS-Specific Flags:** +| Flag | Description | +|------|-------------| +| `--identity` | Signing identity (use '-' for ad-hoc) | +| `--entitlements` | Path to entitlements plist | +| `--hardened-runtime` | Enable hardened runtime (default: true) | +| `--notarize` | Submit for notarization | +| `--keychain-profile` | Keychain profile for notarization | + +**Windows-Specific Flags:** +| Flag | Description | +|------|-------------| +| `--thumbprint` | Certificate thumbprint in Windows store | +| `--description` | Application description | +| `--url` | Application URL | + +**Linux-Specific Flags:** +| Flag | Description | +|------|-------------| +| `--pgp-key` | Path to PGP private key | +| `--pgp-password` | PGP key password | +| `--role` | DEB signing role (origin/maint/archive/builder) | + +### wails3 signing info + +Display signing capabilities on the current system. + +```bash +wails3 signing info +``` + +### wails3 signing list + +List available signing identities. + +```bash +wails3 signing list +``` + +### wails3 signing credentials + +Store notarization credentials in keychain (macOS only). + +```bash +wails3 signing credentials [flags] + +Flags: + --apple-id Apple ID email + --team-id Apple Developer Team ID + --password App-specific password + --profile Keychain profile name +``` + +### wails3 signing generate-key + +Generate a PGP key pair for Linux package signing. + +```bash +wails3 signing generate-key [flags] + +Flags: + --name Name for the key (required) + --email Email for the key (required) + --comment Comment for the key + --bits Key size in bits (default: 4096) + --expiry Key expiry (e.g., "1y", "6m", "0" for never) + --password Password to encrypt private key + --output-private Path for private key output + --output-public Path for public key output +``` + +### wails3 signing key-info + +Display information about a PGP key. + +```bash +wails3 signing key-info --key +``` + +## Troubleshooting + +### macOS Issues + +**"No Developer ID certificate found"** +- Ensure your certificate is installed in the Keychain +- Check it hasn't expired with `wails3 signing list` +- Make sure you have a "Developer ID Application" certificate (not just "Apple Development") + +**"Notarization failed"** +- Check the notarization log: `xcrun notarytool log --keychain-profile ` +- Ensure hardened runtime is enabled +- Verify your app doesn't include unsigned binaries + +**"Codesign failed"** +- Make sure the keychain is unlocked: `security unlock-keychain` +- Check file permissions on the app bundle + +### Windows Issues + +**"Certificate not found"** +- Verify the certificate path is correct +- Check the certificate password +- Ensure the certificate is valid (not expired or revoked) + +**"Timestamp server error"** +- Try a different timestamp server: + - `http://timestamp.digicert.com` + - `http://timestamp.sectigo.com` + - `http://timestamp.comodoca.com` + +### Linux Issues + +**"Invalid PGP key"** +- Ensure the key file is in ASCII-armored format +- Check the key hasn't expired with `wails3 signing key-info` +- Verify the password is correct + +**"Signature verification failed"** +- Ensure the public key is properly imported +- Check that the package wasn't modified after signing + +## Additional Resources + +### Official Documentation +- [Apple Code Signing Guide](https://developer.apple.com/support/code-signing/) +- [Apple Notarization Documentation](https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution) +- [Microsoft Code Signing](https://docs.microsoft.com/en-us/windows-hardware/drivers/dashboard/get-a-code-signing-certificate) +- [Debian Package Signing](https://wiki.debian.org/SecureApt) +- [RPM Package Signing](https://rpm-software-management.github.io/rpm/manual/signatures.html) diff --git a/docs/src/content/docs/guides/build/windows.mdx b/docs/src/content/docs/guides/build/windows.mdx new file mode 100644 index 000000000..fe4183cb4 --- /dev/null +++ b/docs/src/content/docs/guides/build/windows.mdx @@ -0,0 +1,124 @@ +--- +title: Windows Packaging +description: Package your Wails application for Windows distribution +sidebar: + order: 3 +--- + +import { Aside } from '@astrojs/starlight/components'; + +## NSIS Installer + +The default packaging format creates an NSIS installer: + +```bash +wails3 package GOOS=windows +``` + +This runs `wails3 task windows:package` which: +1. Builds the application +2. Generates the WebView2 bootstrapper +3. Creates an NSIS installer + +Output: `build/windows/nsis/-installer.exe` + +### MSIX Package + +For Microsoft Store distribution or modern Windows deployment: + +```bash +wails3 package GOOS=windows FORMAT=msix +``` + +Output: `bin/-.msix` + + + +## Customizing the Installer + +NSIS configuration is in `build/windows/nsis/project.nsi`. Edit this file to customize: + +- Installer UI and branding +- Installation directory +- Start menu and desktop shortcuts +- File associations +- License agreement + +Application metadata comes from `build/windows/info.json`: + +```json +{ + "fixed": { + "file_version": "1.0.0" + }, + "info": { + "0000": { + "ProductVersion": "1.0.0", + "CompanyName": "My Company", + "FileDescription": "My Application", + "ProductName": "MyApp" + } + } +} +``` + +## Code Signing + +Sign your executable and installer to avoid SmartScreen warnings: + +```bash +# Using the wrapper (auto-detects platform) +wails3 sign GOOS=windows + +# Or using tasks directly +wails3 task windows:sign +wails3 task windows:sign:installer +``` + +Configure signing in `build/windows/Taskfile.yml`: + +```yaml +vars: + SIGN_CERTIFICATE: "path/to/certificate.pfx" + # Or use thumbprint for certificates in Windows store + SIGN_THUMBPRINT: "certificate-thumbprint" + TIMESTAMP_SERVER: "http://timestamp.digicert.com" +``` + +Store your certificate password securely: + +```bash +wails3 setup signing +``` + +See [Signing Applications](/guides/build/signing) for details. + +## Building for ARM + +```bash +wails3 build GOOS=windows GOARCH=arm64 +wails3 package GOOS=windows GOARCH=arm64 +``` + +## Troubleshooting + +### makensis not found + +Install NSIS: + +```bash +# Windows +winget install NSIS.NSIS + +# Or download from https://nsis.sourceforge.io/ +``` + +### SmartScreen warning + +Your executable isn't signed. See [Code Signing](#code-signing) above. + +### WebView2 missing + +The installer includes a WebView2 bootstrapper that downloads the runtime if needed. If you need offline installation, download the Evergreen Standalone Installer from Microsoft. diff --git a/docs/src/content/docs/guides/cli.mdx b/docs/src/content/docs/guides/cli.mdx new file mode 100644 index 000000000..fb9f23969 --- /dev/null +++ b/docs/src/content/docs/guides/cli.mdx @@ -0,0 +1,539 @@ +--- +title: CLI Reference +description: Complete reference for the Wails CLI commands +sidebar: + order: 1 +--- + +The Wails CLI provides a comprehensive set of commands to help you develop, build, and maintain your Wails applications. + +## Core Commands + +Core commands are the primary commands used for project creation, development, and building. + +All CLI commands are of the following format: `wails3 `. + +### `init` +Initializes a new Wails project. During this initialization, the `go mod tidy` command is run to bring the project packages up to date. This can be bypassed by using the `-skipgomodtidy` flag with the `init` command. + +```bash +wails3 init [flags] +``` + +#### Flags +| Flag | Description | Default | +|-----------------------|----------------------|--------------------------| +| `-p` | Package name | `main` | +| `-t` | Template name or URL | `vanilla` | +| `-n` | Project name | | +| `-d` | Project directory | `.` | +| `-q` | Suppress output | `false` | +| `-l` | List templates | `false` | +| `-git` | Git repository URL | | +| `-productname` | Product name | `My Product` | +| `-productdescription` | Product description | `My Product Description` | +| `-productversion` | Product version | `0.1.0` | +| `-productcompany` | Company name | `My Company` | +| `-productcopyright` | Copyright notice | ` now, My Company` | +| `-productcomments` | File comments | `This is a comment` | +| `-productidentifier` | Product identifier | | +| `-skipgomodtidy` | Skip go mod tidy | `false` | + +The `-git` flag accepts various Git URL formats: +- HTTPS: `https://github.com/username/project` +- SSH: `git@github.com:username/project` or `ssh://git@github.com/username/project` +- Git protocol: `git://github.com/username/project` +- Filesystem: `file:///path/to/project.git` + +When provided, this flag will: +1. Initialize a git repository in the project directory +2. Set the specified URL as the remote origin +3. Update the module name in `go.mod` to match the repository URL +4. Add all files + +### `dev` +Runs the application in development mode. This will give you a live view of your frontend code, and you can make changes and see them reflected +in the running application without having to rebuild the entire application. Changes to your Go code will also be detected and the application +will automatically rebuild and relaunch. + +```bash +wails3 dev [flags] +``` + +#### Flags +| Flag | Description | Default | +|-----------|----------------------|----------------------| +| `-config` | Config file path | `./build/config.yml` | +| `-port` | Vite dev server port | `9245` | +| `-s` | Enable HTTPS | `false` | + + + :::note + This is equivalent to running `wails3 task dev` and runs the `dev` task in the project's main Taskfile. You can customise this by editing the `Taskfile.yml` file. + ::: + + +### `build` +Builds a debug version of your application. It defaults to building for the current platform and architecture. + +```bash +wails3 build [CLI variables...] +``` + +You can pass CLI variables to customize the build: +```bash +wails3 build PLATFORM=linux CONFIG=production +``` + + :::note + This is equivalent to running `wails3 task build` which runs the `build` task in the project's main Taskfile. Any CLI variables passed to `build` are forwarded to the underlying task. You can customise the build process by editing the `Taskfile.yml` file. + ::: + +### `package` + +Creates platform-specific packages for distribution. + +```bash +wails3 package [CLI variables...] +``` + +You can pass CLI variables to customize the packaging: +```bash +wails3 package VERSION=2.0.0 OUTPUT=myapp.pkg +``` + +#### Package Types + +The following package types are available for each platform: + +| Platform | Package Type | +|----------|-------------------------------------------| +| Windows | `.exe` | +| macOS | `.app`, | +| Linux | `.AppImage`, `.deb`, `.rpm`, `.archlinux` | + + :::note + This is equivalent to `wails3 task package` which runs the `package` task in the project's main Taskfile. Any CLI variables passed to `package` are forwarded to the underlying task. You can customise the packaging process by editing the `Taskfile.yml` file. + ::: + + +### `task` +Runs tasks defined in your project's Taskfile.yml. This is an embedded version of [Taskfile](https://taskfile.dev) that allows you to define and run custom build, test, and deployment tasks. + +```bash +wails3 task [taskname] [CLI variables...] [flags] +``` + +#### CLI Variables +You can pass variables to tasks in the format `KEY=VALUE`: +```bash +wails3 task build PLATFORM=linux CONFIG=production +wails3 task deploy ENV=staging VERSION=1.2.3 +``` + +These variables can be accessed in your Taskfile.yml using Go template syntax: +```yaml +tasks: + build: + cmds: + - echo "Building for {{.PLATFORM | default "darwin"}}" + - echo "Config: {{.CONFIG | default "debug"}}" +``` + +#### Flags +| Flag | Description | Default | +|--------------|-------------------------------------------|----------| +| `-h` | Shows Task usage | `false` | +| `-i` | Creates a new Taskfile.yml | `false` | +| `-list` | Lists tasks with descriptions | `false` | +| `-list-all` | Lists all tasks (with or without descriptions) | `false` | +| `-json` | Formats task list as JSON | `false` | +| `-status` | Exits with non-zero if task is not up-to-date | `false` | +| `-f` | Forces execution even when task is up-to-date | `false` | +| `-w` | Enables watch mode for the given task | `false` | +| `-v` | Enables verbose mode | `false` | +| `-version` | Prints Task version | `false` | +| `-s` | Disables echoing | `false` | +| `-p` | Executes tasks in parallel | `false` | +| `-dry` | Compiles and prints tasks without executing | `false` | +| `-summary` | Shows summary about a task | `false` | +| `-x` | Pass-through the exit code of the task | `false` | +| `-dir` | Sets directory of execution | | +| `-taskfile` | Choose which Taskfile to run | | +| `-output` | Sets output style: [interleaved|group|prefixed] | | +| `-c` | Colored output (enabled by default) | `true` | +| `-C` | Limit number of tasks to run concurrently | | +| `-interval` | Interval to watch for changes (in seconds) | | + +#### Examples +```bash +# Run the default task +wails3 task + +# Run a specific task +wails3 task test + +# Run a task with variables +wails3 task build PLATFORM=windows ARCH=amd64 + +# List all available tasks +wails3 task --list + +# Run multiple tasks in parallel +wails3 task -p task1 task2 task3 + +# Watch for changes and re-run task +wails3 task -w dev +``` + +### `doctor` +Performs a system check and displays a status report. + +```bash +wails3 doctor +``` + +## Generate Commands + +Generate commands help create various project assets like bindings, icons, and build files. All generate commands use the base command: `wails3 generate `. + +### `generate bindings` +Generates bindings and models for your Go code. + +```bash +wails3 generate bindings [flags] [patterns...] +``` + +#### Flags +| Flag | Description | Default | +|-------------|---------------------------|---------------------| +| `-f` | Additional Go build flags | | +| `-d` | Output directory | `frontend/bindings` | +| `-models` | Models filename | `models` | +| `-index` | Index filename | `index` | +| `-ts` | Generate TypeScript | `false` | +| `-i` | Use TS interfaces | `false` | +| `-b` | Use bundled runtime | `false` | +| `-names` | Use names instead of IDs | `false` | +| `-noindex` | Skip index files | `false` | +| `-dry` | Dry run | `false` | +| `-silent` | Silent mode | `false` | +| `-v` | Debug output | `false` | +| `-clean` | Clean output directory | `false` | + +### `generate build-assets` +Generates build assets for your application. + +```bash +wails3 generate build-assets [flags] +``` + +#### Flags +| Flag | Description | Default | +|----------------|---------------------|--------------------| +| `-name` | Project name | | +| `-dir` | Output directory | `build` | +| `-silent` | Suppress output | `false` | +| `-company` | Company name | | +| `-productname` | Product name | | +| `-description` | Product description | | +| `-version` | Product version | | +| `-identifier` | Product identifier | `com.wails.[name]` | +| `-copyright` | Copyright notice | | +| `-comments` | File comments | | + +### `generate icons` +Generates application icons. + +```bash +wails3 generate icons [flags] +``` + +#### Flags +| Flag | Description | Default | +|--------------------|------------------------------|-----------------------| +| `-input` | Input PNG file | Required | +| `-windowsfilename` | Windows output filename | | +| `-macfilename` | macOS output filename | | +| `-sizes` | Icon sizes (comma-separated) | `256,128,64,48,32,16` | +| `-example` | Generate example icon | `false` | + +### `generate syso` +Generates Windows .syso file. + +```bash +wails3 generate syso [flags] +``` + +#### Flags +| Flag | Description | Default | +|-------------|---------------------------|----------------------------| +| `-manifest` | Path to manifest file | Required | +| `-icon` | Path to icon file | Required | +| `-info` | Path to version info file | | +| `-arch` | Target architecture | Current GOARCH | +| `-out` | Output filename | `rsrc_windows_[arch].syso` | + +### `generate .desktop` +Generates a Linux .desktop file. + +```bash +wails3 generate .desktop [flags] +``` + +#### Flags +| Flag | Description | Default | +|------------------|---------------------------|------------------| +| `-name` | Application name | Required | +| `-exec` | Executable path | Required | +| `-icon` | Icon path | | +| `-categories` | Application categories | `Utility` | +| `-comment` | Application comment | | +| `-terminal` | Run in terminal | `false` | +| `-keywords` | Search keywords | | +| `-version` | Application version | | +| `-genericname` | Generic name | | +| `-startupnotify` | Show startup notification | `false` | +| `-mimetype` | Supported MIME types | | +| `-output` | Output filename | `[name].desktop` | + +### `generate runtime` +Generates the pre-built version of the runtime. + +```bash +wails3 generate runtime +``` + +### `generate constants` +Generates JavaScript constants from Go code. + +```bash +wails3 generate constants +``` + +### `generate appimage` +Generates a Linux AppImage. + +```bash +wails3 generate appimage [flags] +``` + +#### Flags +| Flag | Description | Default | +|-------------|-----------------------|----------------| +| `-binary` | Path to binary | Required | +| `-icon` | Path to icon file | Required | +| `-desktop` | Path to .desktop file | Required | +| `-builddir` | Build directory | Temp directory | +| `-output` | Output directory | `.` | + +Base command: `wails3 service` + +## Service Commands + +Service commands help manage Wails services. All service commands use the base command: `wails3 service `. + +### `service init` +Initializes a new service. + +```bash +wails3 service init [flags] +``` + +#### Flags +| Flag | Description | Default | +|------|---------------------|-------------------| +| `-n` | Service name | `example_service` | +| `-d` | Service description | `Example service` | +| `-p` | Package name | | +| `-o` | Output directory | `.` | +| `-q` | Suppress output | `false` | +| `-a` | Author name | | +| `-v` | Version | | +| `-w` | Website URL | | +| `-r` | Repository URL | | +| `-l` | License | | + +Base command: `wails3 tool` + +## Tool Commands + +Tool commands provide utilities for development and debugging. All tool commands use the base command: `wails3 tool `. + +### `tool checkport` +Checks if a port is open. Useful for testing if vite is running. + +```bash +wails3 tool checkport [flags] +``` + +#### Flags +| Flag | Description | Default | +|---------|---------------|-------------| +| `-port` | Port to check | `9245` | +| `-host` | Host to check | `localhost` | + +### `tool watcher` +Watches files and runs a command when they change. + +```bash +wails3 tool watcher [flags] +``` + +#### Flags +| Flag | Description | Default | +|------------|---------------------|----------------------| +| `-config` | Config file path | `./build/config.yml` | +| `-ignore` | Patterns to ignore | | +| `-include` | Patterns to include | | + +### `tool cp` +Copies files. + +```bash +wails3 tool cp +``` + +### `tool buildinfo` +Shows build information about the application. + +```bash +wails3 tool buildinfo +``` + +### `tool version` +Bumps a semantic version based on the provided flags. + +```bash +wails3 tool version [flags] +``` + +#### Flags +| Flag | Description | Default | +|---------------|--------------------------------------------------|---------| +| `-v` | Current version to bump | | +| `-major` | Bump major version | `false` | +| `-minor` | Bump minor version | `false` | +| `-patch` | Bump patch version | `false` | +| `-prerelease` | Bump prerelease version (e.g., alpha.5 to alpha.6) | `false` | + +The command follows the precedence order: major > minor > patch > prerelease. It preserves the "v" prefix if present in the input version, as well as any prerelease and metadata components. + +Example usage: +```bash +wails3 tool version -v 1.2.3 -major # Output: 2.0.0 +wails3 tool version -v v1.2.3 -minor # Output: v1.3.0 +wails3 tool version -v 1.2.3-alpha -patch # Output: 1.2.4-alpha +wails3 tool version -v v3.0.0-alpha.5 -prerelease # Output: v3.0.0-alpha.6 +``` + +### `tool package` +Generates Linux packages (deb, rpm, archlinux). + +```bash +wails3 tool package [flags] +``` + +#### Flags +| Flag | Description | Default | +|-----------|--------------------------------------|----------| +| `-format` | Package format (deb, rpm, archlinux) | `deb` | +| `-name` | Executable name | Required | +| `-config` | Config file path | Required | +| `-out` | Output directory | `.` | + +#### Flags +| Flag | Description | Default | +|-----------|--------------------------------------|---------| +| `-format` | Package format (deb, rpm, archlinux) | `deb` | +| `-name` | Executable name | `myapp` | +| `-config` | Config file path | | +| `-out` | Output directory | `.` | + +Base command: `wails3 update` + +## Update Commands + +Update commands help manage and update project assets. All update commands use the base command: `wails3 update `. + +### `update cli` +Updates the Wails CLI to a new version. + +```bash +wails3 update cli [flags] +``` + +#### Flags +| Flag | Description | Default | +|-------------|---------------------------------|---------| +| `-pre` | Update to latest pre-release | `false` | +| `-version` | Update to specific version | | +| `-nocolour` | Disable colored output | `false` | + +The update cli command allows you to update your Wails CLI installation. By default, it updates to the latest stable release. +You can use the `-pre` flag to update to the latest pre-release version, or specify a particular version using the `-version` flag. + +After updating, remember to update your project's go.mod file to use the same version: +```bash +require github.com/wailsapp/wails/v3 v3.x.x +``` + +### `update build-assets` +Updates the build assets using the given config file. + +```bash +wails3 update build-assets [flags] +``` + +#### Flags +| Flag | Description | Default | +|----------------|---------------------|---------| +| `-config` | Config file path | | +| `-dir` | Output directory | `build` | +| `-silent` | Suppress output | `false` | +| `-company` | Company name | | +| `-productname` | Product name | | +| `-description` | Product description | | +| `-version` | Product version | | +| `-identifier` | Product identifier | | +| `-copyright` | Copyright notice | | +| `-comments` | File comments | | + +Base command: `wails3` + +## Utility Commands + +Utility commands provide helpful shortcuts for common tasks. Use these commands directly with the base command: `wails3 `. + +### `docs` +Opens the Wails documentation in your default browser. + +```bash +wails3 docs +``` + +### `releasenotes` +Shows the release notes for the current or specified version. + +```bash +wails3 releasenotes [flags] +``` + +#### Flags +| Flag | Description | Default | +|------|-----------------------------------|---------| +| `-v` | Version to show release notes for | | +| `-n` | Disable colour output | `false` | + +### `version` +Prints the current version of Wails. + +```bash +wails3 version +``` + +### `sponsor` +Opens the Wails sponsorship page in your default browser. + +```bash +wails3 sponsor diff --git a/docs/src/content/docs/guides/custom-templates.mdx b/docs/src/content/docs/guides/custom-templates.mdx new file mode 100644 index 000000000..3da561b35 --- /dev/null +++ b/docs/src/content/docs/guides/custom-templates.mdx @@ -0,0 +1,293 @@ +--- +title: Creating Custom Templates +description: Learn how to create and customise your own Wails v3 templates +sidebar: + order: 50 +--- + +This guide will walk you through the process of creating a custom template for Wails v3. + +## Why would I make a custom template? + +Wails comes with a number of pre-configured templates that allow you to get your application up and running quickly. But if you need a more customised setup, you can create your own template to suit your needs. This can then be shared with the Wails community for others to use. + +### 1. Generating a Template + +To create a custom template, you can use the `wails generate template` command: + +```bash +wails3 generate template -name mytemplate +``` + +This will create a new directory called "mytemplate" in your current directory. + +The `wails3 generate template` command supports the following options: + +| Option | Description | Default | +|----------------|---------------------------------------------------|-------------------| +| `-name` | The name of your template (required) | - | +| `-frontend` | Path to an existing frontend directory to include | - | +| `-author` | The author of the template | - | +| `-description` | A description of the template | - | +| `-helpurl` | URL for template documentation | - | +| `-dir` | Directory to generate the template in | Current directory | +| `-version` | Template version | v0.0.1 | + +For example, to create a template with all options: + +```bash +wails3 generate template \ + -name "My Custom Template" \ + -frontend ./my-existing-frontend \ + -author "Your Name" \ + -description "A template with my preferred setup" \ + -helpurl "https://github.com/yourusername/template-docs" \ + -dir ./templates \ + -version "v1.0.0" +``` + + :::tip + Using the `-frontend` option will copy an existing web frontend project into the template. + ::: + +### 2. Configure Template Metadata + +If you didn't specify the template configuration when generating the template, you can update the `template.json` file in the template directory: + +```json5 +{ + "name": "Your Template Name", // Display name of your template + "shortname": "template-shortname", // Used when referencing your template + "author": "Your Name", // Template author + "description": "Template description", // Template description + "helpurl": "https://your-docs.com", // Documentation URL + "version": "v0.0.1", // Template version + "schema": 3 // Must be kept as 3 for Wails v3 +} +``` +:::caution +The `schema` field must remain set to `3` for compatibility with Wails v3. +::: + +### 3. Set Up Build Tasks + +In the `build` directory is `Taskfile.yml` where you can define your template's build process. +This file uses [Task](https://taskfile.dev) for build automation. The key steps are: + +```yaml +tasks: + install:frontend:deps: + summary: Install frontend dependencies + dir: frontend + sources: + - package.json + - package-lock.json + generates: + - node_modules/* + preconditions: + - sh: npm version + msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/" + cmds: + - npm install + + build:frontend: + summary: Build the frontend project + dir: frontend + sources: + - "**/*" + generates: + - dist/* + deps: + - task: install:frontend:deps + - task: generate:bindings + cmds: + - npm run build -q + + +``` + +### 4. Frontend Setup + +If you did not use `-frontend` when generating the template, you need to add frontend files to your template. + +There are a number of ways to set up your frontend: starting from scratch or using an existing framework. + +import { Tabs, TabItem } from '@astrojs/starlight/components'; + + + + If you want to start from scratch, you can create your frontend project just like you would for any web application. + The `frontend` directory in your template is just a regular directory where you can set up your preferred + development environment. You might want to use build tools like Vite, webpack, or even just plain HTML, CSS, and + JavaScript - it's entirely up to you! + + For example, if you're using Vite, you could navigate to the `frontend` directory and run: + + ```bash + npm create vite@latest . + ``` + + Then follow the prompts to set up your project exactly how you want it. The key thing to remember is that this is just a regular frontend project - you can use any tools, frameworks, or libraries you're familiar with. + + + For this example, we'll use [Vite](https://vitejs.dev/) to set up a React frontend project: + + ```bash + npm create vite@latest frontend -- --template react + cd frontend + npm install + ``` + + + + + +Now you have the frontend files in place, update `common/Taskfile.yml` with the appropriate commands: + ```yaml + tasks: + install:frontend:deps: + summary: Install frontend dependencies + dir: frontend + sources: + - package.json + - package-lock.json + generates: + - node_modules/* + preconditions: + - sh: npm version + msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/" + cmds: + - npm install + + build:frontend: + summary: Build the frontend project + dir: frontend + sources: + - "**/*" + generates: + - dist/* + deps: + - task: install:frontend:deps + - task: generate:bindings + cmds: + - npm run build -q + ``` + + :::note + For this example, the default Tasks do not need updating as they use the standard `npm install` and `npm run build` commands. + ::: + +### 5. Configure the Go Application + +The default files in the template directory are sufficient to get users started. However, you may want to provide some additional functionality to demonstrate your template's capabilities. The best way to do this is to rename `main.go.tmpl` to `main.go` and edit it like any other Go file. Once finished, ensure you rename it back to `main.go.tmpl` before committing your changes. If you do not care about having a templated `main.go` file (the default template injests the project name into the `Name` field of the application), you can skip this step. + +#### Template Variables + +Wails uses Go's templating engine to process files with the `.tmpl` extension. During template generation, several variables are available for use in your template files: + +| Variable | Description | Example | +|----------------------|----------------------------------|-----------------------------------| +| `Name` | The name of the project | `"MyApp"` | +| `BinaryName` | The name of the generated binary | `"myapp"` | +| `ProductName` | The product name | `"My Application"` | +| `ProductDescription` | Description of the product | `"An awesome application"` | +| `ProductVersion` | Version of the product | `"1.0.0"` | +| `ProductCompany` | Company name | `"My Company Ltd"` | +| `ProductCopyright` | Copyright information | `"Copyright 2024 My Company Ltd"` | +| `ProductComments` | Additional product comments | `"Built with Wails"` | +| `ProductIdentifier` | Unique product identifier | `"com.mycompany.myapp"` | +| `Typescript` | Whether TypeScript is being used | `true` or `false` | +| `WailsVersion` | The version of Wails being used | `"3.0.0"` | + +You can use these variables in your template files using Go's template syntax: + +```go +// main.go.tmpl +package main + +import ( + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Name: "{{.ProductName}}", + Description: "{{.ProductDescription}}", + }) + // ... +} +``` + + :::tip + Templating can be applied to any file in your template, even html files, so long as the filename has `.tmpl` in its name. + ::: + +### 6. Testing Your Template + +To test your template: + +1. Generate a project using your template: `wails3 init -n testproject -t path/to/your/template` +2. Run `wails3 build` to generate the production build and make sure the binary in `bin` runs correctly +3. Run `wails3 dev` to start the development server. +4. Test that changes to the frontend code are reflected in the application. +5. Test that changes to the Go code rebuild and relaunch the application + +### 7. Sharing Your Template + +Once your template is ready, you can share it with the community by hosting it on GitHub. Here's how: + +1. Create a new GitHub repository for your template +2. Push your template code to the repository +3. Tag your releases using semantic versioning (e.g., v1.0.0) + +Users can then use your template directly from GitHub using the HTTPS URL: + +```bash +wails3 init -n myapp -t https://github.com/yourusername/your-template +``` + +You can also specify a particular version using the URL format: + +```bash +# Use a specific version tag +wails3 init -n myapp -t https://github.com/yourusername/your-template/releases/tag/v1.0.0 + +# Use a specific branch +wails3 init -n myapp -t https://github.com/yourusername/your-template/tree/main +``` + +To test your template before sharing: + +1. Push your changes to GitHub +2. Create a new test project using the HTTPS URL: + ```bash + wails3 init -n testapp -t https://github.com/yourusername/your-template + ``` +3. Verify that all files are correctly generated +4. Test the build and development workflow as described in the testing section + + :::note + Make sure your repository is public if you want others to use your template. + ::: + +For more information, visit the [Wails documentation](https://wails.io) + + +## Best Practices + +Let's talk about some key practices that will help make your template more useful and maintainable. Here are the main areas to focus on: + +1. **Make Your Template Easy to Understand** + - Write a clear, helpful README.md that gets users started quickly + - Add comments in your config files to explain the "why" behind your choices + - Show examples of common customisations - users love to see real-world use cases! + +2. **Keep Dependencies Happy** + - Stay on top of your frontend package updates + - Lock down specific versions in package.json to avoid surprises + - Let users know upfront what they'll need to have installed + +3. **Love Your Template** + - Keep it fresh with regular updates + - Give it a thorough test drive before sharing + - Share it with the Wails community - we'd love to see what you create! diff --git a/docs/src/content/docs/guides/custom-transport.mdx b/docs/src/content/docs/guides/custom-transport.mdx new file mode 100644 index 000000000..ef72302a4 --- /dev/null +++ b/docs/src/content/docs/guides/custom-transport.mdx @@ -0,0 +1,172 @@ +--- +title: Creating Custom Transport Layer +description: Learn how to create and customize your own Wails v3 custom IPC transport layer +sidebar: + order: 51 +--- + +Wails v3 allows you to provide a custom IPC transport layer while retaining all generated bindings and event communication. This enables you to replace the default HTTP fetch-based transport with WebSockets, custom protocols, or any other transport mechanism. + +## Overview + +By default, Wails uses HTTP fetch requests from the frontend to communicate with the backend via `/wails/runtime`. The custom transport API allows you to: + +- Replace the HTTP transport with WebSockets, gRPC, or any custom protocol +- Maintain full compatibility with Wails code generation +- Keep all existing bindings, events, dialogs, and other Wails features +- Implement your own connection management, authentication, and error handling + +## Architecture + +```text +┌─────────────────────────────────────────────────┐ +│ Frontend (TypeScript) │ +│ - Generated bindings still work │ +│ - Your custom client transport │ +└──────────────────┬──────────────────────────────┘ + │ + │ Your Protocol (WebSocket/etc) + │ +┌──────────────────▼──────────────────────────────┐ +│ Backend (Go) │ +│ - Your Transport implementation │ +│ - Wails MessageProcessor │ +│ - All existing Wails infrastructure │ +└─────────────────────────────────────────────────┘ +``` + +## Usage + +### 1. Implement the Transport Interface + +Create a custom transport by implementing the `Transport` interface: + +```go +package main + +import ( + "context" + "github.com/wailsapp/wails/v3/pkg/application" +) + +type MyCustomTransport struct { + // Your fields +} + +func (t *MyCustomTransport) Start(ctx context.Context, processor *application.MessageProcessor) error { + // Initialize your transport (WebSocket server, gRPC server, etc.) + // When you receive requests, call processor.HandleRuntimeCallWithIDs() + return nil +} + +func (t *MyCustomTransport) Stop() error { + // Clean up your transport + return nil +} +``` + +### 2. Configure Your Application + +Pass your custom transport to the application options: + +```go +func main() { + app := application.New(application.Options{ + Name: "My App", + Transport: &MyCustomTransport{}, + // ... other options + }) + + err := app.Run() + if err != nil { + log.Fatal(err) + } +} +``` + +### 3. Modify Frontend Runtime + +If using a custom transport, you'll need to modify the frontend runtime to use your transport instead of HTTP fetch. Implement `RuntimeTransport` interface that will be used to handle requests: + +```typescript +const { setTransport } = await import('/wails/runtime.js'); + +class MyRuntimeTransport { + call(objectID: number, method: number, windowName: string, args: any): Promise { + // TODO: implement IPC call with your transport protocol + + return resp; + } +} + +const myTransport = new MyRuntimeTransport(); +setTransport(myTransport); +``` + +## Notes + +- The default HTTP transport continues to work if no custom transport is specified +- Generated bindings remain unchanged - only the transport layer changes +- Events, dialogs, clipboard, and all other Wails features work transparently +- You're responsible for error handling, reconnection logic, and security in your custom transport +- The WebSocket example provided is for demonstration and may need hardening for production use + +## API Reference + +### Transport Interface + +```go +type Transport interface { + Start(ctx context.Context, messageProcessor *application.MessageProcessor) error + Stop() error +} +``` + +### AssetServerTransport Interface (Optional) + +For browser-based deployments or when you want to serve both assets and IPC through your custom transport, implement the `AssetServerTransport` interface: + +```go +type AssetServerTransport interface { + Transport + + // ServeAssets configures the transport to serve assets alongside IPC. + // The assetHandler is Wails' internal asset server that handles all assets, + // runtime.js, capabilities, flags, etc. + ServeAssets(assetHandler http.Handler) error +} +``` + +**When to implement this interface:** +- Running the app in a browser instead of a webview +- Serving assets over HTTP alongside your custom IPC transport +- Building network-accessible applications + +**Example implementation:** + +```go +func (t *MyTransport) ServeAssets(assetHandler http.Handler) error { + mux := http.NewServeMux() + + // Mount your IPC endpoint + mux.HandleFunc("/my/ipc/endpoint", t.handleIPC) + + // Mount Wails asset server for everything else + mux.Handle("/", assetHandler) + + // Start HTTP server + t.httpServer.Handler = mux + go t.httpServer.ListenAndServe() + + return nil +} +``` + +When `ServeAssets()` is called, the assetHandler provides: +- All static assets (HTML, CSS, JS, images, etc.) +- `/wails/runtime.js` - The Wails runtime library + +## See Also + +- `transport.go` - Core transport interfaces and types +- `messageprocessor.go` - The underlying message processor that handles all Wails IPC diff --git a/docs/src/content/docs/guides/customising-windows.mdx b/docs/src/content/docs/guides/customising-windows.mdx new file mode 100644 index 000000000..915439a28 --- /dev/null +++ b/docs/src/content/docs/guides/customising-windows.mdx @@ -0,0 +1,129 @@ +--- +title: Customising Windows in Wails +sidebar: + order: 10 +--- + +import {Badge} from '@astrojs/starlight/components'; + +Relevant Platforms: +
      + +Wails provides an API to control the appearance and functionality of the +controls of a window. This functionality is available on Windows and macOS, but +not on Linux. + +## Setting the Window Button States + +The button states are defined by the `ButtonState` enum: + +```go +type ButtonState int + +const ( + ButtonEnabled ButtonState = 0 + ButtonDisabled ButtonState = 1 + ButtonHidden ButtonState = 2 +) +``` + +- `ButtonEnabled`: The button is enabled and visible. +- `ButtonDisabled`: The button is visible but disabled (grayed out). +- `ButtonHidden`: The button is hidden from the titlebar. + +The button states can be set during window creation or at runtime. + +### Setting Button States During Window Creation + +When creating a new window, you can set the initial state of the buttons using +the `WebviewWindowOptions` struct: + +```go title="main.go" +package main + +import ( + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Name: "My Application", + }) + + app.Window.NewWithOptions(application.WebviewWindowOptions{ + MinimiseButtonState: application.ButtonHidden, + MaximiseButtonState: application.ButtonDisabled, + CloseButtonState: application.ButtonEnabled, + }) + + app.Run() +} +``` + +In the example above, the minimise button is hidden, the maximise button is +inactive (grayed out), and the close button is active. + +### Setting Button States at Runtime + +You can also change the button states at runtime using the following methods on +the `Window` interface: + +```go +window.SetMinimiseButtonState(wails.ButtonHidden) +window.SetMaximiseButtonState(wails.ButtonEnabled) +window.SetCloseButtonState(wails.ButtonDisabled) +``` + +### Platform Differences + +The button state functionality behaves slightly differently on Windows and +macOS: + +| | Windows | Mac | +|-----------------------|------------------------|------------------------| +| Disable Min/Max/Close | Disables Min/Max/Close | Disables Min/Max/Close | +| Hide Min | Disables Min | Hides Min button | +| Hide Max | Disables Max | Hides Max button | +| Hide Close | Hides all controls | Hides Close | + +Note: On Windows, it is not possible to hide the Min/Max buttons individually. +However, disabling both will hide both of the controls and only show the close +button. + +### Controlling Window Style (Windows) + +To control the style of the titlebar on Windows, you can use the `ExStyle` field +in the `WebviewWindowOptions` struct: + +Example: + +```go title="main.go" +package main + +import ( + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/w32" +) + +func main() { + app := application.New(application.Options{ + Name: "My Application", + }) + + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Windows: application.WindowsWindow{ + ExStyle: w32.WS_EX_TOOLWINDOW | w32.WS_EX_NOREDIRECTIONBITMAP | w32.WS_EX_TOPMOST, + }, + }) + + app.Run() +} +``` + +Other options that affect the Extended Style of a window will be overridden by +this setting: + +- HiddenOnTaskbar +- AlwaysOnTop +- IgnoreMouseEvents +- BackgroundType diff --git a/docs/src/content/docs/guides/distribution/auto-updates.mdx b/docs/src/content/docs/guides/distribution/auto-updates.mdx new file mode 100644 index 000000000..273915653 --- /dev/null +++ b/docs/src/content/docs/guides/distribution/auto-updates.mdx @@ -0,0 +1,564 @@ +--- +title: Auto-Updates +description: Implement automatic application updates with Wails v3 +sidebar: + order: 5 +--- + +import { Tabs, TabItem } from '@astrojs/starlight/components'; +import { Steps } from '@astrojs/starlight/components'; +import { Card, CardGrid } from '@astrojs/starlight/components'; +import { Aside } from '@astrojs/starlight/components'; + +# Automatic Updates + +Wails v3 provides a built-in updater system that supports automatic update checking, downloading, and installation. The updater includes support for binary delta updates (patches) for minimal download sizes. + + + + Configure periodic update checks in the background + + + Download only what changed with bsdiff patches + + + Works on macOS, Windows, and Linux + + + SHA256 checksums and optional signature verification + + + +## Quick Start + +Add the updater service to your application: + +```go title="main.go" +package main + +import ( + "github.com/wailsapp/wails/v3/pkg/application" + "time" +) + +func main() { + // Create the updater service + updater, err := application.CreateUpdaterService( + "1.0.0", // Current version + application.WithUpdateURL("https://updates.example.com/myapp/"), + application.WithCheckInterval(24 * time.Hour), + ) + if err != nil { + panic(err) + } + + app := application.New(application.Options{ + Name: "MyApp", + Services: []application.Service{ + application.NewService(updater), + }, + }) + + // ... rest of your app + app.Run() +} +``` + +Then use it from your frontend: + +```typescript title="App.tsx" +import { updater } from './bindings/myapp'; + +async function checkForUpdates() { + const update = await updater.CheckForUpdate(); + + if (update) { + console.log(`New version available: ${update.version}`); + console.log(`Release notes: ${update.releaseNotes}`); + + // Download and install + await updater.DownloadAndApply(); + } +} +``` + +## Configuration Options + +The updater supports various configuration options: + +```go +updater, err := application.CreateUpdaterService( + "1.0.0", + // Required: URL where update manifests are hosted + application.WithUpdateURL("https://updates.example.com/myapp/"), + + // Optional: Check for updates automatically every 24 hours + application.WithCheckInterval(24 * time.Hour), + + // Optional: Allow pre-release versions + application.WithAllowPrerelease(true), + + // Optional: Update channel (stable, beta, canary) + application.WithChannel("stable"), + + // Optional: Require signed updates + application.WithRequireSignature(true), + application.WithPublicKey("your-ed25519-public-key"), +) +``` + +## Update Manifest Format + +Host an `update.json` file on your server: + +```json title="update.json" +{ + "version": "1.2.0", + "release_date": "2025-01-15T00:00:00Z", + "release_notes": "## What's New\n\n- Feature A\n- Bug fix B", + "platforms": { + "macos-arm64": { + "url": "https://updates.example.com/myapp/myapp-1.2.0-macos-arm64.tar.gz", + "size": 12582912, + "checksum": "sha256:abc123...", + "patches": [ + { + "from": "1.1.0", + "url": "https://updates.example.com/myapp/patches/1.1.0-to-1.2.0-macos-arm64.patch", + "size": 14336, + "checksum": "sha256:def456..." + } + ] + }, + "macos-amd64": { + "url": "https://updates.example.com/myapp/myapp-1.2.0-macos-amd64.tar.gz", + "size": 13107200, + "checksum": "sha256:789xyz..." + }, + "windows-amd64": { + "url": "https://updates.example.com/myapp/myapp-1.2.0-windows-amd64.zip", + "size": 14680064, + "checksum": "sha256:ghi789..." + }, + "linux-amd64": { + "url": "https://updates.example.com/myapp/myapp-1.2.0-linux-amd64.tar.gz", + "size": 11534336, + "checksum": "sha256:jkl012..." + } + }, + "minimum_version": "1.0.0", + "mandatory": false +} +``` + +### Platform Keys + +| Platform | Key | +|----------|-----| +| macOS (Apple Silicon) | `macos-arm64` | +| macOS (Intel) | `macos-amd64` | +| Windows (64-bit) | `windows-amd64` | +| Linux (64-bit) | `linux-amd64` | +| Linux (ARM64) | `linux-arm64` | + +## Frontend API + +The updater exposes methods that are automatically bound to your frontend: + +### TypeScript Types + +```typescript +interface UpdateInfo { + version: string; + releaseDate: Date; + releaseNotes: string; + size: number; + patchSize?: number; + mandatory: boolean; + hasPatch: boolean; +} + +interface Updater { + // Get the current application version + GetCurrentVersion(): string; + + // Check if an update is available + CheckForUpdate(): Promise; + + // Download the update (emits progress events) + DownloadUpdate(): Promise; + + // Apply the downloaded update (restarts app) + ApplyUpdate(): Promise; + + // Download and apply in one call + DownloadAndApply(): Promise; + + // Get current state: idle, checking, available, downloading, ready, installing, error + GetState(): string; + + // Get the available update info + GetUpdateInfo(): UpdateInfo | null; + + // Get the last error message + GetLastError(): string; + + // Reset the updater state + Reset(): void; +} +``` + +### Progress Events + +Listen for download progress events: + +```typescript +import { Events } from '@wailsio/runtime'; + +Events.On('updater:progress', (data) => { + console.log(`Downloaded: ${data.downloaded} / ${data.total}`); + console.log(`Progress: ${data.percentage.toFixed(1)}%`); + console.log(`Speed: ${(data.bytesPerSecond / 1024 / 1024).toFixed(2)} MB/s`); +}); +``` + +## Complete Example + +Here's a complete example with a React component: + +```tsx title="UpdateChecker.tsx" +import { useState, useEffect } from 'react'; +import { updater } from './bindings/myapp'; +import { Events } from '@wailsio/runtime'; + +interface Progress { + downloaded: number; + total: number; + percentage: number; + bytesPerSecond: number; +} + +export function UpdateChecker() { + const [checking, setChecking] = useState(false); + const [updateInfo, setUpdateInfo] = useState(null); + const [downloading, setDownloading] = useState(false); + const [progress, setProgress] = useState(null); + const [error, setError] = useState(null); + + useEffect(() => { + // Listen for progress events + const cleanup = Events.On('updater:progress', (data: Progress) => { + setProgress(data); + }); + + // Check for updates on mount + checkForUpdates(); + + return () => cleanup(); + }, []); + + async function checkForUpdates() { + setChecking(true); + setError(null); + + try { + const info = await updater.CheckForUpdate(); + setUpdateInfo(info); + } catch (err) { + setError(err.message); + } finally { + setChecking(false); + } + } + + async function downloadAndInstall() { + setDownloading(true); + setError(null); + + try { + await updater.DownloadAndApply(); + // App will restart automatically + } catch (err) { + setError(err.message); + setDownloading(false); + } + } + + if (checking) { + return
      Checking for updates...
      ; + } + + if (error) { + return ( +
      +

      Error: {error}

      + +
      + ); + } + + if (!updateInfo) { + return ( +
      +

      You're up to date! (v{updater.GetCurrentVersion()})

      + +
      + ); + } + + if (downloading) { + return ( +
      +

      Downloading update...

      + {progress && ( +
      + +

      {progress.percentage.toFixed(1)}%

      +

      {(progress.bytesPerSecond / 1024 / 1024).toFixed(2)} MB/s

      +
      + )} +
      + ); + } + + return ( +
      +

      Update Available!

      +

      Version {updateInfo.version} is available

      +

      Size: {updateInfo.hasPatch + ? `${(updateInfo.patchSize / 1024).toFixed(0)} KB (patch)` + : `${(updateInfo.size / 1024 / 1024).toFixed(1)} MB`} +

      +
      + + +
      + ); +} +``` + +## Update Strategies + +### Check on Startup + +```go +func (a *App) OnStartup(ctx context.Context) { + // Check for updates after a short delay + go func() { + time.Sleep(5 * time.Second) + info, err := a.updater.CheckForUpdate() + if err == nil && info != nil { + // Emit event to frontend + application.Get().EmitEvent("update-available", info) + } + }() +} +``` + +### Background Checking + +Configure automatic background checks: + +```go +updater, _ := application.CreateUpdaterService( + "1.0.0", + application.WithUpdateURL("https://updates.example.com/myapp/"), + application.WithCheckInterval(6 * time.Hour), // Check every 6 hours +) +``` + +### Manual Check Menu Item + +```go +menu := application.NewMenu() +menu.Add("Check for Updates...").OnClick(func(ctx *application.Context) { + info, err := updater.CheckForUpdate() + if err != nil { + application.InfoDialog().SetMessage("Error checking for updates").Show() + return + } + if info == nil { + application.InfoDialog().SetMessage("You're up to date!").Show() + return + } + // Show update dialog... +}) +``` + +## Delta Updates (Patches) + +Delta updates (patches) allow users to download only the changes between versions, dramatically reducing download sizes. + +### How It Works + +1. When building a new version, generate patches from previous versions +2. Host patches alongside full updates on your server +3. The updater automatically downloads patches when available +4. If patching fails, it falls back to the full download + +### Generating Patches + +Patches are generated using the bsdiff algorithm. You'll need the `bsdiff` tool: + +```bash +# Install bsdiff (macOS) +brew install bsdiff + +# Install bsdiff (Ubuntu/Debian) +sudo apt-get install bsdiff + +# Generate a patch +bsdiff old-binary new-binary patch.bsdiff +``` + +### Patch File Naming + +Organize your patches in your update directory: + +``` +updates/ +├── update.json +├── myapp-1.2.0-macos-arm64.tar.gz +├── myapp-1.2.0-windows-amd64.zip +└── patches/ + ├── 1.0.0-to-1.2.0-macos-arm64.patch + ├── 1.1.0-to-1.2.0-macos-arm64.patch + ├── 1.0.0-to-1.2.0-windows-amd64.patch + └── 1.1.0-to-1.2.0-windows-amd64.patch +``` + + + +## Hosting Updates + +### Static File Hosting + +Updates can be hosted on any static file server: + +- **Amazon S3** / **Cloudflare R2** +- **Google Cloud Storage** +- **GitHub Releases** +- **Any CDN or web server** + +Example S3 bucket structure: + +``` +s3://my-updates-bucket/myapp/ +├── stable/ +│ ├── update.json +│ ├── myapp-1.2.0-macos-arm64.tar.gz +│ └── patches/ +│ └── 1.1.0-to-1.2.0-macos-arm64.patch +└── beta/ + └── update.json +``` + +### CORS Configuration + +If hosting on a different domain, configure CORS: + +```json +{ + "CORSRules": [ + { + "AllowedOrigins": ["*"], + "AllowedMethods": ["GET"], + "AllowedHeaders": ["*"] + } + ] +} +``` + +## Security + +### Checksum Verification + +All downloads are verified against SHA256 checksums in the manifest: + +```json +{ + "checksum": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" +} +``` + +### Signature Verification + +For additional security, enable signature verification: + + +1. **Generate a key pair**: + ```bash + # Generate Ed25519 key pair + openssl genpkey -algorithm Ed25519 -out private.pem + openssl pkey -in private.pem -pubout -out public.pem + ``` + +2. **Sign your update manifest**: + ```bash + openssl pkeyutl -sign -inkey private.pem -in update.json -out update.json.sig + ``` + +3. **Configure the updater**: + ```go + updater, _ := application.CreateUpdaterService( + "1.0.0", + application.WithUpdateURL("https://updates.example.com/myapp/"), + application.WithRequireSignature(true), + application.WithPublicKey("MCowBQYDK2VwAyEA..."), // Base64-encoded public key + ) + ``` + + +## Best Practices + +### Do + +- Test updates thoroughly before deploying +- Keep previous versions available for rollback +- Show release notes to users +- Allow users to skip non-mandatory updates +- Use HTTPS for all downloads +- Verify checksums before applying updates +- Handle errors gracefully + +### Don't + +- Force immediate restarts without warning +- Skip checksum verification +- Interrupt users during important work +- Delete the previous version immediately +- Ignore update failures + +## Troubleshooting + +### Update Not Found + +- Verify the manifest URL is correct +- Check the platform key matches (e.g., `macos-arm64`) +- Ensure the version in the manifest is newer + +### Download Fails + +- Check network connectivity +- Verify the download URL is accessible +- Check CORS configuration if cross-origin + +### Patch Fails + +- The updater automatically falls back to full download +- Ensure `bspatch` is available on the system +- Verify the patch checksum is correct + +### Application Won't Restart + +- On macOS, ensure the app is properly code-signed +- On Windows, check for file locks +- On Linux, verify file permissions + +## Next Steps + +- [Code Signing](/guides/build/signing) - Sign your updates +- [Creating Installers](/guides/installers) - Package your application +- [CI/CD Integration](/guides/ci-cd) - Automate your release process diff --git a/docs/src/content/docs/guides/distribution/custom-protocols.mdx b/docs/src/content/docs/guides/distribution/custom-protocols.mdx new file mode 100644 index 000000000..39a9b157c --- /dev/null +++ b/docs/src/content/docs/guides/distribution/custom-protocols.mdx @@ -0,0 +1,664 @@ +--- +title: Custom URL Protocols +description: Register custom URL schemes to launch your application from links +sidebar: + order: 3 +--- + +import { Tabs, TabItem, Aside } from '@astrojs/starlight/components'; + +Custom URL protocols (also called URL schemes) allow your application to be launched when users click links with your custom protocol, such as `myapp://action` or `myapp://open/document`. + +## Overview + +Custom protocols enable: +- **Deep linking**: Launch your app with specific data +- **Browser integration**: Handle links from web pages +- **Email links**: Open your app from email clients +- **Inter-app communication**: Launch from other applications + +**Example**: `myapp://open/document?id=123` launches your app and opens document 123. + +## Configuration + +Define custom protocols in your application options: + +```go +package main + +import "github.com/wailsapp/wails/v3/pkg/application" + +func main() { + app := application.New(application.Options{ + Name: "My Application", + Description: "My awesome application", + Protocols: []application.Protocol{ + { + Scheme: "myapp", + Description: "My Application Protocol", + Role: "Editor", // macOS only + }, + }, + }) + + // Register handler for protocol events + app.Event.On(application.Events.ApplicationOpenedWithURL, func(event *application.ApplicationEvent) { + url := event.Context().ClickedURL() + handleCustomURL(url) + }) + + app.Run() +} + +func handleCustomURL(url string) { + // Parse and handle the custom URL + // Example: myapp://open/document?id=123 + println("Received URL:", url) +} +``` + +## Protocol Handler + +Listen for protocol events to handle incoming URLs: + +```go +app.Event.On(application.Events.ApplicationOpenedWithURL, func(event *application.ApplicationEvent) { + url := event.Context().ClickedURL() + + // Parse the URL + parsedURL, err := parseCustomURL(url) + if err != nil { + app.Logger.Error("Failed to parse URL:", err) + return + } + + // Handle different actions + switch parsedURL.Action { + case "open": + openDocument(parsedURL.DocumentID) + case "settings": + showSettings() + case "user": + showUser Profile(parsedURL.UserID) + default: + app.Logger.Warn("Unknown action:", parsedURL.Action) + } +}) +``` + +## URL Structure + +Design clear, hierarchical URL structures: + +``` +myapp://action/resource?param=value + +Examples: +myapp://open/document?id=123 +myapp://settings/theme?mode=dark +myapp://user/profile?username=john +``` + +**Best practices:** +- Use lowercase scheme names +- Keep schemes short and memorable +- Use hierarchical paths for resources +- Include query parameters for optional data +- URL-encode special characters + +## Platform Registration + +Custom protocols are registered differently on each platform. + + + + +### Windows NSIS Installer + +**Wails v3 automatically registers custom protocols** when using NSIS installers. + +#### Automatic Registration + +When you build your application with `wails3 build`, the NSIS installer: +1. Automatically registers all protocols defined in `application.Options.Protocols` +2. Associates protocols with your application executable +3. Sets up proper registry entries +4. Removes protocol associations during uninstall + +**No additional configuration required!** + +#### How It Works + +The NSIS template includes built-in macros: +- `wails.associateCustomProtocols` - Registers protocols during installation +- `wails.unassociateCustomProtocols` - Removes protocols during uninstall + +These macros are automatically called based on your `Protocols` configuration. + +#### Manual Registry (Advanced) + +If you need manual registration (outside NSIS): + +```batch +@echo off +REM Register custom protocol +REG ADD "HKEY_CURRENT_USER\SOFTWARE\Classes\myapp" /ve /d "URL:My Application Protocol" /f +REG ADD "HKEY_CURRENT_USER\SOFTWARE\Classes\myapp" /v "URL Protocol" /t REG_SZ /d "" /f +REG ADD "HKEY_CURRENT_USER\SOFTWARE\Classes\myapp\shell\open\command" /ve /d "\"%1\"" /f +``` + +#### Testing + +Test your protocol registration: + +```powershell +# Open protocol URL from PowerShell +Start-Process "myapp://test/action" + +# Or from command prompt +start myapp://test/action +``` + +### Windows MSIX Package + +Custom protocols are also automatically registered when using MSIX packaging. + +#### Automatic Registration + +When you build your application with MSIX, the manifest automatically includes protocol registrations from your `build/config.yml` protocols configuration. + +The generated manifest includes: + +```xml + + + My Application Protocol + + +``` + +#### Universal Links (Web-to-App Linking) + +Windows supports **Web-to-App linking**, which works similarly to Universal Links on macOS. When deploying your application as an MSIX package, you can enable HTTPS links to launch your app directly. + + + +To enable Web-to-App linking, follow the [Microsoft guide on web-to-app linking](https://learn.microsoft.com/en-us/windows/apps/develop/launch/web-to-app-linking). You'll need to: + +1. **Manually add App URI Handler to your MSIX manifest** (`build/windows/msix/app_manifest.xml`): + ```xml + + + + + + ``` + +2. **Configure `windows-app-web-link` on your website:** Host a `windows-app-web-link` file at `https://myawesomeapp.com/.well-known/windows-app-web-link`. This file should contain your app's package information and the paths it handles. + +When a Web-to-App link launches your application, you'll receive the same `ApplicationOpenedWithURL` event as with custom protocol schemes. + + + + + +### Info.plist Configuration + +On macOS, protocols are registered via your `Info.plist` file. + +#### Automatic Configuration + +Wails automatically generates the `Info.plist` with your protocols when you build with `wails3 build`. + +The protocols from `application.Options.Protocols` are added to: + +```xml +CFBundleURLTypes + + + CFBundleURLName + My Application Protocol + CFBundleURLSchemes + + myapp + + CFBundleTypeRole + Editor + + +``` + +#### Testing + +```bash +# Open protocol URL from terminal +open "myapp://test/action" + +# Check registered handlers +/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister -dump | grep myapp +``` + +#### Universal Links + +In addition to custom protocol schemes, macOS also supports **Universal Links**, which allow your app to be launched by regular HTTPS links (e.g., `https://myawesomeapp.com/path`). Universal Links provide a seamless user experience between your web and desktop app. + + + +To enable Universal Links, follow the [Apple guide on supporting Universal Links in your app](https://developer.apple.com/documentation/xcode/supporting-universal-links-in-your-app). You'll need to: + +1. **Add entitlements** in your `entitlements.plist`: + ```xml + com.apple.developer.associated-domains + + applinks:myawesomeapp.com + + ``` + +2. **Add NSUserActivityTypes to Info.plist**: + ```xml + NSUserActivityTypes + + NSUserActivityTypeBrowsingWeb + + ``` + +3. **Configure `apple-app-site-association` on your website:** Host an `apple-app-site-association` file at `https://myawesomeapp.com/.well-known/apple-app-site-association`. + +When a Universal Link triggers your app, you'll receive the same `ApplicationOpenedWithURL` event, making the handling code identical to custom protocol schemes. + + + + + +### Desktop Entry + +On Linux, protocols are registered via `.desktop` files. + +#### Automatic Configuration + +Wails generates a desktop entry file with protocol handlers when you build with `wails3 build`. + +**Fixed in v3**: Linux desktop template now properly includes protocol handling. + +The generated desktop file includes: + +```ini +[Desktop Entry] +Type=Application +Name=My Application +Exec=/usr/bin/myapp %u +MimeType=x-scheme-handler/myapp; +``` + +#### Manual Registration + +If needed, manually install the desktop file: + +```bash +# Copy desktop file +cp myapp.desktop ~/.local/share/applications/ + +# Update desktop database +update-desktop-database ~/.local/share/applications/ + +# Register protocol handler +xdg-mime default myapp.desktop x-scheme-handler/myapp +``` + +#### Testing + +```bash +# Open protocol URL +xdg-open "myapp://test/action" + +# Check registered handler +xdg-mime query default x-scheme-handler/myapp +``` + + + + +## Complete Example + +Here's a complete example handling multiple protocol actions: + +```go +package main + +import ( + "fmt" + "net/url" + "strings" + "github.com/wailsapp/wails/v3/pkg/application" +) + +type App struct { + app *application.Application + window *application.WebviewWindow +} + +func main() { + app := application.New(application.Options{ + Name: "DeepLink Demo", + Description: "Custom protocol demonstration", + Protocols: []application.Protocol{ + { + Scheme: "deeplink", + Description: "DeepLink Demo Protocol", + Role: "Editor", + }, + }, + }) + + myApp := &App{app: app} + myApp.setup() + + app.Run() +} + +func (a *App) setup() { + // Create window + a.window = a.app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "DeepLink Demo", + Width: 800, + Height: 600, + URL: "http://wails.localhost/", + }) + + // Handle custom protocol URLs + a.app.Event.On(application.Events.ApplicationOpenedWithURL, func(event *application.ApplicationEvent) { + customURL := event.Context().ClickedURL() + a.handleDeepLink(customURL) + }) +} + +func (a *App) handleDeepLink(rawURL string) { + // Parse URL + parsedURL, err := url.Parse(rawURL) + if err != nil { + a.app.Logger.Error("Failed to parse URL:", err) + return + } + + // Bring window to front + a.window.Show() + a.window.SetFocus() + + // Extract path and query + path := strings.Trim(parsedURL.Path, "/") + query := parsedURL.Query() + + // Handle different actions + parts := strings.Split(path, "/") + if len(parts) == 0 { + return + } + + action := parts[0] + + switch action { + case "open": + if len(parts) >= 2 { + resource := parts[1] + id := query.Get("id") + a.openResource(resource, id) + } + + case "settings": + section := "" + if len(parts) >= 2 { + section = parts[1] + } + a.openSettings(section) + + case "user": + if len(parts) >= 2 { + username := parts[1] + a.openUserProfile(username) + } + + default: + a.app.Logger.Warn("Unknown action:", action) + } +} + +func (a *App) openResource(resourceType, id string) { + fmt.Printf("Opening %s with ID: %s\n", resourceType, id) + // Emit event to frontend + a.app.Event.Emit("navigate", map[string]string{ + "type": resourceType, + "id": id, + }) +} + +func (a *App) openSettings(section string) { + fmt.Printf("Opening settings section: %s\n", section) + a.app.Event.Emit("navigate", map[string]string{ + "page": "settings", + "section": section, + }) +} + +func (a *App) openUserProfile(username string) { + fmt.Printf("Opening user profile: %s\n", username) + a.app.Event.Emit("navigate", map[string]string{ + "page": "user", + "user": username, + }) +} +``` + +## Frontend Integration + +Handle navigation events in your frontend: + +```javascript +import { Events } from '@wailsio/runtime' + +// Listen for navigation events from protocol handler +Events.On('navigate', (event) => { + const { type, id, page, section, user } = event.data + + if (type === 'document') { + // Open document with ID + router.push(`/document/${id}`) + } else if (page === 'settings') { + // Open settings + router.push(`/settings/${section}`) + } else if (page === 'user') { + // Open user profile + router.push(`/user/${user}`) + } +}) +``` + +## Security Considerations + +### Validate All Input + +Always validate and sanitize URLs from external sources: + +```go +func (a *App) handleDeepLink(rawURL string) { + // Parse URL + parsedURL, err := url.Parse(rawURL) + if err != nil { + a.app.Logger.Error("Invalid URL:", err) + return + } + + // Validate scheme + if parsedURL.Scheme != "myapp" { + a.app.Logger.Warn("Invalid scheme:", parsedURL.Scheme) + return + } + + // Validate path + path := strings.Trim(parsedURL.Path, "/") + if !isValidPath(path) { + a.app.Logger.Warn("Invalid path:", path) + return + } + + // Sanitize parameters + params := sanitizeQueryParams(parsedURL.Query()) + + // Process validated URL + a.processDeepLink(path, params) +} + +func isValidPath(path string) bool { + // Only allow alphanumeric and forward slashes + validPath := regexp.MustCompile(`^[a-zA-Z0-9/]+$`) + return validPath.MatchString(path) +} + +func sanitizeQueryParams(query url.Values) map[string]string { + sanitized := make(map[string]string) + for key, values := range query { + if len(values) > 0 { + // Take first value and sanitize + sanitized[key] = sanitizeString(values[0]) + } + } + return sanitized +} +``` + +### Prevent Injection Attacks + +Never execute URLs directly as code or SQL: + +```go +// ❌ DON'T: Execute URL content +func badHandler(url string) { + exec.Command("sh", "-c", url).Run() // DANGEROUS! +} + +// ✅ DO: Parse and validate +func goodHandler(url string) { + parsed, _ := url.Parse(url) + action := parsed.Query().Get("action") + + // Whitelist allowed actions + allowed := map[string]bool{ + "open": true, + "settings": true, + "help": true, + } + + if allowed[action] { + handleAction(action) + } +} +``` + +## Testing + +### Manual Testing + +Test protocol handlers during development: + +**Windows:** +```powershell +Start-Process "myapp://test/action?id=123" +``` + +**macOS:** +```bash +open "myapp://test/action?id=123" +``` + +**Linux:** +```bash +xdg-open "myapp://test/action?id=123" +``` + +### HTML Testing + +Create a test HTML page: + +```html + + + + Protocol Test + + +

      Custom Protocol Test Links

      + + + + +``` + +## Troubleshooting + +### Protocol Not Registered + +**Windows:** +- Check registry: `HKEY_CURRENT_USER\SOFTWARE\Classes\` +- Reinstall with NSIS installer +- Verify installer ran with proper permissions + +**macOS:** +- Rebuild application with `wails3 build` +- Check `Info.plist` in app bundle: `MyApp.app/Contents/Info.plist` +- Reset Launch Services: `/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -kill` + +**Linux:** +- Check desktop file: `~/.local/share/applications/myapp.desktop` +- Update database: `update-desktop-database ~/.local/share/applications/` +- Verify handler: `xdg-mime query default x-scheme-handler/myapp` + +### Application Not Launching + +**Check logs:** +```go +app := application.New(application.Options{ + Logger: application.NewLogger(application.LogLevelDebug), + // ... +}) +``` + +**Common issues:** +- Application not installed in expected location +- Executable path in registration doesn't match actual location +- Permissions issues + +## Best Practices + +### ✅ Do + +- **Use descriptive scheme names** - `mycompany-myapp` instead of `mca` +- **Validate all input** - Never trust URLs from external sources +- **Handle errors gracefully** - Log invalid URLs, don't crash +- **Provide user feedback** - Show what action was triggered +- **Test on all platforms** - Protocol handling varies +- **Document your URL structure** - Help users and integrators + +### ❌ Don't + +- **Don't use common scheme names** - Avoid `http`, `file`, `app`, etc. +- **Don't execute URLs as code** - Huge security risk +- **Don't expose sensitive operations** - Require confirmation for destructive actions +- **Don't assume protocols work everywhere** - Have fallback mechanisms +- **Don't forget URL encoding** - Handle special characters properly + +## Next Steps + +- [Windows Packaging](/guides/build/windows) - Learn about NSIS installer options +- [File Associations](/guides/distribution/file-associations) - Open files with your app +- [Single Instance](/guides/distribution/single-instance) - Prevent multiple app instances + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples). diff --git a/docs/src/content/docs/guides/e2e-testing.mdx b/docs/src/content/docs/guides/e2e-testing.mdx new file mode 100644 index 000000000..f461afa2e --- /dev/null +++ b/docs/src/content/docs/guides/e2e-testing.mdx @@ -0,0 +1,173 @@ +--- +title: End-to-End Testing +description: Test complete user workflows +sidebar: + order: 6 +--- + +## Overview + +End-to-end testing validates complete user workflows in your application. + +## Using Playwright + +### Setup + +```bash +# Install Playwright +npm install -D @playwright/test + +# Initialize +npx playwright install +``` + +### Configuration + +```javascript +// playwright.config.js +import { defineConfig } from '@playwright/test' + +export default defineConfig({ + testDir: './e2e', + use: { + baseURL: 'http://localhost:34115', // Wails dev server + }, +}) +``` + +### Writing Tests + +```javascript +// e2e/app.spec.js +import { test, expect } from '@playwright/test' + +test('create note', async ({ page }) => { + await page.goto('/') + + // Click new note button + await page.click('#new-note-btn') + + // Fill in title + await page.fill('#note-title', 'Test Note') + + // Fill in content + await page.fill('#note-content', 'Test content') + + // Verify note appears in list + await expect(page.locator('.note-item')).toContainText('Test Note') +}) + +test('delete note', async ({ page }) => { + await page.goto('/') + + // Create a note first + await page.click('#new-note-btn') + await page.fill('#note-title', 'To Delete') + + // Delete it + await page.click('#delete-btn') + + // Confirm dialog + page.on('dialog', dialog => dialog.accept()) + + // Verify it's gone + await expect(page.locator('.note-item')).not.toContainText('To Delete') +}) +``` + +## Testing dialogs + +```javascript +test('file save dialog', async ({ page }) => { + await page.goto('/') + + // Intercept file dialog + page.on('filechooser', async (fileChooser) => { + await fileChooser.setFiles('/path/to/test/file.json') + }) + + // Trigger save + await page.click('#save-btn') + + // Verify success message + await expect(page.locator('.success-message')).toBeVisible() +}) +``` + +## Testing Window Behaviour + +```javascript +test('window state', async ({ page }) => { + await page.goto('/') + + // Test window title + await expect(page).toHaveTitle('My App') + + // Test window size + const size = await page.viewportSize() + expect(size.width).toBe(800) + expect(size.height).toBe(600) +}) +``` + +## Best Practices + +### ✅ Do + +- Test critical user flows +- Use data-testid attributes +- Clean up test data +- Run tests in CI/CD +- Test error scenarios +- Keep tests independent + +### ❌ Don't + +- Don't test implementation details +- Don't write brittle selectors +- Don't skip cleanup +- Don't ignore flaky tests +- Don't test everything + +## Running E2E Tests + +```bash +# Run all tests +npx playwright test + +# Run in headed mode +npx playwright test --headed + +# Run specific test +npx playwright test e2e/app.spec.js + +# Debug mode +npx playwright test --debug +``` + +## CI/CD Integration + +```yaml +# .github/workflows/e2e.yml +name: E2E Tests + +on: [push] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + - name: Install dependencies + run: npm ci + - name: Install Playwright + run: npx playwright install --with-deps + - name: Run tests + run: npx playwright test +``` + +## Next Steps + +- [Testing](/guides/testing) - Learn unit testing +- [Building](/guides/building) - Build your application diff --git a/docs/src/content/docs/guides/events-reference.mdx b/docs/src/content/docs/guides/events-reference.mdx new file mode 100644 index 000000000..afa61fc3e --- /dev/null +++ b/docs/src/content/docs/guides/events-reference.mdx @@ -0,0 +1,569 @@ +--- +title: Events Guide +description: A practical guide to using events in Wails v3 for application communication and lifecycle management +--- + +**NOTE: This guide is a work in progress** + +# Events Guide + +Events are the heartbeat of communication in Wails applications. They allow different parts of your application to talk to each other without being tightly coupled. This guide will walk you through everything you need to know about using events effectively in your Wails application. + +## Understanding Wails Events + +Think of events as messages that get broadcast throughout your application. Any part of your application can listen for these messages and react accordingly. This is particularly useful for: + +- **Responding to window changes**: Know when your window is minimized, maximized, or moved +- **Handling system events**: React to theme changes or power events +- **Custom application logic**: Create your own events for features like data updates or user actions +- **Cross-component communication**: Let different parts of your app communicate without direct dependencies + +## Event Naming Convention + +All Wails events follow a namespace pattern to clearly indicate their origin: + +- `common:` - Cross-platform events that work on Windows, macOS, and Linux +- `windows:` - Windows-specific events +- `mac:` - macOS-specific events +- `linux:` - Linux-specific events + +For example: +- `common:WindowFocus` - Window gained focus (works everywhere) +- `windows:APMSuspend` - System is suspending (Windows only) +- `mac:ApplicationDidBecomeActive` - App became active (macOS only) + +## Getting Started with Events + +### Listening to Events (Frontend) + +The most common use case is listening for events in your frontend code: + +```javascript +import { Events } from '@wailsio/runtime'; + +// Listen for when the window gains focus +Events.On('common:WindowFocus', () => { + console.log('Window is now focused!'); + // Maybe refresh some data or resume animations +}); + +// Listen for theme changes +Events.On('common:ThemeChanged', (event) => { + console.log('Theme changed:', event.data); + // Update your app's theme accordingly +}); + +// Listen for custom events from your Go backend +Events.On('my-app:data-updated', (event) => { + console.log('Data updated:', event.data); + // Update your UI with the new data +}); +``` + +### Emitting Events (Backend) + +From your Go code, you can emit events that your frontend can listen to: + +```go +package main + +import ( + "github.com/wailsapp/wails/v3/pkg/application" + "time" +) + +func (s *Service) UpdateData() { + // Do some data processing... + + // Notify the frontend + app := application.Get() + app.Event.Emit("my-app:data-updated", + map[string]interface{}{ + "timestamp": time.Now(), + "count": 42, + }, + ) +} +``` + +### Emitting Events (Frontend) + +While not as commonly used, you can also emit events from your frontend that your Go code can listen to: + +```javascript +import { Events } from '@wailsio/runtime'; + +// Event without data +Events.Emit('myapp:close-window') + +// Event with data +Events.Emit('myapp:disconnect-requested', 'id-123') +``` + +If you are using TypeScript in your frontend and [registering typed events](#typed-events-with-typecheck) in your Go code, you will get event name autocomplete/checking and data type checking. + +### Removing Event Listeners + +Always clean up your event listeners when they're no longer needed: + +```javascript +import { Events } from '@wailsio/runtime'; + +// Store the handler reference +const focusHandler = () => { + console.log('Window focused'); +}; + +// Add the listener +Events.On('common:WindowFocus', focusHandler); + +// Later, remove it when no longer needed +Events.Off('common:WindowFocus', focusHandler); + +// Or remove all listeners for an event +Events.Off('common:WindowFocus'); +``` + +## Common Use Cases + +### 1. Pause/Resume on Window Focus + +Many applications need to pause certain activities when the window loses focus: + +```javascript +import { Events } from '@wailsio/runtime'; + +let animationRunning = true; + +Events.On('common:WindowLostFocus', () => { + animationRunning = false; + pauseBackgroundTasks(); +}); + +Events.On('common:WindowFocus', () => { + animationRunning = true; + resumeBackgroundTasks(); +}); +``` + +### 2. Responding to Theme Changes + +Keep your app in sync with the system theme: + +```javascript +import { Events } from '@wailsio/runtime'; + +Events.On('common:ThemeChanged', (event) => { + const isDarkMode = event.data.isDark; + + if (isDarkMode) { + document.body.classList.add('dark-theme'); + document.body.classList.remove('light-theme'); + } else { + document.body.classList.add('light-theme'); + document.body.classList.remove('dark-theme'); + } +}); +``` + +### 3. Handling File Drops + +Make your app accept dragged files: + +```javascript +import { Events } from '@wailsio/runtime'; + +Events.On('common:WindowFilesDropped', (event) => { + const files = event.data.files; + + files.forEach(file => { + console.log('File dropped:', file); + // Process the dropped files + handleFileUpload(file); + }); +}); +``` + +### 4. Window Lifecycle Management + +Respond to window state changes: + +```javascript +import { Events } from '@wailsio/runtime'; + +Events.On('common:WindowClosing', () => { + // Save user data before closing + saveApplicationState(); + + // You could also prevent closing by returning false + // from a registered window close handler +}); + +Events.On('common:WindowMaximise', () => { + // Adjust UI for maximized view + adjustLayoutForMaximized(); +}); + +Events.On('common:WindowRestore', () => { + // Return UI to normal state + adjustLayoutForNormal(); +}); +``` + +### 5. Platform-Specific Features + +Handle platform-specific events when needed: + +```javascript +import { Events } from '@wailsio/runtime'; + +// Windows-specific power management +Events.On('windows:APMSuspend', () => { + console.log('System is going to sleep'); + saveState(); +}); + +Events.On('windows:APMResumeSuspend', () => { + console.log('System woke up'); + refreshData(); +}); + +// macOS-specific app lifecycle +Events.On('mac:ApplicationWillTerminate', () => { + console.log('App is about to quit'); + performCleanup(); +}); +``` + +## Creating Custom Events + +You can create your own events for application-specific needs. + +### Backend (Go) + +```go +// Emit a custom event when data changes + +func (s *Service) ProcessUserData(userData UserData) error { + // Process the data... + + app := application.Get() + // Notify all listeners + app.Event.Emit("user:data-processed", + map[string]interface{}{ + "userId": userData.ID, + "status": "completed", + "timestamp": time.Now(), + }, + ) + return nil +} + +// Emit periodic updates +func (s *Service) StartMonitoring() { + app := application.Get() + ticker := time.NewTicker(5 * time.Second) + go func() { + for range ticker.C { + stats := s.collectStats() + app.Event.Emit("monitor:stats-updated", stats) + } + }() +} +``` + +### Frontend (JavaScript) + +```javascript +import { Events } from '@wailsio/runtime'; + +// Listen for your custom events +Events.On('user:data-processed', (event) => { + const { userId, status, timestamp } = event.data; + + showNotification(`User ${userId} processing ${status}`); + updateUIWithNewData(); +}); + +Events.On('monitor:stats-updated', (event) => { + updateDashboard(event.data); +}); +``` + +## Typed Events with Type Safety + +Wails v3 supports typed events with full TypeScript type safety through event registration and automatic binding generation. + +### Registering Custom Events + +Call `application.RegisterEvent` at init time to register custom event names with their data types: + +```go +package main + +import "github.com/wailsapp/wails/v3/pkg/application" + +type UserLoginData struct { + UserID string + Username string + LoginTime string +} + +type MonitorStats struct { + CPUUsage float64 + MemoryUsage float64 +} + +func init() { + // Register events with their data types + application.RegisterEvent[UserLoginData]("user:login") + application.RegisterEvent[MonitorStats]("monitor:stats") + + // Register events without data (void events) + application.RegisterEvent[application.Void]("app:ready") +} +``` + +:::caution +`RegisterEvent` is meant to be called at init time and will panic if: +- Arguments are not valid +- The same event name is registered twice with different data types +::: + +:::note +It is safe to register the same event multiple times as long as the data type is always the same. This can be useful to ensure an event is registered when any of multiple packages is loaded. +::: + +### Benefits of Event Registration + +Once registered, data arguments passed to `Event.Emit` are type-checked against the specified type. On mismatch: +- An error is emitted and logged (or passed to the registered error handler) +- The offending event will not be propagated +- This ensures the data field of registered events is always assignable to the declared type + +### Strict Mode + +Use the `strictevents` build tag to enable warnings for unregistered events in development: + +```bash +go build -tags strictevents +``` + +With strict mode enabled, the runtime emits at most one warning per unregistered event name to avoid spamming logs. + +### TypeScript Binding Generation + +The binding generator outputs TypeScript definitions and glue code for transparent typed event support in the frontend. + +#### 1. Set up the Vite Plugin + +In your `vite.config.ts`: + +```typescript +import { defineConfig } from 'vite' +import wails from '@wailsio/runtime/plugins/vite' + +export default defineConfig({ + plugins: [wails()], +}) +``` + +#### 2. Generate Bindings + +Run the binding generator: + +```bash +wails3 generate bindings +``` + +This creates TypeScript files in your frontend directory with typed event creators and data interfaces. + +#### 3. Use Typed Events in Frontend + +```typescript +import { Events } from '@wailsio/runtime' +import { UserLogin, MonitorStats } from './bindings/events' + +// Type-safe event emission with autocomplete +Events.Emit(UserLogin({ + UserID: "123", + Username: "john_doe", + LoginTime: new Date().toISOString() +})) + +// Type-safe event listening +Events.On(UserLogin, (event) => { + // event.data is typed as UserLoginData + console.log(`User ${event.data.Username} logged in`) +}) + +Events.On(MonitorStats, (event) => { + // event.data is typed as MonitorStats + updateDashboard({ + cpu: event.data.CPUUsage, + memory: event.data.MemoryUsage + }) +}) +``` + +The typed events provide: +- **Autocomplete** for event names +- **Type checking** for event data +- **Compile-time errors** for mismatched data types +- **IntelliSense** documentation + +## Event Reference + +### Common Events (Cross-platform) + +These events work on all platforms: + +| Event | Description | When to Use | +|-------|-------------|-------------| +| `common:ApplicationStarted` | Application has fully started | Initialize your app, load saved state | +| `common:WindowRuntimeReady` | Wails runtime is ready | Start making Wails API calls | +| `common:ThemeChanged` | System theme changed | Update app appearance | +| `common:WindowFocus` | Window gained focus | Resume activities, refresh data | +| `common:WindowLostFocus` | Window lost focus | Pause activities, save state | +| `common:WindowMinimise` | Window was minimized | Pause rendering, reduce resource usage | +| `common:WindowMaximise` | Window was maximized | Adjust layout for full screen | +| `common:WindowRestore` | Window restored from min/max | Return to normal layout | +| `common:WindowClosing` | Window is about to close | Save data, cleanup resources | +| `common:WindowFilesDropped` | Files dropped on window | Handle file imports | +| `common:WindowDidResize` | Window was resized | Adjust layout, rerender charts | +| `common:WindowDidMove` | Window was moved | Update position-dependent features | + +### Platform-Specific Events + +#### Windows Events + +Key events for Windows applications: + +| Event | Description | Use Case | +|-------|-------------|----------| +| `windows:SystemThemeChanged` | Windows theme changed | Update app colors | +| `windows:APMSuspend` | System suspending | Save state, pause operations | +| `windows:APMResumeSuspend` | System resumed | Restore state, refresh data | +| `windows:APMPowerStatusChange` | Power status changed | Adjust performance settings | + +#### macOS Events + +Important macOS application events: + +| Event | Description | Use Case | +|-------|-------------|----------| +| `mac:ApplicationDidBecomeActive` | App became active | Resume operations | +| `mac:ApplicationDidResignActive` | App became inactive | Pause operations | +| `mac:ApplicationWillTerminate` | App will quit | Final cleanup | +| `mac:WindowDidEnterFullScreen` | Entered fullscreen | Adjust UI for fullscreen | +| `mac:WindowDidExitFullScreen` | Exited fullscreen | Restore normal UI | + +#### Linux Events + +Core Linux window events: + +| Event | Description | Use Case | +|-------|-------------|----------| +| `linux:SystemThemeChanged` | Desktop theme changed | Update app theme | +| `linux:WindowFocusIn` | Window gained focus | Resume activities | +| `linux:WindowFocusOut` | Window lost focus | Pause activities | +| `linux:WindowLoadStarted` | WebView started loading | Show loading indicator | +| `linux:WindowLoadRedirected` | WebView redirected | Track navigation redirects | +| `linux:WindowLoadCommitted` | WebView committed load | Content is being received | +| `linux:WindowLoadFinished` | WebView finished loading | Hide loading indicator, inject JS/CSS | + +## Best Practices + +### 1. Use Event Namespaces + +When creating custom events, use namespaces to avoid conflicts: + +```javascript +import { Events } from '@wailsio/runtime'; + +// Good - namespaced events +Events.Emit('myapp:user:login'); +Events.Emit('myapp:data:updated'); +Events.Emit('myapp:network:connected'); + +// Avoid - generic names that might conflict +Events.Emit('login'); +Events.Emit('update'); +``` + +### 2. Clean Up Listeners + +Always remove event listeners when components unmount: + +```javascript +import { Events } from '@wailsio/runtime'; + +// React example +useEffect(() => { + const handler = (event) => { + // Handle event + }; + + Events.On('common:WindowResize', handler); + + // Cleanup + return () => { + Events.Off('common:WindowResize', handler); + }; +}, []); +``` + +### 3. Handle Platform Differences + +Check platform availability when using platform-specific events: + +```javascript +import { Platform, Events } from '@wailsio/runtime'; + +if (Platform.isWindows) { + Events.On('windows:APMSuspend', handleSuspend); +} else if (Platform.isMac) { + Events.On('mac:ApplicationWillTerminate', handleTerminate); +} +``` + +### 4. Don't Overuse Events + +While events are powerful, don't use them for everything: + +- ✅ Use events for: System notifications, lifecycle changes, broadcast updates +- ❌ Avoid events for: Direct function returns, single component updates, synchronous operations + +## Debugging Events + +To debug event issues: + +```javascript +import { Events } from '@wailsio/runtime'; + +// Log all events (development only) +if (isDevelopment) { + const originalOn = Events.On; + Events.On = function(eventName, handler) { + console.log(`[Event Registered] ${eventName}`); + return originalOn.call(this, eventName, function(event) { + console.log(`[Event Fired] ${eventName}`, event); + return handler(event); + }); + }; +} +``` + +## Source of Truth + +The complete list of available events can be found in the Wails source code: +- Frontend events: [`v3/internal/runtime/desktop/@wailsio/runtime/src/event_types.ts`](https://github.com/wailsapp/wails/blob/main/v3/internal/runtime/desktop/@wailsio/runtime/src/event_types.ts) +- Backend events: [`v3/pkg/events/events.go`](https://github.com/wailsapp/wails/blob/main/v3/pkg/events/events.go) + +Always refer to these files for the most up-to-date event names and availability. + +## Summary + +Events in Wails provide a powerful, decoupled way to handle communication in your application. By following the patterns and practices in this guide, you can build responsive, platform-aware applications that react smoothly to system changes and user interactions. + +Remember: start with common events for cross-platform compatibility, add platform-specific events when needed, and always clean up your event listeners to prevent memory leaks. \ No newline at end of file diff --git a/docs/src/content/docs/guides/file-associations.mdx b/docs/src/content/docs/guides/file-associations.mdx new file mode 100644 index 000000000..ba37d9f35 --- /dev/null +++ b/docs/src/content/docs/guides/file-associations.mdx @@ -0,0 +1,178 @@ +--- +title: File Associations +sidebar: + order: 20 +--- + +import { Steps } from "@astrojs/starlight/components"; +import {Badge} from '@astrojs/starlight/components'; + +Relevant Platforms: +
      + +File associations allow your application to handle specific file types when +users open them. This is particularly useful for text editors, image viewers, or +any application that works with specific file formats. This guide explains how +to implement file associations in your Wails v3 application. + +## Overview + +File association support in Wails v3 is currently available for: + +- Windows (NSIS installer packages) +- macOS (application bundles) + +## Configuration + +File associations are configured in the `config.yml` file located in your +project's `build` directory. + +### Basic Configuration + +To set up file associations: + +1. Open `build/config.yml` +2. Add your file associations under the `fileAssociations` section +3. Run `wails3 update build-assets` to update the build assets +4. Set the `FileAssociations` field in the application options +5. Package your application using `wails3 package` + +Here's an example configuration: + +```yaml +fileAssociations: + - ext: myapp + name: MyApp Document + description: MyApp Document File + iconName: myappFileIcon + role: Editor + - ext: custom + name: Custom Format + description: Custom File Format + iconName: customFileIcon + role: Editor +``` + +### Configuration Properties + +| Property | Description | Platform | +|-------------|------------------------------------------------------------------|----------| +| ext | File extension without the leading period (e.g., `txt`) | All | +| name | Display name for the file type | All | +| description | Description shown in file properties | Windows | +| iconName | Name of the icon file (without extension) in the build folder | All | +| role | Application's role for this file type (e.g., `Editor`, `Viewer`) | macOS | +| mimeType | MIME type for the file (e.g., `image/jpeg`) | macOS | + +## Listening for File Open Events + +To handle file open events in your application, you can listen for the +`events.Common.ApplicationOpenedWithFile` event: + +```go title="main.go" +package main + +import ( + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/events" +) + +func main() { + app := application.New(application.Options{ + Name: "MyApp", + FileAssociations: []string{".txt", ".md"}, // Specify supported extensions + }) + + // Listen for files being used to open the application + app.Event.OnApplicationEvent(events.Common.ApplicationOpenedWithFile, func(event *application.ApplicationEvent) { + associatedFile := event.Context().Filename() + application.InfoDialog().SetMessage("Application opened with file: " + associatedFile).Show() + }) + + // Create your window and run the app... +} + +``` + +## Step-by-Step Tutorial + +Let's walk through setting up file associations for a simple text editor: + + + +1. ### Create Icons + + - Create icons for your file type (recommended sizes: 16x16, 32x32, 48x48, + 256x256) + - Save the icons in your project's `build` folder + - Name them according to your `iconName` configuration (e.g., + `textFileIcon.png`) + + :::tip + You can use `wails3 generate icons` to generate the required icons for you. + Run `wails3 generate icons --help` for more information. + ::: + + - For macOS add copy statement like `cp build/darwin/documenticon.icns {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources` in the `create:app:bundle:` task. + +2. ### Configure File Associations + + Edit the `build/config.yml` file to add your file associations: + + ```yaml + # build/config.yml + fileAssociations: + - ext: txt + name: Text Document + description: Plain Text Document + iconName: textFileIcon + role: Editor + ``` + +3. ### Update Build Assets + + Run the following command to update the build assets: + + ```bash + wails3 update build-assets + ``` + +4. ### Set File Associations in the Application Options + + In your `main.go` file, set the `FileAssociations` field in the application + options: + + ```go + app := application.New(application.Options{ + Name: "MyApp", + FileAssociations: []string{".txt", ".md"}, // Specify supported extensions + }) + ``` + + :::tip[Why are file extensions required in both the application config and config.yml?] + + On Windows, when a file is opened with a file association, the application is + launched with the filename as the first argument to the application. The + application has no way of knowing if the first argument is a file or a command + line argument, so it uses the `FileAssociations` field in the application + options to determine if the first argument is an associated file or not. + + ::: + +5. ### Package Your Application + + Package your application using the following command: + + ```bash + wails3 package + ``` + + The packaged application will be created in the `bin` directory. You can then + install and test the application. + + ## Additional Notes + + - Icons should be provided in PNG format in the build folder + - Testing file associations requires installing the packaged application + + \ No newline at end of file diff --git a/docs/src/content/docs/guides/gin-routing.mdx b/docs/src/content/docs/guides/gin-routing.mdx new file mode 100644 index 000000000..fc3ce289a --- /dev/null +++ b/docs/src/content/docs/guides/gin-routing.mdx @@ -0,0 +1,263 @@ +--- +title: Using Gin for Routing +description: A comprehensive guide to integrating Gin web framework with Wails v3 applications +--- + +This guide demonstrates how to integrate the [Gin web framework](https://github.com/gin-gonic/gin) with Wails v3. Gin is a high-performance HTTP web framework written in Go that makes it easy to build web applications and APIs. + +## Introduction + +Wails v3 provides a flexible asset system that allows you to use any HTTP handler, including popular web frameworks like Gin. This integration enables you to: + +- Serve web content using Gin's routing and middleware +- Create RESTful APIs accessible from your Wails application +- Use Gin's features while maintaining Wails desktop integration + +## Setting Up Gin with Wails + +To integrate Gin with Wails, you need to create a Gin router and configure it as the asset handler in your Wails application. Here's a step-by-step guide: + +### 1. Install Dependencies + +First, ensure you have the Gin package installed: + +```bash +go get -u github.com/gin-gonic/gin +``` + +### 2. Create a Middleware for Gin + +Create a middleware function that will handle the integration between Wails and Gin: + +```go +// GinMiddleware creates a middleware that passes requests to Gin if they're not handled by Wails +func GinMiddleware(ginEngine *gin.Engine) application.Middleware { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Let Wails handle the `/wails` route + if strings.HasPrefix(r.URL.Path, "/wails") { + next.ServeHTTP(w, r) + return + } + // Let Gin handle everything else + ginEngine.ServeHTTP(w, r) + }) + } +} +``` + +This middleware passes all HTTP requests to the Gin router. + +### 3. Configure Your Gin Router + +Set up your Gin router with routes, middlewares, and handlers: + +```go +// Create a new Gin router +ginEngine := gin.New() // Using New() instead of Default() to add custom middleware + +// Add middlewares +ginEngine.Use(gin.Recovery()) +ginEngine.Use(LoggingMiddleware()) // Your custom middleware + +// Define routes +ginEngine.GET("/", func(c *gin.Context) { + // Serve your main page +}) + +ginEngine.GET("/api/hello", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "message": "Hello from Gin API!", + "time": time.Now().Format(time.RFC3339), + }) +}) +``` + +### 4. Integrate with Wails Application + +Configure your Wails application to use the Gin router as its asset handler: + +```go +// Create a new Wails application +app := application.New(application.Options{ + Name: "Gin Example", + Description: "A demo of using Gin with Wails", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + Assets: application.AssetOptions{ + Handler: ginEngine, + Middleware: GinMiddleware(ginEngine), + }, +}) + +// Create window +app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Wails + Gin Example", + Width: 900, + Height: 700, + URL: "/", // This will load the route handled by Gin +}) +``` + +## Serving Static Content + +Use Go's `embed` package to embed static files into your binary: + +```go +//go:embed static +var staticFiles embed.FS + +ginEngine.StaticFS("/static", http.FS(staticFiles)) +ginEngine.GET("/", func(c *gin.Context) { + file, _ := staticFiles.ReadFile("static/index.html") + c.Data(http.StatusOK, "text/html; charset=utf-8", file) +}) +``` + +For development, serve files directly from disk using `ginEngine.Static("/static", "./static")`. + +## Custom Middleware + +Gin allows you to create custom middleware for various purposes. Here's an example of a logging middleware: + +```go +// LoggingMiddleware is a Gin middleware that logs request details +func LoggingMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + // Start timer + startTime := time.Now() + + // Process request + c.Next() + + // Calculate latency + latency := time.Since(startTime) + + // Log request details + log.Printf("[GIN] %s | %s | %s | %d | %s", + c.Request.Method, + c.Request.URL.Path, + c.ClientIP(), + c.Writer.Status(), + latency, + ) + } +} +``` + +## Handling API Requests + +Gin makes it easy to create RESTful APIs. Here's how to define API endpoints: + +```go +// GET endpoint +ginEngine.GET("/api/users", func(c *gin.Context) { + c.JSON(http.StatusOK, users) +}) + +// POST endpoint with JSON binding +ginEngine.POST("/api/users", func(c *gin.Context) { + var newUser User + if err := c.ShouldBindJSON(&newUser); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + // Process the new user... + c.JSON(http.StatusCreated, newUser) +}) + +// Path parameters +ginEngine.GET("/api/users/:id", func(c *gin.Context) { + id := c.Param("id") + // Find user by ID... + c.JSON(http.StatusOK, user) +}) + +// Query parameters +ginEngine.GET("/api/search", func(c *gin.Context) { + query := c.DefaultQuery("q", "") + limit := c.DefaultQuery("limit", "10") + // Perform search... + c.JSON(http.StatusOK, results) +}) +``` + +## Using Wails Features + +Your Gin-served web content can interact with Wails features using the `@wailsio/runtime` package. + +### Event Handling + +Register event handlers in Go: + +```go +app.Event.On("my-event", func(event *application.CustomEvent) { + log.Printf("Received event: %v", event.Data) +}) +``` + +Call from JavaScript using the runtime: + +```html + +``` + +## Advanced Configuration + +### Customising Gin's Mode + +Set Gin to release mode for production: + +```go +gin.SetMode(gin.ReleaseMode) // Use gin.DebugMode for development +ginEngine := gin.New() +``` + +### Handling WebSockets + +You can integrate WebSockets with Gin using libraries like Gorilla WebSocket: + +```go +import "github.com/gorilla/websocket" + +var upgrader = websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, + CheckOrigin: func(r *http.Request) bool { + return true // Allow all connections + }, +} + +// In your route handler: +ginEngine.GET("/ws", func(c *gin.Context) { + conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) + if err != nil { + log.Println(err) + return + } + defer conn.Close() + + // Handle WebSocket connection... +}) +``` + +## Best Practices + +- **Use Go's embed Package:** Embed static files into your binary for better distribution. +- **Separate Concerns:** Keep your API logic separate from your UI logic. +- **Error Handling:** Implement proper error handling in both Gin routes and frontend code. +- **Security:** Be mindful of security considerations, especially when handling user input. +- **Performance:** Use Gin's release mode in production for better performance. +- **Testing:** Write tests for your Gin routes using Gin's testing utilities. + +## Conclusion + +Integrating Gin with Wails provides a powerful combination for building desktop applications with web technologies. Gin's performance and feature set complement Wails' desktop integration capabilities, allowing you to create sophisticated applications that use the best of both worlds. + +For more information, refer to the [Gin documentation](https://github.com/gin-gonic/gin) and the [Wails documentation](https://wails.io). diff --git a/docs/src/content/docs/guides/gin-services.mdx b/docs/src/content/docs/guides/gin-services.mdx new file mode 100644 index 000000000..123f876eb --- /dev/null +++ b/docs/src/content/docs/guides/gin-services.mdx @@ -0,0 +1,556 @@ +--- +title: Using Gin for Services +description: A guide to integrating the Gin web framework with Wails v3 Services +--- + +# Using Gin for Services + +The Gin web framework is a popular choice for building HTTP services in Go. With Wails v3, you can easily integrate Gin-based services into your application, providing a powerful way to handle HTTP requests, implement RESTful APIs, and serve web content. + +This guide will walk you through creating a Gin-based service that can be mounted at a specific route in your Wails application. We'll build a complete example that demonstrates how to: + +1. Create a Gin-based service +2. Implement the Wails Service interface +3. Set up routes and middleware +4. Integrate with the Wails event system +5. Interact with the service from the frontend + +## Prerequisites + +Before you begin, make sure you have: + +- Wails v3 installed +- Basic knowledge of Go and the Gin framework +- Familiarity with HTTP concepts and RESTful APIs + +You'll need to add the Gin framework to your project: + +```bash +go get github.com/gin-gonic/gin +``` + +## Creating a Gin-Based Service + +Let's start by creating a Gin service that implements the Wails Service interface. Our service will manage a collection of users and provide API endpoints for retrieving and creating user records. + +### 1. Define Your Data Models + +First, define the data structures your service will work with: + +```go +package services + +import ( + "context" + "net/http" + "strconv" + "sync" + "time" + + "github.com/gin-gonic/gin" + "github.com/wailsapp/wails/v3/pkg/application" +) + +// User represents a user in the system +type User struct { + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + CreatedAt time.Time `json:"createdAt"` +} + +// EventData represents data sent in events +type EventData struct { + Message string `json:"message"` + Timestamp string `json:"timestamp"` +} +``` + +### 2. Create Your Service Structure + +Next, define the service structure that will hold your Gin router and any state your service needs to maintain: + +```go +// GinService implements a Wails service that uses Gin for HTTP handling +type GinService struct { + ginEngine *gin.Engine + users []User + nextID int + mu sync.RWMutex + app *application.App +} + +// NewGinService creates a new GinService instance +func NewGinService() *GinService { + // Create a new Gin router + ginEngine := gin.New() + + // Add middlewares + ginEngine.Use(gin.Recovery()) + ginEngine.Use(LoggingMiddleware()) + + service := &GinService{ + ginEngine: ginEngine, + users: []User{ + {ID: 1, Name: "Alice", Email: "alice@example.com", CreatedAt: time.Now().Add(-72 * time.Hour)}, + {ID: 2, Name: "Bob", Email: "bob@example.com", CreatedAt: time.Now().Add(-48 * time.Hour)}, + {ID: 3, Name: "Charlie", Email: "charlie@example.com", CreatedAt: time.Now().Add(-24 * time.Hour)}, + }, + nextID: 4, + } + + // Define routes + service.setupRoutes() + + return service +} +``` + +### 3. Implement the Service Interface + +Implement the required methods for the Wails Service interface: + +```go +// ServiceName returns the name of the service +func (s *GinService) ServiceName() string { + return "Gin API Service" +} + +// ServiceStartup is called when the service starts +func (s *GinService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + // Store the application instance for later use + s.app = application.Get() + + // Register an event handler that can be triggered from the frontend + s.app.Event.On("gin-api-event", func(event *application.CustomEvent) { + // Log the event data + s.app.Logger.Info("Received event from frontend", "data", event.Data) + + // Emit an event back to the frontend + s.app.Event.Emit("gin-api-response", + map[string]interface{}{ + "message": "Response from Gin API Service", + "time": time.Now().Format(time.RFC3339), + }, + ) + }) + + return nil +} + +// ServiceShutdown is called when the service shuts down +func (s *GinService) ServiceShutdown(ctx context.Context) error { + // Clean up resources if needed + return nil +} +``` + +### 3. Implement the http.Handler Interface + +To make your service mountable at a specific route, implement the `http.Handler` interface. This single method, `ServeHTTP`, is the gateway for all HTTP requests to your service. It delegates the request handling to the Gin router, allowing you to use all of Gin's powerful features for routing and middleware. + +```go +// ServeHTTP implements the http.Handler interface +func (s *GinService) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // All requests go to the Gin router + s.ginEngine.ServeHTTP(w, r) +} +``` + +### 4. Set Up Your Routes + +Define your API routes in a separate method for better organisation. This approach keeps your code clean and makes it easier to understand the structure of your API. The Gin router provides a fluent API for defining routes, including support for route groups, which help organise related endpoints. + +```go +// setupRoutes configures the API routes +func (s *GinService) setupRoutes() { + // Basic info endpoint + s.ginEngine.GET("/info", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "service": "Gin API Service", + "version": "1.0.0", + "time": time.Now().Format(time.RFC3339), + }) + }) + + // Users group + users := s.ginEngine.Group("/users") + { + // Get all users + users.GET("", func(c *gin.Context) { + s.mu.RLock() + defer s.mu.RUnlock() + c.JSON(http.StatusOK, s.users) + }) + + // Get user by ID + users.GET("/:id", func(c *gin.Context) { + id, err := strconv.Atoi(c.Param("id")) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"}) + return + } + + s.mu.RLock() + defer s.mu.RUnlock() + + for _, user := range s.users { + if user.ID == id { + c.JSON(http.StatusOK, user) + return + } + } + + c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) + }) + + // Create a new user + users.POST("", func(c *gin.Context) { + var newUser User + if err := c.ShouldBindJSON(&newUser); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + s.mu.Lock() + defer s.mu.Unlock() + + // Set the ID and creation time + newUser.ID = s.nextID + newUser.CreatedAt = time.Now() + s.nextID++ + + // Add to the users slice + s.users = append(s.users, newUser) + + c.JSON(http.StatusCreated, newUser) + + // Emit an event to notify about the new user + s.app.Event.Emit("user-created", newUser) + }) + + // Delete a user + users.DELETE("/:id", func(c *gin.Context) { + id, err := strconv.Atoi(c.Param("id")) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"}) + return + } + + s.mu.Lock() + defer s.mu.Unlock() + + for i, user := range s.users { + if user.ID == id { + // Remove the user from the slice + s.users = append(s.users[:i], s.users[i+1:]...) + c.JSON(http.StatusOK, gin.H{"message": "User deleted"}) + return + } + } + + c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) + }) + } +} +``` + +### 5. Create Custom Middleware + +You can create custom Gin middleware to enhance your service. Middleware functions in Gin are executed in the order they are added to the router and can perform tasks such as logging, authentication, and error handling. This example shows a simple logging middleware that records request details. + +```go +// LoggingMiddleware is a Gin middleware that logs request details +func LoggingMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + // Start timer + start := time.Now() + + // Process request + c.Next() + + // Calculate latency + latency := time.Since(start) + + // Log request details + log.Printf("[GIN] %s %s %d %s", c.Request.Method, c.Request.URL.Path, c.Writer.Status(), latency) + } +} +``` + +## Registering Your Service + +To use your Gin-based service in a Wails application, you need to register it with the application and specify the route where it should be mounted. This is done when creating the Wails application instance. The route you specify becomes the base path for all endpoints defined in your Gin router. + +```go +app := application.New(application.Options{ + Name: "Gin Service Demo", + Description: "A demo of using Gin in Wails services", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + LogLevel: slog.LevelDebug, + Services: []application.Service{ + application.NewServiceWithOptions(services.NewGinService(), application.ServiceOptions{ + Route: "/api", + }), + }, + Assets: application.AssetOptions{ + Handler: application.BundledAssetFileServer(assets), + }, +}) +``` + +In this example, the Gin service is mounted at the `/api` route. This means that if your Gin router has an endpoint defined as `/info`, it will be accessible at `/api/info` in your application. This approach allows you to organise your API endpoints logically and avoid conflicts with other parts of your application. + +## Integrating with the Wails Event System + +One of the powerful features of using Gin with Wails Services is the ability to seamlessly integrate with the Wails event system. This allows for real-time communication between your backend service and the frontend. + +In your service's `ServiceStartup` method, you can register event handlers to process events from the frontend: + +```go +s.app.Event.On("gin-api-event", func(event *application.CustomEvent) { + // Log the event data + s.app.Logger.Info("Received event from frontend", "data", event.Data) + + // Emit an event back to the frontend + s.app.Event.Emit("gin-api-response", + map[string]interface{}{ + "message": "Response from Gin API Service", + "time": time.Now().Format(time.RFC3339), + }, + ) +}) +``` + +You can also emit events to the frontend from your Gin routes or other parts of your service: + +```go +// After creating a new user +s.app.Event.Emit("user-created", newUser) +``` + +## Frontend Integration + +To interact with your Gin service from the frontend, you'll need to import the Wails runtime, make HTTP requests to your API endpoints, and use the Wails event system for real-time communication. + +For production use, it's recommended to use the `@wailsio/runtime` package instead of directly importing `/wails/runtime.js`. This ensures type safety, better IDE support, version management through npm, and compatibility with modern JavaScript tooling. + +Install the package: +```bash +npm install @wailsio/runtime +``` + +Then use it in your code: +```javascript +import * as wails from '@wailsio/runtime'; + +// Event emission +wails.Events.Emit('gin-api-event', eventData); +``` + +Here's an example of how to set up frontend integration: + +```html + + + + + + Gin Service Example + + + +

      Gin Service Example

      + +
      +

      API Endpoints

      +

      Try the Gin API endpoints mounted at /api:

      + + + + + + + +
      +
      Results will appear here...
      +
      +
      + +
      +

      Event Communication

      +

      Trigger an event to communicate with the Gin service:

      + + + +
      +
      Event responses will appear here...
      +
      +
      + + + + + + +``` + +## Closing Thoughts + +Integrating the Gin web framework with Wails v3 Services provides a powerful and flexible approach to building modular, maintainable web applications. By leveraging Gin's routing and middleware capabilities alongside the Wails event system, you can create rich, interactive applications with clean separation of concerns. + +The complete example code for this guide can be found in the Wails repository under `v3/examples/gin-service`. diff --git a/docs/src/content/docs/guides/installers.mdx b/docs/src/content/docs/guides/installers.mdx new file mode 100644 index 000000000..170b26865 --- /dev/null +++ b/docs/src/content/docs/guides/installers.mdx @@ -0,0 +1,160 @@ +--- +title: Creating Installers +description: Package your application for distribution +sidebar: + order: 3 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +## Overview + +Create professional installers for your Wails application on all platforms. + +## Platform Installers + + + + ### NSIS Installer + + ```bash + # Install NSIS + # Download from: https://nsis.sourceforge.io/ + + # Create installer script (installer.nsi) + makensis installer.nsi + ``` + + **installer.nsi:** + ```nsis + !define APPNAME "MyApp" + !define VERSION "1.0.0" + + Name "${APPNAME}" + OutFile "MyApp-Setup.exe" + InstallDir "$PROGRAMFILES\${APPNAME}" + + Section "Install" + SetOutPath "$INSTDIR" + File "build\bin\myapp.exe" + CreateShortcut "$DESKTOP\${APPNAME}.lnk" "$INSTDIR\myapp.exe" + SectionEnd + ``` + + ### WiX Toolset + + Alternative for MSI installers. + + + + ### DMG Creation + + ```bash + # Create DMG + hdiutil create -volname "MyApp" -srcfolder build/bin/MyApp.app -ov -format UDZO MyApp.dmg + ``` + + ### Code Signing + + ```bash + # Sign application + codesign --deep --force --verify --verbose --sign "Developer ID" MyApp.app + + # Notarize + xcrun notarytool submit MyApp.dmg --apple-id "email" --password "app-password" + ``` + + ### App Store + + Use Xcode for App Store distribution. + + + + ### DEB Package + + ```bash + # Create package structure + mkdir -p myapp_1.0.0/DEBIAN + mkdir -p myapp_1.0.0/usr/bin + + # Copy binary + cp build/bin/myapp myapp_1.0.0/usr/bin/ + + # Create control file + cat > myapp_1.0.0/DEBIAN/control << EOF + Package: myapp + Version: 1.0.0 + Architecture: amd64 + Maintainer: Your Name + Description: My Application + EOF + + # Build package + dpkg-deb --build myapp_1.0.0 + ``` + + ### RPM Package + + Use `rpmbuild` for RPM-based distributions. + + ### AppImage + + ```bash + # Use appimagetool + appimagetool myapp.AppDir + ``` + + + +## Automated Packaging + +### Using GoReleaser + +```yaml +# .goreleaser.yml +project_name: myapp + +builds: + - binary: myapp + goos: + - windows + - darwin + - linux + goarch: + - amd64 + - arm64 + +archives: + - format: zip + name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" + +nfpms: + - formats: + - deb + - rpm + vendor: Your Company + homepage: https://example.com + description: My Application +``` + +## Best Practices + +### ✅ Do + +- Code sign on all platforms +- Include version information +- Create uninstallers +- Test installation process +- Provide clear documentation + +### ❌ Don't + +- Don't skip code signing +- Don't forget file associations +- Don't hardcode paths +- Don't skip testing + +## Next Steps + +- [Auto-Updates](/guides/auto-updates) - Implement automatic updates +- [Cross-Platform Building](/guides/cross-platform) - Build for multiple platforms diff --git a/docs/src/content/docs/guides/menus.mdx b/docs/src/content/docs/guides/menus.mdx new file mode 100644 index 000000000..c16c40ff6 --- /dev/null +++ b/docs/src/content/docs/guides/menus.mdx @@ -0,0 +1,531 @@ +--- +title: Menus +description: A guide to creating and customising menus in Wails v3 +--- + +Wails v3 provides a powerful menu system that allows you to create both application menus and context menus. This guide will walk you through the various features and capabilities of the menu system. + +### Creating a Menu + +To create a new menu, use the `New()` method from the Menus manager: + +```go +menu := app.Menu.New() +``` + +### Adding Menu Items + +Wails supports several types of menu items, each serving a specific purpose: + +#### Regular Menu Items +Regular menu items are the basic building blocks of menus. They display text and can trigger actions when clicked: + +```go +menuItem := menu.Add("Click Me") +``` + +#### Checkboxes +Checkbox menu items provide a toggleable state, useful for enabling/disabling features or settings: + +```go +checkbox := menu.AddCheckbox("My checkbox", true) // true = initially checked +``` + +#### Radio Groups +Radio groups allow users to select one option from a set of mutually exclusive choices. They are automatically created when radio items are placed next to each other: + +```go +menu.AddRadio("Option 1", true) // true = initially selected +menu.AddRadio("Option 2", false) +menu.AddRadio("Option 3", false) +``` + +#### Separators +Separators are horizontal lines that help organise menu items into logical groups: + +```go +menu.AddSeparator() +``` + +#### Submenus +Submenus are nested menus that appear when hovering over or clicking a menu item. They're useful for organizing complex menu structures: + +```go +submenu := menu.AddSubmenu("File") +submenu.Add("Open") +submenu.Add("Save") +``` + +#### Combining menus +A menu can be added into another menu by appending or prepending it. +```go +menu := app.Menu.New() +menu.Add("First Menu") + +secondaryMenu := app.Menu.New() +secondaryMenu.Add("Second Menu") + +// insert 'secondaryMenu' after 'menu' +menu.Append(secondaryMenu) + +// insert 'secondaryMenu' before 'menu' +menu.Prepend(secondaryMenu) + +// update the menu +menu.Update() +``` + +:::note +By default, `prepend` and `append` will share state with the original menu. If you want to create a new menu with its own state, +you can call `.Clone()` on the menu. + +E.g: `menu.Append(secondaryMenu.Clone())` +::: + +#### Clearing a menu +In some cases it'll be better to construct a whole new menu if you are working with a variable amount of menu items. + +This will clear all items on an existing menu and allows you to add items again. + +```go +menu := app.Menu.New() +menu.Add("Waiting for update...") + +// after certain logic, the menu has to be updated +menu.Clear() +menu.Add("Update complete!") +menu.Update() +``` + +:::note +Clearing a menu simply clears the menu items at the top level. Whilst Submenus won't be visible, they will still occupy memory +so be sure to manage your menus carefully. +::: + +#### Destroying a menu + +If you want to clear and release a menu, use the `Destroy()` method: + +```go +menu := app.Menu.New() +menu.Add("Waiting for update...") + +// after certain logic, the menu has to be destroyed +menu.Destroy() +``` + + +### Menu Item Properties + +Menu items have several properties that can be configured: + +| Property | Method | Description | +|-------------|--------------------------|-----------------------------------------------------| +| Label | `SetLabel(string)` | Sets the display text | +| Enabled | `SetEnabled(bool)` | Enables/disables the item | +| Checked | `SetChecked(bool)` | Sets the checked state (for checkboxes/radio items) | +| Tooltip | `SetTooltip(string)` | Sets the tooltip text | +| Hidden | `SetHidden(bool)` | Shows/hides the item | +| Accelerator | `SetAccelerator(string)` | Sets the keyboard shortcut | + +### Menu Item States + +Menu items can be in different states that control their visibility and interactivity: + +#### Visibility + +Menu items can be shown or hidden dynamically using the `SetHidden()` method: + +```go +menuItem := menu.Add("Dynamic Item") + +// Hide the menu item +menuItem.SetHidden(true) + +// Show the menu item +menuItem.SetHidden(false) + +// Check current visibility +isHidden := menuItem.Hidden() +``` + +Hidden menu items are completely removed from the menu until shown again. This is useful for contextual menu items that should only appear in certain application states. + +#### Enabled State + +Menu items can be enabled or disabled using the `SetEnabled()` method: + +```go +menuItem := menu.Add("Save") + +// Disable the menu item +menuItem.SetEnabled(false) // Item appears grayed out and cannot be clicked + +// Enable the menu item +menuItem.SetEnabled(true) // Item becomes clickable again + +// Check current enabled state +isEnabled := menuItem.Enabled() +``` + +Disabled menu items remain visible but appear grayed out and cannot be clicked. This is commonly used to indicate that an action is currently unavailable, such as: +- Disabling "Save" when there are no changes to save +- Disabling "Copy" when nothing is selected +- Disabling "Undo" when there's no action to undo + +#### Dynamic State Management + +You can combine these states with event handlers to create dynamic menus: + +```go +saveMenuItem := menu.Add("Save") + +// Initially disable the Save menu item +saveMenuItem.SetEnabled(false) + +// Enable Save only when there are unsaved changes +documentChanged := func() { + saveMenuItem.SetEnabled(true) + menu.Update() // Remember to update the menu after changing states +} + +// Disable Save after saving +documentSaved := func() { + saveMenuItem.SetEnabled(false) + menu.Update() +} +``` + +### Event Handling + +Menu items can respond to click events using the `OnClick` method: + +```go +menuItem.OnClick(func(ctx *application.Context) { + // Handle the click event + println("Menu item clicked!") +}) +``` + +The context provides information about the clicked menu item: + +```go +menuItem.OnClick(func(ctx *application.Context) { + // Get the clicked menu item + clickedItem := ctx.ClickedMenuItem() + // Get its current state + isChecked := clickedItem.Checked() +}) +``` + +### Role-Based Menu Items + +Wails provides a set of predefined menu roles that automatically create menu items with standard functionality. Here are the supported menu roles: + +#### Complete Menu Structures +These roles create entire menu structures with common functionality: + +| Role | Description | Platform Notes | +|------|-------------|----------------| +| `AppMenu` | Application menu with About, Services, Hide/Show, and Quit | macOS only | +| `EditMenu` | Standard Edit menu with Undo, Redo, Cut, Copy, Paste, etc. | All platforms | +| `ViewMenu` | View menu with Reload, Zoom, and Fullscreen controls | All platforms | +| `WindowMenu` | Window controls (Minimise, Zoom, etc.) | All platforms | +| `HelpMenu` | Help menu with "Learn More" link to Wails website | All platforms | + +#### Individual Menu Items +These roles can be used to add individual menu items: + +| Role | Description | Platform Notes | +|------|-------------|----------------| +| `About` | Show application About dialog | All platforms | +| `Hide` | Hide application | macOS only | +| `HideOthers` | Hide other applications | macOS only | +| `UnHide` | Show hidden application | macOS only | +| `CloseWindow` | Close current window | All platforms | +| `Minimise` | Minimise window | All platforms | +| `Zoom` | Zoom window | macOS only | +| `Front` | Bring window to front | macOS only | +| `Quit` | Quit application | All platforms | +| `Undo` | Undo last action | All platforms | +| `Redo` | Redo last action | All platforms | +| `Cut` | Cut selection | All platforms | +| `Copy` | Copy selection | All platforms | +| `Paste` | Paste from clipboard | All platforms | +| `PasteAndMatchStyle` | Paste and match style | macOS only | +| `SelectAll` | Select all | All platforms | +| `Delete` | Delete selection | All platforms | +| `Reload` | Reload current page | All platforms | +| `ForceReload` | Force reload current page | All platforms | +| `ToggleFullscreen` | Toggle fullscreen mode | All platforms | +| `ResetZoom` | Reset zoom level | All platforms | +| `ZoomIn` | Increase zoom | All platforms | +| `ZoomOut` | Decrease zoom | All platforms | + +Here's an example showing how to use both complete menus and individual roles: + +```go +menu := app.Menu.New() + +// Add complete menu structures +menu.AddRole(application.AppMenu) // macOS only +menu.AddRole(application.EditMenu) // Common edit operations +menu.AddRole(application.ViewMenu) // View controls +menu.AddRole(application.WindowMenu) // Window controls + +// Add individual role-based items to a custom menu +fileMenu := menu.AddSubmenu("File") +fileMenu.AddRole(application.CloseWindow) +fileMenu.AddSeparator() +fileMenu.AddRole(application.Quit) +``` + +## Application Menus + +Application menus are the menus that appear at the top of your application window (Windows/Linux) or at the top of the screen (macOS). + + +### Application Menu Behaviour + +When you set an application menu using `app.Menu.Set()`, it becomes the main menu on macOS. +Menus are set on a per-window basis for Windows/Linux. + +```go +app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Custom Menu Window", + Windows: application.WindowsWindow{ + Menu: customMenu, // Override application menu for this window + }, +}) +``` + +Here's a complete example showing these different menu behaviours: + +```go +func main() { + app := application.New(application.Options{}) + + // Create application menu + appMenu := app.Menu.New() + fileMenu := appMenu.AddSubmenu("File") + fileMenu.Add("New").OnClick(func(ctx *application.Context) { + // This will be available in all windows unless overridden + window := app.Windows.Current() + window.SetTitle("New Window") + }) + + // Set as application menu - this is for macOS + app.Menu.Set(appMenu) + + // Window with custom menu on Windows + customMenu := app.Menu.New() + customMenu.Add("Custom Action") + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Custom Menu", + Windows: application.WindowsWindow{ + Menu: customMenu, + }, + }) + + app.Run() +} +``` + +## Context Menus + +Context menus are popup menus that appear when right-clicking elements in your application. They provide quick access to relevant actions for the clicked element. + +### Default Context Menu + +The default context menu is the webview's built-in context menu that provides system-level operations such as: +- Copy, Cut, and Paste for text manipulation +- Text selection controls +- Spell checking options + +#### Controlling the Default Context Menu + +You can control when the default context menu appears using the `--default-contextmenu` CSS property: + +```html + +
      + + +
      + + +
      +
      Custom context menu only
      +
      + + +
      + +

      Select this text to see the default menu

      + +
      +``` + +:::note +This feature will only work as expected after the runtime [has been initialised](../../learn/runtime#initialisation). +::: + +#### Nested Context Menu Behavior + +When using the `--default-contextmenu` property on nested elements, the following rules apply: + +1. Child elements inherit their parent's context menu setting unless explicitly overridden +2. The most specific (closest) setting takes precedence +3. The `auto` value can be used to reset to default behaviour + +Example of nested context menu behaviour: + +```html + +
      + +

      No context menu here

      + + +
      +

      Context menu shown here

      + + + Also has context menu + + +
      +

      Shows menu only when text is selected

      +
      +
      +
      +``` + +### Custom Context Menus + +Custom context menus allow you to provide application-specific actions that are relevant to the element being clicked. They're particularly useful for: +- File operations in a document manager +- Image manipulation tools +- Custom actions in a data grid +- Component-specific operations + +#### Creating a Custom Context Menu + +When creating a custom context menu, you provide a unique identifier (name) that links the menu to HTML elements: + +```go +// Create a context menu with identifier "imageMenu" +contextMenu := application.NewContextMenu() +app.ContextMenus.Add("imageMenu", contextMenu) +``` + +The name parameter ("imageMenu" in this example) serves as a unique identifier that will be used to: +1. Link HTML elements to this specific context menu +2. Identify which menu should be shown when right-clicking +3. Allow menu updates and cleanup + +#### Context Data + +When handling context menu events, you can access both the clicked menu item and its associated context data: + +```go +contextMenu.Add("Process").OnClick(func(ctx *application.Context) { + // Get the clicked menu item + menuItem := ctx.ClickedMenuItem() + + // Get the context data as a string + contextData := ctx.ContextMenuData() + + // Check if the menu item is checked (for checkbox/radio items) + isChecked := ctx.IsChecked() + + // Use the data + if contextData != "" { + processItem(contextData) + } +}) +``` + +The context data is passed from the HTML element's `--custom-contextmenu-data` property and is available in the click handler through `ctx.ContextMenuData()`. This is particularly useful when: + +- Working with lists or grids where each item needs unique identification +- Handling operations on specific components or elements +- Passing state or metadata from the frontend to the backend + +#### Context Menu Management + +After making changes to a context menu, call the `Update()` method to apply the changes: + +```go +contextMenu.Update() +``` + +When you no longer need a context menu, you can destroy it: + +```go +contextMenu.Destroy() +``` +:::danger[Warning] +After calling `Destroy()`, using the context menu reference again will result in a panic. +::: + +### Real-World Example: Image Gallery + +Here's a complete example of implementing a custom context menu for an image gallery: + +```go +// Backend: Create the context menu +imageMenu := application.NewContextMenu() +app.ContextMenus.Add("imageMenu", imageMenu) + +// Add relevant operations +imageMenu.Add("View Full Size").OnClick(func(ctx *application.Context) { + // Get the image ID from context data + if imageID := ctx.ContextMenuData(); imageID != "" { + openFullSizeImage(imageID) + } +}) + +imageMenu.Add("Download").OnClick(func(ctx *application.Context) { + if imageID := ctx.ContextMenuData(); imageID != "" { + downloadImage(imageID) + } +}) + +imageMenu.Add("Share").OnClick(func(ctx *application.Context) { + if imageID := ctx.ContextMenuData(); imageID != "" { + showShareDialog(imageID) + } +}) +``` + +```html + + +``` + +In this example: +1. The context menu is created with the identifier "imageMenu" +2. Each image container is linked to the menu using `--custom-contextmenu: imageMenu` +3. Each container provides its image ID as context data using `--custom-contextmenu-data` +4. The backend receives the image ID in click handlers and can perform specific operations +5. The same menu is reused for all images, but the context data tells us which image to operate on + +This pattern is particularly powerful for: +- Data grids where rows need specific operations +- File managers where files need context-specific actions +- Design tools where different elements need different operations +- Any component where the same operations apply to multiple instances diff --git a/docs/src/content/docs/guides/panic-handling.mdx b/docs/src/content/docs/guides/panic-handling.mdx new file mode 100644 index 000000000..f252f0dc3 --- /dev/null +++ b/docs/src/content/docs/guides/panic-handling.mdx @@ -0,0 +1,116 @@ +--- +title: Handling Panics +description: How to handle panics in your Wails application +--- + +In Go applications, panics can occur during runtime when something unexpected happens. This guide explains how to handle panics both in general Go code and specifically in your Wails application. + +## Understanding Panics in Go + +Before diving into Wails-specific panic handling, it's essential to understand how panics work in Go: + +1. Panics are for unrecoverable errors that shouldn't happen during normal operation +2. When a panic occurs in a goroutine, only that goroutine is affected +3. Panics can be recovered using `defer` and `recover()` + +Here's a basic example of panic handling in Go: + +```go +func doSomething() { + // Deferred functions run even when a panic occurs + defer func() { + if r := recover(); r != nil { + fmt.Printf("Recovered from panic: %v\n", r) + } + }() + + // Your code that might panic + panic("something went wrong") +} +``` + +For more detailed information about panic and recover in Go, see the [Go Blog: Defer, Panic, and Recover](https://go.dev/blog/defer-panic-and-recover). + +## Panic Handling in Wails + +Wails automatically handles panics that occur in your Service methods when they are called from the frontend. This means you don't need to add panic recovery to these methods - Wails will catch the panic and process it through your configured panic handler. + +The panic handler is specifically designed to catch: +- Panics in bound service methods called from the frontend +- Internal panics from the Wails runtime + +For other scenarios, such as background goroutines or standalone Go code, you should handle panics yourself using Go's standard panic recovery mechanisms. + +## The PanicDetails Struct + +When a panic occurs, Wails captures important information about the panic in a `PanicDetails` struct: + +```go +type PanicDetails struct { + StackTrace string // The stack trace of where the panic occurred. Potentially trimmed to provide more context + Error error // The error that caused the panic + Time time.Time // The time when the panic occurred + FullStackTrace string // The complete stack trace including runtime frames +} +``` + +This structure provides comprehensive information about the panic: +- `StackTrace`: A formatted string showing the call stack that led to the panic +- `Error`: The actual error or panic message +- `Time`: The exact time when the panic occurred +- `FullStackTrace`: The complete stack trace including runtime frames + +:::note[Panics in Service Code] + +When panics are caught in your Service code after being called from the frontend, the stack trace is trimmed to focus on exactly where in your code the panic occurred. +If you want to see the full stack trace, you can use the `FullStackTrace` field. + +::: + +## Default Panic Handler + +If you don't specify a custom panic handler, Wails will use its default handler which outputs error information in a formatted log message and then quits. +For example: + +``` +************************ FATAL ****************************** +* There has been a catastrophic failure in your application * +********************* Error Details ************************* +panic error: oh no! something went wrong deep in my service! :( +main.(*WindowService).call2 + at E:/wails/v3/examples/panic-handling/main.go:23 +main.(*WindowService).call1 + at E:/wails/v3/examples/panic-handling/main.go:19 +main.(*WindowService).GeneratePanic + at E:/wails/v3/examples/panic-handling/main.go:15 +************************************************************* +``` + +## Custom Panic Handler + +You can implement your own panic handler by setting the `PanicHandler` option when creating your application. Here's an example: + +```go +app := application.New(application.Options{ + Name: "My App", + PanicHandler: func(panicDetails *application.PanicDetails) { + fmt.Printf("*** Custom Panic Handler ***\n") + fmt.Printf("Time: %s\n", panicDetails.Time) + fmt.Printf("Error: %s\n", panicDetails.Error) + fmt.Printf("Stacktrace: %s\n", panicDetails.StackTrace) + fmt.Printf("Full Stacktrace: %s\n", panicDetails.FullStackTrace) + + // You could also: + // - Log to a file + // - Send to a crash reporting service + // - Show a user-friendly error dialog + // - Attempt to recover or restart the application + }, +}) +``` + +For a complete working example of panic handling in a Wails application, see the panic-handling example in `v3/examples/panic-handling`. + +## Final Notes + +Remember that the Wails panic handler is specifically for managing panics in bound methods and internal runtime errors. For other parts of your application, you should use Go's standard error handling patterns and panic recovery mechanisms where appropriate. As with all Go applications, it's better to prevent panics through proper error handling where possible. diff --git a/docs/src/content/docs/guides/performance.mdx b/docs/src/content/docs/guides/performance.mdx new file mode 100644 index 000000000..1d38a1ef4 --- /dev/null +++ b/docs/src/content/docs/guides/performance.mdx @@ -0,0 +1,341 @@ +--- +title: Performance Optimisation +description: Optimise your Wails application for maximum performance +sidebar: + order: 9 +--- + +## Overview + +Optimise your Wails application for speed, memory efficiency, and responsiveness. + +## Frontend Optimisation + +### Bundle Size + +```javascript +// vite.config.js +export default { + build: { + rollupOptions: { + output: { + manualChunks: { + vendor: ['react', 'react-dom'], + }, + }, + }, + minify: 'terser', + terserOptions: { + compress: { + drop_console: true, + }, + }, + }, +} +``` + +### Code Splitting + +```javascript +// Lazy load components +const Settings = lazy(() => import('./Settings')) + +function App() { + return ( + }> + + + ) +} +``` + +### Asset Optimisation + +```javascript +// Optimise images +import { defineConfig } from 'vite' +import imagemin from 'vite-plugin-imagemin' + +export default defineConfig({ + plugins: [ + imagemin({ + gifsicle: { optimizationLevel: 3 }, + optipng: { optimizationLevel: 7 }, + svgo: { plugins: [{ removeViewBox: false }] }, + }), + ], +}) +``` + +## Backend Optimisation + +### Efficient Bindings + +```go +// ❌ Bad: Return everything +func (s *Service) GetAllData() []Data { + return s.db.FindAll() // Could be huge +} + +// ✅ Good: Paginate +func (s *Service) GetData(page, size int) (*PagedData, error) { + return s.db.FindPaged(page, size) +} +``` + +### Caching + +```go +type CachedService struct { + cache *lru.Cache + ttl time.Duration +} + +func (s *CachedService) GetData(key string) (interface{}, error) { + // Check cache + if val, ok := s.cache.Get(key); ok { + return val, nil + } + + // Fetch and cache + data, err := s.fetchData(key) + if err != nil { + return nil, err + } + + s.cache.Add(key, data) + return data, nil +} +``` + +### Goroutines for Long Operations + +```go +func (s *Service) ProcessLargeFile(path string) error { + // Process in background + go func() { + result, err := s.process(path) + if err != nil { + s.app.Event.Emit("process-error", err.Error()) + return + } + s.app.Event.Emit("process-complete", result) + }() + + return nil +} +``` + +## Memory Optimisation + +### Avoid Memory Leaks + +```go +// ❌ Bad: Goroutine leak +func (s *Service) StartPolling() { + ticker := time.NewTicker(1 * time.Second) + go func() { + for range ticker.C { + s.poll() + } + }() + // ticker never stopped! +} + +// ✅ Good: Proper cleanup +func (s *Service) StartPolling() { + ticker := time.NewTicker(1 * time.Second) + s.stopChan = make(chan bool) + + go func() { + for { + select { + case <-ticker.C: + s.poll() + case <-s.stopChan: + ticker.Stop() + return + } + } + }() +} + +func (s *Service) StopPolling() { + close(s.stopChan) +} +``` + +### Pool Resources + +```go +var bufferPool = sync.Pool{ + New: func() interface{} { + return new(bytes.Buffer) + }, +} + +func processData(data []byte) []byte { + buf := bufferPool.Get().(*bytes.Buffer) + defer bufferPool.Put(buf) + + buf.Reset() + buf.Write(data) + // Process... + return buf.Bytes() +} +``` + +## Event Optimisation + +### Debounce Events + +```javascript +// Debounce frequent events +let debounceTimer +function handleInput(value) { + clearTimeout(debounceTimer) + debounceTimer = setTimeout(() => { + UpdateData(value) + }, 300) +} +``` + +### Batch Updates + +```go +type BatchProcessor struct { + items []Item + mu sync.Mutex + timer *time.Timer +} + +func (b *BatchProcessor) Add(item Item) { + b.mu.Lock() + defer b.mu.Unlock() + + b.items = append(b.items, item) + + if b.timer == nil { + b.timer = time.AfterFunc(100*time.Millisecond, b.flush) + } +} + +func (b *BatchProcessor) flush() { + b.mu.Lock() + items := b.items + b.items = nil + b.timer = nil + b.mu.Unlock() + + // Process batch + processBatch(items) +} +``` + +## Build Optimisation + +### Binary Size + +```bash +# Strip debug symbols +wails3 build -ldflags "-s -w" + +# Reduce binary size further +go build -ldflags="-s -w" -trimpath +``` + +### Compilation Speed + +```bash +# Use build cache +go build -buildmode=default + +# Parallel compilation +go build -p 8 +``` + +## Profiling + +### CPU Profiling + +```go +import "runtime/pprof" + +func profileCPU() { + f, _ := os.Create("cpu.prof") + defer f.Close() + + pprof.StartCPUProfile(f) + defer pprof.StopCPUProfile() + + // Code to profile +} +``` + +### Memory Profiling + +```go +import "runtime/pprof" + +func profileMemory() { + f, _ := os.Create("mem.prof") + defer f.Close() + + runtime.GC() + pprof.WriteHeapProfile(f) +} +``` + +### Analyse Profiles + +```bash +# View CPU profile +go tool pprof cpu.prof + +# View memory profile +go tool pprof mem.prof + +# Web interface +go tool pprof -http=:8080 cpu.prof +``` + +## Best Practices + +### ✅ Do + +- Profile before optimising +- Cache expensive operations +- Use pagination for large datasets +- Debounce frequent events +- Pool resources +- Clean up goroutines +- Optimise bundle size +- Use lazy loading + +### ❌ Don't + +- Don't optimise prematurely +- Don't ignore memory leaks +- Don't block the main thread +- Don't return huge datasets +- Don't skip profiling +- Don't forget cleanup + +## Performance Checklist + +- [ ] Frontend bundle optimised +- [ ] Images compressed +- [ ] Code splitting implemented +- [ ] Backend methods paginated +- [ ] Caching implemented +- [ ] Goroutines cleaned up +- [ ] Events debounced +- [ ] Binary size optimised +- [ ] Profiling done +- [ ] Memory leaks fixed + +## Next Steps + +- [Architecture](/guides/architecture) - Application architecture patterns +- [Testing](/guides/testing) - Test your application +- [Building](/guides/building) - Build optimised binaries diff --git a/docs/src/content/docs/guides/raw-messages.mdx b/docs/src/content/docs/guides/raw-messages.mdx new file mode 100644 index 000000000..5f2a7a653 --- /dev/null +++ b/docs/src/content/docs/guides/raw-messages.mdx @@ -0,0 +1,393 @@ +--- +title: Raw Messages +description: Implement custom frontend-to-backend communication for performance-critical applications +sidebar: + order: 10 +--- + +Raw messages provide a low-level communication channel between your frontend and backend, bypassing the standard binding system. This trades convenience for speed. + +## When to Use Raw Messages + +Raw messages are best suited for extreme edge cases: + +- **Ultra-high-frequency updates** - Thousands of messages per second where every microsecond matters +- **Custom message protocols** - When you need complete control over the wire format + +:::tip +For almost all use cases, standard [service bindings](/features/bindings/services) are recommended as they provide type safety, automatic serialization, and a better developer experience with negligible overhead. +::: + +## Backend Setup + +Configure the `RawMessageHandler` in your application options: + +```go +package main + +import ( + "encoding/json" + "fmt" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Name: "Raw Message Demo", + Assets: application.AssetOptions{ + Handler: application.BundledAssetFileServer(assets), + }, + RawMessageHandler: func(window application.Window, message string, originInfo *application.OriginInfo) { + fmt.Printf("Raw message from window '%s': %s (origin: %+v)\n", window.Name(), message, originInfo.Origin) + + // Process the message and respond via events + response := processMessage(message) + window.EmitEvent("raw-response", response) + }, + }) + + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "My App", + Name: "main", + }) + + app.Run() +} + +func processMessage(message string) map[string]any { + // Your custom message processing logic + return map[string]any{ + "received": message, + "status": "processed", + } +} +``` + +### Handler Signature + +```go +RawMessageHandler func(window Window, message string, originInfo *application.OriginInfo) +``` + +| Parameter | Type | Description | +|-----------|------|-------------| +| `window` | `Window` | The window that sent the message | +| `message` | `string` | The raw message content | +| `originInfo` | `*application.OriginInfo` | Origin information about the message source | + +#### OriginInfo Structure + +```go +type OriginInfo struct { + Origin string + TopOrigin string + IsMainFrame bool +} +``` + +| Field | Type | Description | +|-------|------|-------------| +| `Origin` | `string` | The origin URL of the document that sent the message | +| `TopOrigin` | `string` | The top-level origin URL (may differ from Origin in iframes) | +| `IsMainFrame` | `bool` | Whether the message originated from the main frame | + +#### Platform-Specific Availability + +- **macOS**: `Origin` and `IsMainFrame` are provided +- **Windows**: `Origin` and `TopOrigin` are provided +- **Linux**: Only `Origin` is provided + +### Origin Validation + +:::caution +Never assume a message is safe because it arrives in your handler. The origin information must be validated before processing sensitive operations or operations that modify state. +::: + +**Always verify the origin of incoming messages before processing them.** The `originInfo` parameter provides critical security information that must be validated to prevent unauthorized access. +Malicious content, compromised content, or unintended scripts could send raw messages. Without origin validation, you may process commands from untrusted sources. Use `originInfo` to ensure messages come from expected sources. + +### Key Validation Points + +- **Always check `Origin`** - Verify the origin matches your expected trusted sources (typically `wails://wails` or `http://wails.localhost` for local assets or your app's specific origin) +- **Validate `IsMainFrame`** (macOS) - Be aware if the message comes from an iframe, as this may indicate embedded content with different security contexts +- **Use `TopOrigin`** (Windows) - Verify the top-level origin when dealing with framed content +- **Reject unexpected origins** - Fail securely by rejecting messages from origins you don't explicitly allow + + +:::note +Messages prefixed with `wails:` are reserved for internal Wails communication and will not be passed to your handler. +::: + +## Frontend Setup + +Send raw messages using `System.invoke()`: + +```html + + + + + + + + + + +``` + +### Using the Pre-built Bundle + +If you're not using npm, access `invoke` via the global `wails` object: + +```html + + +``` + +## Structured Messages + +For complex data, serialize to JSON: + +### Frontend + +```javascript +import { System } from '@wailsio/runtime' + +const command = { + action: 'update', + payload: { + id: 123, + value: 'new value' + } +} + +System.invoke(JSON.stringify(command)) +``` + +### Backend + +```go +RawMessageHandler: func(window application.Window, message string, originInfo *application.OriginInfo) { + var cmd struct { + Action string `json:"action"` + Payload struct { + ID int `json:"id"` + Value string `json:"value"` + } `json:"payload"` + } + + if err := json.Unmarshal([]byte(message), &cmd); err != nil { + window.EmitEvent("error", err.Error()) + return + } + + switch cmd.Action { + case "update": + // Handle update + result := handleUpdate(cmd.Payload.ID, cmd.Payload.Value) + window.EmitEvent("update-complete", result) + default: + window.EmitEvent("error", "unknown action") + } +} +``` + +## Performance Comparison + +| Approach | Overhead | Type Safety | Use Case | +|----------|----------|-------------|----------| +| Service Bindings | Higher | Full | General purpose | +| Raw Messages | Minimal | Manual | High-frequency, performance-critical | + +### Benchmark Example + +Raw messages can process significantly more messages per second compared to service bindings for simple payloads: + +```go +// Raw message handler - minimal overhead +RawMessageHandler: func(window application.Window, message string, originInfo *application.OriginInfo) { + // Direct string processing, no reflection or marshaling + counter++ +} +``` + +## Complete Example + +Here's a full example implementing a simple command protocol: + +### main.go + +```go +package main + +import ( + "embed" + "encoding/json" + "fmt" + "time" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed assets +var assets embed.FS + +type Command struct { + Type string `json:"type"` + Data json.RawMessage `json:"data"` +} + +func main() { + app := application.New(application.Options{ + Name: "Raw Message Demo", + Assets: application.AssetOptions{ + Handler: application.BundledAssetFileServer(assets), + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + RawMessageHandler: func(window application.Window, message string, originInfo *application.OriginInfo) { + var cmd Command + if err := json.Unmarshal([]byte(message), &cmd); err != nil { + window.EmitEvent("error", map[string]string{"error": err.Error()}) + return + } + + switch cmd.Type { + case "ping": + window.EmitEvent("pong", map[string]any{ + "time": time.Now().UnixMilli(), + "window": window.Name(), + }) + case "echo": + var text string + json.Unmarshal(cmd.Data, &text) + window.EmitEvent("echo", text) + default: + window.EmitEvent("error", map[string]string{ + "error": fmt.Sprintf("unknown command: %s", cmd.Type), + }) + } + }, + }) + + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Raw Message Demo", + Name: "main", + Width: 400, + Height: 300, + }) + + app.Run() +} +``` + +### assets/index.html + +```html + + + + Raw Message Demo + + + +

      Raw Message Demo

      + + + + +
      Waiting for response...
      + + + + +``` + +## Best Practices + +### Do + +- Use raw messages for genuinely performance-critical paths +- Implement proper error handling in your handler +- Use events to send responses back to the frontend +- Consider JSON for structured data +- Keep message processing fast to avoid blocking + +### Don't + +- Use raw messages when service bindings would suffice +- Forget to validate incoming messages +- Block in the handler with long-running operations (use goroutines) +- Ignore the window parameter when responses need to target specific windows + +## Multi-Window Considerations + +The `window` parameter identifies which window sent the message, allowing you to: + +- Send responses to the correct window +- Implement window-specific behavior +- Track message sources for debugging + +```go +RawMessageHandler: func(window application.Window, message string, originInfo *application.OriginInfo) { + // Respond only to the sending window + window.EmitEvent("response", result) + + // Or broadcast to all windows + app.Event.Emit("broadcast", result) +} +``` + +## Next Steps + +- [Service Bindings](/features/bindings/services) - Standard approach for most applications +- [Events](/guides/events-reference) - Event system for backend-to-frontend communication +- [Performance](/guides/performance) - General performance optimization diff --git a/docs/src/content/docs/guides/security.mdx b/docs/src/content/docs/guides/security.mdx new file mode 100644 index 000000000..51bc21b3e --- /dev/null +++ b/docs/src/content/docs/guides/security.mdx @@ -0,0 +1,261 @@ +--- +title: Security Best Practices +description: Secure your Wails application +sidebar: + order: 8 +--- + +## Overview + +Security is critical for desktop applications. Follow these practices to keep your application secure. + +## Input Validation + +### Always Validate + +```go +func (s *UserService) CreateUser(email, password string) (*User, error) { + // Validate email + if !isValidEmail(email) { + return nil, errors.New("invalid email") + } + + // Validate password strength + if len(password) < 8 { + return nil, errors.New("password too short") + } + + // Sanitise input + email = strings.TrimSpace(email) + email = html.EscapeString(email) + + // Continue... +} +``` + +### Sanitise HTML + +```go +import "html" + +func (s *Service) SaveComment(text string) error { + // Escape HTML + text = html.EscapeString(text) + + // Validate length + if len(text) > 1000 { + return errors.New("comment too long") + } + + return s.db.Save(text) +} +``` + +## Authentication + +### Secure Password Storage + +```go +import "golang.org/x/crypto/bcrypt" + +func hashPassword(password string) (string, error) { + hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + return string(hash), err +} + +func verifyPassword(hash, password string) bool { + err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) + return err == nil +} +``` + +### Session Management + +```go +type Session struct { + UserID int + Token string + ExpiresAt time.Time +} + +func (a *AuthService) CreateSession(userID int) (*Session, error) { + token := generateSecureToken() + + session := &Session{ + UserID: userID, + Token: token, + ExpiresAt: time.Now().Add(24 * time.Hour), + } + + return session, a.saveSession(session) +} +``` + +## Data Protection + +### Encrypt Sensitive Data + +```go +import "crypto/aes" +import "crypto/cipher" + +func encrypt(data []byte, key []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + + nonce := make([]byte, gcm.NonceSize()) + return gcm.Seal(nonce, nonce, data, nil), nil +} +``` + +### Secure Storage + +```go +// Use OS keychain for sensitive data +import "github.com/zalando/go-keyring" + +func saveAPIKey(key string) error { + return keyring.Set("myapp", "api_key", key) +} + +func getAPIKey() (string, error) { + return keyring.Get("myapp", "api_key") +} +``` + +## Network Security + +### Use HTTPS + +```go +func makeAPICall(url string) (*Response, error) { + // Always use HTTPS + if !strings.HasPrefix(url, "https://") { + return nil, errors.New("only HTTPS allowed") + } + + return http.Get(url) +} +``` + +### Verify Certificates + +```go +import "crypto/tls" + +func secureClient() *http.Client { + return &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + MinVersion: tls.VersionTLS12, + }, + }, + } +} +``` + +## File Operations + +### Validate Paths + +```go +func readFile(path string) ([]byte, error) { + // Prevent path traversal + if strings.Contains(path, "..") { + return nil, errors.New("invalid path") + } + + // Check file exists in allowed directory + absPath, err := filepath.Abs(path) + if err != nil { + return nil, err + } + + if !strings.HasPrefix(absPath, allowedDir) { + return nil, errors.New("access denied") + } + + return os.ReadFile(absPath) +} +``` + +## Rate Limiting + +```go +type RateLimiter struct { + requests map[string][]time.Time + mu sync.Mutex + limit int + window time.Duration +} + +func (r *RateLimiter) Allow(key string) bool { + r.mu.Lock() + defer r.mu.Unlock() + + now := time.Now() + + // Clean old requests + var recent []time.Time + for _, t := range r.requests[key] { + if now.Sub(t) < r.window { + recent = append(recent, t) + } + } + + if len(recent) >= r.limit { + return false + } + + r.requests[key] = append(recent, now) + return true +} +``` + +## Best Practices + +### ✅ Do + +- Validate all input +- Use HTTPS for network calls +- Encrypt sensitive data +- Use secure password hashing +- Implement rate limiting +- Keep dependencies updated +- Log security events +- Use OS keychains + +### ❌ Don't + +- Don't trust user input +- Don't store passwords in plain text +- Don't hardcode secrets +- Don't skip certificate verification +- Don't expose sensitive data in logs +- Don't use weak encryption +- Don't ignore security updates + +## Security Checklist + +- [ ] All user input validated +- [ ] Passwords hashed with bcrypt +- [ ] Sensitive data encrypted +- [ ] HTTPS used for all network calls +- [ ] Rate limiting implemented +- [ ] File paths validated +- [ ] Dependencies up to date +- [ ] Security logging enabled +- [ ] Error messages don't leak info +- [ ] Code reviewed for vulnerabilities + +## Next Steps + +- [Architecture](/guides/architecture) - Application architecture patterns +- [Best Practices](/features/bindings/best-practices) - Bindings best practices diff --git a/docs/src/content/docs/guides/server-build.mdx b/docs/src/content/docs/guides/server-build.mdx new file mode 100644 index 000000000..d0a9bcd73 --- /dev/null +++ b/docs/src/content/docs/guides/server-build.mdx @@ -0,0 +1,353 @@ +--- +title: Server Build +description: Run Wails applications as HTTP servers without a native GUI window +sidebar: + order: 52 + badge: + text: Experimental + variant: caution +--- + +import { Aside } from '@astrojs/starlight/components'; + + + +Wails v3 supports server mode, allowing you to run your application as a pure HTTP server without creating native windows or requiring GUI dependencies. This enables deploying the same Wails application to servers, containers, and web browsers. + +## Overview + +Server mode is useful for: + +- **Docker/Container deployments** - Run without X11/Wayland dependencies +- **Server-side applications** - Deploy as a web server accessible via browser +- **Web-only access** - Share the same codebase between desktop and web +- **CI/CD testing** - Run integration tests without a display server +- **Microservices** - Use Wails bindings in headless backend services + +## Quick Start + +Server mode is enabled via the `server` build tag. Your application code remains the same - you just build with the tag: + +```bash +# Using Taskfile (recommended) +wails3 task build:server +wails3 task run:server + +# Or build directly with Go +go build -tags server -o myapp-server . +``` + +Here's a minimal example: + +```go +package main + +import ( + "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed frontend/dist +var assets embed.FS + +func main() { + app := application.New(application.Options{ + Name: "My App", + // Server options are used when built with -tags server + Server: application.ServerOptions{ + Host: "localhost", + Port: 8080, + }, + Services: []application.Service{ + application.NewService(&MyService{}), + }, + Assets: application.AssetOptions{ + Handler: application.AssetFileServerFS(assets), + }, + }) + + log.Println("Starting application...") + if err := app.Run(); err != nil { + log.Fatal(err) + } +} +``` + +The same code can be built for desktop (without the tag) or server mode (with `-tags server`). + +## Configuration + +### ServerOptions + +Configure the HTTP server with `ServerOptions`: + +```go +Server: application.ServerOptions{ + // Host to bind to. Default: "localhost" + // Use "0.0.0.0" to listen on all interfaces + Host: "localhost", + + // Port to listen on. Default: 8080 + Port: 8080, + + // Request read timeout. Default: 30s + ReadTimeout: 30 * time.Second, + + // Response write timeout. Default: 30s + WriteTimeout: 30 * time.Second, + + // Idle connection timeout. Default: 120s + IdleTimeout: 120 * time.Second, + + // Graceful shutdown timeout. Default: 30s + ShutdownTimeout: 30 * time.Second, + + // TLS configuration (optional) + TLS: &application.TLSOptions{ + CertFile: "/path/to/cert.pem", + KeyFile: "/path/to/key.pem", + }, +}, +``` + +## Features + +### Health Check Endpoint + +A health check endpoint is automatically available at `/health`: + +```bash +curl http://localhost:8080/health +# {"status":"ok"} +``` + +This is useful for: +- Kubernetes liveness/readiness probes +- Load balancer health checks +- Monitoring systems + +### Service Bindings + +All service bindings work identically to desktop mode: + +```go +type GreetService struct{} + +func (g *GreetService) Greet(name string) string { + return "Hello, " + name + "!" +} + +// Register in options +Services: []application.Service{ + application.NewService(&GreetService{}), +}, +``` + +The frontend can call these bindings using the standard Wails runtime: + +```javascript +const greeting = await wails.Call.ByName('main.GreetService.Greet', 'World'); +``` + +### Events + +Events work bidirectionally in server mode: + +- **Frontend to Backend**: Events emitted from the browser are sent via HTTP and received by your Go event handlers +- **Backend to Frontend**: Events emitted from Go are broadcast to all connected browsers via WebSocket + +Each browser tab is represented as a "window" with a unique name (`browser-1`, `browser-2`, etc.), accessible via `event.Sender`: + +```go +// Listen for events from browsers +app.Event.On("user-action", func(event *application.CustomEvent) { + log.Printf("Event from %s: %v", event.Sender, event.Data) + // event.Sender will be "browser-1", "browser-2", etc. +}) + +// Emit events to all connected browsers +app.Event.Emit("server-update", data) +``` + +From the frontend: + +```javascript +// Emit event to server (and all other browsers) +await wails.Events.Emit('user-action', { action: 'click' }); + +// Listen for events from server +wails.Events.On('server-update', (event) => { + console.log('Update from server:', event.data); +}); +``` + +### Graceful Shutdown + +The server handles `SIGINT` and `SIGTERM` signals gracefully: + +1. Stops accepting new connections +2. Waits for active requests to complete (up to `ShutdownTimeout`) +3. Runs `OnShutdown` hooks +4. Shuts down services in reverse order + +## Differences from Desktop Mode + +| Feature | Desktop Mode | Server Mode | +|---------|-------------|-------------| +| Native windows | Created | Browser windows (`browser-N`) | +| System tray | Available | Not available | +| Native dialogs | Available | Not available | +| Application menu | Available | Not available | +| Screen info | Available | Returns error | +| Service bindings | Works | Works | +| Events | Works | Works (via WebSocket) | +| Assets | Via webview | Via HTTP | +| CGO required | Yes | No | + +### Window API Behavior + +In server mode, window-related APIs are safely handled: + +- `app.Window.NewWithOptions()` - Logs a warning, returns nil +- `app.Hide()` / `app.Show()` - No-op +- `app.Screen.GetPrimary()` - Returns error + +This allows code that references windows to run without crashing, though window operations have no effect. + +## Building for Production + +### Using Task (Recommended) + +Projects created with `wails3 init` include a `build:server` task: + +```bash +# Build for server mode +wails3 task build:server + +# Build and run +wails3 task run:server +``` + +### Manual Build + +```bash +# Build with server mode +go build -tags server -o myapp-server . +``` + +### Docker + +Wails projects include a ready-to-use Docker setup. To build and run your application in a container: + +```bash +# Build the Docker image +wails3 task build:docker + +# Run it +wails3 task run:docker +``` + +That's it! Your application will be available at `http://localhost:8080`. + +You can customise the build with a few options: + +```bash +# Use a custom image tag +wails3 task build:docker TAG=myapp:v1.0.0 + +# Run on a different port +wails3 task run:docker PORT=3000 +``` + +The generated `Dockerfile.server` creates a minimal image based on distroless. It handles the network binding automatically, so your application will be accessible from outside the container. + +### Docker Compose + +For more complex deployments, here's a Docker Compose configuration with health checks: + +```yaml +services: + app: + build: . + ports: + - "8080:8080" + environment: + - WAILS_SERVER_HOST=0.0.0.0 + healthcheck: + test: ["CMD", "wget", "-q", "--spider", "http://localhost:8080/health"] + interval: 30s + timeout: 10s + retries: 3 +``` + +:::note +The healthcheck example uses `wget`. If you're using a distroless base image, you'll need to either include a healthcheck binary in your image or use an external health check mechanism (e.g., Docker's `curl` option or a sidecar container). +::: + +### Custom Dockerfile + +If you need more control, you can create your own Dockerfile. The key thing to remember is setting `WAILS_SERVER_HOST=0.0.0.0` so the server accepts connections from outside the container: + +```dockerfile +# Build stage +FROM golang:alpine AS builder +WORKDIR /app +RUN apk add --no-cache git +COPY . . +RUN go mod tidy +RUN go build -tags server -ldflags="-s -w" -o server . + +# Runtime stage +FROM gcr.io/distroless/static-debian12 +COPY --from=builder /app/server /server +COPY --from=builder /app/frontend/dist /frontend/dist +EXPOSE 8080 +ENV WAILS_SERVER_HOST=0.0.0.0 +ENTRYPOINT ["/server"] +``` + +## Security Considerations + +When deploying server mode applications: + +1. **Bind to localhost by default** - Only use `0.0.0.0` when needed +2. **Use TLS in production** - Configure `ServerOptions.TLS` +3. **Place behind reverse proxy** - Use nginx/traefik for additional security +4. **Validate all inputs** - Same security practices as any web application + +## Example + +A complete example is available at `v3/examples/server/`: + +```bash +cd v3/examples/server + +# Using Taskfile +task dev + +# Or run directly +go run -tags server . + +# Open http://localhost:8080 in browser +``` + +## Environment Variables + +For deployment scenarios where you need to override the server configuration without changing code, Wails recognises these environment variables: + +| Variable | Description | Default | +|----------|-------------|---------| +| `WAILS_SERVER_HOST` | Network interface to bind to | `localhost` | +| `WAILS_SERVER_PORT` | Port to listen on | `8080` | + +These take precedence over the `ServerOptions` in your code, which is why the Docker examples set `WAILS_SERVER_HOST=0.0.0.0` - it allows the container to accept external connections without requiring any changes to your application. + +## See Also + +- [Custom Transport](/docs/guides/custom-transport) - For advanced IPC customization +- [Services](/docs/concepts/services) - Service binding documentation +- [Events](/docs/guides/events-reference) - Event system documentation diff --git a/docs/src/content/docs/guides/single-instance.mdx b/docs/src/content/docs/guides/single-instance.mdx new file mode 100644 index 000000000..25b7fac47 --- /dev/null +++ b/docs/src/content/docs/guides/single-instance.mdx @@ -0,0 +1,119 @@ +--- +title: Single Instance +description: Limiting your app to a single running instance +sidebar: + order: 40 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +Single instance locking is a mechanism that prevents multiple instances of your app from running at the same time. +It is useful for apps that are designed to open files from the command line or from the OS file explorer. + + +## Usage + +To enable single instance functionality in your app, provide a `SingleInstanceOptions` struct when creating your application: + +```go +app := application.New(application.Options{ + // ... other options ... + SingleInstance: &application.SingleInstanceOptions{ + UniqueID: "com.myapp.unique-id", + OnSecondInstanceLaunch: func(data application.SecondInstanceData) { + log.Printf("Second instance launched with args: %v", data.Args) + log.Printf("Working directory: %s", data.WorkingDir) + log.Printf("Additional data: %v", data.AdditionalData) + }, + // Optional: Pass additional data to second instance + AdditionalData: map[string]string{ + "launchtime": time.Now().String(), + }, + }, +}) +``` + +The `SingleInstanceOptions` struct has the following fields: + +- `UniqueID`: A unique identifier for your application. This should be a unique string, typically in reverse domain notation (e.g., "com.company.appname"). +- `EncryptionKey`: Optional 32-byte array for encrypting data passed between instances using AES-256-GCM. If provided as a non-zero array, all communication between instances will be encrypted. +- `OnSecondInstanceLaunch`: A callback function that is called when a second instance of your app is launched. The callback receives a `SecondInstanceData` struct containing: + - `Args`: The command line arguments passed to the second instance + - `WorkingDir`: The working directory of the second instance + - `AdditionalData`: Any additional data passed from the second instance (if provided) +- `AdditionalData`: Optional map of string key-value pairs that will be passed to the first instance when subsequent instances are launched + +:::danger[Warning] +The Single Instance feature implements an optional encryption protocol using AES-256-GCM. Without encryption enabled, +data passed between instances is not secure. When using the single instance feature without encryption, +your app should treat any data passed to it from second instance callback as untrusted. +You should verify that args that you receive are valid and don't contain any malicious data. +::: + +### Secure Communication + +To enable secure communication between instances, provide a 32-byte encryption key. This key must be the same for all instances of your application: + +```go +// Define your encryption key (must be exactly 32 bytes) +var encryptionKey = [32]byte{ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, +} + +// Use the key in SingleInstanceOptions +SingleInstance: &application.SingleInstanceOptions{ + UniqueID: "com.myapp.unique-id", + // Enable encryption for instance communication + EncryptionKey: encryptionKey, + // ... other options ... +} +``` + +:::tip[Security Best Practices] +- Use a unique key for your application +- Store the key securely if loading it from configuration +- Do not use the example key shown above - create your own! +::: + +### Window Management + +When handling second instance launches, you'll often want to bring your application window to the front. You can do this using the window's `Focus()` method. If your window is minimized, you may need to restore it first: + +```go + + var mainWindow *application.WebviewWindow + + SingleInstance: &application.SingleInstanceOptions{ + // Other options... + OnSecondInstanceLaunch: func(data application.SecondInstanceData) { + // Focus the window if needed + if mainWindow != nil { + mainWindow.Restore() + mainWindow.Focus() + } + }, + } +``` + +## How it works + + + + + Single instance lock using a named mutex. The mutex name is generated from the unique id that you provide. Data is passed to the first instance via [NSDistributedNotificationCenter](https://developer.apple.com/documentation/foundation/nsdistributednotificationcenter) + + + + + Single instance lock using a named mutex. The mutex name is generated from the unique id that you provide. Data is passed to the first instance via a shared window using [SendMessage](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendmessage) + + + + + Single instance lock using [dbus](https://www.freedesktop.org/wiki/Software/dbus/). The dbus name is generated from the unique id that you provide. Data is passed to the first instance via [dbus](https://www.freedesktop.org/wiki/Software/dbus/) + + + diff --git a/docs/src/content/docs/guides/testing.mdx b/docs/src/content/docs/guides/testing.mdx new file mode 100644 index 000000000..4bd3a07b8 --- /dev/null +++ b/docs/src/content/docs/guides/testing.mdx @@ -0,0 +1,175 @@ +--- +title: Testing +description: Test your Wails application +sidebar: + order: 5 +--- + +## Overview + +Testing ensures your application works correctly and prevents regressions. + +## Unit Testing + +### Testing Services + +```go +func TestUserService_Create(t *testing.T) { + service := &UserService{ + users: make(map[string]*User), + } + + user, err := service.Create("john@example.com", "password123") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if user.Email != "john@example.com" { + t.Errorf("expected email john@example.com, got %s", user.Email) + } +} +``` + +### Testing with Mocks + +```go +type MockDB struct { + users map[string]*User +} + +func (m *MockDB) Create(user *User) error { + m.users[user.ID] = user + return nil +} + +func TestUserService_WithMock(t *testing.T) { + mockDB := &MockDB{users: make(map[string]*User)} + service := &UserService{db: mockDB} + + user, err := service.Create("test@example.com", "pass") + if err != nil { + t.Fatal(err) + } + + if len(mockDB.users) != 1 { + t.Error("expected 1 user in mock") + } +} +``` + +## Integration Testing + +### Testing with Real Dependencies + +```go +func TestIntegration(t *testing.T) { + // Setup test database + db, err := sql.Open("sqlite3", ":memory:") + if err != nil { + t.Fatal(err) + } + defer db.Close() + + // Create schema + _, err = db.Exec(`CREATE TABLE users (...)`) + if err != nil { + t.Fatal(err) + } + + // Test service + service := &UserService{db: db} + user, err := service.Create("test@example.com", "password") + if err != nil { + t.Fatal(err) + } + + // Verify in database + var count int + db.QueryRow("SELECT COUNT(*) FROM users").Scan(&count) + if count != 1 { + t.Errorf("expected 1 user, got %d", count) + } +} +``` + +## Frontend Testing + +### JavaScript Unit Tests + +```javascript +// Using Vitest +import { describe, it, expect } from 'vitest' +import { formatDate } from './utils' + +describe('formatDate', () => { + it('formats date correctly', () => { + const date = new Date('2024-01-01') + expect(formatDate(date)).toBe('2024-01-01') + }) +}) +``` + +### Testing Bindings + +```javascript +import { vi } from 'vitest' +import { GetUser } from './bindings/myapp/userservice' + +// Mock the binding +vi.mock('./bindings/myapp/userservice', () => ({ + GetUser: vi.fn() +})) + +describe('User Component', () => { + it('loads user data', async () => { + GetUser.mockResolvedValue({ name: 'John', email: 'john@example.com' }) + + // Test your component + const user = await GetUser(1) + expect(user.name).toBe('John') + }) +}) +``` + +## Best Practices + +### ✅ Do + +- Write tests before fixing bugs +- Test edge cases +- Use table-driven tests +- Mock external dependencies +- Test error handling +- Keep tests fast + +### ❌ Don't + +- Don't skip error cases +- Don't test implementation details +- Don't write flaky tests +- Don't ignore test failures +- Don't skip integration tests + +## Running Tests + +```bash +# Run Go tests +go test ./... + +# Run with coverage +go test -cover ./... + +# Run specific test +go test -run TestUserService + +# Run frontend tests +cd frontend && npm test + +# Run with watch mode +cd frontend && npm test -- --watch +``` + +## Next Steps + +- [End-to-End Testing](/guides/e2e-testing) - Test complete user flows +- [Best Practices](/features/bindings/best-practices) - Learn best practices diff --git a/docs/src/content/docs/guides/windows-uac.mdx b/docs/src/content/docs/guides/windows-uac.mdx new file mode 100644 index 000000000..5bb380179 --- /dev/null +++ b/docs/src/content/docs/guides/windows-uac.mdx @@ -0,0 +1,151 @@ +--- +title: Windows UAC Configuration +sidebar: + order: 11 +--- + +import {Badge} from '@astrojs/starlight/components'; + +Relevant Platforms: +
      + +Windows User Account Control (UAC) determines the execution privileges of your Wails application. By default, Wails v3 applications include explicit UAC configuration in their Windows manifest, ensuring consistent behavior across different machines. + +## UAC Execution Levels + +Windows applications can request different execution levels through their manifest file. Wails v3 automatically includes UAC configuration with a default execution level that you can customize based on your application's needs. + +### Available Execution Levels + +| Level | Description | Use Case | +|-------|-------------|----------| +| `asInvoker` | Runs with the same privileges as the parent process | Default for most applications | +| `highestAvailable` | Runs with the highest privileges available to the user | Applications that may need elevated access | +| `requireAdministrator` | Always requires administrator privileges | System utilities, installers | + +### Default Configuration + +Wails v3 applications include a default UAC configuration in their Windows manifest: + +```xml + + + + + + + +``` + +This configuration ensures your application: +- Runs with the same privileges as the launching process +- Does not require elevation by default +- Works consistently across different machines +- Does not trigger UAC prompts for normal users + +## Customizing UAC Configuration + +Since Wails v3 encourages users to customize their build assets, you can modify the UAC configuration by editing your Windows manifest template directly. + +### Locating the Manifest Template + +The Windows manifest template is located at: +``` +build/windows/wails.exe.manifest +``` + +### Modifying the Execution Level + +To change the execution level, edit the `level` attribute in the `requestedExecutionLevel` element: + +```xml title="build/windows/wails.exe.manifest" + + + + + + + +``` + +### Examples + +#### Standard Application (Default) +Most applications should use the default `asInvoker` level: + +```xml + +``` + +#### System Utility +Applications that need elevated access when available: + +```xml + +``` + +#### Administrative Tool +Applications that always require administrator privileges: + +```xml + +``` + +## UI Access + +The `uiAccess` attribute controls whether your application can interact with higher-privilege UI elements. In most cases, this should remain `false`. + +Set to `true` only if your application needs to: +- Send input to other applications +- Drive the UI of other applications +- Access UI elements of higher-privilege processes + +:::caution[UI Access Requirements] +Setting `uiAccess="true"` requires your application to be: +- Digitally signed with a certificate from a trusted certificate authority +- Installed in a secure location (Program Files or Windows\System32) +::: + +## Building with Custom UAC Settings + +After modifying your manifest template, build your application normally: + +```bash +wails3 build +``` + +The build process will automatically embed your custom UAC configuration into the executable. + +## Verifying UAC Configuration + +You can verify that your UAC settings are properly embedded using the `go-winres` tool: + +```bash +go-winres extract --in your-app.exe --out extracted-resources/ +``` + +Then examine the extracted manifest file to confirm your UAC configuration is present. + +:::tip[Manifest Persistence] +Unlike some other frameworks, Wails v3's UAC configuration is embedded directly into the executable during compilation, ensuring it persists when the application is copied to other machines. +::: + +## Troubleshooting + +### UAC Prompts Not Appearing +If you set `requireAdministrator` but don't see UAC prompts: +- Verify the manifest is properly embedded in your executable +- Check that you're not running from an already-elevated process +- Ensure the manifest syntax is valid XML + +### Application Not Starting +If your application fails to start after UAC changes: +- Check the manifest syntax for XML errors +- Verify the execution level value is valid +- Try reverting to `asInvoker` to isolate the issue + +### Inconsistent Behavior Across Machines +If UAC behavior differs between machines: +- Ensure the manifest is embedded in the executable (not external) +- Check that the executable wasn't modified after building +- Verify Windows UAC settings are enabled on the target machine \ No newline at end of file diff --git a/docs/src/content/docs/index.mdx b/docs/src/content/docs/index.mdx new file mode 100644 index 000000000..c5f1c8790 --- /dev/null +++ b/docs/src/content/docs/index.mdx @@ -0,0 +1,294 @@ +--- +title: Build Desktop Apps with Go +description: "Native desktop applications using Go and web technologies" +banner: + content: | + Wails v3 is in ALPHA. v2 docs +template: splash +hero: + tagline: Build beautiful, performant desktop applications using Go and modern web technologies. One codebase. Three platforms. No browsers. + image: + dark: ../../assets/wails-logo-dark.svg + light: ../../assets/wails-logo-light.svg + alt: Wails Logo + actions: + - text: Get Started + link: /quick-start/installation + icon: right-arrow + variant: primary + - text: View Tutorial + link: /tutorials/03-notes-vanilla + icon: open-book + variant: secondary +--- +{/* Updated 2025-11-23 */} +import { Tabs, TabItem, Card, CardGrid } from "@astrojs/starlight/components"; + + + +## Quickstart + + + +```bash +# Install Wails +go install github.com/wailsapp/wails/v3/cmd/wails3@latest + +# Create your application +wails3 init -n myapp -t vanilla + +# Run with hot reload +cd myapp && wails3 dev +``` + + + +```powershell +# Install Wails +go install github.com/wailsapp/wails/v3/cmd/wails3@latest + +# Create your application +wails3 init -n myapp -t vanilla + +# Run with hot reload +cd myapp; wails3 dev +``` + + + +```bash +# Install Wails +go install github.com/wailsapp/wails/v3/cmd/wails3@latest + +# Create your application +wails3 init -n myapp -t vanilla + +# Run with hot reload +cd myapp && wails3 dev +``` + + + +**Your application is now running** with hot reload and type-safe Go-to-JS bindings. + + +## Why Wails? + + + + - ~15MB binaries vs Electron's 150MB + - ~10MB baseline memory vs 100MB+ + - <0.5s startup time vs 2-3s + - Native rendering using OS WebView + - No bundled browser overhead + + + + - One Go codebase for all platforms + - Any web framework - React, Vue, Svelte + - Hot reload during development + - Auto-generated bindings to easily call Go from Javascript + - In-memory IPC. No network ports + + + + - Multiple windows with lifecycles + - Native menus and system tray + - Platform-native file dialogs + - System integration and shortcuts + - Code signing and packaging tools + + + + - Single codebase for Windows, macOS, Linux + - Platform-specific features when needed + - No compromise on user experience + - Deploy to all platforms from one build + - Mobile coming soon... + + + + + + +## Next Steps + +Next: [Build a complete application](/tutorials/03-notes-vanilla), browse [examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples), or check the [API reference](/reference/application). Migrating from v2? See the [upgrade guide](/migration/v2-to-v3). + + +:::note[Production Ready] +Wails v3 is in **ALPHA**. The API is *reasonably* stable, and applications are running in production. We're refining documentation and tooling before the final release. +::: + + + Wails is free and open source, built by developers for developers. If Wails helps you build amazing applications, consider supporting its continued development. + + Your sponsorship helps maintain the project, improve documentation, and develop new features that benefit the entire community. + + [Become a Sponsor →](https://github.com/sponsors/leaanthony) + + diff --git a/docs/src/content/docs/migration/v2-to-v3.mdx b/docs/src/content/docs/migration/v2-to-v3.mdx new file mode 100644 index 000000000..5eb94ca67 --- /dev/null +++ b/docs/src/content/docs/migration/v2-to-v3.mdx @@ -0,0 +1,727 @@ +--- +title: Migrating from v2 to v3 +description: Complete guide to migrating your Wails v2 application to v3 +sidebar: + order: 1 +--- + +import { Card, CardGrid, Tabs, TabItem } from "@astrojs/starlight/components"; + +Wails v3 is a **complete rewrite** with significant improvements in architecture, performance, and developer experience. This guide helps you migrate your v2 application to v3. + +**Key changes:** +- New application structure +- Improved bindings system +- Enhanced window management +- Better event system +- Simplified configuration + +**Migration time:** 1-4 hours for typical applications + +## Breaking Changes + +### Application Initialisation + +In v2, application setup, window configuration, and execution were all combined into a single `wails.Run()` call. This monolithic approach made it difficult to create multiple windows, handle errors at different stages, or test individual components of your application. + +v3 separates these concerns into distinct phases: application creation, window creation, and execution. This separation gives you explicit control over each stage of your application's lifecycle and makes the code more modular and testable. + +**v2:** + +```go +err := wails.Run(&options.App{ + Title: "My App", + Width: 1024, + Height: 768, + Bind: []interface{}{ + &GreetService{}, + }, +}) +``` + +**v3:** + +```go +app := application.New(application.Options{ + Name: "My App", + Services: []application.Service{ + application.NewService(&GreetService{}), + }, +}) + +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "My App", + Width: 1024, + Height: 768, +}) + +app.Run() +``` + +**Why this is better:** + +- **Multi-window support**: You can create windows dynamically at any point, not just at startup +- **Better error handling**: Each phase can be validated separately with proper error handling +- **Clearer code**: The separation makes it obvious what's happening at each stage +- **More testable**: You can test application setup without running the event loop +- **More flexible**: Windows can be created, destroyed, and recreated throughout the application lifecycle + +### Bindings + +In v2, every bound struct required a context field and a `startup(ctx)` method to receive the runtime context. This created tight coupling between your business logic and the Wails runtime, making code harder to test and understand. + +v3 introduces the service pattern, where your structs are completely standalone and don't need to store runtime context. If a service needs access to the application instance, it explicitly receives it through dependency injection rather than implicit context threading. + +**v2:** + +```go +type App struct { + ctx context.Context +} + +func (a *App) startup(ctx context.Context) { + a.ctx = ctx +} + +func (a *App) Greet(name string) string { + return "Hello " + name +} +``` + +**v3:** + +```go +type GreetService struct{} + +func (g *GreetService) Greet(name string) string { + return "Hello " + name +} + +// Register as service +app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&GreetService{}), + }, +}) +``` + +**Why this is better:** + +- **No implicit dependencies**: Services are plain Go structs without hidden runtime dependencies +- **Easier testing**: You can test service methods without mocking a Wails context +- **Clearer code**: Dependencies are explicit (passed as constructor arguments) rather than hidden in a context field +- **Better organization**: Services can be grouped by domain rather than all living in a single `App` struct +- **Proper initialization**: Use `ServiceStartup()` method when you need initialization, making it explicit + +### Runtime + +In v2, all runtime operations required passing a context to global functions from the `runtime` package. This created tight coupling to the context object throughout your codebase and made the API feel procedural rather than object-oriented. + +v3 replaces the context-based runtime with direct method calls on application and window objects. Operations are called directly on the objects they affect, making the code more intuitive and object-oriented. + +**v2:** + +```go +import "github.com/wailsapp/wails/v2/pkg/runtime" + +runtime.WindowSetTitle(a.ctx, "New Title") +runtime.EventsEmit(a.ctx, "event-name", data) +``` + +**v3:** + +```go +// Store app reference +type MyService struct { + app *application.Application +} + +func (s *MyService) UpdateTitle() { + window := s.app.Window.Current() + window.SetTitle("New Title") +} + +func (s *MyService) EmitEvent() { + s.app.Event.Emit("event-name", data) +} +``` + +**Why this is better:** + +- **Object-oriented design**: Methods are called on the objects they affect (window, app, menu, etc.) +- **Clearer intent**: `window.SetTitle()` is more obvious than `runtime.WindowSetTitle(ctx, ...)` +- **Better IDE support**: Autocomplete works properly when methods are on objects +- **Multi-window clarity**: With multiple windows, you explicitly choose which window to operate on +- **No context threading**: You don't need to pass context through every function + +### Frontend Bindings + +In v2, bindings were organized by Go package and struct name, typically resulting in paths like `wailsjs/go/main/App`. This structure didn't reflect logical grouping and made it hard to find related functionality. + +v3 organizes bindings by service name and application module, creating a clearer logical structure. The bindings are generated into a `bindings` directory organized by your application name and service names, making it easier to understand what functionality is available. + +**v2:** + +```javascript +import { Greet } from '../wailsjs/go/main/App' + +const result = await Greet("World") +``` + +**v3:** + +```javascript +import { Greet } from './bindings/myapp/greetservice' + +const result = await Greet("World") +``` + +**Why this is better:** + +- **Logical organization**: Bindings are grouped by service name rather than Go package structure +- **Clearer imports**: The path reflects the domain logic (greetservice) not the file structure (main/App) +- **Better discoverability**: You can navigate bindings by feature rather than by technical structure +- **Consistent naming**: Service-based organization matches your backend architecture +- **Simpler paths**: No more `../wailsjs/go` prefix - just `./bindings` + +### Events + +In v2, events used variadic `interface{}` parameters and required passing context to every event function. Event handlers received untyped data that needed manual type assertions, making the event system error-prone and hard to debug. + +v3 introduces typed event objects and removes the context requirement. Event handlers receive a proper event object with typed data, making the event system more reliable and easier to use. + +**v2:** + +```go +runtime.EventsOn(ctx, "event-name", func(data ...interface{}) { + // Handle event +}) + +runtime.EventsEmit(ctx, "event-name", data) +``` + +**v3:** + +```go +app.Event.On("event-name", func(e *application.CustomEvent) { + data := e.Data + // Handle event +}) + +app.Event.Emit("event-name", data) +``` + +**Why this is better:** + +- **Type safety**: Events use proper event objects instead of `...interface{}` +- **Better debugging**: Event objects contain metadata like event name, making debugging easier +- **Clearer API**: `app.Event.On()` and `app.Event.Emit()` are more intuitive than runtime functions +- **No context needed**: Events work directly on the app object without threading context +- **Simpler handlers**: Event handlers have a clear signature instead of variadic parameters + +### Windows + +v2 supported only a single window per application. The window was created at startup and all window operations were performed through runtime functions that implicitly targeted that single window. + +v3 introduces native multi-window support as a core feature. Each window is a first-class object with its own methods and lifecycle. You can create, manage, and destroy multiple windows dynamically throughout your application's lifetime. + +**v2:** + +```go +// Single window only +runtime.WindowSetSize(ctx, 800, 600) +``` + +**v3:** + +```go +// Multiple windows supported +window1 := app.Window.New() +window1.SetSize(800, 600) + +window2 := app.Window.New() +window2.SetSize(1024, 768) +``` + +**Why this is better:** + +- **Multi-window applications**: Build apps with multiple independent windows (dashboards, preferences, tools, etc.) +- **Explicit window references**: Each window is an object you can store and manipulate directly +- **Dynamic window creation**: Create and destroy windows at any time during runtime +- **Independent window state**: Each window has its own events, properties, and lifecycle +- **Better architecture**: Window management is object-oriented rather than context-based + +## Migration Steps + +### Step 1: Update Dependencies + +**go.mod:** + +```go +module myapp + +go 1.21 + +require ( + github.com/wailsapp/wails/v3 v3.0.0-alpha.1 +) +``` + +**Update:** + +```bash +go get github.com/wailsapp/wails/v3@latest +go mod tidy +``` + +### Step 2: Update main.go + +**v2:** + +```go +package main + +import ( + "embed" + "github.com/wailsapp/wails/v2/pkg/options" + "github.com/wailsapp/wails/v2/pkg/options/assetserver" + "github.com/wailsapp/wails/v2/pkg/options/windows" +) + +//go:embed all:frontend/dist +var assets embed.FS + +func main() { + app := NewApp() + + err := wails.Run(&options.App{ + Title: "My App", + Width: 1024, + Height: 768, + AssetServer: &assetserver.Options{ + Assets: assets, + }, + Bind: []interface{}{ + app, + }, + Windows: &windows.Options{ + WebviewIsTransparent: false, + }, + }) + + if err != nil { + println("Error:", err.Error()) + } +} +``` + +**v3:** + +```go +package main + +import ( + "embed" + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed frontend/dist +var assets embed.FS + +func main() { + app := application.New(application.Options{ + Name: "My App", + Services: []application.Service{ + application.NewService(&MyService{}), + }, + Assets: application.AssetOptions{ + Handler: application.AssetFileServerFS(assets), + }, + }) + + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "My App", + Width: 1024, + Height: 768, + }) + + err := app.Run() + if err != nil { + panic(err) + } +} +``` + +### Step 3: Convert App Struct to Service + +**v2:** + +```go +type App struct { + ctx context.Context +} + +func NewApp() *App { + return &App{} +} + +func (a *App) startup(ctx context.Context) { + a.ctx = ctx + // Initialisation +} + +func (a *App) Greet(name string) string { + return "Hello " + name +} +``` + +**v3:** + +```go +type MyService struct { + app *application.Application +} + +func NewMyService(app *application.Application) *MyService { + return &MyService{app: app} +} + +func (s *MyService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + // Initialisation + return nil +} + +func (s *MyService) Greet(name string) string { + return "Hello " + name +} + +// Register after app creation +app := application.New(application.Options{}) +app.RegisterService(application.NewService(NewMyService(app))) +``` + +### Step 4: Update Runtime Calls + +**v2:** + +```go +func (a *App) DoSomething() { + runtime.WindowSetTitle(a.ctx, "New Title") + runtime.EventsEmit(a.ctx, "update", data) + runtime.LogInfo(a.ctx, "Message") +} +``` + +**v3:** + +```go +func (s *MyService) DoSomething() { + window := s.app.Window.Current() + window.SetTitle("New Title") + + s.app.Event.Emit("update", data) + + s.app.Logger.Info("Message") +} +``` + +### Step 5: Update Frontend + +**Generate new bindings:** + +```bash +wails3 generate bindings +``` + +**Update imports:** + +```javascript +// v2 +import { Greet } from '../wailsjs/go/main/App' + +// v3 +import { Greet } from './bindings/myapp/myservice' +``` + +**Update event handling:** + +```javascript +// v2 +import { EventsOn, EventsEmit } from '../wailsjs/runtime/runtime' + +EventsOn("update", (data) => { + console.log(data) +}) + +EventsEmit("action", data) + +// v3 +import { OnEvent, Emit } from '@wailsio/runtime' + +OnEvent("update", (data) => { + console.log(data) +}) + +Emit("action", data) +``` + +### Step 6: Update Configuration + +**v2 (wails.json):** + +```json +{ + "name": "myapp", + "outputfilename": "myapp", + "frontend:install": "npm install", + "frontend:build": "npm run build", + "frontend:dev:watcher": "npm run dev", + "frontend:dev:serverUrl": "auto" +} +``` + +**v3 (wails.json):** + +```json +{ + "name": "myapp", + "frontend": { + "dir": "./frontend", + "install": "npm install", + "build": "npm run build", + "dev": "npm run dev", + "devServerUrl": "http://localhost:5173" + } +} +``` + +## Feature Mapping + +### dialogs + +**v2:** + +```go +selection, err := runtime.OpenFileDialog(ctx, runtime.OpenDialogOptions{ + Title: "Select File", +}) +``` + +**v3:** + +```go +selection, err := app.Dialog.OpenFile(application.OpenFileDialogOptions{ + Title: "Select File", +}) +``` + +### Menus + +**v2:** + +```go +menu := menu.NewMenu() +menu.Append(menu.Text("File", nil, []*menu.MenuItem{ + menu.Text("Quit", nil, func(_ *menu.CallbackData) { + runtime.Quit(ctx) + }), +})) +``` + +**v3:** + +```go +menu := app.NewMenu() +fileMenu := menu.AddSubmenu("File") +fileMenu.Add("Quit").OnClick(func(ctx *application.Context) { + app.Quit() +}) +``` + +### System Tray + +**v2:** + +```go +// Not available in v2 +``` + +**v3:** + +```go +systray := app.SystemTray.New() +systray.SetIcon(iconBytes) +systray.SetLabel("My App") + +menu := app.NewMenu() +menu.Add("Show").OnClick(showWindow) +menu.Add("Quit").OnClick(app.Quit) +systray.SetMenu(menu) +``` + +## Common Issues + +### Issue: Bindings not found + +**Problem:** Import errors after migration + +**Solution:** + +```bash +# Regenerate bindings +wails3 generate bindings + +# Check output directory +ls frontend/bindings +``` + +### Issue: Context errors + +**Problem:** `ctx` not available + +**Solution:** + +Store app reference instead: + +```go +type MyService struct { + app *application.Application +} + +func NewMyService(app *application.Application) *MyService { + return &MyService{app: app} +} +``` + +### Issue: Window methods not working + +**Problem:** `runtime.WindowSetTitle()` doesn't exist + +**Solution:** + +Use window methods directly: + +```go +window := s.app.Window.Current() +window.SetTitle("New Title") +``` + +### Issue: Events not firing + +**Problem:** Events registered but not received + +**Solution:** + +Check event names match exactly: + +```go +// Go +app.Event.Emit("my-event", data) + +// JavaScript +OnEvent("my-event", handler) // Must match exactly +``` + +## Testing Migration + +### Checklist + +- [ ] Application starts without errors +- [ ] All bindings work +- [ ] Events are sent and received +- [ ] Windows open and close correctly +- [ ] Menus work (if applicable) +- [ ] dialogs work (if applicable) +- [ ] System tray works (if applicable) +- [ ] Build process works +- [ ] Production build works + +### Test Commands + +```bash +# Development +wails3 dev + +# Build +wails3 build + +# Generate bindings +wails3 generate bindings +``` + +## Benefits of v3 + +### Performance + +- **Faster startup** - Optimised initialisation +- **Lower memory** - Efficient resource usage +- **Better bridge** - <1ms call overhead + +### Features + +- **Multi-window** - Native support +- **System tray** - Built-in +- **Better events** - Typed, simpler API +- **Services** - Better code organisation + +### Developer Experience + +- **Type safety** - Full TypeScript support +- **Better errors** - Clear error messages +- **Hot reload** - Faster development +- **Better docs** - Comprehensive guides + +## Getting Help + +### Resources + +- [Documentation](/quick-start/why-wails) +- [Discord Community](https://discord.gg/JDdSxwjhGf) +- [GitHub Issues](https://github.com/wailsapp/wails/issues) +- [Examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples) + +### Common Questions + +**Q: Can I run v2 and v3 side by side?** +A: Yes, they use different import paths. + +**Q: Is v3 production-ready?** +A: v3 is in alpha/beta. Test thoroughly before production. + +**Q: Will v2 be maintained?** +A: Yes, v2 will receive critical updates. + +**Q: How long does migration take?** +A: 1-4 hours for typical applications. + +## Next Steps + + + + Get started with Wails v3. + + [Learn More →](/quick-start/installation) + + + + Understand v3 architecture. + + [Learn More →](/concepts/architecture) + + + + Learn the new bindings system. + + [Learn More →](/features/bindings/methods) + + + + See complete v3 examples. + + [View Examples →](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples) + + + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or [open an issue](https://github.com/wailsapp/wails/issues). diff --git a/docs/src/content/docs/quick-start/first-app.mdx b/docs/src/content/docs/quick-start/first-app.mdx new file mode 100644 index 000000000..204fed2f8 --- /dev/null +++ b/docs/src/content/docs/quick-start/first-app.mdx @@ -0,0 +1,270 @@ +--- +title: Your First App +description: Build a working Wails application in 10 minutes +sidebar: + order: 3 +--- + +import { Tabs, TabItem, Steps } from "@astrojs/starlight/components"; + +We will build a simple greeting application that demonstrates the core Wails concepts: +- Go backend managing logic +- Frontend calling Go functions +- Type-safe bindings +- Hot reload during development + +**Time to complete:** 10 minutes + +:::tip[Performance Tip for Windows 11 Users] +Consider using [Dev Drive](https://learn.microsoft.com/en-us/windows/dev-drive/) to store your projects. Dev Drives are optimized for developer workloads and can significantly improve build times and disk access speeds by up to 30% compared to regular NTFS drives. +::: + +## Create Your Project + + + +1. **Generate the project** + + ```bash + wails3 init -n myapp + cd myapp + ``` + + This creates a new project with the default Vanilla + Vite template (plain HTML/CSS/JS with Vite bundler). + + :::tip[Other Templates] + Try `-t react`, `-t vue`, or `-t svelte` for your preferred framework. + Run `wails3 init -l` to see all available templates. + ::: + +2. **Understand the project structure** + + ``` + myapp/ + ├── main.go # Application entry point + ├── greetservice.go # Greet service + ├── frontend/ # Your UI code + │ ├── index.html # HTML entry point + │ ├── src/ + │ │ └── main.js # Frontend JavaScript + │ ├── public/ + │ │ └── style.css # Styles + │ ├── package.json # Frontend dependencies + │ └── vite.config.js # Vite bundler config + ├── build/ # Build configuration + └── Taskfile.yml # Build tasks + ``` + +3. **Run the app** + + ```bash + wails3 dev + ``` + + :::note[First Run] + The first run may take longer than expected as it installs frontend dependencies, generates bindings, etc. Subsequent runs are much faster. + ::: + + The app opens showing a greeting interface. Enter your name and click "Greet" - the Go backend processes your input and returns a greeting. + + + +## How It Works + +Let's understand the code that makes this work. + +### The Go Backend + +Open `greetservice.go`: + +```go title="greetservice.go" +package main + +import ( + "fmt" +) + +type GreetService struct{} + +func (g *GreetService) Greet(name string) string { + return fmt.Sprintf("Hello %s, It's show time!", name) +} +``` + +**Key concepts:** + +1. **Service** - A Go struct with exported methods +2. **Exported method** - `Greet` is capitalized, making it available to the frontend +3. **Simple logic** - Takes a name, returns a greeting +4. **Type safety** - Input and output types are defined + +:::tip[Understanding Services and Bindings] +**Services** are self-contained Go modules that expose functionality to your frontend. They're just regular Go structs with exported methods that you register in the `Services` field of your application config. + +**Bindings** are the auto-generated TypeScript/JavaScript SDK that lets your frontend call these services. When you run `wails3 dev` or `wails3 build`, Wails analyzes your registered services and generates type-safe bindings in `frontend/bindings/`. + +Think of services as your backend API, and bindings as the client library that talks to it. +::: + +### Registering the Service + +Open `main.go` and find the service registration: + +```go title="main.go" {4-6} +err := application.New(application.Options{ + Name: "myapp", + Services: []application.Service{ + application.NewService(&GreetService{}), + }, + // ... other options +}) +``` + +This registers your `GreetService` with Wails, making all its exported methods available to the frontend. + +### The Frontend + +Open `frontend/src/main.js`: + +```javascript title="frontend/src/main.js" +import {GreetService} from "../bindings/changeme"; + +window.greet = async () => { + const nameElement = document.getElementById('name'); + const resultElement = document.getElementById('result'); + + const name = nameElement.value; + if (!name) { + return; + } + + try { + const result = await GreetService.Greet(name); + resultElement.innerText = result; + } catch (err) { + console.error(err); + } +}; +``` + +**Key concepts:** + +1. **Auto-generated bindings** - `GreetService` is imported from generated code +2. **Type-safe calls** - Method names and signatures match your Go code +3. **Async by default** - All Go calls return Promises +4. **Error handling** - Errors from Go are caught in try/catch + +:::note[Where are the bindings?] +Generated bindings are in `frontend/bindings/`. They're created automatically when you run `wails3 dev` or `wails3 build`. + +**Never edit these files manually**—they're regenerated on every build. +::: + +## Customize Your App + +Let's add a new feature to understand the workflow. + +### Add a "Greet Many" Feature + + + +1. **Add the method to GreetService** + + Add this to `greetservice.go`: + + ```go title="greetservice.go" + func (g *GreetService) GreetMany(names []string) []string { + greetings := make([]string, len(names)) + for i, name := range names { + greetings[i] = fmt.Sprintf("Hello %s!", name) + } + return greetings + } + ``` + +2. **The app will auto-rebuild** + + Save the file and `wails3 dev` will automatically rebuild your Go code and restart the app. + + :::note[Auto-Rebuild] + Go code changes trigger an automatic rebuild and restart. Frontend changes hot-reload without restart. + ::: + +3. **Use it in the frontend** + + Add this to `frontend/src/main.js`: + + ```javascript title="frontend/src/main.js" + window.greetMany = async () => { + const names = ['Alice', 'Bob', 'Charlie']; + const greetings = await GreetService.GreetMany(names); + console.log(greetings); + }; + ``` + + Open the browser console and call `greetMany()` - you'll see the array of greetings. + + + +## Build for Production + +When you're ready to distribute your app: + +```bash +wails3 build +``` + +**What this does:** +- Compiles Go code with optimizations +- Builds frontend for production (minified) +- Creates a native executable in `build/bin/` + + + + **Output:** `build/bin/myapp.exe` + + Double-click to run. No dependencies needed (WebView2 is part of Windows). + + + + **Output:** `build/bin/myapp.app` + + Drag to Applications folder or double-click to run. + + + + **Output:** `build/bin/myapp` + + Run with `./build/bin/myapp` or create a `.desktop` file for your launcher. + + + +:::tip[Cross-Platform Builds] +Want to build for other platforms? See [Cross-Platform Builds →](/guides/build/cross-platform) +::: + +## What we've learned + +**Project Structure** +- `main.go` for Go backend +- `frontend/` for UI code +- `Taskfile.yml` for build tasks + +**Services** +- Create Go structs with exported methods +- Register with `application.NewService()` +- Methods automatically available in frontend + +**Bindings** +- Auto-generated TypeScript definitions +- Type-safe function calls +- Async by default (Promises) + +**Development Workflow** +- `wails3 dev` for hot reload +- Go changes auto-rebuild and restart +- Frontend changes hot-reload instantly + +--- + +**Questions?** Join [Discord](https://discord.gg/JDdSxwjhGf) and ask the community. diff --git a/docs/src/content/docs/quick-start/installation.mdx b/docs/src/content/docs/quick-start/installation.mdx new file mode 100644 index 000000000..20bdc51ba --- /dev/null +++ b/docs/src/content/docs/quick-start/installation.mdx @@ -0,0 +1,402 @@ +--- +title: Installation +description: Get Wails installed and ready to build applications +sidebar: + order: 2 +--- + +import { Card, CardGrid, Tabs, TabItem, Steps } from "@astrojs/starlight/components"; + +## Quick Install (5 Minutes) + +:::tip[TL;DR - Experienced Developers] +```bash +# Install Go 1.25+, then: +go install github.com/wailsapp/wails/v3/cmd/wails3@latest +wails3 doctor # Verify installation +``` + +If `wails3 doctor` passes, you're done. [Skip to First App →](/quick-start/first-app) +::: + +## Step-by-Step Installation + + + +1. **Install Go (Required)** + + Wails requires Go 1.25 or later. + + + + Download the Windows installer from **[go.dev/dl](https://go.dev/dl/)** and run it. + + **Verify installation:** + ```powershell + go version # Should show 1.25 or later + ``` + + **Check PATH:** + ```powershell + $env:PATH -split ';' | Where-Object { $_ -like '*\go\bin' } + ``` + + If empty, add `C:\Users\YourName\go\bin` to your PATH. + + + + **Option 1: Official Installer** + + Download the macOS installer (.pkg file) from **[go.dev/dl](https://go.dev/dl/)** and run it. + + **Option 2: Homebrew** + ```bash + brew install go + ``` + + **Verify installation:** + ```bash + go version # Should show 1.25 or later + echo $PATH | grep go/bin # Should show ~/go/bin + ``` + + If `~/go/bin` isn't in PATH, add to `~/.zshrc` or `~/.bash_profile`: + ```bash + export PATH=$PATH:~/go/bin + ``` + + + + **Option 1: Official Tarball** + + Download the Linux tarball from **[go.dev/dl](https://go.dev/dl/)**, then: + ```bash + sudo rm -rf /usr/local/go + sudo tar -C /usr/local -xzf go1.25.linux-amd64.tar.gz + ``` + + **Option 2: Package Manager** + ```bash + # Ubuntu/Debian + sudo apt install golang-go + + # Fedora + sudo dnf install golang + + # Arch + sudo pacman -S go + ``` + + **Add to PATH** (add to `~/.bashrc` or `~/.zshrc`): + ```bash + export PATH=$PATH:/usr/local/go/bin:~/go/bin + source ~/.bashrc # Reload + ``` + + **Verify:** + ```bash + go version + echo $PATH | grep go/bin + ``` + + + +2. **Install Platform Dependencies** + + + + **WebView2 Runtime** (usually pre-installed) + + Windows 10/11 includes WebView2 by default. If missing: + - Download from [Microsoft](https://developer.microsoft.com/microsoft-edge/webview2/) + - Or run `wails3 doctor` later—it will guide you + + **That's it!** No other dependencies needed. + + :::tip[Performance Tip for Windows 11] + Consider using [Dev Drive](https://learn.microsoft.com/en-us/windows/dev-drive/) to store your projects. Dev Drives are optimized for developer workloads and can significantly improve build times and disk access speeds by up to 30%. + ::: + + + + **Xcode Command Line Tools** (required) + + ```bash + xcode-select --install + ``` + + Click "Install" in the dialog that appears. + + **Verify:** + ```bash + xcode-select -p # Should show /Library/Developer/CommandLineTools + ``` + + **That's it!** macOS includes WebKit by default. + + + + **Build tools and WebKit** + + + + ```bash + sudo apt update + sudo apt install build-essential pkg-config libgtk-3-dev libwebkit2gtk-4.0-dev + ``` + + + + ```bash + sudo dnf install gcc pkg-config gtk3-devel webkit2gtk4.0-devel + ``` + + + + ```bash + sudo pacman -S base-devel gtk3 webkit2gtk + ``` + + + + Run `wails3 doctor` after installing Wails—it will show the exact packages needed for your distribution. + + + + + +3. **Install Wails CLI** + + ```bash + go install github.com/wailsapp/wails/v3/cmd/wails3@latest + ``` + + This installs the `wails3` command to `~/go/bin` (or `%USERPROFILE%\go\bin` on Windows). + +4. **Verify Installation** + + ```bash + wails3 doctor + ``` + + **Expected output (or similar):** + ``` + Wails (v3.0.0-dev) Wails Doctor + + # System + + ┌──────────────────────────────────────────────────┐ + | Name | MacOS | + | Version | 26.0 | + | ID | 25A354 | + | Branding | MacOS 26.0 | + | Platform | darwin | + | Architecture | arm64 | + | Apple Silicon | true | + | CPU | Apple M2 Pro | + | CPU 1 | Apple M2 Pro | + | CPU 2 | Apple M2 Pro | + | GPU | 16 cores, Metal Support: Metal 4 | + | Memory | 16 GB | + └──────────────────────────────────────────────────┘ + + # Build Environment + + ┌─────────────┬─────────────────┐ + | Wails CLI | v3.0.0-alpha.40 | + | Go Version | go1.24.6 | + └─────────────┴─────────────────┘ + + # Dependencies + + ┌─────────────────┬─────────────────────────────────────────────────┐ + | npm | 11.6.2 | + | *NSIS | Not Installed. Install with `brew install...`. | + | Xcode cli tools | 2412 | + └─────────────────┴─────────────────────────────────────────────────┘ + + # Checking for issues + + SUCCESS No issues found + + # Diagnosis + + SUCCESS Your system is ready for Wails development! + ``` + + :::note[If `wails3` command not found] + Your `~/go/bin` isn't in PATH. See step 1 above to fix this, then restart your terminal. + ::: + +5. **Install npm (Optional but Recommended)** + + Most Wails templates use npm for frontend tooling. + + + + Download from [nodejs.org](https://nodejs.org/) and run the installer. + + **Verify:** + ```powershell + npm --version + ``` + + + + **Option 1: Official Installer** + Download from [nodejs.org](https://nodejs.org/) + + **Option 2: Homebrew** + ```bash + brew install node + ``` + + **Verify:** + ```bash + npm --version + ``` + + + + **Option 1: NodeSource** + ```bash + curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash - + sudo apt-get install -y nodejs # Ubuntu/Debian + ``` + + **Option 2: Package Manager** + ```bash + sudo dnf install nodejs # Fedora + sudo pacman -S nodejs npm # Arch + ``` + + **Verify:** + ```bash + npm --version + ``` + + + + :::tip[Alternative Package Managers] + Prefer `pnpm`, `yarn`, or `bun`? No problem! Just update the `Taskfile.yml` in your project to use your preferred tool. + ::: + + + +## Troubleshooting + +#### `wails3` command not found + +**Cause:** `~/go/bin` (or `%USERPROFILE%\go\bin`) isn't in your PATH. + +**Solution:** + + + + 1. Open "Environment Variables" (search in Start menu) + 2. Under "User variables", find `Path` + 3. Click "Edit" → "New" + 4. Add: `C:\Users\YourName\go\bin` (replace `YourName`) + 5. Click "OK" on all dialogs + 6. **Restart your terminal** + + **Verify:** + ```powershell + $env:PATH -split ';' | Where-Object { $_ -like '*\go\bin' } + ``` + + + + Add to `~/.zshrc` (macOS) or `~/.bashrc` (Linux): + ```bash + export PATH=$PATH:~/go/bin + ``` + + Reload: + ```bash + source ~/.zshrc # or ~/.bashrc + ``` + + **Verify:** + ```bash + echo $PATH | grep go/bin + wails3 version + ``` + + +--- + +#### `wails3 doctor` reports missing dependencies + +**Linux:** The output tells you exactly which packages to install. Example: +``` +❌ webkit2gtk not found + Install with: sudo apt install libwebkit2gtk-4.0-dev +``` + +**Windows:** If WebView2 is missing: +- Download from [Microsoft](https://developer.microsoft.com/microsoft-edge/webview2/) +- Or it will be installed automatically when you run your first app + +**macOS:** If Xcode tools are missing: +```bash +xcode-select --install +``` +--- +#### Go version too old + +Wails v3 requires Go 1.25+. If you have an older version: + + + + Download the latest from [go.dev/dl](https://go.dev/dl/) and reinstall. + + + + Download the latest tarball from [go.dev/dl](https://go.dev/dl/), then: + ```bash + sudo rm -rf /usr/local/go + sudo tar -C /usr/local -xzf go1.25.linux-amd64.tar.gz + ``` + + + +## Development Version (Bleeding Edge) + +Want to use the absolute latest code from the main development branch? This gives you access to new features and fixes before they're released, but comes with the risk of bugs and breaking changes. Only recommended for contributors or those who need to test upcoming features. + +```bash +git clone https://github.com/wailsapp/wails.git +cd wails +git checkout v3 +cd v3/cmd/wails3 +go install +``` + +:::caution[Development Version] +- May have bugs or breaking changes +- Projects created will use `replace` directive to point to local Wails +- Only recommended for contributors or testing new features +::: + +## Next Steps + +**Installation Complete!** Your system is ready for Wails development. + + + Create a working application in 10 minutes. + + [First App Tutorial →](/quick-start/first-app) + + + + See what's available out of the box. + + ```bash + wails3 init -l # List templates + ``` + + +--- + +**Having issues?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or [open an issue](https://github.com/wailsapp/wails/issues). diff --git a/docs/src/content/docs/quick-start/next-steps.mdx b/docs/src/content/docs/quick-start/next-steps.mdx new file mode 100644 index 000000000..394d8e69a --- /dev/null +++ b/docs/src/content/docs/quick-start/next-steps.mdx @@ -0,0 +1,34 @@ +--- +title: Next Steps +description: Where to go after building your first app +sidebar: + order: 4 +--- + +You've built your first Wails app and understand the basics. Here's where to go next. + +## Learn More + +Practice by building complete applications: + +- [TODO App](/tutorials/02-todo-vanilla) - CRUD operations, state management, modern UI +- [All Tutorials](/tutorials/overview) - Notes apps, dashboards, system tray apps, and more + +## Features + +Wails provides native desktop capabilities: + +- [Windows](/features/windows/basics) - Multiple windows, frameless windows, positioning +- [Menus](/features/menus/application) - Application menus, context menus, system tray +- [Dialogs](/features/dialogs/file) - File open/save, message dialogs +- [Events](/features/events/system) - Communication between components +- [Bindings](/features/bindings/methods) - Type-safe Go ↔ JavaScript calls +- [Clipboard](/features/clipboard) - Copy/paste operations +- [Drag & Drop](/features/drag-and-drop/files) - File drag and drop +- [Keyboard](/features/keyboard) - Global shortcuts + +## Get Help + +- [Discord](https://discord.gg/JDdSxwjhGf) - Ask questions, share projects +- [Examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples) - 50+ working examples +- [API Reference](/reference/overview) - Complete documentation diff --git a/docs/src/content/docs/quick-start/why-wails.mdx b/docs/src/content/docs/quick-start/why-wails.mdx new file mode 100644 index 000000000..a99c299d2 --- /dev/null +++ b/docs/src/content/docs/quick-start/why-wails.mdx @@ -0,0 +1,133 @@ +--- +title: Why Wails? +description: Understand why Wails is the right choice for your desktop application +sidebar: + order: 1 +--- + + +Wails combines **Go's performance and simplicity** with **modern web UI flexibility**, enabling you to build beautiful, native desktop applications with the tools you already know. + +## Performance That Users Notice + +**Wails Applications:** +- **~15MB binaries** (vs Electron's 150MB) +- **~10MB baseline memory** (vs Electron's 100MB+) +- **<0.5s startup time** (vs Electron's 2-3s) +- **Native rendering** using the OS-provided WebView + +Users perceive your application as fast, lightweight, and professional. + +## Developer Experience + +**Write Once, Run Everywhere:** +- One Go codebase for Windows, macOS, and Linux +- Use any web framework (React, Vue, Svelte, vanilla JS) +- Hot reload during development +- TypeScript bindings auto-generated from Go code + +Ship faster with less code to maintain. + +## Production-Ready Features + +**Everything you need:** +- Multiple windows with independent lifecycles +- Native menus (application, context, system tray) +- File dialogs with platform-native UI +- System integration (notifications, clipboard, keyboard shortcuts) +- Code signing and packaging for all platforms + +Build professional applications, not prototypes. + +## Faster Development + +- **One codebase, three platforms** - Write once, build for Windows, macOS, and Linux +- **Use existing skills** - Go for backend, HTML/CSS/JS for UI +- **Instant feedback** - Hot reload during development, compile times measured in seconds +- **Small binaries** - 15MB apps mean faster builds, faster downloads, faster iteration + +## When to Choose Wails + +**Wails is Perfect For:** + +- **Business applications** (CRM, inventory, dashboards, admin tools) +- **Developer tools** (database clients, API testers, deployment tools) +- **Productivity apps** (note-taking, task managers, time trackers) +- **Creative tools** (image editors, video processors, design utilities) +- **Internal tools** (company-specific applications, automation tools) + +## Real-World Success Stories + +:::tip[Production Applications] +Wails powers real applications used by thousands of users: +- **Database management tools** with complex UIs +- **Financial dashboards** processing real-time data +- **Video editing tools** with native performance +- **Development utilities** used by engineering teams + +[See the showcase →](/community/showcase) +::: + +## How Wails Works + +Unlike Electron which bundles an entire browser and Node.js runtime, Wails takes a fundamentally different approach: your Go code compiles to a native binary, and your UI runs in the operating system's built-in WebView. This architecture delivers the small binaries, fast startup, and low memory usage that make Wails applications feel native. + +### Architecture + +Wails applications consist of two main parts that communicate seamlessly: a Go backend handling business logic and system operations, and a web-based frontend for your user interface. The OS-provided WebView renders your UI without bundling a browser, while the bindings layer provides type-safe communication between Go and JavaScript. + +
      +
      + +```d2 +direction: down + +Application: { +Go: "Go Backend" { + shape: rectangle + style.fill: "#00ADD8" +} +WebView: "Native WebView" { + shape: rectangle + style.fill: "#3B82F6" + + Frontend: "Your UI\n(React/Vue/etc)" { + shape: rectangle + style.fill: "#8B5CF6" + } + +} +Go <-> WebView: "Bindings & Events" +} + + + +``` + +
      +
      + +This simple architecture enables JavaScript code to call Go functions directly (through auto-generated bindings), while Go can send events and data back to the frontend. Both layers communicate through an efficient in-memory bridge with sub-millisecond overhead. + +**How Wails achieves performance:** +1. **No runtime bundled** - Uses Go's compiled binary +2. **Native WebView** - OS-provided rendering engine +3. **Direct Go ↔ JS bridge** - In-memory communication, no network overhead +4. **Compiled binary** - Instant startup, no JIT compilation + +## Next Steps + +Now that you understand what Wails provides, let's get you set up: + +1. **Install Wails** - Set up your development environment in 5 minutes + [Installation Guide →](/quick-start/installation) + +2. **Build Your First App** - Create a working application and understand the basics + [First App Tutorial →](/quick-start/first-app) + +3. **Explore Features** - Discover what Wails can do for your application + [Feature Overview →](/quick-start/next-steps) + +--- + +**Still have questions?** Join our [Discord community](https://discord.gg/JDdSxwjhGf) and ask the team directly. diff --git a/docs/src/content/docs/reference/application.mdx b/docs/src/content/docs/reference/application.mdx new file mode 100644 index 000000000..cab3a279d --- /dev/null +++ b/docs/src/content/docs/reference/application.mdx @@ -0,0 +1,489 @@ +--- +title: Application API +description: Complete reference for the Application API +sidebar: + order: 1 +--- + +import { Card, CardGrid } from "@astrojs/starlight/components"; + +## Overview + +The `Application` is the core of your Wails app. It manages windows, services, events, and provides access to all platform features. + +## Creating an Application + +```go +import "github.com/wailsapp/wails/v3/pkg/application" + +app := application.New(application.Options{ + Name: "My App", + Description: "My awesome application", + Services: []application.Service{ + application.NewService(&MyService{}), + }, +}) +``` + +## Core Methods + +### Run() + +Starts the application event loop. + +```go +func (a *App) Run() error +``` + +**Example:** + +```go +err := app.Run() +if err != nil { + log.Fatal(err) +} +``` + +**Returns:** Error if startup fails + +### Quit() + +Gracefully shuts down the application. + +```go +func (a *App) Quit() +``` + +**Example:** + +```go +// In a menu handler +menu.Add("Quit").OnClick(func(ctx *application.Context) { + app.Quit() +}) +``` + +### Config() + +Returns the application configuration. + +```go +func (a *App) Config() Options +``` + +**Example:** + +```go +config := app.Config() +fmt.Println("App name:", config.Name) +``` + +## Window Management + +### app.Window.New() + +Creates a new webview window with default options. + +```go +func (wm *WindowManager) New() *WebviewWindow +``` + +**Example:** + +```go +window := app.Window.New() +window.Show() +``` + +### app.Window.NewWithOptions() + +Creates a new webview window with custom options. + +```go +func (wm *WindowManager) NewWithOptions(options WebviewWindowOptions) *WebviewWindow +``` + +**Example:** + +```go +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "My Window", + Width: 800, + Height: 600, + BackgroundColour: application.NewRGB(255, 255, 255), +}) +``` + +### app.Window.GetByName() + +Gets a window by its name. + +```go +func (wm *WindowManager) GetByName(name string) Window +``` + +**Example:** + +```go +window := app.Window.GetByName("main") +if window != nil { + window.Show() +} +``` + +### app.Window.GetAll() + +Returns all application windows. + +```go +func (wm *WindowManager) GetAll() []Window +``` + +**Example:** + +```go +windows := app.Window.GetAll() +for _, window := range windows { + fmt.Println("Window:", window.Name()) +} +``` + +## Managers + +The Application provides access to various managers through properties: + +```go +app.Window // Window management +app.Menu // Menu management +app.Dialog // dialog management +app.Event // Event management +app.Clipboard // Clipboard operations +app.Screen // Screen information +app.SystemTray // System tray +app.Browser // Browser operations +app.Env // Environment variables +``` + +### Example Usage + +```go +// Create window +window := app.Window.New() + +// Show dialog +app.Dialog.Info().SetMessage("Hello!").Show() + +// Copy to clipboard +app.Clipboard.SetText("Copied text") + +// Get screens +screens := app.Screen.GetAll() +``` + +## Service Management + +### RegisterService() + +Registers a service with the application. + +```go +func (a *App) RegisterService(service Service) error +``` + +**Example:** + +```go +type MyService struct { + app *application.Application +} + +func NewMyService(app *application.Application) *MyService { + return &MyService{app: app} +} + +// Register after app creation +app.RegisterService(application.NewService(NewMyService(app))) +``` + +## Event Management + +### app.Event.Emit() + +Emits a custom event. + +```go +func (em *EventManager) Emit(name string, data ...interface{}) +``` + +**Example:** + +```go +// Emit event with data +app.Event.Emit("user-logged-in", map[string]interface{}{ + "username": "john", + "timestamp": time.Now(), +}) +``` + +### app.Event.On() + +Listens for custom events. + +```go +func (em *EventManager) On(name string, callback func(*CustomEvent)) +``` + +**Example:** + +```go +app.Event.On("user-logged-in", func(e *application.CustomEvent) { + data := e.Data.(map[string]interface{}) + username := data["username"].(string) + fmt.Println("User logged in:", username) +}) +``` + +### app.Event.OnApplicationEvent() + +Listens for application lifecycle events. + +```go +func (em *EventManager) OnApplicationEvent(eventType ApplicationEventType, callback func(*ApplicationEvent)) func() +``` + +**Example:** + +```go +// Listen for shutdown +app.Event.OnApplicationEvent(application.EventApplicationShutdown, func(e *application.ApplicationEvent) { + fmt.Println("Application shutting down") + // Cleanup +}) +``` + +## Dialog Methods + +Dialogs are accessed through the `app.Dialog` manager. See [Dialogs API](/reference/dialogs/) for complete reference. + +### Message Dialogs + +```go +// Information dialog +app.Dialog.Info(). + SetTitle("Success"). + SetMessage("Operation completed!"). + Show() + +// Error dialog +app.Dialog.Error(). + SetTitle("Error"). + SetMessage("Something went wrong."). + Show() + +// Warning dialog +app.Dialog.Warning(). + SetTitle("Warning"). + SetMessage("This action cannot be undone."). + Show() +``` + +### Question Dialogs + +Question dialogs use button callbacks to handle user responses: + +```go +dialog := app.Dialog.Question(). + SetTitle("Confirm"). + SetMessage("Continue?") + +yes := dialog.AddButton("Yes") +yes.OnClick(func() { + // Handle yes +}) + +no := dialog.AddButton("No") +no.OnClick(func() { + // Handle no +}) + +dialog.SetDefaultButton(yes) +dialog.SetCancelButton(no) +dialog.Show() +``` + +### File Dialogs + +```go +// Open file dialog +path, err := app.Dialog.OpenFile(). + SetTitle("Select File"). + AddFilter("Images", "*.png;*.jpg"). + PromptForSingleSelection() + +// Save file dialog +path, err := app.Dialog.SaveFile(). + SetTitle("Save File"). + SetFilename("document.pdf"). + AddFilter("PDF", "*.pdf"). + PromptForSingleSelection() + +// Folder selection (use OpenFile with directory options) +path, err := app.Dialog.OpenFile(). + SetTitle("Select Folder"). + CanChooseDirectories(true). + CanChooseFiles(false). + PromptForSingleSelection() +``` + +## Logger + +The application provides a structured logger: + +```go +app.Logger.Info("Message", "key", "value") +app.Logger.Error("Error occurred", "error", err) +app.Logger.Debug("Debug info") +app.Logger.Warn("Warning message") +``` + +**Example:** + +```go +func (s *MyService) ProcessData(data string) error { + s.app.Logger.Info("Processing data", "length", len(data)) + + if err := process(data); err != nil { + s.app.Logger.Error("Processing failed", "error", err) + return err + } + + s.app.Logger.Info("Processing complete") + return nil +} +``` + +## Raw Message Handling + +For applications that need direct, low-level control over frontend-to-backend communication, Wails provides the `RawMessageHandler` option. This bypasses the standard binding system. + +:::note +Raw messages should only be used as a last resort. The standard binding system is highly optimized and sufficient for almost all applications. Only use raw messages if you have profiled your application and confirmed that bindings are a bottleneck. +::: + +### RawMessageHandler + +The `RawMessageHandler` callback receives raw messages sent from the frontend using `System.invoke()`. + +```go +func (a *App) RawMessageHandler(window Window, message string) +``` + +**Example:** + +```go +app := application.New(application.Options{ + Name: "My App", + RawMessageHandler: func(window application.Window, message string) { + // Handle the raw message + fmt.Printf("Received from %s: %s\n", window.Name(), message) + + // You can respond using events + window.EmitEvent("response", processMessage(message)) + }, +}) +``` + + +For more details, see the [Raw Messages Guide](/guides/raw-messages). + +## Platform-Specific Options + +### Windows Options + +Configure Windows-specific behavior at the application level: + +```go +app := application.New(application.Options{ + Name: "My App", + Windows: application.WindowsOptions{ + // WebView2 browser flags (apply to ALL windows) + EnabledFeatures: []string{"msWebView2EnableDraggableRegions"}, + DisabledFeatures: []string{"msExperimentalFeature"}, + AdditionalBrowserArgs: []string{"--remote-debugging-port=9222"}, + + // Other Windows options + WndClass: "MyAppClass", + WebviewUserDataPath: "", // Default: %APPDATA%\[BinaryName.exe] + WebviewBrowserPath: "", // Default: system WebView2 + DisableQuitOnLastWindowClosed: false, + }, +}) +``` + +**Browser Flags:** +- `EnabledFeatures` - WebView2 feature flags to enable +- `DisabledFeatures` - WebView2 feature flags to disable +- `AdditionalBrowserArgs` - Chromium command-line arguments + +See [Window Options - Application-Level Windows Options](/features/windows/options#application-level-windows-options) for detailed documentation. + +### Mac Options + +```go +app := application.New(application.Options{ + Name: "My App", + Mac: application.MacOptions{ + ActivationPolicy: application.ActivationPolicyRegular, + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, +}) +``` + +### Linux Options + +```go +app := application.New(application.Options{ + Name: "My App", + Linux: application.LinuxOptions{ + ProgramName: "my-app", + DisableQuitOnLastWindowClosed: false, + }, +}) +``` + +## Complete Application Example + +```go +package main + +import ( + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Name: "My Application", + Description: "A demo application", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + + // Create main window + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "My App", + Width: 1024, + Height: 768, + MinWidth: 800, + MinHeight: 600, + BackgroundColour: application.NewRGB(255, 255, 255), + URL: "http://wails.localhost/", + }) + + window.Centre() + window.Show() + + app.Run() +} +``` + diff --git a/docs/src/content/docs/reference/cli.mdx b/docs/src/content/docs/reference/cli.mdx new file mode 100644 index 000000000..9f37ff6bf --- /dev/null +++ b/docs/src/content/docs/reference/cli.mdx @@ -0,0 +1,28 @@ +--- +title: CLI Reference +description: Complete reference for the Wails CLI commands +sidebar: + order: 1 +--- + +## Overview + +The Wails CLI (`wails3`) provides commands for creating, developing, building, and managing Wails applications. + +## Coming Soon + +This section is under construction. For now, use `wails3 --help` or `wails3 [command] --help` for command documentation. + +**Common commands:** + +```bash +wails3 init # Create a new project +wails3 dev # Run in development mode +wails3 build # Build for production +wails3 generate # Generate bindings or runtime +wails3 doctor # Check development environment +``` + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples). diff --git a/docs/src/content/docs/reference/dialogs.mdx b/docs/src/content/docs/reference/dialogs.mdx new file mode 100644 index 000000000..799c453b8 --- /dev/null +++ b/docs/src/content/docs/reference/dialogs.mdx @@ -0,0 +1,844 @@ +--- +title: Dialogs API +description: Complete reference for native dialog APIs +sidebar: + order: 5 +--- + +import { Card, CardGrid } from "@astrojs/starlight/components"; + +## Overview + +The Dialogs API provides methods to show native file dialogs and message dialogs. Access dialogs through the `app.Dialog` manager. + +**Dialog Types:** +- **File Dialogs** - Open and Save dialogs +- **Message Dialogs** - Info, Error, Warning, and Question dialogs + +All dialogs are **native OS dialogs** that match the platform's look and feel. + +## Accessing Dialogs + +Dialogs are accessed through the `app.Dialog` manager: + +```go +app.Dialog.OpenFile() +app.Dialog.SaveFile() +app.Dialog.Info() +app.Dialog.Question() +app.Dialog.Warning() +app.Dialog.Error() +``` + +## File Dialogs + +### OpenFile() + +Creates a file open dialog. + +```go +func (dm *DialogManager) OpenFile() *OpenFileDialogStruct +``` + +**Example:** +```go +dialog := app.Dialog.OpenFile() +``` + +### OpenFileDialogStruct Methods + +#### SetTitle() + +Sets the dialog title. + +```go +func (d *OpenFileDialogStruct) SetTitle(title string) *OpenFileDialogStruct +``` + +**Example:** +```go +dialog.SetTitle("Select Image") +``` + +#### AddFilter() + +Adds a file type filter. + +```go +func (d *OpenFileDialogStruct) AddFilter(displayName, pattern string) *OpenFileDialogStruct +``` + +**Parameters:** +- `displayName` - Filter description shown to user (e.g., "Images", "Documents") +- `pattern` - Semicolon-separated list of extensions (e.g., "*.png;*.jpg") + +**Example:** +```go +dialog.AddFilter("Images", "*.png;*.jpg;*.gif"). + AddFilter("Documents", "*.pdf;*.docx"). + AddFilter("All Files", "*.*") +``` + +#### SetDirectory() + +Sets the initial directory. + +```go +func (d *OpenFileDialogStruct) SetDirectory(directory string) *OpenFileDialogStruct +``` + +**Example:** +```go +homeDir, _ := os.UserHomeDir() +dialog.SetDirectory(homeDir) +``` + +#### CanChooseDirectories() + +Enables or disables directory selection. + +```go +func (d *OpenFileDialogStruct) CanChooseDirectories(canChooseDirectories bool) *OpenFileDialogStruct +``` + +**Example (folder selection):** +```go +// Select folders instead of files +path, err := app.Dialog.OpenFile(). + SetTitle("Select Folder"). + CanChooseDirectories(true). + CanChooseFiles(false). + PromptForSingleSelection() +``` + +#### CanChooseFiles() + +Enables or disables file selection. + +```go +func (d *OpenFileDialogStruct) CanChooseFiles(canChooseFiles bool) *OpenFileDialogStruct +``` + +#### CanCreateDirectories() + +Enables or disables creating new directories. + +```go +func (d *OpenFileDialogStruct) CanCreateDirectories(canCreateDirectories bool) *OpenFileDialogStruct +``` + +#### ShowHiddenFiles() + +Shows or hides hidden files. + +```go +func (d *OpenFileDialogStruct) ShowHiddenFiles(showHiddenFiles bool) *OpenFileDialogStruct +``` + +#### AttachToWindow() + +Attaches the dialog to a specific window. + +```go +func (d *OpenFileDialogStruct) AttachToWindow(window Window) *OpenFileDialogStruct +``` + +#### PromptForSingleSelection() + +Shows the dialog and returns the selected file. + +```go +func (d *OpenFileDialogStruct) PromptForSingleSelection() (string, error) +``` + +**Returns:** +- `string` - Selected file path (empty if cancelled) +- `error` - Error if dialog failed + +**Example:** +```go +path, err := app.Dialog.OpenFile(). + SetTitle("Select Image"). + AddFilter("Images", "*.png;*.jpg;*.gif"). + PromptForSingleSelection() + +if err != nil { + // User cancelled or error occurred + return +} + +// Use the selected file +processFile(path) +``` + +#### PromptForMultipleSelection() + +Shows the dialog and returns multiple selected files. + +```go +func (d *OpenFileDialogStruct) PromptForMultipleSelection() ([]string, error) +``` + +**Returns:** +- `[]string` - Array of selected file paths +- `error` - Error if dialog failed + +**Example:** +```go +paths, err := app.Dialog.OpenFile(). + SetTitle("Select Images"). + AddFilter("Images", "*.png;*.jpg"). + PromptForMultipleSelection() + +if err != nil { + return +} + +for _, path := range paths { + processFile(path) +} +``` + +### SaveFile() + +Creates a file save dialog. + +```go +func (dm *DialogManager) SaveFile() *SaveFileDialogStruct +``` + +**Example:** +```go +dialog := app.Dialog.SaveFile() +``` + +### SaveFileDialogStruct Methods + +#### SetTitle() + +Sets the dialog title. + +```go +func (d *SaveFileDialogStruct) SetTitle(title string) *SaveFileDialogStruct +``` + +#### SetFilename() + +Sets the default filename. + +```go +func (d *SaveFileDialogStruct) SetFilename(filename string) *SaveFileDialogStruct +``` + +**Example:** +```go +dialog.SetFilename("document.pdf") +``` + +#### AddFilter() + +Adds a file type filter. + +```go +func (d *SaveFileDialogStruct) AddFilter(displayName, pattern string) *SaveFileDialogStruct +``` + +**Example:** +```go +dialog.AddFilter("PDF Document", "*.pdf"). + AddFilter("Text Document", "*.txt") +``` + +#### SetDirectory() + +Sets the initial directory. + +```go +func (d *SaveFileDialogStruct) SetDirectory(directory string) *SaveFileDialogStruct +``` + +#### AttachToWindow() + +Attaches the dialog to a specific window. + +```go +func (d *SaveFileDialogStruct) AttachToWindow(window Window) *SaveFileDialogStruct +``` + +#### PromptForSingleSelection() + +Shows the dialog and returns the save path. + +```go +func (d *SaveFileDialogStruct) PromptForSingleSelection() (string, error) +``` + +**Example:** +```go +path, err := app.Dialog.SaveFile(). + SetTitle("Save Document"). + SetFilename("untitled.pdf"). + AddFilter("PDF Document", "*.pdf"). + PromptForSingleSelection() + +if err != nil { + // User cancelled + return +} + +// Save to the selected path +saveDocument(path) +``` + +### Folder Selection + +There is no separate `SelectFolderDialog`. Use `OpenFile()` with directory options: + +```go +path, err := app.Dialog.OpenFile(). + SetTitle("Select Output Folder"). + CanChooseDirectories(true). + CanChooseFiles(false). + PromptForSingleSelection() + +if err != nil { + // User cancelled + return +} + +// Use the selected folder +outputDir = path +``` + +## Message Dialogs + +All message dialogs return `*MessageDialog` and share the same methods. + +### Info() + +Creates an information dialog. + +```go +func (dm *DialogManager) Info() *MessageDialog +``` + +**Example:** +```go +app.Dialog.Info(). + SetTitle("Success"). + SetMessage("File saved successfully!"). + Show() +``` + +### Error() + +Creates an error dialog. + +```go +func (dm *DialogManager) Error() *MessageDialog +``` + +**Example:** +```go +app.Dialog.Error(). + SetTitle("Error"). + SetMessage("Failed to save file: " + err.Error()). + Show() +``` + +### Warning() + +Creates a warning dialog. + +```go +func (dm *DialogManager) Warning() *MessageDialog +``` + +**Example:** +```go +app.Dialog.Warning(). + SetTitle("Warning"). + SetMessage("This action cannot be undone."). + Show() +``` + +### Question() + +Creates a question dialog with custom buttons. + +```go +func (dm *DialogManager) Question() *MessageDialog +``` + +**Example:** +```go +dialog := app.Dialog.Question(). + SetTitle("Confirm"). + SetMessage("Do you want to save changes?") + +save := dialog.AddButton("Save") +save.OnClick(func() { + saveDocument() +}) + +dontSave := dialog.AddButton("Don't Save") +dontSave.OnClick(func() { + // Continue without saving +}) + +cancel := dialog.AddButton("Cancel") +cancel.OnClick(func() { + // Do nothing +}) + +dialog.SetDefaultButton(save) +dialog.SetCancelButton(cancel) +dialog.Show() +``` + +### MessageDialog Methods + +#### SetTitle() + +Sets the dialog title. + +```go +func (d *MessageDialog) SetTitle(title string) *MessageDialog +``` + +#### SetMessage() + +Sets the dialog message. + +```go +func (d *MessageDialog) SetMessage(message string) *MessageDialog +``` + +#### SetIcon() + +Sets a custom icon for the dialog. + +```go +func (d *MessageDialog) SetIcon(icon []byte) *MessageDialog +``` + +#### AddButton() + +Adds a button to the dialog and returns the button for configuration. + +```go +func (d *MessageDialog) AddButton(label string) *Button +``` + +**Returns:** `*Button` - The button instance for further configuration + +**Example:** +```go +button := dialog.AddButton("OK") +button.OnClick(func() { + // Handle click +}) +``` + +#### SetDefaultButton() + +Sets which button is the default (activated by pressing Enter). + +```go +func (d *MessageDialog) SetDefaultButton(button *Button) *MessageDialog +``` + +**Example:** +```go +yes := dialog.AddButton("Yes") +no := dialog.AddButton("No") +dialog.SetDefaultButton(yes) +``` + +#### SetCancelButton() + +Sets which button is the cancel button (activated by pressing Escape). + +```go +func (d *MessageDialog) SetCancelButton(button *Button) *MessageDialog +``` + +**Example:** +```go +ok := dialog.AddButton("OK") +cancel := dialog.AddButton("Cancel") +dialog.SetCancelButton(cancel) +``` + +#### AttachToWindow() + +Attaches the dialog to a specific window. + +```go +func (d *MessageDialog) AttachToWindow(window Window) *MessageDialog +``` + +#### Show() + +Shows the dialog. Button callbacks handle user responses. + +```go +func (d *MessageDialog) Show() +``` + +**Note:** `Show()` does not return a value. Use button callbacks to handle user responses. + +### Button Methods + +#### OnClick() + +Sets the callback function for when the button is clicked. + +```go +func (b *Button) OnClick(callback func()) *Button +``` + +#### SetAsDefault() + +Marks this button as the default button. + +```go +func (b *Button) SetAsDefault() *Button +``` + +#### SetAsCancel() + +Marks this button as the cancel button. + +```go +func (b *Button) SetAsCancel() *Button +``` + +## Complete Examples + +### File Selection Example + +```go +type FileService struct { + app *application.App +} + +func (s *FileService) OpenImage() (string, error) { + path, err := s.app.Dialog.OpenFile(). + SetTitle("Select Image"). + AddFilter("Images", "*.png;*.jpg;*.jpeg;*.gif"). + AddFilter("All Files", "*.*"). + PromptForSingleSelection() + + if err != nil { + return "", err + } + + return path, nil +} + +func (s *FileService) SaveDocument(defaultName string) (string, error) { + path, err := s.app.Dialog.SaveFile(). + SetTitle("Save Document"). + SetFilename(defaultName). + AddFilter("PDF Document", "*.pdf"). + AddFilter("Text Document", "*.txt"). + PromptForSingleSelection() + + if err != nil { + return "", err + } + + return path, nil +} + +func (s *FileService) SelectOutputFolder() (string, error) { + path, err := s.app.Dialog.OpenFile(). + SetTitle("Select Output Folder"). + CanChooseDirectories(true). + CanChooseFiles(false). + PromptForSingleSelection() + + if err != nil { + return "", err + } + + return path, nil +} +``` + +### Confirmation Dialog Example + +```go +func (s *Service) DeleteItem(app *application.App, id string) { + dialog := app.Dialog.Question(). + SetTitle("Confirm Delete"). + SetMessage("Are you sure you want to delete this item?") + + deleteBtn := dialog.AddButton("Delete") + deleteBtn.OnClick(func() { + deleteFromDatabase(id) + }) + + cancelBtn := dialog.AddButton("Cancel") + // Cancel does nothing + + dialog.SetDefaultButton(cancelBtn) // Default to Cancel for safety + dialog.SetCancelButton(cancelBtn) + dialog.Show() +} +``` + +### Save Changes Dialog + +```go +func (s *Editor) PromptSaveChanges(app *application.App) { + dialog := app.Dialog.Question(). + SetTitle("Unsaved Changes"). + SetMessage("Do you want to save your changes before closing?") + + save := dialog.AddButton("Save") + save.OnClick(func() { + s.Save() + s.Close() + }) + + dontSave := dialog.AddButton("Don't Save") + dontSave.OnClick(func() { + s.Close() + }) + + cancel := dialog.AddButton("Cancel") + // Cancel does nothing, dialog closes + + dialog.SetDefaultButton(save) + dialog.SetCancelButton(cancel) + dialog.Show() +} +``` + +### Multi-File Processing + +```go +func (s *Service) ProcessMultipleFiles(app *application.App) error { + // Select multiple files + paths, err := app.Dialog.OpenFile(). + SetTitle("Select Files to Process"). + AddFilter("Images", "*.png;*.jpg"). + PromptForMultipleSelection() + + if err != nil { + return err + } + + if len(paths) == 0 { + app.Dialog.Info(). + SetTitle("No Files Selected"). + SetMessage("Please select at least one file."). + Show() + return nil + } + + // Process files + for _, path := range paths { + err := processFile(path) + if err != nil { + app.Dialog.Error(). + SetTitle("Processing Error"). + SetMessage(fmt.Sprintf("Failed to process %s: %v", path, err)). + Show() + continue + } + } + + // Show completion + app.Dialog.Info(). + SetTitle("Complete"). + SetMessage(fmt.Sprintf("Successfully processed %d files", len(paths))). + Show() + + return nil +} +``` + +### Error Handling with Dialogs + +```go +func (s *Service) SaveFile(app *application.App, data []byte) error { + // Select save location + path, err := app.Dialog.SaveFile(). + SetTitle("Save File"). + SetFilename("data.json"). + AddFilter("JSON File", "*.json"). + PromptForSingleSelection() + + if err != nil { + // User cancelled - not an error + return nil + } + + // Attempt to save + err = os.WriteFile(path, data, 0644) + if err != nil { + // Show error dialog + app.Dialog.Error(). + SetTitle("Save Failed"). + SetMessage(fmt.Sprintf("Could not save file: %v", err)). + Show() + return err + } + + // Show success + app.Dialog.Info(). + SetTitle("Success"). + SetMessage("File saved successfully!"). + Show() + + return nil +} +``` + +### Platform-Specific Defaults + +```go +import ( + "os" + "path/filepath" + "runtime" +) + +func (s *Service) GetDefaultDirectory() string { + homeDir, _ := os.UserHomeDir() + + switch runtime.GOOS { + case "windows": + return filepath.Join(homeDir, "Documents") + case "darwin": + return filepath.Join(homeDir, "Documents") + case "linux": + return filepath.Join(homeDir, "Documents") + default: + return homeDir + } +} + +func (s *Service) OpenWithDefaults(app *application.App) (string, error) { + return app.Dialog.OpenFile(). + SetTitle("Open File"). + SetDirectory(s.GetDefaultDirectory()). + AddFilter("All Files", "*.*"). + PromptForSingleSelection() +} +``` + +## Best Practices + +### Do + +- **Use native dialogs** - They match the platform's look and feel +- **Provide clear titles** - Help users understand the purpose +- **Set appropriate filters** - Guide users to correct file types +- **Handle cancellation** - Check for errors (user may cancel) +- **Show confirmation for destructive actions** - Use Question dialogs +- **Provide feedback** - Use Info dialogs for success messages +- **Set sensible defaults** - Default directory, filename, etc. +- **Use callbacks for button actions** - Handle user responses properly + +### Don't + +- **Don't ignore errors** - User cancellation returns an error +- **Don't use ambiguous button labels** - Be specific: "Save"/"Cancel" +- **Don't overuse dialogs** - They interrupt workflow +- **Don't show errors for cancellation** - It's a normal action +- **Don't forget file filters** - Help users find the right files +- **Don't hardcode paths** - Use os.UserHomeDir() or similar + +## Dialog Types by Platform + +### macOS + +- Dialogs slide down from title bar +- "Sheet" style attached to parent window +- Native macOS appearance + +### Windows + +- Standard Windows dialogs +- Follows Windows design guidelines +- Modern Windows 10/11 appearance + +### Linux + +- GTK dialogs on GTK-based systems +- Qt dialogs on Qt-based systems +- Matches desktop environment + +#### Linux GTK4 Limitations + +On Linux with GTK4 (the default for modern distributions), file dialogs use the **xdg-desktop-portal** +for native integration. This provides better desktop integration but means some options have no effect: + +| Option | GTK3 | GTK4 | Notes | +|--------|------|------|-------| +| `ShowHiddenFiles()` | ✅ Works | ❌ No effect | User controls via dialog's UI toggle (Ctrl+H or menu) | +| `CanCreateDirectories()` | ✅ Works | ❌ No effect | Always enabled in the portal | +| `ResolvesAliases()` | ✅ Works | ❌ No effect | Portal handles symlink resolution | +| `SetButtonText()` | ✅ Works | ✅ Works | Custom accept button text works | + +**Why these limitations exist:** GTK4's portal-based dialogs delegate UI control to the desktop +environment (GNOME, KDE, etc.). This is by design - the portal provides consistent UX across +applications and respects user preferences. + +:::note +The table above shows GTK4 behavior when building with `-tags gtk4`. By default, Wails uses GTK3 +which provides full control over these dialog options. +::: + +## Common Patterns + +### "Save As" Pattern + +```go +func (s *Service) SaveAs(app *application.App, currentPath string) (string, error) { + // Extract filename from current path + filename := filepath.Base(currentPath) + + // Show save dialog + path, err := app.Dialog.SaveFile(). + SetTitle("Save As"). + SetFilename(filename). + PromptForSingleSelection() + + if err != nil { + return "", err + } + + return path, nil +} +``` + +### "Open Recent" Pattern + +```go +func (s *Service) OpenRecent(app *application.App, recentPath string) error { + // Check if file still exists + if _, err := os.Stat(recentPath); os.IsNotExist(err) { + dialog := app.Dialog.Question(). + SetTitle("File Not Found"). + SetMessage("The file no longer exists. Remove from recent files?") + + remove := dialog.AddButton("Remove") + remove.OnClick(func() { + s.removeFromRecent(recentPath) + }) + + cancel := dialog.AddButton("Cancel") + dialog.SetCancelButton(cancel) + dialog.Show() + + return err + } + + return s.openFile(recentPath) +} +``` diff --git a/docs/src/content/docs/reference/events.mdx b/docs/src/content/docs/reference/events.mdx new file mode 100644 index 000000000..d6286018e --- /dev/null +++ b/docs/src/content/docs/reference/events.mdx @@ -0,0 +1,801 @@ +--- +title: Events API +description: Complete reference for the Events API +sidebar: + order: 4 +--- + +import { Card, CardGrid } from "@astrojs/starlight/components"; + +## Overview + +The Events API provides methods to emit and listen to events, enabling communication between different parts of your application. + +**Event Types:** +- **Application Events** - App lifecycle events (startup, shutdown) +- **Window Events** - Window state changes (focus, blur, resize) +- **Custom Events** - User-defined events for app-specific communication + +**Communication Patterns:** +- **Go to Frontend** - Emit events from Go, listen in JavaScript +- **Frontend to Go** - Not directly (use service bindings instead) +- **Frontend to Frontend** - Via Go or local runtime events +- **Window to Window** - Target specific windows or broadcast to all + +## Event Methods (Go) + +### app.Event.Emit() + +Emits a custom event to all windows. + +```go +func (em *EventManager) Emit(name string, data ...interface{}) +``` + +**Parameters:** +- `name` - Event name +- `data` - Optional data to send with the event + +**Example:** +```go +// Emit simple event +app.Event.Emit("user-logged-in") + +// Emit with data +app.Event.Emit("data-updated", map[string]interface{}{ + "count": 42, + "status": "success", +}) + +// Emit multiple values +app.Event.Emit("progress", 75, "Processing files...") +``` + +### app.Event.On() + +Listens for custom events in Go. + +```go +func (em *EventManager) On(name string, callback func(*CustomEvent)) func() +``` + +**Parameters:** +- `name` - Event name to listen for +- `callback` - Function called when event is emitted + +**Returns:** Cleanup function to remove the event listener + +**Example:** +```go +// Listen for events +cleanup := app.Event.On("user-action", func(e *application.CustomEvent) { + data := e.Data.(map[string]interface{}) + action := data["action"].(string) + app.Logger.Info("User action", "action", action) +}) + +// Later, remove listener +cleanup() +``` + +### Window-Specific Events + +Emit events to a specific window: + +```go +// Emit to specific window +window.EmitEvent("notification", "Hello from Go!") + +// Emit to all windows +app.Event.Emit("global-update", data) +``` + +## Event Methods (Frontend) + +### On() + +Listens for events from Go. + +```javascript +import { On } from '@wailsio/runtime' + +On(eventName, callback) +``` + +**Parameters:** +- `eventName` - Name of the event to listen for +- `callback` - Function called when event is received + +**Returns:** Cleanup function + +**Example:** +```javascript +import { On } from '@wailsio/runtime' + +// Listen for events +const cleanup = On('data-updated', (data) => { + console.log('Count:', data.count) + console.log('Status:', data.status) + updateUI(data) +}) + +// Later, remove listener +cleanup() +``` + +### Once() + +Listens for a single event occurrence. + +```javascript +import { Once } from '@wailsio/runtime' + +Once(eventName, callback) +``` + +**Example:** +```javascript +import { Once } from '@wailsio/runtime' + +// Listen for first occurrence only +Once('initialization-complete', (data) => { + console.log('App initialized!', data) + // This will only fire once +}) +``` + +### Off() + +Removes an event listener. + +```javascript +import { Off } from '@wailsio/runtime' + +Off(eventName, callback) +``` + +**Example:** +```javascript +import { On, Off } from '@wailsio/runtime' + +const handler = (data) => { + console.log('Event received:', data) +} + +// Start listening +On('my-event', handler) + +// Stop listening +Off('my-event', handler) +``` + +### OffAll() + +Removes all listeners for an event. + +```javascript +import { OffAll } from '@wailsio/runtime' + +OffAll(eventName) +``` + +**Example:** +```javascript +// Remove all listeners for this event +OffAll('data-updated') +``` + +## Application Events + +### app.Event.OnApplicationEvent() + +Listens for application lifecycle events. + +```go +func (em *EventManager) OnApplicationEvent(eventType ApplicationEventType, callback func(*ApplicationEvent)) func() +``` + +**Event Types:** +- `EventApplicationStarted` - Application has started +- `EventApplicationShutdown` - Application is shutting down +- `EventApplicationDebug` - Debug event (dev mode only) + +**Example:** +```go +// Handle application startup +app.Event.OnApplicationEvent(application.EventApplicationStarted, func(e *application.ApplicationEvent) { + app.Logger.Info("Application started") + // Initialize resources +}) + +// Handle application shutdown +app.Event.OnApplicationEvent(application.EventApplicationShutdown, func(e *application.ApplicationEvent) { + app.Logger.Info("Application shutting down") + // Cleanup resources, save state + database.Close() + saveSettings() +}) +``` + +## Window Events + +### OnWindowEvent() + +Listens for window-specific events. + +```go +func (w *Window) OnWindowEvent(eventType WindowEventType, callback func(*WindowEvent)) func() +``` + +**Event Types:** +- `EventWindowFocus` - Window gained focus +- `EventWindowBlur` - Window lost focus +- `EventWindowClose` - Window is closing +- `EventWindowResize` - Window was resized +- `EventWindowMove` - Window was moved + +**Example:** +```go +// Handle window focus +window.OnWindowEvent(application.EventWindowFocus, func(e *application.WindowEvent) { + app.Logger.Info("Window focused") +}) + +// Handle window resize +window.OnWindowEvent(application.EventWindowResize, func(e *application.WindowEvent) { + width, height := window.Size() + app.Logger.Info("Window resized", "width", width, "height", height) +}) +``` + +## Common Patterns + +These patterns demonstrate proven approaches for using events in real-world applications. Each pattern solves a specific communication challenge between your Go backend and frontend, helping you build responsive, well-structured applications. + +### Request/Response Pattern + +Use this when you want to notify the frontend about the completion of backend operations, such as after data fetching, file processing, or background tasks. The service binding returns data directly, while events provide additional notifications for UI updates like showing toast messages or refreshing lists. + +**Go:** + +```go +// Service method +type DataService struct { + app *application.Application +} + +func (s *DataService) FetchData(query string) ([]Item, error) { + items := fetchFromDatabase(query) + + // Emit event when done + s.app.Event.Emit("data-fetched", map[string]interface{}{ + "query": query, + "count": len(items), + }) + + return items, nil +} +``` + +**JavaScript:** + +```javascript +import { FetchData } from './bindings/DataService' +import { On } from '@wailsio/runtime' + +// Listen for completion event +On('data-fetched', (data) => { + console.log(`Fetched ${data.count} items for query: ${data.query}`) + showNotification(`Found ${data.count} results`) +}) + +// Call service method +const items = await FetchData("search term") +displayItems(items) +``` + +### Progress Updates + +Ideal for long-running operations like file uploads, batch processing, large data imports, or video encoding. Emit progress events during the operation to update progress bars, status text, or step indicators in the UI, providing users with real-time feedback. + +**Go:** + +```go +func (s *Service) ProcessFiles(files []string) error { + total := len(files) + + for i, file := range files { + // Process file + processFile(file) + + // Emit progress event + s.app.Event.Emit("progress", map[string]interface{}{ + "current": i + 1, + "total": total, + "percent": float64(i+1) / float64(total) * 100, + "file": file, + }) + } + + s.app.Event.Emit("processing-complete") + return nil +} +``` + +**JavaScript:** + +```javascript +import { On, Once } from '@wailsio/runtime' + +// Update progress bar +On('progress', (data) => { + progressBar.style.width = `${data.percent}%` + statusText.textContent = `Processing ${data.file}... (${data.current}/${data.total})` +}) + +// Handle completion +Once('processing-complete', () => { + progressBar.style.width = '100%' + statusText.textContent = 'Complete!' + setTimeout(() => hideProgressBar(), 2000) +}) +``` + +### Multi-Window Communication + +Perfect for applications with multiple windows like settings panels, dashboards, or document viewers. Broadcast events to synchronize state across all windows (theme changes, user preferences) or send targeted events to specific windows for window-specific updates. + +**Go:** + +```go +// Broadcast to all windows +app.Event.Emit("theme-changed", "dark") + +// Send to specific window +preferencesWindow.EmitEvent("settings-updated", settings) + +// Window-specific listener +window1.OnEvent("request-data", func(e *application.CustomEvent) { + // Only this window will receive this event + window1.EmitEvent("data-response", data) +}) +``` + +**JavaScript:** + +```javascript +import { On } from '@wailsio/runtime' + +// Listen in any window +On('theme-changed', (theme) => { + document.body.className = theme +}) +``` + +### State Synchronization + +Use when you need to keep frontend and backend state in sync, such as user sessions, application configuration, or collaborative features. When state changes on the backend, emit events to update all connected frontends, ensuring consistency across your application. + +**Go:** + +```go +type StateService struct { + app *application.Application + state map[string]interface{} + mu sync.RWMutex +} + +func (s *StateService) UpdateState(key string, value interface{}) { + s.mu.Lock() + s.state[key] = value + s.mu.Unlock() + + // Notify all windows + s.app.Event.Emit("state-updated", map[string]interface{}{ + "key": key, + "value": value, + }) +} + +func (s *StateService) GetState(key string) interface{} { + s.mu.RLock() + defer s.mu.RUnlock() + return s.state[key] +} +``` + +**JavaScript:** + +```javascript +import { On } from '@wailsio/runtime' +import { GetState } from './bindings/StateService' + +// Keep local state in sync +let localState = {} + +On('state-updated', async (data) => { + localState[data.key] = data.value + updateUI(data.key, data.value) +}) + +// Initialize state +const initialState = await GetState("all") +localState = initialState +``` + +### Event-Driven Notifications + +Best for displaying user feedback like success confirmations, error alerts, or info messages. Instead of calling UI code directly from services, emit notification events that the frontend handles consistently, making it easy to change notification styles or add features like notification history. + +**Go:** + +```go +type NotificationService struct { + app *application.Application +} + +func (s *NotificationService) Success(message string) { + s.app.Event.Emit("notification", map[string]interface{}{ + "type": "success", + "message": message, + }) +} + +func (s *NotificationService) Error(message string) { + s.app.Event.Emit("notification", map[string]interface{}{ + "type": "error", + "message": message, + }) +} + +func (s *NotificationService) Info(message string) { + s.app.Event.Emit("notification", map[string]interface{}{ + "type": "info", + "message": message, + }) +} +``` + +**JavaScript:** + +```javascript +import { On } from '@wailsio/runtime' + +// Unified notification handler +On('notification', (data) => { + const toast = document.createElement('div') + toast.className = `toast toast-${data.type}` + toast.textContent = data.message + + document.body.appendChild(toast) + + setTimeout(() => { + toast.classList.add('fade-out') + setTimeout(() => toast.remove(), 300) + }, 3000) +}) +``` + +## Complete Example + +**Go:** + +```go +package main + +import ( + "sync" + "time" + "github.com/wailsapp/wails/v3/pkg/application" +) + +type EventDemoService struct { + app *application.Application + mu sync.Mutex +} + +func NewEventDemoService(app *application.Application) *EventDemoService { + service := &EventDemoService{app: app} + + // Listen for custom events + app.Event.On("user-action", func(e *application.CustomEvent) { + data := e.Data.(map[string]interface{}) + app.Logger.Info("User action received", "data", data) + }) + + return service +} + +func (s *EventDemoService) StartLongTask() { + go func() { + s.app.Event.Emit("task-started") + + for i := 1; i <= 10; i++ { + time.Sleep(500 * time.Millisecond) + + s.app.Event.Emit("task-progress", map[string]interface{}{ + "step": i, + "total": 10, + "percent": i * 10, + }) + } + + s.app.Event.Emit("task-completed", map[string]interface{}{ + "message": "Task finished successfully!", + }) + }() +} + +func (s *EventDemoService) BroadcastMessage(message string) { + s.app.Event.Emit("broadcast", message) +} + +func main() { + app := application.New(application.Options{ + Name: "Event Demo", + }) + + // Handle application lifecycle + app.Event.OnApplicationEvent(application.EventApplicationStarted, func(e *application.ApplicationEvent) { + app.Logger.Info("Application started!") + }) + + app.Event.OnApplicationEvent(application.EventApplicationShutdown, func(e *application.ApplicationEvent) { + app.Logger.Info("Application shutting down...") + }) + + // Register service + service := NewEventDemoService(app) + app.RegisterService(application.NewService(service)) + + // Create window + window := app.Window.New() + + // Handle window events + window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) { + window.EmitEvent("window-state", "focused") + }) + + window.OnWindowEvent(events.Common.WindowLostFocus, func(e *application.WindowEvent) { + window.EmitEvent("window-state", "blurred") + }) + + window.Show() + app.Run() +} +``` + +**JavaScript:** + +```javascript +import { On, Once } from '@wailsio/runtime' +import { StartLongTask, BroadcastMessage } from './bindings/EventDemoService' + +// Task events +On('task-started', () => { + console.log('Task started...') + document.getElementById('status').textContent = 'Running...' +}) + +On('task-progress', (data) => { + const progressBar = document.getElementById('progress') + progressBar.style.width = `${data.percent}%` + console.log(`Step ${data.step} of ${data.total}`) +}) + +Once('task-completed', (data) => { + console.log('Task completed!', data.message) + document.getElementById('status').textContent = data.message +}) + +// Broadcast events +On('broadcast', (message) => { + console.log('Broadcast:', message) + alert(message) +}) + +// Window state events +On('window-state', (state) => { + console.log('Window is now:', state) + document.body.dataset.windowState = state +}) + +// Trigger long task +document.getElementById('startTask').addEventListener('click', async () => { + await StartLongTask() +}) + +// Send broadcast +document.getElementById('broadcast').addEventListener('click', async () => { + const message = document.getElementById('message').value + await BroadcastMessage(message) +}) +``` + +## Built-in Events + +Wails provides built-in system events for application and window lifecycle. These events are emitted automatically by the framework. + +### Common Events vs Platform-Native Events + +Wails provides two types of system events: + +**Common Events** (`events.Common.*`) are cross-platform abstractions that work consistently across macOS, Windows, and Linux. These are the events you should use in your application for maximum portability. + +**Platform-Native Events** (`events.Mac.*`, `events.Windows.*`, `events.Linux.*`) are the underlying OS-specific events that Common Events are mapped from. These provide access to platform-specific behaviors and edge cases. + +**How They Work:** + +```go +import "github.com/wailsapp/wails/v3/pkg/events" + +// ✅ RECOMMENDED: Use Common Events for cross-platform code +window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) { + // This works on all platforms +}) + +// Platform-specific events for advanced use cases +window.OnWindowEvent(events.Mac.WindowWillClose, func(e *application.WindowEvent) { + // macOS-specific "will close" event (before WindowClosing) +}) + +window.OnWindowEvent(events.Windows.WindowClosing, func(e *application.WindowEvent) { + // Windows-specific close event +}) +``` + +**Event Mapping:** + +Platform-native events are automatically mapped to Common Events: + +- macOS: `events.Mac.WindowShouldClose` → `events.Common.WindowClosing` +- Windows: `events.Windows.WindowClosing` → `events.Common.WindowClosing` +- Linux: `events.Linux.WindowDeleteEvent` → `events.Common.WindowClosing` + +This mapping happens automatically in the background, so when you listen for `events.Common.WindowClosing`, you'll receive it regardless of the platform. + +**When to Use Each:** + +- **Use Common Events** for 99% of your application code - they provide consistent behavior across platforms +- **Use Platform-Native Events** only when you need platform-specific functionality that isn't available in Common Events (e.g., macOS-specific window lifecycle events, Windows power management events) + +### Application Events + +| Event | Description | When Emitted | Cancellable | +|-------|-------------|--------------|-------------| +| `ApplicationOpenedWithFile` | Application opened with a file | When app is launched with a file (e.g., from file association) | No | +| `ApplicationStarted` | Application has finished launching | After app initialization is complete and app is ready | No | +| `ApplicationLaunchedWithUrl` | Application launched with a URL | When app is launched via URL scheme | No | +| `ThemeChanged` | System theme changed | When OS theme switches between light/dark mode | No | + +**Usage:** + +```go +import "github.com/wailsapp/wails/v3/pkg/events" + +app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(e *application.ApplicationEvent) { + app.Logger.Info("Application ready!") +}) + +app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.ApplicationEvent) { + // Update app theme +}) +``` + +### Window Events + +| Event | Description | When Emitted | Cancellable | +|-------|-------------|--------------|-------------| +| `WindowClosing` | Window is about to close | Before window closes (user clicked X, Close() called) | Yes | +| `WindowDidMove` | Window moved to new position | After window position changes (debounced) | No | +| `WindowDidResize` | Window was resized | After window size changes | No | +| `WindowDPIChanged` | Window DPI scaling changed | When moving between monitors with different DPI (Windows) | No | +| `WindowFilesDropped` | Files dropped via native OS drag-drop | After files are dropped from OS onto window | No | +| `WindowFocus` | Window gained focus | When window becomes active | No | +| `WindowFullscreen` | Window entered fullscreen | After Fullscreen() or user enters fullscreen | No | +| `WindowHide` | Window was hidden | After Hide() or window becomes occluded | No | +| `WindowLostFocus` | Window lost focus | When window becomes inactive | No | +| `WindowMaximise` | Window was maximized | After Maximise() or user maximizes | Yes (macOS) | +| `WindowMinimise` | Window was minimized | After Minimise() or user minimizes | Yes (macOS) | +| `WindowRestore` | Window restored from min/max state | After Restore() (Windows primarily) | No | +| `WindowRuntimeReady` | Wails runtime loaded and ready | When JavaScript runtime initialization completes | No | +| `WindowShow` | Window became visible | After Show() or window becomes visible | No | +| `WindowUnFullscreen` | Window exited fullscreen | After UnFullscreen() or user exits fullscreen | No | +| `WindowUnMaximise` | Window exited maximized state | After UnMaximise() or user unmaximizes | Yes (macOS) | +| `WindowUnMinimise` | Window exited minimized state | After UnMinimise()/Restore() or user restores | Yes (macOS) | +| `WindowZoomIn` | Window content zoom increased | After ZoomIn() called (macOS primarily) | Yes (macOS) | +| `WindowZoomOut` | Window content zoom decreased | After ZoomOut() called (macOS primarily) | Yes (macOS) | +| `WindowZoomReset` | Window content zoom reset to 100% | After ZoomReset() called (macOS primarily) | Yes (macOS) | +| `WindowDropZoneFilesDropped` | Files dropped on JS-defined drop zone | When files dropped onto element with drop zone | No | + +**Usage:** + +```go +import "github.com/wailsapp/wails/v3/pkg/events" + +// Listen for window events +window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) { + app.Logger.Info("Window focused") +}) + +// Cancel window close +window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { + result, _ := app.Dialog.Question(). + SetMessage("Close window?"). + SetButtons("Yes", "No"). + Show() + + if result == "No" { + e.Cancel() // Prevent close + } +}) + +// Wait for runtime ready +window.OnWindowEvent(events.Common.WindowRuntimeReady, func(e *application.WindowEvent) { + app.Logger.Info("Runtime ready, safe to emit events to frontend") + window.EmitEvent("app-initialized", data) +}) +``` + +**Important Notes:** + +- **WindowRuntimeReady** is critical - wait for this event before emitting events to the frontend +- **WindowDidMove** and **WindowDidResize** are debounced (50ms default) to prevent event flooding +- **Cancellable events** can be prevented by calling `event.Cancel()` in a `RegisterHook()` handler +- **WindowFilesDropped** is for native OS file drops; **WindowDropZoneFilesDropped** is for web-based drop zones +- Some events are platform-specific (e.g., WindowDPIChanged on Windows, zoom events primarily on macOS) + +## Event Naming Conventions + +```go +// Good - descriptive and specific +app.Event.Emit("user:logged-in", user) +app.Event.Emit("data:fetch:complete", results) +app.Event.Emit("ui:theme:changed", theme) + +// Bad - vague and unclear +app.Event.Emit("event1", data) +app.Event.Emit("update", stuff) +app.Event.Emit("e", value) +``` + +## Performance Considerations + +### Debouncing High-Frequency Events + +```go +type Service struct { + app *application.Application + lastEmit time.Time + debounceWindow time.Duration +} + +func (s *Service) EmitWithDebounce(event string, data interface{}) { + now := time.Now() + if now.Sub(s.lastEmit) < s.debounceWindow { + return // Skip this emission + } + + s.app.Event.Emit(event, data) + s.lastEmit = now +} +``` + +### Throttling Events + +```javascript +import { On } from '@wailsio/runtime' + +let lastUpdate = 0 +const throttleMs = 100 + +On('high-frequency-event', (data) => { + const now = Date.now() + if (now - lastUpdate < throttleMs) { + return // Skip this update + } + + processUpdate(data) + lastUpdate = now +}) +``` diff --git a/docs/src/content/docs/reference/frontend-runtime.mdx b/docs/src/content/docs/reference/frontend-runtime.mdx new file mode 100644 index 000000000..4d56667fc --- /dev/null +++ b/docs/src/content/docs/reference/frontend-runtime.mdx @@ -0,0 +1,953 @@ +--- +title: Frontend Runtime +description: The Wails JavaScript runtime package for frontend integration +sidebar: + order: 1 +--- + +The Wails frontend runtime is the standard library for Wails applications. It provides a +number of features that may be used in your applications, including: + +- Window management +- Dialogs +- Browser integration +- Clipboard +- Menus +- System information +- Events +- Context Menus +- Screens +- WML (Wails Markup Language) + +The runtime is required for integration between Go and the frontend. There are 2 +ways to integrate the runtime: + +- Using the `@wailsio/runtime` package +- Using a pre-built bundle + +## Using the npm package + +The `@wailsio/runtime` package is a JavaScript package that provides access to +the Wails runtime from the frontend. It is used by all standard templates +and is the recommended way to integrate the runtime into your application. +By using the `@wailsio/runtime` package, you will only include the parts of the runtime that you use. + +The package is available on npm and can be installed using: + +```shell +npm install --save @wailsio/runtime +``` + +## Using a pre-built bundle + +Some projects will not use a Javascript bundler and may prefer to use a +pre-built bundled version of the runtime. This version can be generated locally +using the following command: + +```shell +wails3 generate runtime +``` + +The command will output a `runtime.js` (and `runtime.debug.js`) file in the current +directory. This file is an ES module that can be imported by your application scripts +just like the npm package, but the API is also exported to the global window object, +so for simpler applications you can use it as follows: + +```html + + + + + + + +``` + +:::caution +It is important to include the `type="module"` attribute on the ` + + + ``` + + Run `wails3 dev` to start the dev server. After a few seconds, the application should open. + + Type in some text and click the "Generate QR Code" button. You should see a QR code in the center of the page: + + QR Code + +
      +
      + +7. ## Alternative Approach: HTTP Handler + + So far, we have covered the following areas: + - Creating a new Service + - Generating Bindings + - Using the Bindings in our Frontend code + + **Why use an HTTP handler?** + + Method bindings work great for data operations, but there's an alternative approach for serving files, images, or other media. Instead of converting everything to base64 and sending it through bindings, you can make your service act like a mini web server. + + This is useful when: + - You're serving images, videos, or large files + - You want to use standard HTML `` or `