refactor(chat): retire members' handles: nothing reads or writes them

Merged
DA
5d ago
+15616-153

Summary

This is step 3a of replacing chat handles with mention links, from Audit Chat page and use common app components/styles. Since #4004 and #4009, nothing shows or uses a member's handle, but the code still minted and read one. This PR stops both, so the column can be dropped in a contract migration. That drop is step 3b, at least 25 hours after this reaches production, following the repo's expand–migrate–contract rule: a column may only be dropped once the code that stopped writing it has drained.

Store

  • No handle is minted or read. These are removed: mintHandle, findHandles, findAccountName, personStem, SPOKEN_FOR, FALLBACK_HANDLE and MAX_HANDLE_STEM_CHARS, plus handle on ChatMemberRow and insertMember.
  • Migration 20261021033224_chat_member_handle_retire: chat_member.handle defaults to gen_random_uuid(). A deployment from before this PR still reads a non-null string, and the column's unique index still holds for rows minted without a handle.

Wire and service

  • Removed: chatMemberSchema.handle, chatHandleSchema, LUKE_HANDLE and CHAT_BOUNDS.MAX_HANDLE_CHARS.
  • The window's held read no longer sends a handle, and the desktop's live-row decoder no longer reads one.
  • Live members statement: it hands each row the member's id in place of a handle, instead of reading the column.
    • Desktops from #4004 to #4009 still decode a required handle there, so they keep working.
    • No live query sealed before the drop reads the column.
    • The stand-in can go once those desktops have updated.

Evidence

  • Typecheck is clean in features, database, service, ui, panel, host and desktop.
  • Lint and migrations: oxlint, biome and check:migrations pass. Knip's findings are all on main already; none are from this PR.
  • Tests: 248 pass across the features, panel, host, ui and inbox suites. coding-agents-app.test.ts passes 46 chat tests, workspace-chat.test.ts and live-statements.test.ts pass 11, and the chat-store and inbox-store suites pass 54.
  • New and rewritten tests:
    • Minting: members are minted once per subject and numbered among their kind. The test inserts five people and two agents with no handle, which proves the new default stays unique under the index.
    • Live stand-in: each live member row's handle is the member's id.
    • Wire assertions: the window's read names its reader and teammates by name rather than by handle.

Invariants

  • ./scripts/check.sh green. (CI)
  • No column dropped. The -- contract: drop is step 3b, at least 25 hours after this reaches production.
  • Every behavior change has, in this PR, the test that fails without it; no change-detector test added.
  • No new as type assertion outside the allowlisted files. (CI via anti-slop)
  • No Effect.runPromise/runSync/runFork outside the runtime edges. (CI)
  • No new raw async primitives. (CI)
  • Test count: the handle grammar test removed; the minting test rewritten; the live stand-in test added.
  • PRIVACY.md unchanged.
  • Package deps unchanged. (CI)

After this

Step 3b: a migration that drops chat_member.handle and chat_member_organization_handle, marked -- contract:, with the schema property removed. It can start 25 hours after this reaches production.

Made with Luke


Devin Review