rsynko-manager 0.1.2

Renderer-neutral transfer-manager specification for Rsynko
Documentation

rsynko-manager

rsynko-manager defines a renderer-neutral transfer manager. It owns the source collection, stable queue identities, list selection, details navigation, breadcrumbs, add-source editing, scheduling intentions, keyed transfer state, the record of what was observed about each request, diagnostics, and safe-exit meaning. It also owns the pre-start format choice, source-format discovery, first-start immutability, and fresh-request duplication.

How that state is presented — the key map, the menu, the composition of every page, and the vocabulary a reader reads — is a separate specification in rsynko-ui, and Ratatui is the first interpreter of both. Future web, desktop, mobile, and accessibility interfaces can interpret the same manager state without redefining application behavior.

Specification surface

The specification begins with associated meanings rather than a storage layout:

  • [ManagerSorts::Downloads] represents a downloads collection chosen by an interpreter;
  • [ManagerSorts::Source] represents a source request chosen by an interpreter;
  • [ManagerSorts::Options] and [ManagerSorts::Output] represent choices without fixing their storage;
  • [DownloadsAlg::empty_downloads] defines the empty collection;
  • [SourceRequestAlg::source] defines one request;
  • [DownloadCollectionAlg::add_sources] denotes ordered collection composition.

[DownloadsExt::downloads] is the derived construction. Its bounds are the specification:

This: DownloadsAlg + SourceRequestAlg
This::Downloads: DownloadCollectionAlg<Source = This::Source>

The associated-type equality states that the requests constructed by the ambient vocabulary are exactly the requests accepted by its collection carrier. A specification is therefore an ordinary generic function composed through the extension; it does not allocate or mutate a concrete manager record:

use alux_ext::ext;
use rsynko_manager::{
    DownloadCollectionAlg, DownloadsAlg, MediaOptionsAlg, OutputChoiceAlg,
    ProgressiveDownloadsExt, SourceRequestAlg,
};

#[ext(name = ExampleDownloadsExt)]
impl<This, Options, Output> This
where
    This: DownloadsAlg
        + SourceRequestAlg<Options = Options, Output = Output>
        + MediaOptionsAlg<Options = Options>
        + OutputChoiceAlg<Output = Output>,
    This::Downloads: DownloadCollectionAlg<Source = This::Source>,
{
    fn example_downloads(&self) -> This::Downloads {
        self.progressive_downloads([
            "fixture://single-video",
            "https://www.youtube.com/watch?v=VIDEO_ID",
        ])
    }
}

Different interpreters may represent that result as an in-memory state machine, a persistent command value, a remote application request, or a test trace. ManagerIntent is separately reified first-order syntax because interactive interpreters must inspect and dispatch intentions received over time. [ManagerIntentExt] is only the event fold. Collection navigation, expanded-row navigation, choice editing, output editing, draft submission, and queue lifecycle are separate extensions with independent primitive bounds; renderers and tests can reuse any one meaning without acquiring the entire manager vocabulary.

Application map

Transfers                         collection page
├── Add sources                   editor page
└── <selected source>             expanded collection row
    ├── Transfer | Formats        what the request chooses between
    ├── Input                     input editor, until the transfer is performed
    ├── Output | File name        where what is retrieved comes to rest
    ├── Report                    what a transfer would do
    ├── Command                   the command a transfer states
    └── Log                       the record of what was observed

Every page states what is running it and which version of it, then the pages it rests under. The collection is left out of that: every page rests under it and it names itself, so stating it in the header would say the same thing twice on every screen. Which application it is belongs to whoever runs the manager, so it is stated to the composition rather than known by it.

Breadcrumbs derive from the current page, stable identity, and entry label observations. An entry states itself by its title where it has one, and by its source where it has not:

Transfers
Transfers  ›  Add sources
Transfers  ›  backup@nas.local:/volume1/photos/2026/
Transfers  ›  backup@nas.local:/volume1/photos/2026/  ›  Transfer
Transfers  ›  backup@nas.local:/volume1/photos/2026/  ›  Input
Transfers  ›  backup@nas.local:/volume1/photos/2026/  ›  Output
Transfers  ›  backup@nas.local:/volume1/photos/2026/  ›  Report
Transfers  ›  backup@nas.local:/volume1/photos/2026/  ›  Command
Transfers  ›  backup@nas.local:/volume1/photos/2026/  ›  Log
Transfers  ›  First fetched title  ›  Formats
Transfers  ›  First fetched title  ›  File name

