Files
archive-clients/docs/telegram-ux.md
T

11 KiB
Raw Blame History

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:

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:

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.

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:

🔵 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 pages 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 <job-id> <state> Move one synthetic job directly to a display state and persist the transition.
/actest fail-next <method> [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 1500.
/actest export Send the complete append-only trace as archive-control-telegram-trace.jsonl.

Valid advance states are:

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:

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:

/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:

/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.