Skip to content

Latest commit

 

History

History
282 lines (203 loc) · 14.8 KB

AGENTS.md

File metadata and controls

282 lines (203 loc) · 14.8 KB

AGENTS.md

You are a full-stack engineer leveraging Alpine.js, Basecoat/Tailwind on the frontend and Tauri (Rust) on the backend.

Project Overview

mt is a desktop music player designed for large music collections, built with Tauri (Rust backend), basecoat (with Tailwind CSS), and Alpine.js. The backend uses Rust for audio playback and system integration, while the frontend is a modern web-based UI with reactive components.

General Guidelines

  • ALWAYS use atomic commits — see Git Workflow for interactive staging, patch mode, and worktree management
  • NEVER create *.backup files. This is a version controlled repo

Context7

Always use Context7 MCP when I need library/API documentation, code generation, setup or configuration steps without me having to explicitly ask.

Libraries

  • alpinejs/alpine
  • basharovv/musicat
  • dubzzz/fast-check
  • hunvreus/basecoat
  • jdx/mise
  • microsoft/playwright
  • mrlesk/backlog.md
  • nextest-rs/nextest
  • proptest-rs/proptest
  • reporails/cli
  • serial-ata/lofty-rs
  • sharkdp/hyperfine
  • taiko2k/tauon
  • tailwindlabs/tailwindcss
  • tranxuanthang/lrclib
  • useblacksmith/setup-docker-builder
  • websites/deno
  • websites/last_fm_api
  • websites/rs_rig-core
  • websites/rs_tauri_2_9_5
  • websites/taskfile_dev

Quick Reference

Topic Guide
Commands, tooling, dependencies Development Guide
Testing strategy, E2E, coverage Testing Guide
Local GitHub Actions testing act Guide
Atomic commits, worktrees Git Workflow
System architecture, components Tauri Architecture
MCP bridge tools MCP Tool Reference
Remote debugging, crash analysis Debugging Guide
Last.fm scrobbling Last.fm Integration
Themes, dark mode toggle fixes Theming Guide
Cross-platform builds, CI/CD Build Configuration
Shuffle, Play Next pinning Shuffle Implementation
Agent script, prompt tuning Agent Script
Video/frame extraction for debugging Frame Extraction

Architecture Overview

MT uses a pure Rust + Tauri architecture. See Tauri Architecture for full details and Observability for database and log paths per platform.

Tech Stack

  • Frontend: Tauri WebView (Alpine.js + Basecoat/Tailwind CSS), linted and formatted with Deno
  • Backend: Native Rust in crates/mt-tauri/ (91 Tauri commands)
  • Audio: Rodio/Symphonia
  • Database: SQLite via rusqlite (mt.db in the Tauri app_data_dir() for bundle identifier com.mt.desktop)
  • Build Cache: sccache (shared across worktrees/workspaces)
  • Tests: Vitest and Playwright (frontend), cargo nextest and proptest (backend)

Rust Toolchain (mise + rustup)

Do not symlink the project-local .cache/cargo/bin/ to the mise install dir. See Rust Toolchain for the three directories involved.

Windows CI Toolchain Constraint

On Windows CI runners, RUSTUP_TOOLCHAIN MUST use the fully qualified name with host triple suffix (e.g. nightly-2026-02-09-x86_64-pc-windows-msvc). Bare channel names like nightly-2026-02-09 resolve using the runner's default host triple, which can be stale GNU — causing dlltool.exe not found errors. See Build Configuration — Windows Toolchain Pinning for details.

When modifying taskfiles/ci.yml or .github/actions/setup-tauri-build/action.yml:

  • Never set RUSTUP_TOOLCHAIN to a bare toolchain name on Windows
  • Always run rustup set default-host x86_64-pc-windows-msvc before toolchain installation
  • Pass PINNED_RUST explicitly from the composite action rather than relying on shell extraction in Task

Domain Terminology

Term Meaning
Queue The items array in app/frontend/js/stores/queue.js, kept in play order
Now Playing view The view that renders the queue starting with the current track
Shuffle Fisher-Yates reorder of items; the pre-shuffle order is kept in _originalOrder
Play Next Tracks pinned ahead of shuffled tracks via _playNextTrackIds; see Shuffle Implementation
Loop mode Queue loop state: 'none', 'all' or 'one'
Scrobble A play submitted to Last.fm; see Last.fm Integration
Backlog task A TASK-<n> entry managed through Backlog.md MCP under backlog/tasks/

Queue and Shuffle Behavior

The queue store (app/frontend/js/stores/queue.js) maintains tracks in play order — the items array always reflects the order tracks will be played.

  • Without shuffle: Tracks play sequentially in the order they were added
  • With shuffle enabled: The items array is physically reordered using Fisher-Yates shuffle
    • Current track moves to index 0
    • Remaining tracks are randomly shuffled
  • When shuffle is disabled: Original order is restored from _originalOrder
  • Loop + Shuffle: When queue ends with loop=all, items are re-shuffled for a new random order

Now Playing view: Render $store.queue.items in order, with the current track first and upcoming tracks after it.

Linting, Formatting, and Testing

All commands use Taskfile. Run task --list for full list.

Task Runner (preferred)

task lint                     # Run all linters (Rust + JS)
task format                   # Run all formatters (Rust + JS)
task test                     # Run all tests (Rust + JS unit)
task test:e2e                 # Run Playwright E2E tests
task pre-commit               # Run pre-commit hooks

Direct Commands

Frontend (uses Deno toolchain, not ESLint/Prettier):

deno lint                                         # Lint JS/TS
deno fmt                                          # Format JS/TS
deno fmt --check                                  # Check formatting without changes
cd app/frontend && npx vitest run                 # Unit/property tests (Vitest via Node)
cd app/frontend && npx playwright test            # E2E tests

Backend (Rust):

cargo clippy --workspace                          # Lint
cargo fmt --all                                   # Format
cargo nextest run --workspace                     # Tests (falls back to cargo test)
cargo check --manifest-path crates/mt-tauri/Cargo.toml  # Fast type check (no binary)

Dockerfiles:

hadolint docker/linux/Dockerfile                         # Lint Linux Dockerfile

GitHub Actions workflows:

actionlint .github/workflows/test.yml                    # Lint single workflow
actionlint                                              # Lint all workflows

When to Run

  • Before committing: task lint && task format
  • After changing frontend code: cd app/frontend && npx vitest run
  • After changing Rust code: cargo nextest run --workspace

Test Boundaries

Each test layer has a clear responsibility. Do NOT write a Playwright E2E test for something that belongs in Vitest or Rust tests.

What to test Where Why
Store logic (queue, player, library mutations) Vitest __tests__/ Fast, isolated, supports property-based testing
Pure functions (sorting, filtering, formatting) Vitest __tests__/ No DOM needed
CSS details (padding, user-select, computed styles) Nowhere (design review) Brittle, low value
User interaction flows (click, drag, context menu) Playwright tests/ Needs real browser
Cross-component integration (play track -> queue updates -> Now Playing renders) Playwright tests/ Tests wiring between components
Backend logic (audio, DB, concurrency) Rust #[test] / proptest Must run in Rust

Rules:

  • Playwright tests in app/frontend/tests/*.spec.js MUST drive the UI with real interactions such as page.click(), page.keyboard.press() or locator.dragTo(). A test that only calls page.evaluate() to manipulate Alpine stores belongs in Vitest under app/frontend/__tests__/.
  • NEVER duplicate store logic tests between Playwright and Vitest, because Vitest is authoritative for store behavior.
  • Tag Playwright tests that need the Tauri runtime with @tauri. Default CI mode (fast) skips these.

Implementation Notes

  1. Components: Write each component with a single responsibility, using Alpine.js for interactivity and Basecoat/Tailwind classes for styling.
  2. IPC: Route every backend operation through a Tauri command called with async/await. Emit real-time updates from Rust with app.emit() and subscribe in the frontend with window.__TAURI__.event.listen().
  3. File Organization: Keep frontend code in app/frontend/ and backend code in crates/mt-tauri/src/. Keep each file under 500 lines.
  4. Testing: Place each test in the layer defined by Test Boundaries above. The Testing Guide holds the full decision framework.
  5. Code Style: Run deno lint and deno fmt on frontend code before committing. Run cargo clippy and cargo fmt --all on backend code before committing.

<CRITICAL_INSTRUCTION>

BACKLOG WORKFLOW INSTRUCTIONS

This project uses Backlog.md MCP for all task and project management.

CRITICAL RESOURCE: Read backlog://workflow/overview to understand when and how to use Backlog for this project.

  • First time working here? Read the overview resource IMMEDIATELY to learn the workflow
  • Already familiar? Reuse the cached ## Backlog.md Overview (MCP) content and re-read backlog://workflow/overview only if it is missing
  • When to read it: BEFORE creating tasks, or when you're unsure whether to track work

Key MCP Commands

Command Purpose
task_create Create a new task (status defaults to "To Do")
task_edit Edit a task's metadata, notes, status or acceptance criteria
task_view View full task details
task_search Find tasks by keyword
task_list List tasks with optional filters
task_complete Moves task to backlog/completed/ — only use for cleanup, not for marking done

Task Lifecycle

  1. Create: task_create — new task in backlog/tasks/
  2. Start: task_edit(status: "In Progress") — mark as active
  3. Done: task_edit(status: "Done") — mark finished, stays in backlog/tasks/ (visible on kanban)
  4. Archive: task_complete — moves to backlog/completed/ (use only when explicitly cleaning up)

IMPORTANT: Use task_edit(status: "Done") to mark tasks as done. Do NOT use task_complete unless the user explicitly asks to archive/clean up — it removes the task from the kanban.

Preventing ID Collisions

Backlog.md does NOT enforce ID uniqueness on the filesystem. If two task files declare the same id: in their frontmatter, task_view/task_search silently resolve to whichever has the newer updated_date, hiding the other from the kanban and MCP lookups. This is how TASK-341 ended up with two owners (iOS-sync vs. Plex umbrella) in commit 89ff249.

Before creating any task, ALWAYS:

  1. Run task_list (or rg "^id: TASK-" backlog/tasks/ backlog/archive/tasks/ backlog/completed/) to find the highest existing ID.
  2. Let task_create auto-assign the next ID — never set id: manually in frontmatter or filenames.
  3. For subtasks, pass parentTaskId to task_create so Backlog.md generates the hierarchical ID (e.g., TASK-342.1) for you.

Detecting a collision:

rg -N "^id: " backlog/tasks/ backlog/archive/tasks/ backlog/completed/ \
  | awk -F': ' '{print $NF}' | sort | uniq -d

Resolving a collision: the MCP has no rename/change-id operation. Renumbering requires: (1) git mv the file to a new filename with the new ID; (2) edit the frontmatter id: field; (3) edit parent_task_id: if the task is a subtask whose parent was also renumbered (this field is NOT settable via task_edit); (4) edit dependencies: arrays in other tasks that reference the old ID; (5) verify with rg "^id: TASK-OLD$" backlog/tasks/ returning zero results.

Cross-Branch Task Scanning (disabled)

check_active_branches and remote_operations are both disabled in backlog/config.yml. With worktrees, these features scan other branches and pull in tasks that were already completed/archived on main but still exist in backlog/tasks/ on older branches — bloating the kanban with ghost tasks. Do not re-enable without accounting for worktree branch divergence.

Multiline Field Gotcha

The finalSummary, description, implementationNotes, and planSet MCP parameters are single-line JSON strings. Literal \n sequences are NOT interpreted as newlines — they render as the two characters \ n in the markdown file. To write multiline content:

  • Use task_edit with the field for short single-paragraph content
  • For multiline content, edit the task markdown file directly with the file editing tool (the file path is shown in task_view output)

Backlog MCP Parameter Reference (Common Pitfalls)

task_edit parameter names are different from task_create:

Operation task_create param task_edit param
Task ID — (auto-assigned) id (NOT taskId)
Title title title
Description description description
Acceptance Criteria acceptanceCriteria acceptanceCriteriaSet (replaces all), acceptanceCriteriaAdd, acceptanceCriteriaRemove, acceptanceCriteriaCheck, acceptanceCriteriaUncheck
Dependencies dependencies dependencies
Parent Task parentTaskId not supported; edit parent_task_id: in the markdown frontmatter directly
Status status status
Notes — notesAppend, notesSet, notesClear
Plan — planAppend, planSet, planClear
Final Summary — finalSummary, finalSummaryAppend
References references references, addReferences, removeReferences
Documentation documentation documentation, addDocumentation, removeDocumentation

Common error: Using taskId instead of id, or acceptanceCriteria instead of acceptanceCriteriaSet, in task_edit calls. Always pass id to task_edit. Always use the acceptanceCriteria* variant names. Edit the file's parent_task_id: frontmatter directly to re-parent a task, because task_edit cannot re-parent.

The overview resource contains additional detail on decision frameworks, search-first workflow, and guides for task creation, execution, and completion.

</CRITICAL_INSTRUCTION>