Files
yellowjacket/AGENTS.md
T
logan 62f9509b59 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
2026-02-14 01:02:10 -06:00

183 lines
7.4 KiB
Markdown

# 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
```bash
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
```bash
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
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`
```bash
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:
```go
// Package player provides audio playback functionality.
package player
```
### Import Organization
Three groups separated by blank lines (enforced by `gci`): stdlib, third-party, internal.
```go
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`):
```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/exported: `PascalCase` — Unexported: `camelCase`
- Constants: `PascalCase` for exported, grouped with `const (...)`
- 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")`:
```go
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
```go
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`):
1. `New*()` constructor — created before Wails runtime is available
2. `SetContext(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`).
```typescript
import { EventsOn, EventsEmit } from '@runtime/runtime';
import type { TrackInfo } from '@store/player-store';
```
Aliases: `@go/*`, `@components/*`, `@store/*`, `@runtime/*`, `@utils/*`, `@assets/*`, `@pages/*`
### 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`...`; }
}
```
- `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
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) => { ... });
```
### 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 detection
- `pkg/templcomp/` — Shared templ component utilities
- `test_data/` — Audio test fixtures