From ae5199132d89c043f7eb0576ea09359ab9dc5631 Mon Sep 17 00:00:00 2001 From: NimBold Date: Fri, 31 Jul 2026 21:15:59 +0330 Subject: [PATCH] docs: document macOS dev app validation --- AGENTS.md | 337 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 337 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..442e9fa --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,337 @@ +# 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 edc76a7 and 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 + +1. Establish scope, current branch/status, submodule state, and the exact user + or issue claim. +2. Read the relevant source path and nearby tests. Search for all producers, + consumers, persistence fields, events, and user-visible copy. +3. 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. +4. 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. +5. Run the focused test first and wait for its real postcondition. Then broaden + validation according to the risk matrix below. +6. 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.