The code cited its plan instead of saying why: `E28 review J4`, `(R6 S3b)`, `S1 justified…`,
`deep-review round 5`. That plan lives in the private ops repo, so for every reader of this one the
citation was noise, and a sentence whose subject was the plan step ("round 2 showed…") hid its
lesson behind an id. Each one is gone and the "why" kept: a bare citation is deleted, a sentence
carrying a lesson is reworded around it ("a later attempt showed…").
Measured (`scripts/audit_metrics.py`, product code): 652 lines in 270 files carried an epic code by
the definition the next commit holds (507 / 241 by the old, two-digit-only one) -> 0. Across all
code, tests, scripts and configs: ~1100 epic-code lines plus ~320 sprint/round ids.
Behaviour unchanged, checked mechanically: every changed .py file has the same AST once docstrings
are blanked; css/sh/toml/yml/json files are identical once comments are stripped; the only
non-comment changes in TS/JS are test titles, one description string in a contract table, and five
ESLint messages, whose text is all the gates snapshot diff holds. Not touched: Markdown (docs and
the changelog keep their history), Alembic migrations, TV episode notation (`S2 · E5`), DESIGN.md
ids (`C1`, `B4`, `S3h`), tracker ids (`SEC-16`, `DX-17`).
One stale claim fixed on the way: test_hls_reaper's docstring said the player checkpoints "every
10 s whether or not it is playing", which plex/stream.py calls wrong; it now says what the code does.
54 lines
2.2 KiB
TypeScript
54 lines
2.2 KiB
TypeScript
import type { QueryClient } from "@tanstack/react-query";
|
|
import { qk } from "./queryKeys";
|
|
import { api } from "./api";
|
|
import { copyText } from "./clipboard";
|
|
import { notify } from "./notifications";
|
|
import { notifyActionError } from "./actionErrors";
|
|
|
|
// The download queue, the per-card download index, and the storage-usage meter all move together
|
|
// whenever a job is enqueued, deleted, or its state changes — invalidate them as one unit.
|
|
export function invalidateDownloads(qc: QueryClient) {
|
|
qc.invalidateQueries({ queryKey: qk.downloads() });
|
|
qc.invalidateQueries({ queryKey: qk.downloadIndex() });
|
|
qc.invalidateQueries({ queryKey: qk.downloadUsage() });
|
|
}
|
|
|
|
// One-click share: the row's Share button turns "share this" into a single action.
|
|
// Ensure the job has a public link (minting one on demand — omitting `allow_download` so the backend
|
|
// default makes it downloadable), copy it to the clipboard, and toast with an "open in new tab"
|
|
// action. A copy failure still toasts (warning) WITH that action, so the link is never a dead end.
|
|
// Advanced controls (password, expiry, revoke, share-with-a-user) live in the Share dialog. Returns
|
|
// the shared URL, or null if link creation failed — so the caller/tests can react to the outcome.
|
|
export async function quickShareLink(
|
|
qc: QueryClient,
|
|
job: { id: number },
|
|
t: (key: string) => string,
|
|
): Promise<string | null> {
|
|
let link;
|
|
try {
|
|
const existing = await qc.fetchQuery({
|
|
queryKey: qk.downloadLinks(job.id),
|
|
queryFn: () => api.downloadLinks(job.id),
|
|
});
|
|
link = existing[0];
|
|
if (!link) {
|
|
link = await api.createDownloadLink(job.id, {}); // omit allow_download → backend default (true)
|
|
qc.setQueryData(qk.downloadLinks(job.id), [link]);
|
|
}
|
|
} catch (e) {
|
|
notifyActionError(e, t("downloads.share.linkFailed"));
|
|
return null;
|
|
}
|
|
const url = window.location.origin + link.url;
|
|
const action = {
|
|
label: t("downloads.share.openNewTab"),
|
|
onClick: () => window.open(url, "_blank", "noopener,noreferrer"),
|
|
};
|
|
notify(
|
|
(await copyText(url))
|
|
? { level: "success", message: t("downloads.share.linkCopied"), action }
|
|
: { level: "warning", message: t("downloads.share.copyFailed"), action },
|
|
);
|
|
return url;
|
|
}
|