Skip to content

Allow deleting the #general chat thread; enforce at-least-one-thread invariant #1422

Description

@ptone

Summary

The #general thread in native chat cannot be deleted — three layers of guards prevent it (store, handler, UI). Users who want to reorganize their threads around project-specific topics are stuck with the default #general thread forever. The request is to make #general deletable while enforcing that at least one thread always exists, and falling back to the first thread in the list when no #general is present.

Current state

#General deletion guards (three layers)

  1. Store layerDeleteTopic in both SQLite (webchannel_store.go:874-884) and Postgres (webchannel_store_postgres.go:505-514) checks is_general and returns "cannot delete #general topic".
  2. Handler layerhandleTopicDelete (handlers_chat_v2.go:664-667) surfaces the error as HTTP 400.
  3. UI layerchat-space-rail.ts:1663 hides Delete/Rename menu items for isGeneral threads; handleDeleteThread (line 1245) has an early return guard.

Navigation assumes #general exists

  • selectSpaceBySlug (chat.ts:1282-1301) uses threads.find(t => t.isGeneral) with no fallback to threads[0].
  • handleCollapsedSpaceClick (chat-space-rail.ts:929-933) — same pattern, no fallback.

Lazy #general re-creation (critical interaction)

ListTopics in both store implementations auto-creates #general if none exists (webchannel_store_postgres.go:453-464, webchannel_store.go:828-843). If #general becomes deletable, this lazy creation would immediately re-create it, defeating the purpose.

No "last thread" guard

Neither store nor handler checks thread count before deletion.

What must change

1. Make #general deletable

  • Store: Remove is_general guard in DeleteTopic (both dialects). Replace with a "last remaining thread" count check.
  • Handler: Replace the #general error with a "cannot delete the last thread" error.
  • UI: Remove !thread.isGeneral guards hiding Delete from the context menu. Show error to user when "last thread" deletion is rejected.

2. Enforce at-least-one-thread invariant

  • Store: Before soft-deleting, count active (non-deleted) topics for the project. If count ≤ 1, return error.
  • Handler: Surface as 400 validation error.

3. Navigation fallback when no #general

  • selectSpaceBySlug (chat.ts:1282-1301): Fallback to threads[0] when no isGeneral thread found.
  • handleCollapsedSpaceClick (chat-space-rail.ts:929-933): Same fallback.
  • No sort changes needed — existing sort already produces stable order.

4. Remove lazy #general re-creation

  • Remove auto-create from ListTopics (webchannel_store_postgres.go:453-464, webchannel_store.go:828-843). Keep EnsureGeneralTopic only on project creation path (handlers_projects_core.go:592-625).

5. Decide on #general rename

  • Currently blocked (handleTopicPatch:571-574). If #general is deletable, renaming should also be allowed. Remove the rename guard.

Edge cases

  • Messages in deleted threads: No FK constraints — messages survive soft-delete but become inaccessible via UI (history endpoint returns 404 for deleted topics). Acceptable for v1.
  • Read state orphaning: webchat_read_state rows keyed by topic UUID become orphaned. Harmless but accumulate.
  • Conversation entity: Topic deletion does not delete the linked conversations row. May need cleanup.

Sizing

Small — changes are well-localized: 2 store files, 1 handler file, 2 frontend files, plus test updates. Conditional pivot to Medium if rename support, data migration, or conversation-entity cleanup are included.

Files

  • pkg/hub/webchannel_store_postgres.go:453-464,503-523,546-595 — Postgres store (lazy creation, delete guard, ensure)
  • pkg/hub/webchannel_store.go:828-843,872-893 — SQLite store (lazy creation, delete guard)
  • pkg/hub/handlers_chat_v2.go:524-676 — Handler (patch/delete guards)
  • web/src/components/shared/chat/chat-space-rail.ts:1220,1245,1663 — UI guards
  • web/src/components/pages/chat.ts:1260-1303 — Navigation fallback
  • Investigation doc: scratchpad/projects/roadmap/inv-chat-thread-deletion.md

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions