Skip to content

Workspace chooser: Archive action + modal, Archived section + Restore (CS-11669)#5373

Merged
lukemelia merged 11 commits into
mainfrom
cs-11669-workspace-chooser-archive-action-modal-archived-section
Jul 1, 2026
Merged

Workspace chooser: Archive action + modal, Archived section + Restore (CS-11669)#5373
lukemelia merged 11 commits into
mainfrom
cs-11669-workspace-chooser-archive-action-modal-archived-section

Conversation

@lukemelia

@lukemelia lukemelia commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

What

Front-end for the owner archive/restore flows in the Workspace Chooser. Builds on the realm-server archive endpoints (POST /_archive-realm, POST /_unarchive-realm, GET /_archived-realms) and on _realm-auth enumeration that omits archived realms from the active workspace list.

Changes

  • RealmServerService — adds archiveRealm / unarchiveRealm (owner-only POSTs) and fetchArchivedRealms (owner-only GET /_archived-realms), plus a tracked archived-realms list that's cleared on reset. Archiving drops the realm from the active "Your Workspaces" enumeration and refreshes the archived list; restoring reverses both.
  • Owner tile menu — an owner-only Archive Workspace item opens a non-destructive confirmation modal explaining the realm will not be available for viewing or editing — to the owner or any other user — until restored. Confirming archives the realm and removes its tile from the active section.
  • Archived section — a collapsed Archived disclosure row at the bottom of the chooser (below Catalogs). The archived list is fetched only when the row is first expanded (with a loading indicator and a "No archived workspaces" empty state). Archived realms are sealed (their _info/session endpoints answer 403), so tiles render from the _archived-realms display metadata (name/icon/background) rather than mounting each realm, and each offers a Restore action.

Design notes

  • The Archived section is collapsed by default and sits at the bottom, below Catalogs — archived workspaces are rarely needed, so they stay out of the way.
  • The archived list loads lazily, only on first expand, so opening the chooser costs no extra request.
  • The disclosure row is shown to all users; visibility does not depend on the deferred fetch. The _archived-realms endpoint is owner-scoped, so non-owners see an empty state. The Archive action itself is owner-only, gated on realm ownership in the tile menu.

Behavior contract

  • Archive appears only on tiles the user owns; the modal copy explains the realm becomes unavailable to everyone until restored.
  • Confirming archive removes the tile from the active section.
  • The Archived section lists the caller's archived realms; Restore returns a realm to the active section.
  • The Archived section is visible to everyone but empty for non-owners; the Archive action is owner-only.

Tests

Host realm-server mock routes for the three endpoints (and the mock _realm-auth omits archived realms so the active list excludes them), plus an acceptance test covering: owner archive → expand → Archived section → Restore → active; lazy-load + empty state; modal cancel; and that non-owned tiles have no Archive action.

🤖 Generated with Claude Code

Add the owner-facing archive/restore flows to the workspace chooser:

- RealmServerService gains archiveRealm / unarchiveRealm (owner-only POST
  /_archive-realm and /_unarchive-realm) and fetchArchivedRealms (owner-only
  GET /_archived-realms), with a tracked archived-realms list cleared on reset.
  Archiving drops the realm from the active "Your Workspaces" enumeration and
  refreshes the archived list; restoring reverses both.
- The owner tile menu gains an "Archive Workspace" item (owner-only) that opens
  a non-destructive confirmation modal explaining the realm will be unavailable
  for viewing or editing until restored.
- A new owner-only Archived section lists the caller's archived realms. Archived
  realms are sealed, so their tiles render from the /_archived-realms display
  metadata (name/icon/background) rather than mounting each realm, and each
  offers a Restore action. The section stays hidden for non-owners because the
  endpoint is owner-scoped.

Adds host realm-server mock routes for the three endpoints (and omits archived
realms from the mock _realm-auth enumeration, mirroring CS-11665) plus an
acceptance test covering archive -> Archived section -> restore -> active, modal
cancel, and the non-owner case.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Preview deployments

Host Test Results

    1 files      1 suites   2h 33m 33s ⏱️
3 372 tests 3 357 ✅ 15 💤 0 ❌
3 391 runs  3 376 ✅ 15 💤 0 ❌

Results for commit ae109ab.

Realm Server Test Results

    1 files  ±0      1 suites  ±0   9m 23s ⏱️ +43s
1 674 tests ±0  1 674 ✅ ±0  0 💤 ±0  0 ❌ ±0 
1 753 runs  ±0  1 753 ✅ ±0  0 💤 ±0  0 ❌ ±0 

Results for commit ae109ab. ± Comparison against earlier commit 8478b3f.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements workspace archive/restore UI in the Operator Mode workspace chooser, including owner-only archive confirmation flow and an owner-only “Archived” section backed by realm-server endpoints.

Changes:

  • Adds realm-server service methods/state to archive/unarchive realms and fetch the current user’s archived realms.
  • Updates workspace tiles to offer an owner-only “Archive Workspace” action with a confirmation modal and active-workspace cleanup.
  • Extends host realm-server mocks and adds an acceptance test covering archive → archived section → restore, cancel, and non-owner behavior.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
packages/host/tests/helpers/realm-server-mock/types.ts Adds archived realm state to the realm-server mock state.
packages/host/tests/helpers/realm-server-mock/routes.ts Mocks archive/unarchive/list-archived endpoints and omits archived realms from enumeration.
packages/host/tests/helpers/realm-server-mock/index.ts Initializes the new archived realm mock state.
packages/host/tests/acceptance/workspace-chooser-archive-test.gts Acceptance coverage for owner archive/restore, cancel, and non-owner UI.
packages/host/app/services/realm-server.ts Adds archive/unarchive/fetchArchivedRealms APIs and tracked archived realm list.
packages/host/app/components/operator-mode/workspace-chooser/workspace.gts Adds owner-only archive menu item + confirmation modal and archive task logic.
packages/host/app/components/operator-mode/workspace-chooser/index.gts Adds Archived section rendering and initial archived list fetch.
packages/host/app/components/operator-mode/workspace-chooser/archived-workspace.gts Introduces archived workspace tile with Restore action rendered from archived metadata.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/host/tests/helpers/realm-server-mock/routes.ts Outdated
Comment thread packages/host/tests/helpers/realm-server-mock/routes.ts Outdated
Comment thread packages/host/app/services/realm-server.ts
lukemelia and others added 3 commits June 30, 2026 15:39
Archived realms are rarely needed, so the section now sits at the bottom of the
chooser (below Catalogs) and is collapsed behind a disclosure triangle showing
the archived count. Expanding it reveals the archived tiles.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Archived disclosure row is now always shown at the bottom of the chooser
and the archived list is fetched only when the row is first expanded (with a
loading indicator and a "No archived workspaces" empty state). Drops the eager
fetch on every chooser open.

Because visibility no longer depends on the fetch, the row is shown to all
users; the endpoint stays owner-scoped, so non-owners simply see an empty
state. The Archive action remains owner-only on the tile menu.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Describe the current contract by mechanism rather than referencing a private
tracker ID, and drop temporal phrasing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lukemelia lukemelia requested review from a team and FadhlanR June 30, 2026 19:59
lukemelia and others added 3 commits June 30, 2026 15:59
- Give the archive confirmation dialog an explicit aria-label so it has an
  accessible name despite the visually-customized header.
- Order the mock /_archived-realms response by archived-at then url to match
  the real query.
- Clear the archived-realms list in place on reset so the cached getter
  recomputes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The bare setupAuthEndpoints() call registered testRealmURL with no backing
realm, so boot-time authentication fetched its info and got a 503 that
surfaced as a background rejection during the longer archive/restore tests.
setupAcceptanceTestRealm already registers auth for each realm it creates, so
the extra call is unnecessary.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@FadhlanR

FadhlanR commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

One small comment: What happens if unarchiveRealm throws an error, such as a 403? Would the user know that the unarchiveRealm fails?

lukemelia and others added 2 commits July 1, 2026 07:09
A failed unarchiveRealm (e.g. a 403 for a non-owner) previously left the
Restore action silent: the spinner cleared, the button reappeared, and the
user got no feedback. Catch the error and render it on the tile, matching the
archive action's error handling.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lukemelia

Copy link
Copy Markdown
Contributor Author

[Claude Code 🤖] Good catch — it was failing silently. Previously an unarchiveRealm rejection (e.g. a 403 for a non-owner) just cleared the spinner and brought the Restore button back with no feedback. Fixed in e15bebb: the tile now catches the error and shows it inline, matching how the Archive action surfaces archiveError. Added an acceptance test that forces a 403 and asserts the message appears and the workspace stays archived.

lukemelia and others added 2 commits July 1, 2026 07:54
The test-realm service worker relays http://test-realm/ fetches to the window
and answers 503 "No responsive client available" when nothing replies within
its 1500ms timeout. An active worker keeps controlling the page after
unregister() until the page unloads, so once an image/audio module registers
it, it lingers and intercepts test-realm fetches in later modules too. The
responder was bound per-module, so those later modules (e.g. show-card,
fallback-models, theme-card) had no responder — their escaped test-realm
fetches timed out and 503'd, cascading whole modules into setup failures. This
was shard-composition-sensitive, hence the intermittent shard-10 failures.

Install a single, never-removed responder that resolves the current test's
network service at message time, wired into setupApplicationTest and
setupRenderingTest so every module is covered regardless of ordering.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lukemelia

Copy link
Copy Markdown
Contributor Author

[Claude Code 🤖] Update on the intermittent Host shard failures (shards 10/12/13): these are a pre-existing test-harness flake, not this PR's code. Root cause: the test-realm service worker (test-realm-sw.js) keeps controlling the QUnit runner after unregister(), so once an image/audio module registers it, it lingers into later modules and 503s their http://test-realm/ fetches (including boot-time setSystemCard) → login form → cascade/timeout. It's shard-composition-sensitive, which is why merging main / adding tests can trigger it.

The always-on-responder fix I pushed here earlier backfired (turned fast failures into 243s timeouts by leaking pending fetches) and has been reverted (ae109ab0d9).

A proper fix — gate the SW so it passes through when no module has claimed it — plus the full diagnosis is in #5380. This PR's own changes are unaffected; once #5380 lands (or on a green re-run) the shards should pass.

@lukemelia lukemelia merged commit 9a5b766 into main Jul 1, 2026
72 of 73 checks passed
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.

3 participants