Files
Firelink/INTERACTION_REVIEW.md
T

569 lines
46 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Firelink Interaction Inventory and Modernization Review
Static, inventory-only review of the current React/Tauri code. No application
click-through or manual UI testing was performed.
## Implementation update — June 21, 2026
The following recommendations from this review have now been implemented:
- all new and migrated downloads receive an explicit Main Queue identity unless
the user chooses another queue;
- shared action-policy helpers now drive Start, Resume, Pause, Redownload, and
Properties lock behavior;
- tray Pause/Resume All uses centralized global store actions, and tray
reconstruction retains the complete menu;
- queued, processing, and retrying lifecycle actions are handled consistently;
- Show in Finder now sends the exact owned output path for completed and partial
downloads;
- duplicate replacement no longer suppresses deletion errors, blocks replacement
of active transfers, reports batch failures, and fails explicitly when no
rename candidate is available;
- bulk removal reports partial failures instead of failing an opaque concurrent
batch;
- List Row Density and Automatically Check for Updates are functional;
- update notifications include an action to open the release;
- the duplicate global-speed editor was replaced with a single Speed Limiter
configuration path and its capability copy is now accurate;
- extension server status reports the actual bound port;
- queue assignment, IPC event listening, path resolution, and editable-state
policy are centralized;
- the enqueue payload now uses a Rust-generated TypeScript binding;
- column widths persist, menus close with Escape, async clipboard/keychain/log
export failures are surfaced, and fatal render errors provide recovery;
- the confirmed-dead legacy `QualityModal` and its unused metadata store state
were removed. The active Add Downloads media-format flow and all backend media
format/progress/size logic were preserved.
The following larger architectural items remain recommendations rather than
being forced into this change:
- generating the complete command/event client instead of maintaining command
names in `src/ipc.ts`;
- moving all settings migration logic to one generated cross-language schema;
- replacing custom menus with a complete focus-managed menu primitive;
- making scheduler runs backend-owned objects with explicit run membership;
- adding request-level acknowledgement from the app UI back to automatic
browser-download capture.
## Status and validation legend
- **wired**: the visible control reaches the intended state/IPC/backend path.
- **partially wired**: the main path exists, but a setting, branch, result, or
failure state is not fully applied.
- **unwired/dead**: code or a visible control exists without an effective
consumer.
- **fragile**: behavior depends on duplicated state, timing, implicit status
rules, swallowed failures, or incomplete synchronization.
- **duplicated**: materially similar behavior exists in multiple handlers.
- **outdated**: the implementation works or persists, but no longer matches the
current interaction architecture or expected desktop behavior.
- **unsafe**: destructive or privileged behavior lacks a sufficiently strong
confirmation/error boundary.
- **Manual QA needed**: static inspection cannot confirm native dialog,
notification, menu, focus, accessibility, or operating-system behavior.
Validation methods named below are non-UI methods: **code trace**, **type
check**, **unit test**, **integration test**, **IPC contract check**, and
**build check**.
## Executive risk inventory
1. **Queue identity and global controls are inconsistent.** `start-now` and
`add-to-list` downloads have no `queueId`, while tray Pause/Resume All and
Scheduler Run Now operate by queue IDs. Those downloads can be omitted from
global actions. Status: **fragile / partially wired**.
2. **Tray menu rebuilding changes its action set.** Startup creates Show,
Pause All, Resume All, Quit; re-enabling the menu-bar icon creates only Show
and Quit. Status: **duplicated / partially wired**.
3. **Several settings advertise behavior that is not consumed.** List Row
Density and Automatically Check for Updates are persisted but have no
runtime consumer. Status: **unwired/dead**.
4. **Destructive duplicate replacement suppresses deletion failure.** The
existing list item may be removed before `delete_file` fails, and the
replacement continues. Status: **unsafe / fragile**.
5. **Action availability is implemented separately in rows, single-select
menus, multi-select menus, toolbar controls, queue controls, tray handlers,
and scheduler handlers.** Their status sets already differ, especially for
`processing` and `retrying`. Status: **duplicated / fragile**.
6. **The frontend IPC wrapper is incomplete and handwritten.** Rust-generated
data types exist, but command/event names and payloads are manually mapped;
some live calls bypass the wrapper and some mapped commands are legacy.
Status: **outdated / fragile**.
7. **Legacy media-quality state and component are dead.** `QualityModal`,
`activeMetadata`, `fetchMetadataAction`, and `activeDownloadId` are not
connected to the rendered app; the active add flow has its own media
selection implementation. Status: **unwired/dead / duplicated**.
---
## 1. Main window
### 1.1 Window shell, toolbar, and status bar
| ID | UI action | Component / function | Store action | IPC command | Rust function | Expected behavior | Status | Validation |
|---|---|---|---|---|---|---|---|---|
| MW-01 | Drag window | `WindowDragRegion.onPointerDown`; title-bar drag regions | none | Tauri window API | Tauri runtime | Move the native window | wired; Manual QA needed | code trace, build check |
| MW-02 | Resize sidebar | `App.startSidebarResize` | local `sidebarWidth`, `localStorage` | none | none | Resize sidebar from 190260 px and persist width | wired; fragile pointer-only interaction; Manual QA needed | code trace |
| MW-03 | Hide sidebar | `Sidebar` title control | `toggleSidebar` | none | none | Collapse sidebar | wired | code trace |
| MW-04 | Show sidebar | `DownloadTable` title control | `toggleSidebar` | none | none | Restore sidebar | wired | code trace |
| MW-05 | Add Download | `DownloadTable` plus button | `toggleAddModal(true)` | none | none | Open Add Downloads | wired | code trace |
| MW-06 | Resume All in current view | `DownloadTable` toolbar loop | `resumeDownload` per matching row | `resume_download`, sometimes `enqueue_download` | `resume_download`, `enqueue_download` | Start/resume ready or paused items visible in the current filter | wired but fragile: label sounds global and excludes failed/retrying | unit test, IPC contract check |
| MW-07 | Pause All in current view | `DownloadTable` toolbar loop | none; direct handler | `pause_download` per downloading row | `pause_download` | Pause downloading items visible in the current filter | wired but fragile: excludes processing/retrying and is not global | unit test, integration test |
| MW-08 | Read active/queued/done counts | `App` status bar | derived store state | none | none | Show application totals | wired; `queued` excludes `ready` | code trace |
Recommendation: replace the three toolbar buttons with a macOS-style primary
Add split button plus an overflow menu for view-scoped Start/Pause actions.
Name scope explicitly, for example “Pause Visible Downloads.” Route all status
eligibility through shared action selectors.
### 1.2 Download list selection and row actions
| ID | UI action | Component / function | Store action | IPC command | Rust function | Expected behavior | Status | Validation |
|---|---|---|---|---|---|---|---|---|
| MW-09 | Select row | `DownloadTable.handleItemClick` | local `selectedIds` | none | none | Select one row | wired | unit test |
| MW-10 | Toggle multi-selection | same; Cmd/Ctrl click | local `selectedIds` | none | none | Add/remove row from selection | wired; Manual QA needed for platform modifiers | unit test |
| MW-11 | Range selection | same; Shift click | local `selectedIds`, `lastSelectedId` | none | none | Select visible range | wired; fragile when filtering/reordering changes anchor | unit test |
| MW-12 | Right-click row | `DownloadItem.onContextMenu` | local context-menu state | none | none | Select row if needed and open menu | wired; Manual QA needed | code trace |
| MW-13 | Options button | `DownloadItem` overflow button | local context-menu state | none | none | Open same menu | wired | code trace |
| MW-14 | Double-click completed row | `handleDownloadDoubleClick``openDownloadFile` | none | `open_downloaded_file` | `commands::open_downloaded_file` | Open owned downloaded file | wired; Manual QA needed | IPC contract check, integration test |
| MW-15 | Double-click unfinished row | `handleDownloadDoubleClick``openProperties` | `setSelectedPropertiesDownloadId` | none | none | Open Properties | wired | code trace |
| MW-16 | Move queued item up/down | `DownloadItem` hover controls | `moveInQueue` | `move_in_queue` | `QueueManager::move_in_queue` | Reorder backend pending queue | wired; visible order remains download-list order rather than pending order | integration test |
| MW-17 | Pause row | `DownloadItem` hover control | none | `pause_download` | `pause_download` | Pause downloading, processing, or retrying item | wired; errors only reach console | integration test |
| MW-18 | Start/Resume row | `DownloadItem` hover control | `resumeDownload` | `resume_download`, fallback `enqueue_download` | `resume_download`, `enqueue_download` | Start ready item or resume paused item | wired; errors only reach console | unit test, integration test |
| MW-19 | Resize table column | `DownloadTable.startColumnResize` | local `columnWidths` | none | none | Resize column until view remount | wired; outdated because widths are not persisted and pointer-only; Manual QA needed | code trace |
Recommendation: introduce a `DownloadAction` enum and a single
`getAvailableActions(download, selectionContext)` function. Row buttons,
context menus, toolbar actions, keyboard commands, tray actions, and queue
actions should call the same handlers and eligibility rules.
### 1.3 Single-selection context menu
| ID | Action | Component / function | Store action | IPC / Rust | Expected behavior | Status | Validation |
|---|---|---|---|---|---|---|---|
| CM-01 | Open | `openDownloadFile` | none | `open_downloaded_file` / `commands::open_downloaded_file` | Open completed owned file | wired; Manual QA needed | integration test |
| CM-02 | Show in Finder | `revealDownloadFile` | none | `reveal_in_file_manager` / `commands::reveal_in_file_manager` | Reveal completed file, partial file, or known destination | partially wired: backend requires an owned file path, but unfinished UI may send only a directory | integration test |
| CM-03 | Pause | `handlePause` | none | `pause_download` / `pause_download` | Pause queued/downloading/retrying item | wired, but menu omits `processing` although row action includes it | unit test |
| CM-04 | Start/Resume | `handleResume` | `resumeDownload` | `resume_download`, `enqueue_download` | Start ready or resume paused/failed/retrying | fragile: retrying exposes both Pause and Resume in the same menu | unit test, integration test |
| CM-05 | Redownload | `redownload` | `redownload` | `enqueue_download` / `enqueue_download` | Create and immediately enqueue a copy | wired; status is initialized as queued and queue identity may be absent | unit test |
| CM-06 | Add to Queue | inline menu handler | `updateDownload({queueId})` | none | Reassign logical queue | partially wired: frontend grouping changes but no explicit backend queue/order reconciliation | integration test |
| CM-07 | Copy Address | clipboard handler | none | browser clipboard | Copy URL | wired; Manual QA needed | code trace |
| CM-08 | Copy File Path | clipboard handler | none | browser clipboard | Copy resolved completed path | wired; Manual QA needed | unit test |
| CM-09 | Remove | `handleDelete` | `openDeleteModal` | none | Open removal confirmation | wired | code trace |
| CM-10 | Properties | `openProperties` | `setSelectedPropertiesDownloadId` | none | Open Properties | wired | code trace |
### 1.4 Multi-selection context menu
| ID | Action | Component / function | Store action | IPC / Rust | Expected behavior | Status | Validation |
|---|---|---|---|---|---|---|---|
| CM-11 | Start/Resume selected | inline loop | `resumeDownload` | resume/enqueue commands | Start eligible selected items | wired but duplicated; eligibility differs from toolbar and single menu | unit test |
| CM-12 | Add selected to Queue | inline loop | `updateDownload({queueId})` | none | Reassign non-completed items | partially wired / duplicated | integration test |
| CM-13 | Copy selected addresses | inline clipboard handler | none | browser clipboard | Copy URLs separated by newlines | wired; no success feedback | code trace |
| CM-14 | Remove selected | `openDeleteModal(ids)` | modal state | none | Confirm bulk removal/deletion | wired; destructive operations execute concurrently | integration test |
Recommendation: use an accessible menu primitive with roving focus, Escape,
arrow-key navigation, viewport collision handling, and one action registry.
The current hover-only nested queue submenu is fragile for keyboard and trackpad
users. Manual QA is needed to confirm current focus behavior.
### 1.5 Sidebar navigation and queues
| ID | Action | Component / function | Store action | IPC / Rust | Expected behavior | Status | Validation |
|---|---|---|---|---|---|---|---|
| SB-01 | Select Library filter | `Sidebar.NavItem` | `App.setFilter`, `setActiveView('downloads')` | none | Filter All/Active/Completed/Unfinished | wired | unit test |
| SB-02 | Select category folder | `Sidebar.NavItem` | same | none | Filter by category | wired | unit test |
| SB-03 | Select queue | `QueueItem` | same | none | Filter by queue ID | wired | unit test |
| SB-04 | Add queue | add input, Enter/blur | `addQueue` | DB persistence subscription | DB replace functions | Create a persisted logical queue | wired; duplicate/blank-after-trim names are not explicitly reported | unit test |
| SB-05 | Cancel add queue | Escape | local state | none | Close input without adding | wired; blur after Escape can still run submit with current value depending event order; Manual QA needed | unit test |
| SB-06 | Rename queue | context action, input Enter/blur | `renameQueue` | DB persistence subscription | DB replace functions | Rename queue | wired; same blur/keyboard fragility | unit test |
| SB-07 | Start Queue | queue context menu | `startQueue` | resume/enqueue commands | Start queued/paused/failed items with this queue ID | wired but fragile around `hasBeenDispatched` and absent queue IDs | unit test, integration test |
| SB-08 | Pause Queue | queue context menu | `pauseQueue` | `pause_download` | Pause active items in queue | partially wired: only `downloading`, not `processing` or `retrying` | unit test |
| SB-09 | Delete Queue | queue context menu | `removeQueue` | DB persistence subscription | Reassign items to Main Queue and delete custom queue | wired; no confirmation | unit test |
| SB-10 | Open Scheduler | `ToolItem` | `setActiveView` | none | Show Scheduler | wired | code trace |
| SB-11 | Open Speed Limiter | `ToolItem` | `setActiveView` | none | Show Speed Limiter | wired | code trace |
| SB-12 | Open Logs | `ToolItem` | `setActiveView` | none | Show Logs | wired | code trace |
| SB-13 | Open Settings | footer button | `setActiveView` | none | Show Settings | wired | code trace |
Recommendation: make queue membership explicit and total. Every non-completed
download should either have a queue ID or an explicit `unassigned` state that
global commands intentionally include/exclude. Do not use optional `queueId`
as an implicit behavior switch.
---
## 2. Add Downloads window
### 2.1 Input, metadata, preview, and destination
| ID | Action | Component / function | Store action | IPC / Rust | Expected behavior | Status | Validation |
|---|---|---|---|---|---|---|---|
| AD-01 | Enter/paste URL lines | URL textarea and metadata effect | local state | `fetch_metadata` or `fetch_media_metadata` | Parse each line and show metadata | wired but fragile: any non-empty line is counted as “valid”; metadata requests are sequential | unit test, integration test |
| AD-02 | Refresh Metadata | refresh button | nonce increment | same metadata commands | Re-run metadata lookup | wired | unit test |
| AD-03 | Select preview item | preview row click/Enter/Space | local selected index | none | Select item for media format details | wired | unit test |
| AD-04 | Select media stream | `selectMediaFormat` | local parsed item | none | Change format, extension, and estimated size | wired; duplicated with dead `QualityModal` implementation | unit test |
| AD-05 | Browse save location | `handleBrowse` | local manual destination | dialog plugin | Select one shared folder | wired; Manual QA needed | code trace |
| AD-06 | Ask where to save on action | `handleAction` | reads setting | dialog plugin | Prompt before committing additions | wired; name says “each file” but one folder is selected for the whole batch | unit test, Manual QA needed |
| AD-07 | Read free space | save-location effect | local `freeSpace` | `get_free_space` / `get_free_space` | Show available space for destination | wired; path is not constrained because this is read-only | IPC contract check |
Recommendation: separate URL parsing from metadata loading. Build a validated
draft list first, reject unsupported schemes immediately, then run metadata
lookups concurrently with bounded concurrency and per-item retry/error actions.
### 2.2 Transfer and authentication controls
| ID | Action | Component / function | Store effect | Backend payload | Expected behavior | Status | Validation |
|---|---|---|---|---|---|---|---|
| AD-08 | Connections slider | local state | passed to `addDownload` | `connections` | Set 116 connections for non-media | wired; defaults to 16 instead of current setting; disabled if any batch item is media | unit test |
| AD-09 | Per-file speed toggle/value | local state | passed to `addDownload` | `speed_limit` | Apply per-download limit | wired; accepts invalid/zero text until backend normalization/use | unit test |
| AD-10 | Authorization toggle | local state | passed to `addDownload` | username/password | Use ad-hoc credentials | wired; session-only secrets are intentionally not persisted | unit test |
| AD-11 | Username/password | local state | passed to store | username/password | Override matching site login | wired | integration test |
| AD-12 | Advanced disclosure | local state | none | none | Show advanced fields | wired | code trace |
| AD-13 | Checksum toggle/algorithm/digest | local state | passed to store | checksum | Verify checksum | wired; digest format is not validated in UI | unit test |
| AD-14 | Headers | local state | passed to store | headers | Add request headers, including extension referer | wired; raw multiline input | integration test |
| AD-15 | Cookies | local state | passed to store | cookies | Add Cookie header | wired; raw secret input | integration test |
| AD-16 | Mirrors | local state | passed to store | mirrors | Add alternate URIs | wired; scheme/value validation deferred | integration test |
### 2.3 Commit actions and duplicate modal
| ID | Action | Component / function | Store action | IPC / Rust | Expected behavior | Status | Validation |
|---|---|---|---|---|---|---|---|
| AD-17 | Start Downloads | `handleAction({type:'start-now'})` | `addDownload``dispatchItem` | `enqueue_download` | Add and immediately dispatch each item | wired; per-item failures are logged and modal still closes | integration test |
| AD-18 | Add to List | action menu | `addDownload({type:'add-to-list'})` | DB persistence only | Add as ready without dispatch | wired; omitted from queue-based global actions | unit test |
| AD-19 | Add to Queue | nested action menu | `addDownload({type:'add-to-queue'})` | no immediate backend enqueue | Add as queued for selected logical queue | wired; local pending order is updated before backend registration | unit test |
| AD-20 | Cancel | footer button | `toggleAddModal(false)` | none | Close and clear pending extension/deep-link fields | wired | code trace |
| AD-21 | Choose Rename/Replace/Skip | `DuplicateResolutionModal` | local conflict state | none | Select resolution per conflict | wired; URL duplicates default to Rename although renaming does not resolve URL duplication | unit test |
| AD-22 | Continue duplicate resolution | `executeAddDownloads` | remove/add actions | `check_file_exists`, `delete_file`, enqueue commands | Apply selected resolutions and add items | unsafe / fragile: replacement delete failures are swallowed; rename loop silently stops at 999 | integration test |
| AD-23 | Cancel duplicate resolution | duplicate modal Cancel | local state | none | Return to Add Downloads without committing | wired | code trace |
Recommendation: move duplicate resolution into a transactional service that
returns typed outcomes. Never remove the existing list item until filesystem
replacement succeeds or a backend-owned overwrite operation has accepted the
request. Show a batch result summary when some additions fail.
---
## 3. Download Properties window
| ID | Action | Component / function | Store action | IPC / Rust | Expected behavior | Status | Validation |
|---|---|---|---|---|---|---|---|
| PR-01 | Edit URL | local state | applied by `applyProperties` | may re-enqueue later | Change source for eligible item | wired | unit test |
| PR-02 | Edit filename | local state | `applyProperties` | may re-enqueue later | Change output name | wired | unit test |
| PR-03 | Select save location | `handleBrowse` | local state | dialog plugin | Change destination | wired; Manual QA needed | code trace |
| PR-04 | Edit connections | local state | `applyProperties` | enqueue payload | Change future/reconfigured transfer | wired; no on-blur clamp | unit test |
| PR-05 | Edit speed limit | local state | `applyProperties` | enqueue payload | Change future/reconfigured transfer | wired | unit test |
| PR-06 | Matching/custom/no-login mode | local state | `applyProperties` | dispatch credential resolution | Choose credential source | wired | unit test |
| PR-07 | Custom credentials | local state | `applyProperties` | enqueue payload | Use ad-hoc credentials | wired; secret is not persisted | integration test |
| PR-08 | Advanced transfer fields | local state | `applyProperties` | enqueue payload | Change checksum/cookies/headers/mirrors | wired | unit test |
| PR-09 | Save | `handleSave` | `applyProperties` | `remove_from_queue`, `detach_download_for_reconfigure`, `enqueue_download` depending status | Apply safe changes and close | wired but fragile: behavior is status-dependent and rollback is incomplete after re-dispatch failure | unit test, integration test |
| PR-10 | Cancel | footer button | clear selected ID | none | Close without applying | wired | code trace |
Status rules:
- active `downloading`/`processing`/`retrying`: Save disabled and store rejects.
- `ready`/`completed`/`failed`: frontend store update only.
- backend-registered `queued`: remove, update, re-dispatch.
- backend-registered `paused`: detach with acknowledgement, update, re-dispatch on resume.
Recommendation: model Properties as a typed edit session with explicit modes:
`identity-editable`, `transfer-options-only`, `requires-detach`, and
`read-only`. A single policy function should drive disabled fields, copy, save,
and backend transition behavior. The current `isLocked` and
`isTransferLocked` booleans are easy to diverge.
---
## 4. Removal confirmation modal
| ID | Action | Component / function | Store action | IPC / Rust | Expected behavior | Status | Validation |
|---|---|---|---|---|---|---|---|---|
| RM-01 | Cancel | `handleCancel` | `closeDeleteModal` | none | Close without changes | wired | code trace |
| RM-02 | Remove from list | `handleRemoveFromList` | `removeDownload(id,false)` | `remove_download` | Stop backend work, remove record, retain file | wired; bulk operations run concurrently and partially completed batches remain if one fails | integration test |
| RM-03 | Delete file | `handleDeleteFile` | `removeDownload(id,true)` | `trash_download_assets`, then `remove_download` | Trash owned primary/partial assets and remove record | wired with strong ownership checks; bulk partial-failure UX is fragile | integration test |
Recommendation: use a batch command returning per-item results, then display a
clear partial-success summary. Keep the exact owned-path authorization model in
`commands.rs`; do not replace it with broad folder-prefix authorization.
---
## 5. Settings window
### 5.1 Navigation and Downloads
| ID | Action | Store / IPC | Expected behavior | Status | Validation |
|---|---|---|---|---|---|
| ST-01 | Select one of nine settings tabs | `setActiveSettingsTab` | Switch pane and persist selected tab | wired | unit test |
| ST-02 | Default connections | `setPerServerConnections` | Set default 116 | wired; only clamped on blur | unit test |
| ST-03 | Parallel downloads | `setMaxConcurrentDownloads` → App effect `set_concurrent_limit` | Resize backend concurrency | wired; transient invalid values can reach backend before blur | integration test |
| ST-04 | Global speed limit text | `setGlobalSpeedLimit` → App effect `set_global_speed_limit` | Apply global limit | wired but duplicated with Speed Limiter; invalid input silently becomes unlimited in backend normalization | unit test |
| ST-05 | Automatic retries | `setMaxAutomaticRetries` | Set future enqueue retry count | wired; current active tasks are unchanged | unit test |
| ST-06 | Completion notifications | `setShowNotifications` | Gate terminal OS notifications | wired; permission is requested at startup regardless of setting | integration test, Manual QA needed |
| ST-07 | Completion sound | `setPlayCompletionSound` | Add sound to completion notification | wired; failed notifications never use sound | code trace, Manual QA needed |
### 5.2 Look and Feel
| ID | Action | Store / IPC | Expected behavior | Status | Validation |
|---|---|---|---|---|---|
| ST-08 | Theme | `setTheme`; App root-class effect | Apply system/light/dark/Dracula/Nord | wired; Manual QA needed | code trace |
| ST-09 | Font size | `setAppFontSize`; App data attribute | Apply small/standard/large | wired; Manual QA needed | code trace |
| ST-10 | List Row Density | `setListRowDensity` | Change download-row density | **unwired/dead**: no consumer outside settings persistence | code trace, unit test |
| ST-11 | Dock badge | `setShowDockBadge`; `update_dock_badge` | Show active count | wired but duplicated between setter, App effect, and download-store sync | integration test, Manual QA needed |
| ST-12 | Menu bar icon | `setShowMenuBarIcon`; `toggle_tray_icon` | Show/hide tray | partially wired: re-created tray loses Pause/Resume All | integration test, Manual QA needed |
### 5.3 Network
| ID | Action | Store / IPC | Expected behavior | Status | Validation |
|---|---|---|---|---|---|
| ST-13 | Proxy mode | `setProxyMode`; dispatch-time `getProxyArgs` | None/system/custom proxy for new dispatches | wired | unit test |
| ST-14 | Proxy host/port | setters | Build custom HTTP proxy URL | wired; host/scheme/auth validation is minimal | unit test |
| ST-15 | Custom User Agent | `setCustomUserAgent` | Apply to metadata and downloads | wired; preset strings are dated static examples | integration test |
### 5.4 Locations
| ID | Action | Store / IPC | Expected behavior | Status | Validation |
|---|---|---|---|---|---|
| ST-16 | Edit base folder text | `setBaseDownloadFolder` | Change automatic category base | wired; does not create folders or verify access until used | unit test |
| ST-17 | Browse base folder | `handleBrowseBase`; `create_category_directories` | Select base and create normalized category folders | wired; directory creation warnings do not reach UI | integration test, Manual QA needed |
| ST-18 | Ask where to save each file | `setAskWhereToSaveEachFile` | Prompt during Add action | partially wired: one prompt per batch, not per file | unit test |
| ST-19 | Edit category path/subfolder | `CategoryFolderInput` | Use relative automatic subfolder or absolute override | wired but fragile: writes persisted settings on each keystroke and infers mode from string prefix | unit test |
| ST-20 | Custom folder | `handleBrowseCategory` | Set absolute category override | wired; Manual QA needed | code trace |
| ST-21 | Use automatic | clear override | Return to base/subfolder resolution | wired | unit test |
| ST-22 | Reset Defaults | `resetCategoryLocations` | Reset subfolders and overrides | wired; no confirmation | unit test |
The project already has shared frontend location normalization and a backend
settings decoder/migration layer, but equivalent migration and normalization
logic still exists in both TypeScript and Rust. Recommendation: define one
versioned persisted-settings schema and generate both bindings and migrations,
or make Rust the authoritative migration service and return normalized settings
to the frontend.
### 5.5 Site Logins
| ID | Action | Store / IPC | Expected behavior | Status | Validation |
|---|---|---|---|---|---|
| ST-23 | Add login | `handleAddLogin`; `set_keychain_password`; `addSiteLogin` | Store username/pattern and password in keychain | wired; pattern validation is deferred to matching logic | integration test |
| ST-24 | Delete login | inline delete; `delete_keychain_password`; `removeSiteLogin` | Remove keychain secret and persisted metadata | fragile: keychain deletion failure is logged but metadata is still removed | integration test |
### 5.6 Power, Engines, Integrations, About
| ID | Action | Store / IPC | Expected behavior | Status | Validation |
|---|---|---|---|---|---|
| ST-25 | Prevent system sleep | setter plus download-state sync; `set_prevent_sleep` | Keep system awake during active downloads | wired but duplicated in setter/store synchronization | integration test, Manual QA needed |
| ST-26 | Recheck engines | four engine status commands | Validate packaged sidecars | wired | integration test, build check |
| ST-27 | Show/hide engine details | local expanded state | Reveal engine status | wired | code trace |
| ST-28 | Browser Cookies Source | `setMediaCookieSource` | Pass selected browser to media metadata/downloads | wired; Manual QA needed for browser permissions | integration test |
| ST-29 | Copy pairing token | clipboard handler | Copy keychain-hydrated token | wired; success toast is shown without awaiting clipboard result | code trace, Manual QA needed |
| ST-30 | Regenerate pairing token | `regeneratePairingToken`; keychain + App effect | Rotate token and reconfigure local server | wired but fragile: UI reports success before keychain/server calls confirm | integration test |
| ST-31 | Open extension links | external anchors | Open Firefox store or GitHub releases | wired; Manual QA needed | code trace |
| ST-32 | Check Now | `check_for_updates` | Compare stable GitHub release version and show toast | wired; available update toast has no action to open release/download | integration test |
| ST-33 | Automatically check for updates | `setAutoCheckUpdates` | Check on startup/periodically | **unwired/dead**: persisted only | code trace |
| ST-34 | Open source/license links | external anchors | Open project pages | wired; Manual QA needed | code trace |
The Integrations status text is hard-coded to “Active” and the full port range;
it is not backed by server state. Status: **outdated / partially wired**.
---
## 6. Scheduler
| ID | Action | Component / function | Store / IPC / Rust | Expected behavior | Status | Validation |
|---|---|---|---|---|---|---|
| SC-01 | Enable Scheduler | draft checkbox | local draft then `setScheduler` on Save | Enable timed triggers | wired | unit test |
| SC-02 | Set start/stop time | draft inputs | persisted scheduler; `scheduler::spawn_scheduler` | Trigger start/stop at local time | wired; one-second polling and string-key persistence are fragile | unit test, integration test |
| SC-03 | Run Every Day / day buttons | draft controls | persisted selected days | Select schedule days | wired | unit test |
| SC-04 | Select post-queue action | radio cards | persisted enum; `perform_system_action` | Do nothing/sleep/restart/shutdown after scheduled work | wired but high-impact; no final confirmation at execution | integration test, Manual QA needed |
| SC-05 | Run Now | `runNow` | `startQueue(MAIN_QUEUE_ID)` | Start Main Queue | partially wired: misses unassigned ready/paused/failed downloads despite UI text saying all paused/failed | unit test |
| SC-06 | Pause | `pauseNow` | `pauseQueue(MAIN_QUEUE_ID)` | Pause Main Queue | partially wired: only downloading items with Main Queue ID | unit test |
| SC-07 | Save Settings | `save` | `setScheduler` | Persist normalized draft | wired | unit test |
| SC-08 | Grant permission | `handlePermissionAction` | `request_automation_permission` | Prompt/check macOS Automation | wired; command is also used as a status probe and can prompt during passive view entry; Manual QA needed | integration test |
| SC-09 | “Revoke permission” | same handler | `open_automation_settings` | Explain and open System Settings | partially wired/outdated label: app cannot revoke directly | code trace, Manual QA needed |
Recommendation: represent scheduler execution as a backend-owned state machine
with a specific run ID and set of download IDs. The frontend should observe
that run, not infer completion from all globally active statuses.
---
## 7. Speed Limiter
| ID | Action | Store / IPC | Expected behavior | Status | Validation |
|---|---|---|---|---|---|
| SP-01 | Enable/disable draft | local state | Prepare limit change | wired | unit test |
| SP-02 | Enter value | local state | Set numeric limit | wired | unit test |
| SP-03 | Select KB/s or MB/s | local state | Change unit | wired | unit test |
| SP-04 | Select 1/5/10 MB/s preset | local state | Fill common limit | wired | unit test |
| SP-05 | Save Limit | settings setters → App effect `set_global_speed_limit` | Apply backend global cap | wired; duplicated with Settings text field; UI claims active jobs are gracefully restarted, but backend only changes aria2 global option | IPC contract check, integration test |
Recommendation: keep one global speed-limit editor and one parser/formatter.
Expose backend capability/result so copy does not promise behavior for media or
native transfers that the command does not implement.
---
## 8. Logs
| ID | Action | Component / function | IPC / Rust | Expected behavior | Status | Validation |
|---|---|---|---|---|---|---|
| DG-01 | Filter severity | local `levelFilter` | none | Filter visible attached logs | wired | unit test |
| DG-02 | Clear console | `handleClear` | none | Clear only current in-memory view | wired; label could clarify that log file remains | unit test |
| DG-03 | Export Logs | `handleExport` | `export_logs` / `export_logs` | Copy current log file to selected path | wired; no success/error toast; Manual QA needed | integration test |
---
## 9. Toasts, notifications, and error surfaces
| ID | User-facing action/event | Component / function | Expected behavior | Status | Validation |
|---|---|---|---|---|---|
| NT-01 | Dismiss toast | `ToastItem` close button | Remove toast with exit animation | wired | unit test |
| NT-02 | Hover/focus toast | `ToastItem` timer logic | Pause auto-dismiss | wired | unit test |
| NT-03 | Pairing-token migration: Copy token | actionable toast in `App` | Copy token and acknowledge notice | partially wired: clipboard failure is ignored; toast is not explicitly dismissed | unit test |
| NT-04 | Pairing-token migration: Integrations | actionable toast in `App` | Open Integrations and acknowledge | wired | unit test |
| NT-05 | Download complete/failed OS notification | `App` terminal-state listener | Show native notification when enabled | wired; permission request result is ignored; Manual QA needed | integration test |
| NT-06 | Root render failure | `ErrorBoundary` | Show fatal error and component stack | wired but outdated/unsafe for production: exposes technical stack and offers no recovery action | build check |
The root toast provider is already the correct architectural direction.
Recommendation: standardize async user actions around a shared result-to-toast
helper, and ensure success is shown only after clipboard, keychain, filesystem,
or IPC completion.
---
## 10. Tray and native menus
No custom main-window application menu actions were found. The native
user-facing menu is the menu-bar/tray menu.
| ID | Action | Rust location | Frontend/store path | Expected behavior | Status | Validation |
|---|---|---|---|---|---|---|
| TR-01 | Left-click tray icon | startup and rebuilt tray handlers | `restore_main_window` | Show/focus main window | wired; Manual QA needed | integration test |
| TR-02 | Show Firelink | tray menu | `restore_main_window` | Show/focus main window | wired | integration test |
| TR-03 | Pause All | startup tray emits `tray-action` | `initDownloadListener``pauseQueue` per distinct queue ID | Pause all downloads | partially wired: excludes unassigned items and queue pause ignores processing/retrying | integration test |
| TR-04 | Resume All | startup tray emits `tray-action` | `startQueue` per distinct queue ID | Resume all downloads | partially wired: excludes unassigned items | integration test |
| TR-05 | Quit | tray handler | `app.exit(0)` | Exit application | wired; Manual QA needed | integration test |
| TR-06 | Close main window | `on_window_event` | hide instead of exit | Keep app running in background | wired; discoverability depends on Dock/tray state; Manual QA needed | integration test |
Recommendation: define one tray menu builder and update visibility in place.
Use backend commands for true global pause/resume rather than reconstructing
global behavior from optional frontend queue IDs.
---
## 11. Extension-triggered UI actions
### 11.1 Firefox extension popup and browser menus
| ID | Extension action | JS function/listener | App effect | Status | Validation |
|---|---|---|---|---|---|
| EX-01 | Toggle Capture All Downloads | popup `globalToggle` | Browser download interception on/off | wired | extension integration test |
| EX-02 | Disable capture on current site | popup `siteToggle` | Host-specific interception exclusion | wired; naming stores inverse boolean | unit test |
| EX-03 | Save pairing token | popup `saveTokenBtn` | Store token and probe `/ping` | wired; token remains extension-local plaintext storage by browser design | integration test |
| EX-04 | Expand/collapse token field | popup `pairingToggleBtn` | Show/hide pairing controls | wired | code trace |
| EX-05 | Toggle popup theme | popup `themeToggleBtn` | Persist popup light/dark theme | wired | code trace |
| EX-06 | Download link with Firelink | browser context menu | authenticated `/download` → app Add Downloads | wired | integration test |
| EX-07 | Download selected with Firelink | context menu + injected `content.js` | Extract selected links and open Add Downloads | wired; fallback paths are complex and duplicated | integration test |
| EX-08 | Automatic browser download capture | `chrome.downloads.onCreated` | Forward, then cancel/erase browser download after accepted response | wired but high-risk: acceptance means UI event emission, not confirmed app enqueue | integration test |
| EX-09 | Deep-link fallback | `sendToFirelink.triggerDeepLink` | `firelink://add``deep-link-add-download` | Open Add Downloads when local server unavailable | wired; browser fallback behavior needs Manual QA | integration test |
### 11.2 Native extension server to UI
| ID | Trigger | Backend / frontend path | Expected behavior | Status | Validation |
|---|---|---|---|---|---|
| EX-10 | Authenticated `/download` | `extension_server::download_handler``extension-add-download``handleExtensionDownload` | Wake/focus app and merge sanitized URLs into Add Downloads | wired | integration test |
| EX-11 | Deep link | deep-link parser/event → `openAddModalWithUrls` | Open Add Downloads with URLs | wired | integration test |
| EX-12 | Extension connection status shown in Settings | static Settings text | Report live server state | **unwired/dead**: text is hard-coded | code trace |
Recommendation: return a request ID from `/download`, acknowledge only after
the frontend has created a draft, and optionally return a second “queued”
result. That would let automatic browser capture cancel the browser download
only after Firelink has actually accepted responsibility.
---
## 12. IPC-backed background actions affecting UI
| ID | Background action | Frontend path | IPC / Rust | User-facing result | Status | Validation |
|---|---|---|---|---|---|---|
| BG-01 | Database hydration | `initDB` | DB load commands | Restore downloads/queues and auto-enqueue queued items | fragile: `enqueue_many` per-item results are ignored and frontend backend-registration state is not rebuilt directly | integration test |
| BG-02 | Progress events | `initDownloadListener` | `download-progress` | Update progress, speed, ETA, size | wired | integration test |
| BG-03 | State events | `initDownloadListener` | `download-state` | Update lifecycle, pending order, registration state | wired but event errors/retry reasons are not surfaced in row UI | integration test |
| BG-04 | Scheduler trigger | `App` listener | `schedule-trigger` | Start/pause Main Queue | wired with queue-scope gaps | integration test |
| BG-05 | Pairing token hydration | App startup | keychain hydration commands | Configure extension authentication and migration toast | wired | integration test |
| BG-06 | Theme system change | App media-query listener | none | Follow macOS appearance | wired; Manual QA needed | code trace |
| BG-07 | Paste outside inputs | App paste listener | none | Extract supported URLs and open Add Downloads | wired | unit test |
---
## 13. Dead, duplicated, outdated, and drift-prone implementation inventory
| Area | Evidence | Status | Practical replacement |
|---|---|---|---|
| Legacy media quality flow | `QualityModal.tsx`, `activeMetadata`, `fetchMetadataAction`, `activeDownloadId` have no rendered caller | unwired/dead, duplicated | Remove after confirming no external import; keep the Add Downloads media-format flow |
| IPC command typing | `src/ipc.ts` manually lists commands; `utils/downloads.ts` and tray listener bypass it; several listed commands are legacy UI-unused | outdated, fragile | Generate command and event bindings from Rust, including argument/result types |
| Action rules | Status checks repeated across row, toolbar, menus, queues, tray, scheduler | duplicated, fragile | Shared action enum, selectors, and command handlers |
| Tray construction | Startup and `toggle_tray_icon` build different menus | duplicated, partially wired | One builder/update function |
| Global speed editor | Settings and Speed Limiter use different input models/copy | duplicated | One shared control and parser |
| Settings decoding/migration | TypeScript normalization plus Rust decoder/migration | duplicated, fragile | One authoritative versioned schema/migration boundary |
| Category classification | TypeScript and Rust maintain separate extension lists | duplicated, drift-prone | Generate/shared data table or backend-owned classification |
| Supported media domains | Rust list copied into frontend fallback, then asynchronously refreshed | duplicated, drift-prone | Generated/static shared binding or required startup capability payload |
| Path resolution | Shared frontend resolver exists, but store also has a separate tilde resolver and backend has additional resolution/authorization paths | duplicated, fragile | One frontend resolver plus backend canonical ownership API; avoid new scattered joins |
| Boolean lock policy | `isLocked`, `isTransferLocked`, many status condition arrays | fragile | Explicit edit/action policy enums |
| Update preference | `autoCheckUpdates` persisted without consumer | unwired/dead | Startup update-check service with throttling and actionable result |
| Row density preference | `listRowDensity` persisted without consumer | unwired/dead | Root data attribute/CSS variable or remove setting |
| Integration status | Static “Active” server label | outdated | Query actual bound port/readiness and render truthful state |
| Error handling | Many clipboard, pause, folder creation, export, metadata, and deletion errors go only to console or are swallowed | fragile | Standard async action result and root toast reporting |
| Custom menus | Hand-built floating div/button menus and hover submenus | outdated/fragile | Accessible menu/split-button primitive with keyboard and collision support |
| Scheduler lifecycle | Frontend boolean plus global download scan | fragile | Backend-owned run state machine with run membership |
---
## 14. Recommended modernization sequence
### Priority 1 — behavior correctness
1. Define total queue ownership and make true global Pause/Resume backend
commands.
2. Centralize action eligibility and execution for row/menu/toolbar/queue/tray.
3. Make duplicate replacement transactional and surface batch failures.
4. Fix tray reconstruction so the same menu is retained after hide/show.
5. Wire or remove Row Density and Automatic Update Check.
### Priority 2 — contract and lifecycle reliability
1. Generate IPC command/event bindings from Rust and prohibit raw command-name
strings outside the generated client.
2. Reconcile `enqueue_many` results into frontend registration/status state.
3. Replace scheduler completion inference with an explicit backend run model.
4. Consolidate settings migration and location normalization around one
authoritative schema.
### Priority 3 — modern desktop interaction
1. Use an accessible Add split button and accessible context/overflow menus.
2. Add keyboard commands for Add, Start/Resume, Pause, Remove, Properties, and
search/filter.
3. Persist column widths and support reset-to-default.
4. Replace hard-coded extension status with live connection state.
5. Add actionable update notifications with a safe external release link.
---
## 15. Static verification matrix
Recommended non-UI checks for this inventory:
- `npm run build` — TypeScript and production frontend build.
- `npm run test -- --run` — store and location unit tests.
- `cargo check --manifest-path src-tauri/Cargo.toml` — Rust command/type check.
- `cargo test --manifest-path src-tauri/Cargo.toml --all-targets` — queue,
ownership, settings, extension, and engine tests.
- `npm run bindings` followed by a clean diff check — generated Rust data
binding drift.
- Add a command-registration/TypeScript-command-map contract test until the IPC
client is generated.
Manual QA remains needed for native dialogs, Finder/open behavior, tray
visibility and focus, notification permission/sound, macOS Automation prompts,
external links, browser extension fallback behavior, menu keyboard navigation,
and pointer resizing.