Files
yellowjacket/.planning/milestones/v1.0-phases/02-backend-correctness/02-01-PLAN.md
T
yonlu 6ce0661fca chore: complete v1.0 Consolidation milestone
Archive milestone artifacts:
- milestones/v1.0-ROADMAP.md (full roadmap archive)
- milestones/v1.0-REQUIREMENTS.md (26/26 requirements complete)
- milestones/v1.0-phases/ (8 phase directories with plans, summaries, verifications)

Updated:
- PROJECT.md: full evolution review, all consolidation requirements validated
- ROADMAP.md: collapsed to milestone summary with archive link
- STATE.md: reset for next milestone
- MILESTONES.md: created with stats and accomplishments
- RETROSPECTIVE.md: created with lessons learned

Deleted:
- REQUIREMENTS.md (archived, fresh for next milestone)

8 phases, 17 plans, 34 tasks, 84 tests added, 6 days
2026-03-05 09:34:43 -05:00

7.4 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
02-backend-correctness 01 execute 1
backend/app.go
backend/config/config.go
true
CORR-05
CORR-06
CORR-07
truths artifacts key_links
Package-level startupErr variable no longer exists; startup errors are stored in a YellowJacketApp struct field
Config files are written with 0o644 permissions
MPRIS callback errors (Pause, Seek) appear in the application log instead of being silently discarded
path provides contains
backend/app.go Startup error as struct field + MPRIS error logging startupErr error
path provides contains
backend/config/config.go Secure config file permissions 0o644
from to via pattern
backend/app.go:OnStartup backend/app.go:OnDomReady yj.startupErr field (not package-level var) yj.startupErr
from to via pattern
backend/app.go:MPRIS callbacks yj.logger Warn log on Pause/Seek error yj.logger.Warn.*MPRIS
Fix three independent error handling gaps in the application shell and config layer: eliminate the package-level startupErr variable, secure config file permissions, and log MPRIS callback errors.

Purpose: Remove global mutable state (startupErr), prevent world-writable config files, and ensure MPRIS failures are observable in logs. Output: Modified backend/app.go and backend/config/config.go with all three fixes applied.

<execution_context> @/home/caleb/.config/opencode/get-shit-done/workflows/execute-plan.md @/home/caleb/.config/opencode/get-shit-done/templates/summary.md </execution_context>

@.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/02-backend-correctness/02-CONTEXT.md @.planning/phases/02-backend-correctness/02-RESEARCH.md

@backend/app.go @backend/config/config.go

From backend/app.go:

// YellowJacketApp is the main application struct for Wails.
type YellowJacketApp struct {
    FEBindings   []any
    FrontendUtil *frontendutil.FrontendUtil

    logger        *slog.Logger
    assetHandler  *assets.Handler
    database      *database.DB
    library       *library.Library
    player        *player.Player
    playlist      *playlist.Service
    queue         *queue.Queue
    mediaControls mediacontrols.Handler
    appContext    context.Context
    appConfig     *config.Config
}

var startupErr error  // line 134 — TO BE REMOVED

func (yj *YellowJacketApp) OnStartup(ctx context.Context)  // line 137 — uses startupErr
func (yj *YellowJacketApp) OnDomReady(ctx context.Context)  // line 251 — checks startupErr

MPRIS callback closures at lines 181-203:

OnPause: func() { _ = yj.player.Pause() },
OnPlayPause: func() {
    if yj.player.IsPlaying() {
        _ = yj.player.Pause()
    } else {
        yj.queue.Play()
    }
},
OnStop:     func() { _ = yj.player.Pause() },
OnSeek: func(positionSec int) {
    _ = yj.player.Seek(positionSec)
},
Task 1: Move startupErr to struct field and fix config permissions backend/app.go, backend/config/config.go **CORR-05 — Startup error struct field (backend/app.go):** 1. Add `startupErr error` field to the `YellowJacketApp` struct (after `appConfig`) 2. Delete the package-level `var startupErr error` declaration at line 134 3. In `OnStartup` (line 154-155): change `startupErr = errors.Join(startupErr, ...)` to `yj.startupErr = errors.Join(yj.startupErr, ...)` 4. In `OnDomReady` (line 252-254): change `if startupErr != nil` to `if yj.startupErr != nil`, and `startupErr.Error()` to `yj.startupErr.Error()` 5. Verify no other references to the package-level `startupErr` exist

CORR-06 — Config permissions (backend/config/config.go):

  1. At line 152, change os.FileMode(int(0o666)) to 0o644
  2. This is a single expression replacement — the os.WriteFile call signature stays the same cd /mnt/vault/dev/golang/yellowjacket && go vet ./backend/... && grep -q "startupErr error" backend/app.go && ! grep -q "^var startupErr" backend/app.go && grep -q "0o644" backend/config/config.go && ! grep -q "0o666" backend/config/config.go Package-level startupErr is gone; YellowJacketApp has startupErr field; OnStartup and OnDomReady reference yj.startupErr; config.go writes with 0o644 permissions
Task 2: Log MPRIS callback errors backend/app.go **CORR-07 — MPRIS callback error logging (backend/app.go):**

Replace the four MPRIS closures (lines 183-195) that discard errors with closures that log on failure. Use Warn level per research recommendation — these are non-fatal conditions. Keep inline closures (no named method extraction).

  1. OnPause (line 183): Replace func() { _ = yj.player.Pause() } with:
func() {
    if err := yj.player.Pause(); err != nil {
        yj.logger.Warn("MPRIS Pause failed", "err", err)
    }
}
  1. OnPlayPause (lines 184-189): Replace the _ = yj.player.Pause() inside the if yj.player.IsPlaying() branch:
func() {
    if yj.player.IsPlaying() {
        if err := yj.player.Pause(); err != nil {
            yj.logger.Warn("MPRIS PlayPause(pause) failed", "err", err)
        }
    } else {
        yj.queue.Play()
    }
}
  1. OnStop (line 191): Replace func() { _ = yj.player.Pause() } with:
func() {
    if err := yj.player.Pause(); err != nil {
        yj.logger.Warn("MPRIS Stop failed", "err", err)
    }
}
  1. OnSeek (lines 194-196): Replace func(positionSec int) { _ = yj.player.Seek(positionSec) } with:
func(positionSec int) {
    if err := yj.player.Seek(positionSec); err != nil {
        yj.logger.Warn("MPRIS Seek failed", "err", err)
    }
}

Ensure all four closures no longer use _ = to discard errors. cd /mnt/vault/dev/golang/yellowjacket && go vet ./backend/... && ! grep -q '_ = yj.player.Pause()' backend/app.go && ! grep -q '_ = yj.player.Seek' backend/app.go && grep -c 'MPRIS.*failed' backend/app.go | grep -q '^4$' All four MPRIS callbacks (OnPause, OnPlayPause, OnStop, OnSeek) check errors and log at Warn level; no discarded errors remain in MPRIS closures

```bash # All backend packages compile and pass vet go vet ./backend/...

No package-level startupErr

! grep -q "^var startupErr" backend/app.go

Struct field exists

grep -q "startupErr error" backend/app.go

Config permissions fixed

grep -q "0o644" backend/config/config.go ! grep -q "0o666" backend/config/config.go

MPRIS errors logged (4 occurrences)

test "$(grep -c 'MPRIS.*failed' backend/app.go)" -eq 4

No discarded player errors in MPRIS closures

! grep -q '_ = yj.player' backend/app.go

Linting passes

golangci-lint run ./backend/...

</verification>

<success_criteria>
- `go vet ./backend/...` passes
- `golangci-lint run ./backend/...` passes
- Package-level `startupErr` variable eliminated
- Config file written with 0o644 permissions
- All four MPRIS callbacks log errors at Warn level
</success_criteria>

<output>
After completion, create `.planning/phases/02-backend-correctness/02-01-SUMMARY.md`
</output>