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
@@ -24,6 +24,7 @@ import DownloadIntake from "./DownloadIntake";
import { LinkBox } from "./LinkBox";
import { clearPendingDownload, requestLinkBox } from "../lib/downloadIntake";
import { ensureNavId } from "../lib/history";
import { pushLayer, removeLayer } from "../lib/layerStack";
afterEach(() => {
cleanup();
@@ -79,6 +80,18 @@ describe("DownloadIntake · a pasted link", () => {
expect(dialog()).toBeNull();
});
it("with a player or a menu on top (a layer, no aria-modal), a paste is not a download", async () => {
render(<DownloadIntake />);
const layer = pushLayer();
try {
paste(document.body, "https://a.example/1");
await act(async () => {});
expect(dialog()).toBeNull();
} finally {
removeLayer(layer);
}
});
it("closing the dialog closes it", async () => {
render(<DownloadIntake />);
paste(document.body, "https://a.example/1");
@@ -102,6 +115,20 @@ describe("LinkBox", () => {
expect((field as HTMLInputElement).value).toBe("");
});
it("a clipboard link read on focus never replaces what was typed while it was read", async () => {
let answer: (text: string) => void = () => {};
Object.defineProperty(navigator, "clipboard", {
configurable: true,
value: { readText: () => new Promise<string>((resolve) => (answer = resolve)) },
});
render(<LinkBox />);
const box = screen.getByRole("textbox");
fireEvent.focus(box);
fireEvent.change(box, { target: { value: "https://typed.example/1" } });
await act(async () => answer("https://clipboard.example/2"));
expect((box as HTMLInputElement).value).toBe("https://typed.example/1");
});
it("an empty box downloads nothing", async () => {
render(
<>