18 KiB
Firelink agent guidance
This file is local operational guidance for work in the Firelink checkout. Live code, tests, workflow files, issue text, and reproducible behavior outrank this file. Details below that contain a date, version, commit, or test count are orientation only and must be rechecked before being used as current evidence.
AGENTS.md is currently ignored by the repository's .gitignore; changing the ignore policy or publishing this file is a separate, intentional decision.
Current project snapshot
Snapshot checked on 2026-07-28:
- The desktop app is Firelink 1.2.0, aligned in package.json, src-tauri/Cargo.toml, and src-tauri/tauri.conf.json.
- The parent checkout is on main at
edc76a7and was aligned with origin/main when this snapshot was taken. - Extensions/Browser is the Companion 2.0.6 submodule at b5118f1, compatible with Firelink 1.2.0. Its standalone repository remains the source of truth for extension behavior.
- The product is a Rust/Tauri desktop app with a React/TypeScript frontend, Zustand stores, SQLite persistence, local signed browser handoff, and bundled aria2, yt-dlp, FFmpeg, and Deno engines.
- The maintained release matrix is macOS arm64, Windows x64, and Linux x64. macOS builds are ad-hoc signed and not notarized; Windows builds are unsigned. Do not describe either as fully trusted or store-signed.
Before saying that a release, issue, workflow, submodule, or native platform is current, inspect it live. In particular, do not infer current status from this snapshot, an old handoff, a pushed tag, or a workflow that has not completed.
macOS dev-app Computer Use limitation
npm run tauri dev launches the bare src-tauri/target/debug/firelink
executable, not a macOS .app bundle. Computer Use cannot register that
executable as a controllable application, and app lookup by
com.nimbold.firelink can be ambiguous when /Applications/Firelink.app and
a repository-built release bundle are both present. This is an app-discovery
limitation, not a Shadowrocket/TUN failure. Check the live executable path and
duplicate bundle registrations before rebuilding a package; use an exact
packaged .app path for native Computer Use validation. Shadowrocket may still
affect Torrent metadata or transfers when Firelink's System Proxy mode is
selected, so keep app discovery and proxy-consumer behavior as separate
diagnoses.
First actions for every non-trivial task
Run these before editing:
git status --short --branch
git submodule status
Then locate the source of truth and its tests. Preserve unrelated worktree changes, including unstaged changes in tests or generated-looking files. Never use git reset --hard, git checkout --, broad cleanup, or recursive deletion to make the checkout convenient.
For issue or audit work, read the complete issue, comments, attachments, and follow-up replies before accepting the claim. Consultant or auditor feedback is hypothesis, not authority. Reproduce it against current code and retain only findings supported by source, tests, logs, or direct runtime evidence.
Trace user-visible behavior end to end before changing it:
UI event -> Zustand/store lifecycle -> typed IPC -> Tauri command
-> persistence/queue/sidecar -> event or response -> store -> rendered UI
Do not fix a symptom in one layer while leaving a stale-state, ownership, security, or visible-claim defect in another layer.
Consultants and delegation
Codex remains the primary investigator, implementer, tester, and final decision-maker. Agy is explicit opt-in second-opinion support only; do not invoke it unless the user asks for an Agy consultation. Keep any consultation bounded to relevant files and diffs, never send secrets or unrelated private data, and verify every actionable suggestion against live code, tests, logs, and repository state. A consultant must never edit, commit, push, or become the sole source of a finding.
Source map
- src-tauri/src/lib.rs owns download orchestration, media metadata and format normalization, yt-dlp arguments, progress parsing/emission, cleanup, and many focused unit tests.
- src-tauri/src/queue.rs owns queue admission, global and per-queue reservations, fairness, permits, dispatch, retries, Aria2 lifecycle registration, and terminal reconciliation.
- src-tauri/src/extension_server.rs owns the local browser handoff boundary: pairing, request validation, authentication, replay/identity checks, and extension payload normalization.
- src-tauri/src/db.rs, storage.rs, settings.rs, and download.rs own persistence and durable download state. A UI-only fix is incomplete if a restart can recreate the bug.
- src-tauri/src/commands.rs and ipc.rs define the native command boundary; src/bindings/ contains generated shared TypeScript types.
- src/store/useDownloadStore.ts owns Add-window routing, frontend lifecycle serialization, dispatch invalidation, queue actions, and extension capture handling. src/store/downloadStore.ts owns native event listeners and the canonical React projection of progress/state.
- src/components/AddDownloadsModal.tsx owns the Add-window interaction; DownloadTable.tsx and DownloadItem.tsx own the main list and live row rendering. src/i18n/ owns typed locale resources and direction handling.
- src-tauri/tests/queue_manager.rs is the first regression target for queue, permit, Aria2, retry, cancellation, and admission races. Media parser and format tests are concentrated in src-tauri/src/lib.rs; frontend lifecycle tests live beside the stores and utilities.
- scripts/ owns engine provisioning/staging, binary and package checks, release identity, and packaged-app smoke checks. .github/workflows/ is the authority for what CI and release automation actually verify.
- Read YouTube_media_download_handoff.md, RELEASE.md, and src-tauri/tests/README.md for deeper task-specific notes, but verify their claims against the current checkout.
Non-negotiable product and security boundaries
Browser captures
- Every intercepted or automatic capture, including a request marked silent, must go through handleExtensionDownload and openAddModalWithUrls. The user reviews it in the Add window before it is started or queued.
- Never add an intercepted URL directly to the download list or backend queue.
- Explicit media handoff is the canonical page URL plus media: true. Do not attach a raw browser Cookie header or a browser request-header snapshot.
- Old extension builds may still send cookies with a media request. Desktop code must discard those media cookies before opening the Add window while preserving cookies for ordinary captured file downloads.
- Media authentication belongs to Firelink's configured mediaCookieSource. Cookies or headers the user explicitly enters in the Add window are a separate, validated trust path and must not be conflated with extension data.
- The extension source of truth is /Users/nima/Documents/Code/Firelink-Extension. When extension behavior changes, verify and publish that repository first, then advance the Extensions/Browser gitlink in this repository. Never pin the parent to an older extension behavior by accident.
Downloads and bundled engines
- Normal file downloads are Aria2-backed. Media downloads use yt-dlp and may invoke bundled FFmpeg and Deno. Do not treat the Swift/legacy implementation or a user-installed binary as the active architecture.
- Resolve aria2, yt-dlp, FFmpeg, and Deno from Firelink's bundled engine resolution. Do not depend on the user's PATH.
- Keep yt-dlp as its official onedir payload: executable plus adjacent _internal/, embedded runtime, and solver files. Do not replace it with a onefile or system installation without a deliberate packaging decision.
- Never log, persist, or expose cookies, authorization headers, pairing tokens, passwords, private paths, or other secrets merely to make a failure easier to diagnose. Redact at the boundary and keep errors actionable without secret material.
- Windows subprocess and daemon file handles can outlive process termination. Deleting or replacing an artifact after sidecar activity needs bounded retry with backoff and a safe failure path.
Proxy and network claims
Proxy capability is per consumer: metadata requests, yt-dlp media transfers, FFmpeg/Deno child work, and Aria2 normal downloads do not have identical support. Metadata success does not prove final transfer success. Do not claim SOCKS or another proxy mode works end to end unless every consumer in the described workflow supports it; Aria2 is the limiting path for broad normal- download promises.
Queue and Aria2 lifecycle invariants
Treat this as a state machine, not a collection of independent button handlers.
- Reserve global and per-queue ownership atomically in one serialized admission decision. Select eligible work before capacity can be consumed by an ineligible front queue; rotate eligible queues fairly. A queue-limit reduction does not cancel transfers already active.
- run_dispatcher must not await a slow addUri before dispatching another already-admitted task. Dispatch asynchronously while retaining the reservation and lifecycle fencing in the dispatch task.
- Keep exactly one queue permit per Aria2 transfer for the full daemon lifecycle. Park it until terminal reconciliation and make release idempotent. has_active_permit(id) alone never proves that the current worker owns the permit; ownership must be established by the current queue- and lifecycle-aware parking operation.
- An Aria2 GID is not a permanent Firelink identity. Retries or re-enqueues can receive a new GID; a paused transfer can retain the same GID. Pair the download ID and GID with a monotonic control epoch/lifecycle generation.
- A legitimate resume of a paused GID must rebind that GID to the new control epoch immediately before aria2.unpause. After every awaited RPC, recheck the current epoch, registered ID, and mapped GID before emitting state or mutating queue ownership.
- tellActive is not a terminal event feed. A mapped GID missing from it must be reconciled through tellStatus, applying only complete, error, or removed; leave paused and waiting alone.
- Pause can race with completion. If tellStatus says complete, reconcile completion idempotently and report success rather than claiming pause failed. Duplicate WebSocket/poller terminal events must be harmless.
- If unpause returns an ambiguous RPC error, query tellStatus before releasing the permit: retain it for active/waiting, reconcile completion, and stay conservative when the daemon state cannot be verified.
- Terminal cleanup must invalidate delayed retry/control workers, clear retry state, forget the GID mapping, release the permit, and release the registered ID under the per-download control lock. Late addUri results must not revive a removed or newer lifecycle.
- For queue changes, cover worst cases rather than only happy paths: stale terminal events after pause, same-GID resume epoch rebinding, GID replacement during retry, lost completion events, completion racing with pause, ambiguous RPC errors, duplicate terminal events, late addUri, fairness, queue-limit shrink, cancellation, restart, and permit ownership.
Use real postconditions in asynchronous tests. Wait for a GID mapping, state transition, notification, or permit count to become correct; do not assert only that an RPC call counter increased, and do not use arbitrary sleeps as proof of completion.
Media, metadata, progress, and size invariants
- build_media_format_options in src-tauri/src/lib.rs is the source of truth for selectable media formats. Do not duplicate format interpretation in React. Preserve concrete stream IDs, exact displayed resolutions, exclusion of storyboards/MHTML/thumbnails/subtitles, split-track handling, and the explicit no-common-format state.
- Keep metadata cache/deduplication keys sensitive to all relevant request context: URL, cookie source/browser, credentials, headers/cookies, proxy, and user agent. Do not let React development behavior create duplicate yt-dlp work.
- yt-dlp arguments must keep explicit --progress together with --progress-template; Firelink also uses --print after_move:%(filepath)s, and --print implies quiet mode unless progress is enabled.
- Preserve chunk buffering in drain_media_output_lines, structured and numeric-percent parsing in parse_media_progress_line, HLS fragment progress, split-track aggregation in aggregate_media_fraction, and the emit_media_progress event path. One shell output event is not necessarily one complete line.
- The live UI path is download-progress -> src/store/downloadStore.ts -> src/components/DownloadItem.tsx. React/store state is authoritative; do not use imperative DOM mutation for live progress.
- filesize is exact, filesize_approx and bitrate-derived values are estimates, and progress-event size is authoritative only when size_is_final is true. A completed download should replace an estimate with the actual output size from disk.
- Cleanup must use exact artifact boundaries and remain safe for failed, canceled, paused, retried, and split-track media work. Never broaden a filename pattern until archive-like or unrelated files are proven safe.
React, UI, i18n, and accessibility
- Async UI work must check that a terminal state or newer lifecycle has not won before emitting a transitional status such as Downloading.
- Visible labels, settings, placeholders, controls, and status nouns are product claims. Match them to backend capability. A live Aria2 control must not be presented as a process-start-only yt-dlp control, and parity tests do not prove that a translation is natural.
- Keep the UI dense, native, and functional. For RTL, preserve correct document.dir behavior and the physical-LTR download-table/file-column contract unless the product explicitly changes it. Review scrolling, narrow widths, focus, keyboard paths, reduced motion, and pointer cancellation.
- If an exit animation collapses a wrapper to height 0, the collapsing wrapper needs overflow: hidden. Verify visual fixes in the running app or a packaged smoke path; a build alone does not prove geometry or focus behavior.
- When changing a native command or shared Rust type, keep generated bindings in sync through the repository's binding test/workflow instead of hand-editing a stale type.
Investigation and implementation workflow
- Establish scope, current branch/status, submodule state, and the exact user or issue claim.
- Read the relevant source path and nearby tests. Search for all producers, consumers, persistence fields, events, and user-visible copy.
- State the root cause and the invariant that must hold after the fix before editing. Prefer a focused regression test that fails for the reproduced behavior, including a race or malformed-input case when applicable.
- Implement the smallest root-cause change across every affected layer. Do not add a retry, timeout, DOM mutation, fallback, or duplicate parser merely to hide an unresolved contract mismatch.
- Run the focused test first and wait for its real postcondition. Then broaden validation according to the risk matrix below.
- Review git diff, git diff --check, and git status for scope, secrets, generated artifacts, unrelated formatting, and accidental submodule changes.
Verification matrix
Run the smallest relevant checks first. These are the repository's high-value gates:
# Frontend and script checks from the repository root
node --test scripts/*.node-test.js
npm test -- --run
npm run build
npm run check:i18n
# Backend checks from src-tauri
cd src-tauri
cargo test --test queue_manager -- --nocapture
cargo test --all-targets
cd ..
# Engine/package and tree checks when applicable
node scripts/verify-binaries.js
git diff --check
Use npm run check:updates for dependency/engine-source audits, but isolate network endpoint failures and retry them before changing checker logic. Use node scripts/verify-binaries.js --staged --target after staging engines and --search-root --target for packaged payloads. Use npm run tauri build for native Tauri/package changes; npm run build is frontend-only and cannot validate Rust, bundled engines, or packaging.
The CI workflow currently verifies frontend scripts/tests/build plus Rust and engine checks on macOS arm64, Windows x64, and Linux x64. Windows has a different Rust test split from Unix. The release workflow additionally builds the native packages, checks release identity, verifies final engine payloads, and runs packaged smoke checks. Local macOS testing is not evidence that Windows/Linux native behavior is verified; local unit tests are not packaged launch evidence; a pushed tag is not a published release until the workflow and release assets are read back.
For live media checks, use an available public multi-quality URL and cookies only when required. Bot checks, deleted/private videos, age gates, and geo restrictions are environmental failures, not proof of a Firelink regression.
Release, submodule, and Git discipline
- Keep package.json, src-tauri/Cargo.toml, and src-tauri/tauri.conf.json aligned. Read RELEASE.md before provisioning engines, building packages, or making public release claims.
- For paired releases, publish Firelink Companion first, verify its release, then advance Extensions/Browser, update Firelink release notes, and publish Firelink. Verify the completed workflow, exact assets, release body, and browser installation links rather than relying on tag creation.
- Do not claim Apple Developer signing, notarization, Gatekeeper approval, or Windows code signing unless the current artifact proves it. Do not claim cross-platform runtime QA from a macOS checkout.
- When the user explicitly requests conventional commit spec push main, commit and push main, or equivalent, verify branch/status, stage only the intended paths, create a Conventional Commit, push origin main, and report the commit hash. Do not create a PR unless asked.
- Otherwise, do not commit, push, tag, alter a submodule pointer, or publish external replies without explicit authorization. For public issue replies, write concise, human, paste-ready text backed by the live result.