Free unused attachments, and keep versions by count rather than by age
Two rules governed version retention: the newest 50 per note, and a sweep of anything past 90 days. The second was wrong. The count already bounds what a note can accumulate, so age only ever deleted history nothing had replaced — and it bit hardest exactly where history is most wanted, a note left alone for a quarter of a year losing its whole past for having done nothing. It is off unless VERSION_RETENTION_DAYS is set. How many versions to keep is now a notebook's own setting, falling back to the server default when left empty. A reference notebook can keep fifty of everything while a scratch one keeps three. That makes freeing attachments answerable. A picture taken out of a note cannot be deleted straight away: the versions from before still show it, and restoring one would find a hole. It is released once no current note and no kept version refers to it — which, with retention by count, is when the last version holding it is pushed out. The server cannot look for itself, bodies being encrypted, so the client reports a note's attachments on every save as it already does for links, and a version inherits what the note held when it was snapshotted — by definition what that version contains. Two things make it safe rather than merely plausible. Silence is not "uses nothing": a note written before this existed has no rows, which looks identical to one with no pictures, so nothing is released for a user while any of their notes has yet to report — the prefetch that fills the search index reports for them on its first pass. And an upload has a row before the note using it does, so recent attachments are left alone. POST /api/admin/housekeeping runs the sweep on demand. Caught while testing: the same block of markup had been pasted into the create-notebook dialog as well, where the state it referenced did not exist — the dialog crashed on open. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a32d6eba99
commit
3acb8b66a4
12 files changed
+507
-28
No files matched your search
@@ -4,6 +4,15 @@ import { COVERS } from "./constants.jsx";
|
||||
export function EditNotebookModal({ notebook, onClose, onSave, dark }) {
|
||||
const [name, setName] = useState(notebook.name);
|
||||
const [sel, setSel] = useState(notebook.cover);
|
||||
// Empty means "use the server's default", which is why it is a string here and
|
||||
// not a number with a stand-in value.
|
||||
const [versions, setVersions] = useState(notebook.maxVersions == null ? "" : String(notebook.maxVersions));
|
||||
|
||||
const save = () => {
|
||||
if (!name.trim()) return;
|
||||
const n = versions.trim() === "" ? null : Math.max(1, Math.round(Number(versions)));
|
||||
onSave({ name: name.trim(), cover: sel, maxVersions: Number.isFinite(n) ? n : null });
|
||||
};
|
||||
const T = useMemo(
|
||||
() => ({
|
||||
bg: dark ? "#161b22" : "#fff",
|
||||
@@ -69,9 +78,40 @@ export function EditNotebookModal({ notebook, onClose, onSave, dark }) {
|
||||
boxSizing: "border-box",
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" && name.trim()) onSave({ name: name.trim(), cover: sel });
|
||||
if (e.key === "Enter") save();
|
||||
}}
|
||||
/>
|
||||
<div style={{ marginBottom: 18 }}>
|
||||
<label style={{ fontSize: 12, color: T.muted, display: "block", marginBottom: 5 }}>
|
||||
Versionen pro Notiz
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
value={versions}
|
||||
onChange={(e) => setVersions(e.target.value)}
|
||||
placeholder="Vorgabe"
|
||||
aria-label="Versions per note"
|
||||
style={{
|
||||
width: 110,
|
||||
padding: "8px 12px",
|
||||
borderRadius: 10,
|
||||
border: `1px solid ${T.border}`,
|
||||
background: dark ? "#0d1117" : "#f6f8fa",
|
||||
color: T.text,
|
||||
fontSize: 14,
|
||||
outline: "none",
|
||||
boxSizing: "border-box",
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") save();
|
||||
}}
|
||||
/>
|
||||
<div style={{ fontSize: 11, color: T.muted, marginTop: 6, lineHeight: 1.5 }}>
|
||||
Ältere Fassungen werden erst gelöscht, wenn neue sie verdrängen — nicht nach Zeit. Leer lassen für
|
||||
die Vorgabe des Servers.
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ marginBottom: 22 }}>
|
||||
<div style={{ fontSize: 12, color: T.muted, marginBottom: 8 }}>Cover style</div>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "repeat(5,1fr)", gap: 8 }}>
|
||||
@@ -115,7 +155,7 @@ export function EditNotebookModal({ notebook, onClose, onSave, dark }) {
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
if (name.trim()) onSave({ name: name.trim(), cover: sel });
|
||||
save();
|
||||
}}
|
||||
style={{
|
||||
padding: "8px 20px",
|
||||
|
||||
Reference in new issue
Block a user