diff --git a/docs/DESIGN.md b/docs/DESIGN.md
index eae067e..6740057 100644
--- a/docs/DESIGN.md
+++ b/docs/DESIGN.md
@@ -496,7 +496,12 @@ text. On focus the border moves to faint. The field gets no glow, because a form
- **Detail sheet:** `surface` fill with a hairline border and 16px corners. It rises from the bottom
on a phone, is centred at `max-w-lg` from `sm` up, and sits over the scrim.
- **Toast / update notice:** `raised` fill, hairline border, 12px corners and the only shadow in the
- system. Each offers one action.
+ system. Each offers one action, and the "Moved to Archive" toast also a plain OK that only closes
+ it. That toast sits in the bottom-right corner, 24px in, at most 420px wide, so it stays off the
+ reading column and the events moving up into the gap; up to 700px wide there is no side to clear,
+ so it spans the width 16px in from each edge at the top, over the header, because at the bottom it
+ covered the end of an event's page opened while it was up. It was centred, for eight seconds,
+ until 2026-11-10, when the owner found it covering the next events.
### Daily burst
A small spray of 3px sparks that fires once, in the row, when a reader finishes one thing: a daily
diff --git a/docs/PRD.md b/docs/PRD.md
index 122d565..0e1102d 100644
--- a/docs/PRD.md
+++ b/docs/PRD.md
@@ -193,7 +193,8 @@ the cut are still counted in the header, still on the timeline, and one tap away
**F3 — Mark completed.**
Done writes durable progress and a metadata snapshot immediately. A confirmation remains for about
0.9 seconds (1.5 until 2026-10-20, which read as unresponsive) before the row fades/collapses into
-Archive. Undo is available for eight seconds; Archive Restore remains available afterwards. A failed
+Archive. Undo is offered in a toast, out of the list's way, for four seconds (eight until 2026-11-10, when it sat
+over the next events), and its OK closes it sooner; Archive Restore remains available afterwards. A failed
write is disclosed and never presented as saved. Reduced motion removes the fade.
**F4 — Settings and visibility.**
diff --git a/src/client/ReaderApp.tsx b/src/client/ReaderApp.tsx
index 038b654..e672ce6 100644
--- a/src/client/ReaderApp.tsx
+++ b/src/client/ReaderApp.tsx
@@ -69,6 +69,7 @@ import { ENTRIES, NOTES, newestId, unseen } from "../shared/changelog.ts";
import {
ARCHIVE_COLLAPSE_MS,
ARCHIVE_HOLD_MS,
+ ARCHIVE_UNDO_MS,
ChecklistEvent,
leavingMarks,
LedgerColumns,
@@ -498,7 +499,7 @@ function ReaderContent({
() => setTransition((t) => (t ? { ...t, phase: "hidden" } : null)),
ARCHIVE_COLLAPSE_MS,
);
- const undo = setTimeout(() => setTransition(null), 8000);
+ const undo = setTimeout(() => setTransition(null), ARCHIVE_UNDO_MS);
return () => {
clearTimeout(t);
clearTimeout(collapse);
@@ -1137,21 +1138,29 @@ function ReaderContent({
transition.phase === "hold" && (
)}
- {transition.label} “{transition.title}” · Moved to Archive{" "}
-
+
+ {transition.label} “{transition.title}” · Moved to Archive
+
+
+
+ {/* Closes the notice only: the event stays archived. */}
+
+
)}
{archivedOpen && (
diff --git a/src/client/components/redesign/ChecklistEvent.tsx b/src/client/components/redesign/ChecklistEvent.tsx
index ceecf65..207111c 100644
--- a/src/client/components/redesign/ChecklistEvent.tsx
+++ b/src/client/components/redesign/ChecklistEvent.tsx
@@ -56,7 +56,13 @@ export function UrgencyLegend() {
* when it is gone once the 250 ms collapse has run.
*/
export const ARCHIVE_HOLD_MS = 900,
- ARCHIVE_COLLAPSE_MS = ARCHIVE_HOLD_MS + 250;
+ ARCHIVE_COLLAPSE_MS = ARCHIVE_HOLD_MS + 250,
+ /**
+ * How long the "Moved to Archive" toast offers Undo. Eight seconds until
+ * 2026-11-10, when the owner found it sitting over the next events; OK closes
+ * it sooner, and Archive Restore stays available after it is gone.
+ */
+ ARCHIVE_UNDO_MS = 4000;
/**
* The marks on a checklist row on its way to the Archive. While it holds, its
* text greys; a completed one also gets the codes' shine, an ignored one does
diff --git a/src/client/redesign.css b/src/client/redesign.css
index b164c85..a70d07b 100644
--- a/src/client/redesign.css
+++ b/src/client/redesign.css
@@ -1003,25 +1003,53 @@ textarea {
text-decoration: underline;
text-underline-offset: 3px;
}
+/* Bottom right, out of the reading column, so it does not sit over the next
+ events (centred until 2026-11-10). On a phone there is no side to move to,
+ and the bottom is where the list continues and an event's page ends, so it
+ spans the width at the top, over the header, instead. */
.archive-toast {
position: fixed;
bottom: 24px;
- left: 50%;
- transform: translateX(-50%);
+ right: 24px;
width: max-content;
- max-width: calc(100vw - 32px);
+ max-width: min(420px, calc(100vw - 32px));
z-index: 70;
- padding: 14px 20px;
+ display: flex;
+ align-items: center;
+ gap: 6px 16px;
+ padding: 12px 16px;
+ border: 1px solid var(--color-hairline);
border-radius: 12px;
background: var(--color-raised);
color: var(--color-ink);
font-size: 13px;
box-shadow: 0 8px 30px #0003;
}
+.archive-toast-text {
+ min-width: 0;
+}
+.archive-toast-actions {
+ display: flex;
+ flex: none;
+ gap: 4px;
+}
.archive-toast button {
+ min-height: 32px;
+ padding: 0 8px;
+ border-radius: 8px;
color: var(--color-near);
- margin-left: 14px;
text-decoration: underline;
+ text-underline-offset: 3px;
+}
+@media (max-width: 700px) {
+ .archive-toast {
+ top: calc(16px + env(safe-area-inset-top, 0px));
+ bottom: auto;
+ left: 16px;
+ right: 16px;
+ width: auto;
+ max-width: none;
+ }
}
.archive-confirm {
padding: 8px 20px;
diff --git a/test/redesign-checklist.test.tsx b/test/redesign-checklist.test.tsx
index 57df9f2..198e209 100644
--- a/test/redesign-checklist.test.tsx
+++ b/test/redesign-checklist.test.tsx
@@ -86,6 +86,7 @@ test("a completed event shines and greys while it holds, then fades; an ignored
import {
ARCHIVE_COLLAPSE_MS,
ARCHIVE_HOLD_MS,
+ ARCHIVE_UNDO_MS,
} from "../src/client/components/redesign/ChecklistEvent.tsx";
test("a completed event holds under a second, long enough for its shine, then collapses", () => {
expect(ARCHIVE_HOLD_MS).toBe(900);
@@ -94,3 +95,8 @@ test("a completed event holds under a second, long enough for its shine, then co
// The collapse waits out the 250 ms max-height transition.
expect(ARCHIVE_COLLAPSE_MS - ARCHIVE_HOLD_MS).toBe(250);
});
+test("the Moved to Archive notice offers Undo for four seconds, outlasting the row's collapse", () => {
+ // Halved from eight on 2026-11-10: the notice sat over the next events.
+ expect(ARCHIVE_UNDO_MS).toBe(4000);
+ expect(ARCHIVE_UNDO_MS).toBeGreaterThan(ARCHIVE_COLLAPSE_MS);
+});