Skip to content

v3: replace 11 direct dependencies with internal stdlib implementations - #5508

Merged
leaanthony merged 7 commits into
masterfrom
reduce-deps
May 30, 2026
Merged

leaanthony merged 7 commits into
masterfrom
reduce-deps

Conversation

@leaanthony

@leaanthony leaanthony commented May 25, 2026 •

Copy link
Copy Markdown
Member

Summary

Removes 11 direct Go dependencies from v3/go.mod by replacing them with small internal packages backed entirely by the standard library.

Removed dependency Replacement
github.com/Masterminds/semver v1 internal/semver — parse + compare, fields not methods
github.com/bep/debounce internal/debounce — mutex + time.AfterFunc
github.com/google/shlex inline splitShell() in internal/s/s.go
github.com/google/uuid internal/uuid — v4 (crypto/rand) + v5 (SHA-1)
github.com/leaanthony/go-ansi-parser pluggable SystemTrayLabelParser var (see below)
github.com/leaanthony/gosod internal/gosod — fs.WalkDir + text/template
github.com/leaanthony/u internal/optional — generic Var[T] + Bool alias
github.com/lmittmann/tint internal/tint — slog.Handler with ANSI colour
github.com/pkg/browser internal/browser — exec.Command(open/xdg-open/rundll32)
github.com/pkg/errors stdlib fmt.Errorf("…: %w", err) / errors.New
github.com/samber/lo internal/lo — 8 generic helpers (Go 1.18+)

Some packages still appear as // indirect in go.mod because transitive dependencies (e.g. atterpac/refresh) still reference them — they are no longer direct dependencies of this module.

SystemTrayLabelParser (macOS)

Rather than hard-wiring go-ansi-parser for macOS system-tray label rendering, the parser is now a pluggable package-level variable:

// Default: strips ANSI escape codes, returns plain text.
var SystemTrayLabelParser func(label string) ([]SystemTrayLabelPart, error) = stripANSI

// To use a full ANSI parser, replace before app.Run():
application.SystemTrayLabelParser = myAnsiParser

Test plan

  • go build ./... — no compilation errors
  • wails3 init -t vanilla — project created, templates substituted correctly
  • wails3 generate build-assets — build assets extracted correctly
  • wails3 service init — service scaffolded correctly

Summary by CodeRabbit

  • Refactor
    • Replaced many external utilities with in‑project implementations for consistency and reduced external surface area.
  • New Features
    • System tray labels support styled/segmented parsing; added internal utilities: semver, UUIDs, debounce, optional values, templated asset extraction, colorized dev logger, and a cross‑platform open URL/file helper.
  • Tests
    • Comprehensive unit and integration tests added for the new internal utilities and template extraction logic.
  • Chores
    • Trimmed and reorganized dependency declarations.

Review Change Stack

Copilot AI review requested due to automatic review settings May 25, 2026 10:46
@coderabbitai

coderabbitai Bot commented May 25, 2026 •

Copy link
Copy Markdown
Contributor

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Introduces internal replacements for multiple external utilities, migrates imports across the codebase to these internal packages, standardizes error wrapping, updates system-tray label parsing, and adds comprehensive tests and go.mod updates.

Changes

Internal Utility Package Creation

Layer / File(s) Summary
Collection helpers (lo)
v3/internal/lo/lo.go, v3/internal/lo/lo_test.go
Adds generic helpers: Associate, Contains, ContainsBy, Find, Keys, Must, Ternary, Without and tests.
UUID (uuid)
v3/internal/uuid/uuid.go, v3/internal/uuid/uuid_test.go
Adds UUID type, New, NewSHA1, Nil, and String() with tests validating RFC 4122 bits and formatting.
Browser opener
v3/internal/browser/*, v3/internal/browser/browser_test.go, v3/internal/browser/browser_integration_test.go
Adds OpenURL/OpenFile API and platform-specific open implementations (darwin, windows, other) with unit and integration tests.
Debounce
v3/internal/debounce/debounce.go, v3/internal/debounce/debounce_test.go
Adds debouncer New(after) returning a scheduling closure and tests for generation safety, timer reuse, and zero-duration behavior.
Optional
v3/internal/optional/optional.go, v3/internal/optional/optional_test.go
Adds generic Var[T], NewVar, Bool alias, NewBool, package True/False and tests.
Gosod (template extractor)
v3/internal/gosod/gosod.go, v3/internal/gosod/gosod_test.go, v3/internal/gosod/gosod_unix_test.go
Implements TemplateDir extraction, template rendering, rename/ignore rules, directory creation, copying, and extensive tests including error paths.
SemVer
v3/internal/semver/semver.go, v3/internal/semver/semver_test.go
Adds Version struct, NewVersion parser, String, comparison semantics honoring prerelease precedence; tests cover parsing and ordering.
Tint logger
v3/internal/tint/tint.go, v3/internal/tint/tint_test.go
Adds color/no-color slog.Handler with options and many formatting/attribute/group tests.
Shell splitter
v3/internal/s/s.go
Replaces external shlex with internal splitShell for quoted/backslash-aware argument splitting used by EXEC.

Import Migration to Internal Packages

Layer / File(s) Summary
Migrate lo usages
multiple v3/* files (CLI, examples, doctor, tasks, application debug)
Replace github.com/samber/lo imports with github.com/wailsapp/wails/v3/internal/lo.
Migrate browser and uuid usages
v3/pkg/application/*, v3/internal/setupwizard/*, v3/internal/go-common-file-dialog/*, v3/pkg/services/notifications/*
Call sites now use internal/browser and internal/uuid.
Migrate gosod, tint, debounce, optional, semver
various internal and pkg files
Files updated to use internal implementations instead of external modules.
Tooling changes
v3/internal/commands/tool_version.go, v3/internal/github/semver.go
Tooling now reads semver.Version fields and calls its comparison helpers.

Behavioral Changes & Error Handling

Layer / File(s) Summary
System tray ANSI label parsing
v3/pkg/application/systemtray.go, v3/pkg/application/systemtray_darwin.go
Adds SystemTrayLabelPart, SystemTrayLabelParser hook and macOS code to build attributed strings from parsed parts, freeing C strings properly.
Standardize error wrapping
v3/internal/commands/syso.go, v3/pkg/services/kvstore/kvstore.go, v3/pkg/services/sqlite/sqlite.go, v3/internal/templates/templates.go
Replace github.com/pkg/errors with fmt.Errorf(...%w...) and stdlib errors usage; refine error-return logic.

Dependency Management

Layer / File(s) Summary
go.mod updates
v3/go.mod
Removes or reclassifies direct requires for packages now provided internally and moves some dependencies to // indirect as appropriate.

Estimated code review effort:
🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • wailsapp/wails#4359: Touches BrowserManager and browser integration; related to this PR's browser internalization changes.

Suggested labels

Enhancement, go, size:XXL, v3, lgtm

"I nibble at imports through the night, 🐰
Pull in patches, make the internals tight,
New helpers hum, old externals gone,
Tests hop forward with the morning sun,
A tidy burrow, code snug and right."

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch reduce-deps

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR reduces the v3 module’s direct Go dependency surface by replacing several third-party libraries with small internal stdlib-backed implementations, while also adding first-class support for Garble-based obfuscated builds (stable binding method IDs, Taskfile plumbing, and docs). It additionally makes macOS system-tray ANSI label parsing pluggable to avoid hard-wiring an ANSI parsing dependency.

Changes:

  • Replaced several direct deps (e.g. lo, uuid, debounce, browser, tint, semver, gosod, shlex, pkg/errors) with internal packages / stdlib equivalents and updated call sites.
  • Added obfuscation support: stable binding method ID registration + generator emission of wails_obfuscated.gen.go, plus wails3 build --obfuscated --garbleargs wiring into platform Taskfiles and the cross-build Docker image.
  • Added/updated docs and changelog entries for obfuscated builds and related CLI flags.

Reviewed changes

Copilot reviewed 68 out of 69 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
v3/UNRELEASED_CHANGELOG.md Adds unreleased changelog entry describing Garble obfuscation support.
v3/tasks/sed/sed.go Switches from external lo to internal/lo.
v3/pkg/services/sqlite/sqlite.go Replaces pkg/errors wrapping with stdlib fmt.Errorf/errors.
v3/pkg/services/notifications/notifications_windows.go Switches UUID generation to internal/uuid.
v3/pkg/services/kvstore/kvstore.go Replaces pkg/errors wrapping with stdlib fmt.Errorf.
v3/pkg/doctor-ng/platform_windows.go Switches from external lo to internal/lo.
v3/pkg/doctor-ng/platform_darwin.go Switches from external lo to internal/lo.
v3/pkg/doctor-ng/doctor.go Switches from external lo to internal/lo.
v3/pkg/application/webview_window.go Replaces u with internal/optional and updates Enabled/Disabled aliases.
v3/pkg/application/webview_window_windows.go Replaces external debounce with internal/debounce.
v3/pkg/application/webview_window_options.go Replaces u.Bool with optional.Bool in options structs.
v3/pkg/application/webview_window_linux.go Replaces external debounce with internal/debounce.
v3/pkg/application/systemtray.go Introduces SystemTrayLabelPart + pluggable SystemTrayLabelParser with default ANSI stripping.
v3/pkg/application/systemtray_darwin.go Uses SystemTrayLabelParser instead of hard-wired ANSI parser dependency.
v3/pkg/application/screenmanager.go Adds JSON tags to runtime-facing structs for obfuscation-safe wire format.
v3/pkg/application/messageprocessor_browser.go Switches from external browser opener to internal/browser.
v3/pkg/application/logger_dev.go Switches from external tint to internal/tint.
v3/pkg/application/logger_dev_windows.go Switches from external tint to internal/tint.
v3/pkg/application/environment.go Adds JSON tags to EnvironmentInfo fields.
v3/pkg/application/browser_manager.go Switches from external browser opener to internal/browser.
v3/pkg/application/bindings.go Adds global stable binding-method ID registry + uses it when generating bound method IDs.
v3/pkg/application/bindings_test.go Adds tests for registered stable binding method IDs and panic behavior.
v3/pkg/application/application_debug.go Switches from external lo to internal/lo.
v3/internal/uuid/uuid.go New minimal UUID implementation (v4 + name-based SHA-1).
v3/internal/tint/tint.go New internal colorized slog.Handler implementation.
v3/internal/templates/templates.go Switches to internal gosod, internal lo, and stdlib error wrapping.
v3/internal/setupwizard/wizard.go Switches from external browser opener to internal/browser.
v3/internal/service/service.go Switches to internal gosod and internal lo.
v3/internal/semver/semver.go New minimal semver parsing/comparison implementation.
v3/internal/s/s.go Inlines shell-splitting logic (replacing google/shlex).
v3/internal/optional/optional.go New generic optional value implementation replacing leaanthony/u.
v3/internal/operatingsystem/os.go Adds JSON tags to OS info fields.
v3/internal/lo/lo.go New minimal generic helper set replacing samber/lo.
v3/internal/gosod/gosod.go New internal template FS extractor replacing external gosod.
v3/internal/go-common-file-dialog/util/util.go Switches to internal UUID implementation for deterministic GUID creation.
v3/internal/go-common-file-dialog/cfd/iFileOpenDialog.go Switches to internal UUID implementation.
v3/internal/github/semver.go Adapts GitHub semver helpers to new internal semver struct/fields.
v3/internal/generator/generate.go Adds obfuscated bindings aggregation plumbing + main-package dir detection.
v3/internal/generator/binding_ids.go New aggregated wails_obfuscated.gen.go emitter (imports, aliasing, init registrations).
v3/internal/generator/binding_ids_test.go Tests for obfuscated bindings source generation and alias/self-import rules.
v3/internal/flags/task_wrapper.go Adds build flags for --obfuscated and --garbleargs.
v3/internal/flags/bindings.go Adds -obfuscated and -obfuscated-output flags for bindings generation.
v3/internal/doctor/doctor.go Switches from external lo to internal/lo.
v3/internal/doctor/doctor_windows.go Switches from external lo to internal/lo.
v3/internal/doctor/doctor_darwin.go Switches from external lo to internal/lo.
v3/internal/debounce/debounce.go New internal debounce implementation replacing external dep.
v3/internal/commands/tool_version.go Updates version field access to match new internal semver representation.
v3/internal/commands/taskfile_obfuscation_test.go Adds tests asserting Taskfile templates include obfuscation support.
v3/internal/commands/task_wrapper.go Wires --obfuscated / --garbleargs into Taskfile variables.
v3/internal/commands/task_wrapper_test.go Adds test coverage for obfuscation args passed through Build wrapper.
v3/internal/commands/syso.go Replaces pkg/errors wrapping with stdlib fmt.Errorf.
v3/internal/commands/build-assets.go Switches to internal gosod.
v3/internal/commands/build_assets/windows/Taskfile.yml Adds obfuscated build support (garble, tags, env passthrough).
v3/internal/commands/build_assets/Taskfile.tmpl.yml Adds -obfuscated forwarding to wails3 generate bindings.
v3/internal/commands/build_assets/linux/Taskfile.yml Adds obfuscated build support (garble, tags, env passthrough).
v3/internal/commands/build_assets/docker/Dockerfile.cross Bumps Go base image, installs garble, and supports obfuscated builds in build script.
v3/internal/commands/build_assets/darwin/Taskfile.yml Adds obfuscated build support (garble, tags, env passthrough).
v3/internal/commands/appimage_testfiles/main.go Switches from external lo to internal/lo.
v3/internal/capabilities/capabilities.go Adds JSON tags to capabilities struct.
v3/internal/buildinfo/buildinfo.go Switches from external lo to internal/lo.
v3/internal/browser/browser.go New internal URL/file opener replacing pkg/browser.
v3/go.sum Removes checksums for removed direct deps; updates indirect entries.
v3/go.mod Removes 11 direct deps; adjusts some entries to indirect and tweaks x/mod directness.
v3/examples/window/main.go Switches from external lo to internal/lo.
v3/cmd/wails3/main.go Switches from external browser/lo to internal equivalents.
docs/src/content/docs/reference/cli.mdx Documents new wails3 build --obfuscated/--garbleargs and generate bindings -obfuscated flags.
docs/src/content/docs/guides/build/obfuscation.mdx New guide describing Garble obfuscated build workflow and requirements.
docs/astro.config.mjs Adds sidebar link to new obfuscation guide.
.github/workflows/build-cross-image.yml Updates CI Go version used to build the cross-compilation image.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread v3/pkg/application/systemtray_darwin.go Resolved
Comment thread v3/internal/tint/tint.go Resolved
Comment thread v3/internal/s/s.go Resolved
Comment thread v3/internal/semver/semver.go Resolved
Comment thread v3/internal/generator/binding_ids.go Resolved

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (1)
.github/workflows/build-cross-image.yml (1)

149-149: ⚡ Quick win

Use consistent Go versions across CI workflows (build-cross-image.yml sets 1.26 while most others use 1.25)

Go 1.26 is released and can be installed via actions/setup-go, so this change shouldn’t fail for availability reasons. However, .github/workflows/build-cross-image.yml is the only workflow using go-version: '1.26', while the rest of .github/workflows/ (and v3/go.mod) use 1.25—either update the other workflows intentionally or switch this one back to 1.25 for consistency.

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

In @.github/workflows/build-cross-image.yml at line 149, The workflow is
inconsistent: it sets go-version: '1.26' while other CI workflows and v3/go.mod
use 1.25; update the workflow's go-version key from '1.26' to '1.25' (replace
the string value for the go-version input) so all workflows use the same Go
version, or alternatively update the other workflows and v3/go.mod to '1.26' if
you intend to standardize on Go 1.26—ensure the change targets the go-version
property in the build-cross-image workflow (the line containing go-version) and
keep versions consistent across CI and go.mod.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@v3/internal/commands/syso.go`:
- Line 47: The error assignment that handles iconFile.Close() currently formats
err2 with "%s", losing the original error chain; change the fmt.Errorf call that
sets err (the one referencing err2) to wrap the underlying error using "%w" so
callers can unwrap/inspect the original iconFile.Close() error (i.e., replace
the "%s" formatting of err2 with "%w" when constructing the error).

In `@v3/internal/debounce/debounce.go`:
- Around line 23-30: The current debouncer.add implementation can let stale
callbacks run because time.AfterFunc spawns the callback if Stop returns false;
fix this by adding a generation/guard on the debouncer (e.g., a uint64
generation field) and incrementing it under d.mu before creating the new timer,
then pass a wrapper to time.AfterFunc that captures the generation value and
checks (using either the mutex or an atomic read of d.generation) that the
captured generation still matches the debouncer's current generation before
invoking f; ensure you still Stop the old timer and assign the new timer to
d.timer and update generation atomically/under lock so only the latest
invocation's f executes.

In `@v3/internal/s/s.go`:
- Around line 269-314: splitShell corrupts UTF-8 by indexing s as bytes and can
return an empty slice for whitespace-only input; change iteration to range over
runes (for _, r := range s) so you always work with full runes, replace
WriteByte/WriteRune usage accordingly (use cur.WriteRune for runes and when
consuming an escaped rune), handle backslash escape by peeking/consuming the
next rune from a rune slice or using an index over runes, preserve quote and
escape logic but operating on runes, and at the end return an error (e.g.,
fmt.Errorf("no tokens in input") ) if args is empty or input contains only
whitespace; update function splitShell to use these rune-based operations and
the new empty-input error.

In `@v3/internal/semver/semver.go`:
- Around line 90-100: The prerelease comparison using strings.Compare is
incorrect; instead split v.Prerelease and other.Prerelease on '.' and compare
each dot-separated identifier in order: if both identifiers are numeric (use
strconv.Atoi) compare numerically; if one is numeric and the other is
non-numeric treat the numeric identifier as lower precedence; if both are
non-numeric compare lexically (ASCII); if all compared identifiers are equal the
shorter list has lower precedence. Replace the default branch that returns
strings.Compare(v.Prerelease, other.Prerelease) with this dot-segmented,
numeric-aware comparison using v.Prerelease and other.Prerelease.

In `@v3/internal/tint/tint.go`:
- Around line 110-122: WithAttrs (and similarly WithGroup) currently risks slice
aliasing by doing preformatted: append(h.preformatted, buf.Bytes()...) and
groups: h.groups / groups: append(h.groups, name); fix by explicitly allocating
and copying new slices so derived handlers never share backing arrays: create a
new preformatted slice via append([]byte(nil), h.preformatted...) then append
the new bytes, and set groups to append(append([]string(nil), h.groups...),
name) (or use make+copy) so both preformatted and groups are fresh copies in the
new handler.

In `@v3/pkg/application/systemtray_darwin.go`:
- Around line 250-283: The ANSI-label path leaks C.CString allocations because
partToCStrings() creates C strings that are passed into
createAttributedString/appendAttributedString but never freed; update setLabel
(and partToCStrings usage) to free each C string after it's handed to the native
calls: for the first part, call createAttributedString(...) and then free
label/fg/bg (using C.free(unsafe.Pointer(...)) for each non-nil pointer), and
for each subsequent part call appendAttributedString(...) and then free that
part's label/fg/bg similarly; import the unsafe package if not present. Ensure
you free only after the native call returns (or change the native API to take
ownership and document it) and keep the existing non-ANSI path that relies on
systemTraySetLabel freeing its argument.

In `@v3/pkg/services/sqlite/sqlite.go`:
- Line 473: The current return unconditionally wraps the result of
s.sqlStmt.Close(), causing a non-nil error even when Close() succeeds; change
the logic in the method that calls s.sqlStmt.Close() so you capture err :=
s.sqlStmt.Close(), then if err != nil return fmt.Errorf("error closing prepared
statement: %w", err) else return nil (i.e., only wrap and return when err is
non-nil) to ensure successful closes return nil.

---

Nitpick comments:
In @.github/workflows/build-cross-image.yml:
- Line 149: The workflow is inconsistent: it sets go-version: '1.26' while other
CI workflows and v3/go.mod use 1.25; update the workflow's go-version key from
'1.26' to '1.25' (replace the string value for the go-version input) so all
workflows use the same Go version, or alternatively update the other workflows
and v3/go.mod to '1.26' if you intend to standardize on Go 1.26—ensure the
change targets the go-version property in the build-cross-image workflow (the
line containing go-version) and keep versions consistent across CI and go.mod.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fa289d61-fd03-4e16-b0a3-61c30069aaa1

📥 Commits

Reviewing files that changed from the base of the PR and between 0c93e26 and 75b2de2.

⛔ Files ignored due to path filters (1)
  • v3/go.sum is excluded by !**/*.sum