Two of those segments state what the entry is rather than which page it is. What a request chooses between is Transfer where it moves paths and Formats where it retrieves media; where it comes to rest is Output where somebody stated a path and File name where this application derives one from the title.

Collection and list selector

The main page is a classic collection selector. Selection is a stable [ManagerSorts::Id], never a vector index, so removing or updating other entries does not change what the details page denotes.

┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
│ rsynko  0.1.0                                                                                    │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘
┌ Transfers (3) ───────────────────────────────────────────────────────────────────────────────────┐
│▸ ● ██▏       28% First fetched title                Downloading  18.2 MiB / 63.0 MiB  2.0 MiB/s  │
│  ● ████████ 100% Second fetched title               Complete     —                               │
│  ○            0% A portable video title             Ready        —                               │
│                                                                                                  │
│                                                                                                  │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘
┌ Actions ─────────────────────────────────────────────────────────────────────────────────────────┐
│[↑↓] Select  [Enter] Details  [a] Add  [Space] Pause  [Del] Remove  [Ctrl+Q] Quit                 │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘

Selection wraps at both ends. Adding a non-empty source batch focuses its first new entry. An empty addition preserves focus. Removing the selected entry selects the nearest remaining neighbor. Opening details preserves the selected identity.

Before source inspection succeeds, each row and breadcrumb displays exactly the source text entered or pasted by the user. After inspection, the extracted media title replaces that provisional label. Every compact row and expanded-row heading begins with the same short progress bar and percentage; percentage is therefore visible before the title rather than repeated after byte counts.

○ denotes a source that has not started or an active transfer that is paused. ● denotes a source that has entered the execution queue, including waiting, running, and terminal entries. On a ready source, Space fixes its options and appends it to that queue. On an active transfer whose interpreter advertises cooperative pause, Space switches between Pause and Resume. Waiting, publishing, and terminal entries have no Space action.

Starting multiple downloads requires no batch mode: select each ready source and press Space. Each becomes Waiting immediately, and the interpreter consumes waiting entries in collection order.

Menu availability

The manager derives every reusable menu action as [ActionAvailability::Enabled] or [ActionAvailability::Disabled]. Availability is application meaning, not a color chosen by the terminal renderer. An interpreter uses the same observation twice: enabled actions accept input; disabled actions remain visible for orientation but cannot be selected or dispatched.

An empty collection has no selection to move, inspect, start, or remove, so every action naming one is disabled. Once an inspected ready entry is selected, Details, Space Start, and Remove become enabled. Cursor movement remains disabled until the page has more than one position. During an active transfer, Space pause is enabled only after the runtime advertises cooperative pause support.

┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
│ rsynko  0.1.0                                                                                    │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘
┌ Transfers (0) ───────────────────────────────────────────────────────────────────────────────────┐
│  No sources. Press [a] to add or paste sources.                                                  │
│                                                                                                  │
│                                                                                                  │
│                                                                                                  │
│                                                                                                  │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘
┌ Actions ─────────────────────────────────────────────────────────────────────────────────────────┐
│[↑↓] Select  [Enter] Details  [a] Add  [Space] —  [Del] Remove  [Ctrl+Q] Quit                     │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘

Ratatui renders those disabled items in dim gray. [a] Add and [Ctrl+Q] Quit remain normally styled and usable.

A renderer composes availability without knowing the manager representation, and without materializing a menu: the actions it offers are a sequence in and a sequence out.

use alux_ext::ext;
use rsynko_manager::{
    DetailSelectionAlg, DraftStateAlg, InputDraftAlg, ManagerAction, ManagerMenuExt,
    NavigationStateAlg, OutputDraftAlg, QueueCatalogAlg, QueueEntryAlg, RequestOptionsAlg,
    TextEditorStateAlg,
};

#[ext(name = MenuViewExt)]
impl<This, Id> This
where
    This: NavigationStateAlg<Id = Id>
        + QueueCatalogAlg<Id = Id>
        + DraftStateAlg
        + OutputDraftAlg
        + InputDraftAlg
        + TextEditorStateAlg
        + DetailSelectionAlg,
    This::Entry: QueueEntryAlg + RequestOptionsAlg,
    Id: Copy + Eq,
{
    /// Observes which of the stated actions may be dispatched now, in the order stated.
    fn dispatchable<'a>(
        &'a self,
        actions: impl IntoIterator<Item = ManagerAction> + 'a,
    ) -> impl Iterator<Item = ManagerAction> + 'a {
        actions
            .into_iter()
            .filter(move |action| self.action_availability(*action).is_enabled())
    }
}

Add-sources page

The editor accepts one source per line. Submitting an empty draft preserves the page and reports a message. Successful submission appends every non-empty trimmed line in order and returns to the collection.

┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
│ rsynko  0.1.0  Add sources                                                                       │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘
┌ Sources — editing ───────────────────────────────────────────────────────────────────────────────┐
│/home/dev/photos/2026                                                                             │
│backup@nas.local:/volume1/photos  /home/dev/photos                                                │
│https://www.youtube.com/watch?v=VIDEO_ID                                                          │
│                                                                                                  │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘
┌ Examples ────────────────────────────────────────────────────────────────────────────────────────┐
│ /home/dev/photos/2026                               a folder here, into a folder of the same name│
│ backup@nas.local:/volume1/photos  /home/dev/photos  a folder on another machine, into a folder he│
│ /home/dev/photos  backup@nas.local:/volume1/photos  the same transfer, the other way around      │
│ rsync -a nas.local:/srv/data /mnt/data              a whole command, read as the two ends it name│
│ https://www.youtube.com/watch?v=VIDEO_ID            a web address a source recognizes, fetched in│
│ https://x.com/user/status/1234567890                a tweet, taking the media it carries         │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘
┌ Actions ─────────────────────────────────────────────────────────────────────────────────────────┐
│[←] Move  [→] Move  [Enter] Add sources  [Esc] Transfers  [Ctrl+Q] Quit                           │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘

Expanded details

Details expand the selected row in place without moving focus away from that collection row. Enter therefore expands and then collapses the same row, so two consecutive Enter presses preserve the original collection state. Up and Down move from the row into visible fields and actions; Enter then opens or executes the selected control. The collection remains visible, so progress on neighboring downloads is never hidden. Escape also collapses the row. Breadcrumbs continue to identify the expanded stable [ManagerSorts::Id].

The expanded content is derived from one [ManagerSorts::Entry] and its transfer observations. It shows every known transfer observation when space permits. Concise failure summaries and detailed diagnostics are distinct values, so collection rows never leak enormous signed URLs.

┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
│ rsynko  0.1.0  A portable video title                                                            │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘
┌ Transfers (3) ───────────────────────────────────────────────────────────────────────────────────┐
│  ● ██▏       28% First fetched title                Downloading  18.2 MiB / 63.0 MiB  2.0 MiB/s  │
│  ● ████████ 100% Second fetched title               Complete     —                               │
│▾ ○            0% A portable video title  Ready                                                   │
│    Source     https://www.youtube.com/watch?v=third                                              │
│    File name  A portable video title.mp4                                                         │
│    Format     Best matching audio + video                                                        │
│    State      Ready                                                                              │
│    Downloaded —                                                                                  │
│    Speed      —                                                                                  │
│    Elapsed    00:00                                                                              │
│    Estimated  —                                                                                  │
│    Log        137  mp4   video only    1080p                                                     │
│    [Duplicate]                                                                                   │
│    [Delete]                                                                                      │
│                                                                                                  │
│                                                                                                  │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘
┌ Actions ─────────────────────────────────────────────────────────────────────────────────────────┐
│[↑↓] Select field or action  [Enter] Close details  [Space] Start  [Esc] Transfers  [Ctrl+Q] Quit │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘

Speed is the average byte rate downloaded / elapsed. Estimated is the remaining duration derived from that same observed rate and appears only when downloaded bytes, elapsed time, and total size make an estimate meaningful. These are specification-level derived values, not Ratatui calculations. Details contain no empty error placeholders. A failure shows one red Error row. Note appears only for non-error information, never as a duplicate error diagnostic.

Output file name

Successful source inspection prefills the editable File name field from the media title and the selected format extension. When no title exists, the extractor media identity is used. Select the field in expanded details and press Enter to edit that same field inline.

┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
│ rsynko  0.1.0  A portable video title  ›  File name                                              │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘
┌ Transfers (3) ───────────────────────────────────────────────────────────────────────────────────┐
│  ● ██▏       28% First fetched title                Downloading  18.2 MiB / 63.0 MiB  2.0 MiB/s  │
│  ● ████████ 100% Second fetched title               Complete     —                               │
│▾ ○            0% A portable video title  Ready                                                   │
│    Source     https://www.youtube.com/watch?v=third                                              │
│  ▸ File name  A portable video title.mp4                                                         │
│    Format     Best matching audio + video                                                        │
│    State      Ready                                                                              │
│    Downloaded —                                                                                  │
│    Speed      —                                                                                  │
│    Elapsed    00:00                                                                              │
│    Estimated  —                                                                                  │
│    Log        137  mp4   video only    1080p                                                     │
│    [Duplicate]                                                                                   │
│    [Delete]                                                                                      │
│                                                                                                  │
│                                                                                                  │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘
┌ Actions ─────────────────────────────────────────────────────────────────────────────────────────┐
│[←] Move  [→] Move  [Enter] Save  [Esc] Details  [Ctrl+Q] Quit                                    │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘

The editor displays a terminal cursor at the exact insertion position. Left and Right move by one Unicode scalar, Home and End move to logical line boundaries, Backspace removes the preceding scalar, Delete removes the following scalar, and paste inserts at the cursor. Cursor position and UTF-8-safe editing are manager-spec meaning shared with the add-sources editor; terminal column width, horizontal clipping, and caret placement are renderer interpretations. Enter commits the normalized name. Escape cancels the draft and returns to the same selected File name details row; the collection and neighboring download activity remain visible throughout editing.

The portable-name specification replaces /, \\, Windows-forbidden punctuation and control characters, removes trailing spaces and dots, avoids Windows device names such as CON and LPT1, and bounds the UTF-8 component length. It therefore denotes one ordinary file component on both Linux and Windows. A valid extension typed by the user is preserved. Until first Start, changing the format choice also refreshes the suggested extension unless the user has explicitly saved a file name.

Format options

One selector states the whole choice. It begins with the three preferred stream roles — Best audio + video, Best video only, Best audio only — and continues with every format extraction discovered, in extractor preference order. Preferring a role means "the most preferred format carrying those streams, whatever they turn out to be"; choosing an identity fixes exactly that format, and the roles then add nothing to it.

On expanded details, select the visible Format field with Up and Down and press Enter. The page behaves like a collection list: the current value is highlighted, Up and Down select an available value, and Enter or Escape returns to details. Format discovery stores identifiers and stream descriptions, never transient signed media URLs.

┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
│ rsynko  0.1.0  A portable video title  ›  Formats                                                │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘
┌ Format (6) ──────────────────────────────────────────────────────────────────────────────────────┐
│▸ Best audio + video                                                                              │
│  Best video only                                                                                 │
│  Best audio only                                                                                 │
│  18   mp4   audio + video 360p                                                                   │
│  140  m4a   audio only    tiny                                                                   │
│  137  mp4   video only    1080p                                                                  │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘
┌ Actions ─────────────────────────────────────────────────────────────────────────────────────────┐
│[↑↓] Choose format  [Enter] Accept  [Esc] Details  [Ctrl+Q] Quit                                  │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘

Adding a source and opening Format therefore shows exactly what was extracted, with the three preferences in front of it.

First-start immutability and duplication

The transition from Ready to Waiting is the first start and permanently fixes the request's media role, format, and output policy. Failure does not make it editable again, but a failed request exposes Restart, which schedules the same fixed request again. This keeps a queue item an honest record of what was requested and executed.

A value that can no longer be changed is marked by nothing at all. The cursor does not stop on it, because [QueueEntryAlg::detail_controls] no longer offers it, and a field the cursor walks past has already said so; a mark beside it would be a second way of saying the same thing, and one a reader would have to learn. After first start, details expose state-appropriate actions as selectable rows. Duplicate creates a new stable identity for the same source, preserves its media and format choices, clears explicit output and transfer state, and makes the new entry editable. A successfully discovered format catalog is reusable by the duplicate; incomplete or failed discovery is restarted when needed.

┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
│ rsynko  0.1.0  First fetched title                                                               │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘
┌ Transfers (3) ───────────────────────────────────────────────────────────────────────────────────┐
│▾ ● ██▏       28% First fetched title  Downloading                                                │
│    Source     https://www.youtube.com/watch?v=first                                              │
│    File name  first.mp4                                                                          │
│    Format     Best matching audio + video                                                        │
│    State      Downloading                                                                        │
│    Downloaded 18.2 MiB / 63.0 MiB                                                                │
│    Speed      2.0 MiB/s                                                                          │
│    Elapsed    00:09                                                                              │
│    Estimated  00:22                                                                              │
│    Log        retrieval started                                                                  │
│    [Duplicate]                                                                                   │
│    [Delete]                                                                                      │
│  ● ████████ 100% Second fetched title               Complete     —                               │
│  ○            0% A portable video title             Ready        —                               │
│                                                                                                  │
│                                                                                                  │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘
┌ Actions ─────────────────────────────────────────────────────────────────────────────────────────┐
│[↑↓] Select field or action  [Enter] Close details  [Space] Pause  [Esc] Transfers  [Ctrl+Q] Quit │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘

Transfers

This application transfers paths, so a submitted line names one unless a source recognizes it as its own: /home/dev/music, nas.local:/srv/data, and rsync://nas.local/data are all transfers, and a web address is not because retrieval claims it. Which lines a source claims is [SourceRecognitionAlg], and it is the whole of what a source adds to submission — nothing here knows what any of them look like. What is transferred may be one file or a whole folder — rsync makes no distinction and neither does this. A folder is one queue entry and is never expanded into an entry per file: what is transferred, what is reported, and what is recorded is the path itself.

Both ends are stated by whoever asked, so they are named Input and Output rather than a source and a file name, and both may be edited until the transfer is first performed — a rehearsal is not a performance, so rehearsing, correcting an end, and rehearsing again is an ordinary thing to do. A media item offers no such thing: its source is what extraction already read, and changing it would make the title, the formats, and the name derived from them a lie. An edited Output is taken exactly as written — [OutputNaming] is the distinction, and it is the reason a path is not run through the portable-file-name rules that would replace its separators. Each end is also passed on exactly as written, because a trailing separator is what says the contents of rather than this, into, and both are wanted.

A transfer chooses no format, because a media role says nothing about a path, so expanded details state no Format row for it and the format selector is not offered.

One line names one request, and a line naming two ends names a transfer between them — which is how a whole transfer command reads as well, so one can be pasted. The add-sources page states the shapes it accepts beside the draft.

A transfer naming only where it comes from comes to rest under the name that end already has, beside wherever the application was started. Naming a folder is safer than naming the one somebody happens to be standing in, which is what a transfer that removes would empty.

A transfer performed by another program is held still by signalling it, so Space pauses and resumes one exactly as it does a download, and time spent held is not counted as time spent transferring. A transfer whose request is removed, or which is still running at exit, is ended rather than left behind: a program that has not started yet cannot be told to stop, so it is told again until it is over.

A transfer is rehearsed before it is performed. A fresh transfer request states a rehearsal mode, which is on, so Space means Dry run rather than Start: it asks what the transfer would do and changes nothing.

Expanded details are therefore a builder for one command, and state the command they build. [QueueEntryAlg::stated_command] observes exactly what an interpreter would run to perform the request, so the fields above it read as the parts that make it and Space reads as running it. A request retrieved by fetching states nothing there — there is no command to read.

┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
│ rsynko  0.1.0  backup@nas.local:/volume1/photos/2026/                                            │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘
┌ Transfers (1) ───────────────────────────────────────────────────────────────────────────────────┐
│▾ ○            0% backup@nas.local:/volume1/photos/2026/  Ready                                   │
│    Input      backup@nas.local:/volume1/photos/2026/                                             │
│    Output     /home/dev/photos/2026                                                              │
│    Transfer   copy adds and replaces, removes nothing, good for topping a folder up              │
│    Command    rsync --archive --partial --itemize-changes --itemize-changes --out-format=%i|%l|%…│
│    State      Ready                                                                              │
│    Downloaded —                                                                                  │
│    Speed      —                                                                                  │
│    Elapsed    00:00                                                                              │
│    Estimated  —                                                                                  │
│    Log        added backup@nas.local:/volume1/photos/2026/                                       │
│    [Dry run — changes nothing]                                                                   │
│    [Duplicate]                                                                                   │
│    [Delete]                                                                                      │
│                                                                                                  │
│                                                                                                  │
│                                                                                                  │
│                                                                                                  │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘
┌ Actions ─────────────────────────────────────────────────────────────────────────────────────────┐
│[↑↓] Select field or action  [Enter] Close details  [Space] Dry run  [Esc] Transfers  [Ctrl+Q] Qu…│
└──────────────────────────────────────────────────────────────────────────────────────────────────┘

A rehearsal is not a start. It runs in [TransferPhase::Rehearsing] and returns the request to Ready exactly as editable as it found it, so the first-start locking that fixes a media request's options never triggers. Rehearsing, adjusting, and rehearsing again is therefore an ordinary thing to do. A rehearsal that fails is not a failed request either: it returns to Ready with its reason recorded, and Space still means something afterward.

What the rehearsal said appears as a selectable Report field naming what would happen, counted by kind.

┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
│ rsynko  0.1.0  backup@nas.local:/volume1/photos/2026/                                            │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘
┌ Transfers (1) ───────────────────────────────────────────────────────────────────────────────────┐
│▾ ○            0% backup@nas.local:/volume1/photos/2026/  Ready                                   │
│    Input      backup@nas.local:/volume1/photos/2026/                                             │
│    Output     /home/dev/photos/2026                                                              │
│    Transfer   copy adds and replaces, removes nothing, good for topping a folder up              │
│    Command    rsync --archive --partial --itemize-changes --itemize-changes --out-format=%i|%l|%…│
│    State      Ready                                                                              │
│    Downloaded —                                                                                  │
│    Speed      —                                                                                  │
│    Elapsed    00:00                                                                              │
│    Estimated  —                                                                                  │
│    Report     2 new, 1 updated, 1 deleted, 2 unchanged                                           │
│    Log        the rehearsal stated 4 of 6 paths would change                                     │
│    [Dry run — changes nothing]                                                                   │
│    [Duplicate]                                                                                   │
│    [Delete]                                                                                      │
│                                                                                                  │
│                                                                                                  │
│                                                                                                  │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘
┌ Actions ─────────────────────────────────────────────────────────────────────────────────────────┐
│[↑↓] Select field or action  [Enter] Close details  [Space] Dry run  [Esc] Transfers  [Ctrl+Q] Qu…│
└──────────────────────────────────────────────────────────────────────────────────────────────────┘

A transfer chooses instead one of the ways a path may be transferred. Each way is named by what it does rather than by what it is for — mirror, mirror-keeping, skip-newer — and states what it does before what that is usually wanted for, so a reader chooses by the first half and confirms by the second. The Transfer field names the chosen way; activating it opens every way on offer, the same page a media item chooses a representation on, because choosing how a request runs is one meaning and not two.

┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
│ rsynko  0.1.0  backup@nas.local:/volume1/photos/2026/  ›  Transfer                               │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘
┌ Transfer (13) ───────────────────────────────────────────────────────────────────────────────────┐
│▸ copy            adds and replaces, removes nothing, good for topping a folder up                │
│  mirror          makes the destination match exactly, good for keeping a copy current            │
│  mirror-keeping  mirrors, keeps what it replaces, compresses, good for remote backups            │
│  mirror-whole    mirrors by sending whole files, good for local disks and SSDs                   │
│  mirror-readable mirrors and makes everything readable, good for publishing a web root           │
│  skip-newer      replaces nothing newer at the destination, good for merging two copies          │
│  compare-content compares what files hold, not when they changed, good after clock drift         │
│  resume          continues large files where they left off, good for flaky links                 │
│  move            moves, removing what arrived safely, good for clearing a staging area           │
│  limit-rate      leaves room on the line, good for a shared connection                           │
│  keep-marks      keeps links, ownership, and every file mark, good for system backups            │
│  one-disk        stays on the disk it started on, good for roots with mounts under               │
│  skip-junk       leaves behind what no one meant to keep, good for editor droppings              │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘
┌ Actions ─────────────────────────────────────────────────────────────────────────────────────────┐
│[↑↓] Choose transfer  [Enter] Accept  [Esc] Details  [Ctrl+Q] Quit                                │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘

Choosing one rewrites the command, so what a way is can be read rather than remembered. It also forgets the report: a report describes one command, and changing either end or the way of running it leaves the old report describing something nobody is going to do.

┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
│ rsynko  0.1.0  backup@nas.local:/volume1/photos/2026/                                            │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘
┌ Transfers (1) ───────────────────────────────────────────────────────────────────────────────────┐
│▾ ○            0% backup@nas.local:/volume1/photos/2026/  Ready                                   │
│    Input      backup@nas.local:/volume1/photos/2026/                                             │
│    Output     /home/dev/photos/2026                                                              │
│    Transfer   mirror-keeping mirrors, keeps what it replaces, compresses, good for remote backup │
│    Command    rsync --archive --partial --itemize-changes --itemize-changes --out-format=%i|%l|%…│
│    State      Ready                                                                              │
│    Downloaded —                                                                                  │
│    Speed      —                                                                                  │
│    Elapsed    00:00                                                                              │
│    Estimated  —                                                                                  │
│    Log        the report was forgotten: it no longer states what would happen                    │
│  ▸ [⚠️ Real run — writes files]                                                                  │
│    [Duplicate]                                                                                   │
│    [Delete]                                                                                      │
│                                                                                                  │
│                                                                                                  │
│                                                                                                  │
│                                                                                                  │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘
┌ Actions ─────────────────────────────────────────────────────────────────────────────────────────┐
│[↑↓] Select field or action  [Enter] Activate  [Space] Start  [Esc] Transfers  [Ctrl+Q] Quit      │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘

Activating Report states every path the rehearsal named, and not only the ones that would change: what the transfer would leave exactly as it is stands under the changes, quietly, so the report is the whole picture rather than a list of alarms. [ChangeKind] distinguishes the four, and an interpreter states each with its own weight.

Every field above builds one command, and Command states as much of it as the column holds. Activating it states the whole command on its own, with nothing drawn around it: no border, no label, and no break the command did not already have. A reader selects it the way they select anything else in a terminal, and what they take away is exactly the command — which is the point, since a command wrapped inside a bordered pane cannot be copied out of one.

┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
│ rsynko  0.1.0  backup@nas.local:/volume1/photos/2026/  ›  Command                                │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘
rsync --archive --partial --itemize-changes --itemize-changes --out-format=%i|%l|%n --info=progress2
--dry-run backup@nas.local:/volume1/photos/2026/ /home/dev/photos/2026



┌ Actions ─────────────────────────────────────────────────────────────────────────────────────────┐
│[Esc] Details  [Ctrl+Q] Quit                                                                      │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
│ rsynko  0.1.0  backup@nas.local:/volume1/photos/2026/  ›  Report                                 │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘
┌ Report (4 of 6 would change) ────────────────────────────────────────────────────────────────────┐
│  + new          3.1 MiB  IMG_0431.jpg                                                            │
│  + new          2.7 MiB  IMG_0432.jpg                                                            │
│  ~ updated      1.1 KiB  album.json                                                              │
│  - deleted            —  old/IMG_0090.jpg                                                        │
│  = unchanged    2.2 MiB  IMG_0001.jpg                                                            │
│  = unchanged    2.0 MiB  IMG_0002.jpg                                                            │
│                                                                                                  │
│                                                                                                  │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘
┌ Actions ─────────────────────────────────────────────────────────────────────────────────────────┐
│[Esc] Details  [Ctrl+Q] Quit                                                                      │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘

The rehearsal mode is turned by one row in expanded details, and that row is the one exception to actions naming what they do: it states which mode the request is in, and carries the weight of being in it. [Dry run — changes nothing] is safe; [⚠️ Real run — writes files] is a caution, and says why. The warning asks for its emoji presentation, which is what makes it two columns wide to everyone measuring it rather than one width to a renderer and another to a font. Activating the row moves between them. Once the mode is off, Space means Start — and the stated command loses the argument that was keeping it harmless, which is the clearest statement of what changed.

┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
│ rsynko  0.1.0  backup@nas.local:/volume1/photos/2026/                                            │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘
┌ Transfers (1) ───────────────────────────────────────────────────────────────────────────────────┐
│▾ ○            0% backup@nas.local:/volume1/photos/2026/  Ready                                   │
│    Input      backup@nas.local:/volume1/photos/2026/                                             │
│    Output     /home/dev/photos/2026                                                              │
│    Transfer   copy adds and replaces, removes nothing, good for topping a folder up              │
│    Command    rsync --archive --partial --itemize-changes --itemize-changes --out-format=%i|%l|%…│
│    State      Ready                                                                              │
│    Downloaded —                                                                                  │
│    Speed      —                                                                                  │
│    Elapsed    00:00                                                                              │
│    Estimated  —                                                                                  │
│    Report     2 new, 1 updated, 1 deleted, 2 unchanged                                           │
│    Log        dry run disabled: the next run will perform the transfer                           │
│  ▸ [⚠️ Real run — writes files]                                                                  │
│    [Duplicate]                                                                                   │
│    [Delete]                                                                                      │
│                                                                                                  │
│                                                                                                  │
│                                                                                                  │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘
┌ Actions ─────────────────────────────────────────────────────────────────────────────────────────┐
│[↑↓] Select field or action  [Enter] Activate  [Space] Start  [Esc] Transfers  [Ctrl+Q] Quit      │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘

Everything above is manager meaning. What a folder transfer is — the two ends it joins, exactly the command that joins them, and what each line the running transfer writes means — is stated by rsynko-rsync, and crates/rsynko-process runs that command as an operating-system process. The manager never learns what rsync is; it learns that some requests can state what they would do instead of doing it.

Download record

Every request keeps the record of what was observed about it: the source it was added from, the identity and title extraction recovered, every format the source described, retrieval start, publication, and failure. Notes are keyed by stable identity and observed in the order they were stated, so the record of one download never mixes with another's.

Expanded details state the record as a selectable Log field naming its most recent note, and activating that field opens the whole record. The record is reference material rather than the thing being watched, which is why an interpreter states it without competing for attention — Ratatui renders it in dim gray.

┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
│ rsynko  0.1.0  First fetched title  ›  Log                                                       │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘
┌ Log (7) ─────────────────────────────────────────────────────────────────────────────────────────┐
│added https://www.youtube.com/watch?v=first                                                       │
│extracted Yd9s7MN8lPk: First fetched title                                                        │
│extraction described 3 formats                                                                    │
│  18   mp4   audio + video 360p                                                                   │
│  140  m4a   audio only    tiny                                                                   │
│  137  mp4   video only    1080p                                                                  │
│retrieval started                                                                                 │
│                                                                                                  │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘
┌ Actions ─────────────────────────────────────────────────────────────────────────────────────────┐
│[Esc] Details  [Ctrl+Q] Quit                                                                      │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘

State and interpreter boundary

The manager specification decides:

  • collection order and stable identity;
  • which entry is selected;
  • collection, editor, and expanded-details navigation;
  • breadcrumb meaning;
  • ready, waiting, active, paused, complete, and failed states;
  • state-dependent Start, Pause, Resume, or absent Space meaning;
  • page- and state-dependent menu-action availability;
  • editable output file name, the preferred stream roles, the concrete format choice, and discovered format catalogs;
  • the record of what was observed about one request, and the order its notes were stated in;
  • the rehearsal mode of a request that has one, and that rehearsing is not starting;
  • what a rehearsal stated would happen, and which of it would change anything;
  • irreversible first-start locking and fresh-request duplication;
  • how keyed progress and terminal observations update entries;
  • removal, duplication, and safe-exit intentions;
  • concise summaries versus diagnostic details;
  • average byte rate, estimated remaining time, and completed share derived from observed progress.

An interpreter decides:

  • keyboard, pointer, touch, or HTTP input mapping;
  • list widgets, colors, borders, responsive layout, and how finely a bar draws a share;
  • clipboard and terminal mechanisms;
  • background source-inspection mechanisms;
  • how a rehearsal and a transfer are actually performed, and what a folder is transferred by;
  • sequential or parallel execution of entries denoted Waiting;
  • persistence and notification mechanisms.

Exit is bound to Ctrl+Q on every page, and safe exit is what it states: every run is told to stop, and the reader is out once they have. Ctrl+C is bound to nothing, because it is the key people press out of habit to stop a terminal program — the Ratatui interpreter states what it is for and ends nothing, and reads two of them in quick succession as the exit it is; that reading is the interpreter's own and the manager hears nothing of a single one. Escape closes selectors or returns to the parent page and has no exit meaning on the root collection. The interpreter cancels its active stream immediately when that entry is removed or safe exit is requested.

[ManagerIntent::apply_selected_space] moves one selected ready entry to Waiting. Repeating that operation for other selected sources builds the execution queue, while the interpreter chooses how much concurrency to provide. The state model permits any number of waiting and active transfers.

Laws

Every law below is checked against any interpreter by the extension it names: