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.
59 lines
2.0 KiB
TypeScript
59 lines
2.0 KiB
TypeScript
import { useTranslation } from "react-i18next";
|
|
import { Copy, Link2 } from "lucide-react";
|
|
|
|
import { copyText } from "../lib/clipboard";
|
|
import { notify } from "../lib/notifications";
|
|
import { type ActionMenuItem } from "./ui/ActionMenu";
|
|
|
|
// "Copy title" and "Copy link", the two rows every object's menu ends its middle group with
|
|
// (DESIGN.md C1, "Text stays selectable"): on touch the long-press belongs to the context menu, so the text a
|
|
// finger cannot select is copied from here. One builder, so every object says it the same way and
|
|
// the clipboard write always runs inside the gesture (`inGesture`, which Safari insists on).
|
|
//
|
|
// A feature file, not `ui/`: it speaks the app's notifications.
|
|
|
|
type Named = "title" | "name";
|
|
|
|
export function useCopyItems() {
|
|
const { t } = useTranslation();
|
|
const copy = (text: string, done: string) => {
|
|
void copyText(text).then((ok) =>
|
|
notify(
|
|
ok
|
|
? { level: "success", message: done }
|
|
: { level: "error", message: t("common.copy.failed") },
|
|
),
|
|
);
|
|
};
|
|
/** The rows for one object. `text` is its title (a video, a show) or its name (a channel, a
|
|
* playlist); a row whose value is missing is left out rather than copying an empty string. */
|
|
return (
|
|
text: string | null | undefined,
|
|
link: string | null | undefined,
|
|
named: Named = "title",
|
|
): ActionMenuItem[] => [
|
|
...(text
|
|
? [
|
|
{
|
|
id: "copy-text",
|
|
label: t(named === "title" ? "common.copy.title" : "common.copy.name"),
|
|
icon: <Copy className="w-4 h-4" />,
|
|
inGesture: true,
|
|
onSelect: () => copy(text, t("common.copy.done")),
|
|
},
|
|
]
|
|
: []),
|
|
...(link
|
|
? [
|
|
{
|
|
id: "copy-link",
|
|
label: t("common.copy.link"),
|
|
icon: <Link2 className="w-4 h-4" />,
|
|
inGesture: true,
|
|
onSelect: () => copy(link, t("common.copy.linkDone")),
|
|
},
|
|
]
|
|
: []),
|
|
];
|
|
}
|