📒 Files selected for processing (68)
  • .github/workflows/build-cross-image.yml
  • docs/astro.config.mjs
  • docs/src/content/docs/guides/build/obfuscation.mdx
  • docs/src/content/docs/reference/cli.mdx
  • v3/UNRELEASED_CHANGELOG.md
  • v3/cmd/wails3/main.go
  • v3/examples/window/main.go
  • v3/go.mod
  • v3/internal/browser/browser.go
  • v3/internal/buildinfo/buildinfo.go
  • v3/internal/capabilities/capabilities.go
  • v3/internal/commands/appimage_testfiles/main.go
  • v3/internal/commands/build-assets.go
  • v3/internal/commands/build_assets/Taskfile.tmpl.yml
  • v3/internal/commands/build_assets/darwin/Taskfile.yml
  • v3/internal/commands/build_assets/docker/Dockerfile.cross
  • v3/internal/commands/build_assets/linux/Taskfile.yml
  • v3/internal/commands/build_assets/windows/Taskfile.yml
  • v3/internal/commands/syso.go
  • v3/internal/commands/task_wrapper.go
  • v3/internal/commands/task_wrapper_test.go
  • v3/internal/commands/taskfile_obfuscation_test.go
  • v3/internal/commands/tool_version.go
  • v3/internal/debounce/debounce.go
  • v3/internal/doctor/doctor.go
  • v3/internal/doctor/doctor_darwin.go
  • v3/internal/doctor/doctor_windows.go
  • v3/internal/flags/bindings.go
  • v3/internal/flags/task_wrapper.go
  • v3/internal/generator/binding_ids.go
  • v3/internal/generator/binding_ids_test.go
  • v3/internal/generator/generate.go
  • v3/internal/github/semver.go
  • v3/internal/go-common-file-dialog/cfd/iFileOpenDialog.go
  • v3/internal/go-common-file-dialog/util/util.go
  • v3/internal/gosod/gosod.go
  • v3/internal/lo/lo.go
  • v3/internal/operatingsystem/os.go
  • v3/internal/optional/optional.go
  • v3/internal/s/s.go
  • v3/internal/semver/semver.go
  • v3/internal/service/service.go
  • v3/internal/setupwizard/wizard.go
  • v3/internal/templates/templates.go
  • v3/internal/tint/tint.go
  • v3/internal/uuid/uuid.go
  • v3/pkg/application/application_debug.go
  • v3/pkg/application/bindings.go
  • v3/pkg/application/bindings_test.go
  • v3/pkg/application/browser_manager.go
  • v3/pkg/application/environment.go
  • v3/pkg/application/logger_dev.go
  • v3/pkg/application/logger_dev_windows.go
  • v3/pkg/application/messageprocessor_browser.go
  • v3/pkg/application/screenmanager.go
  • v3/pkg/application/systemtray.go
  • v3/pkg/application/systemtray_darwin.go
  • v3/pkg/application/webview_window.go
  • v3/pkg/application/webview_window_linux.go
  • v3/pkg/application/webview_window_options.go
  • v3/pkg/application/webview_window_windows.go
  • v3/pkg/doctor-ng/doctor.go
  • v3/pkg/doctor-ng/platform_darwin.go
  • v3/pkg/doctor-ng/platform_windows.go
  • v3/pkg/services/kvstore/kvstore.go
  • v3/pkg/services/notifications/notifications_windows.go
  • v3/pkg/services/sqlite/sqlite.go
  • v3/tasks/sed/sed.go

Comment thread v3/internal/commands/syso.go Resolved
Comment thread v3/internal/debounce/debounce.go Resolved
Comment thread v3/internal/s/s.go Resolved
Comment thread v3/internal/semver/semver.go Resolved
Comment thread v3/internal/tint/tint.go Resolved
Comment thread v3/pkg/application/systemtray_darwin.go Resolved
Comment thread v3/pkg/services/sqlite/sqlite.go Resolved
Removes the following direct dependencies from go.mod by inlining
lightweight stdlib-only replacements:

- github.com/Masterminds/semver v1     → internal/semver
- github.com/bep/debounce              → internal/debounce
- github.com/google/shlex             → inline splitShell() in internal/s
- github.com/google/uuid              → internal/uuid
- github.com/leaanthony/go-ansi-parser → pluggable SystemTrayLabelParser var
- github.com/leaanthony/gosod         → internal/gosod
- github.com/leaanthony/u             → internal/optional
- github.com/lmittmann/tint           → internal/tint
- github.com/pkg/browser              → internal/browser
- github.com/pkg/errors               → fmt.Errorf / stdlib errors
- github.com/samber/lo                → internal/lo (generics)

The SystemTrayLabelParser change makes macOS system tray ANSI label
rendering pluggable: the default implementation strips escape codes,
and callers can replace the var with a full ANSI parser such as
go-ansi-parser or any compatible library.

All replacements tested: go build ./..., wails3 init, wails3 generate
build-assets, and wails3 service init all pass cleanly.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (5)
v3/pkg/services/sqlite/sqlite.go (1)

473-473: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Return nil when Stmt.Close() succeeds.

Line 473 wraps and returns err unconditionally. Successful closes should return nil, not a wrapped error value.

💡 Minimal fix
-	return fmt.Errorf("error closing prepared statement: %w", err)
+	if err != nil {
+		return fmt.Errorf("error closing prepared statement: %w", err)
+	}
+	return nil
#!/bin/bash
set -euo pipefail

# Verify the unconditional wrapped return in Stmt.Close.
rg -n -C8 'func \(s \*Stmt\) Close\(\)|return fmt.Errorf\("error closing prepared statement: %w", err\)' v3/pkg/services/sqlite/sqlite.go
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@v3/pkg/services/sqlite/sqlite.go` at line 473, The Stmt.Close() method
currently always wraps and returns err even when Close succeeded; update the
method on type Stmt so that it checks the result of s.stmt.Close() and only
returns fmt.Errorf("error closing prepared statement: %w", err) when err != nil,
otherwise return nil (i.e., call s.stmt.Close(), if err != nil wrap and return
it, else return nil).
v3/pkg/application/systemtray_darwin.go (1)

261-267: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Free C.CString allocations after attributed-string calls.

Lines 261-267 allocate C strings for each parsed part, but those pointers are never freed after C.createAttributedString / C.appendAttributedString return. That leaks native heap on repeated ANSI label updates.

💡 Minimal fix
@@
 `#include` "Cocoa/Cocoa.h"
 `#include` "menuitem_darwin.h"
 `#include` "systemtray_darwin.h"
+#include <stdlib.h>
@@
 func (s *macosSystemTray) setLabel(label string) {
@@
 	cLabel, cFg, cBg := partToCStrings(parts[0])
 	attr := C.createAttributedString(cLabel, cFg, cBg)
+	freeCStrings(cLabel, cFg, cBg)
 	for _, p := range parts[1:] {
 		cLabel, cFg, cBg = partToCStrings(p)
 		attr = C.appendAttributedString(attr, cLabel, cFg, cBg)
+		freeCStrings(cLabel, cFg, cBg)
 	}
 	C.systemTraySetANSILabel(s.nsStatusItem, attr)
 }
@@
 func partToCStrings(p SystemTrayLabelPart) (label, fg, bg *C.char) {
@@
 	return
 }
+
+func freeCStrings(label, fg, bg *C.char) {
+	if label != nil {
+		C.free(unsafe.Pointer(label))
+	}
+	if fg != nil {
+		C.free(unsafe.Pointer(fg))
+	}
+	if bg != nil {
+		C.free(unsafe.Pointer(bg))
+	}
+}
#!/bin/bash
set -euo pipefail

echo "Go-side allocations and frees:"
rg -n -C3 'partToCStrings|C\.CString|C\.free|createAttributedString|appendAttributedString' v3/pkg/application/systemtray_darwin.go

echo
echo "Native-side free() usage for attributed label helpers:"
rg -n -C3 'createAttributedString|appendAttributedString|free\(' v3/pkg/application/systemtray_darwin.m

Also applies to: 274-283

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

In `@v3/pkg/application/systemtray_darwin.go` around lines 261 - 267, The
C.CString pointers returned by partToCStrings are never freed, leaking native
memory; after calling C.createAttributedString and each
C.appendAttributedString, call C.free on the cLabel, cFg, and cBg pointers (or
defer free immediately after each allocation) so each trio returned by
partToCStrings is released once the native call consumes them; update the code
around partToCStrings, C.createAttributedString, C.appendAttributedString and
before C.systemTraySetANSILabel to ensure every allocated C string is freed (use
C.free on each cLabel/cFg/cBg).
v3/internal/tint/tint.go (1)

46-53: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clone handlers with deep-copied slices and shared lock.

Line 120/131 can alias parent slices via append, and cloned handlers currently do not share a mutex for the same writer. This can cause cross-handler state bleed and interleaved output under concurrent use.

💡 Proposed fix
 type handler struct {
-	mu         sync.Mutex
+	mu         *sync.Mutex
 	w          io.Writer
 	opts       Options
 	timeFormat string
 	preformatted []byte
 	groups     []string
 }
@@
-	return &handler{w: w, opts: *opts, timeFormat: tf}
+	return &handler{mu: &sync.Mutex{}, w: w, opts: *opts, timeFormat: tf}
 }
@@
 	return &handler{
+		mu:           h.mu,
 		w:            h.w,
 		opts:         h.opts,
 		timeFormat:   h.timeFormat,
-		preformatted: append(h.preformatted, buf.Bytes()...),
-		groups:       h.groups,
+		preformatted: append(append([]byte(nil), h.preformatted...), buf.Bytes()...),
+		groups:       append([]string(nil), h.groups...),
 	}
 }
 
 func (h *handler) WithGroup(name string) slog.Handler {
 	return &handler{
+		mu:           h.mu,
 		w:            h.w,
 		opts:         h.opts,
 		timeFormat:   h.timeFormat,
-		preformatted: h.preformatted,
-		groups:       append(h.groups, name),
+		preformatted: append([]byte(nil), h.preformatted...),
+		groups:       append(append([]string(nil), h.groups...), name),
 	}
 }
In Go, can append(...) reuse backing arrays and alias parent/child slices, and for slog.Handler WithAttrs/WithGroup implementations should derived handlers deep-copy slice state and share synchronization for the same writer?

Also applies to: 110-132

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

In `@v3/internal/tint/tint.go` around lines 46 - 53, The cloned handler currently
aliases parent slices and uses a separate mutex causing interleaved output;
change the handler to share synchronization and deep-copy slice state in the
clone code: make the lock a pointer (mu *sync.Mutex) so clones share the same
mutex instance for the same writer, and when creating derived handlers in
WithAttrs and WithGroup (and any clone/derived creation logic) copy slices with
append on a nil slice (e.g., preformatted := append([]byte(nil),
h.preformatted...) and groups := append([]string(nil), h.groups...)) so backing
arrays are not aliased; ensure other mutable fields (opts) are copied as needed
and assign the shared mu pointer to the cloned handler.
v3/internal/debounce/debounce.go (1)

23-30: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Timer.Stop() alone does not enforce “last invocation wins.”

On Line 27, stopping the old timer is insufficient for time.AfterFunc; if Stop() returns false, the old callback may already be running. Add a generation guard so only the newest scheduled callback executes.

💡 Proposed fix
 type debouncer struct {
 	mu    sync.Mutex
 	after time.Duration
 	timer *time.Timer
+	gen   uint64
 }
 
 func (d *debouncer) add(f func()) {
 	d.mu.Lock()
-	defer d.mu.Unlock()
+	d.gen++
+	gen := d.gen
 	if d.timer != nil {
 		d.timer.Stop()
 	}
-	d.timer = time.AfterFunc(d.after, f)
+	d.timer = time.AfterFunc(d.after, func() {
+		d.mu.Lock()
+		if gen != d.gen {
+			d.mu.Unlock()
+			return
+		}
+		d.mu.Unlock()
+		f()
+	})
+	d.mu.Unlock()
 }
For Go timers created with time.AfterFunc, does Timer.Stop() guarantee the callback won't run, and what pattern ensures only the latest debounced callback executes?
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@v3/internal/debounce/debounce.go` around lines 23 - 30, In debouncer.add,
stopping d.timer is not enough because time.AfterFunc callbacks may already be
running; introduce a generation/token on the debouncer (e.g., gen or seq) that
you increment each time you schedule a new timer, capture the current generation
inside the closure passed to time.AfterFunc, and have the closure check that the
captured generation still equals the debouncer's latest generation before
calling f; use an atomic counter or protect gen with d.mu when reading/updating
so only the most recently scheduled callback (the newest generation) runs while
older callbacks exit early.
v3/internal/semver/semver.go (1)

90-100: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pre-release comparison is not SemVer-correct.

On Line 99, strings.Compare(v.Prerelease, other.Prerelease) misorders numeric/dot-separated prerelease identifiers (e.g., beta.10 vs beta.2).

💡 Proposed fix
 	default:
-		return strings.Compare(v.Prerelease, other.Prerelease)
+		return comparePrerelease(v.Prerelease, other.Prerelease)
 	}
 }
@@
 func cmpUint(a, b uint64) int {
@@
 }
+
+func comparePrerelease(a, b string) int {
+	as := strings.Split(a, ".")
+	bs := strings.Split(b, ".")
+	for i := 0; i < len(as) && i < len(bs); i++ {
+		ai, aErr := strconv.ParseUint(as[i], 10, 64)
+		bi, bErr := strconv.ParseUint(bs[i], 10, 64)
+		switch {
+		case aErr == nil && bErr == nil:
+			if c := cmpUint(ai, bi); c != 0 {
+				return c
+			}
+		case aErr == nil:
+			return -1
+		case bErr == nil:
+			return 1
+		default:
+			if c := strings.Compare(as[i], bs[i]); c != 0 {
+				return c
+			}
+		}
+	}
+	return cmpUint(uint64(len(as)), uint64(len(bs)))
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@v3/internal/semver/semver.go` around lines 90 - 100, The current prerelease
comparison uses strings.Compare(v.Prerelease, other.Prerelease) which misorders
dot-separated and numeric identifiers; replace that fallback with
SemVer-compliant logic: split v.Prerelease and other.Prerelease on '.', iterate
identifiers pairwise and for each if both are numeric compare as integers, if
one is numeric it has lower precedence than the non-numeric, otherwise compare
lexically; if all compared identifiers are equal then the shorter identifier
list has lower precedence (e.g., "alpha" < "alpha.1"). Update the switch default
branch to perform this identifier-by-identifier comparison instead of the single
strings.Compare call, using v.Prerelease and other.Prerelease as the sources.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@v3/internal/browser/browser.go`:
- Around line 19-30: The open function currently returns cmd.Start() directly
which leaves the child process unreaped; instead, after calling cmd.Start() in
open (the function handling runtime.GOOS and creating the exec.Cmd named cmd),
spawn a goroutine that calls cmd.Wait() to reap the child and optionally logs
any Wait() error (so open remains non-blocking), or alternatively call cmd.Run()
if blocking until the opener exits is acceptable; ensure you reference
exec.Cmd.Start and exec.Cmd.Wait on the cmd variable inside the open function so
the started process is always reaped to avoid zombie/resource leaks.

In `@v3/internal/templates/templates.go`:
- Around line 199-203: The fallback branch currently masks the real metadata
read error by returning a "no template" message or the jsonErr even when the
YAML read failed; update the branch that checks errors.Is(yamlErr,
fs.ErrNotExist) to only treat the YAML as missing in that case, and otherwise
return the actual yamlErr (with context) instead of jsonErr so callers see the
real failure; locate the conditional that references yamlErr, jsonErr and result
in templates.go and replace the current returned error with the yamlErr-wrapped
error when yamlErr is not fs.ErrNotExist.

---

Duplicate comments:
In `@v3/internal/debounce/debounce.go`:
- Around line 23-30: In debouncer.add, stopping d.timer is not enough because
time.AfterFunc callbacks may already be running; introduce a generation/token on
the debouncer (e.g., gen or seq) that you increment each time you schedule a new
timer, capture the current generation inside the closure passed to
time.AfterFunc, and have the closure check that the captured generation still
equals the debouncer's latest generation before calling f; use an atomic counter
or protect gen with d.mu when reading/updating so only the most recently
scheduled callback (the newest generation) runs while older callbacks exit
early.

In `@v3/internal/semver/semver.go`:
- Around line 90-100: The current prerelease comparison uses
strings.Compare(v.Prerelease, other.Prerelease) which misorders dot-separated
and numeric identifiers; replace that fallback with SemVer-compliant logic:
split v.Prerelease and other.Prerelease on '.', iterate identifiers pairwise and
for each if both are numeric compare as integers, if one is numeric it has lower
precedence than the non-numeric, otherwise compare lexically; if all compared
identifiers are equal then the shorter identifier list has lower precedence
(e.g., "alpha" < "alpha.1"). Update the switch default branch to perform this
identifier-by-identifier comparison instead of the single strings.Compare call,
using v.Prerelease and other.Prerelease as the sources.

In `@v3/internal/tint/tint.go`:
- Around line 46-53: The cloned handler currently aliases parent slices and uses
a separate mutex causing interleaved output; change the handler to share
synchronization and deep-copy slice state in the clone code: make the lock a
pointer (mu *sync.Mutex) so clones share the same mutex instance for the same
writer, and when creating derived handlers in WithAttrs and WithGroup (and any
clone/derived creation logic) copy slices with append on a nil slice (e.g.,
preformatted := append([]byte(nil), h.preformatted...) and groups :=
append([]string(nil), h.groups...)) so backing arrays are not aliased; ensure
other mutable fields (opts) are copied as needed and assign the shared mu
pointer to the cloned handler.

In `@v3/pkg/application/systemtray_darwin.go`:
- Around line 261-267: The C.CString pointers returned by partToCStrings are
never freed, leaking native memory; after calling C.createAttributedString and
each C.appendAttributedString, call C.free on the cLabel, cFg, and cBg pointers
(or defer free immediately after each allocation) so each trio returned by
partToCStrings is released once the native call consumes them; update the code
around partToCStrings, C.createAttributedString, C.appendAttributedString and
before C.systemTraySetANSILabel to ensure every allocated C string is freed (use
C.free on each cLabel/cFg/cBg).

In `@v3/pkg/services/sqlite/sqlite.go`:
- Line 473: The Stmt.Close() method currently always wraps and returns err even
when Close succeeded; update the method on type Stmt so that it checks the
result of s.stmt.Close() and only returns fmt.Errorf("error closing prepared
statement: %w", err) when err != nil, otherwise return nil (i.e., call
s.stmt.Close(), if err != nil wrap and return it, else return nil).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ad1409b6-3286-44fb-ae58-55009fafa381

📥 Commits

Reviewing files that changed from the base of the PR and between 75b2de2 and 9c2ec6e.

⛔ Files ignored due to path filters (1)
  • v3/go.sum is excluded by !**/*.sum
📒 Files selected for processing (44)
  • v3/cmd/wails3/main.go
  • v3/examples/window/main.go
  • v3/go.mod
  • v3/internal/browser/browser.go
  • v3/internal/buildinfo/buildinfo.go
  • v3/internal/commands/appimage_testfiles/main.go
  • v3/internal/commands/build-assets.go
  • v3/internal/commands/syso.go
  • v3/internal/commands/tool_version.go
  • v3/internal/debounce/debounce.go
  • v3/internal/doctor/doctor.go
  • v3/internal/doctor/doctor_darwin.go
  • v3/internal/doctor/doctor_windows.go
  • v3/internal/github/semver.go
  • v3/internal/go-common-file-dialog/cfd/iFileOpenDialog.go
  • v3/internal/go-common-file-dialog/util/util.go
  • v3/internal/gosod/gosod.go
  • v3/internal/lo/lo.go
  • v3/internal/optional/optional.go
  • v3/internal/s/s.go
  • v3/internal/semver/semver.go
  • v3/internal/service/service.go
  • v3/internal/setupwizard/wizard.go
  • v3/internal/templates/templates.go
  • v3/internal/tint/tint.go
  • v3/internal/uuid/uuid.go
  • v3/pkg/application/application_debug.go
  • v3/pkg/application/browser_manager.go
  • v3/pkg/application/logger_dev.go
  • v3/pkg/application/logger_dev_windows.go
  • v3/pkg/application/messageprocessor_browser.go
  • v3/pkg/application/systemtray.go
  • v3/pkg/application/systemtray_darwin.go
  • v3/pkg/application/webview_window.go
  • v3/pkg/application/webview_window_linux.go
  • v3/pkg/application/webview_window_options.go
  • v3/pkg/application/webview_window_windows.go
  • v3/pkg/doctor-ng/doctor.go
  • v3/pkg/doctor-ng/platform_darwin.go
  • v3/pkg/doctor-ng/platform_windows.go
  • v3/pkg/services/kvstore/kvstore.go
  • v3/pkg/services/notifications/notifications_windows.go
  • v3/pkg/services/sqlite/sqlite.go
  • v3/tasks/sed/sed.go
✅ Files skipped from review due to trivial changes (6)
  • v3/pkg/application/messageprocessor_browser.go
  • v3/internal/setupwizard/wizard.go
  • v3/internal/doctor/doctor.go
  • v3/pkg/application/webview_window_linux.go
  • v3/pkg/doctor-ng/platform_windows.go
  • v3/internal/commands/syso.go

Comment thread v3/internal/browser/browser.go Resolved
Comment thread v3/internal/templates/templates.go Resolved
- sqlite: guard errors.Wrap replacement so nil err returns nil (critical
  bug: every successful Stmt.Close was returning a non-nil error)
- debounce: use generation counter to drop callbacks from superseded
  timers that fire after Stop() returns false
- semver: implement SemVer 2.0-correct prerelease comparison (numeric
  identifiers compared numerically, numeric < alphanumeric, dot-split)
- tint: make defensive copies in WithAttrs/WithGroup to prevent slice
  aliasing between parent and child handlers
- s/splitShell: operate on []rune to fix UTF-8 multi-byte corruption;
  track inWord flag to correctly preserve empty quoted arguments
- systemtray_darwin: free C strings allocated by partToCStrings after
  each createAttributedString/appendAttributedString call
- browser: reap child process with go cmd.Wait() to avoid zombies
- syso: use %w instead of %s for icon-close error to allow unwrapping
- templates: fix error reporting when template.yaml exists but is
  unreadable — surface the real yamlErr instead of the json not-found
  message; check both files are absent before reporting "not found"

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
v3/internal/s/s.go (1)

318-320: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Return an error when no tokens are parsed.

If input is empty/whitespace, splitShell returns an empty slice; Line 330 in EXEC then indexes args[0] and panics.

💡 Proposed fix
 func splitShell(s string) ([]string, error) {
@@
 	if inWord {
 		args = append(args, cur.String())
 	}
+	if len(args) == 0 {
+		return nil, fmt.Errorf("empty command")
+	}
 	return args, nil
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@v3/internal/s/s.go` around lines 318 - 320, splitShell currently returns an
empty slice for inputs that are empty/whitespace, which causes EXEC to panic
when it indexes args[0]; change the contract so callers can handle this: modify
splitShell to detect when no tokens were parsed (i.e., args is empty after the
final inWord check around cur.String()) and return an error (e.g., ErrNoTokens)
instead of an empty slice, and update EXEC to check the error from splitShell
before accessing args[0] (or, if you prefer, keep splitShell returning []string
but update EXEC to check len(args)==0 and return a descriptive error),
referencing the functions splitShell and EXEC and the variables args and
cur.String() to locate the relevant code.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@v3/internal/s/s.go`:
- Around line 318-320: splitShell currently returns an empty slice for inputs
that are empty/whitespace, which causes EXEC to panic when it indexes args[0];
change the contract so callers can handle this: modify splitShell to detect when
no tokens were parsed (i.e., args is empty after the final inWord check around
cur.String()) and return an error (e.g., ErrNoTokens) instead of an empty slice,
and update EXEC to check the error from splitShell before accessing args[0] (or,
if you prefer, keep splitShell returning []string but update EXEC to check
len(args)==0 and return a descriptive error), referencing the functions
splitShell and EXEC and the variables args and cur.String() to locate the
relevant code.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 582bb599-923d-4337-90e0-f3697b948f02

📥 Commits

Reviewing files that changed from the base of the PR and between 9c2ec6e and a828f9b.

📒 Files selected for processing (9)
  • v3/internal/browser/browser.go
  • v3/internal/commands/syso.go
  • v3/internal/debounce/debounce.go
  • v3/internal/s/s.go
  • v3/internal/semver/semver.go
  • v3/internal/templates/templates.go
  • v3/internal/tint/tint.go
  • v3/pkg/application/systemtray_darwin.go
  • v3/pkg/services/sqlite/sqlite.go
✅ Files skipped from review due to trivial changes (1)
  • v3/internal/commands/syso.go

- internal/browser: split open() into per-platform files (darwin/windows/other)
  so each platform achieves 100% statement coverage; removes unreachable
  branches that the GOOS switch compiled in on every platform
- internal/uuid: drop unreachable error check after crypto/rand.Read; Go 1.20+
  calls runtime.fatal internally and never returns an error
- Add _test.go files for all 8 new packages:
  browser(100%), debounce(100%), lo(100%), optional(100%),
  semver(100%), tint(100%), uuid(100%), gosod(97.9% — two error
  paths structurally unreachable: filepath.Abs needs deleted CWD,
  os.File.Close needs concrete-type mock)
- Debounce generation-mismatch branch covered deterministically by holding
  d.mu externally while timer fires then bumping generation before Unlock
- SemVer prerelease tests cover all §11.4 comparison branches including the
  full alpha<alpha.1<alpha.beta<beta<beta.2<beta.11<rc.1<1.0.0 ordering
Add absPath and osCreateFile package-level variables (default to
filepath.Abs and os.Create) so tests can substitute failing stubs to
cover the two previously unreachable error paths:
- Extract: filepath.Abs error branch
- processTemplates: w.Close() error branch

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
v3/internal/debounce/debounce_test.go (1)

10-12: ⚡ Quick win

Reduce timing-flake risk in debounce tests.

These assertions depend on tiny fixed sleeps; on slow CI they can intermittently fail. Prefer deadline-based polling (Eventually-style loop) over exact millisecond sleeps to make tests deterministic.

Also applies to: 46-47, 68-74, 112-113, 126-127, 148-151, 170-173

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

In `@v3/internal/debounce/debounce_test.go` around lines 10 - 12, Tests in
debounce_test.go use tiny fixed sleeps (debounceAfter and waitFor) causing
flakes; replace those exact millisecond Sleep-based assertions with
deadline-based polling (an "Eventually" loop) that repeatedly checks the
expected condition until a timeout. For each assertion that currently does
time.Sleep(...) then assert, implement a loop that records deadline :=
time.Now().Add(waitFor) and in a for loop until time.Now().After(deadline)
checks the condition (e.g., output length, call count, or state used in the
test) and breaks on success, sleeping a short interval (e.g., 1-5ms) between
retries, and fail the test if deadline passes; update all spots referencing
debounceAfter and waitFor (and the assertions around them) to use this pattern
to reduce CI timing flakiness.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@v3/internal/browser/browser_test.go`:
- Around line 12-27: Tests TestOpenURL_Success and TestOpenFile_Success
currently launch host handlers via OpenURL and OpenFile; change them so unit
test runs don't trigger OS actions by either (a) converting these tests into
integration tests using a build tag (e.g., add a separate file with a build tag
like //go:build integration that contains TestOpenURL_Success and
TestOpenFile_Success) or (b) refactor OpenURL/OpenFile to accept an opener
interface or function and update tests to inject a stub opener that records
calls instead of launching real apps (update tests to call OpenURL/OpenFile via
the injectable opener or move them to the new integration file). Ensure you
reference the existing symbols OpenURL, OpenFile, TestOpenURL_Success, and
TestOpenFile_Success when applying the change.

In `@v3/internal/gosod/gosod_test.go`:
- Around line 409-430: The test TestExtract_MkdirAllFails relies on Unix
permission semantics; make it Unix-only by moving or duplicating the test into a
file with a Unix build constraint (use //go:build unix and the old +build unix
line) so it only compiles on Unix-like platforms; keep the test body
(referencing TestExtract_MkdirAllFails, td.Extract, os.Mkdir with mode 0555,
t.TempDir) unchanged but ensure the file-level build tag is added at the top to
prevent non-Unix CI runs from executing this platform-specific behavior.

In `@v3/internal/tint/tint_test.go`:
- Around line 137-156: The tests TestHandle_NoColor_ProducesNoEscapeCodes and
TestHandle_Color_ContainsEscapeCodes currently ignore the error returned by
h.Handle; change both calls to check the returned error and fail the test on
non-nil (e.g., if err != nil { t.Fatalf("Handle error: %v", err) }) so real
handler failures don't get masked; locate the h.Handle(context.Background(), r)
invocations in those two test functions and replace the discarded assignment
with proper error handling.

---

Nitpick comments:
In `@v3/internal/debounce/debounce_test.go`:
- Around line 10-12: Tests in debounce_test.go use tiny fixed sleeps
(debounceAfter and waitFor) causing flakes; replace those exact millisecond
Sleep-based assertions with deadline-based polling (an "Eventually" loop) that
repeatedly checks the expected condition until a timeout. For each assertion
that currently does time.Sleep(...) then assert, implement a loop that records
deadline := time.Now().Add(waitFor) and in a for loop until
time.Now().After(deadline) checks the condition (e.g., output length, call
count, or state used in the test) and breaks on success, sleeping a short
interval (e.g., 1-5ms) between retries, and fail the test if deadline passes;
update all spots referencing debounceAfter and waitFor (and the assertions
around them) to use this pattern to reduce CI timing flakiness.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6409b745-8937-4e4f-8c52-a842ea6f1da6

📥 Commits

Reviewing files that changed from the base of the PR and between a828f9b and b83544d.

📒 Files selected for processing (14)
  • v3/internal/browser/browser.go
  • v3/internal/browser/browser_darwin.go
  • v3/internal/browser/browser_other.go
  • v3/internal/browser/browser_test.go
  • v3/internal/browser/browser_windows.go
  • v3/internal/debounce/debounce_test.go
  • v3/internal/gosod/gosod.go
  • v3/internal/gosod/gosod_test.go
  • v3/internal/lo/lo_test.go
  • v3/internal/optional/optional_test.go
  • v3/internal/semver/semver_test.go
  • v3/internal/tint/tint_test.go
  • v3/internal/uuid/uuid.go
  • v3/internal/uuid/uuid_test.go
💤 Files with no reviewable changes (1)
  • v3/internal/browser/browser.go
✅ Files skipped from review due to trivial changes (1)
  • v3/internal/browser/browser_other.go

Comment thread v3/internal/browser/browser_test.go Resolved
Comment thread v3/internal/gosod/gosod_test.go Resolved
Comment thread v3/internal/tint/tint_test.go Resolved
- internal/browser: move OS-launching tests to integration build tag
  (browser_integration_test.go); add openCmd injectable var to all
  platform files so unit tests can stub without real side effects;
  TestOpen_Success stubs with os.Args[0] -test.run=^$ (exits 0, cross-platform)
- internal/gosod: move TestExtract_MkdirAllFails to gosod_unix_test.go
  with //go:build unix to avoid Windows CI failures on chmod semantics
- internal/tint: check error return from h.Handle() instead of discarding
  it with _ in TestHandle_NoColor_ProducesNoEscapeCodes and
  TestHandle_Color_ContainsEscapeCodes
@leaanthony
leaanthony merged commit 609a080 into master May 30, 2026
59 of 60 checks passed
@leaanthony
leaanthony deleted the reduce-deps branch May 30, 2026 00:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants