Skip to content

feat(desktop): restore archive identity UI in profile panel#961

Draft
tellaho wants to merge 7 commits into
mainfrom
tho/restore-archive-ui
Draft

feat(desktop): restore archive identity UI in profile panel#961
tellaho wants to merge 7 commits into
mainfrom
tho/restore-archive-ui

Conversation

@tellaho

@tellaho tellaho commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Category: new-feature
User Impact: Identity and agent profiles regain a Manage → Archive control, now guarded by a confirmation dialog that plainly explains what archiving does before it happens.

Problem: A prior holistic-pass merge stripped the archive-identity control out of the profile panel and deferred it. Beyond just restoring a button, the archive action needed to explain itself: archiving is relay-scoped and reversible, but without context it reads like account deletion — and the bot case (where permanent deletion lives elsewhere) was conflated with the human-identity case.

Solution: Restore the archive UI as a dedicated Manage section beneath the profile metadata, collapse the old six-prop drilling into a single useIdentityArchive hook, and gate the destructive-sounding action behind a calm, bot-aware confirmation dialog. The dialog reassures rather than alarms (secondary confirm button, not destructive red), splits its copy by identity type, and — for agents only — links to where permanent deletion actually lives (the Agents tab). The growing panel file was split along its natural seams to stay under the 1000-line cap.

File changes

desktop/src/features/identity-archive/hooks.ts
Introduces useIdentityArchive(pubkey), composing the three gate queries (relay membership, NIP-OA owner, archived status) plus current pubkey, owning both mutations and the toasted archive/unarchive callbacks. Returns { canArchive, isArchived, isPending, archive, unarchive }. Gate preserved verbatim: isSelf || isRelayAdminOrOwner || isOaOwnerOfViewee.

desktop/src/features/profile/ui/ArchiveConfirmDialog.tsx
New confirmation dialog gating the archive action. Bot-aware copy throughout ("Archive this agent?" vs "Archive this identity?"). Three effect bullets in a <ul> rendered as a sibling of AlertDialogDescription (which is a <p> and can't legally nest a list). Bot path adds a trailing paragraph linking to permanent deletion in the Agents tab; identity path renders no link (no delete target for a human). Confirm button styled secondary, not destructive.

desktop/src/features/profile/ui/ProfileManageSection.tsx
The Manage section hosting Archive/Unarchive. Lifts navigation here (useAppNavigation().goAgents()) and passes onGoToAgents into the dialog so the dialog stays presentational — the link closes the dialog first, then navigates (no routing behind an open modal). Unarchive stays a bare one-click recovery action.

desktop/src/features/profile/ui/ProfileFields.tsx
Extracted from the oversized panel file: field type, public/owner builders, the group + row layout, and the copy helper.

desktop/src/features/profile/ui/ProfileHero.tsx
Extracted hero header and collapsible description.

desktop/src/features/profile/ui/ProfileActions.tsx
Extracted primary actions, quick-action row, working badge, and ingress row.

desktop/src/features/profile/ui/profileSummaryTypes.ts
Shared profile/status types, broken out to avoid a circular import between the new sub-components.

desktop/src/features/profile/ui/UserProfilePanelSections.tsx
Reduced to ProfileSummaryView composition and focused views after the split. The Manage section now renders below the metadata field group. Pure refactor of render output — same gate, same section order, testids intact.

desktop/tests/e2e/identity-archive.spec.ts
Restores the 5-case e2e gate matrix dropped by the earlier UI removal, now testid-driven. Case 1 clicks through the confirmation dialog.

Reproduction steps

  1. Run the desktop app and open a profile panel for an identity you can manage (self, or one where you're relay admin/owner, or NIP-OA owner of the viewee).
  2. Scroll past the metadata fields (Channels → Public key → Agent type → Capabilities) to the new Manage section at the bottom.
  3. Click Archive — confirm the dialog opens with a calm secondary button, a three-bullet effects list, and copy matching the identity type.
  4. For an agent profile: verify the trailing paragraph links to the Agents tab; clicking it closes the dialog and navigates to /agents.
  5. For a human identity: verify there is no link in the dialog.
  6. Confirm archiving applies the "Archived on this relay" flair under the display name; Unarchive is a one-click recovery with no dialog.

Screenshots

archivable
archived


Coordination: buzz://message?channel=67a2c861-cd9a-4365-9cf1-d78bc9b24abc&thread=557d7005f943f81972a498452d95fa51cce54f94aa39fed31cf518cd1a6a51bd

@tellaho tellaho force-pushed the tho/restore-archive-ui branch from d9f873d to 9074860 Compare June 10, 2026 22:55
@tellaho tellaho marked this pull request as ready for review June 11, 2026 00:12
@tellaho tellaho force-pushed the tho/restore-archive-ui branch from 9074860 to 234b8c1 Compare June 12, 2026 22:24
tellaho and others added 3 commits June 13, 2026 19:55
Adds a Manage section below the quick-actions row with a full-width
Archive / Unarchive button, plus the "Archived on this relay" flair
under the displayName. Gated by the original three-path composition
(self / relay admin or owner / NIP-OA owner of viewee) — the relay
re-verifies authority on submit. Button label flips to "Archive agent"
on bot profiles.

Restores the 5-case e2e gate matrix that #917 dropped alongside the UI.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…hive hook

Introduces useIdentityArchive(pubkey) in features/identity-archive/hooks.ts,
composing the three gate queries (relay membership, OA owner, archived
status) plus currentPubkey, owning both mutations and the toasted
archive/unarchive callbacks. Returns { canArchive, isArchived, isPending,
archive, unarchive }.

Both call sites (ProfileSummaryView and ProfileManageSection) consume the
hook directly, removing the six drilled props from UserProfilePanel and the
now-dead hook calls, handlers, and imports. Gate composition is preserved
verbatim: isSelf || isRelayAdminOrOwner || isOaOwnerOfViewee.

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Relocates the Archive/Unarchive Manage section so it renders after the
metadata field group instead of above the memories/channels ingress,
placing identity management at the bottom of the profile summary.

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
@tellaho tellaho force-pushed the tho/restore-archive-ui branch from 234b8c1 to 17b4a2f Compare June 14, 2026 02:58
npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w and others added 2 commits June 13, 2026 20:06
Collapses the isSelf check to a single line and removes a stray blank
line introduced during the rebase conflict resolution. Whitespace only,
no logic touched.

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
The archive UI work tipped UserProfilePanelSections.tsx past the hard
1000-line cap (1016 > 1000). Per the file-size guard's own rule, split
the file rather than adding an override.

Extracts the natural section seams into self-documenting sibling files:
- ProfileFields.tsx — field type, public/owner builders, group + row, copy helper
- ProfileHero.tsx — hero header + collapsible description
- ProfileActions.tsx — primary actions, quick action, working badge, ingress row
- ProfileManageSection.tsx — NIP-IA archive/unarchive section
- profileSummaryTypes.ts — shared profile/status types (avoids circular import)

UserProfilePanelSections.tsx now holds only ProfileSummaryView composition
and the focused views. Pure refactor: same render output, same gate, same
section order, testids and the identity-archive 5-case matrix untouched.

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
@tellaho tellaho marked this pull request as draft June 14, 2026 19:14
Gate the archive action behind a calm AlertDialog (ArchiveConfirmDialog)
that honestly explains what archiving does before it fires. Archiving is
relay-scoped (NIP-IA kind:13535 snapshot, not account deletion, not
global) and reversible, so the dialog reassures rather than alarms — the
confirm button renders secondary, not destructive.

- Bot-aware copy throughout: "Archive this agent?" vs "Archive this
  identity?", "removes this agent" vs "removes this person".
- Three effect bullets (won't appear in search/autocomplete/member-add,
  affects only this space, reversible via unarchive) rendered as a <ul>
  sibling of AlertDialogDescription — that component is a <p> and can't
  legally nest a list.
- Bot path adds a trailing paragraph pointing to permanent deletion in
  the Agents tab via an in-app link (Button variant=link) that closes
  the dialog then navigates with useAppNavigation().goAgents() — no raw
  anchor, no routing behind an open modal. Navigation is lifted to
  ProfileManageSection and passed in via onGoToAgents so the dialog
  stays presentational. Identity path renders no link (there is no
  delete target for a human identity).
- Confirm button styling fix: AlertDialogAction applies buttonVariants()
  (primary) to its own element; the secondary classes are passed
  straight to it so tailwind-merge overrides the primary background
  rather than asChild + a nested Button (which concatenates variants and
  leaves primary winning on source order).

Unarchive stays bare one-click (recovery action, no friction). All three
archive testids plus archive-confirm-dialog/archive-confirm-action are
intact; identity-archive.spec.ts is testid-driven, stays 5/5 green, and
case 1 clicks through the confirm. Zero validateDOMNesting warnings.

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
@tellaho tellaho force-pushed the tho/restore-archive-ui branch from 12df2b8 to d11b8ac Compare June 14, 2026 20:15
…le panel

Owned agents rendered as humans in the profile panel's archive flow: the
Archive button + confirm modal showed the human variant even for an agent
the viewer owns (repro: tho's agent Edna).

Root cause: two gates disagreed. The archive button's canArchive gate
resolves correctly via OA-ownership, but the human-vs-agent framing used a
separate signal — isBot = Boolean(relayAgent || managedAgent) — that checks
the relay-agents registry + the local managed-agents list. An owned agent
deployed elsewhere can miss BOTH lists, so isBot was false and the panel
rendered the human framing while the button still showed.

Fix: OR in the kind:0-derived agent flag (isAgent on the users-batch
summary, which the backend sets from profile_has_valid_oa_owner — a verified
NIP-OA auth tag on the target's kind:0). That's the same authoritative
signal the archive gate's resolveOaOwner trusts, so isBot can no longer
drift from the gate. Client-only change, no relay/registry change.

- UserProfilePanel: query useUsersBatchQuery([pubkey]) and OR its isAgent
  into isBot (keyed by lowercased pubkey, matching the house pattern).
- BotIdenticon: forward an optional data-testid to its wrapper.
- ProfileHero: tag the profile bot indicator with
  data-testid=profile-bot-indicator for the regression test.
- profile.spec.ts: regression test — an owned agent seeded with the kind:0
  agent flag but absent from relay/managed lists now renders agent framing.

Applied fresh in-branch on #961 (not cherry-picked) so the bot-detection
fix and the archive dialog ship together: Edna now renders the full
agent-framed dialog ("Archive this agent?" + Agents-tab link) end-to-end.

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
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