fix(messaging): unblock local dev, let members create channels, name DMs - #96
Merged
Conversation
Three separate blockers made every module render an empty shell in local development, which read as an application bug: - The api service exposed 3001 but published no host port, so the Next.js rewrite proxied /api/* to a dead localhost:3001. Publish it on loopback. - apps/web imports @vencore/config from a client component. The package barrel re-exports readConfig, which imports `fs`, so the browser bundle failed with "Module not found: Can't resolve 'fs'". Add a browser-safe ./theme subpath export and point both importers at it. Workspace packages must also be built before `next dev`; that is a runbook step rather than a code change.
Two defects confirmed against a running instance with a non-admin user. Members could not create a channel. POST /messaging/channels was gated on messaging:manage, which defaults to admin only, so every member got a 403 and the sidebar's "New channel" button could never succeed. Split channel creation into its own messaging:create_channel permission granted to admin and member, leaving rename/archive on the admin-only manage gate. Adding a permission to a ModuleDefinition only affects new workspaces, since seedWorkspaceRoles runs once when a workspace's Member role is created. Added a migration backfilling create_channel to every role that already holds messaging:send. DM channels are all stored as name='dm' with the display name expected to be built from participants, but GET /messaging/channels returned no members, so every DM rendered as the literal string "dm". The list endpoint now attaches members for dm/group_dm rows, and a shared channelDisplayName() helper derives the label from the other participants for the sidebar, channel header, and composer placeholder. Covers both with tests: permission gates asserted off the router, and channelDisplayName across regular channels, DMs, group DMs, and the not-yet-loaded and only-self fallbacks.
There was a problem hiding this comment.
Pull request overview
This PR restores local messaging usability and fixes two end-user messaging bugs by adjusting dev networking/bundling, refining messaging permissions, and improving DM naming in the UI/API.
Changes:
- Publish the API container port for local Next.js rewrites and add a browser-safe
@vencore/config/themeexport to avoid bundlingfsinto client code. - Introduce
messaging:create_channelpermission (member+admin) and backfill it for existing workspaces via migration; keepmessaging:manageadmin-only. - Attach DM members in the channels list API and add a shared
channelDisplayName()helper used across messaging UI, with tests.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| packages/modules/src/messaging/index.ts | Adds messaging:create_channel permission and documents intent. |
| packages/db/migrations/20260803_001_messaging_create_channel_permission.ts | Backfills messaging:create_channel for roles that can already send messages. |
| packages/config/src/theme.ts | Introduces browser-safe theming entrypoint for client imports. |
| packages/config/package.json | Adds exports map including ./theme subpath. |
| docker-compose.yml | Publishes API port on loopback for local dev web ↔ API connectivity. |
| apps/web/modules/shared/components/AppearanceControls.tsx | Switches client import to @vencore/config/theme to avoid fs bundling. |
| apps/web/modules/messaging/lib/dm-name.ts | Adds channelDisplayName() helper for DM/group DM naming. |
| apps/web/modules/messaging/lib/dm-name.test.ts | Adds unit tests for channelDisplayName(). |
| apps/web/modules/messaging/components/ChannelView.tsx | Uses channelDisplayName() for DM header and composer placeholder. |
| apps/web/modules/messaging/components/ChannelSidebar.tsx | Uses channelDisplayName() for DM rows; carries optional members. |
| apps/web/app/layout.tsx | Switches to @vencore/config/theme import to keep server/client boundaries clean. |
| apps/api/src/routes/messaging/channels.ts | Attaches members for DM/group DM rows in list endpoint; gates create on messaging:create_channel. |
| apps/api/src/tests/messaging-channels-permissions.test.ts | Adds router-level permission gate assertions for messaging routes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+23
to
+30
| const isDm = channel.type === 'dm' || channel.type === 'group_dm'; | ||
| if (!isDm) return channel.name; | ||
|
|
||
| const others = (channel.members ?? []).filter(m => m.user_id !== currentUserId); | ||
| if (others.length === 0) { | ||
| // Members not loaded yet, or a DM with only yourself left in it. | ||
| return channel.name === 'dm' ? 'Direct message' : channel.name; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context
Reported as "the messaging module has zero working features — private messages don't work and channel messages aren't visible."
The messaging module itself was largely fine. Most of the symptom was the local dev environment failing in a way that renders an empty page with no error. Two genuine bugs were found underneath, both confirmed against a running instance with a real non-admin user.
Environment fixes (
5a207f6)Three separate blockers made every module render an empty shell locally:
apipublished no host port. Compose mapped db (5432) and redis (6379) but leftapiasEXPOSE 3001on the internal network only. Next's rewrite proxies/api/*tohttp://localhost:3001, so every API call died at the proxy. Now published on loopback.@vencore/configpulledfsinto the browser bundle.AppearanceControls.tsxis a client component importing the package barrel, which re-exportsreadConfig→import * as fs from 'fs'. Build failed withModule not found: Can't resolve 'fs'. Added a browser-safe@vencore/config/themesubpath export and repointed both importers.next dev— runbook step, no code change. A stale pnpm store also needspnpm installfirst.Bug 1 — members could not create channels
POST /messaging/channelswas gated onmessaging:manage, which defaults to admin only. Every member hit403 FORBIDDEN, so the sidebar's "New channel" button could never succeed.Split channel creation into its own
messaging:create_channelpermission granted to admin and member. Rename/archive stay on the admin-onlymanagegate.Adding a permission to a
ModuleDefinitiononly reaches new workspaces —seedWorkspaceRolesruns once, when a workspace's Member role is created. Added a migration backfillingcreate_channelto every role already holdingmessaging:send. Roles withgrants_allare unaffected; they short-circuit to superuser.Bug 2 — every DM rendered as the literal string "dm"
DM channels are stored as
name='dm', with the display name meant to be built from participants — butGET /messaging/channelsreturned no members, so the frontend had nothing to build from. Every DM row in the sidebar looked identical.The list endpoint now attaches
membersfordm/group_dmrows only. A sharedchannelDisplayName()helper derives the label from the other participants, used by the sidebar row, channel header, and composer placeholder.Verification
Seeded a second workspace user and drove the running app as a non-admin:
POST /channels→ 201, channel appears in the sidebarTests: 13 new (router permission gates asserted off the router;
channelDisplayNameacross regular channels, DMs, group DMs, and the not-yet-loaded and only-self fallbacks). Full suites green — 405 API, 43 web, 17 modules. Type-checks clean.Base branch note
Targets
mainrather thandevelopmentdeliberately.origin/mainalready containsorigin/developmentand is 50 commits ahead.AppearanceControls.tsx,packages/config/src/palette.ts, andpresets.tsdo not exist ondevelopment, and thefsbug only manifests because of them — so this branch does not apply there.Known issue, not addressed here
The dev database's migration ledger is corrupted independently of this change:
kysely_migrationrecords20260722/20260724/20260725entries that don't exist inpackages/db/migrationson either branch, sopnpm db:migraterefuses to run. The new migration was validated by applying its SQL directly; it is idempotent and will no-op once the ledger is reconciled.Still open
Not fixed here:
typing.startbroadcasts workspace-wide instead of per-channel;GET /dmsis dead code; noisErrorbranch anywhere, so API failures render as empty UI with no feedback (this is what made the original report so hard to diagnose); the JWT is passed in the WebSocket query string where the cookie already works.