Omniscio documentation
Browse all documentation
  1. Getting Started13
  2. Sessions & Agents115
  3. Inbox & Notifications59
  4. Projects & Tasks95
  5. Automation & Scheduling75
  6. Knowledge & Memory26
  7. AI Features60
  8. Integrations100
  9. Plugins & Marketplace33
  10. Cloud & Teams56
  11. Settings & Customization58
  12. Account & Billing28
  13. Troubleshooting84
  14. CLI & API Reference22
  15. Legal & Policies4
  16. Uncategorised22

Team Chat (built-in channels and direct messages) (part 2)

Part 2 of the Team Chat page: the second wave of features — day dividers, drafts, saved items, custom status, scheduled send, attachments and custom emoji — plus the workspace story, with per-channel roles and invitations, self-serve workspaces you create and share by invite code, and 1:1 connections with people in other organizations.

What it is

This is part 2 of the Team Chat (built-in channels and direct messages) page. It covers everything added to Team Chat after the first version — the message-surface polish, the composer upgrades, saved items, custom status, scheduled send, attachments and custom emoji — and then the workspace story: the per-channel roles and invitations that let a channel be managed rather than just used, the self-serve chat workspaces you create and run yourself beside your company workspace, and the one-to-one connections that reach people in other organizations.

Where to find it

The same surface as the parent page: once it is switched on, Team Chat appears as a built-in virtual project in the projects sidebar, with the channel and direct-message list in the sidebar pane and the open conversation filling the main panel. It ships dark until Settings → Lab → Team Chat is turned on. The parent page covers finding and enabling it.

Everything on this page is found inside the chat surface itself: the second wave of features sits in the conversation, the composer, the sidebar header and the channel context menus, while self-serve workspaces are the switcher rail down the side of the chat sidebar and cross-organization connections are reached from the same New-message picker. The per-channel notification controls and the bell menu are described on part 3.

How it behaves

New in this wave (v2 — the "destroy Slack" overhaul)

The daily-driver polish + the four schema-foundation features are now BUILT (desktop; the phone PWA renders the text and ignores the richer bits until its own parity lane). Invariants + the locking tests for each: team-chat-v2-features-contract.md.

  • Message-surface polish — Slack-style day dividers (a centered "Today / Yesterday / July 2" pill) and a bold "N new messages" line above the first unread, shown both in the full Team Chat view and in the inbox conversation embed (the embed fetches your read marker on open, so the new-messages line renders there too), a floating jump-to-latest pill (counting what arrived while you were scrolled up), the same floating up/down message arrows an agent session has — the up arrow steps back one message per tap, holding it (or Shift/Ctrl/Cmd+clicking) jumps to the top of the loaded history, and Shift+Up / Shift+Down step through messages from the keyboard while the up arrow is on screen, a live typing indicator under the composer, and online/away dots on DM rows.
  • Composer power — drafts that survive channel switches + restart (device-local, LRU-capped), up-arrow on an empty box to edit your last message, :emoji: autocomplete as you type, and auto-capitalization as you type — the first word, sentence starts (after . ! ?), and new paragraphs, while skipping URLs, paths, and abbreviations. A word that is the first CONTENT of its line counts as a paragraph start even when a list marker precedes it, so a numbered or bulleted item capitalizes like any other block (2. run tests becomes 2. Run tests) — the marker's own . is not read as a decimal, which is what used to leave every list item lowercase. It reuses the shared @shared/smart-capitalize engine (the same one the Tasks input + Quick Email body use), runs live on each word plus a finalize pass on send, is default ON with an opt-out toggle in Settings → Sessions → Composer & input (autoCapitalizeTeamChat), and is locked by composer contract auto-capitalize-as-you-type.
  • Search, wired in — the sidebar magnifier opens a search overlay over the existing search index: type ≥2 chars → results (channel + author + snippet), click to jump. Degrades to "search isn't switched on yet" until the search Cloud Function is deployed.
  • Saved items — a bookmark on any message + a sidebar "Saved" view (own-doc saved/{uid}, cap 200), newest-first. Each save captures a snapshot (author name, message text, channel name) so the Saved panel renders with zero Firestore reads and survives message deletion. Design modeled after an earlier internal prototype.
  • Custom status — set an emoji + text + auto-expiry (30m / 1h / 4h / Today / This week / Don't clear); shown on the profile card, member directory, and beside DM names. Global (workspaceId: null): status shows in every workspace. Expired statuses are actively cleared from Firestore by useStatusAutoClean (a precise setTimeout at the expiresAt moment writes null; already-past expiries clear immediately on mount), and also hidden client-side via statusIsActive() as a fast visual gate. The field-scoped customStatus merge write can't clobber the presence heartbeat, and vice-versa.
  • Desktop notifications + unread badge — an app-level watcher raises native OS toasts for new messages while you're elsewhere (honoring per-channel mutes + Focus Mode via the notification-service funnel) and folds an unread count onto the Team Chat nav row. Baselines on first load so there's no start-up storm; skips the channel you're actively looking at. The same watcher also drops a row into your unified Inbox — one coalescing row per channel, titled with the sender's name (resolved from the workspace roster, never a raw id), refreshed as new messages arrive — so a message you missed is reliably waiting there even when the OS toast was suppressed (the inbox is the durable record). It applies the same audience rule as the toast (DMs + @mentions + unmuted channels, never your own, and skips a channel you're actively reading) — but, unlike the ephemeral toast, the inbox row is NOT dropped just because a message is a few seconds old or arrived in a quick burst, so a delayed or bursty reply still lands there (this fixed the reported "replies don't reliably show in my inbox"). And messages that arrived while Omniscio was fully closed are swept into your inbox the next time you open it (silently, no late toast, and only for channels you'd genuinely left unread), so nothing is missed. Each row stays until you dismiss it. Selecting the row embeds the live conversation + a composer, so you read the recent messages AND reply straight from the inbox — a reply posts to the channel and clears the notice (a failed send keeps it, with Retry); viewing marks the channel read; "Open channel" stays as a secondary jump to the full Team Chat surface. After you dismiss, the next new message raises a fresh row. Thread replies aimed at you surface too: when someone replies in a thread to a message you wrote — or @-mentions you in a reply — you get a separate, targeted Inbox row ("Jordan replied to your message in #general"), distinct from the channel's new-message row, cleared when you read the channel, and opening straight into that thread when you click it (not just the channel). It never fires for your own reply, a muted channel, or a reply that isn't aimed at you, and there is an on-by-default switch (Settings → Team Chat notifications) to turn it off. (The phone push still treats a reply as an ordinary channel message — a "replied to you"-aware push is a follow-up.) Reactions to your messages can surface too — opt-in, OFF by default: with teamChatNotifyReactions ON, someone reacting (emoji) to a message you wrote raises a separate, targeted Inbox row ("Jordan reacted 👍 to your message"), inbox-row ONLY (no OS toast — reactions are higher-frequency and lower-signal), its own coalescing row that clears when you read the channel; never for your own reaction, a muted channel, or a reaction on someone else's message. Toggle in Settings → Team Chat notifications. Invariant I8 in inbox-alert-contract.md; thread-reply notice details in team-chat-threads-contract.md; reaction-notice details in team-chat-reactions-contract.md.
  • Remind me later (snooze a note-to-self from the chat, not just the Inbox) — on your note-to-self (a DM whose only participant is you), a clock "Remind me later" button in the conversation header and a "Remind me later…" item in the conversation's right-click menu in the sidebar both open the same snooze time picker you already use in the Inbox. Picking a time creates a durable reminder for that note and hides it until then, so it pops back into your Inbox at the time you chose — even a note you'd already read (unlike the ordinary "new message" notice, which reading clears). Clicking the reminder opens the note; you can re-snooze or dismiss it like any Inbox row, and setting one offers undo. It reuses the existing snooze storage (no new table, no database change) and is scoped to the note-to-self for now (the underlying action is channel-generic, so widening to any conversation later is a one-line change). Desktop-only — there is no CLI route for this action; it is driven entirely by the renderer UI. Mechanics + the team-chat-reminder: notice kind: inbox-alert-contract.md.
  • Attachments — paperclip / paste / drag-drop a file (client pre-validated to 25 MB + a content-type allowlist mirroring the storage rules, ≤10/message, sanitized filename), uploaded to Storage with progress + cancel — while it is staged, its chip above the composer shows a thumbnail of the image itself (fitted whole, never cropped to a square) and a plain file icon for anything else; images render inline in the sent message (shared ImageCanvas), files as download cards. Send is blocked while uploads are in flight; an attachment-only send auto-fills the text with the filenames (the frozen text≥1 create rule) but the renderer hides that fallback body when every attachment name matches (the image/file cards already show the name). Before you send, a staged chip is removed by its X or a middle-click on the chip, and either is undoable (a toast Undo, or Ctrl+Z / Ctrl+Y) — a completed upload's bytes are not purged until the undo window has passed, so undo genuinely restores the file rather than a chip pointing at nothing. Once a message is sent, its attachments are not middle-click-removable — removing a sent message is the existing delete flow, which best-effort cleans up its files after the undo window (and undo restores them).
  • Scheduled send — "Send later" (presets in your local time + a custom picker) writes an author-owned doc at channels/{channelId}/scheduledMessages/{id} (nested under the target channel); a sidebar "Scheduled" view lists + cancels. A once-a-minute onScheduledSendTick Cloud Function delivers each due message in a per-doc transaction (deterministic id sched-<id> = idempotent, re-checks the author can still post, drops if the channel's gone), then deletes the scheduled doc. Security by construction (Uplink pattern): the function derives the target channel from schedDoc.ref.parent.parent — never from a client field — so a scheduled doc can only post to the channel it was created under.
  • Custom emoji — all workspace members upload custom images by default (party-parrot style, PNG/GIF/WebP ≤256 KB, no SVG) with a unique :shortcode: — via the emoji picker's "+" button (compact upload dialog), the sidebar management dialog, or the bulk upload dialog (up to 50 files at once with drag-and-drop, per-file validation, auto-deduplication of shortcodes, and sequential upload with per-file progress — opened from the management dialog's "Bulk Upload" button). A toggleable adminOnly flag on canUploadCustomEmoji lets a workspace restrict uploads to owners/admins without code changes. Members can delete their own uploads; admins can delete anyone's. Every member can use custom emoji in the emoji picker's Custom tab, in reactions, in custom status, and inline in message text (type :shortcode: and the rendered message shows the image). The current user's own uploads are also kept in a personal emoji layer, persisted by Firebase uid, so they remain available in DMs and after switching workspaces; the active workspace's emoji still win on shortcode collisions. The identifier format custom:<shortcode> slots into every surface a native emoji uses — existing reaction/status code needs no separate branch. Inline rendering uses the same remark-plugin pattern as @mentions (text node → internal-scheme link → component override, no innerHTML). Invariants: team-chat-custom-emoji-contract.md.

Delivery hops (need a live run / deploy to prove end to end): the OS toast actually firing (main-process), Storage upload/download (storage.rules now DEPLOYED + live-verified 2026-07-08 — org uploads flip 403→200; the client also gained self-serve chatWorkspaces upload support, shipping with the next app build), and the scheduled-send tick (needs onScheduledSendTick + a new scheduledMessages collection-group index on deliverAt deployed). The logic is unit/emulator-tested; the Storage hop is now proven live, the OS-toast + scheduled-send hops remain deploy steps. The scheduled-send path is now {root}/channels/{channelId}/scheduledMessages/{id} (nested under channels); the collection-group index on deliverAt is still needed for the tick function's cross-channel query.

Channel invitations + per-channel roles

Status: Shipped (the channel-invitations flag is flipped to shipped). Going live requires the Firestore rules to be deployed first — the channelMembers subcollection rules plus the postingPermission admin-write clause — otherwise the live feature can't persist role or posting-permission changes. Follows the Slack model: instant add (no accept/decline flow), system messages for every membership change, workspace admin override.

  • Per-channel membership — managers can add workspace members to specific channels (not just the whole workspace) and remove them. A member can leave any channel. Every change is instant and generates a centered system message (e.g. "Alice added Bob", "Charlie left").
  • Three-tier role model — each channel member has a role: manager (full control — add/remove/promote/demote), member (read + write), or readonly (read + react, no posting). The channel creator is automatically a manager; invited members default to member.
  • Posting permissions — a channel-wide postingPermission setting: everyone (default) or managers-only, set via the "Who can post" control in the Edit Channel dialog (workspace admins only). When set to managers-only, non-managers — and any readonly member in any channel — see a ReadOnlyBar banner in place of the message box (wired into the composer, matching what the server-side message-create rule enforces).
  • Bulk invite — add up to 50 members at once via the Add Members dialog, each with an assigned role. The dialog shows all workspace members with a search filter, checkboxes, and per-member role selectors.
  • Member count + panel — the channel header shows a member count button (Users icon + number); clicking it opens a ChannelMembersPanel slide-out showing all members sorted by role (managers first), with role badges and management actions for managers.
  • Last-manager protection — the last manager cannot leave, be removed, or be demoted. The system refuses the operation and surfaces an error.
  • Lazy backfill — pre-feature members (in memberUids but with no subcollection doc) are treated as role: 'member'; getChannelMemberRole() creates the subcollection doc on first check.
  • DMs excluded — all per-channel role operations skip DMs (DMs have no roles, no subcollection).
  • New Channel dialog — when creating a private channel, a member picker appears to pre-select initial members with per-member role assignment.

For engineers: per-channel membership lives in a Firestore subcollection channels/{channelId}/channelMembers/{uid} (role + addedBy + addedAt). The existing denormalized memberUids: string[] array stays as the fast-query index (79+ files depend on it). Every membership mutation writes subcollection doc + memberUids + system message in one writeBatch (atomicity invariant). joinChannelWithDoc uses setDoc(…, { merge: true }) for idempotent re-join. messageType: 'membership-change' renders as a centered divider, and that divider's copy is recomposed at render from the message's stored systemMessageKey + systemMessageParams with the actor named from authorUid against the live member directory (messageDocToClient carries both fields for exactly this). A doc written by an older build carries only the frozen text; parseMembershipText recovers its key + params by matching that text against the same template table, so its actor is named live too (a text matching no template, e.g. one frozen in another language, renders as stored). Never rely on the message's frozen text to name someone: the auto-join writer runs from the always-on background sync, before the member directory has loaded, so a name it baked in could only ever be the generic fallback — and it would be permanent. The postingPermission field is writable only by a workspace owner/admin (added to the channel admin-update rule alongside visibility); a plain member cannot set it. The channelMembers subcollection rules + this postingPermission clause must be deployed before the shipped feature works live (their emulator rules tests run in CI). Invariants + the deploy checklist: team-chat-channel-invitations-contract.md.

Self-serve workspaces (create + share your own chat spaces)

Alongside your governed company workspace, you can now create and join your own chat workspaces — Uplink-style, fully self-serve, with no admin to ask and no separate account to make. They live beside the company workspace in one workspace switcher rail down the side of the chat sidebar: the company workspace sits first (a building glyph), your self-serve workspaces follow (each a tile with its initial), and only the active one carries the accent. A "+" at the bottom opens a small chooser to Create a new workspace or Join one by code. Like the rest of Team Chat it ships dark — same Settings → Lab toggle / AMC_SHOW_TEAM_CHAT=1 — and the backend is authored but not yet deployed.

  • Create a workspace — name it and you become its owner; a #general channel is created and you're switched straight into it. (There's a small per-account cap on how many you can own.)
  • Share an invite code — as an owner/admin, the sidebar's "Invite people" affordance opens a share-code dialog that mints a 10-character code on demand and copies it — hand it to whoever you want in (owners/admins can also revoke). There is no list of existing codes: clients can't read the code docs, so the freshly-minted code is the only time one is ever shown. For your company workspace the same affordance still opens the by-email invite instead.
  • Join by code — paste a code into the Join dialog and you're added as a member and switched in. A bad / revoked / expired / used-up code is refused with a plain message, and the dialog stays open (never a false success).
  • Leave a workspace — right-click (or long-press on mobile) a self-serve workspace tile and choose "Leave workspace", behind a confirm that names what you lose (access to all its channels and messages; you can rejoin later with a code). Shown only to non-owner members — an owner can't leave (it would orphan the workspace), and the server refuses it too. Leaving the workspace you're currently viewing switches you away first so nothing flashes an error.
  • Delete a workspace (with a 30-day undo) — as the owner, right-click (or long-press on mobile) your workspace tile and choose "Delete workspace" — the owner's counterpart to Leave (an owner can't leave, but can delete). It's a soft delete behind a confirm: the workspace and all its channels, messages, and members are hidden immediately but kept for 30 days, and a toast offers an Undo that restores it. After the 30-day window a scheduled reaper permanently deletes the whole tree. Deleting the workspace you're viewing switches you away first. This closes audit finding F046 (a mis-click was previously an instant, unrecoverable delete).
  • Per-workspace identity — your display name in a workspace defaults from your account and is never blank. You can set a custom display name per workspace (like Discord) via the "Edit workspace name" button in your profile popover — the custom name shows on all messages (past and future, resolved at render time), survives global profile edits (the mirror skips displayName when customDisplayName is set), and can be reset to your account name with one click. A name-change system message is optionally announced in #general (default on, togglable in the dialog). Works in both company org workspaces and self-serve workspaces. Per-workspace avatar editing is a follow-on.
  • Workspace icon color — right-click (or long-press on mobile) a self-serve workspace tile to pick a gradient color from an 8-color palette (green, blue, red, purple, orange, pink, slate, teal), so you can tell your workspaces apart at a glance. The color is stored locally on each device; server persistence is a follow-on. Your custom color shows immediately when selected, including on the active tile (with an accent outline ring). See workspace-icon-color.md.
  • Workspace notification badge — each workspace icon in the desktop rail and the mobile switcher pill shows a notification count badge (Discord-style) when that workspace has active inbox alerts (non-archived, non-snoozed, surfaceable channel). The count derives from the unified Inbox (parsed from each alert's dedupKey workspace encoding), auto-adjusts reactively when alerts are dismissed from the Inbox, and caps at 99+. Connection requests (cross-workspace) are excluded. Implementation: countPerWorkspaceTeamChatNotices() in team-chat-inbox-count.ts; badge rendered on the tile wrapper (not inside the overflow-hidden Button).
  • What works in a self-serve workspace (v1): the core chat surface — public/private channels, DMs, live send / receive, paged history, @mentions, real names & avatars, edit/delete-your-own, presence/availability (user-level, tied to your company org — your Online/Away/DND/OOO/Offline status persists across all workspaces), plus reactions, pins, threads, saved items, scheduled send, unread badges, and desktop notifications — all work identically in both roots (lifted 2026-07-15, CW9). The remaining company-only extras are hidden here (not shown as dead buttons): typing indicators, search, per-channel notification prefs, and push-when-away — see the deferred list below.

Cross-org connections (1:1 DMs across organizations)

Connect with any Omniscio user by email — same workspace or a different organization — to start a 1:1 DM that forms only on the recipient's approval. Connections live in a separate top-level connections/{connId} collection (not under any org), with anti-enumeration privacy, server-only email lookups, and a per-user opt-out toggle. Full lifecycle, CLI routes, privacy controls, and renderer components: cross-org-connections.md. Inbox approval flow: connection-request-inbox.md. Contract: team-chat-connections-contract.md (C1-C13).

Link unfurling (rich link previews inside chat)

When a message contains a URL, Omniscio automatically fetches the page's Open Graph metadata and renders a rich preview card below the message — title, description, image, and site name — modeled after an earlier internal prototype's design patterns. In chat the card is a compact horizontal layout (a small thumbnail beside the text) so it stays dense in the conversation; the taller full-width hero image is reserved for roomier reading panes (inbox drip/alert bodies).

  • Automatic: fires on render (eager, not hover-gated). Max 3 URLs per message; image-only URLs (.png, .jpg, .gif, etc.) are skipped because they're already rendered inline as attachments.
  • SSRF-safe: all fetches go through redirectAwareFetch in the main process — a three-layer guard (sync IP check → DNS resolution → connect-time pinned dispatcher) that blocks private/loopback/link-local/CGNAT/metadata IPs, including through redirects.
  • Metadata priority: og:title → twitter:title → <title> tag; same fallback chain for description and image. Images must be https:// (mixed-content rejected). Fields capped at 500 chars; text fields have HTML entities decoded to a bounded fixed point, so a double-encoded title (e.g. Vimeo's &amp;#x27;) renders correctly.
  • Caching: main-process LRU (500 entries, 6h positive / 5min negative TTL) + renderer LRU (500 entries, 30min TTL) with deduped in-flight requests. No persistence (v1).
  • Rate-limited: 30 IPC calls per 10s window per renderer.
  • Cost: $0 — no AI spend; it's a plain HTTP fetch + HTML parse.

Sources of truth: src/main/services/link-preview/link-unfurl.ts (service), src/shared/team-chat/extract-urls.ts (URL extraction), src/renderer/src/hooks/useChatLinkUnfurl.ts (hook), src/renderer/src/features/team-chat/TeamChatLinkPreviews.tsx (component). Contract: team-chat-link-unfurl-contract.md.

For agents

How self-serve works (for agents / engineers)

  • A separate tenant, on purpose (the frozen boundary). Self-serve chat is a new Firestore-native chatWorkspaces/{wsId} collection, deliberately NOT the company org. organizations/{orgId} is Postgres-bound business authority (money · roles/tiers/entitlements · admin), so making it self-serve would violate the cloud-authority-boundary. A chatWorkspaces/* workspace carries chat-local roles only — never a custom claim, never billing/entitlement coupling — which is exactly why it's legitimate realtime/edge data in Firestore (design B; Uplink is the named precedent). The company org stays untouched (identity membership-truth-stays-on-the-user-doc intact).
  • Two roots, one chat layer. A WorkspaceRef = { kind: 'org' | 'workspace', id } drives rootPath() — org→organizations/{id}, workspace→chatWorkspaces/{id}. The core chat data layer builds every path from tenant-root-agnostic *For builders (firestore-paths.ts), so one implementation serves both roots; the original org-scalar builders stay byte-identical, so company chat + the live PWA don't move. Switching workspaces resets the org-scoped store slices (channels/messages/members/…) but keeps cross-tenant state (connections, composer drafts).
  • The trust boundary = ten Admin-SDK-only callables + a daily reaper (createChatWorkspace / joinChatWorkspace / leaveChatWorkspace / deleteChatWorkspace / restoreChatWorkspace / createChatInviteCode / revokeChatInviteCode / updateChatWorkspaceMemberRole / removeChatWorkspaceMember / updateChatWorkspace, plus the scheduled reapDeletedChatWorkspaces, in chat-workspaces.ts). The workspace, members, invite-code, and user-index docs are all write: if false — a user can never self-insert a membership by guessing a workspace id, nor self-elevate a role; the only way in is joinChatWorkspace with a code the server validates (revoked/expired/exhausted). This closes Uplink's known self-join hole by default. Clients write only channels + messages directly (member-gated); creation is transactional + per-user rate-capped, and join is idempotent (an existing member is a zero-write no-op). leaveChatWorkspace removes only the caller's OWN membership — the uid is the verified token, never the payload, so there's no way to evict another member — refuses the sole owner (leaving would orphan the workspace; mirrors the delete-account last-owner guard) and is an idempotent no-op for a non-member. deleteChatWorkspace is the owner's soft delete — it stamps deletedAt/deletedBy and destroys nothing; every listing filters deletedAt (so the workspace vanishes but its data is kept), and joinChatWorkspace now rejects a soft-deleted workspace so a live code can't resurrect it. restoreChatWorkspace clears the marker to undo, but is refused once past the window (so the undo window is strictly [0, 30d) and restore can never race the reaper). reapDeletedChatWorkspaces (daily onSchedule) does the permanent cascade — but only for a workspace whose deletedAt is >30 days old, re-verified against a fresh read with a pure fail-safe isReapable before it prunes indexes + deletes invite codes + recursiveDeletes the tree (kill-switch CHATWORKSPACE_REAP_DISABLED=1). Within a workspace, channel edits are anti-griefing-gated — a member may only toggle their own membership or denorm their own message's recency, never rename / re-topic / flip visibility / spoof an author / add or evict someone else (mirrors the org channel rule).
  • Not deployed. The new rules blocks + the ten callables + the reaper are authored and (core/unit) tested but not deployed — they share Team Chat's undeployed-rules posture and are inert until a client writes the collections. Deploying the reaper additionally needs its Cloud Scheduler job created (over-retention is the safe failure if it's missing). Design, invariants (CW1–CW12), and the invariant→test map: team-chat-workspaces-contract.md.
  • Explicitly deferred (v1 cut): push-when-away · search query UI inside a self-serve workspace (indexing is workspace-aware, query side needs the shared-handler generalization) · per-channel notification prefs (needs subcollection + rules under chatWorkspaces/*; desktop notifications themselves fire — CW9); a full cross-workspace unread rollup (v1 unread = the active + company workspace only); a per-workspace avatar upload (the icon color picker and the display-name editor are now built — see above); server persistence of the icon color (the Firestore iconColor field exists; the updateChatWorkspace callable is now built — CW14); invite expiry / max-uses inputs (the fields exist server-side, no UI yet); and PWA / phone parity. (Workspace delete + cascade is now BUILT — as a soft delete with a 30-day undo window; see the "Delete a workspace" item above.)

Related

This is one of four pages describing Team Chat. The parent page describes the feature and what the first version can do. Its three companions are part 2 on the second wave of features and workspaces, part 3 on the phone client, notifications and the smaller surfaces, and part 4 on the limits and the engineering behind it.

Two pages outside this set are worth having open alongside it: cross-org-connections.md for connecting with someone in another organization, and session-starter-bot.md for the bot that starts a Claude session from a channel message.

Last verified 2026-10-02