Message Shortcut Design Proposal — "Check against brand guide"
Date: 2026-04-22 Scope: Add a Slack message shortcut that lets any user right-click a message and get a brand-guide voice/terminology check in a modal.
Why
The Slack bot has four possible surfaces (slash commands · @mention · Home tab · message shortcuts). The first three are complete. This adds the fourth — and it's the one where the bot moves from "reference" to "active helper." Someone drafts a post in #marketing, right-clicks it, gets instant brand feedback without leaving Slack.
Rules-based v1 (no LLM needed, zero latency, zero cost). LLM-enhanced v2 when the dedicated Anthropic org lands.
Trigger flow
- User right-clicks any message in Slack → More actions → Check against brand guide.
- Slack POSTs a
message_actionpayload to/api/slack/interactivitywith the message text + atrigger_id. - Handler runs the rule engine against the text.
- Handler calls
views.openwith thetrigger_idto display the analysis modal (<3s window).
No response posted in-channel. The modal is private to the triggering user.
v1 rule set (pure deterministic)
Rules are sourced from the tenant's brand guide content — nothing is hardcoded to CF.
| Rule | Source | Check |
|---|---|---|
| Banned names | messaging.namingConventions entries where usage contains "never" |
Substring match each banned example against the input text. |
| Contractions | voice.writingRules rule named "Contractions" |
Detect non-contracted forms: \b(we|it|do|does|is|are|will|would|should|have|has)\s+(not|are|is|will|would)\b → suggest contracted form. |
| Numbers | voice.writingRules rule named "Numbers" |
Detect standalone digits \b[1-9]\b (not 10+) → flag "spell out one through nine." |
| Corporate jargon | voice.examples[].bad |
For each bad example, extract 3-5-word fingerprint phrases → fuzzy match (case-insensitive, whitespace-normalized) against input. |
Rules absent from the content (e.g. a tenant with no namingConventions) are silently skipped — no false "pass" messages.
Modal layout
[header] 🔎 Brand Check
[context] Against California Forever brand guide
[divider]
[section] Original message (quote style)
> [truncated to ~280 chars, ellipsis if longer]
[divider]
(for each rule in the fixed order above)
[section (pass)] ✓ {Rule name} — clean
OR
[section (fail)] ✗ {Rule name} — {count} issue{s}
[context] {first issue's detail + suggestion}
(if >1 issue: [context] {second detail}, etc. capped at 3)
[divider]
[section] Summary: {N of M rules passed} · {tight one-line takeaway}
[context] Full brand guide · <url|Voice & Tone →>
No action buttons on this view v1 — it's a one-shot analysis. If a user wants to iterate they edit the original message and re-trigger the shortcut.
Copy (voice of the bot)
Pass row: ✓ Naming — clean
Fail row: ✗ Naming — 1 issue (not "Error" / "Warning" — those read cold)
Fail detail: "Cal Forever" isn't an approved name. Use "California Forever" or "CF" in internal contexts only.
Summary examples:
- All pass: All 4 rules passed. On-brand.
- 3/4 pass: 3 of 4 rules passed. Review the naming flag.
- 1/4 pass: 1 of 4 rules passed. Strong revision needed.
Written in the brand's own voice — direct, no corporate hedging, no AI-sounding "I noticed..." preamble.
Technical plan
Manifest update:
- Add features.shortcuts entry:
json
{
"name": "Check against brand guide",
"type": "message",
"callback_id": "check_brand",
"description": "Get a brand-guide voice + terminology check on this message."
}
- Interactivity is already enabled (Phase 1).
- No new OAuth scopes needed — message shortcuts piggyback on the app's existing bot install.
Code structure:
- New lib/brand-check/rules.ts — pure functions: checkNaming(text, rules) → Issue[], same for contractions / numbers / jargon. No Slack knowledge, unit-testable.
- lib/brand-check/engine.ts — composes the rules, accepts the content subset it needs, returns { ruleName, status: "pass" | "fail", issues: Issue[] }[].
- lib/slack/format.ts — formatBrandCheckModal(result, opts) returns the view blocks.
- app/api/slack/interactivity/route.ts — currently a stub; extend to parse message_action payloads with callback_id === "check_brand", fetch the needed content categories (messaging + voice) in parallel, run the engine, call views.open via the bot client.
Fetching & caching: Every trigger fetches messaging + voice from the brand guide API. Both are already cached 60s by the existing fetchBrandCategory revalidate. Acceptable for v1.
Content loading: Only pull the categories we actually check. Don't fetch all 9 categories just to run 4 rules.
What this doesn't cover (v2 scope)
- LLM-enhanced checks: tone spectrum, voice trait alignment, contextual appropriateness. Wait for Anthropic org.
- Severity levels (warning vs. error). v1 treats everything as "issue" or "clean".
- Suggested rewrites. v1 flags and explains; v2 offers in-place suggestions.
- "Reply with this fixed version" action. v2 with action buttons.
- Message actions that POST publicly (e.g. "Share this check with @so-and-so"). Not in scope.
- Checking mesasges with attachments / threads / files. Plain text only for v1.
Open questions
- Should the modal show a "full brand guide →" link for EACH failed rule (e.g. naming issues link to
/messaging, contraction issues link to/voice-and-tone)? Or just one footer link? Leaning: single footer link to/voice-and-tonesince all rules source from voice + messaging and voice is the thematic umbrella. Keep it tight. - Modal close behavior: default to "Close" button only, or also offer a "Recheck" button that re-runs after the user edits the original message? Slack's modal can't re-read the original message — would have to pre-populate a textarea for editing. Out of v1 scope.
- Error handling: rule engine throws on bad content (e.g. missing messaging.namingConventions structure). Wrap in try/catch and show a clean "Couldn't run the check — content format changed" message in the modal, fall back to whatever rules succeeded.
What happens next
- Approval on this doc.
- Manifest update: add message shortcut entry, push via chrome-devtools.
- Implement:
lib/brand-check/*, modal formatter, interactivity handler extension. - Smoke test: trigger on a test message in G&M Slack → verify modal renders + checks run.
- Iterate.
- Commit atomically, push, verify.
- Update
docs/SLACK-UX.mdwith the Message Shortcut surface + rule engine reference.