Skip to content

Send the first comment on a heading that has no chat yet #298

Description

@HMarzban

Problem

A document comment fails to send when its heading has no channel row yet. The chat creates that row only on some opens. It decides from client state, not from the server.

On 14ab7f9c1. Webapp paths are under apps/webapp/src/. Paths that start with packages/ are from the repo root.

  1. A heading chat gets its channel row when it first loads. useChannelMetadata calls upsertChannel only when workspaceSettings.channels has no entry for the heading id (components/chatroom/hooks/useChannelMetadata.ts:29-44). That map is client state: mode memory, the member flag, and channelInfo. It is not a record of channel rows.
  2. A comment start writes an entry before the chat loads. openHeadingChatroom with intent: 'comment' calls setCommentMessageMemory(headingId, …) (services/openHeadingChatroom.ts:137-145). Then it schedules the pane (:153). setMemory creates the entry when it is missing (stores/chat/workspaceSettingsStore.ts:93-102). So when the chat loads, the check passes and the create step is skipped. When the pane is already open on that heading, it only focuses the composer (services/openHeadingChatroom.ts:148-151). That chat has already run its load.
  3. Every load also writes an entry, even when the row is missing (stores/chat/bootstrapStore.ts:24-27). So after a create step that failed, a reopen of the same heading chat in the tab never runs it again.
  4. With no row, the metadata RPC returns one row with channel_info null (packages/supabase/scripts/10-functions.sql:94-113). AccessControl shows the composer when channelInfo is missing (components/chatroom/components/ChannelComposer/ChannelComposer.tsx:39).
  5. The comment insert then fails. The insert policy needs internal.can_read_channel(channel_id) (packages/supabase/scripts/13-RLS.sql:211-216). That check needs the channel row (packages/supabase/scripts/10-0-func-helpers.sql:143-162). The submit catch shows the error in a toast (components/chatroom/components/MessageComposer/hooks/useComposerSubmit.ts:172-179).

Steps to reproduce

  1. Sign in and open a pad. Add a new heading with a line of text under it. No user has opened its chat, so it has no channel row. Keep the chat closed.
  2. Reload the page, so the chat store is empty.
  3. Select text under that heading and choose Comment.
  4. Type first comment and press Enter.
  5. See an error toast. No comment is added. public.channels has no row with that heading id.
  6. Control: reload, open the same heading chat from the table of contents, then comment. The comment sends.

Acceptance criteria

  • A signed-in workspace member comments on a heading with no channel row. The comment sends on the first try, and the channel row exists after it.
  • The same holds if the chat of another heading is open when the comment starts.
  • After a create step that failed, the next open of that heading chat in the same tab runs the create step again.
  • In a production build, each heading chat open sends at most one channel create request. In dev, React StrictMode runs the load twice, so dev may send two.
  • A visitor who is not signed in sends no channel create request, as today.

Agent Brief

Category: bug
Summary: The chat must create a heading's channel row when the server reports it missing, whatever the client store holds.

Current behavior:
useChannelMetadata treats an entry in workspaceSettings.channels as proof that the channel row exists. A comment start writes that entry before the chat loads, so the create step is skipped. Every load writes an entry too, so a failed create step is never retried in the tab. The metadata RPC then returns no channel, the composer shows, and the comment insert fails on the messages insert policy.

Desired behavior:
The chat creates the channel row whenever the row is missing, as the server reports it. A comment on a heading with no chat sends on the first try, like a message after a table of contents open does today.

Key interfaces:

  • useChannelMetadata() — the create gate, and the load that runs the metadata RPC.
  • workspaceSettings.channels — per-channel client state. Its entries are not channel rows.
  • setCommentMessageMemory() and setMemory() — write an entry before any load.
  • bootstrapChannel() — writes an entry on every load, and channelInfo only when the row exists.
  • get_channel_aggregate_data — returns channel_info null when the channel row is missing.
  • upsertChannel() — an upsert with onConflict: 'id' and ignoreDuplicates: true. It returns no row when the row already exists.
  • openHeadingChatroom() with intent: 'comment'.
  • AccessControl — shows the composer when channelInfo is missing.

Out of scope

Notes

Use channel_info from the metadata RPC as the missing-row signal, as #292 also advises. The RPC already runs on every open. After the create step, run the metadata RPC again, and store the channelInfo it returns, so AccessControl gets the channel type. The create trigger already adds the creator as an admin member (packages/supabase/scripts/10-2-func-channels.sql:16-44). A join after that fails in the duplicate-member trigger (:345-371). So join only when the RPC still reports no membership. That happens when another user created the row first. Do not move mode memory out of workspaceSettings.channels to fix this.

The chatroom rules say authenticated writes assume the channel and member rows exist (apps/webapp/src/components/chatroom/CLAUDE.md:117). This defect breaks that assumption for comments.

A comment with an attachment fails sooner, at the upload. The upload policy needs a channel member row (packages/supabase/scripts/12-buckets.sql:179-189). A fix that creates the row during the load covers this too.

Issue #142 (closed 2024-12-19) reported a failed comment on a heading with no channel row. Then the RPC raised P0001, and the chat did not open. Today the RPC returns an empty row, so the chat opens and the send fails instead.

Evidence:

  • A scratch harness mounted the real useChannelMetadata in a DOM emulator, without React StrictMode. It used a fetch stub and an empty-room RPC reply. A plain open sent one POST /rest/v1/channels. After setCommentMessageMemory for the heading, the mount sent none, and channelInfo stayed null. When the first channel create request failed, a second mount sent no new one.
  • A reviewer's StrictMode copy of the harness sent two channel create requests for one open, as dev does.
  • On the local Supabase stack, as the authenticated role in a transaction that was rolled back, a comment insert into messages for a missing channel failed with new row violates row-level security policy for table "messages". In a reviewer's second rolled-back test, the same insert succeeded after the user created the channel row.
  • The write order in Problem item 2 is a code trace. The defect was not reproduced end to end in the running app.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    ChatRelated to chat featuresbugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions