From e01d0feb22df02f36321f90a0af2be27b67ebce1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 8 Feb 2026 03:10:46 +0000 Subject: [PATCH 01/17] chore(v3): bump to v3.0.0-alpha.69 and update changelog [skip ci] --- docs/src/content/docs/changelog.mdx | 18 ++++++++++++++++++ v3/UNRELEASED_CHANGELOG.md | 12 ------------ v3/internal/version/version.txt | 2 +- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/docs/src/content/docs/changelog.mdx b/docs/src/content/docs/changelog.mdx index a44e73055..45289ddf4 100644 --- a/docs/src/content/docs/changelog.mdx +++ b/docs/src/content/docs/changelog.mdx @@ -30,6 +30,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 */ ## [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 diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index 7814bcfaa..8e4648038 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -17,24 +17,12 @@ After processing, the content will be moved to the main changelog and this file ## Added -- Add `-tags` flag to `wails3 build` command for passing custom Go build tags (e.g., `wails3 build -tags gtk4`) (#4957) ## Changed ## 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 ## Deprecated diff --git a/v3/internal/version/version.txt b/v3/internal/version/version.txt index 776e58d05..be73e69e1 100644 --- a/v3/internal/version/version.txt +++ b/v3/internal/version/version.txt @@ -1 +1 @@ -v3.0.0-alpha.68 \ No newline at end of file +v3.0.0-alpha.69 \ No newline at end of file From bbc4e532c45e110853e9417a9946b335efce625d Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Sun, 8 Feb 2026 21:45:23 +1100 Subject: [PATCH 02/17] docs: document automatic enum generation in binding generator (#4973) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: document automatic enum generation in binding generator (#4972) Add dedicated Enums page covering string/integer/type-alias enums, $zero values, struct field typing, imported package enums, supported types, and limitations. Fix inaccurate enum section in Data Models page and add both Data Models and Enums to sidebar navigation. Co-Authored-By: Claude Opus 4.6 * docs: add enum generation changelog entry (#4972) Co-Authored-By: Claude Opus 4.6 * docs: address review feedback on enum documentation - Add bool → false to $zero value table - Fix title.String() → string(title) in imported package example - Clarify $zero defaults apply to class output, not interfaces - Improve iota limitation wording for clarity Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- docs/astro.config.mjs | 2 + .../content/docs/features/bindings/enums.mdx | 529 ++++++++++++++++++ .../content/docs/features/bindings/models.mdx | 30 +- v3/UNRELEASED_CHANGELOG.md | 2 + 4 files changed, 545 insertions(+), 18 deletions(-) create mode 100644 docs/src/content/docs/features/bindings/enums.mdx diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index 352989727..03133e2f5 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -138,6 +138,8 @@ export default defineConfig({ 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" }, ], diff --git a/docs/src/content/docs/features/bindings/enums.mdx b/docs/src/content/docs/features/bindings/enums.mdx new file mode 100644 index 000000000..1e389273a --- /dev/null +++ b/docs/src/content/docs/features/bindings/enums.mdx @@ -0,0 +1,529 @@ +--- +title: Enums +description: Automatic enum generation from Go constants +sidebar: + order: 4 +--- + +import { Card, CardGrid } from "@astrojs/starlight/components"; + +## Enum Bindings + +The Wails v3 binding generator **automatically detects Go constant types and generates TypeScript enums or JavaScript const objects**. No registration, no configuration — just define your types and constants in Go, and the generator handles the rest. + +:::note +Unlike Wails v2, there is **no need to call `EnumBind`** or register enums manually. The generator discovers them automatically from your source code. +::: + +## Quick Start + +**Define a named type with constants in Go:** + +```go +type Status string + +const ( + StatusActive Status = "active" + StatusPending Status = "pending" + StatusClosed Status = "closed" +) +``` + +**Use the type in a struct or service method:** + +```go +type Ticket struct { + ID int `json:"id"` + Title string `json:"title"` + Status Status `json:"status"` +} +``` + +**Generate bindings:** + +```bash +wails3 generate bindings +``` + +The generator output will report enum counts alongside models: + +``` +3 Enums, 5 Models +``` + +**Use in your frontend:** + +```javascript +import { Ticket, Status } from './bindings/myapp/models' + +const ticket = new Ticket({ + id: 1, + title: "Bug report", + status: Status.StatusActive +}) +``` + +**That's it!** The enum type is enforced in both Go and JavaScript/TypeScript. + +## Defining Enums + +An enum in Wails is a **named type** with an underlying basic type, combined with **const declarations** of that type. + +### String Enums + +```go +// Title is a title +type Title string + +const ( + // Mister is a title + Mister Title = "Mr" + Miss Title = "Miss" + Ms Title = "Ms" + Mrs Title = "Mrs" + Dr Title = "Dr" +) +``` + +**Generated TypeScript:** + +```typescript +/** + * Title is a title + */ +export enum Title { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero = "", + + /** + * Mister is a title + */ + Mister = "Mr", + Miss = "Miss", + Ms = "Ms", + Mrs = "Mrs", + Dr = "Dr", +} +``` + +**Generated JavaScript:** + +```javascript +/** + * Title is a title + * @readonly + * @enum {string} + */ +export const Title = { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero: "", + + /** + * Mister is a title + */ + Mister: "Mr", + Miss: "Miss", + Ms: "Ms", + Mrs: "Mrs", + Dr: "Dr", +}; +``` + +### Integer Enums + +```go +type Priority int + +const ( + PriorityLow Priority = 0 + PriorityMedium Priority = 1 + PriorityHigh Priority = 2 +) +``` + +**Generated TypeScript:** + +```typescript +export enum Priority { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero = 0, + + PriorityLow = 0, + PriorityMedium = 1, + PriorityHigh = 2, +} +``` + +### Type Alias Enums + +Go type aliases (`=`) also work, but generate a slightly different output — a type definition plus a const object, rather than a native TypeScript `enum`: + +```go +// Age is an integer with some predefined values +type Age = int + +const ( + NewBorn Age = 0 + Teenager Age = 12 + YoungAdult Age = 18 + + // Oh no, some grey hair! + MiddleAged Age = 50 + Mathusalem Age = 1000 // Unbelievable! +) +``` + +**Generated TypeScript:** + +```typescript +/** + * Age is an integer with some predefined values + */ +export type Age = number; + +/** + * Predefined constants for type Age. + * @namespace + */ +export const Age = { + NewBorn: 0, + Teenager: 12, + YoungAdult: 18, + + /** + * Oh no, some grey hair! + */ + MiddleAged: 50, + + /** + * Unbelievable! + */ + Mathusalem: 1000, +}; +``` + +**Generated JavaScript:** + +```javascript +/** + * Age is an integer with some predefined values + * @typedef {number} Age + */ + +/** + * Predefined constants for type Age. + * @namespace + */ +export const Age = { + NewBorn: 0, + Teenager: 12, + YoungAdult: 18, + + /** + * Oh no, some grey hair! + */ + MiddleAged: 50, + + /** + * Unbelievable! + */ + Mathusalem: 1000, +}; +``` + +:::tip +**Named types** (`type Title string`) generate native TypeScript `enum` declarations with a `$zero` member. +**Type aliases** (`type Age = int`) generate a `type` + `const` namespace pair without `$zero`. +::: + +## The `$zero` Value + +Every named-type enum includes a special `$zero` member representing the **Go zero value** for the underlying type: + +| Underlying Type | `$zero` Value | +|----------------|---------------| +| `string` | `""` | +| `int`, `int8`, `int16`, `int32`, `int64` | `0` | +| `uint`, `uint8`, `uint16`, `uint32`, `uint64` | `0` | +| `float32`, `float64` | `0` | +| `bool` | `false` | + +When a struct field uses an enum type and no value is provided, the constructor defaults to `$zero`: + +```typescript +export class Person { + "Title": Title; + + constructor($$source: Partial = {}) { + if (!("Title" in $$source)) { + this["Title"] = Title.$zero; // defaults to "" + } + Object.assign(this, $$source); + } +} +``` + +This ensures type-safe initialisation when generating classes — enum fields are never `undefined`. When generating TypeScript interfaces (using `-i`), there is no constructor and fields may be absent as usual. + +## Using Enums in Structs + +When a struct field has an enum type, the generated code **preserves that type** rather than falling back to the primitive: + +```go +type Person struct { + Title Title + Name string + Age Age +} +``` + +**Generated TypeScript:** + +```typescript +export class Person { + "Title": Title; + "Name": string; + "Age": Age; + + constructor($$source: Partial = {}) { + if (!("Title" in $$source)) { + this["Title"] = Title.$zero; + } + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Age" in $$source)) { + this["Age"] = 0; + } + + Object.assign(this, $$source); + } +} +``` + +The `Title` field is typed as `Title`, not `string`. This gives your IDE full autocompletion and type checking on enum values. + +## Enums from Imported Packages + +Enums defined in separate packages are fully supported. They are generated into the corresponding package directory: + +```go +// services/types.go +package services + +type Title string + +const ( + Mister Title = "Mr" + Miss Title = "Miss" + Ms Title = "Ms" +) +``` + +```go +// main.go +package main + +import "myapp/services" + +func (*GreetService) Greet(name string, title services.Title) string { + return "Hello " + string(title) + " " + name +} +``` + +The `Title` enum is generated in the `services` models file, and import paths are resolved automatically: + +```typescript +// bindings/myapp/services/models.ts +export enum Title { + $zero = "", + Mister = "Mr", + Miss = "Miss", + Ms = "Ms", +} +``` + +## Enum Methods + +You can add methods to your enum types in Go. These don't affect binding generation but provide useful server-side functionality: + +```go +type Title string + +func (t Title) String() string { + return string(t) +} + +const ( + Mister Title = "Mr" + Miss Title = "Miss" +) +``` + +The generated enum is identical whether or not Go methods exist on the type. + +## Comments and Documentation + +The generator preserves Go comments as JSDoc in the generated output: + +- **Type comments** become the enum's doc comment +- **Const group comments** become section separators +- **Individual const comments** become member doc comments +- **Inline comments** are preserved where possible + +This means your IDE will show documentation for enum values on hover. + +## Supported Underlying Types + +The binding generator supports enums with the following Go underlying types: + +| Go Type | Works as Enum | +|---------|:---:| +| `string` | Yes | +| `int`, `int8`, `int16`, `int32`, `int64` | Yes | +| `uint`, `uint8`, `uint16`, `uint32`, `uint64` | Yes | +| `float32`, `float64` | Yes | +| `byte` (`uint8`) | Yes | +| `rune` (`int32`) | Yes | +| `bool` | Yes | +| `complex64`, `complex128` | No | + +## Limitations + +The following are **not** supported for enum generation: + +- **Generic types** — Type parameters prevent constant detection +- **Types with custom `json.Marshaler` or `encoding.TextMarshaler`** — Custom serialisation means the generated values may not match runtime behaviour, so the generator skips these +- **Constants whose values cannot be statically evaluated or represented** — Constants must have known, representable values in their underlying type. Standard `iota` patterns work fine since the compiler resolves them to concrete values +- **Complex number types** — `complex64` and `complex128` cannot be enum underlying types + +## Complete Example + +**Go:** + +```go +package main + +import ( + "github.com/wailsapp/wails/v3/pkg/application" +) + +// BackgroundType defines the type of background +type BackgroundType string + +const ( + BackgroundSolid BackgroundType = "solid" + BackgroundGradient BackgroundType = "gradient" + BackgroundImage BackgroundType = "image" +) + +type BackgroundConfig struct { + Type BackgroundType `json:"type"` + Value string `json:"value"` +} + +type ThemeService struct{} + +func (*ThemeService) GetBackground() BackgroundConfig { + return BackgroundConfig{ + Type: BackgroundSolid, + Value: "#ffffff", + } +} + +func (*ThemeService) SetBackground(config BackgroundConfig) error { + // Apply background + return nil +} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&ThemeService{}), + }, + }) + app.Window.New() + app.Run() +} +``` + +**Frontend (TypeScript):** + +```typescript +import { GetBackground, SetBackground } from './bindings/myapp/themeservice' +import { BackgroundConfig, BackgroundType } from './bindings/myapp/models' + +// Get current background +const bg = await GetBackground() + +// Check the type using enum values +if (bg.type === BackgroundType.BackgroundSolid) { + console.log("Solid background:", bg.value) +} + +// Set a new background +await SetBackground(new BackgroundConfig({ + type: BackgroundType.BackgroundGradient, + value: "linear-gradient(to right, #000, #fff)" +})) +``` + +**Frontend (JavaScript):** + +```javascript +import { GetBackground, SetBackground } from './bindings/myapp/themeservice' +import { BackgroundConfig, BackgroundType } from './bindings/myapp/models' + +// Use enum values for type-safe comparisons +const bg = await GetBackground() + +switch (bg.type) { + case BackgroundType.BackgroundSolid: + applySolid(bg.value) + break + case BackgroundType.BackgroundGradient: + applyGradient(bg.value) + break + case BackgroundType.BackgroundImage: + applyImage(bg.value) + break +} +``` + +## Next Steps + + + + Structs, type mapping, and model generation. + + [Learn More →](/features/bindings/models) + + + + Bind Go methods to the frontend. + + [Learn More →](/features/bindings/methods) + + + + Directives, code injection, and custom IDs. + + [Learn More →](/features/bindings/advanced) + + + + Binding design patterns. + + [Learn More →](/features/bindings/best-practices) + + + +--- + +**Questions?** 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/features/bindings/models.mdx b/docs/src/content/docs/features/bindings/models.mdx index f393eb694..5df312ea7 100644 --- a/docs/src/content/docs/features/bindings/models.mdx +++ b/docs/src/content/docs/features/bindings/models.mdx @@ -480,6 +480,8 @@ if (user.nickname) { ### Enums +The binding generator automatically detects Go named types with constants and generates TypeScript enums or JavaScript const objects — including a `$zero` member for Go's zero value and full JSDoc preservation. + ```go type UserRole string @@ -488,26 +490,16 @@ const ( RoleUser UserRole = "user" RoleGuest UserRole = "guest" ) - -type User struct { - ID int `json:"id"` - Name string `json:"name"` - Role UserRole `json:"role"` -} ``` -**Generated:** +**Generated TypeScript:** -```javascript -export const UserRole = { - Admin: "admin", - User: "user", - Guest: "guest" -} - -export class User { - /** @type {string} */ - role = UserRole.User +```typescript +export enum UserRole { + $zero = "", + RoleAdmin = "admin", + RoleUser = "user", + RoleGuest = "guest", } ``` @@ -518,10 +510,12 @@ import { User, UserRole } from './bindings/myapp/models' const admin = new User({ name: "Admin", - role: UserRole.Admin + role: UserRole.RoleAdmin }) ``` +For comprehensive coverage of string enums, integer enums, type aliases, imported package enums, and limitations, see the dedicated **[Enums](/features/bindings/enums)** page. + ### Validation ```javascript diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index 8e4648038..1710e1795 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -17,6 +17,8 @@ After processing, the content will be moved to the main changelog and this file ## Added +- Add `-tags` flag to `wails3 build` command for passing custom Go build tags (e.g., `wails3 build -tags gtk4`) (#4957) +- Add documentation for automatic enum generation in binding generator, including dedicated Enums page and sidebar navigation (#4972) ## Changed From e999741e9b00fbad5128dff51e409dc76c2a292c Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Mon, 9 Feb 2026 07:35:35 +1100 Subject: [PATCH 03/17] fix(v3/macos): guard InvisibleTitleBarHeight and fix top-corner resize shaking (#4962) * fix(v3/macos): guard InvisibleTitleBarHeight and fix top-corner resize shaking (#4960) - Only apply InvisibleTitleBarHeight when the native drag area is actually hidden (frameless window or transparent title bar presets like HiddenInset). Previously it was applied unconditionally, which could swallow clicks near the top of standard windows. - Skip drag initiation when the click is near the left/right window edges within the invisible title bar zone. This prevents conflict between dragging and native top-corner resizing, which caused window content to shake/jitter. Fixes #4960 Co-Authored-By: Claude Opus 4.6 * docs: update InvisibleTitleBarHeight docs and changelog (#4960) Document that InvisibleTitleBarHeight only applies to frameless or transparent title bar windows, and add changelog entries for the guard and edge-detection fixes. Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- docs/src/content/docs/features/windows/frameless.mdx | 2 +- docs/src/content/docs/features/windows/options.mdx | 3 ++- v3/UNRELEASED_CHANGELOG.md | 3 +++ v3/pkg/application/webview_window_darwin.go | 5 ++++- v3/pkg/application/webview_window_darwin.m | 9 +++++++++ 5 files changed, 19 insertions(+), 3 deletions(-) diff --git a/docs/src/content/docs/features/windows/frameless.mdx b/docs/src/content/docs/features/windows/frameless.mdx index c13cb9d4d..361cced18 100644 --- a/docs/src/content/docs/features/windows/frameless.mdx +++ b/docs/src/content/docs/features/windows/frameless.mdx @@ -394,7 +394,7 @@ body { ``` **Invisible title bar:** - Allows dragging whilst hiding the title bar: + Allows dragging whilst hiding the title bar. This only takes effect when the window is frameless or uses `AppearsTransparent`: ```go Mac: application.MacOptions{ InvisibleTitleBarHeight: 40, diff --git a/docs/src/content/docs/features/windows/options.mdx b/docs/src/content/docs/features/windows/options.mdx index 730e62045..9b38d5e2b 100644 --- a/docs/src/content/docs/features/windows/options.mdx +++ b/docs/src/content/docs/features/windows/options.mdx @@ -792,7 +792,8 @@ Mac: application.MacWindow{ **InvisibleTitleBarHeight** (`int`) - Height of invisible title bar area (for dragging) -- Useful when title bar is hidden +- Only takes effect when the native title bar drag area is hidden — i.e. when the window is frameless (`Frameless: true`) or uses a transparent title bar (`AppearsTransparent: true`) +- Has no effect on standard windows with a visible title bar **WindowLevel** (`MacWindowLevel`) - `MacWindowLevelNormal` - Standard window level (default) diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index 1710e1795..796685b40 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -25,6 +25,9 @@ After processing, the content will be moved to the main changelog and this file ## Fixed +- Fix `InvisibleTitleBarHeight` being applied to all macOS windows instead of only frameless or transparent title bar windows (#4960) +- Fix window shaking/jitter when resizing from top corners with `InvisibleTitleBarHeight` enabled, by skipping drag initiation near window edges (#4960) +- Fix generation of mapped types with enum keys in JS/TS bindings (#4437) by @fbbdev ## Deprecated diff --git a/v3/pkg/application/webview_window_darwin.go b/v3/pkg/application/webview_window_darwin.go index 4a53d7337..ff49ad49b 100644 --- a/v3/pkg/application/webview_window_darwin.go +++ b/v3/pkg/application/webview_window_darwin.go @@ -1334,7 +1334,10 @@ func (w *macosWebviewWindow) run() { C.windowSetAppearanceTypeByName(w.nsWindow, C.CString(string(macOptions.Appearance))) } - if macOptions.InvisibleTitleBarHeight != 0 { + // Only apply invisible title bar when the native drag area is hidden + // (frameless window or transparent/hidden title bar presets like HiddenInset) + if macOptions.InvisibleTitleBarHeight != 0 && + (w.parent.options.Frameless || titleBarOptions.AppearsTransparent) { C.windowSetInvisibleTitleBar(w.nsWindow, C.uint(macOptions.InvisibleTitleBarHeight)) } diff --git a/v3/pkg/application/webview_window_darwin.m b/v3/pkg/application/webview_window_darwin.m index 4e2f1cabc..48130a562 100644 --- a/v3/pkg/application/webview_window_darwin.m +++ b/v3/pkg/application/webview_window_darwin.m @@ -345,6 +345,15 @@ typedef NS_ENUM(NSInteger, MacLiquidGlassStyle) { NSPoint location = [event locationInWindow]; NSRect frame = [window frame]; if( location.y > frame.size.height - self.invisibleTitleBarHeight ) { + // Skip drag if the click is near a window edge (resize zone). + // This prevents conflict between dragging and native top-corner resizing, + // which causes window content to shake/jitter (#4960). + CGFloat resizeThreshold = 5.0; + BOOL nearLeftEdge = location.x < resizeThreshold; + BOOL nearRightEdge = location.x > frame.size.width - resizeThreshold; + if( nearLeftEdge || nearRightEdge ) { + return; + } [window performWindowDragWithEvent:event]; return; } From 52d0b305491179afce631f6d6e7649235a79c2e9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 9 Feb 2026 03:10:32 +0000 Subject: [PATCH 04/17] chore(v3): bump to v3.0.0-alpha.70 and update changelog [skip ci] --- docs/src/content/docs/changelog.mdx | 11 +++++++++++ v3/UNRELEASED_CHANGELOG.md | 5 ----- v3/internal/version/version.txt | 2 +- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/docs/src/content/docs/changelog.mdx b/docs/src/content/docs/changelog.mdx index 45289ddf4..28d3e712a 100644 --- a/docs/src/content/docs/changelog.mdx +++ b/docs/src/content/docs/changelog.mdx @@ -30,6 +30,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 */ ## [Unreleased] +## v3.0.0-alpha.70 - 2026-02-09 + +## Added +- Add `-tags` flag to `wails3 build` command for passing custom Go build tags (e.g., `wails3 build -tags gtk4`) (#4957) +- Add documentation for automatic enum generation in binding generator, including dedicated Enums page and sidebar navigation (#4972) + +## Fixed +- Fix `InvisibleTitleBarHeight` being applied to all macOS windows instead of only frameless or transparent title bar windows (#4960) +- Fix window shaking/jitter when resizing from top corners with `InvisibleTitleBarHeight` enabled, by skipping drag initiation near window edges (#4960) +- Fix generation of mapped types with enum keys in JS/TS bindings (#4437) by @fbbdev + ## v3.0.0-alpha.69 - 2026-02-08 ## Added diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index 796685b40..8e4648038 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -17,17 +17,12 @@ After processing, the content will be moved to the main changelog and this file ## Added -- Add `-tags` flag to `wails3 build` command for passing custom Go build tags (e.g., `wails3 build -tags gtk4`) (#4957) -- Add documentation for automatic enum generation in binding generator, including dedicated Enums page and sidebar navigation (#4972) ## Changed ## Fixed -- Fix `InvisibleTitleBarHeight` being applied to all macOS windows instead of only frameless or transparent title bar windows (#4960) -- Fix window shaking/jitter when resizing from top corners with `InvisibleTitleBarHeight` enabled, by skipping drag initiation near window edges (#4960) -- Fix generation of mapped types with enum keys in JS/TS bindings (#4437) by @fbbdev ## Deprecated diff --git a/v3/internal/version/version.txt b/v3/internal/version/version.txt index be73e69e1..1230907f6 100644 --- a/v3/internal/version/version.txt +++ b/v3/internal/version/version.txt @@ -1 +1 @@ -v3.0.0-alpha.69 \ No newline at end of file +v3.0.0-alpha.70 \ No newline at end of file From 1a5c6dceeef8b8ef3af0a59167ad6c1a6285e2e4 Mon Sep 17 00:00:00 2001 From: Zach Botterman <6074435+popaprozac@users.noreply.github.com> Date: Mon, 9 Feb 2026 02:03:45 -0800 Subject: [PATCH 05/17] fix(v3): Dock ops sync and add GetBadge method (#4838) * dock fixes and get method * update changelog * async -> sync * cleanup iOS and darwin set call * handle potential errors --- .../content/docs/features/platform/dock.mdx | 6 ++ v3/UNRELEASED_CHANGELOG.md | 2 + v3/pkg/services/dock/badge_ios.go | 11 ++- v3/pkg/services/dock/dock.go | 6 ++ v3/pkg/services/dock/dock_darwin.go | 81 ++++++++++++++----- v3/pkg/services/dock/dock_linux.go | 5 ++ v3/pkg/services/dock/dock_windows.go | 9 +++ 7 files changed, 99 insertions(+), 21 deletions(-) diff --git a/docs/src/content/docs/features/platform/dock.mdx b/docs/src/content/docs/features/platform/dock.mdx index 2638f7bf2..3cc5d9a92 100644 --- a/docs/src/content/docs/features/platform/dock.mdx +++ b/docs/src/content/docs/features/platform/dock.mdx @@ -131,6 +131,11 @@ Remove the badge from the application icon: dockService.RemoveBadge() ``` +### Getting the set badge +```go +dockService.GetBadge() +``` + ## Platform Considerations @@ -220,6 +225,7 @@ dockService.RemoveBadge() | `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 | +| `GetBadge() *string` | Gets the current badge | ### Structs and Types diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index 8e4648038..2244540f3 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -17,12 +17,14 @@ After processing, the content will be moved to the main changelog and this file ## Added +- Add `GetBadge` method to the dock service ## Changed ## Fixed +- Fix dock badge methods consistency on macOS ## Deprecated diff --git a/v3/pkg/services/dock/badge_ios.go b/v3/pkg/services/dock/badge_ios.go index 62dda0cb7..136771839 100644 --- a/v3/pkg/services/dock/badge_ios.go +++ b/v3/pkg/services/dock/badge_ios.go @@ -8,7 +8,8 @@ import ( "github.com/wailsapp/wails/v3/pkg/application" ) -type iosDock struct{} +type iosDock struct { +} // New creates a new Dock Service. // On iOS, this returns a stub implementation. @@ -61,4 +62,10 @@ func (d *iosDock) SetCustomBadge(label string, options BadgeOptions) error { func (d *iosDock) RemoveBadge() error { // iOS badge removal would go here via native bridge return nil -} \ No newline at end of file +} + +// GetBadge retrieves the badge from the iOS app icon. +func (d *iosDock) GetBadge() *string { + // iOS badge retrieval would go here via native bridge + return nil +} diff --git a/v3/pkg/services/dock/dock.go b/v3/pkg/services/dock/dock.go index eb11f59ca..3f9890c67 100644 --- a/v3/pkg/services/dock/dock.go +++ b/v3/pkg/services/dock/dock.go @@ -20,6 +20,7 @@ type platformDock interface { SetBadge(label string) error SetCustomBadge(label string, options BadgeOptions) error RemoveBadge() error + GetBadge() *string } // Service represents the dock service @@ -75,3 +76,8 @@ func (d *DockService) SetCustomBadge(label string, options BadgeOptions) error { func (d *DockService) RemoveBadge() error { return d.impl.RemoveBadge() } + +// GetBadge returns the badge label on the application icon. +func (d *DockService) GetBadge() *string { + return d.impl.GetBadge() +} diff --git a/v3/pkg/services/dock/dock_darwin.go b/v3/pkg/services/dock/dock_darwin.go index c42ae6b0c..361e3ef8a 100644 --- a/v3/pkg/services/dock/dock_darwin.go +++ b/v3/pkg/services/dock/dock_darwin.go @@ -8,42 +8,59 @@ package dock #import void hideDockIcon() { - dispatch_async(dispatch_get_main_queue(), ^{ + dispatch_sync(dispatch_get_main_queue(), ^{ [NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory]; }); } void showDockIcon() { - dispatch_async(dispatch_get_main_queue(), ^{ + dispatch_sync(dispatch_get_main_queue(), ^{ [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; }); } -static void setBadge(const char *label) { - dispatch_async(dispatch_get_main_queue(), ^{ +bool setBadge(const char *label) { + __block bool success = false; + dispatch_sync(dispatch_get_main_queue(), ^{ + // Ensure the app is in Regular activation policy (dock icon visible) + NSApplicationActivationPolicy currentPolicy = [NSApp activationPolicy]; + if (currentPolicy != NSApplicationActivationPolicyRegular) { + success = false; + return; + } + NSString *nsLabel = nil; if (label != NULL) { nsLabel = [NSString stringWithUTF8String:label]; } [[NSApp dockTile] setBadgeLabel:nsLabel]; [[NSApp dockTile] display]; + success = true; }); + return success; } */ import "C" import ( "context" + "fmt" + "sync" "unsafe" "github.com/wailsapp/wails/v3/pkg/application" ) -type darwinDock struct{} +type darwinDock struct { + mu sync.RWMutex + Badge *string +} // Creates a new Dock Service. func New() *DockService { return &DockService{ - impl: &darwinDock{}, + impl: &darwinDock{ + Badge: nil, + }, } } @@ -67,22 +84,42 @@ func (d *darwinDock) HideAppIcon() { } // ShowAppIcon shows the app icon in the macOS Dock. +// Note: After showing the dock icon, you may need to call SetBadge again +// to reapply any previously set badge, as changing activation policies clears the badge. func (d *darwinDock) ShowAppIcon() { C.showDockIcon() } -// SetBadge sets the badge label on the application icon. -func (d *darwinDock) SetBadge(label string) error { - // Always pick a label (use “●” if empty), then allocate + free exactly once. - value := label - if value == "" { - value = "●" // Default badge character - } - cLabel := C.CString(value) - defer C.free(unsafe.Pointer(cLabel)) +// setBadge handles the C call and updates the internal badge state with locking. +func (d *darwinDock) setBadge(label *string) error { + var cLabel *C.char + if label != nil { + cLabel = C.CString(*label) + defer C.free(unsafe.Pointer(cLabel)) + } - C.setBadge(cLabel) - return nil + success := C.setBadge(cLabel) + if !success { + return fmt.Errorf("failed to set badge") + } + + d.mu.Lock() + d.Badge = label + d.mu.Unlock() + + return nil +} + +// SetBadge sets the badge label on the application icon. +// Available default badge labels: +// Single space " " empty badge +// Empty string "" dot "●" indeterminate badge +func (d *darwinDock) SetBadge(label string) error { + // Always pick a label (use "●" if empty), then allocate + free exactly once. + if label == "" { + label = "●" // Default badge character + } + return d.setBadge(&label) } // SetCustomBadge is not supported on macOS, SetBadge is called instead. @@ -92,6 +129,12 @@ func (d *darwinDock) SetCustomBadge(label string, options BadgeOptions) error { // RemoveBadge removes the badge label from the application icon. func (d *darwinDock) RemoveBadge() error { - C.setBadge(nil) - return nil + return d.setBadge(nil) +} + +// GetBadge returns the badge label on the application icon. +func (d *darwinDock) GetBadge() *string { + d.mu.RLock() + defer d.mu.RUnlock() + return d.Badge } diff --git a/v3/pkg/services/dock/dock_linux.go b/v3/pkg/services/dock/dock_linux.go index af0311c67..633e6b005 100644 --- a/v3/pkg/services/dock/dock_linux.go +++ b/v3/pkg/services/dock/dock_linux.go @@ -68,3 +68,8 @@ func (l *linuxDock) RemoveBadge() error { // No-op: Linux doesn't have standardized badge support return nil } + +func (l *linuxDock) GetBadge() *string { + // No-op: Linux doesn't have standardized badge support + return nil +} \ No newline at end of file diff --git a/v3/pkg/services/dock/dock_windows.go b/v3/pkg/services/dock/dock_windows.go index 26f40d259..0a0c45efe 100644 --- a/v3/pkg/services/dock/dock_windows.go +++ b/v3/pkg/services/dock/dock_windows.go @@ -24,6 +24,7 @@ type windowsDock struct { badgeSize int fontManager *FontManager badgeOptions BadgeOptions + badge *string } var defaultOptions = BadgeOptions{ @@ -48,6 +49,7 @@ func NewWithOptions(options BadgeOptions) *DockService { return &DockService{ impl: &windowsDock{ badgeOptions: options, + badge: nil, }, } } @@ -121,6 +123,7 @@ func (w *windowsDock) SetBadge(label string) error { } defer w32.DestroyIcon(hicon) + w.badge = &label return w.taskbar.SetOverlayIcon(hwnd, hicon, nil) }) } @@ -182,6 +185,7 @@ func (w *windowsDock) SetCustomBadge(label string, options BadgeOptions) error { } defer w32.DestroyIcon(hicon) + w.badge = &label return w.taskbar.SetOverlayIcon(hwnd, hicon, nil) }) } @@ -209,6 +213,7 @@ func (w *windowsDock) RemoveBadge() error { } hwnd := uintptr(nativeWindow) + w.badge = nil return w.taskbar.SetOverlayIcon(hwnd, 0, nil) }) } @@ -394,3 +399,7 @@ func (w *windowsDock) createBadge() { w.badgeImg = img } + +func (w *windowsDock) GetBadge() *string { + return w.badge +} \ No newline at end of file From eacc8a0a20674668674d362df7576dbdaff8f0c0 Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Mon, 9 Feb 2026 21:07:30 +1100 Subject: [PATCH 06/17] chore(v3): update github.com/jaypipes/ghw to v0.21.3 (#4977) * chore(v3): update github.com/jaypipes/ghw to v0.21.3 Bumps ghw from v0.21.2 to v0.21.3, bringing cpuinfo parsing fixes and snapshot handling improvements. Co-Authored-By: Claude Opus 4.6 * Update changelog --------- Co-authored-by: Claude Opus 4.6 --- v3/UNRELEASED_CHANGELOG.md | 1 + v3/go.mod | 10 +++++----- v3/go.sum | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index 2244540f3..823ba99b8 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -17,6 +17,7 @@ After processing, the content will be moved to the main changelog and this file ## Added +- Bumped ghw version for better Apple device support by @leaanthony (#4977) - Add `GetBadge` method to the dock service ## Changed diff --git a/v3/go.mod b/v3/go.mod index c5aa9a11e..ba777a8f1 100644 --- a/v3/go.mod +++ b/v3/go.mod @@ -6,8 +6,11 @@ require ( git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 github.com/Masterminds/semver v1.5.0 github.com/adrg/xdg v0.5.3 + github.com/atotto/clipboard v0.1.4 github.com/atterpac/refresh v0.8.6 github.com/bep/debounce v1.2.1 + github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 + github.com/charmbracelet/bubbletea v1.3.10 github.com/charmbracelet/glamour v0.10.0 github.com/charmbracelet/huh v0.8.0 github.com/coder/websocket v1.8.14 @@ -22,7 +25,7 @@ require ( github.com/goreleaser/nfpm/v2 v2.44.1 github.com/gorilla/websocket v1.5.3 github.com/jackmordaunt/icns/v2 v2.2.7 - github.com/jaypipes/ghw v0.21.2 + github.com/jaypipes/ghw v0.21.3 github.com/konoui/lipo v0.10.0 github.com/leaanthony/clir v1.7.0 github.com/leaanthony/go-ansi-parser v1.6.1 @@ -52,10 +55,7 @@ require ( require ( al.essio.dev/pkg/shellescape v1.6.0 // indirect atomicgo.dev/schedule v0.1.0 // indirect - github.com/atotto/clipboard v0.1.4 // indirect github.com/catppuccin/go v0.3.0 // indirect - github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 // indirect - github.com/charmbracelet/bubbletea v1.3.10 // indirect github.com/charmbracelet/colorprofile v0.4.1 // indirect github.com/charmbracelet/x/cellbuf v0.0.14 // indirect github.com/charmbracelet/x/exp/slice v0.0.0-20260122224438-b01af16209d9 // indirect @@ -93,7 +93,7 @@ require ( github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect github.com/cavaliergopher/cpio v1.0.1 // indirect github.com/chainguard-dev/git-urls v1.0.2 // indirect - github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 // indirect + github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 github.com/charmbracelet/x/ansi v0.11.4 // indirect github.com/cloudflare/circl v1.6.3 // indirect github.com/containerd/console v1.0.5 // indirect diff --git a/v3/go.sum b/v3/go.sum index dd0b0ae81..122a5fd73 100644 --- a/v3/go.sum +++ b/v3/go.sum @@ -218,8 +218,8 @@ github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/jackmordaunt/icns/v2 v2.2.7 h1:K/RbfvuzjmjVY5y4g+XENRs8ZZatwz4YnLHypa2KwQg= github.com/jackmordaunt/icns/v2 v2.2.7/go.mod h1:ovoTxGguSuoUGKMk5Nn3R7L7BgMQkylsO+bblBuI22A= -github.com/jaypipes/ghw v0.21.2 h1:woW0lqNMPbYk59sur6thOVM8YFP9Hxxr8PM+JtpUrNU= -github.com/jaypipes/ghw v0.21.2/go.mod h1:GPrvwbtPoxYUenr74+nAnWbardIZq600vJDD5HnPsPE= +github.com/jaypipes/ghw v0.21.3 h1:v5mUHM+RN854Vqmk49Uh213jyUA4+8uqaRajlYESsh8= +github.com/jaypipes/ghw v0.21.3/go.mod h1:GPrvwbtPoxYUenr74+nAnWbardIZq600vJDD5HnPsPE= github.com/jaypipes/pcidb v1.1.1 h1:QmPhpsbmmnCwZmHeYAATxEaoRuiMAJusKYkUncMC0ro= github.com/jaypipes/pcidb v1.1.1/go.mod h1:x27LT2krrUgjf875KxQXKB0Ha/YXLdZRVmw6hH0G7g8= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= From 8eaed8c5f65612442115830cf3c82a3e5b71cf67 Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Mon, 9 Feb 2026 21:44:03 +1100 Subject: [PATCH 07/17] fix(linux): remove Screen typedef collision with X11 Xlib.h (#4979) * fix(linux): remove unused C Screen typedef that collides with X11 Xlib.h The GTK4 backend includes for X11 window positioning, which pulls in . Xlib defines its own `Screen` typedef, conflicting with the identically-named struct in linux_cgo_gtk4.h. The C Screen struct was unused - the Go code constructs Go Screen structs directly from GDK monitor data, never referencing C.Screen. Fixes #4957 Co-Authored-By: Claude Opus 4.6 * ci: run GTK4 build and tests on all v3-alpha PRs The GTK4 CI steps were gated on github.head_ref == 'feature/webkit-gtk6-support', meaning no other branch ever ran GTK4 compilation checks. This allowed the Screen typedef collision to slip through undetected. Now that GTK4 support has been merged, run GTK4 dependency installation, example builds, and test suite on every PR targeting v3-alpha. Co-Authored-By: Claude Opus 4.6 * docs: add GTK4 Screen typedef fix to unreleased changelog Co-Authored-By: Claude Opus 4.6 * Fix changelog * fix changelog * fix(ci): pass BUILD_TAGS through to go build in test:example:linux The GTK4 CI step ran BUILD_TAGS=gtk4 task test:examples but the Taskfile task ignored the env var, always building with no -tags flag. Use shell parameter expansion so BUILD_TAGS flows through to go build. Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- .github/workflows/build-and-test-v3.yml | 22 ++++++++++------------ v3/Taskfile.yaml | 13 ++++--------- v3/UNRELEASED_CHANGELOG.md | 1 + v3/pkg/application/linux_cgo_gtk4.h | 16 ---------------- 4 files changed, 15 insertions(+), 37 deletions(-) diff --git a/.github/workflows/build-and-test-v3.yml b/.github/workflows/build-and-test-v3.yml index cc2c6ded0..0392e5abf 100644 --- a/.github/workflows/build-and-test-v3.yml +++ b/.github/workflows/build-and-test-v3.yml @@ -118,8 +118,8 @@ jobs: 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' + - name: Install linux dependencies (GTK4) + if: matrix.os == 'ubuntu-latest' run: | sudo apt-get update sudo apt-get install -y libgtk-4-dev libwebkitgtk-6.0-dev @@ -150,8 +150,8 @@ jobs: 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' + - name: Build Examples (GTK4) + if: matrix.os == 'ubuntu-latest' working-directory: v3 run: | echo "Starting example compilation tests (GTK4)..." @@ -180,11 +180,10 @@ jobs: go test -v ./... ' - - name: Run tests (ubuntu) - GTK4 experimental - if: matrix.os == 'ubuntu-latest' && github.head_ref == 'feature/webkit-gtk6-support' + - name: Run tests (ubuntu) - GTK4 + if: matrix.os == 'ubuntu-latest' 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 + # Skip service tests that hang in CI due to GTK4 display requirements run: > xvfb-run --auto-servernum sh -c ' @@ -243,8 +242,8 @@ jobs: 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' + - name: Install linux dependencies (GTK4) + if: matrix.os == 'ubuntu-latest' run: | sudo apt-get update sudo apt-get install -y libgtk-4-dev libwebkitgtk-6.0-dev @@ -292,8 +291,7 @@ jobs: 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. + # GTK4 template builds are covered by the Go example compilation tests above. build_results: if: ${{ always() }} diff --git a/v3/Taskfile.yaml b/v3/Taskfile.yaml index 171853e14..81603c409 100644 --- a/v3/Taskfile.yaml +++ b/v3/Taskfile.yaml @@ -92,14 +92,14 @@ tasks: GOARCH: amd64 test:example:linux: - summary: Build example for Linux with GTK3 (default) + summary: Build example for Linux (pass BUILD_TAGS env var to add -tags, e.g. BUILD_TAGS=gtk4) dir: 'examples/{{.DIR}}' platforms: - linux cmds: - - echo "Building example {{.DIR}} for Linux (GTK3)" + - echo "Building example {{.DIR}} for Linux (${BUILD_TAGS:-GTK3})" - go mod tidy - - go build -o "testbuild-{{.DIR}}-linux" + - go build ${BUILD_TAGS:+-tags $BUILD_TAGS} -o "testbuild-{{.DIR}}-linux" test:example:linux:gtk4: summary: Build example for Linux with GTK4 (experimental, opt-in via -tags gtk4) @@ -365,12 +365,7 @@ tasks: vars: DIR: "{{.ITEM}}" platforms: [linux] - # GTK4 tests are run separately via Docker tasks since CI doesn't have GTK4 deps - # - for: { var: EXAMPLEDIRS } - # task: test:example:linux:gtk4 - # vars: - # DIR: "{{.ITEM}}" - # platforms: [linux] + # GTK4 example builds are handled in CI via: BUILD_TAGS=gtk4 task test:examples - for: { var: EXAMPLEDIRS } task: test:example:windows vars: diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index 823ba99b8..fbd0259cf 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -25,6 +25,7 @@ After processing, the content will be moved to the main changelog and this file ## Fixed +- Fix GTK4 build failure caused by C `Screen` typedef colliding with X11 Xlib.h (#4957) - Fix dock badge methods consistency on macOS ## Deprecated diff --git a/v3/pkg/application/linux_cgo_gtk4.h b/v3/pkg/application/linux_cgo_gtk4.h index ff1d6a93a..81126e98a 100644 --- a/v3/pkg/application/linux_cgo_gtk4.h +++ b/v3/pkg/application/linux_cgo_gtk4.h @@ -29,22 +29,6 @@ typedef struct WindowEvent { uint event; } WindowEvent; -typedef struct Screen { - const char* id; - const char* name; - int p_width; - int p_height; - int x; - int y; - int w_width; - int w_height; - int w_x; - int w_y; - float scaleFactor; - double rotation; - bool isPrimary; -} Screen; - typedef struct MenuItemData { guint id; GSimpleAction *action; From 20d368a700dfa45f1389738d8639ea031586ec90 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 10 Feb 2026 03:11:14 +0000 Subject: [PATCH 08/17] chore(v3): bump to v3.0.0-alpha.71 and update changelog [skip ci] --- docs/src/content/docs/changelog.mdx | 10 ++++++++++ v3/UNRELEASED_CHANGELOG.md | 4 ---- v3/internal/version/version.txt | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/docs/src/content/docs/changelog.mdx b/docs/src/content/docs/changelog.mdx index 28d3e712a..b64011ad1 100644 --- a/docs/src/content/docs/changelog.mdx +++ b/docs/src/content/docs/changelog.mdx @@ -30,6 +30,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 */ ## [Unreleased] +## v3.0.0-alpha.71 - 2026-02-10 + +## Added +- Bumped ghw version for better Apple device support by @leaanthony (#4977) +- Add `GetBadge` method to the dock service + +## Fixed +- Fix GTK4 build failure caused by C `Screen` typedef colliding with X11 Xlib.h (#4957) +- Fix dock badge methods consistency on macOS + ## v3.0.0-alpha.70 - 2026-02-09 ## Added diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index fbd0259cf..8e4648038 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -17,16 +17,12 @@ After processing, the content will be moved to the main changelog and this file ## Added -- Bumped ghw version for better Apple device support by @leaanthony (#4977) -- Add `GetBadge` method to the dock service ## Changed ## Fixed -- Fix GTK4 build failure caused by C `Screen` typedef colliding with X11 Xlib.h (#4957) -- Fix dock badge methods consistency on macOS ## Deprecated diff --git a/v3/internal/version/version.txt b/v3/internal/version/version.txt index 1230907f6..77fbee776 100644 --- a/v3/internal/version/version.txt +++ b/v3/internal/version/version.txt @@ -1 +1 @@ -v3.0.0-alpha.70 \ No newline at end of file +v3.0.0-alpha.71 \ No newline at end of file From 8f191372c49956c5d0eb0f53ec2e59e79f0e7cfb Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Sat, 14 Feb 2026 00:55:40 +1100 Subject: [PATCH 09/17] Add Claude Code GitHub Workflow (#4988) * "Claude PR Assistant workflow" * "Claude Code Review workflow" --- .github/workflows/claude-code-review.yml | 44 +++++++++++++++++++++ .github/workflows/claude.yml | 50 ++++++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 .github/workflows/claude-code-review.yml create mode 100644 .github/workflows/claude.yml diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml new file mode 100644 index 000000000..b5e8cfd4d --- /dev/null +++ b/.github/workflows/claude-code-review.yml @@ -0,0 +1,44 @@ +name: Claude Code Review + +on: + pull_request: + types: [opened, synchronize, ready_for_review, reopened] + # Optional: Only run on specific file changes + # paths: + # - "src/**/*.ts" + # - "src/**/*.tsx" + # - "src/**/*.js" + # - "src/**/*.jsx" + +jobs: + claude-review: + # Optional: Filter by PR author + # if: | + # github.event.pull_request.user.login == 'external-contributor' || + # github.event.pull_request.user.login == 'new-developer' || + # github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' + + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + issues: read + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Run Claude Code Review + id: claude-review + uses: anthropics/claude-code-action@v1 + with: + claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + plugin_marketplaces: 'https://github.com/anthropics/claude-code.git' + plugins: 'code-review@claude-code-plugins' + prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}' + # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md + # or https://code.claude.com/docs/en/cli-reference for available options + diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml new file mode 100644 index 000000000..d300267f1 --- /dev/null +++ b/.github/workflows/claude.yml @@ -0,0 +1,50 @@ +name: Claude Code + +on: + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + issues: + types: [opened, assigned] + pull_request_review: + types: [submitted] + +jobs: + claude: + if: | + (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || + (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + issues: read + id-token: write + actions: read # Required for Claude to read CI results on PRs + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Run Claude Code + id: claude + uses: anthropics/claude-code-action@v1 + with: + claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + + # This is an optional setting that allows Claude to read CI results on PRs + additional_permissions: | + actions: read + + # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it. + # prompt: 'Update the pull request description to include a summary of changes.' + + # Optional: Add claude_args to customize behavior and configuration + # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md + # or https://code.claude.com/docs/en/cli-reference for available options + # claude_args: '--allowed-tools Bash(gh pr:*)' + From 355d91707c269b531f9410e14a16fcfcc003ff52 Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Sun, 15 Feb 2026 14:41:37 +1100 Subject: [PATCH 10/17] fix(ci): correct Claude Code Review action configuration - Use ANTHROPIC_API_KEY environment variable instead of invalid claude_code_oauth_token parameter - Remove unsupported plugin parameters - Update permissions to allow PR comments (pull-requests: write) - Simplify prompt to standard code review request Fixes the OIDC token error by using the correct action configuration. Refs: https://github.com/anthropics/claude-code-action --- .github/workflows/claude-code-review.yml | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index b5e8cfd4d..fa7ab9822 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -21,9 +21,8 @@ jobs: runs-on: ubuntu-latest permissions: contents: read - pull-requests: read - issues: read - id-token: write + pull-requests: write + issues: write steps: - name: Checkout repository @@ -35,10 +34,7 @@ jobs: id: claude-review uses: anthropics/claude-code-action@v1 with: - claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - plugin_marketplaces: 'https://github.com/anthropics/claude-code.git' - plugins: 'code-review@claude-code-plugins' - prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}' - # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md - # or https://code.claude.com/docs/en/cli-reference for available options + prompt: 'Review this pull request for code quality, potential bugs, and suggest improvements. Focus on the changes made in this PR.' + env: + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} From 347044f4eaafe41e1939662b8cbba8153de43912 Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Sun, 15 Feb 2026 14:45:00 +1100 Subject: [PATCH 11/17] fix(ci): temporarily disable Claude Code Review until secret is configured The workflow requires ANTHROPIC_API_KEY secret to be set in repository settings. Without it, the action attempts OIDC authentication which fails. To enable: 1. Go to https://github.com/wailsapp/wails/settings/secrets/actions 2. Add ANTHROPIC_API_KEY with your Anthropic API key 3. Remove the 'if: false' condition from the workflow file Refs: https://github.com/anthropics/claude-code-action --- .github/workflows/claude-code-review.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index fa7ab9822..b85b8dfbc 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -12,6 +12,10 @@ on: jobs: claude-review: + # Temporarily disabled until ANTHROPIC_API_KEY secret is configured + # To enable: Remove the 'if: false' line below and add ANTHROPIC_API_KEY to repository secrets + if: false + # Optional: Filter by PR author # if: | # github.event.pull_request.user.login == 'external-contributor' || From bb5f0edc8b90d0c184a3f1df0151ee3d0befa1fb Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Sun, 15 Feb 2026 14:56:19 +1100 Subject: [PATCH 12/17] chore(ci): remove Claude Code Review workflows These workflows require paid Anthropic API keys. Removing to avoid unnecessary CI failures and costs. --- .github/workflows/claude-code-review.yml | 44 --------------------- .github/workflows/claude.yml | 50 ------------------------ 2 files changed, 94 deletions(-) delete mode 100644 .github/workflows/claude-code-review.yml delete mode 100644 .github/workflows/claude.yml diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml deleted file mode 100644 index b85b8dfbc..000000000 --- a/.github/workflows/claude-code-review.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: Claude Code Review - -on: - pull_request: - types: [opened, synchronize, ready_for_review, reopened] - # Optional: Only run on specific file changes - # paths: - # - "src/**/*.ts" - # - "src/**/*.tsx" - # - "src/**/*.js" - # - "src/**/*.jsx" - -jobs: - claude-review: - # Temporarily disabled until ANTHROPIC_API_KEY secret is configured - # To enable: Remove the 'if: false' line below and add ANTHROPIC_API_KEY to repository secrets - if: false - - # Optional: Filter by PR author - # if: | - # github.event.pull_request.user.login == 'external-contributor' || - # github.event.pull_request.user.login == 'new-developer' || - # github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' - - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - issues: write - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - name: Run Claude Code Review - id: claude-review - uses: anthropics/claude-code-action@v1 - with: - prompt: 'Review this pull request for code quality, potential bugs, and suggest improvements. Focus on the changes made in this PR.' - env: - ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} - diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml deleted file mode 100644 index d300267f1..000000000 --- a/.github/workflows/claude.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: Claude Code - -on: - issue_comment: - types: [created] - pull_request_review_comment: - types: [created] - issues: - types: [opened, assigned] - pull_request_review: - types: [submitted] - -jobs: - claude: - if: | - (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || - (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: read - issues: read - id-token: write - actions: read # Required for Claude to read CI results on PRs - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - name: Run Claude Code - id: claude - uses: anthropics/claude-code-action@v1 - with: - claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - - # This is an optional setting that allows Claude to read CI results on PRs - additional_permissions: | - actions: read - - # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it. - # prompt: 'Update the pull request description to include a summary of changes.' - - # Optional: Add claude_args to customize behavior and configuration - # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md - # or https://code.claude.com/docs/en/cli-reference for available options - # claude_args: '--allowed-tools Bash(gh pr:*)' - From b2be682176033f73268432f81e20ff5f35b9dcc3 Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Sun, 15 Feb 2026 18:00:20 +1100 Subject: [PATCH 13/17] fix(v3): exclude node_modules from build:frontend sources glob (#4983) * fix(v3): exclude node_modules from build:frontend sources glob The `sources: "**/*"` glob in the build:frontend task causes go-task to enumerate and checksum every file in node_modules during up-to-date checking. With heavy dependencies (e.g. MUI), this means 50-100k+ files are statted, causing 20-30 minute hangs especially on Windows/NTFS. Fixes #4939 Co-Authored-By: Claude Opus 4.6 * docs(v3): add changelog entry for #4939 fix * chore: trigger CI re-run with fixed workflow --------- Co-authored-by: Claude Opus 4.6 --- v3/UNRELEASED_CHANGELOG.md | 2 ++ v3/examples/android/build/Taskfile.yml | 1 + v3/examples/badge-custom/build/Taskfile.yml | 1 + v3/examples/badge/build/Taskfile.yml | 1 + v3/examples/custom-protocol-example/build/Taskfile.yml | 1 + v3/examples/dock/build/Taskfile.yml | 1 + v3/examples/file-association/build/Taskfile.common.yml | 1 + v3/examples/ios/build/Taskfile.yml | 1 + v3/examples/notifications/build/Taskfile.yml | 1 + v3/internal/commands/build_assets/Taskfile.tmpl.yml | 1 + 10 files changed, 11 insertions(+) diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index 8e4648038..f52aeedc9 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -24,6 +24,8 @@ After processing, the content will be moved to the main changelog and this file ## Fixed +- Fix 20-30 minute hangs during `wails3 build` and `wails3 dev` by excluding `node_modules/` from go-task's up-to-date checking. Previously, the `sources: "**/*"` glob caused go-task to enumerate and checksum every file in `node_modules/` (50-100k+ files with heavy dependencies like MUI), especially slow on Windows/NTFS (#4939) + ## Deprecated diff --git a/v3/examples/android/build/Taskfile.yml b/v3/examples/android/build/Taskfile.yml index 209793bfd..e0a74df87 100644 --- a/v3/examples/android/build/Taskfile.yml +++ b/v3/examples/android/build/Taskfile.yml @@ -27,6 +27,7 @@ tasks: dir: frontend sources: - "**/*" + - exclude: node_modules/**/* generates: - dist/**/* deps: diff --git a/v3/examples/badge-custom/build/Taskfile.yml b/v3/examples/badge-custom/build/Taskfile.yml index 5f3517efc..f0aab9b9c 100644 --- a/v3/examples/badge-custom/build/Taskfile.yml +++ b/v3/examples/badge-custom/build/Taskfile.yml @@ -27,6 +27,7 @@ tasks: dir: frontend sources: - "**/*" + - exclude: node_modules/**/* generates: - dist/**/* deps: diff --git a/v3/examples/badge/build/Taskfile.yml b/v3/examples/badge/build/Taskfile.yml index 5f3517efc..f0aab9b9c 100644 --- a/v3/examples/badge/build/Taskfile.yml +++ b/v3/examples/badge/build/Taskfile.yml @@ -27,6 +27,7 @@ tasks: dir: frontend sources: - "**/*" + - exclude: node_modules/**/* generates: - dist/**/* deps: diff --git a/v3/examples/custom-protocol-example/build/Taskfile.yml b/v3/examples/custom-protocol-example/build/Taskfile.yml index ba497b5b6..f3475d2cf 100644 --- a/v3/examples/custom-protocol-example/build/Taskfile.yml +++ b/v3/examples/custom-protocol-example/build/Taskfile.yml @@ -26,6 +26,7 @@ tasks: dir: frontend sources: - "**/*" + - exclude: node_modules/**/* generates: - dist/**/* deps: diff --git a/v3/examples/dock/build/Taskfile.yml b/v3/examples/dock/build/Taskfile.yml index 5f3517efc..f0aab9b9c 100644 --- a/v3/examples/dock/build/Taskfile.yml +++ b/v3/examples/dock/build/Taskfile.yml @@ -27,6 +27,7 @@ tasks: dir: frontend sources: - "**/*" + - exclude: node_modules/**/* generates: - dist/**/* deps: diff --git a/v3/examples/file-association/build/Taskfile.common.yml b/v3/examples/file-association/build/Taskfile.common.yml index 650c8ea83..540c8a991 100644 --- a/v3/examples/file-association/build/Taskfile.common.yml +++ b/v3/examples/file-association/build/Taskfile.common.yml @@ -30,6 +30,7 @@ tasks: dir: frontend sources: - "**/*" + - exclude: node_modules/**/* generates: - dist/* deps: diff --git a/v3/examples/ios/build/Taskfile.yml b/v3/examples/ios/build/Taskfile.yml index 209793bfd..e0a74df87 100644 --- a/v3/examples/ios/build/Taskfile.yml +++ b/v3/examples/ios/build/Taskfile.yml @@ -27,6 +27,7 @@ tasks: dir: frontend sources: - "**/*" + - exclude: node_modules/**/* generates: - dist/**/* deps: diff --git a/v3/examples/notifications/build/Taskfile.yml b/v3/examples/notifications/build/Taskfile.yml index 5f3517efc..f0aab9b9c 100644 --- a/v3/examples/notifications/build/Taskfile.yml +++ b/v3/examples/notifications/build/Taskfile.yml @@ -27,6 +27,7 @@ tasks: dir: frontend sources: - "**/*" + - exclude: node_modules/**/* generates: - dist/**/* deps: diff --git a/v3/internal/commands/build_assets/Taskfile.tmpl.yml b/v3/internal/commands/build_assets/Taskfile.tmpl.yml index 07592bff8..7837ab350 100644 --- a/v3/internal/commands/build_assets/Taskfile.tmpl.yml +++ b/v3/internal/commands/build_assets/Taskfile.tmpl.yml @@ -27,6 +27,7 @@ tasks: dir: frontend sources: - "**/*" + - exclude: node_modules/**/* generates: - dist/**/* deps: From 4aaec93fbb525042161d3420e5ac0d3d201fb52c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 16 Feb 2026 03:10:09 +0000 Subject: [PATCH 14/17] chore(v3): bump to v3.0.0-alpha.72 and update changelog [skip ci] --- docs/src/content/docs/changelog.mdx | 5 +++++ v3/UNRELEASED_CHANGELOG.md | 2 -- v3/internal/version/version.txt | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/src/content/docs/changelog.mdx b/docs/src/content/docs/changelog.mdx index b64011ad1..16206db25 100644 --- a/docs/src/content/docs/changelog.mdx +++ b/docs/src/content/docs/changelog.mdx @@ -30,6 +30,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 */ ## [Unreleased] +## v3.0.0-alpha.72 - 2026-02-16 + +## Fixed +- Fix 20-30 minute hangs during `wails3 build` and `wails3 dev` by excluding `node_modules/` from go-task's up-to-date checking. Previously, the `sources: "**/*"` glob caused go-task to enumerate and checksum every file in `node_modules/` (50-100k+ files with heavy dependencies like MUI), especially slow on Windows/NTFS (#4939) + ## v3.0.0-alpha.71 - 2026-02-10 ## Added diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index f52aeedc9..8e4648038 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -24,8 +24,6 @@ After processing, the content will be moved to the main changelog and this file ## Fixed -- Fix 20-30 minute hangs during `wails3 build` and `wails3 dev` by excluding `node_modules/` from go-task's up-to-date checking. Previously, the `sources: "**/*"` glob caused go-task to enumerate and checksum every file in `node_modules/` (50-100k+ files with heavy dependencies like MUI), especially slow on Windows/NTFS (#4939) - ## Deprecated diff --git a/v3/internal/version/version.txt b/v3/internal/version/version.txt index 77fbee776..080cb6498 100644 --- a/v3/internal/version/version.txt +++ b/v3/internal/version/version.txt @@ -1 +1 @@ -v3.0.0-alpha.71 \ No newline at end of file +v3.0.0-alpha.72 \ No newline at end of file From 6ad671f34dfc173d4a2a14ca7af8b36a203d431c Mon Sep 17 00:00:00 2001 From: StupidBear Date: Thu, 26 Feb 2026 18:10:42 +0800 Subject: [PATCH 15/17] fix (darwin/v3) can't minimise in frameless (#5016) * fix v3 can't minimise in frameless * update UNRELEASED_CHANGELOG --- v3/UNRELEASED_CHANGELOG.md | 1 + v3/pkg/application/webview_window_darwin.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index 8e4648038..ac6c10de9 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -23,6 +23,7 @@ After processing, the content will be moved to the main changelog and this file ## Fixed +- Fix frameless window cannot be minimized on darwin (#4294) ## Deprecated diff --git a/v3/pkg/application/webview_window_darwin.go b/v3/pkg/application/webview_window_darwin.go index ff49ad49b..8fe0ed022 100644 --- a/v3/pkg/application/webview_window_darwin.go +++ b/v3/pkg/application/webview_window_darwin.go @@ -27,7 +27,7 @@ extern void registerListener(unsigned int event); void* windowNew(unsigned int id, int width, int height, bool fraudulentWebsiteWarningEnabled, bool frameless, bool enableDragAndDrop, struct WebviewPreferences preferences) { NSWindowStyleMask styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable; if (frameless) { - styleMask = NSWindowStyleMaskBorderless | NSWindowStyleMaskResizable; + styleMask = NSWindowStyleMaskBorderless | NSWindowStyleMaskResizable | NSWindowStyleMaskMiniaturizable; } WebviewWindow* window = [[WebviewWindow alloc] initWithContentRect:NSMakeRect(0, 0, width-1, height-1) styleMask:styleMask From 972bb6faa366ac715094095b64c852a65507539b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 27 Feb 2026 03:04:44 +0000 Subject: [PATCH 16/17] chore(v3): bump to v3.0.0-alpha.73 and update changelog [skip ci] --- docs/src/content/docs/changelog.mdx | 5 +++++ v3/UNRELEASED_CHANGELOG.md | 1 - v3/internal/version/version.txt | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/src/content/docs/changelog.mdx b/docs/src/content/docs/changelog.mdx index 16206db25..3c30c24e1 100644 --- a/docs/src/content/docs/changelog.mdx +++ b/docs/src/content/docs/changelog.mdx @@ -30,6 +30,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 */ ## [Unreleased] +## v3.0.0-alpha.73 - 2026-02-27 + +## Fixed +- Fix frameless window cannot be minimized on darwin (#4294) + ## v3.0.0-alpha.72 - 2026-02-16 ## Fixed diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index ac6c10de9..8e4648038 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -23,7 +23,6 @@ After processing, the content will be moved to the main changelog and this file ## Fixed -- Fix frameless window cannot be minimized on darwin (#4294) ## Deprecated diff --git a/v3/internal/version/version.txt b/v3/internal/version/version.txt index 080cb6498..c4b06870c 100644 --- a/v3/internal/version/version.txt +++ b/v3/internal/version/version.txt @@ -1 +1 @@ -v3.0.0-alpha.72 \ No newline at end of file +v3.0.0-alpha.73 \ No newline at end of file From 78e701fd548f893236e0db3c5d98a739007eefed Mon Sep 17 00:00:00 2001 From: Zach Botterman <6074435+popaprozac@users.noreply.github.com> Date: Fri, 27 Feb 2026 20:21:10 -0800 Subject: [PATCH 17/17] feat(v3): Modal windows (macOS) (#4839) * modal window * thread safety * update docs * windows support --------- Co-authored-by: Lea Anthony --- .../docs/features/windows/multiple.mdx | 48 ++++++------------- docs/src/content/docs/reference/window.mdx | 25 ++++++++-- v3/UNRELEASED_CHANGELOG.md | 1 + v3/pkg/application/webview_window.go | 17 +++++++ v3/pkg/application/webview_window_android.go | 4 ++ v3/pkg/application/webview_window_darwin.go | 29 +++++++++++ v3/pkg/application/webview_window_ios.go | 4 ++ v3/pkg/application/webview_window_linux.go | 4 ++ v3/pkg/application/webview_window_windows.go | 46 ++++++++++++++++++ v3/pkg/application/window.go | 1 + 10 files changed, 140 insertions(+), 39 deletions(-) diff --git a/docs/src/content/docs/features/windows/multiple.mdx b/docs/src/content/docs/features/windows/multiple.mdx index 41d5a18fe..60694abc9 100644 --- a/docs/src/content/docs/features/windows/multiple.mdx +++ b/docs/src/content/docs/features/windows/multiple.mdx @@ -288,7 +288,7 @@ func CreateToolPalette(app *application.Application) *application.WebviewWindow } ``` -### Pattern 4: Modal dialogs +### Pattern 4: Modal dialogs (macOS only) Child windows that block parent: @@ -303,14 +303,7 @@ func ShowModaldialog(parent *application.WebviewWindow, title string) { Resizable: false, }) - // Disable parent (platform-specific) - parent.SetEnabled(false) - - // Re-enable parent on close - dialog.OnDestroy(func() { - parent.SetEnabled(true) - parent.SetFocus() - }) + parent.AttachModal(dialog) } ``` @@ -359,51 +352,38 @@ func (e *EditorApp) TogglePreview() { ```go childWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Child Window", - Parent: parentWindow, }) + +parentWindow.AttachModal(childWindow) ``` **Behaviour:** -- Child closes when parent closes -- Child stays above parent (on some platforms) -- Child minimises with parent (on some platforms) +- Child stays above parent +- Child moves with parent +- Child blocks interaction to parent **Platform support:** -| Feature | macOS | Windows | Linux | -|---------|-------|---------|-------| -| Auto-close | ✅ | ✅ | ⚠️ Varies | -| Stay above | ✅ | ⚠️ Partial | ⚠️ Varies | -| Minimise together | ✅ | ❌ | ⚠️ Varies | +| macOS | Windows | Linux | +|-------|---------|-------| +| ✅ | ❌ | ❌ | ### Modal Behaviour Create modal-like behaviour: ```go -func ShowModal(parent *application.WebviewWindow) { - modal := app.Window.NewWithOptions(application.WebviewWindowOptions{ - Title: "Modal dialog", +func ShowModaldialog(parent *application.WebviewWindow, title string) { + dialog := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: title, 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 - }) + parent.AttachModal(dialog) } ``` -**Note:** True modal behaviour (blocking) varies by platform. - ## Window Lifecycle Management ### Creation Callbacks diff --git a/docs/src/content/docs/reference/window.mdx b/docs/src/content/docs/reference/window.mdx index 346a326e6..c0fc9c686 100644 --- a/docs/src/content/docs/reference/window.mdx +++ b/docs/src/content/docs/reference/window.mdx @@ -597,12 +597,27 @@ if err != nil { } ``` -**Platform support:** -- **macOS**: Full support -- **Windows**: Full support -- **Linux**: Full support +### AttachModal() -**Note:** This triggers the native OS print dialog, allowing the user to select printer settings and print the current window content. +Attaches a second Window as a sheet modal. + +```go +func (w *Window) AttachModal(modalWindow Window) +``` + +**Parameters:** +- `modalWindow` - The window to attach as a modal + +**Platform support:** +- **macOS**: Full support (presents as a sheet) +- **Windows**: No support +- **Linux**: No support + +**Example:** +```go +modalWindow := app.Window.New() +window.AttachModal(modalWindow) +``` ## Platform-Specific Options diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index 8e4648038..ea616db89 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -17,6 +17,7 @@ After processing, the content will be moved to the main changelog and this file ## Added +- Add support for modal sheets (macOS) ## Changed diff --git a/v3/pkg/application/webview_window.go b/v3/pkg/application/webview_window.go index 6cfa8b821..91909e83e 100644 --- a/v3/pkg/application/webview_window.go +++ b/v3/pkg/application/webview_window.go @@ -113,6 +113,7 @@ type ( setMenu(menu *Menu) snapAssist() setContentProtection(enabled bool) + attachModal(modalWindow *WebviewWindow) } ) @@ -1284,6 +1285,22 @@ func (w *WebviewWindow) NativeWindow() unsafe.Pointer { return w.impl.nativeWindow() } +// AttachModal attaches a modal window to this window, presenting it as a sheet on macOS. +func (w *WebviewWindow) AttachModal(modalWindow Window) { + if w.impl == nil || w.isDestroyed() { + return + } + + modalWebviewWindow, ok := modalWindow.(*WebviewWindow) + if !ok || modalWebviewWindow == nil { + return + } + + InvokeSync(func() { + w.impl.attachModal(modalWebviewWindow) + }) +} + // shouldUnconditionallyClose returns whether the window should close unconditionally func (w *WebviewWindow) shouldUnconditionallyClose() bool { return atomic.LoadUint32(&w.unconditionallyClose) != 0 diff --git a/v3/pkg/application/webview_window_android.go b/v3/pkg/application/webview_window_android.go index ed41a5add..6e139f52c 100644 --- a/v3/pkg/application/webview_window_android.go +++ b/v3/pkg/application/webview_window_android.go @@ -296,6 +296,10 @@ func (w *androidWebviewWindow) nativeWindow() unsafe.Pointer { return nil } +func (w *androidWebviewWindow) attachModal(modalWindow *WebviewWindow) { + // Modal windows are not supported on Android +} + func (w *androidWebviewWindow) on(eventID uint) { // Android event handling } diff --git a/v3/pkg/application/webview_window_darwin.go b/v3/pkg/application/webview_window_darwin.go index 8fe0ed022..14fef21da 100644 --- a/v3/pkg/application/webview_window_darwin.go +++ b/v3/pkg/application/webview_window_darwin.go @@ -331,6 +331,24 @@ void windowZoomOut(void* nsWindow) { } } +// createModalWindow presents a modal window as a sheet attached to the parent window +void createModalWindow(void* parentWindowPtr, void* modalWindowPtr) { + if (parentWindowPtr == NULL || modalWindowPtr == NULL) { + return; + } + + NSWindow* parentWindow = (NSWindow*)parentWindowPtr; + NSWindow* modalWindow = (NSWindow*)modalWindowPtr; + + // Present the modal window as a sheet attached to the parent window + // Must be dispatched to the main thread for UI thread safety + dispatch_async(dispatch_get_main_queue(), ^{ + [parentWindow beginSheet:modalWindow completionHandler:^(NSModalResponse returnCode) { + // Sheet was dismissed - window will be released automatically + }]; + }); +} + // set the window position relative to the screen void windowSetRelativePosition(void* nsWindow, int x, int y) { WebviewWindow* window = (WebviewWindow*)nsWindow; @@ -1551,6 +1569,17 @@ func (w *macosWebviewWindow) setContentProtection(enabled bool) { C.setContentProtection(w.nsWindow, C.bool(enabled)) } +func (w *macosWebviewWindow) attachModal(modalWindow *WebviewWindow) { + if modalWindow == nil || modalWindow.impl == nil || modalWindow.isDestroyed() { + return + } + modalNativeWindow := modalWindow.impl.nativeWindow() + if modalNativeWindow == nil { + return + } + C.createModalWindow(w.nsWindow, modalNativeWindow) +} + func (w *macosWebviewWindow) cut() { } diff --git a/v3/pkg/application/webview_window_ios.go b/v3/pkg/application/webview_window_ios.go index bc3087f2c..481788c87 100644 --- a/v3/pkg/application/webview_window_ios.go +++ b/v3/pkg/application/webview_window_ios.go @@ -330,6 +330,10 @@ func (w *iosWebviewWindow) nativeWindow() unsafe.Pointer { return w.nativeHandle } +func (w *iosWebviewWindow) attachModal(modalWindow *WebviewWindow) { + // Modal windows are not supported on iOS +} + func (w *iosWebviewWindow) on(eventID uint) { // iOS event handling } diff --git a/v3/pkg/application/webview_window_linux.go b/v3/pkg/application/webview_window_linux.go index 9a5eb4131..4eddadbed 100644 --- a/v3/pkg/application/webview_window_linux.go +++ b/v3/pkg/application/webview_window_linux.go @@ -410,6 +410,10 @@ func (w *linuxWebviewWindow) nativeWindow() unsafe.Pointer { return unsafe.Pointer(w.window) } +func (w *linuxWebviewWindow) attachModal(modalWindow *WebviewWindow) { + // Modal windows are not supported on Linux +} + func (w *linuxWebviewWindow) print() error { w.execJS("window.print();") return nil diff --git a/v3/pkg/application/webview_window_windows.go b/v3/pkg/application/webview_window_windows.go index 16ba08f0b..434723fb4 100644 --- a/v3/pkg/application/webview_window_windows.go +++ b/v3/pkg/application/webview_window_windows.go @@ -78,6 +78,9 @@ type windowsWebviewWindow struct { // menubarTheme is the theme for the menubar menubarTheme *w32.MenuBarTheme + + // Modal window tracking + parentHWND w32.HWND // Parent window HWND when this window is a modal } func (w *windowsWebviewWindow) setMenu(menu *Menu) { @@ -233,6 +236,37 @@ func (w *windowsWebviewWindow) startDrag() error { return nil } +func (w *windowsWebviewWindow) attachModal(modalWindow *WebviewWindow) { + if modalWindow == nil || modalWindow.impl == nil || modalWindow.isDestroyed() { + return + } + + // Get the modal window's Windows implementation + modalWindowsImpl, ok := modalWindow.impl.(*windowsWebviewWindow) + if !ok { + return + } + + parentHWND := w.hwnd + modalHWND := modalWindowsImpl.hwnd + + // Set parent-child relationship using GWLP_HWNDPARENT + // This ensures the modal stays above parent and moves with it + w32.SetWindowLongPtr(modalHWND, w32.GWLP_HWNDPARENT, uintptr(parentHWND)) + + // Track the parent HWND in the modal window for cleanup + modalWindowsImpl.parentHWND = parentHWND + + // Disable the parent window to block interaction (Microsoft's recommended approach) + // This follows Windows modal dialog best practices + w32.EnableWindow(parentHWND, false) + + // Ensure modal window is shown and brought to front + w32.ShowWindow(modalHWND, w32.SW_SHOW) + w32.SetForegroundWindow(modalHWND) + w32.BringWindowToTop(modalHWND) +} + func (w *windowsWebviewWindow) nativeWindow() unsafe.Pointer { return unsafe.Pointer(w.hwnd) } @@ -725,6 +759,12 @@ func (w *windowsWebviewWindow) setRelativePosition(x int, y int) { } func (w *windowsWebviewWindow) destroy() { + // Re-enable parent window if this was a modal window + if w.parentHWND != 0 { + w32.EnableWindow(w.parentHWND, true) + w.parentHWND = 0 + } + w.parent.markAsDestroyed() // destroy the window w32.DestroyWindow(w.hwnd) @@ -1420,6 +1460,12 @@ func (w *windowsWebviewWindow) WndProc(msg uint32, wparam, lparam uintptr) uintp } defer func() { + // Re-enable parent window if this was a modal window + if w.parentHWND != 0 { + w32.EnableWindow(w.parentHWND, true) + w.parentHWND = 0 + } + windowsApp := globalApplication.impl.(*windowsApp) windowsApp.unregisterWindow(w) diff --git a/v3/pkg/application/window.go b/v3/pkg/application/window.go index 3f4949b16..ec810b115 100644 --- a/v3/pkg/application/window.go +++ b/v3/pkg/application/window.go @@ -91,6 +91,7 @@ type Window interface { Flash(enabled bool) Print() error RegisterHook(eventType events.WindowEventType, callback func(event *WindowEvent)) func() + AttachModal(modalWindow Window) shouldUnconditionallyClose() bool // Editing methods