# Telegram UX Archive Control adds a separate handler/module to the existing bot. Existing commands and conversations retain their current behavior. ## Authorization and shared state The handler accepts private-chat users through the existing `@username` allowlist for compatibility. A configured numeric Telegram user ID may pin an entry; when pinned, both the ID and current allowlist rule must match. This mitigates username reassignment while permitting a staged migration. Jobs, placements, events, queues, and routes are shared administrative state and store no initiating-user identity. UI session state records only what is needed to resume the conversation/view and expires after 24 hours by default. ## Entry point and navigation The existing “Archive/Unarchive Resources” entry opens one message: ```text What do you want to do? [ Archive ] [ Unarchive ] [ Evict Cache ] [ Job Status ] ``` All subsequent pages edit this message. `Cancel` closes the active selection flow. `Back` returns one level while retaining validated filters/selections. Callback payloads contain opaque session/action IDs, not resource names or paths, and are validated against persisted session revision and expiry. ## Archive and unarchive flow ### 1. Select source resource Control queries relevant online nodes on demand. It groups identical resource and selection replicas into one display choice while showing distinct selections separately. The list is stable-sorted and paged: ```text Which cached resource do you want to archive? (page 1/3) Send a message to filter resources by name. Active filters: `documentary`, `2025` Send another message to narrow the results further. 1. `10.0 GB` Resource A — cache-1 2. `840 MB` Resource B — cache-2 [ 1 ] [ 2 ] [ Prev ] [ Next ] [ Remove filters ] [ Cancel ] ``` Size is selected-complete logical content, not total torrent size. Every filterable page explicitly says that a new message applies a filter. New messages add literal, case-insensitive filters and narrow the current results; Remove filters resets the chain. The one-entry tree is filterable by path or display name in the same way. If no item remains, the message explains whether no source is online, no selected-complete data exists, or filters eliminated all results. ### 2. Select source replica and target If multiple identical-selection replicas exist, control may choose a healthy source automatically using deterministic availability/route/free-space rules; otherwise it lists the source replicas. One eligible target is selected automatically; multiple targets are paged. Offline targets are not offered. A resource already on a target is still eligible when the requested selection has an unverified delta. The UI says that it will extend an existing placement. ### 3. Confirm or choose one entry Before showing confirmation, control obtains fresh full trees from source and target and calculates the transfer delta. ```text Archive Resource A from cache-1 to archive-2? Requested: 7 files, 10.0 GB Already verified there: 2 files, 3.1 GB Will transfer: 5 files, 6.9 GB [ Confirm ] [ Choose one entry… ] [ Cancel ] ``` Choose one entry opens a paged/filterable tree. Only source-selected-complete files/directories are enabled. Selecting a directory expands it to descendant file indices, returns to a freshly recomputed confirmation, and clearly states the chosen subtree. The protocol supports multiple entries from v1, but the initial UI exposes one. Confirmation uses a persisted, single-use nonce bound to session revision, operation, resource, source, target, selection, fingerprints, and placement generation. Double taps or stale callbacks cannot create a second job. ## Evict Cache flow The first page lists current cache placements. Selecting one triggers fresh queries to the cache and relevant online archive nodes. Each row/confirmation shows coverage: - enabled when the union of verified archives covers every selected cache file; - disabled with missing file count/size and unavailable archive explanation otherwise. There is no override button. Confirmation creates a durable three-step eviction job; it never clears an archive placement or a transfer history row. ## Job Status Status pages show live and retained jobs with operation, resource, endpoints, state, current-step and overall percentages, bytes/s where meaningful, and failure reason. Routine edits are coalesced to one per five seconds by default. Step changes, stall transitions, failures, commit, cleanup-required, and terminal states edit immediately subject to Telegram rate-limit backoff. State symbols: | Symbol | Meaning | | --- | --- | | 🟡 | queued/not started | | 🔵 | active, waiting, or stalled | | 🟠 | committed but cleanup required | | 🟢 | succeeded | | 🔴 | failed or cancelled | Transfer jobs display five numbered steps: Source Stage/HardLink, Syncthing, Target Merge/Copy, QB Verification, and Staging Cleanup. Route setup and preflight are named, unnumbered pre-steps. Eviction displays Verify Archive Coverage, QB Remove Entry, and Safe File Unlink. A stalled job keeps its last overall percentage: ```text 🔵 Archive: Resource A — cache-1 → archive-2 [2/5 Syncthing: 63.42%, overall 32.68%, stalled, last progress 38m ago] Waiting indefinitely; retry occurs when progress/service connectivity returns. ``` Failure messages include stable phase and clear reason, while hiding secrets and host-only paths. ## Clear and remove behavior `Clear Finished` hides only green successful jobs and leaves a 30-day default tombstone. Orange Cleanup Required is not clearable as finished. `Remove One` pauses status auto-refresh, retains the current page’s numbered job rows, and replaces the buttons with current-page job numbers and Cancel. A successful green row hides immediately. Other confirmation pages identify the resource, operation, source/target or cache node, state, overall progress, and only then the internal job ID. A queued job confirmation deletes only its queue record/reservation. An active, failed-with-residue, or cancelling job requires a second confirmation and begins/continues compensation; its row remains until safe cleanup is known. A committed job can only retry staging cleanup. No history action deletes a cache placement; that is exclusively Evict Cache. Archive Control messages use Telegram HTML formatting with all dynamic resource names, paths, node IDs, filter strings, progress details, and failure reasons escaped before rendering. ## Restart and rate-limit behavior Sessions, pages, filters, chosen entries, confirmation bindings, and live status views are persisted. On bot restart, active status messages resume updates when the stored chat/message remains editable; otherwise users can reopen Job Status without losing job state. “message is not modified” is treated as success. Telegram retry-after values pause/coalesce edits without affecting orchestration. ## Interactive test entry An opt-in `Archive Control (Test)` entry reuses this same handler with an independent callback namespace and SQLite database. Its scripted backend provides comprehensive, empty, and slow-route scenarios without contacting control/client daemons, qBittorrent, or Syncthing. The production entry, callback namespace, database, jobs, placements, and other bot features are unchanged. The test entry exposes administrator-only `/actest` controls to reset a scenario, move a synthetic job through displayed states, fail the next selected backend call, complete a slow route, inspect recent trace events, or export the complete JSONL trace. The trace contains callback and filter inputs, session state transitions, exact message/keyboard renders, backend results/errors, and job transitions, so a UI report can be reproduced without live storage nodes. ### `/actest` operator guide Run `/actest` only in a private chat as an authorized bot manager. The test UI must be enabled, and all commands affect only the isolated dummy database. Sending `/actest` without arguments is equivalent to `/actest status`. | Command | Effect | | --- | --- | | `/actest status` | Show the current scenario, route flags, synthetic job count, trace count, and test database path. | | `/actest reset [scenario]` | Clear dummy sessions, jobs, faults, and traces, then load `comprehensive`, `empty`, or `route_slow`. The configured scenario is used when omitted. | | `/actest scenario [scenario]` | Alias of `reset`; it does not preserve the previous dummy jobs or sessions. | | `/actest advance ` | Move one synthetic job directly to a display state and persist the transition. | | `/actest fail-next [message]` | Make exactly the next matching dummy backend call fail with the supplied message, then disarm the fault. | | `/actest route-ready` | Complete the pending dummy route used by `route_slow`. | | `/actest trace [limit]` | Show the most recent trace events in chronological order; the limit defaults to 10 and is bounded to 1–500. | | `/actest export` | Send the complete append-only trace as `archive-control-telegram-trace.jsonl`. | Valid `advance` states are: ```text queued preparing running waiting stalled cancelling cleanup_required succeeded failed cancelled ``` The internal job ID is shown on the final line of a Remove One confirmation. Press Cancel after copying it if the purpose is only to drive the job with `advance`. It is also present in `trace` backend results and the JSONL export. Valid `fail-next` methods are: ```text list_operation_resources list_clients get_resource_tree preview_transfer preview_eviction create_job list_jobs clear_finished_jobs hide_job cancel_job ``` For example: ```text /actest reset comprehensive /actest fail-next preview_transfer simulated planning failure ``` The next archive or unarchive preview fails visibly with that reason; a later preview proceeds normally. To exercise slow route provisioning: ```text /actest reset route_slow # Start an archive flow and choose a source/target. # The preview reports that route provisioning has started. /actest route-ready # Reopen/retry the archive flow; the preview can now complete. ``` A useful review sequence is: 1. Run `/actest reset comprehensive`, then reopen `Archive Control (Test)`; reset intentionally makes pre-reset inline buttons stale. 2. Walk through Archive, Unarchive, Evict Cache, filtering, selective entry, pagination, Job Status, Remove One, and cancellation without confirming destructive-looking choices unless that behavior is under review. 3. Copy a job ID from Remove One and use `/actest advance` to inspect waiting, stalled, failed, cleanup-required, and terminal rendering. 4. Arm one `fail-next` point and repeat its matching UI action. 5. Run `/actest trace 50` for a quick diagnosis or `/actest export` when reporting a reproducible UI problem. Scenario state, synthetic jobs, and traces survive bot restarts. Only `reset` clears them. None of these commands starts the production control daemon, contacts storage nodes, or mutates production jobs or placements.