Skip to content

Redesign FwLite comments panel with responsive layout - #2470

Open
hahn-kev-bot wants to merge 24 commits into
developfrom
comment-updates
Open

Redesign FwLite comments panel with responsive layout#2470
hahn-kev-bot wants to merge 24 commits into
developfrom
comment-updates

Conversation

@hahn-kev-bot

@hahn-kev-bot hahn-kev-bot commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Refactored CommentDialog.svelte into a modular entry-editor/comments/ component tree (CommentPanel, CommentThread, CommentItem, CommentReplyInput, CommentAuthorAvatar, CommentDialog)
  • Redesigned the comments panel with a responsive docked layout: full sidebar on large screens, snap-point drawer on mobile/tablet
  • Added is-extra-large.svelte.ts breakpoint hook and two new CSS breakpoints to drive the responsive layout

Screenshots

thread list (desktop)
browser-screenshot-localhost-mrx6y7i4

thread list (tablet)
browser-screenshot-localhost-mrx6ykux

thread list (mobile)
browser-screenshot-localhost-mrx6yqfu

thread open at default drawer stop
browser-screenshot-localhost-mrx6yx62

max drawer stop
browser-screenshot-localhost-mrx6z01g

minimum drawer stop
browser-screenshot-localhost-mrx6z615

start new thread (mobile)
browser-screenshot-localhost-mrx6zkup

start new thread (desktop)
browser-screenshot-localhost-mrx6zpvu

hahn-kev and others added 4 commits July 22, 2026 14:47
Split the monolithic dialog into focused components and keep reply/edit draft text local to avoid Svelte bind errors on new threads.

Co-authored-by: Cursor <cursoragent@cursor.com>
Collapse/expand threads, resolve controls, and mobile thread detail with always-visible reply inputs adapted per breakpoint.

Co-authored-by: Cursor <cursoragent@cursor.com>
Keep the entry scrollable behind a non-modal drawer, open threads in full detail below xl, work around vaul-svelte's Svelte 5 activeSnapPoint bug, and keep the reply input pinned to the visible drawer bottom.

Co-authored-by: Cursor <cursoragent@cursor.com>
- Adds ds-bundle with token + visual-reference system (not a React bundle)
- HTML preview cards for core primitives: button, badge, input, label, textarea, alert, card, multi-ws-field, app-shell, master-detail, typography, colors
- .design-sync/ config explaining the off-script Svelte approach
- Compiled Tailwind CSS and design token imports via styles.css
@github-actions github-actions Bot added the 💻 FW Lite issues related to the fw lite application, not miniLcm or crdt related label Jul 23, 2026
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7985156b-c890-4102-bd7c-3ca0afa5563e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Changes

Comments interface rework

Layer / File(s) Summary
Comment contracts and responsive primitives
frontend/viewer/src/css-breakpoints.ts, frontend/viewer/src/lib/hooks/*, frontend/viewer/src/lib/entry-editor/comments/types.ts, comment-author.ts, CommentAuthorAvatar.svelte, frontend/viewer/src/lib/components/ui/drawer/*
Adds comment view types, deterministic author styling, avatar rendering, an extra-large viewport query, and explicit drawer snap-point forwarding with snap restoration.
Comment interaction components
frontend/viewer/src/lib/entry-editor/comments/CommentItem.svelte, CommentReplyInput.svelte, CommentThread.svelte, CommentPanel.svelte
Adds comment display/editing, reply submission, thread resolution, responsive list/detail views, filtering, and mobile navigation.
Comment dialog orchestration
frontend/viewer/src/lib/entry-editor/comments/CommentDialog.svelte
Adds thread loading, create/reply/resolve/edit mutations, refresh handling, callback wiring, and responsive sidebar, drawer, and sheet containers.
Locale catalog updates
frontend/viewer/src/locales/*.po
Adds revised comment-thread and duplicate-check strings, updates component references, and removes obsolete dialog strings across supported locales.
Entry view import wiring
frontend/viewer/src/project/browse/EntryView.svelte
Reformats the existing CommentDialog import without changing runtime behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested reviewers: myieye

Poem

I’m a rabbit with comments to share,
Threads hop neatly from panel to chair.
Replies take flight, resolved flags glow,
Drawers snap softly as viewports grow.
New words bloom in every tongue—
Carrots for code, and fun well-spun!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly reflects the main change: a responsive redesign of the comments panel.
Description check ✅ Passed The description matches the refactor, responsive layout changes, and breakpoint additions in the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch comment-updates

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

- Disable vaul's default handle (it was hidden under the absolute panel)
- Render a custom handle div inside the panel instead
@myieye

myieye commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

It's interesting that new comments and replies have such different buttons 🤔

@hahn-kev hahn-kev changed the title Redesign FwLite comments panel with responsive layout and refactor history service Redesign FwLite comments panel with responsive layout Jul 23, 2026
@hahn-kev

Copy link
Copy Markdown
Collaborator

Yeah, I found that semi consistent between other apps interestingly enough.

@myieye

myieye commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

This is looking very good!

But, I do have a bunch of UX thoughts:

  • The new comment area looks kind of disabled. There's probably a better way to make it distinct than giving it such a gray background
image
  • Clicking "+ Add" should focus the text-area (both if the text-area is not yet visible AND if it is already visible)
  • "+ Add" is the only "add button" we have that isn't "+ <thing>". Maybe it should be "+ Comment".
  • Use "+ Add" to start a thread should probably just be replaced with a button that does what it's suggesting e.g. a "+ Comment" button
image
  • Edge case (?): Use "+ Add" to start a thread is shown even when commenting is not allowed (i.e. there's no "+ Add" button. I saw this on a local only project. But maybe it applied to the viewer role too?
  • Actually after using the dev-only read-only permission toggle the commenting UI seems unchanged. So, it probably needs to respect the user's role.
  • The Resolve and Unresolve buttons should probably have tooltips (preferably the fancy shadcn ones not just a title attribute)
  • On mobile there's the icon resolve-button in the list and a labeled resolve button when a single comment is open. Nice. Should the user be taken back to the list when they click that resolve button? Should comments become un-editable on a resolved thread? (just UI enforced?) It turns out that GDocs does this, but only for the initial comment in a thread 😆!
  • Do we track a timestamp for "Comment edited"? Probably not. Saving and exposing that would be a baby step towards transparency. E.g.
    • User one writes "This is my favorite word in the dictionary"
    • User two writes "Yes mine too"
    • User one edits their comment to "My elbow is shaped like a pineapple" 🍍🙃
    • User three thinks "You both have pineapple-shaped elbows?!"
  • I'd like the "add new comment" area to be decorated with my initials avatar and/or name. It's just nice to know who I'm posting as before hand 🤔. It would also make it more obvious that we have a weird bug. The UI thinks I'm Test Admin while the backend knows I'm Tim Haasdyk...Claude is currently investigating.
  • I'd love to see an indicator on the comment icon-button if there are unresolved comments

@argos-ci

argos-ci Bot commented Jul 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Argos notifications ↗︎

Build Status Details Updated (UTC)
default (Inspect) ✅ No changes detected - Jul 30, 2026, 8:01 AM
e2e (Inspect) ✅ No changes detected - Jul 30, 2026, 8:10 AM

@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: 4

🧹 Nitpick comments (7)
frontend/viewer/src/lib/entry-editor/comments/CommentDialog.svelte (1)

74-92: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Prefer Vaul's onAnimationEnd over a hardcoded setTimeout(…, 500).

Vaul's Root already clears the active snap point back to the first entry via its own timer tied to the close transition duration, and exposes an onAnimationEnd callback described as "Useful to revert any state changes for example." Duplicating that with a hand-tuned 500ms timeout risks racing Vaul's own internal reset if its transition duration ever changes, and the watch callback here doesn't return a cleanup function, so a pending timer can still fire after the drawer state changes again or the component is destroyed.

♻️ Proposed direction
-  watch(
-    () => open,
-    (isOpen) => {
-      if (isOpen) {
-        if (dockBottom) activeSnapPoint = defaultCommentSnap;
-        return;
-      }
-      showResolved = false;
-      addingComment = false;
-      newThreadText = '';
-      editingCommentId = undefined;
-      expandedThreadIds = new Set();
-      mobileThreadId = null;
-      // Vaul assigns snapPoints[0] after close; restore default for the next open.
-      window.setTimeout(() => {
-        if (!open) activeSnapPoint = defaultCommentSnap;
-      }, 500);
-    },
-  );
+  watch(
+    () => open,
+    (isOpen) => {
+      if (isOpen) {
+        if (dockBottom) activeSnapPoint = defaultCommentSnap;
+        return;
+      }
+      showResolved = false;
+      addingComment = false;
+      newThreadText = '';
+      editingCommentId = undefined;
+      expandedThreadIds = new Set();
+      mobileThreadId = null;
+    },
+  );
+
+  // Pass onAnimationEnd to Drawer.Root instead, e.g.:
+  // <Drawer.Root onAnimationEnd={(isOpen) => { if (!isOpen) activeSnapPoint = defaultCommentSnap; }} ...>

Please confirm vaul-svelte 1.0.0-next.7 exposes the same onAnimationEnd prop as core Vaul before applying.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/viewer/src/lib/entry-editor/comments/CommentDialog.svelte` around
lines 74 - 92, Confirm that vaul-svelte 1.0.0-next.7 exposes Root's
onAnimationEnd prop, then replace the hardcoded 500ms setTimeout in the
open-state watch with that callback to restore activeSnapPoint to
defaultCommentSnap after the close animation. Remove the timer and ensure the
callback only restores state when the drawer remains closed, preserving the
existing reset behavior.
frontend/viewer/src/lib/entry-editor/comments/types.ts (1)

4-7: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

ThreadView carries a redundant comments field alongside ICommentThread.comments. ICommentThread already has an optional comments array, and ThreadView re-wraps the same data in a sibling top-level field, giving two places that must stay in sync.

  • frontend/viewer/src/lib/entry-editor/comments/types.ts#L4-L7: drop the extra comments field and have consumers read thread.comments ?? [] directly, or make ThreadView just an alias/refinement of ICommentThread with comments required.
  • frontend/viewer/src/lib/entry-editor/comments/CommentDialog.svelte#L49-L58: stop copying thread.comments into a sibling comments field in the threadsResource mapper once the type is simplified.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/viewer/src/lib/entry-editor/comments/types.ts` around lines 4 - 7,
The ThreadView type duplicates ICommentThread.comments and the threadsResource
mapper copies that duplicate data. In
frontend/viewer/src/lib/entry-editor/comments/types.ts:4-7, remove the top-level
comments field or refine ICommentThread so comments is required; update
consumers to use thread.comments ?? [] as needed. In
frontend/viewer/src/lib/entry-editor/comments/CommentDialog.svelte:49-58, stop
adding a sibling comments property in the threadsResource mapper and rely on
thread.comments.
frontend/viewer/src/lib/entry-editor/comments/CommentReplyInput.svelte (1)

53-56: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Escape discards typed text.

cancel() clears value outright, so Escape destroys an in-progress reply with no undo. Consider blurring only (and clearing just when the field is already empty).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/viewer/src/lib/entry-editor/comments/CommentReplyInput.svelte`
around lines 53 - 56, Update the Escape-key handling in CommentReplyInput so it
blurs or exits the input without clearing an in-progress reply. Adjust the
cancel flow to clear the value only when the field is already empty, preserving
typed text when Escape is pressed.
frontend/viewer/src/lib/entry-editor/comments/CommentPanel.svelte (1)

134-164: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The {:else} "Sign in to add comments." branch is unreachable.

addingComment can only become true via startAdding, which is only reachable from the + Add button on Line 127 — itself gated on canComment. So the whole composer block is canComment-only and the inner {#if canComment} / {:else} is dead. Either drop the else branch (and the inner guard), or surface the sign-in prompt somewhere actually reachable when canComment is false.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/viewer/src/lib/entry-editor/comments/CommentPanel.svelte` around
lines 134 - 164, Remove the unreachable inner canComment guard and its “Sign in
to add comments.” else branch from the addingComment composer in
CommentPanel.svelte. Keep the composer rendered only when addingComment is true,
preserving the existing textarea, controls, and submission behavior.
frontend/viewer/src/lib/components/ui/drawer/drawer.svelte (2)

28-39: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Stale target can be re-applied on a rapid close/reopen.

The async chain captures target at open time, but only re-checks open (not identity of the open cycle) before restoring. If the drawer closes and reopens with a different activeSnapPoint before the two ticks settle, the stale value wins. A generation counter makes this deterministic.

♻️ Guard with an open-generation counter
+  let openGeneration = 0;
   watch(
     () => open,
     (isOpen) => {
       if (!isOpen || !snapPoints?.length || activeSnapPoint == null) return;
       const target = activeSnapPoint;
+      const generation = ++openGeneration;
       void tick().then(async () => {
         activeSnapPoint = null;
         await tick();
-        if (open) activeSnapPoint = target;
+        if (open && generation === openGeneration) activeSnapPoint = target;
       });
     },
   );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/viewer/src/lib/components/ui/drawer/drawer.svelte` around lines 28 -
39, Update the open-state watcher to track an incrementing open-generation
counter for each open cycle. Capture the current generation alongside target in
the tick chain, and only clear and restore activeSnapPoint when both open
remains true and the captured generation is still current, preventing stale
async work from reapplying an earlier snap point.

28-39: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the duplicated vaul snap-point workaround.

The same watch(open, …) / double-tick() block is copied verbatim into both drawer wrappers, so both the stale-target race and the eventual removal of the workaround (once huntabyte/vaul-svelte#129 is fixed) have to be handled twice.

  • frontend/viewer/src/lib/components/ui/drawer/drawer.svelte#L28-L39: move the block into a shared helper (e.g. useSnapPointWorkaround) that takes getters/setters for open, snapPoints, and activeSnapPoint, and include the open-generation guard.
  • frontend/viewer/src/lib/components/ui/drawer/drawer-nested.svelte#L16-L27: replace the copied block with a call to that shared helper.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/viewer/src/lib/components/ui/drawer/drawer.svelte` around lines 28 -
39, Extract the duplicated watch/tick workaround from
frontend/viewer/src/lib/components/ui/drawer/drawer.svelte lines 28-39 into a
shared useSnapPointWorkaround helper accepting getters/setters for open,
snapPoints, and activeSnapPoint, and add an open-generation guard to prevent
stale target updates. Replace the copied block in
frontend/viewer/src/lib/components/ui/drawer/drawer-nested.svelte lines 16-27
with a call to the shared helper; both sites should use the centralized
implementation.
frontend/viewer/src/lib/entry-editor/comments/CommentThread.svelte (1)

71-75: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use Lingui pluralization for reply counts.

replyCount === 1 ? $t1 reply : $t${replyCount} replies$ creates two separate message IDs, so locales with zero/few/many plural categories need a single pluralized key. Use Lingui’s plural macro: import plural, t from svelte-i18n-lingui and render {$plural(replyCount, { one: $t1 reply, other: $t${replyCount} replies, })}.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/viewer/src/lib/entry-editor/comments/CommentThread.svelte` around
lines 71 - 75, Update the reply-count rendering in CommentThread.svelte to use
Lingui pluralization instead of the replyCount ternary. Import the plural and t
macros from svelte-i18n-lingui, then render the count through plural(replyCount)
with one and other forms so all locale plural categories use a single pluralized
message.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/viewer/src/lib/entry-editor/comments/CommentItem.svelte`:
- Around line 32-36: Update the $effect in CommentItem.svelte to seed draftText
only when editing transitions from false to true, without reactively tracking
comment.text changes while editing. Preserve the existing comment.text value as
the initial draft and prevent refetches or other comment updates from
overwriting in-progress edits.

In `@frontend/viewer/src/lib/entry-editor/comments/CommentPanel.svelte`:
- Around line 226-231: Update the empty state around CommentPanel’s
visibleThreads condition to distinguish resolved-view/overall-empty results from
the “no open comments” case, and only show an add-comment action when canComment
is true. Replace the hardcoded “+ Add” prose with a real action button that
reuses the existing add-thread handler and shared button label, keeping it
consistent with the button rendered near line 128.

In `@frontend/viewer/src/lib/entry-editor/comments/CommentReplyInput.svelte`:
- Around line 27-34: Update submit() in CommentReplyInput so it exits
immediately when a submission is already in progress, using the existing saving
state before invoking onSubmit. Keep the current trimmed-text validation and
post-submit cleanup unchanged, ensuring repeated button or Ctrl/Cmd+Enter events
cannot trigger concurrent replies.

In `@frontend/viewer/src/lib/entry-editor/comments/CommentThread.svelte`:
- Around line 52-96: Restructure the header in CommentThread.svelte so the
expand/collapse toggle and resolve control are sibling elements rather than
nesting the resolve Button inside the toggle button. Preserve the existing
layout, toggle behavior via onToggle, resolve behavior via onResolve,
accessibility labels, and saving-disabled state; remove reliance on
stopPropagation for separating the actions.

---

Nitpick comments:
In `@frontend/viewer/src/lib/components/ui/drawer/drawer.svelte`:
- Around line 28-39: Update the open-state watcher to track an incrementing
open-generation counter for each open cycle. Capture the current generation
alongside target in the tick chain, and only clear and restore activeSnapPoint
when both open remains true and the captured generation is still current,
preventing stale async work from reapplying an earlier snap point.
- Around line 28-39: Extract the duplicated watch/tick workaround from
frontend/viewer/src/lib/components/ui/drawer/drawer.svelte lines 28-39 into a
shared useSnapPointWorkaround helper accepting getters/setters for open,
snapPoints, and activeSnapPoint, and add an open-generation guard to prevent
stale target updates. Replace the copied block in
frontend/viewer/src/lib/components/ui/drawer/drawer-nested.svelte lines 16-27
with a call to the shared helper; both sites should use the centralized
implementation.

In `@frontend/viewer/src/lib/entry-editor/comments/CommentDialog.svelte`:
- Around line 74-92: Confirm that vaul-svelte 1.0.0-next.7 exposes Root's
onAnimationEnd prop, then replace the hardcoded 500ms setTimeout in the
open-state watch with that callback to restore activeSnapPoint to
defaultCommentSnap after the close animation. Remove the timer and ensure the
callback only restores state when the drawer remains closed, preserving the
existing reset behavior.

In `@frontend/viewer/src/lib/entry-editor/comments/CommentPanel.svelte`:
- Around line 134-164: Remove the unreachable inner canComment guard and its
“Sign in to add comments.” else branch from the addingComment composer in
CommentPanel.svelte. Keep the composer rendered only when addingComment is true,
preserving the existing textarea, controls, and submission behavior.

In `@frontend/viewer/src/lib/entry-editor/comments/CommentReplyInput.svelte`:
- Around line 53-56: Update the Escape-key handling in CommentReplyInput so it
blurs or exits the input without clearing an in-progress reply. Adjust the
cancel flow to clear the value only when the field is already empty, preserving
typed text when Escape is pressed.

In `@frontend/viewer/src/lib/entry-editor/comments/CommentThread.svelte`:
- Around line 71-75: Update the reply-count rendering in CommentThread.svelte to
use Lingui pluralization instead of the replyCount ternary. Import the plural
and t macros from svelte-i18n-lingui, then render the count through
plural(replyCount) with one and other forms so all locale plural categories use
a single pluralized message.

In `@frontend/viewer/src/lib/entry-editor/comments/types.ts`:
- Around line 4-7: The ThreadView type duplicates ICommentThread.comments and
the threadsResource mapper copies that duplicate data. In
frontend/viewer/src/lib/entry-editor/comments/types.ts:4-7, remove the top-level
comments field or refine ICommentThread so comments is required; update
consumers to use thread.comments ?? [] as needed. In
frontend/viewer/src/lib/entry-editor/comments/CommentDialog.svelte:49-58, stop
adding a sibling comments property in the threadsResource mapper and rely on
thread.comments.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 425168f5-f1ae-4ee5-a4ec-9d2863f50257

📥 Commits

Reviewing files that changed from the base of the PR and between cfec4d2 and e32cf01.

📒 Files selected for processing (22)
  • frontend/viewer/src/css-breakpoints.ts
  • frontend/viewer/src/lib/components/ui/drawer/drawer-nested.svelte
  • frontend/viewer/src/lib/components/ui/drawer/drawer.svelte
  • frontend/viewer/src/lib/entry-editor/CommentDialog.svelte
  • frontend/viewer/src/lib/entry-editor/comments/CommentAuthorAvatar.svelte
  • frontend/viewer/src/lib/entry-editor/comments/CommentDialog.svelte
  • frontend/viewer/src/lib/entry-editor/comments/CommentItem.svelte
  • frontend/viewer/src/lib/entry-editor/comments/CommentPanel.svelte
  • frontend/viewer/src/lib/entry-editor/comments/CommentReplyInput.svelte
  • frontend/viewer/src/lib/entry-editor/comments/CommentThread.svelte
  • frontend/viewer/src/lib/entry-editor/comments/comment-author.ts
  • frontend/viewer/src/lib/entry-editor/comments/types.ts
  • frontend/viewer/src/lib/hooks/is-extra-large.svelte.ts
  • frontend/viewer/src/locales/en.po
  • frontend/viewer/src/locales/es.po
  • frontend/viewer/src/locales/fr.po
  • frontend/viewer/src/locales/id.po
  • frontend/viewer/src/locales/ko.po
  • frontend/viewer/src/locales/ms.po
  • frontend/viewer/src/locales/sw.po
  • frontend/viewer/src/locales/vi.po
  • frontend/viewer/src/project/browse/EntryView.svelte
💤 Files with no reviewable changes (1)
  • frontend/viewer/src/lib/entry-editor/CommentDialog.svelte

Comment on lines +32 to +36
$effect(() => {
if (editing) {
draftText = comment.text;
}
});

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

In-progress edits get clobbered when the comment reloads.

The effect reads comment.text reactively, so it re-runs on any change to the comment text — not just when editing flips to true. CommentDialog refetches threads after every mutation (reply, resolve, another user's edit), so a user typing in this textarea can have their draft silently reset. Seed the draft only on the edit transition.

🐛 Seed the draft without tracking `comment.text`
-  import {t} from 'svelte-i18n-lingui';
+  import {untrack} from 'svelte';
+  import {t} from 'svelte-i18n-lingui';
   $effect(() => {
     if (editing) {
-      draftText = comment.text;
+      untrack(() => {
+        draftText = comment.text;
+      });
     }
   });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$effect(() => {
if (editing) {
draftText = comment.text;
}
});
import {untrack} from 'svelte';
import {t} from 'svelte-i18n-lingui';
$effect(() => {
if (editing) {
untrack(() => {
draftText = comment.text;
});
}
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/viewer/src/lib/entry-editor/comments/CommentItem.svelte` around
lines 32 - 36, Update the $effect in CommentItem.svelte to seed draftText only
when editing transitions from false to true, without reactively tracking
comment.text changes while editing. Preserve the existing comment.text value as
the initial draft and prevent refetches or other comment updates from
overwriting in-progress edits.

Comment on lines +226 to +231
{:else if visibleThreads.length === 0}
<div class="pt-8 text-center text-[13px] leading-relaxed text-muted-foreground">
{$t`No open comments`}
<br />
<span class="text-xs">{$t`Use "+ Add" to start a thread`}</span>
</div>

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Empty state is wrong in two reachable cases.

  • With showResolved true and zero threads overall, "No open comments" is misleading.
  • When canComment is false the + Add button isn't rendered, yet the hint still instructs the user to use it.

Also, "+ Add" is hardcoded inside this sentence and duplicated from the button label on Line 128, so the two must be kept in sync in every locale — the PR feedback already asks to rename that button. Prefer rendering an actual action button here instead of embedding the label in prose.

♻️ Condition the empty state and drop the embedded label
       {:else if visibleThreads.length === 0}
         <div class="pt-8 text-center text-[13px] leading-relaxed text-muted-foreground">
-          {$t`No open comments`}
-          <br />
-          <span class="text-xs">{$t`Use "+ Add" to start a thread`}</span>
+          {threadViews.length === 0 ? $t`No comments yet` : $t`No open comments`}
+          {`#if` canComment}
+            <div class="mt-2">
+              <Button size="sm" onclick={startAdding}>{$t`Add a comment`}</Button>
+            </div>
+          {/if}
         </div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{:else if visibleThreads.length === 0}
<div class="pt-8 text-center text-[13px] leading-relaxed text-muted-foreground">
{$t`No open comments`}
<br />
<span class="text-xs">{$t`Use "+ Add" to start a thread`}</span>
</div>
{:else if visibleThreads.length === 0}
<div class="pt-8 text-center text-[13px] leading-relaxed text-muted-foreground">
{threadViews.length === 0 ? $t`No comments yet` : $t`No open comments`}
{`#if` canComment}
<div class="mt-2">
<Button size="sm" onclick={startAdding}>{$t`Add a comment`}</Button>
</div>
{/if}
</div>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/viewer/src/lib/entry-editor/comments/CommentPanel.svelte` around
lines 226 - 231, Update the empty state around CommentPanel’s visibleThreads
condition to distinguish resolved-view/overall-empty results from the “no open
comments” case, and only show an add-comment action when canComment is true.
Replace the hardcoded “+ Add” prose with a real action button that reuses the
existing add-thread handler and shared button label, keeping it consistent with
the button rendered near line 128.

Comment on lines +27 to +34
async function submit(): Promise<void> {
const text = value.trim();
if (!text) return;
await onSubmit(text);
value = '';
focused = false;
textareaEl?.blur();
}

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.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

submit() has no re-entrancy guard.

canSend disables the button once the parent flips saving, but submit() itself doesn't check saving, and Ctrl/Cmd+Enter bypasses the button entirely. Holding the shortcut or double-firing before the parent updates can post duplicate replies.

🐛 Guard against concurrent submits
   async function submit(): Promise<void> {
     const text = value.trim();
-    if (!text) return;
+    if (!text || saving) return;
     await onSubmit(text);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async function submit(): Promise<void> {
const text = value.trim();
if (!text) return;
await onSubmit(text);
value = '';
focused = false;
textareaEl?.blur();
}
async function submit(): Promise<void> {
const text = value.trim();
if (!text || saving) return;
await onSubmit(text);
value = '';
focused = false;
textareaEl?.blur();
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/viewer/src/lib/entry-editor/comments/CommentReplyInput.svelte`
around lines 27 - 34, Update submit() in CommentReplyInput so it exits
immediately when a submission is already in progress, using the existing saving
state before invoking onSubmit. Keep the current trimmed-text validation and
post-submit cleanup unchanged, ensuring repeated button or Ctrl/Cmd+Enter events
cannot trigger concurrent replies.

Comment thread frontend/viewer/src/lib/entry-editor/comments/CommentThread.svelte Outdated
@hahn-kev

Copy link
Copy Markdown
Collaborator

Worked on comments today
Unread indicator:
image
Shows some unread comments
image

Other stuff:
image

things to notice:

  • ➕Conversation
  • not disabled looking start conversation box
  • avatar for logged in user making comment
  • (edited) what you can't see is that clicking (edited) show the history view for that comment 😍
  • resolved is now at the end of the thread list, there wasn't room in the header anymore for the filter toggle.

autofocus on the new conversation box and on the comment input on mobile, maybe we should do it on desktop too?

hahn-kev and others added 2 commits July 30, 2026 13:23
- Read drawer snapPoints from restProps so the optional prop flows
  through the spread (fixes svelte-check non-optional type mismatch)
- Drop unused Button import in CommentDialog
- void the optional onThreadOpen promise calls in CommentPanel
- Use SvelteSet for the mutated expanded-threads copy
- Remove unnecessary non-null assertions in comment-author

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…green-dd1c6a

# Conflicts:
#	frontend/viewer/src/lib/layout/DevToolsDialog.svelte
hahn-kev and others added 3 commits July 30, 2026 13:48
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Hosted runners no longer permit `sudo timedatectl set-timezone`
("Failed to set time zone: Access denied"), which failed the FW Lite
build deterministically. Set TZ=America/New_York on the dotnet test
step instead; .NET honours TZ for TimeZoneInfo.Local on Linux, so the
non-UTC/DST/negative-offset intent from #2092 is preserved without
privileges and scoped to the tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@myieye myieye left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

When I add a reply, start a new thread, resolve a thread etc, it re-renders the whole list of threads which is quite dissatisfying.
I suppose the expand/collapse arrow on threads shouldn't really be visible on mobile, where that's not the behaviour.
New threads should probably be expanded after creation.

I'm seeing lots of these warning logs:
https://svelte.dev/e/derived_inert
Image

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I might try to merge this with the colouring in the activity later.
But, it's totally fine for now.

}
}

async function resolveThread(threadView: ThreadView): Promise<void> {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Maybe not the best name, considering this sometimes unresolves.

msgid "{num, plural, one {# new FieldWorks Lite commit} other {# new FieldWorks Lite commits}}"
msgstr "{num, plural, one {# new FieldWorks Lite commit} other {# new FieldWorks Lite commits}}"

#. Button revealing the rest of a truncated possible-duplicates list in the New Entry dialog; # is the number of hidden matches

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Devin noticed that lots of translator context is being lost here. task i18n should preserve it. It seems to get wedged sometimes. You probably need to revert your changes and start fresh or something.

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

Labels

💻 FW Lite issues related to the fw lite application, not miniLcm or crdt related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants