Skip to content

fix(channels): preserve context through archive and restore - #452

Draft
tellaho wants to merge 6 commits into
mainfrom
tho/channel-archive-restore-main
Draft

tellaho wants to merge 6 commits into
mainfrom
tho/channel-archive-restore-main

Conversation

@tellaho

@tellaho tellaho commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

Overview

Category: improvement

User Impact: Channel administrators can archive and restore a channel without losing their place or draft, with consistent loading feedback while actions are pending.

Problem: Archiving currently removes the open conversation and Settings, leaving no in-app restoration path. Lifecycle and permission checks also use inconsistent loading feedback.

Solution: Keep the selected conversation and Settings open, replace Archive with Unarchive, and preserve the draft while sending is disabled. Reuse the existing lifecycle, navigation, and loading-button owners so removal actions retain their current behavior and recovery.

Follow-up to #385; only the five unmerged follow-up commits are included. No new relay-server work, permission expansion, or general archived-channel browser.

Draft — known native integration blocker: the packaged-native validator in src-tauri/src/relay/channel_writes.rs:43–51 accepts archived=true but rejects Unarchive's archived=false. The browser/development-broker flow below passes; packaged-native Unarchive does not work at this head. Native validation/coverage and stale native-support wording in docs/channels.md must be reconciled before readiness. This PR makes no Rust changes.

Changes

  • Archive/Unarchive retain the conversation, Settings, and draft; archived sending is disabled and restoration re-enables it.
  • Joined archived channels remain reachable through explicit name search, labelled Archived channel, but stay out of the sidebar and Recent activity.
  • Restoration requires fresh relay metadata; an absent metadata record is not success. Direct owner/admin authority remains required.
  • Shared button spinners cover lifecycle confirmations and Edit details permission lookup, preserving accessible status, pending lockout, errors, retry, and focus recovery.
  • Delete/Leave/Hide retain their existing removal/navigation behavior.
File changes

dev/relay-broker-api.test.mjs
Exercise the narrow Unarchive command at the development-broker boundary.

docs/channels.md
Document restoration, search reachability, retained context, loading feedback, and the full broker-process restart requirement. Native-support wording still needs reconciliation as noted above.

src/app/shell/SearchResults.tsx
Offer eligible joined archived channels in explicit search, with an archive label, without adding them to Recent activity.

src/app/shell/SearchResults.test.tsx
Cover archived search eligibility and exclusion from recent destinations.

src/bundled/channels/ChannelDetailsEditor.tsx
Use the shared loading button for permission checks and retain screen-reader status and visible recovery.

src/bundled/channels/ChannelDetailsEditor.test.tsx
Verify pending permission feedback remains accessible and non-actionable.

src/bundled/channels/ChannelLifecycleActions.tsx
Swap Archive/Unarchive according to current authority and preserve pending/error presentation.

src/bundled/channels/ChannelLifecycleActions.test.tsx
Cover restoration eligibility and action/status recovery.

src/bundled/channels/ChannelLifecycleDialog.tsx
Add Unarchive confirmation and use shared loading feedback without weakening pending lockout.

src/bundled/channels/ChannelLifecycleMenu.tsx
Reuse the lifecycle action model for Archive/Unarchive labels and selection.

src/bundled/channels/ChannelLifecycleMenu.test.tsx
Verify restoration actions and loading/recovery presentation.

src/bundled/channels/ChannelsPage.tsx
Separate joined-channel access from sidebar visibility so an archived destination remains selected; refresh Settings actions when archive state changes.

src/features/channel-navigation/ChannelNavigationState.tsx
Retain a persistent Settings focus fallback when lifecycle action buttons remount.

src/features/channel-navigation/ChannelSidebar.tsx
Keep the destination for Archive/Unarchive while preserving removal navigation for Delete/Leave/Hide.

src/features/messages/MessageComposer.tsx
Disable archived-channel sending without discarding the existing draft.

src/features/messages/MessageComposer.test.tsx
Cover draft retention and restored editing across archive-state changes.

src/features/relay/channel-lifecycle-protocol.ts
Add the narrow Unarchive template and direct owner/admin eligibility using the existing lifecycle command.

src/features/relay/channel-lifecycle.ts
Authorize restoration and require confirming metadata before updating shared discovery.

src/features/relay/channel-lifecycle.test.ts
Cover restore authority, command shape, host isolation, readback, and uncertain outcomes.

tests/browser/channel-archive-delete-pane.spec.mjs
Extend the existing real-app journey through Archive, reload, navigation away, search, Unarchive, and another reload with a nonempty draft; assert spinner geometry, lockout, and focus recovery.

