fix(files): review round 1, the page — honest states, the keyboard, the tray's words

- A library past the list's cap is searched and sorted on the server; the count says "the newest".
- Each folder and each search opens on its first page (DataTable pageScope, useCardPager scope).
- A file dropped where the page takes none never reaches the browser (which would unload the app).
- The viewer's arrows wait while a layer opened from it is on top, and scroll a wide text line;
  a text shows its first 5000 lines and says where the rest is.
- Confirms name the file (no more "your downloads"); the asking menu rows end in "…".
- The tray names a running step beside its bar, shows a pause mark for paused work, polls slowly
  while only paused work waits, and forgets a finished file that was deleted.
- An unchanged rename sends nothing; a paste opens the download dialog only with nothing on top;
  the link box never replaces what was typed.
- Honest states: folders that failed to load, a file no longer there, the storage figure; the rail
  says "nothing downloaded" only when there is nothing.
- Focus after deleting the open folder and when the format form opens; the format name's label.
- ui/Meter (one danger line for both quota bars) and ui/RenameField in the platform layer.
- Copy fixes, stale comments and the speculative hub key; a contract test for the list's
  parameters; e2e: the Files page and the paste intake.
This commit is contained in:
2026-10-13 00:14:09 +02:00
parent 815083bcc4
commit 8b2260c9a8
41 changed files with 920 additions and 218 deletions
+25 -3
View File
@@ -1,4 +1,4 @@
import { api, type QueueJob } from "./api";
import { api, type DownloadStatus, type QueueJob } from "./api";
import { createStore } from "./store";
// The download queue as this tab has watched it: which jobs it has seen finish. The tray shows them
@@ -8,8 +8,26 @@ import { createStore } from "./store";
// through `fetchQueue`, so a finish is seen once whichever of them polled, and nothing has to diff the
// previous render's data.
/** The statuses a job leaves on its own: it is waiting, or working. */
export const IN_FLIGHT: ReadonlySet<string> = new Set(["queued", "running", "paused"]);
/** The statuses the tray lists as under way: waiting, working, or paused by its user. */
export const IN_FLIGHT: ReadonlySet<DownloadStatus> = new Set(["queued", "running", "paused"]);
/** The statuses a job leaves on its own, so the queue is worth asking about often. A paused job is
* not one: it waits for its user, however long that takes. */
export const MOVING: ReadonlySet<DownloadStatus> = new Set(["queued", "running"]);
/** The steps a running job reports that have a name of their own (`downloads.phase.*`). */
export const DOWNLOAD_PHASES: readonly string[] = [
"video",
"audio",
"editing",
"converting",
"merging",
"audio_extract",
"thumbnail",
"sponsorblock",
"metadata",
"processing",
];
let lastSeen: Map<number, string> | null = null;
const finished = createStore<QueueJob[]>([]);
@@ -48,6 +66,10 @@ export const useFinished = (): QueueJob[] => finished.useValue();
/** The tray was closed: what finished is no longer shown. */
export const clearFinished = (): void => finished.set([]);
/** A file was deleted: a "Done · Open" row for it would open and link nothing. */
export const forgetFinished = (id: number): void =>
finished.set((list) => (list.some((j) => j.id === id) ? list.filter((j) => j.id !== id) : list));
/** The finishes not yet announced — the render subscription that wakes the announcer. */
export const useUnannounced = (): QueueJob[] => unannounced.useValue();