Repository source: docs/plans/private_message_history/private_message_history_step4_implementation_summary.md
Private message history Step 4 implementation summary
Stage 1 - Stable conversation snapshots
Changes: added viewer/counterpart-scoped 25-message history pages with insertion-bound snapshots, timestamp/insertion ordering, exact page replay, and validated continuation anchors. Existing list-returning methods and no-cursor behavior remain compatible; the conversation API adds page/next cursors and an explicit invalid-cursor restart response. Initial HTML metadata and ciphertext retrieval now use the same snapshot, with page-specific reader caching.
Verification:
php tests/run.php PrivateMessageStoreTest PrivateMessageMailboxServiceTest PrivateMessagePageControllerTest PrivateMessageReaderTest PrivateMessageApiRoutingTest— 24 passed. Tests cover 63 tied messages, interleaved/backdated arrivals, empty/exact page bounds, replay, malformed/foreign/stale cursors, controller responses, and reader snapshot/cache isolation.node tests/browser/private_message_list_browser.mjspassed existing real-encryption, navigation, recovery, shared-composer and layout journeys; artifacts/tmp/private-message-browser-vW9Xzv/. Syntax and whitespace checks passed.Notes: no schema migration or production mutation. The first regression run caught and corrected a SQLite parameter-affinity issue before commit. Planning commit:
1995c45f; this branch builds on unmerged Cycle 2. Per-message error presentation and historical-key access are unchanged.
Stage 2 - Load older messages
Changes: added bounded, serialized Load older requests and loading/exhaustion feedback. Older messages prepend chronologically with deduplicated IDs using the same shell, grouping, dates, time formatter, and verification path as initial/inline messages. Each card retains its own ciphertext for isolated retry; malformed or foreign message payloads are rejected before rendering.
Verification:
php tests/run.php PrivateMessagePageControllerTest PrivateMessageReaderTest PrivateMessageComposerTest— 10 passed. Expandednode tests/browser/private_message_list_browser.mjspassed, traversing over 63 messages, preserving drafts, excluding an interleaved backdated arrival, preventing duplicate requests on repeated activation, and retrying the oldest message without refetching the newest window. Artifacts:/tmp/private-message-browser-gkwQo2/. Syntax and whitespace checks passed.Notes: explicit scroll anchoring follows in Stage 3; full restart/error recovery follows in Stage 4. Existing unreadable-message warnings were reused without redesign.
Stage 3 - Preserve the reading position
Changes: preserve the visible message and pixel offset through loading feedback, prepend, grouping, and asynchronous decryption. Temporarily disable native anchoring while applying explicit corrections; restore it on completion or user navigation. Rebase after network waits, respect scrolling/keyboard/touch navigation, and cancel the old anchor for Latest message. Initial conversation positioning is not replayed by history loads.
Verification: focused page/reader/composer suite — 10 passed; JavaScript syntax and whitespace checks passed. Expanded real-encryption browser journey passed (
/tmp/private-message-browser-pbo4lx/): mixed long/unverifiable history, out-of-order delayed reads, desktop, 375px mobile, 2× viewport zoom, user navigation, Latest message, and concurrent encrypted reply. Stationary and user-selected message anchors remained within 5px after settlement; native anchoring was restored.Notes: the first browser check exposed a 29px loading-feedback jump, corrected before commit. Physical mobile keyboard/browser testing remains a handoff limitation; automated checks cover viewport and zoom fallbacks.
Stage 4 - Recover without losing context
Changes: retries retain the exact continuation cursor; malformed/nonmatching responses leave the transcript untouched. Access failures explain sign-in/eligibility requirements. Invalid positions offer explicit Restart history, which swaps windows only after a valid fresh response, retains concurrent send confirmations, and leaves composer/draft/attempt storage alone. Detached-view and superseded responses are ignored; page exit aborts pending fetches and restores controls/anchoring for back navigation.
Verification: focused store/service/page/reader/routing suite — 24 passed; PHP/JavaScript syntax and whitespace checks passed. Full browser journey passed (
/tmp/private-message-browser-LLmzM7/), including aborted requests, invalid JSON/payloads, access loss, stale cursors, failed restart, a held fresh snapshot plus concurrent accepted send/newer draft, network-wait navigation, and an obsolete response after view removal. Prior old-message retry and mixed-signature cases also passed.Notes: restart is explicit and does not reload the document or reset drafts. Existing message-level warnings remain unchanged. The new recovery fixture uses isolated requests/data only.
Stage 5 - Final verification and handoff
Changes: documented the additive conversation cursor/replay/error contract in
/api/; updated the master checklist with Cycle 3 completion and retained the requested decryption-widget/key-access deferrals. Browser reporting now names history coverage and screenshots. Final review added explicit handling/coverage for counterpart key eligibility loss (HTTP 400), distinct from viewer authentication/approval failure (401/403).Verification:
php tests/run.php PrivateMessageDatabaseConfigTest PrivateMessageApiRoutingTest PrivateMessageComposerTest PrivateMessageEnvelopeTest PrivateMessageMailboxServiceTest PrivateMessageListTest PrivateMessagePageControllerTest PrivateMessageReaderTest PrivateMessageReleaseIsolationTest PrivateMessageStoreTest ApprovedUserKeyResolverTest AuthNavigationTest OfflineNavigationWorkerTest ProfilePresentationContentTest ProfileThemePresentationTest— 55 passed, 0 failed.node tests/browser/private_message_list_browser.mjs— passed, artifacts/tmp/private-message-browser-fJH8D9/(report.json, screenshots,server.log). Normal Messages entry, at least three history pages, exact-once snapshot coverage, ties/backdated arrivals, anchoring, isolated retries, restart/concurrent send/newer draft, obsolete responses, authorization/no-store, and shared profile/aggregate composers passed. Static artifact/offline exclusion passed. Changed PHP/JavaScript syntax, local document links, and whitespace checks passed; mobile screenshot reviewed.Notes: no migration, deployment, production data changes, merge, or push. Rollback restores the Cycle 2 code/assets while preserving stored messages and drafts. Browser checks use Chromium and controlled identities; physical mobile keyboards and other browser engines remain manual follow-ups. FDP artifacts retain the existing three-file layout. Branch
feature/private-message-historyincludes unmerged Cycle 2; review that dependency when merging. Five stages complete, with one planning-only commit and a summary-bearing commit per stage; unread work remains Cycle 4.