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); +});