Skip to content

fix(messaging): let members create channels, name DMs after participants - #97

Merged
Kavin-Charles merged 3 commits into
developmentfrom
fix/messaging-module-dev
Aug 3, 2026
Merged

fix(messaging): let members create channels, name DMs after participants#97
Kavin-Charles merged 3 commits into
developmentfrom
fix/messaging-module-dev

Conversation

@Kavin-Charles

Copy link
Copy Markdown
Collaborator

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 at all. Two genuine bugs were found underneath, both confirmed against a running instance with a real non-admin user.

This is the development-targeted counterpart to #96. See the note at the bottom on why the two PRs differ.

Bug 1 — members could not create channels

POST /messaging/channels was gated on messaging:manage, which defaults to admin only. Every member hit 403 FORBIDDEN, so the sidebar's "New channel" button could never succeed.

Split channel creation into its own messaging:create_channel permission granted to admin and member. Rename/archive stay on the admin-only manage gate.

Adding a permission to a ModuleDefinition only reaches new workspaces — seedWorkspaceRoles runs once, when a workspace's Member role is created. Added a migration backfilling create_channel to every role already holding messaging:send. Roles with grants_all are unaffected; they short-circuit to superuser and never consult role_permissions.

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 — but GET /messaging/channels returned no members, so the frontend had nothing to build from. Every DM row in the sidebar looked identical.

The list endpoint now attaches members for dm/group_dm rows only. A shared channelDisplayName() helper derives the label from the other participants, used by the sidebar row, channel header, and composer placeholder.

Dev environment

The api service exposed 3001 but published no host port, so a locally-run pnpm dev web server proxied /api/* to a dead localhost:3001. Every call failed at the proxy and the UI rendered an empty shell with no error — this is what made the original report look like a total module failure. Now published on loopback.

Verification

Seeded a second workspace user and drove the running app as a non-admin:

  • POST /channels201, channel appears in the sidebar
  • DM sidebar row, channel header, and composer placeholder all read "Admin" instead of "dm"
  • Channel history, sending, and realtime delivery over WebSocket all confirmed working

Test results — please read

13 new tests (6 API, 7 web), all passing. Web suite fully green: 43/43.

The API suite on this branch reports 25 failed / 323 passed. Those 25 failures are pre-existing on development and unrelated to this change — they are in contacts, pipeline-items, pipelines, projects, and soft-delete-filters. Measured against a clean origin/development checkout:

clean development this branch
API tests 25 failed / 317 passed (342) 25 failed / 323 passed (348)
Failing files 11 11

Delta is exactly +6 passing tests, no new failures. development's broken suite is worth a separate look.

Relationship to #96

#96 targets main and additionally contains a fix for @vencore/config leaking fs into the browser bundle. That fix does not apply here: AppearanceControls.tsx, packages/config/src/palette.ts, and presets.ts exist only on main, and the bug only manifests because of them.

origin/main already contains origin/development and is 50 commits ahead, so the branches have diverged. Merging both PRs is safe — the messaging commits are identical in content.

Known issue, not addressed here

The dev database's migration ledger is corrupted independently of this change: kysely_migration records 20260722/20260724/20260725 entries that do not exist in packages/db/migrations on either branch, so pnpm db:migrate refuses 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.start broadcasts workspace-wide instead of per-channel; GET /dms is dead code; no isError branch anywhere, so API failures render as empty UI with no feedback; the JWT is passed in the WebSocket query string where the cookie already works.

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.
The api service exposed 3001 but published no host port, so a locally-run
`pnpm dev` web server proxied /api/* to a dead localhost:3001 through the
Next.js rewrite. Every API call failed at the proxy and the UI rendered an
empty shell with no error, which reads as an application bug.

Publish on loopback only.
Copilot AI review requested due to automatic review settings August 3, 2026 05:17

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Not ready to approve

There are a few concrete correctness/rollback-safety issues (DM naming when current user id is not yet known, missing regression coverage for that case, and overly-broad down() deletion in the migration) that should be addressed before approval.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Pull request overview

This PR fixes two messaging bugs (member channel creation + DM naming) and unblocks local development by publishing the API service port so the web dev server can reach it via Next.js rewrites.

Changes:

  • Split channel creation into a new messaging:create_channel permission granted to members, while keeping admin-only management on messaging:manage.
  • Add DM/member data to the channels list response for dm/group_dm and introduce a shared channelDisplayName() helper for consistent DM naming across the UI.
  • Publish the API container’s port on loopback in docker-compose.yml to restore local /api/* proxying.
File summaries
File Description
packages/modules/src/messaging/index.ts Adds messaging:create_channel permission and documents the intent.
packages/db/migrations/20260803_001_messaging_create_channel_permission.ts Backfills the new permission to existing roles that already have messaging:send.
docker-compose.yml Publishes api on 127.0.0.1:3001 for local dev proxy/WS connectivity.
apps/web/modules/messaging/lib/dm-name.ts Introduces channelDisplayName() to derive DM/group DM display names from participants.
apps/web/modules/messaging/lib/dm-name.test.ts Adds unit tests for channelDisplayName().
apps/web/modules/messaging/components/ChannelView.tsx Uses channelDisplayName() in the header and composer placeholder.
apps/web/modules/messaging/components/ChannelSidebar.tsx Uses channelDisplayName() for DM rows and extends the row type to include members.
apps/api/src/routes/messaging/channels.ts Attaches DM members in the list endpoint and gates channel creation on messaging:create_channel.
apps/api/src/tests/messaging-channels-permissions.test.ts Adds route-gating and module permission definition tests for the new permission split.
Review details
  • Files reviewed: 9/9 changed files
  • Comments generated: 3
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Comment on lines +23 to +26
const isDm = channel.type === 'dm' || channel.type === 'group_dm';
if (!isDm) return channel.name;

const others = (channel.members ?? []).filter(m => m.user_id !== currentUserId);
Comment on lines +21 to +25
export async function down(db: Kysely<unknown>): Promise<void> {
await sql`
delete from role_permissions where permission = 'messaging:create_channel'
`.execute(db);
}
Comment on lines +45 to +47
it('falls back to a readable label when members are not loaded', () => {
expect(channelDisplayName({ name: 'dm', type: 'dm' }, ME)).toBe('Direct message');
});
The RBAC3 refactor dropped users.role, but tasks-unified.ts still read it, so
`tsc` failed and development did not compile:

  src/routes/tasks-unified.ts(88,40): error TS2339: Property 'role' does not
  exist on type '{ id: string; workspace_id: string; ... }'

isAdmin is already destructured from the authenticated request one line above
and was simply unused. This mirrors the fix already on main (ea4922f).

Unrelated to the messaging changes in this PR, but development's build, lint
and test jobs all fail without it.
@Kavin-Charles
Kavin-Charles merged commit 2619939 into development Aug 3, 2026
5 checks passed
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.

2 participants