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)
- Open Settings → PROJECT → Slack.
- 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 ``
- 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. - 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.
- If your channel is private or new, type
/invite @Vibstrin 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:
- 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
- The
chat.postMessageresponse gives Vibstr athread_ts. We write a row tovibelog_item_slack_threadsmapping(item_id, workspace_id) → (channel_id, thread_ts, permalink)so future replies route to the right item. - A small Slack chip appears in the drawer body showing reply count + permalink + a click-to-expand preview of the last 3 replies.
- The button disappears (idempotent — one thread per item per workspace).
Replies mirror back
A teammate replies in the Slack thread. What happens:
- Slack's Events API POSTs the
message.channelsevent to Vibstr's webhook atvibstr-slack-events. - The webhook verifies Slack's HMAC signature using
SLACK_SIGNING_SECRET(HMAC-SHA256 overv0:<timestamp>:<raw body>, constant-time compare). Replay protection rejects timestamps more than 5 minutes skewed. Signature failure → 401, never executes side effects. - 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). - Auto-map: if the Slack user's email matches a
vibelog_profiles.emailfor a profile that is also a project member, we attribute the new comment to that Vibstr user. Otherwise the comment lands withexternal_user_namepopulated anduser_idnull — a pending mapping is written for later confirm. - A
via='slack'row is inserted intovibelog_item_comments. Followers of the item get an inbox notification withpayload.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.
message_changedsubtype → the corresponding Vibstr comment'sbodyupdates andedited_atbumps. The chip's reply preview rows show an(edited)italic marker.message_deletedsubtype → the Vibstr comment row is deleted. Reply count drops by one.
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:
- 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_mapenforcesLOWER(profile.email) = LOWER(slack_user_email)at the DB level so the JS gate alone is not the trust boundary. - 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
- Bi-directional Vibstr → Slack edit mirror. Editing a comment in Vibstr does not push to Slack. The Slack thread is canonical for the conversation.
- Vibstr-side reactions on mirrored comments. Slack reactions on the thread do not mirror back as Vibstr reactions. The reaction model in
vibelog_item_commentsexists from Phase 3; layering Slack reaction sync is queued if user feedback demands. - Threading depth. Flat replies only. Slack thread replies-of-replies do not have a direct Vibstr representation.
- Voice and video. Not in scope ever per SPEC §18.7. Vibstr is not a Slack replacement.
- Attachment forwarding. Slack file uploads in the thread do not become Vibstr attachments. We post the description excerpt only.
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
- External automation — the Path A
vibstr-item-comment-createendpoint lets your own code post external comments without going through Slack. Use it if you have your own chat tool to bridge from. - Best practices — labels-as-stages, the plan-driven ship loop, anti-patterns.
- Per-item AI sidebar — once your team is discussing in Slack, the per-item AI already has the comment history as context.