tests/browser/channel-lifecycle.spec.mjs
Assert retained archive destinations while keeping Delete/Leave/Hide removal behavior, including reload with no visible rows.

tests/browser/fixture.mjs
Model restore publications in the isolated upstream fixture by removing the archive flag.

Validation

Checked head: 083b43669c33f53e699d2a43a93b7d7352048d44, based on main 329fe2a1de0ad7f9aa8eebbc02e1a957bcafa25d. All five patches replayed unchanged; working tree clean.

  • 34 Chromium/WebKit cases passed, with no retries, after rebase:
    pnpm test:browser --project chromium --project webkit --no-deps channel-archive-delete-pane.spec.mjs channel-lifecycle.spec.mjs channel-leave-pane.spec.mjs channel-settings.spec.mjs global-search.spec.mjs
  • Push hooks passed TypeScript, 3,090 related tests across 192 files, design types, and design guards. Existing approved contrast exceptions remained explicitly reported by the guards; this is not a claim of exception-free contrast.
  • git diff --check origin/main...HEAD passed; remote branch matches the checked head.
  • Browser tests exercise the built app, real services/storage, and production development broker with disposable fixture identities and modeled upstream relay state—not a component showcase or native/live-relay acceptance.
  • Browser case count: no cases added or removed; existing journeys were extended. Browser-only justification: mounted navigation/Settings continuity, focus, loading-button rendering, and persisted draft survival across reload/search. Policy, authority, and command edge cases remain in unit/broker tests. No fresh fail-then-pass mutation run was performed for this publication.
  • Human tryout accepted the pre-rebase browser build; fresh automation covers the rebased head. Independent review, native integration/acceptance, live-relay acceptance, and hosted CI results remain outstanding. The earlier full-suite count is not reused as evidence for this head.

Reproduction Steps

  1. Start the browser app with a fresh development-broker process; frontend hot reload alone does not refresh an older archive-only broker validator.
  2. Open a disposable channel you directly own/administer, type a draft without sending it, and open Settings.
  3. Choose Archive channel and confirm. While pending, the confirmation button shows a spinner and duplicate submission/Cancel/Escape are blocked.
  4. Verify the channel leaves the sidebar but the conversation, Settings, and draft remain. Unarchive channel replaces Archive; sending is disabled.
  5. Reload, navigate to another channel, then search for the archived channel by name. Open the Archived channel result and Settings; the draft should still be present.
  6. Choose Unarchive channel and confirm. The sidebar row and Archive action return, the conversation and Settings stay open, and sending becomes available with the same draft. Reload again to check persistence.
  7. Check a failed operation can recover without losing focus or permitting blind resubmission after an uncertain result. Use disposable data for destructive actions; Delete/Leave/Hide should still navigate away after confirmed removal.

Screenshots / Demos

Real built browser app at the checked head, in dark theme with a color accent, using isolated synthetic fixture identities and neutral content. Crops exclude unrelated workspace context. These are active → archived → restored states of this build, not native captures or before/after revisions.

Active — draft and Settings open

Active channel with its draft and Archive action

Archived — same draft, disabled sending, Unarchive available

Archived channel retains conversation, Settings and draft with sending disabled

Restored — same draft, sending enabled again

Restored channel retains its draft and shows Archive again

Loading states

Confirmation pending: shared button spinner; cancellation and repeat submission are locked.

Archive confirmation while waiting for completion

Edit details permission lookup: shared loading button rather than a visible checking paragraph; accessible status remains.

Settings permission check using the shared loading button

Prepared with Carl (AI agent) assistance.

Carl added 6 commits September 29, 2026 21:29
Replace Archive with Unarchive for direct owners and admins, reuse the confirmed lifecycle command and retain the open conversation. Keep joined archived channels reachable through name search without adding them to recent activity. Cover restore authority, host isolation, readback uncertainty and the full archive-to-restore browser journey.

Signed-off-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
Signed-off-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
Signed-off-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
Keep the selected conversation and Settings through archive and restore, preserve focus when actions remount, and block new messages while archived without discarding drafts. Use standard button spinners and accessible status for lifecycle confirmations and Edit details permission checks. Cover the retained destination, recovery, and held-read presentation in component and browser tests.

Signed-off-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
Exercise a nonempty draft in the existing built-app lifecycle journey, including reload and the search return path. Assert archived send lockout and restored editing without adding browser cases.

Signed-off-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
Align the sorting integration journey with the accepted archive contract: retain the selected URL and disabled composer, including after reload. Preserve Recent ordering, DM visibility and publication assertions.

Signed-off-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant