Skip to content

Polls in messages and server discovery/customization updates#184

Merged
acarlson33 merged 1 commit into
mainfrom
04-15-polls_in_messages_and_server_discovery_customization_updates
May 5, 2026
Merged

Polls in messages and server discovery/customization updates#184
acarlson33 merged 1 commit into
mainfrom
04-15-polls_in_messages_and_server_discovery_customization_updates

Conversation

@acarlson33

@acarlson33 acarlson33 commented Apr 19, 2026

Copy link
Copy Markdown
Owner

This pull request introduces several new features and improvements around server management, privacy controls, and poll infrastructure, as well as some dependency updates and expanded API documentation. The most significant changes include new server customization options, expanded API endpoints and documentation, poll support in the database setup, and improved test coverage for server admin controls.

Server management and privacy enhancements:

  • Added PATCH /api/servers/{serverId} endpoint to support updating server name, description, icon, banner, and visibility; only owners and members with manageServer permission can update these fields. [1] [2]
  • Introduced new server properties: iconFileId, bannerFileId, isPublic, and defaultOnSignup, with corresponding schema and API documentation updates. [1] [2] [3]
  • Documented and enforced privacy/customization behaviors for server discovery, joining, and signup default server logic.

API and documentation improvements:

  • Expanded OpenAPI documentation to include new endpoints (/api/servers/default-signup, /api/servers/{serverId} PATCH), new properties, and improved response schemas. [1] [2] [3]
  • Added error responses for private server join attempts and admin-only endpoints.

Database and setup scripts:

  • Added poll infrastructure to setup scripts, including polls and poll_votes collections, their fields, and indexes. [1] [2]
  • Updated servers collection to include new fields and indexes for isPublic and defaultOnSignup.

Testing and admin tooling:

  • Updated tests to cover new admin controls for setting the default signup server, including mocks and interaction tests for the admin server management page. [1] [2] [3] [4] [5]

Dependency updates:

  • Upgraded several dependencies for both production and development, including appwrite, nanoid, newrelic, posthog-js, react-virtuoso, @typescript-eslint, baseline-browser-mapping, happy-dom, and prettier. [1] [2] [3]

@appwrite

appwrite Bot commented Apr 19, 2026

Copy link
Copy Markdown

Firepit

Project ID: 68b230a0002245833242

Sites (1)
Site Status Logs Preview QR
 firepit
68eed9c6001f50d8f260
Queued Queued View Logs Preview URL QR Code

Tip

Function builds can take up to 45 minutes before timing out

@coderabbitai

coderabbitai Bot commented Apr 19, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@acarlson33 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 23 minutes and 50 seconds before requesting another review.

To continue reviewing without waiting, purchase usage credits in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2d8f53e6-3750-4be6-b711-e1b62a25c593

📥 Commits

Reviewing files that changed from the base of the PR and between f0efe51 and 93bcfef.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock and included by bun.lock
📒 Files selected for processing (53)
  • docs/SERVER_ADMINISTRATION.md
  • docs/openapi-doc.yml
  • package.json
  • scripts/setup-appwrite.ts
  • src/__tests__/admin-server-management.test.tsx
  • src/__tests__/api-routes/message-poll-close.test.ts
  • src/__tests__/api-routes/message-poll-votes.test.ts
  • src/__tests__/api-routes/messages.test.ts
  • src/__tests__/api-routes/server-route-settings.test.ts
  • src/__tests__/api-routes/servers-create-route.test.ts
  • src/__tests__/api-routes/servers-default-signup.test.ts
  • src/__tests__/api-routes/servers-join.test.ts
  • src/__tests__/api-routes/servers-public.test.ts
  • src/__tests__/api-routes/servers.test.ts
  • src/__tests__/appwrite-messages-enriched.test.ts
  • src/__tests__/chat-hooks-useMessages.test.ts
  • src/__tests__/chat-page.test.tsx
  • src/__tests__/components/message-poll.test.tsx
  • src/__tests__/login-security.test.ts
  • src/__tests__/polls.test.ts
  • src/app/(auth)/login/actions.ts
  • src/app/admin/server-actions.ts
  • src/app/admin/server-management.tsx
  • src/app/api/messages/[messageId]/poll-votes/route.ts
  • src/app/api/messages/[messageId]/poll/close/route.ts
  • src/app/api/messages/route.ts
  • src/app/api/servers/[serverId]/route.ts
  • src/app/api/servers/create/route.ts
  • src/app/api/servers/default-signup/route.ts
  • src/app/api/servers/join/route.ts
  • src/app/api/servers/public/route.ts
  • src/app/api/servers/route.ts
  • src/app/chat/components/ServerBrowser.tsx
  • src/app/chat/hooks/useChatSurfaceController.ts
  • src/app/chat/hooks/useMessages.ts
  • src/app/chat/page.tsx
  • src/components/chat-pinned-messages-content.tsx
  • src/components/chat-surface-message-item.tsx
  • src/components/chat-surface-panel.tsx
  • src/components/chat-thread-content.tsx
  • src/components/create-server-dialog.tsx
  • src/components/message-poll.tsx
  • src/components/server-admin-panel.tsx
  • src/components/virtualized-message-list.tsx
  • src/lib/appwrite-core.ts
  • src/lib/appwrite-messages-enriched.ts
  • src/lib/appwrite-polls.ts
  • src/lib/appwrite-servers.ts
  • src/lib/chat-surface.ts
  • src/lib/polls-server.ts
  • src/lib/polls.ts
  • src/lib/server-metadata.ts
  • src/lib/types.ts
📝 Walkthrough

Walkthrough

This PR introduces a message polling feature with voting and closure workflows, and simultaneously adds server-level customization controls: visibility flags, descriptions, branding assets, and a defaultOnSignup mechanism for admin-designated signup servers, alongside related UI enhancements across admin and discovery interfaces.

Changes

Message Polls

Layer / File(s) Summary
Data Shape & Persistence
scripts/setup-appwrite.ts, src/lib/types.ts, src/lib/appwrite-core.ts
New polls and poll_votes Appwrite collections with poll documents (question, options, status, metadata) and vote records (pollId, userId, optionId). EnvConfig.collections extended to reference these IDs. Message and DirectMessage types gain optional poll?: MessagePoll field.
Poll Utility Helpers
src/lib/polls.ts, src/lib/polls-server.ts
Command parsing (parsePollCommand) detects /poll "Q" | "A"... syntax; option serialization/deserialization via JSON; buildMessagePoll constructs poll state from documents and votes. Server-side fetchers (getPollDocumentByMessageId, listVotesForPoll, getPollStateForMessage) retrieve and normalize poll data from Appwrite.
Message Creation & Poll API Routes
src/app/api/messages/route.ts, src/app/api/messages/[messageId]/poll-votes/route.ts, src/app/api/messages/[messageId]/poll/close/route.ts
Messages route detects poll commands and creates poll documents alongside message documents; persists poll as question text. New POST /api/messages/[messageId]/poll-votes for voting (upserts user vote, returns updated poll state). New POST /api/messages/[messageId]/poll/close for closing (auth/permission checks, updates status/metadata, returns poll state).
Message Enrichment
src/lib/appwrite-messages-enriched.ts, src/lib/appwrite-polls.ts
Enrichment pipeline now includes enrichMessagesWithPolls after profile enrichment, fetching related polls and vote records and attaching poll objects to message payloads.
Chat Surface & Hooks
src/lib/chat-surface.ts, src/app/chat/hooks/useMessages.ts, src/app/chat/hooks/useChatSurfaceController.ts
ChatSurfaceMessage now carries optional poll field. useMessages hook adds votePoll and closePoll callbacks with optimistic state updates and rollback on error; send now accepts optional textOverride for poll command injection. useChatSurfaceController maps poll handlers from ChatSurfaceMessage to raw message IDs.
UI Components & Page Integration
src/app/chat/page.tsx, src/components/message-poll.tsx, src/components/chat-surface-message-item.tsx, src/components/chat-surface-panel.tsx, src/components/chat-thread-content.tsx, src/components/chat-pinned-messages-content.tsx, src/components/virtualized-message-list.tsx
New MessagePollBlock component renders poll UI (question, options with vote counts, close button). Message components (ChatSurfaceMessageItem, ChatThreadContent, pinned content) conditionally render polls and wire vote/close callbacks. Chat page adds poll dialog (question/option inputs), slash-command detection for /poll, and state management; callbacks propagated through component tree.
Tests
src/__tests__/api-routes/message-poll-votes.test.ts, src/__tests__/api-routes/message-poll-close.test.ts, src/__tests__/api-routes/messages.test.ts, src/__tests__/polls.test.ts, src/__tests__/chat-hooks-useMessages.test.ts, src/__tests__/chat-page.test.tsx, src/__tests__/components/message-poll.test.tsx, src/__tests__/appwrite-messages-enriched.test.ts
API route tests verify authentication, authorization, poll state mutations, and response payloads. Poll utility tests validate parsing, option serialization, and message-poll construction. Hook/component tests assert optimistic updates, error handling, and UI interactions.

Server Privacy, Customization & Admin Management

Layer / File(s) Summary
Data Shape & Normalization
scripts/setup-appwrite.ts, src/lib/types.ts, src/lib/server-metadata.ts
servers collection schema extended with description, iconFileId, bannerFileId, isPublic, defaultOnSignup fields plus corresponding indexes. Server and ServerPreview types include these fields. server-metadata.ts module adds normalization helpers (visibility defaults to true, description/file IDs validated) and mapServerDocument for consistent document-to-type mapping.
Environment & Configuration
src/lib/appwrite-core.ts, docs/openapi-doc.yml
EnvConfig extended to expose image bucket ID and provide mapServerDocument utility. OpenAPI spec updated with new Server/ServerPreview fields, pagination cursor, and new/updated endpoints.
API Routes: Server Discovery & Creation
src/app/api/servers/route.ts, src/app/api/servers/public/route.ts, src/app/api/servers/create/route.ts
GET /api/servers now requires auth, returns only user's joined servers with nextCursor pagination. GET /api/servers/public filters by isPublic flag and enriches with actual member counts. POST /api/servers/create accepts optional description, icon/banner file IDs, and isPublic; validates inputs and returns updated Server shape with image URLs.
API Routes: Join & Visibility
src/app/api/servers/join/route.ts
POST /api/servers/join now checks server visibility; returns 403 for private servers (requires invite link).
API Routes: Settings & Admin
src/app/api/servers/[serverId]/route.ts, src/app/api/servers/default-signup/route.ts
New PATCH /api/servers/{serverId} allows owners/managers to update name, description, visibility, icon/banner (with file cleanup), and defaultOnSignup (admin-only). New GET /api/servers/default-signup (admin-only) returns the designated default signup server.
Server Listing & Retrieval
src/lib/appwrite-servers.ts
listServers and listServersPage now use mapServerDocument for consistent shape. createServer accepts and persists description/icon/banner/visibility options.
Login & Auto-join
src/app/(auth)/login/actions.ts
autoJoinServerOnSignup now prefers defaultOnSignup server; falls back to single-server auto-join only if no default exists.
Admin Server Actions
src/app/admin/server-actions.ts
createChannelAction now requires ownership (not just moderator role). listServersAction includes defaultOnSignup flag. New setDefaultSignupServerAction clears other servers' flags and sets the specified server as default (or clears all if null).
UI: Server Admin Panel
src/components/server-admin-panel.tsx
Extended with settings tab for editable fields (name, description, public toggle, icon/banner uploads with preview/remove). Distinguishes owner/manager/admin roles. Persists changes via PATCH endpoint.
UI: Server Browser & Discovery
src/app/chat/components/ServerBrowser.tsx
Removed membershipEnabled gate. Refactored to load all public servers (excluding joined), add search/sort/refresh controls, display rich server cards with banners, avatars, member counts, "Recommended" badge, and created date. Join flow adjusted.
UI: Server Admin Management
src/app/admin/server-management.tsx
Added "Default Signup Server" selection card (admin-only) allowing admins to choose which server new users join by default. Loads server list with defaultOnSignup flag, saves via setDefaultSignupServerAction, and shows loading/error states.
UI: Chat Page Integration
src/app/chat/page.tsx
Added "Discover servers" button and dialog with lazy-loaded ServerBrowser. Wired permission checks for canManageServer to control admin panel access (owner or has permission). Updated ServerAdminPanel props to include server metadata and onServerUpdated callback for refresh.
Tests
src/__tests__/api-routes/servers.test.ts, src/__tests__/api-routes/servers-public.test.ts, src/__tests__/api-routes/servers-create-route.test.ts, src/__tests__/api-routes/servers-join.test.ts, src/__tests__/api-routes/server-route-settings.test.ts, src/__tests__/api-routes/servers-default-signup.test.ts, src/__tests__/admin-server-management.test.tsx, src/__tests__/login-security.test.ts
API tests verify auth, visibility filtering, creation/update validation, private-server rejection, admin-only restrictions, and auto-join logic. Admin UI tests assert server list loading, default selection/saving, and permission checks.

Dependency Updates

Layer / File(s) Summary
Package Versions
package.json
Minor version bumps for appwrite, nanoid, newrelic, posthog-js, react-virtuoso, @typescript-eslint/*, baseline-browser-mapping, prettier, and happy-dom.

Documentation

Layer / File(s) Summary
Admin Guide
docs/SERVER_ADMINISTRATION.md
Added "Current privacy/customization behavior" section documenting public/private visibility, defaultOnSignup mechanics (uniqueness, clearing, admin controls), and signup auto-join fallback logic.

Sequence Diagram

sequenceDiagram
    participant User
    participant Frontend as Chat Page
    participant Hook as useMessages Hook
    participant API as POST /api/messages/[id]/poll-votes
    participant Server as Backend
    participant DB as Appwrite DB

    User->>Frontend: Click poll option
    Frontend->>Hook: votePoll(messageId, optionId)
    Hook->>Hook: Apply optimistic vote state
    Frontend->>Frontend: Render updated poll UI
    
    Hook->>API: POST {optionId}
    activate API
    API->>Server: Authenticate session
    Server->>DB: List existing vote for user
    DB-->>Server: Vote document (or null)
    alt Existing Vote
        Server->>DB: Update vote optionId
    else New Vote
        Server->>DB: Create vote document
    end
    DB-->>Server: Updated/created vote
    Server->>DB: Fetch poll & votes
    DB-->>Server: Poll state
    Server->>Server: Build MessagePoll with counts
    API-->>Hook: {poll: updated poll}
    deactivate API
    
    Hook->>Hook: Replace poll state with server response
    Frontend->>Frontend: Render server poll state

    User->>Frontend: Network error occurs
    Hook->>Hook: Revert to previous poll state
    Frontend->>Frontend: Show error toast
    Frontend->>Frontend: Restore original UI
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~60 minutes

This PR spans two substantial, independent feature domains: polls (new API routes, DB schema, component hierarchy, state management) and server privacy/customization (new/updated endpoints, admin controls, UI panels, permissions). The changes involve dense logic across authentication, authorization, data normalization, optimistic updates, and multi-tier component wiring. While changes within each domain follow consistent patterns, the breadth across many files (80+ modified/new files) and diverse logic densities (from simple field additions to intricate optimistic sync and permission enforcement) requires careful, separate reasoning for each area. Homogeneous test additions and dependency bumps reduce total load modestly.

Possibly Related PRs


🐇 Polls hop into chats, servers choose defaults with care,
Admins sculpt their kingdoms in the branding atmosphere,
While votes cascade and forums discover with delight,
A rabbit grins—moderation made fresh, right? 🗳️✨

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 04-15-polls_in_messages_and_server_discovery_customization_updates
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch 04-15-polls_in_messages_and_server_discovery_customization_updates

acarlson33 commented Apr 19, 2026

Copy link
Copy Markdown
Owner Author

@acarlson33

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 2, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@acarlson33

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 2, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 21

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/app/api/messages/route.ts (1)

245-283: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Rollback the message if poll creation fails.

After the message insert succeeds, any failure while creating the poll leaves the plain question message stored but still returns 500. That creates orphaned poll messages and makes retries duplicate the content. This second write needs compensating cleanup or another atomic pattern.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/api/messages/route.ts` around lines 245 - 283, Wrap the poll-creation
block (the databases.createDocument call that creates pollDocument and the
subsequent buildMessagePoll logic) in a try/catch so that if any error occurs
you perform a compensating delete of the already-created message (use
databases.deleteDocument with env.databaseId, env.collections.messages and
String(res.$id) and the same permissions) before rethrowing or returning the
error; ensure you await the delete, handle failures of the delete minimally (log
or ignore) and keep references to parsedPoll, res.$id, databases.createDocument,
and buildMessagePoll to locate the code to modify.
src/components/chat-thread-content.tsx (1)

17-37: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve moderator close rights in the thread pane.

canClose only checks message.poll.createdBy === currentUserId here, so users who can close a poll from src/components/chat-surface-message-item.tsx via canManageMessages lose that action after opening the thread. This needs the same permission prop/predicate to keep poll-closing behavior consistent across views.

Also applies to: 76-98

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/chat-thread-content.tsx` around lines 17 - 37, The thread pane
currently derives canClose solely from message.poll.createdBy === currentUserId
which drops moderator/manager rights; add a permission prop (e.g.,
canManageMessages?: (message: ChatSurfaceMessage) => boolean) to
ChatThreadContentProps and use it when computing canClose (use
message.poll.createdBy === currentUserId || canManageMessages?.(message)).
Update any other canClose checks in this component (the other instances
referenced around the 76-98 block) to use the same combined predicate so
poll-closing behavior matches chat-surface-message-item's canManageMessages
logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/openapi-doc.yml`:
- Around line 879-882: The PATCH schema is missing admin-only documentation for
the defaultOnSignup field; update the PATCH operation's schema where the sibling
isPublic field and the defaultOnSignup field are defined to add a clear
description on defaultOnSignup stating it is "admin-only" (e.g., "Only instance
admins may modify this field") and also update the PATCH 403 response
description to mirror the GET's "(requires instance admin)" note so clients
understand why a PATCH updating defaultOnSignup would be rejected; locate the
defaultOnSignup field in the PATCH request body schema and the PATCH operation's
403 response to apply these changes.
- Around line 828-842: The response currently defines an inline schema for the
"server" property exposing only $id and name; replace that inline object schema
under the "server" property with a $ref to the shared schema (e.g.
"#/components/schemas/Server" or "#/components/schemas/ServerPreview") so the
endpoint returns the full server record and stays in sync; update the "server"
property's schema to use the $ref and remove the inline properties ($id, name)
to avoid duplication.

In `@src/__tests__/api-routes/messages.test.ts`:
- Around line 289-339: The test suite lacks coverage for the partial-failure
case where the first createDocument succeeds (message created) but the second
(poll creation) rejects; add a new test that uses mockGetServerSession and
mockCreateDocument.mockResolvedValueOnce(...) followed by
mockCreateDocument.mockRejectedValueOnce(new Error("Database write failed")),
then call POST(request) and assert the route's intended behavior: either it
returns 500 and triggers a rollback by calling the message deletion helper
(e.g., expect(mockDeleteDocument).toHaveBeenCalledWith("test-db",
"messages-collection", "msg-poll", expect.any(Array))) or, if the design returns
a partial success, assert the exact status and response shape; reference the
POST handler and the mockCreateDocument/mockDeleteDocument mocks to implement
the test and assertions.

In `@src/__tests__/chat-hooks-useMessages.test.ts`:
- Around line 720-889: Add tests that cover optimistic updates and error
rollbacks for useMessages.votePoll and useMessages.closePoll: simulate fetch
rejecting or returning ok: false (stub global fetch via vi.stubGlobal) and
assert that after initiating votePoll/closePoll the messages show the optimistic
intermediate state, then after the failed response the poll state
(options.voterIds and poll.status) reverts to the original initialPoll values;
include separate tests for votePoll rollback and closePoll rollback and reuse
the existing mock initialPoll, renderHook result, and fetchMock pattern to
verify both intermediate optimistic state and final reverted state.

In `@src/app/admin/server-actions.ts`:
- Around line 234-243: The loop over defaultsResponse.documents contains a
redundant boolean check for server.defaultOnSignup === true because the earlier
query already used Query.equal("defaultOnSignup", true); remove the unnecessary
if statement and directly call databases.updateDocument(...) for each server in
defaultsResponse.documents (referencing defaultsResponse.documents,
server.defaultOnSignup, and
databases.updateDocument/SERVERS_COLLECTION_ID/DATABASE_ID to locate the code).

In `@src/app/api/messages/`[messageId]/poll-votes/route.ts:
- Around line 86-125: The current read-then-create/update flow is racy; replace
it with a true upsert using a deterministic document id (e.g. const voteId =
`${poll.$id}:${user.$id}` or a stable hash) and perform conflict-aware writes
against env.collections.pollVotes: attempt databases.createDocument with
ID.unique replaced by the deterministic voteId and the vote payload (pollId,
userId, optionId, votedAt); if createDocument throws a "document already exists"
conflict, catch that error and call databases.updateDocument(env.databaseId,
env.collections.pollVotes, voteId, { optionId, votedAt }) to converge to a
single vote per (pollId, userId). Remove the initial databases.listDocuments
read and ensure you use the same voteId for both create and update paths.

In `@src/app/api/messages/`[messageId]/poll/close/route.ts:
- Around line 35-39: Wrap the call to databases.getDocument(messageId) in a
try/catch and explicitly handle a missing/invalid message: if getDocument
returns no result or throws a "not found" error, return a 404 response (with a
short message) instead of letting the exception bubble; for other unexpected
errors rethrow or return a 500. Concretely, update the code around
databases.getDocument(...) in the route handler so it checks for a
null/undefined message and catches provider errors indicating "not found" (map
those to 404), while preserving other errors for normal error handling.

In `@src/app/api/messages/route.ts`:
- Around line 131-137: The poll validation only rejects imageFileId and
attachments but not imageUrl, letting callers bypass the restriction; update the
conditional that checks creatingPoll to also include imageUrl (e.g., change the
guard that currently checks creatingPoll && (imageFileId || (attachments?.length
?? 0) > 0) to also check imageUrl) and return the same NextResponse.json error;
apply the same addition to the other analogous validation block that handles
attachments/image checks (the second creatingPoll check) so both places reject
imageUrl for poll messages.

In `@src/app/api/servers/`[serverId]/route.ts:
- Around line 177-199: The Query.limit(100) in the default-clear path can miss
records; when payload.defaultOnSignup === true use databases.listDocuments (the
call using Query.equal("defaultOnSignup", true)) in a paginated loop (or remove
the artificial limit) to fetch all matching documents rather than only 100,
iterating over existingDefaultServers.documents and calling
databases.updateDocument for each non-matching defaultServer.$id (skip when
defaultServer.$id === serverId) until no more pages remain; ensure you reference
payload.defaultOnSignup, databases.listDocuments, Query.limit,
existingDefaultServers.documents, databases.updateDocument, and serverId when
implementing pagination.
- Around line 187-198: The loop that clears other default servers is performing
sequential awaits on databases.updateDocument; change the implementation that
iterates existingDefaultServers.documents (where you check if defaultServer.$id
=== serverId) to collect each databases.updateDocument(...) call into an array
of promises and then await Promise.all(promises) so updates run in parallel;
keep the skip for the current serverId and preserve the same arguments
(env.databaseId, env.collections.servers, defaultServer.$id, { defaultOnSignup:
false }) when creating the promises.

In `@src/app/api/servers/default-signup/route.ts`:
- Around line 9-47: Wrap the entire GET handler body in a try/catch to ensure
any exceptions from getServerSession, getUserRoles, or databases.listDocuments
are caught and returned as structured JSON; inside catch return
NextResponse.json({ error: (error as Error).message || "Internal Server Error"
}, { status: 500 }); keep existing authorization checks and response shape
intact and reference the GET function, getServerSession, getUserRoles, and
databases.listDocuments when locating the code; also add the provided test to
servers-default-signup.test.ts that mocks
getServerSession/getUserRoles/listDocuments to throw and asserts a 500 response
with the error message.

In `@src/app/api/servers/route.ts`:
- Around line 37-41: The code calls databases.listDocuments with
env.collections.memberships without guarding for a missing membership
collection, which will throw; add an explicit check for the membership
collection ID (env.collections.memberships) in the route handler before calling
databases.listDocuments (where membershipsResponse is computed) and return a 400
error with the same message used in join/route.ts ("Memberships are not enabled
on this instance") when it's falsy; ensure the check is placed near the start of
the handler and references env.collections.memberships and the listDocuments
call so the path never reaches databases.listDocuments if memberships are
unconfigured.

In `@src/app/chat/components/ServerBrowser.tsx`:
- Around line 322-332: The inline backgroundImage uses server.bannerUrl directly
in ServerBrowser.tsx (the style block that sets backgroundImage from
server.bannerUrl), which risks CSS/URL injection; fix this by
validating/sanitizing banner URLs on the server API that returns
server.bannerUrl (e.g., enforce allowed schemes (https), allowed host/paths or a
storage ID, normalize/encode the URL, and reject or replace unsafe values), and
optionally update the client to only accept a known-safe token/ID or a
pre-signed URL field (instead of raw user input) before using it in the
backgroundImage style.
- Around line 130-134: The effect calls loadServers but doesn't include it in
the dependency array, which risks stale closures; refactor loadServers into a
stable memoized callback (wrap the loadServers function in useCallback and list
any state/props it reads as its dependencies) and then include that memoized
loadServers in the useEffect dependency array alongside canLoadServers, userId,
and joinedServerIdsKey (alternatively, if you prefer the ref pattern, store the
latest loadServers in a ref and call ref.current() from the effect). Ensure the
symbol names match: useCallback for loadServers and the useEffect that currently
depends on canLoadServers, userId, joinedServerIdsKey.

In `@src/app/chat/hooks/useMessages.ts`:
- Around line 954-1063: votePoll and closePoll only update the top-level
messages via replaceMessagePoll/setMessages, so polls in threads
(activeThreadParent and threadMessages) stay stale or cause early returns;
modify the poll update path to patch both places: after computing optimisticPoll
or receiving payload.poll, call replaceMessagePoll(messageId, poll) (or
setMessages mapping) and also update thread state by replacing the poll on
activeThreadParent if its $id matches messageId and mapping threadMessages
similarly (use setActiveThreadParent and setThreadMessages or the existing
thread-state setters in this hook); likewise on error restore both places to
previousPoll. Ensure all three callbacks reference the same messageId/poll logic
so thread parents and replies stay in sync with main messages.

In `@src/app/chat/page.tsx`:
- Around line 1276-1306: buildPollCommand currently only normalizes quotes but
leaves pipe characters which break the `/poll` parser; update the sanitization
to escape any '|' characters (e.g. replaceAll("|", "\\|") or use .replace(/\|/g,
'\\\\|')) for both pollQuestion and each entry in pollOptions before trimming
and validating, then use those escaped values when building the command (refer
to buildPollCommand, pollQuestion, pollOptions, sanitizedQuestion,
sanitizedOptions).

In `@src/components/chat-pinned-messages-content.tsx`:
- Around line 130-137: The pinned-polls UI always passes currentUserId={null}
into MessagePollBlock because ChatPinnedMessagesContentProps lacks a
currentUserId prop; add an optional currentUserId?: string to the
ChatPinnedMessagesContentProps type/interface and thread it through the
ChatPinnedMessagesContent component, then replace currentUserId={null} with
currentUserId={currentUserId ?? null} when rendering MessagePollBlock so the
viewer's identity is used to show their vote.

In `@src/components/create-server-dialog.tsx`:
- Around line 147-160: The Switch component controlling "Public discovery" is
missing an accessible label; add an id to the label text (the <p> containing
"Public discovery") and associate it with the Switch by passing aria-labelledby
(or aria-label) to the Switch component so screen readers hear the label; update
the JSX around the Switch (the Switch element using props checked={isPublic},
onCheckedChange={setIsPublic}, disabled={isCreating}) to include
aria-labelledby="..." that references the new id (or provide a descriptive
aria-label) and ensure the id is unique and matches the visible label.

In `@src/components/message-poll.tsx`:
- Around line 56-62: Currently vote() only disables the clicked option by
calling setSubmittingVote(optionId), allowing other options to be clicked while
the request is in flight; change the logic to block the whole poll during
submission by introducing or using a boolean submitting state (e.g.,
isSubmittingVote) or a sentinel value (e.g., setSubmittingVote('ALL')) so all
option buttons consult that state and disable when submitting is true/'ALL';
update vote(), the option button disabled checks, and the similar logic
referenced around the code at the second occurrence (lines ~149-150) to set
submitting on try, clear it in finally, and prevent any other vote calls until
cleared.
- Around line 30-40: The effect that calls setLocalPollState(null) on every poll
prop change causes successful local votes/closes to be wiped when the parent
re-renders; remove that effect and instead synchronize only when the poll
identity changes (e.g., useEffect that sets localPollState = poll only when
poll?.id differs or when localPollState is null), so localPollState persists
across parent rerenders until a truly new poll arrives; update the useEffect
referencing poll (or remove it) and keep pollState = localPollState ?? poll
as-is.

In `@src/lib/polls-server.ts`:
- Around line 15-66: The two duplicated normalization functions
normalizePollDocument and normalizePollVoteDocument should be extracted into a
shared module (e.g., create src/lib/polls.ts) and exported so both
src/lib/polls-server.ts and src/lib/appwrite-polls.ts can import them; move the
implementations verbatim into that file, export the functions, remove the local
copies from polls-server.ts and appwrite-polls.ts, and add imports for
normalizePollDocument and normalizePollVoteDocument where needed to ensure a
single source of truth.

---

Outside diff comments:
In `@src/app/api/messages/route.ts`:
- Around line 245-283: Wrap the poll-creation block (the
databases.createDocument call that creates pollDocument and the subsequent
buildMessagePoll logic) in a try/catch so that if any error occurs you perform a
compensating delete of the already-created message (use databases.deleteDocument
with env.databaseId, env.collections.messages and String(res.$id) and the same
permissions) before rethrowing or returning the error; ensure you await the
delete, handle failures of the delete minimally (log or ignore) and keep
references to parsedPoll, res.$id, databases.createDocument, and
buildMessagePoll to locate the code to modify.

In `@src/components/chat-thread-content.tsx`:
- Around line 17-37: The thread pane currently derives canClose solely from
message.poll.createdBy === currentUserId which drops moderator/manager rights;
add a permission prop (e.g., canManageMessages?: (message: ChatSurfaceMessage)
=> boolean) to ChatThreadContentProps and use it when computing canClose (use
message.poll.createdBy === currentUserId || canManageMessages?.(message)).
Update any other canClose checks in this component (the other instances
referenced around the 76-98 block) to use the same combined predicate so
poll-closing behavior matches chat-surface-message-item's canManageMessages
logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c70c0a96-4a84-471c-bb8f-c5d13337660c

📥 Commits

Reviewing files that changed from the base of the PR and between 8c1c998 and f0efe51.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock and included by bun.lock
📒 Files selected for processing (53)
  • docs/SERVER_ADMINISTRATION.md
  • docs/openapi-doc.yml
  • package.json
  • scripts/setup-appwrite.ts
  • src/__tests__/admin-server-management.test.tsx
  • src/__tests__/api-routes/message-poll-close.test.ts
  • src/__tests__/api-routes/message-poll-votes.test.ts
  • src/__tests__/api-routes/messages.test.ts
  • src/__tests__/api-routes/server-route-settings.test.ts
  • src/__tests__/api-routes/servers-create-route.test.ts
  • src/__tests__/api-routes/servers-default-signup.test.ts
  • src/__tests__/api-routes/servers-join.test.ts
  • src/__tests__/api-routes/servers-public.test.ts
  • src/__tests__/api-routes/servers.test.ts
  • src/__tests__/appwrite-messages-enriched.test.ts
  • src/__tests__/chat-hooks-useMessages.test.ts
  • src/__tests__/chat-page.test.tsx
  • src/__tests__/components/message-poll.test.tsx
  • src/__tests__/login-security.test.ts
  • src/__tests__/polls.test.ts
  • src/app/(auth)/login/actions.ts
  • src/app/admin/server-actions.ts
  • src/app/admin/server-management.tsx
  • src/app/api/messages/[messageId]/poll-votes/route.ts
  • src/app/api/messages/[messageId]/poll/close/route.ts
  • src/app/api/messages/route.ts
  • src/app/api/servers/[serverId]/route.ts
  • src/app/api/servers/create/route.ts
  • src/app/api/servers/default-signup/route.ts
  • src/app/api/servers/join/route.ts
  • src/app/api/servers/public/route.ts
  • src/app/api/servers/route.ts
  • src/app/chat/components/ServerBrowser.tsx
  • src/app/chat/hooks/useChatSurfaceController.ts
  • src/app/chat/hooks/useMessages.ts
  • src/app/chat/page.tsx
  • src/components/chat-pinned-messages-content.tsx
  • src/components/chat-surface-message-item.tsx
  • src/components/chat-surface-panel.tsx
  • src/components/chat-thread-content.tsx
  • src/components/create-server-dialog.tsx
  • src/components/message-poll.tsx
  • src/components/server-admin-panel.tsx
  • src/components/virtualized-message-list.tsx
  • src/lib/appwrite-core.ts
  • src/lib/appwrite-messages-enriched.ts
  • src/lib/appwrite-polls.ts
  • src/lib/appwrite-servers.ts
  • src/lib/chat-surface.ts
  • src/lib/polls-server.ts
  • src/lib/polls.ts
  • src/lib/server-metadata.ts
  • src/lib/types.ts

Comment thread docs/openapi-doc.yml
Comment thread docs/openapi-doc.yml
Comment thread src/__tests__/api-routes/messages.test.ts
Comment thread src/__tests__/chat-hooks-useMessages.test.ts
Comment thread src/app/admin/server-actions.ts
Comment thread src/components/chat-pinned-messages-content.tsx
Comment thread src/components/create-server-dialog.tsx
Comment thread src/components/message-poll.tsx
Comment thread src/components/message-poll.tsx
Comment thread src/lib/polls-server.ts
@acarlson33

Copy link
Copy Markdown
Owner Author

@coderabbitai resolve

@coderabbitai

coderabbitai Bot commented May 3, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Comments resolved and changes approved.

acarlson33 commented May 5, 2026

Copy link
Copy Markdown
Owner Author

Merge activity

  • May 5, 11:21 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • May 5, 11:24 PM UTC: Graphite rebased this pull request as part of a merge.
  • May 5, 11:25 PM UTC: @acarlson33 merged this pull request with Graphite.

@acarlson33 acarlson33 changed the base branch from 04-12-basic_markdown_support to graphite-base/184 May 5, 2026 23:22
@acarlson33 acarlson33 changed the base branch from graphite-base/184 to main May 5, 2026 23:23
@acarlson33 acarlson33 force-pushed the 04-15-polls_in_messages_and_server_discovery_customization_updates branch from f0efe51 to 93bcfef Compare May 5, 2026 23:24
@acarlson33 acarlson33 merged commit 67b0697 into main May 5, 2026
2 of 3 checks passed
@acarlson33 acarlson33 deleted the 04-15-polls_in_messages_and_server_discovery_customization_updates branch May 5, 2026 23:25
@coderabbitai coderabbitai Bot mentioned this pull request May 12, 2026
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.

1 participant