From 62f9509b592c32ef68636233b6e08395fcf806db Mon Sep 17 00:00:00 2001 From: Logan Jones Date: Sat, 14 Feb 2026 01:02:10 -0600 Subject: [PATCH] docs: improve AGENTS.md with accurate build tags, commands, and style guidelines (#42) - 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 --- AGENTS.md | 150 ++++++++++++++++++++++-------------------------------- 1 file changed, 62 insertions(+), 88 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 8deaf57..6db5a96 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,7 +5,7 @@ Guidelines for AI coding agents working in this repository. ## Project Overview YellowJacket is a cross-platform desktop music player built with: -- **Backend**: Go 1.24+ with Wails v2 framework +- **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 @@ -18,191 +18,165 @@ 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 ```bash -cd frontend -pnpm install # Install dependencies -pnpm dev # Vite dev server -pnpm build # Production build +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. + ```bash -go test ./... # Run all tests -go test ./backend/player/ # Run tests in a specific package -go test -run TestFunctionName ./backend/player/ # Run a single test by name -go test -v -run TestFunctionName ./backend/player/ # Verbose output +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 located alongside source files as `*_test.go`. Test fixtures live in `test_data/`. +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 -The project uses golangci-lint (v2 config) with strict rules. Key enabled linters: +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` ```bash -golangci-lint run +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: generates `*_templ.go` from `.templ` files -- `backend/database/database.go` — sqlc: generates type-safe DB code from SQL +`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.** -After modifying `.templ` files or SQL in `backend/database/sql/`, run `make generate`. +## Go Code Style -## Code Style Guidelines - -### Go Code Style - -#### Package Documentation -Every package must have a doc comment: +### Package Documentation +Every package must have a doc comment ending with a period: ```go // Package player provides audio playback functionality. package player ``` -#### Import Organization -Imports are grouped and ordered by gci/goimports (three groups separated by blank lines): -1. Standard library 2. Third-party packages 3. Internal packages (`yellowjacket/...`) - +### Import Organization +Three groups separated by blank lines (enforced by `gci`): stdlib, third-party, internal. ```go import ( "context" "fmt" - "log/slog" "github.com/wailsapp/wails/v2/pkg/runtime" "yellowjacket/backend/events" - "yellowjacket/backend/metadata" ) ``` -#### Error Handling -- Always wrap errors with context: `fmt.Errorf("failed to open file: %w", err)` -- Define sentinel errors as package-level vars (enforced by `err113`): +### Error Handling +- Wrap errors with context: `fmt.Errorf("failed to open file: %w", err)` +- Sentinel errors as package-level vars (enforced by `err113`): ```go 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: `PascalCase` (e.g., `Player`, `AudioFile`) -- Exported methods: `PascalCase` -- Unexported methods/fields: `camelCase` +### Naming Conventions +- Structs/exported: `PascalCase` — Unexported: `camelCase` - Constants: `PascalCase` for exported, grouped with `const (...)` -- Custom domain types: `type PlayerState string`, `type UserVolume int` +- Custom domain types: `type PlayerState string`, `type UserVolume int`, `type AudioFileExtension string` -#### Logging -Use `log/slog` with structured key-value pairs. Logger instances are injected via constructors: +### Logging +`log/slog` with structured key-value pairs. Logger injected via constructors, scoped with `logger.WithGroup("player")`: ```go p.logger.Info("File loaded", "file", filePath) p.logger.Error("Failed to decode", "path", filePath, "err", err) ``` -Logger groups via `logger.WithGroup("player")` for component-scoped logging. -#### Constructor Pattern +### 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 ```go func NewPlayer(ctx context.Context, logger *slog.Logger, db *database.DB) (*Player, error) { - player := &Player{ctx: ctx, logger: logger, state: Stopped} - // initialization... + player := &Player{ctx: ctx, logger: logger.WithGroup("player"), state: Stopped} return player, nil } ``` -#### SetContext Pattern (Two-Phase Initialization) -Backend components that need the Wails runtime use a two-phase pattern because Wails runtime features (events, dialogs) are unavailable until `OnStartup`: -1. Constructor (`New*`) — created before Wails runtime is available -2. `SetContext(ctx context.Context)` — called after Wails runtime starts; registers event handlers +### SetContext Pattern (Two-Phase Initialization) +Components needing Wails runtime use two phases (runtime unavailable until `OnStartup`): +1. `New*()` constructor — created before Wails runtime is available +2. `SetContext(ctx context.Context)` — called after runtime starts; registers event handlers, restores state -#### Comments -- Doc comments on all exported functions/types -- End sentences with periods (enforced by `godot`) -- Blank line after early returns (enforced by `nlreturn`) +### Build Tags +Dev/prod detection via `internal/dev/`: `//go:build dev` → `IsDev = true`, `//go:build !dev` → `IsDev = false`. -#### Build Tags -Dev/prod detection uses build tags in `internal/dev/`: -- `//go:build dev` → `IsDev = true` (used by `make dev`) -- `//go:build !dev` → `IsDev = false` (production builds) +## TypeScript/Lit Code Style -### TypeScript/Lit Code Style - -#### Import Organization -Use path aliases defined in `tsconfig.json`: +### Import Organization +Use path aliases from `tsconfig.json`. Use `import type` for type-only imports (`verbatimModuleSyntax`). ```typescript import { EventsOn, EventsEmit } from '@runtime/runtime'; import type { TrackInfo } from '@store/player-store'; ``` -Available aliases: `@go/*`, `@components/*`, `@store/*`, `@runtime/*`, `@utils/*`, `@assets/*`, `@pages/*` +Aliases: `@go/*`, `@components/*`, `@store/*`, `@runtime/*`, `@utils/*`, `@assets/*`, `@pages/*` -#### Lit Component Pattern +### Lit Component Pattern ```typescript @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`...`; } } ``` - -#### TypeScript Strictness -- `strict: true` enabled -- `noUncheckedIndexedAccess: true` — check array/object access -- `noImplicitOverride: true` — must use `override` keyword -- `verbatimModuleSyntax: true` — use `import type` for type-only imports -- `experimentalDecorators: true` — required for Lit decorators +- `override` keyword 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). `ReactiveController` pattern connects Lit components to stores — subscribe in `hostConnected()`, unsubscribe in `hostDisconnected()`. ## 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` +- Go: `backend/events/events.go` — TypeScript: `frontend/src/events.ts` ```go runtime.EventsEmit(p.ctx, events.TrackChanged, trackInfo) runtime.EventsOn(p.ctx, events.RequestPlay, func(_ ...any) { p.Play() }) ``` - ```typescript EventsEmit(Events.RequestPlay); EventsOn(Events.TrackChanged, (trackInfo: TrackInfo) => { ... }); ``` -### State Management -- Singleton stores in `frontend/src/store/` — backend is source of truth -- ReactiveController pattern (`PlayerController`) connects Lit components to stores - ### HTMX -The config page uses HTMX for HTML fragment loading. Backend serves HTML fragments via templ templates (`backend/config/config-form.templ`, `backend/library/config.templ`). Config is a separate entry point (`src/pages/config/`). +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/`. After modifying SQL files, run `make generate`. +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 backend: `config/`, `database/`, `events/`, `library/`, `metadata/`, `player/`, `system/` +- `backend/` — Go: `config/`, `database/`, `events/`, `library/`, `metadata/`, `models/`, `player/`, `queue/`, `system/`, `logging/`, `frontendutil/`, `assets/` - `frontend/src/` — TypeScript/Lit: `components/`, `pages/`, `store/`, `utils/` -- `frontend/wailsjs/` — Generated Wails bindings +- `frontend/wailsjs/` — Auto-generated Wails bindings (do not edit) - `internal/dev/` — Build-tag-based dev/prod detection - `pkg/templcomp/` — Shared templ component utilities - `test_data/` — Audio test fixtures - -## Key Dependencies - -- **Wails v2**: Desktop app framework bridging Go and web frontend -- **beep**: Audio playback library (custom fork `TheCodeOfCaleb/beep`) -- **sqlc**: Type-safe SQL code generation -- **templ**: Go HTML templating -- **Lit**: Web component framework -- **Web Awesome**: Web component UI library (`@awesome.me/webawesome`)