Skip to content
vibstrVibstr home
Open Vibstr

Discuss in Slack

Vibstr has no chat surface. There will never be a chat surface inside Vibstr — not channels, not DMs, not threads. Conversation lives where your team already talks. The artifact captures automatically.

This page is the working manual for the Slack discuss-in-thread bridge. If you want the strategic case, SPEC §18 covers the "why no chat" argument; this page is the "how to use it" version.

Why we built it this way

The original sketch had a comments tab on each item. It works fine, but it duplicates a real-time conversation surface your team already pays for. Two surfaces means two notification streams, two unread states, two places to search. Nobody asked for that.

The bridge is the alternative: one click on an item posts a Block Kit thread root to a channel your team already lurks in. Teammates reply in Slack as they would for anything else. Each reply mirrors back to Vibstr as a comment with via='slack' so the item carries the full discussion when you re-open it next week.

The trade-off is honest: you do not get a complete Vibstr-side conversation surface. The Slack thread is canonical. We are fine with that.

Install (one-time per project)

  1. Open Settings → PROJECT → Slack.
  2. Click Install to Slack — opens Slack's OAuth consent in a new tab. The bot requests these scopes:

`` chat:write — post the thread root + future Vibstr-driven messages channels:read — populate the channel picker users:read.email — auto-map Slack users to Vibstr profiles by email im:write — reserved for future DM notifications ``

  1. After consent, Slack redirects you back to Vibstr with the install confirmed. The bot's token is stored in vibelog_slack_workspaces, RLS-gated to project owners only.
  2. Pick a default channel from the dropdown that appears (lists every channel the bot is a member of). This is the channel "Discuss in Slack" posts threads into.
  3. If your channel is private or new, type /invite @Vibstr in Slack first — the bot can only post in channels it has been invited to.

Revoking the install stops Vibstr from using the token. The bot itself stays installed in Slack until you remove it from there manually — that is a Slack workspace admin action.

Discuss in Slack flow

Open any item in the drawer. There is a Discuss in Slack button in the drawer header (only visible to project members; viewers do not see it).

Click it. What happens:

  1. Vibstr posts a Block Kit thread root to the configured channel via chat.postMessage. Payload includes:

- Header: #NN — <first 80 chars of description> - Fields: Type, Area, Found-in build, Priority (when set) - Description excerpt (max 280 chars) - Context line: Open in Vibstr → · <project name> · Posted by <actor display name> - Divider

  1. The chat.postMessage response gives Vibstr a thread_ts. We write a row to vibelog_item_slack_threads mapping (item_id, workspace_id) → (channel_id, thread_ts, permalink) so future replies route to the right item.
  2. A small Slack chip appears in the drawer body showing reply count + permalink + a click-to-expand preview of the last 3 replies.
  3. The button disappears (idempotent — one thread per item per workspace).

Replies mirror back

A teammate replies in the Slack thread. What happens:

  1. Slack's Events API POSTs the message.channels event to Vibstr's webhook at vibstr-slack-events.
  2. The webhook verifies Slack's HMAC signature using SLACK_SIGNING_SECRET (HMAC-SHA256 over v0:<timestamp>:<raw body>, constant-time compare). Replay protection rejects timestamps more than 5 minutes skewed. Signature failure → 401, never executes side effects.
  3. Routing: workspace by slack_team_id → thread by (workspace_id, thread_ts) → item by FK. Anything that misses → ACK 200 + log + bail (Slack expects 200 within 3s; unmatched events are fine, they just go to /dev/null).
  4. Auto-map: if the Slack user's email matches a vibelog_profiles.email for a profile that is also a project member, we attribute the new comment to that Vibstr user. Otherwise the comment lands with external_user_name populated and user_id null — a pending mapping is written for later confirm.
  5. A via='slack' row is inserted into vibelog_item_comments. Followers of the item get an inbox notification with payload.via='slack'.

Open the item in Vibstr later — the comment is there with a 3px accent left border, a Slack glyph next to the author name, and a hover-revealed permalink button.

Edit and delete parity

Slack reply edits and deletes both mirror.

The match key is (thread_ts, external_user_id) ORDER BY created_at DESC LIMIT 1. Imperfect — if you edit your non-latest reply in a long thread, the wrong Vibstr row updates. The known fix is to store Slack's per-message ts in a future schema column; for now the limitation is documented and accepted for the trusted-team deployments Vibstr targets.

Self-confirm flow

When a Slack reply lands from someone whose email did not auto-match, the mapping is pending. Two paths resolve it:

  1. The Slack user themselves — if their Slack email matches their Vibstr profile email, an amber card appears in Settings → ACCOUNT → Notifications: "Confirm this Slack identity is you?" with Yes, confirm / Not me, reject buttons. RLS on vibelog_slack_user_map enforces LOWER(profile.email) = LOWER(slack_user_email) at the DB level so the JS gate alone is not the trust boundary.
  2. The project owner — pending mappings also surface in Settings → PROJECT → Slack with a member-picker dropdown. The owner picks the Vibstr profile and confirms.

Once confirmed, future Slack replies from that user attribute correctly.

The via enum substrate

vibelog_item_comments has a via column with the CHECK constraint:

via TEXT NOT NULL DEFAULT 'app' CHECK (via IN ('app', 'api', 'slack', 'discord', 'teams', 'email'))

This is the extensibility point. Phase 10 wires Slack. Discord, Teams, and email connectors land in Phase 15+ — each gets its own Edge Function reading the same vibelog_item_comments table from the JS client. The drawer treats every non-app comment as external (no edit button, accent left border, channel glyph, permalink chip when set). Adding a new channel does not require touching the comment renderer.

What is NOT in v1

Cost and audit

Slack message posts cost zero AI credits — chat.postMessage is a deterministic API call. The Events API webhook is also deterministic. The only AI-using surface near the Slack bridge is the per-item AI sidebar (Phase 9) which honours your daily AI cap.

Every workspace install, revoke, thread post, reply mirror, edit, delete, and user-map confirm/reject writes an immutable row to vibelog_admin_audit_log. The payload carries team_id, channel_name, thread_ts, item_number, and the actor's UUID — never the bot token, never the message body, never the Slack signing secret. chris-only SELECT RLS on the audit log applies the same way it does for every other Vibstr admin event.

Deep dive