Skip to content

Modernize: interactive-messages -> Bolt, Sentry 10, TS/CI + GHCR/Coolify deploy - #91

Open
ddbruce wants to merge 1 commit into
masterfrom
modernization
Open

Modernize: interactive-messages -> Bolt, Sentry 10, TS/CI + GHCR/Coolify deploy#91
ddbruce wants to merge 1 commit into
masterfrom
modernization

Conversation

@ddbruce

@ddbruce ddbruce commented Jul 24, 2026

Copy link
Copy Markdown
Member

Summary

Replaces Slack's deprecated @slack/interactive-messages adapter with @slack/bolt, and modernizes the rest of the stack. The /inorout poll behavior is preserved: inline + modal poll creation, voting, anonymous/multiple modes, and the reset/lock/move/delete options with author-only ephemeral guards. The vote/tally core (Poll.ts) is unchanged and is now unit-tested.

Verified: tsc compiles, 8 unit tests pass (vote tally, reset, single-vs-multiple, anonymous, author parsing), boots on Bolt 4 with /health → 200 and unsigned /slack/events → 401; amd64 image builds with 0 npm vulnerabilities.

⚠️ This is the migration I could not exercise against live Slack. Structural behavior is verified, but please smoke-test a real poll in staging (see "Verify in staging" below) before production.

Changes

  • SDK/routing: removed @slack/interactive-messages + express + body-parser; added @slack/bolt 4.x. @slack/web-api 6→7, dotenv 10→17. Vote buttons got stable action_ids (vote_<n>) and the options select got poll_options so Bolt can route them (Bolt matches by action_id, not element type); the modal view got callback_id: "poll_modal".
  • Sentry: 6 → 10; replaced the removed configureScope() with setUser()/setExtra().
  • TypeScript: target es5 → ES2022 (kept commonjs); ESLint (the broken 10.x + legacy .eslintrc) → ESLint 9 flat config with typescript-eslint 8; ts-jest tests added.
  • Container/CI: multi-stage Dockerfile (build tsc, ship out/ + prod deps only), node:24-alpine, non-root, HEALTHCHECK; dead Drone CI → GitHub Actions (lint + compile + test + amd64 build); scripts/deploy.sh + config.example.sh.

⚠️ Required Slack app config change

Bolt serves one endpoint. Point both of these at the same URL:

  • Slash Commands → Request URL: https://<host>/slack/events
  • Interactivity & Shortcuts → Request URL: https://<host>/slack/events

(Previously they were split: /slack/commands and /slack/actions.) No app re-creation needed. Env vars are unchanged (SLACK_ACCESS_TOKEN, SLACK_SIGNING_SECRET, PORT, SENTRY_DSN, ENVIRONMENT).

Verify in staging (behavior deltas)

  1. Create a poll both ways: /inorout (modal) and /inorout with option lines.
  2. Vote / change vote; try an anonymous and a multiple poll.
  3. Options select: reset, lock, move to bottom, delete (as author and as non-author).
  4. Note these intentional deltas from the old adapter:
    • The transient "Vote processing!" / "Processing request!" flash messages are gone (they were slowness workarounds; ack + message update is immediate now).
    • On a processing error, the user now gets an ephemeral error instead of the error text replacing the poll.
    • Existing in-flight polls posted before deploy (whose buttons have no action_id) won't route to the new handlers — recreate any active polls after deploying.

Recommended follow-ups (NOT done)

  • ModalMap is still an in-memory Map (open modals are lost on restart / not multi-instance safe).
  • Poll state is still encoded in the message and recovered by substring-matching the title — brittle but unchanged.

…10, tests

Migrate off Slack's deprecated @slack/interactive-messages adapter to
@slack/bolt while preserving the /inorout poll behavior (inline + modal poll
creation, voting, anon/multiple, reset/lock/move/delete, ephemeral author-only
guards). The Poll vote/tally core (Poll.ts) is unchanged.

SDK / routing:
- Remove @slack/interactive-messages + express + body-parser; add @slack/bolt
  4.x. Bolt serves a single endpoint (/slack/events) for the slash command AND
  interactivity, plus a custom /health route.
- @slack/web-api 6 -> 7, @slack/types current, dotenv 10 -> 17.
- Give poll vote buttons stable action_ids (vote_<n>) and the options select
  action_id "poll_options" so Bolt can route them (Bolt matches actions by
  action_id, not element type). Poll.ts parses buttons by element type, so vote
  tallying is unaffected. Add callback_id "poll_modal" to the modal view.

Sentry:
- @sentry/node 6 -> 10. Replace the removed configureScope() with
  Sentry.setUser()/setExtra() in Poll.ts.

TypeScript / tooling:
- tsconfig: target es5 -> ES2022, keep commonjs, add rootDir/skipLibCheck.
- ESLint (broken 10.x + legacy .eslintrc) -> ESLint 9 flat config with
  typescript-eslint 8. Add ts-jest tests for the vote/tally logic (8).
- Multi-stage Dockerfile (build tsc, ship out/ + prod deps), node:24-alpine,
  non-root, HEALTHCHECK. Replace dead Drone CI with GitHub Actions
  (lint + compile + test + amd64 build).
- Add scripts/deploy.sh + config.example.sh (amd64 -> GHCR -> Coolify redeploy).

Verified: tsc compiles, 8 unit tests pass, boots on Bolt 4 (/health 200,
unsigned /slack/events 401), amd64 image builds with 0 npm vulnerabilities.

Behavior deltas to verify in staging (documented in the PR): single unified
Slack Request URL, transient "processing" messages dropped, and error responses
are now ephemeral instead of replacing the poll.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ddbruce
ddbruce requested a review from lramos15 as a code owner July 24, 2026 17:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant