Skip to content

Commit a580b0b

Browse files
tomlarkworthyclaude
authored andcommitted
Slack-Colibri bridge: frame slackRaw under com.feelingof.* as Colibri-churn insurance + consolidate upstream asks
The slackRaw lexicon already lived under com.feelingof.*; this commit makes the namespace boundary explicit and explains the three things it buys us: de-risks Colibri's active lexicon rework (feat/rework branch deletes v1's lexicons.ts wholesale and is staging a new shape), de-risks Colibri being abandoned, and avoids polluting social.colibri.* with bridge-specific concepts. Also pulls the scattered upstream-Colibri asks into a dedicated "Asks of Colibri" section. New entry: collapsed/nested thread rendering — Colibri is Discourse-flat in v1 and feat/rework still renders parent_message as a jump- link rather than a collapsed sub-thread, so the bridged Slack threads lose their visual grouping. Existing asks (displayAuthor, cross-repo channel ownership, quote facet, TID monotonicity, attachment shape) consolidated and sharpened. Reactions / attachments lines in Open questions updated to reflect the implementation that actually shipped. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 8bcce5a commit a580b0b

1 file changed

Lines changed: 21 additions & 2 deletions

File tree

pages/slack-colibri-bridge.md

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,14 @@ Edits and reactions update the same record (`putRecord`). v0.1 could split this
193193

194194
Slack file attachments referenced in `payload.files[]` are not blobbed in v0; their `url_private` is captured but the bytes stay on Slack. v0.1 fetches and re-uploads as atproto blobs, referencing them from the derived `social.colibri.message`.
195195

196+
#### Why this lives under `com.feelingof.*`, not `social.colibri.*`
197+
198+
`slackRaw` is the *FoC community's* archive of its own Slack history. Its lifetime, schema, and ownership belong to FoC — not to Colibri — and we want that boundary explicit in the lexicon namespace. Three practical consequences:
199+
200+
- **De-risks Colibri lexicon churn.** Colibri is actively reworking its lexicons on the `feat/rework` branch (the v1 `src/utils/atproto/lexicons.ts` is deleted there; a new `apps/website/src/utils/atproto/lexicons.ts` is in flight, along with new appview spec, streaming event types, and a refactored Message component tree). If a future Colibri version renames `social.colibri.message`, splits the facet model, or changes the channel/community ownership semantics that drove the constraints in this proposal, the `slackRaw` archive is untouched. We re-run the derivation against the new lexicons and republish — no Slack re-pull, no loss.
201+
- **De-risks Colibri disappearing.** If Colibri is abandoned, the FoC archive is still complete, public, and addressable on atproto. A different reader (or a static-site generator off `foc-server`-style infrastructure) can render it.
202+
- **Avoids polluting Colibri's namespace.** Bridge-specific concepts (Slack `ts`, `slack_user_id`, `subtype`) have no business inside `social.colibri.*`. Other Slack-on-Colibri bridges (different communities, different workspaces) would invent their own `com.<community>.bridge.slackRaw` analogues; that's the right shape.
203+
196204
### New: `com.feelingof.bridge.slackOrigin`
197205

198206
Provenance + dedupe authority. One-to-one with a `social.colibri.message`. `key: "any"` so we control the rkey:
@@ -349,13 +357,24 @@ Once `claimedDid` is set, future messages could be authored from the user's DID.
349357

350358
A claimed user can republish their own messages onto their own repo at any time without granting the bridge anything. The bot's repo is a public archive — pull the `slackOrigin` records matching their `slackUserId`, republish the corresponding messages from their own DID. We ship a small CLI. No trust delegation, full data ownership.
351359

360+
## Asks of Colibri
361+
362+
These are upstream changes the bridge benefits from but does not block on. Until they land, `slackRaw` preserves enough state to re-derive when they do.
363+
364+
- **Per-record author override** — optional `displayAuthor: { name, avatar? }` on `social.colibri.message` and `social.colibri.reaction`. Without it every bridged message and every aggregated reaction renders as the bot, with attribution hacked into the message text body as `@user: ` and reactions collapsed to a single "@bot reacted" entry per emoji. Single biggest UX win; unblocks proper reaction multi-reactor counts too.
365+
- **Collapsed / nested thread rendering** — Colibri's current UI is Discourse-flat (every reply is a top-level row referencing a `parent` rkey). Slack's threaded conversations don't survive the trip: a 30-reply thread on one Slack message becomes 30 sibling rows in the channel scroll. Inspected `feat/rework` (substantial monorepo + lexicon rewrite in flight) and the new Message component still renders flat with `parent_message` as a jump-link, not as a collapsed sub-thread. Worth raising as a v2 UX direction.
366+
- **Cross-repo channel ownership** — the appview hard-codes `community_uri = at://{channel_author}/social.colibri.community/{rkey}`. The bot cannot create channels in a community it does not own. Today's workaround is "community owner pre-creates channels"; cleaner is either a `communityRepo` field on `social.colibri.channel`, or a `social.colibri.delegation` record granting channel-creation to a specific DID.
367+
- **Quote facet feature** — Slack's `rich_text_quote` blocks render as `> `-prefixed plain text today because Colibri's facet feature set covers bold/italic/strikethrough/code/mention/link/channel but not quote.
368+
- **Confirm TID-on-rkey monotonicity expectations**`social.colibri.message` uses `key: "tid"`. `bsky.social` tolerates non-monotonic TIDs on rkeys (otherwise our backfill would 409 against live messages). PDSes that *do* enforce monotonicity would break the bridge. Worth a one-line "we don't require monotonic rkeys" assurance in the lexicon docs, or a switch to `key: "any"`.
369+
- **Attachment shape clarity** — examples / docs for `social.colibri.message.attachments[]` would unblock our v0.1 file-attachment work.
370+
352371
## Open questions
353372

354373
- Backfill from `dump-history.js` snapshot, or forward-only? All-channels backfill is significant volume.
355374
- Channel / category layout: single community with flat siblings, or map Slack groupings to Colibri categories? Sidecar is agnostic.
356375
- Private channels and DMs — out of scope. Bot joins public channels only.
357-
- Reactions, edits, deletes — v0 ignores in the *rendered* Colibri message but captures everything in `slackRaw`, so v0.1 can re-derive the visible message without re-pulling Slack. Edits: `putRecord` on both `slackRaw` and the message. Deletes: tombstone the message, retain the `slackRaw` for audit.
358-
- Slack file attachments — `payload.files[]` is preserved in `slackRaw` (including `url_private`) from v0; v0.1 fetches and re-uploads as atproto blobs.
376+
- Reactions, edits, deletes — v0 publishes one `social.colibri.reaction` per (target_message, emoji) on the bot's repo; multi-reactor counts are preserved losslessly in `slackRaw` and become recoverable once per-record author override lands. Edits: `putRecord` on both `slackRaw` and the message. Deletes: tombstone the message, retain the `slackRaw` for audit.
377+
- Slack file attachments — `payload.files[]` is preserved in `slackRaw` (including `url_private`) from v0; v0.1 fetches and re-uploads as atproto blobs. bsky.social blob size limits (~1 MB images, ~50 MB video) will force large attachments to external hosting or a more permissive PDS.
359378
- False DID claims. v1 unverified; v2 requires two-sided counter-claim.
360379
- OAuth re-auth UX (v2): frequency cap, fallback when user ignores the prompt.
361380

0 commit comments

Comments
 (0)