- Fix Go version from 1.24+ to 1.25 (matching go.mod) - Add missing make lint/test commands - Add critical -tags webkit2_41 requirement to all test commands - Add warning about generated files (sqlcgen/, *_templ.go) - Add unexported sentinel error convention, logger.WithGroup() pattern - Add SQLite WAL mode and SetMaxOpenConns(1) detail - Add frontend type checking command (tsc --noEmit) - Add missing backend directories to structure - Consolidate and tighten sections to reduce line count
7.4 KiB
AGENTS.md - YellowJacket
Guidelines for AI coding agents working in this repository.
Project Overview
YellowJacket is a cross-platform desktop music player built with:
- Backend: Go 1.25 with Wails v2 framework
- Frontend: TypeScript with Lit Web Components
- Database: SQLite (pure-Go driver via
modernc.org/sqlite) - Build Tools: Make, Wails CLI, Vite, pnpm
Build Commands
make dev # Development with hot-reload
make build-dev # Debug build
make build-prod # Production build (obfuscated + UPX compressed)
make generate # Run all code generators (sqlc, templ)
make clean # Clean frontend build artifacts
make lint # Run golangci-lint
make test # Run all Go tests (race detector, no cache, 2min timeout)
Frontend Only
cd frontend && pnpm install # Install dependencies
cd frontend && pnpm dev # Vite dev server
cd frontend && pnpm build # Production build
Testing
Important: Tests require the -tags webkit2_41 build tag.
make test # All tests (preferred)
go test -tags webkit2_41 ./... # All tests manually
go test -tags webkit2_41 ./backend/player/ # Single package
go test -tags webkit2_41 -run TestFunctionName ./backend/player/ # Single test
go test -tags webkit2_41 -v -run TestFunctionName ./backend/player/ # Verbose single test
Test files are colocated with source as *_test.go. Test fixtures live in test_data/. Some tests skip in CI when they require hardware (audio device, Wails runtime).
Linting
golangci-lint v2 config (.golangci.yml) with strict rules. Key linters:
gocritic,errorlint,err113,godot,revive,sloglint,nlreturn,wsl- Formatters:
gci,gofmt,gofumpt,goimports,golines
make lint # Lint all Go code
golangci-lint run --build-tags webkit2_41 ./... # With build tags explicitly
Frontend type checking: cd frontend && pnpm exec tsc --noEmit
Code Generation
go:generate directives live in backend/app.go (templ) and backend/database/database.go (sqlc). After modifying .templ files or SQL in backend/database/sql/, run make generate. Never edit files in backend/database/sql/sqlcgen/ or *_templ.go — they are generated.
Go Code Style
Package Documentation
Every package must have a doc comment ending with a period:
// Package player provides audio playback functionality.
package player
Import Organization
Three groups separated by blank lines (enforced by gci): stdlib, third-party, internal.
import (
"context"
"fmt"
"github.com/wailsapp/wails/v2/pkg/runtime"
"yellowjacket/backend/events"
)
Error Handling
- Wrap errors with context:
fmt.Errorf("failed to open file: %w", err) - Sentinel errors as package-level vars (enforced by
err113):var ErrUnsupportedFileType = errors.New("unsupported file type") - Unexported sentinels for internal use:
var errNotDirectory = errors.New("not a directory") - Use
errors.Join()for accumulating multiple errors - Return early on errors; blank line required after early returns (
nlreturn)
Naming Conventions
- Structs/exported:
PascalCase— Unexported:camelCase - Constants:
PascalCasefor exported, grouped withconst (...) - Custom domain types:
type PlayerState string,type UserVolume int,type AudioFileExtension string
Logging
log/slog with structured key-value pairs. Logger injected via constructors, scoped with logger.WithGroup("player"):
p.logger.Info("File loaded", "file", filePath)
p.logger.Error("Failed to decode", "path", filePath, "err", err)
Comments & Formatting
- Doc comments on all exported functions/types, ending with periods (enforced by
godot) - Blank line after early returns (enforced by
nlreturn)
Constructor Pattern
func NewPlayer(ctx context.Context, logger *slog.Logger, db *database.DB) (*Player, error) {
player := &Player{ctx: ctx, logger: logger.WithGroup("player"), state: Stopped}
return player, nil
}
SetContext Pattern (Two-Phase Initialization)
Components needing Wails runtime use two phases (runtime unavailable until OnStartup):
New*()constructor — created before Wails runtime is availableSetContext(ctx context.Context)— called after runtime starts; registers event handlers, restores state
Build Tags
Dev/prod detection via internal/dev/: //go:build dev → IsDev = true, //go:build !dev → IsDev = false.
TypeScript/Lit Code Style
Import Organization
Use path aliases from tsconfig.json. Use import type for type-only imports (verbatimModuleSyntax).
import { EventsOn, EventsEmit } from '@runtime/runtime';
import type { TrackInfo } from '@store/player-store';
Aliases: @go/*, @components/*, @store/*, @runtime/*, @utils/*, @assets/*, @pages/*
Lit Component Pattern
@customElement('component-name')
export class ComponentName extends LitElement {
@state() private someState: Type = initialValue;
static override styles = css`...`;
override connectedCallback() { super.connectedCallback(); }
override disconnectedCallback() { super.disconnectedCallback(); }
override render() { return html`...`; }
}
overridekeyword required (noImplicitOverride: true)- Private event handlers as arrow functions:
private handleClick = () => { ... } strict: true,noUncheckedIndexedAccess: true,verbatimModuleSyntax: true,experimentalDecorators: true,noUnusedLocals: true,noUnusedParameters: true- Singleton stores in
frontend/src/store/(backend is source of truth).ReactiveControllerpattern connects Lit components to stores — subscribe inhostConnected(), unsubscribe inhostDisconnected().
Frontend-Backend Communication
Event System
Events are the primary communication mechanism. Event names must match exactly in both files:
- Go:
backend/events/events.go— TypeScript:frontend/src/events.ts
runtime.EventsEmit(p.ctx, events.TrackChanged, trackInfo)
runtime.EventsOn(p.ctx, events.RequestPlay, func(_ ...any) { p.Play() })
EventsEmit(Events.RequestPlay);
EventsOn(Events.TrackChanged, (trackInfo: TrackInfo) => { ... });
HTMX
The config page uses HTMX for HTML fragment loading. Backend serves fragments via templ templates (backend/config/config-form.templ, backend/library/config.templ). Config has a separate entry point (src/pages/config/).
Database
SQLite with sqlc for type-safe queries. Schemas in backend/database/sql/schemas/, queries in backend/database/sql/queries/, generated code in backend/database/sql/sqlcgen/. SQLite opened with WAL mode and SetMaxOpenConns(1) (single-writer). After modifying SQL files, run make generate.
Directory Structure
backend/— Go:config/,database/,events/,library/,metadata/,models/,player/,queue/,system/,logging/,frontendutil/,assets/frontend/src/— TypeScript/Lit:components/,pages/,store/,utils/frontend/wailsjs/— Auto-generated Wails bindings (do not edit)internal/dev/— Build-tag-based dev/prod detectionpkg/templcomp/— Shared templ component utilitiestest_data/— Audio test fixtures