Comments
This area covers 33 features. Watch the walkthrough, then use the reference below — each feature links to the exact moment it appears (▶).
Who this is for: Any collaborator. Each feature below lists the role/permission it requires.
Features
Post comment on cell ▶ 00:00
As a translator, I want to post a comment on a specific cell, so that I can ask questions or provide feedback about that cell's translation.
How it works. User hovers over a cell row in the editor, clicks the 'Add comment' button, types text in the CommentsDrawer textarea, and clicks 'Post'. The comment appears immediately in the drawer (optimistic), and is persisted to the server via comment.create event.
Key files
src/components/CommentsDrawer.tsx:49-59src/hooks/useComments.ts:178-237src/components/ProjectWorkspace.tsx:1067-1073
Submit comment via keyboard shortcut ▶ 00:00
As a translator, I want to submit a comment by pressing Ctrl+Enter (or Cmd+Enter on Mac), so that I can quickly post without using the mouse.
How it works. User types text in the comment textarea and presses Ctrl+Enter (or Cmd+Enter). The comment is submitted and the textarea clears.
Key files
src/components/CommentThread.tsx:67-72src/components/CommentsDrawer.tsx:61-66
View comment thread ▶ 00:00
As a translator, I want to see all comments posted on a cell organized as a thread, so that I can understand the full conversation context.
How it works. When CommentsDrawer opens for a cell, all comments (root + replies) are displayed in order. Each comment shows author, timestamp, and body text (with markdown formatting: **bold**, *italic*, `code`, @mentions). Resolved threads show a 'resolved' badge.
Key files
src/components/CommentThread.tsx:84-169src/lib/comments/comment-helpers.ts:20-28
Reply to comment ▶ 00:06
As a translator, I want to reply to an existing comment, so that I can engage in a conversation about a specific cell.
How it works. User opens the CommentsDrawer and sees the root comment. Below it is a 'Reply...' textarea and a 'Reply' button (disabled until text is entered). Typing text and clicking 'Reply' posts the reply (parentCommentId set to root comment's ID) and clears the textarea.
Key files
src/components/CommentThread.tsx:128-159src/hooks/useComments.ts:75-82src/components/ProjectWorkspace.tsx:1075-1082
Close thread with reply ▶ 00:00
As a translator, I want to post a reply AND resolve the thread in a single action, so that I can provide a solution and mark the issue as closed.
How it works. User fills the reply textarea and clicks 'Close with reply'. Both the reply is posted (with parentCommentId) AND the thread is marked resolved in one action. The reply text appears in the thread, and the Resolve button is replaced with Reopen.
Key files
src/components/CommentThread.tsx:147-150src/components/ProjectWorkspace.tsx:1084-1119
Resolve comment thread ▶ 00:06
As a translator or reviewer, I want to mark a comment thread as resolved, so that I can indicate that the issue has been addressed.
How it works. User clicks the 'Resolve' button on a thread. The thread immediately shows resolved state (badge, opacity change), and the button changes to 'Reopen'. The resolved flag is persisted via comment.resolve event.
Key files
src/components/CommentThread.tsx:152-156src/hooks/useComments.ts:289-310src/components/CommentsPage.tsx:513-520
Reopen resolved thread ▶ 00:00
As a translator, I want to reopen a resolved thread, so that I can re-engage with the conversation if needed.
How it works. User clicks 'Reopen' on a resolved thread. The thread returns to open state (badge removed, opacity reset) and the button changes back to 'Resolve'. The change is persisted via comment.resolve event with resolved=false.
Key files
src/components/CommentThread.tsx:163-165src/hooks/useComments.ts:289-310src/components/CommentsPage.tsx:513-520
Edit own comment ▶ 00:00
As a translator, I want to edit a comment I posted, so that I can correct typos or clarify my message.
How it works. User hovers over their own comment (in CommentsPage) and clicks the 'Edit' button in the kebab menu. An inline textarea appears with the current body. User types new text and clicks 'Save' (or presses Cmd/Ctrl+Enter), or 'Cancel' to discard. The edit is persisted via comment.edit event.
Key files
src/components/CommentsPage.tsx:329-346src/components/CommentsPage.tsx:403-423src/hooks/useComments.ts:239-262
Delete own comment ▶ 00:00
As a translator, I want to delete a comment I posted, so that I can remove comments I no longer want visible.
How it works. User hovers over their own comment (in CommentsPage) and clicks the 'Delete' button in the kebab menu. A confirmation dialog appears ('Delete comment? This action cannot be undone.'). User clicks 'Delete' to confirm or 'Cancel' to dismiss. The comment is soft-deleted (body cleared, deletedAt set) via comment.delete event.
Key files
src/components/CommentsPage.tsx:339-344src/components/CommentsPage.tsx:425-440src/hooks/useComments.ts:264-287
View all comments on project (CommentsPage) ▶ 00:00
As a project manager or reviewer, I want to view all comments across the entire project, so that I can get a holistic view of feedback and issues.
How it works. User navigates to /project/:id/comments. The page loads and displays all project-level, file-level, and cell-level comments grouped by thread. Comments are fetched via useComments hook (project-wide scope, no fileId/cellId filter). A header shows the total comment count.
Key files
src/components/CommentsPage.tsx:772-961src/hooks/useComments.ts:121-158
Search comments ▶ 00:00
As a user, I want to search comments by keyword, so that I can find relevant comments quickly.
How it works. User opens CommentsPage, types in the 'Search comments…' input. Threads matching the search term (case-insensitive substring in root body or any reply body) remain visible; non-matching threads are hidden. An active filter badge shows '1 filter'. Clearing the search restores all threads.
Key files
src/components/CommentsPage.tsx:620-628src/components/CommentsPage.tsx:84-120
Filter comments by file ▶ 00:00
As a reviewer, I want to filter comments by file, so that I can focus on feedback for a specific file.
How it works. User clicks 'Filters' to expand the filter panel. A 'File' select dropdown appears with options 'All files' + list of files that have comments. Selecting a file filters to show only threads from that file.
Key files
src/components/CommentsPage.tsx:674-699src/components/CommentsPage.tsx:84-120
Filter comments by author ▶ 00:00
As a reviewer, I want to filter comments by the person who posted them, so that I can focus on feedback from a specific team member.
How it works. User clicks 'Filters' to expand the filter panel. An 'Author' select dropdown appears with options 'Anyone' + list of users who have posted root comments. Selecting an author filters to show only threads where the root comment author matches.
Key files
src/components/CommentsPage.tsx:701-725src/components/CommentsPage.tsx:96-97
Filter comments by participant ▶ 00:00
As a reviewer, I want to see all threads where a specific person participated (either as root author or replier), so that I can track conversations involving that person.
How it works. User clicks 'Filters' to expand the filter panel. A 'Participant' select dropdown appears with options 'Anyone' + list of users who have posted comments or replies. Selecting a participant filters to show only threads where that user is either the root author OR replied.
Key files
src/components/CommentsPage.tsx:728-753src/components/CommentsPage.tsx:99-104
Toggle show resolved threads ▶ 00:00
As a reviewer, I want to toggle visibility of resolved threads, so that I can focus on open issues or review completed discussions.
How it works. User clicks 'Filters' to expand the filter panel. A 'Show resolved' checkbox appears, unchecked by default. Checking it shows resolved threads (in collapsed state); unchecking hides them. The badge count is unaffected but thread visibility changes immediately.
Key files
src/components/CommentsPage.tsx:664-672src/components/CommentsPage.tsx:89-91
Sort comments ▶ 00:00
As a reviewer, I want to change the sort order of comments, so that I can see the most relevant threads first.
How it works. User clicks 'Filters' to expand the filter panel. A 'Sort' select dropdown appears with options: 'Unresolved first' (default, unresolved threads first then by createdAt DESC), 'Most recent activity' (by updatedAt DESC), 'Newest first' (by createdAt DESC). Selecting an option re-sorts threads immediately.
Key files
src/components/CommentsPage.tsx:643-661src/components/CommentsPage.tsx:123-136
Expand/collapse filter panel ▶ 00:00
As a user, I want to expand or collapse the filter controls, so that I can focus on the comments list when not filtering.
How it works. User clicks the 'Filters' button (with SlidersHorizontal icon). The filter panel expands to show Sort, Show resolved, File, Author, Participant, and Reset controls. Clicking 'Filters' again collapses the panel.
Key files
src/components/CommentsPage.tsx:630-638src/components/CommentsPage.tsx:614-768
Reset all filters ▶ 00:00
As a user, I want to quickly clear all active filters, so that I can return to viewing all comments.
How it works. User clicks 'Filters' to expand the filter panel. A 'Reset' button appears at the bottom of the panel. Clicking it clears all filters (search, fileId, authorId, participant, showResolved) and resets sort to default ('unresolved-first').
Key files
src/components/CommentsPage.tsx:755-763src/components/CommentsPage.tsx:73-80
Refresh comment list ▶ 00:00
As a user, I want to manually refresh the comments list, so that I can see new comments from other users without waiting for automatic updates.
How it works. User clicks the 'Refresh' button (outline, sm, in the page header). The button shows a spinner while loading and is disabled. Comments are re-fetched from the server. After fetch completes, the button returns to normal state showing 'Refresh' text.
Key files
src/components/CommentsPage.tsx:876-878src/hooks/useComments.ts:121-158
Navigate to commented cell ▶ 00:00
As a reviewer, I want to navigate from the CommentsPage to the actual cell in the editor, so that I can see the cell context and respond inline.
How it works. On CommentsPage, for each cell-scoped comment thread, an 'Open file' button appears with an 'Go to cell in editor' tooltip. Clicking it navigates to /project/:id/file/:fileId?cellId=:cellId, opening the file in the editor and scrolling to the relevant cell.
Key files
src/components/CommentsPage.tsx:487-512src/components/CommentsPage.tsx:850-860
Back to project navigation ▶ 00:00
As a user, I want to navigate back to the project workspace from the CommentsPage, so that I can resume editing.
How it works. On CommentsPage, the header contains a 'Back to project' button (ghost, sm, ArrowLeft icon). Clicking it navigates to /project/:id.
Key files
src/components/CommentsPage.tsx:872-875
Translation stale indicator ▶ 00:00
As a reviewer, I want to see when a cell's translation has changed since a comment was created, so that I know to re-evaluate the comment's relevance.
How it works. In the cell-scoped CommentsDrawer, if a comment was created when the cell had an empty translation (createdForTranslated=''), and the cell now has a non-empty translation (currentTranslated !== ''), an amber 'stale' indicator with AlertTriangle icon appears next to the thread status badge. Tooltip says 'Translation changed since this thread was created'.
Key files
src/components/CommentThread.tsx:33,102-108
Comment mention typeahead ▶ 00:00
As a user, I want to @mention team members in comments, so that I can notify them about discussions.
How it works. User types '@' in a comment textarea followed by a prefix (e.g., '@al'). A typeahead dropdown appears showing matching users (from useUserSearch). User can click a user to insert '@username' (with trailing space) at the mention position. If less than 2 characters after '@', 'Type more to search…' message appears.
Key files
src/components/CommentsPage.tsx:187-291src/components/CommentsPage.tsx:538-551src/components/CommentsPage.tsx:584-591
Comment markdown rendering ▶ 00:00
As a user, I want to format comments with markdown, so that I can emphasize important parts and make comments more readable.
How it works. User types markdown syntax in the comment body: **text** renders as bold <b>, *text* renders as italic <i>, `text` renders as code <code>, newlines render as <br>, @mentions render as styled spans. HTML is escaped and sanitized (DOMPurify) to prevent injection.
Key files
src/lib/comments/comment-helpers.ts:20-28src/components/CommentThread.tsx:74-82src/components/CommentsPage.tsx:352-362
Cross-user comment visibility ▶ 00:00
As a project member, I want to see comments posted by other team members, so that I can collaborate on shared feedback.
How it works. When one user posts a comment, another user who is a project member can see it on the CommentsPage after refreshing (or via live sync if implemented). Comments are stored server-side in the D1 comments projection table and served to all project members.
Key files
src/hooks/useComments.ts:121-158src/lib/sync/comments-read.ts
Cross-user comment reply ▶ 00:00
As a project member, I want to reply to comments from other team members, so that I can contribute to ongoing discussions.
How it works. User opens the CommentsDrawer for a cell, sees a thread with another user's comment, fills the 'Reply...' textarea, clicks 'Reply', and the reply is posted with parentCommentId set to the root comment ID. Other users see the reply appear in the same thread.
Key files
src/hooks/useComments.ts:75-82src/components/CommentThread.tsx:128-145
Permission-based comment access ▶ 00:00
As a project owner, I want to control who can post, edit, and resolve comments based on project permissions, so that I can maintain discussion quality and privacy.
How it works. Users with canEditComments=true can post comments, edit/delete their own comments, and reply. Users with canResolveComments=true can resolve/reopen threads. Users without these permissions see a read-only state ('Read-only (imported from git)'). The CommentsDrawer and CommentsPage respect these flags.
Key files
src/components/CommentsDrawer.tsx:109-128src/hooks/useProjectPermissions.ts
Comment scope types ▶ 00:00
As a developer, I want to support comments at multiple levels (cell, file, project), so that feedback can be scoped appropriately.
How it works. Comments can be created with different scopes: 'cell' (fileId + cellId), 'file' (fileId only), or 'project' (no fileId/cellId). CommentsPage displays the scope label (e.g., 'Cell GEN 1:1 in Genesis.sfm', 'File Genesis.sfm', 'Project'). Server handles authorization per scope.
Key files
src/hooks/useComments.ts:56-60src/lib/sync/outbox-types.tssrc/components/CommentsPage.tsx:164-174
Optimistic comments ▶ 00:00
As a user, I want comments to appear immediately after I post, even if the network is slow, so that I have immediate feedback.
How it works. When user posts a comment, it appears in the UI immediately (optimistic update) with a client-generated commentId. As the comment.create event flushes and is processed by the server, the optimistic record is confirmed and remains visible (deduplicated). If a refresh happens before confirmation, the optimistic comment survives and is merged with server rows.
Key files
src/hooks/useComments.ts:99-119, 216-235src/hooks/useComments.ts:106-119
Soft-delete comments ▶ 00:00
As a user, I want to delete comments, while preserving the conversation history (audit trail), so that moderation can be tracked.
How it works. User deletes a comment. The comment record remains in the database with deletedAt set to current timestamp and body cleared to empty string. In the UI, '[deleted]' placeholder is shown instead of the body.
Key files
src/hooks/useComments.ts:264-287src/components/CommentsPage.tsx:349-362src/components/CommentThread.tsx:113-124
Comments drawer visibility toggle ▶ 00:00
As a translator, I want to open and close the comments drawer, so that I can focus on editing when not needed.
How it works. User hovers over a cell and clicks 'Add comment'. CommentsDrawer opens as an overlay (z-index, positioned right). Clicking the close button (X icon) in the drawer header closes it.
Key files
src/components/CommentsDrawer.tsx:68-76src/components/ProjectWorkspace.tsx:3736-3741
Draft persistence for reply text ▶ 00:00
As a user, I want my reply drafts saved locally, so that I don't lose text if I accidentally close the drawer.
How it works. User types text in the reply textarea. On every change, the text is persisted to localStorage with key format 'comment-draft:{projectId}:{cellId}:{threadId}'. If the user re-opens the drawer, the draft text is restored. If the reply is posted, the draft is cleared from storage.
Key files
src/components/CommentThread.tsx:23-45
Comment author and timestamp display ▶ 00:00
As a reviewer, I want to see who posted each comment and when, so that I can understand the context and history.
How it works. Each comment displays: author name (authorLabel or authorId), creation timestamp formatted as 'MMM DD, H:MM AP', and '(edited)' label if updatedAt !== createdAt. Format uses toLocaleString for locale-aware rendering.
Key files
src/components/CommentsPage.tsx:309-314src/components/CommentThread.tsx:115-117src/components/CommentsPage.tsx:140-151