Skip to content

fix/ui fixes#160

Merged
matheusfillipe merged 15 commits into
mainfrom
fix/ui-fixes
Mar 13, 2026
Merged

fix/ui fixes#160
matheusfillipe merged 15 commits into
mainfrom
fix/ui-fixes

Conversation

@matheusfillipe
Copy link
Copy Markdown
Contributor

@matheusfillipe matheusfillipe commented Mar 12, 2026

  • 0.2.6
  • Show full nicknames
  • attempt to fix broken changelogs in releases
  • fix avatar, display name, metadata in PMs
  • add view profile in user settings modal
  • fix weird background in sent colored code blocks
  • small UI fixes
  • Multiple design and UI improvements
  • flip reply widget and typing indicator positions
  • Avoid reactions overlapping message hover buttons

Summary by CodeRabbit

  • New Features

    • Dedicated reply UI with close action, combined reactions+actions control, and automated release-note generation for publishes.
  • Bug Fixes

    • Consistent username handling across message displays, preserved IRC color around inline code, and improved wheel handling for reaction popovers.
  • UI/UX Improvements

    • Updated action icons/styling, collapsible message controls, toolbar-overlap avoidance, scroll-to-bottom animation, and input-area typing/reply layout tweaks.
  • Tests

    • Tests updated to use centralized collapsible-line limit and added header/IRC tests.
  • Chores

    • Package version bumped and new theme color token added.

};

const displayName = messageUser?.metadata?.["display-name"]?.value;
const username = message.userId.split("-")[0];
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@ValwareIRC I dont really know why we did this everywhere? So I am just removing...

@github-actions
Copy link
Copy Markdown

Pages Preview
Preview URL: https://fix-ui-fixes.obsidianirc.pages.dev

Automated deployment preview for the PR in the Cloudflare Pages.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 12, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ed0f9e55-456c-4ae6-a642-0a59132d7879

📥 Commits

Reviewing files that changed from the base of the PR and between 73017d9 and cc3d1f9.

📒 Files selected for processing (1)
  • src/components/ui/TextInput.tsx

📝 Walkthrough

Walkthrough

Replaces ReplyBadge with MessageReply, normalizes username handling to use full userId, consolidates reactions/actions into ReactionsWithActions, exports COLLAPSIBLE_MAX_LINES, updates styles/tests, and adds a GitHub Actions step to compute and propagate a dynamic release_body for publish jobs. (45 words)

Changes

Cohort / File(s) Summary
GitHub Actions Release Workflow
\.github/workflows/publish.yaml
Adds skip-guard job, grants contents: write, computes previous non-prerelease tag and generates release_body (fallback "Initial release"), exposes/propagates release_body to publish jobs, and derives make_latest from prerelease.
Version / Tooling
package.json, biome.json
Bumps package version 0.2.50.2.6 and updates Biome schema / devDependency to 2.4.6.
Message UI — username normalization
src/components/message/..., src/lib/eventGrouping.ts
Replaces userId.split("-")[0] with full message.userId across many components and event grouping (ActionMessage, EventMessage, InviteMessage, MessageAvatar, MessageHeader, WhisperMessage, eventGrouping).
Reply UI / Chat input
src/components/layout/ChatArea.tsx, src/components/message/MessageReply.tsx, src/components/ui/ReplyBadge.tsx
Removes ReplyBadge, embeds MessageReply with new optional onClose, clears reply state on context change, and adjusts input-area styling when replying.
Reactions + Actions composition
src/components/message/ReactionsWithActions.tsx, src/components/message/MessageItem.tsx, src/components/message/MessageReactions.tsx, src/components/message/MessageActions.tsx, src/components/message/ActionMessage.tsx, src/components/message/WhisperMessage.tsx
Adds ReactionsWithActions and exports it; consolidates reactions/actions rendering, adds onAddReaction, adjusts handlers/props, updates icons/styles, and adds hover/toolbar-avoidance logic.
CollapsibleMessage & tests
src/components/message/CollapsibleMessage.tsx, tests/components/CollapsibleMessage.test.tsx
Exports COLLAPSIBLE_MAX_LINES = 8, refactors layout/toggle placement, and updates tests to use the new constant.
UI components & styling
src/components/ui/..., src/index.css, tailwind.config.js
ReactionPopover blocks outer wheel scroll (capture), ScrollToBottomButton simplified, UserProfileModal gains optional onBack, UserSettings adds profile/back flow, index.css adds animations and toolbar-avoidance transforms, tailwind adds discord.reply color.
IRC color handling & tests
src/lib/ircUtils.tsx, tests/lib/ircMarkdown.test.ts
applyIrcColorsToHtml preserves inline code by skipping code segments for color spans; adds tests for IRC color handling with inline code.
Store & test updates
src/store/index.ts, tests/components/MessageHeader.test.tsx, tests/components/MetadataDisplay.test.tsx
Removed non-functional comments, added MessageHeader test asserting full userId rendering, and updated test fixtures to align with identifier changes.

Sequence Diagram(s)

sequenceDiagram
  participant Workflow as GitHub Actions Workflow
  participant Compute as Compute step (job)
  participant GitHubAPI as GitHub REST API
  participant Publish as Publish Jobs

  Workflow->>Compute: run compute previous release + generate notes
  Compute->>GitHubAPI: GET tags/releases (find previous non-prerelease)
  GitHubAPI-->>Compute: previous tag or none
  alt previous tag found
    Compute->>GitHubAPI: GET/POST generate-release-notes
    GitHubAPI-->>Compute: release notes
  else none
    Compute-->>Compute: use "Initial release"
  end
  Compute->>Workflow: set output `release_body`
  Workflow->>Publish: start publish jobs with `release_body` and make_latest derived from prerelease
  Publish->>GitHubAPI: publish release(s) using `release_body`
  GitHubAPI-->>Publish: publish response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • ValwareIRC

Poem

🐇 I nibble code lines, full userIds in view,
Replies now closable, reactions in two,
Buttons hop above toolbars, lines fold to eight,
Release notes roll outward — the workflow's first-rate,
A rabbit applauds: small hops, big review.

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'fix/ui fixes' is overly generic and vague, using non-descriptive terms that don't convey meaningful information about the changeset despite the PR addressing multiple significant changes. Use a more specific title that highlights a primary change, such as 'Show full nicknames in messages' or 'Fix UI issues and update message reply handling' instead of the generic 'fix/ui fixes'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/ui-fixes
📝 Coding Plan
  • Generate coding plan for human review comments

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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (3)
src/components/message/WhisperMessage.tsx (1)

116-126: Duplicate rendering logic for outgoing and incoming whispers.

Both branches of the ternary render identical content. This can be simplified.

♻️ Suggested simplification
           <span className="text-purple-300 font-normal">
-            {isOutgoing ? (
-              <>
-                from <span className="font-semibold">{sender}</span> to{" "}
-                <span className="font-semibold">{recipient}</span>
-              </>
-            ) : (
-              <>
-                from <span className="font-semibold">{sender}</span> to{" "}
-                <span className="font-semibold">{recipient}</span>
-              </>
-            )}
+            from <span className="font-semibold">{sender}</span> to{" "}
+            <span className="font-semibold">{recipient}</span>
           </span>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/message/WhisperMessage.tsx` around lines 116 - 126, The JSX in
WhisperMessage (component WhisperMessage) contains a redundant ternary using
isOutgoing that renders the same fragment for both branches; simplify by
removing the conditional and render the single fragment once (the from <span
className="font-semibold">{sender}</span> to <span
className="font-semibold">{recipient}</span>) so you keep sender and recipient
usage but eliminate the duplicated branches and any unused isOutgoing-specific
code.
src/components/ui/UserSettings.tsx (1)

1326-1326: Minor inconsistency in Save button labels.

Mobile view shows "Save" (line 1326) while desktop view shows "Save Changes" (line 1459) when there are unsaved changes. Consider aligning these for consistency.

✨ Optional: Align button labels
// Mobile (line 1326)
-                {hasUnsavedChanges ? "Save" : "No Changes"}
+                {hasUnsavedChanges ? "Save Changes" : "No Changes"}

Or shorten desktop to match mobile if space is a concern.

Also applies to: 1459-1459

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

In `@src/components/ui/UserSettings.tsx` at line 1326, The Save button label is
inconsistent between mobile and desktop in the UserSettings component: update
the conditional rendering that uses hasUnsavedChanges so both mobile and desktop
views display the same text (either "Save" or "Save Changes"); locate the JSX in
UserSettings.tsx where hasUnsavedChanges is used (the mobile instance around the
earlier conditional and the desktop instance around the later conditional) and
change one of them to match the other so the label is consistent across
breakpoints.
src/components/message/MessageItem.tsx (1)

442-446: Measure the actual toolbar bounds instead of hard-coding them.

The overlap detector assumes a single 90x32 bottom-right toolbar, but this component now renders MessageActions in multiple layouts and with an optional delete action. That makes the collision box drift from the real UI and can misclassify copy buttons. Prefer reading the rendered toolbar’s getBoundingClientRect() from a ref/data attribute.

Also applies to: 1060-1111

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

In `@src/components/message/MessageItem.tsx` around lines 442 - 446, The
hard-coded toolbar bounds (toolbarTop, toolbarLeft, toolbarRight, toolbarBottom)
based on a fixed 90x32 box are incorrect; instead obtain the actual toolbar DOM
rect from the rendered MessageActions element (use a ref or data-attribute and
call getBoundingClientRect()) and compute overlap against that rect and msgRect.
Update the overlap-detection logic where toolbarTop/Left/Right/Bottom are
computed (and the similar logic around lines 1060-1111) to read toolbarRect =
toolbarRef.current.getBoundingClientRect(), then use
toolbarRect.top/left/right/bottom for collision checks; ensure the ref is
attached to the MessageActions root and handle missing ref defensively (fallback
to previous msgRect-based calculation).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/message/CollapsibleMessage.tsx`:
- Around line 45-60: The effect in useLayoutEffect (which reads
contentRef.current.scrollHeight and updates setContentHeight,
setCollapsedMaxHeight, setNeedsCollapsing and calls onNeedsCollapsing) must
re-run when the rendered content changes; add the rendered content value (the
prop/state named content) to the dependency array alongside maxLines and
onNeedsCollapsing so the measurement is recalculated whenever content updates.

In `@src/components/message/MessageItem.tsx`:
- Around line 492-505: The PM and channel user lookups use strict equality
against message.userId which causes casing mismatches; update the privateChat
and channel?.users.find predicates to normalize both sides (e.g., compare
lowercased or otherwise normalized username and message.userId) similar to the
no-channel fallback so that privateChat, channel user resolution, and resulting
userKey no longer fall back to "none" due to case differences; ensure you change
the predicates used in server?.privateChats?.find and channel?.users.find (and
any user variable comparisons) to use the same normalization function.

In `@src/components/ui/ReactionPopover.tsx`:
- Around line 61-79: The wheel handler (handleWheel) currently calls
e.preventDefault() which blocks native scrolling and prevents the chat's
useScrollToBottom logic from detecting scrolls; change handleWheel to only
stopPropagation() when the event target is outside popoverRef.current (remove
e.preventDefault()), and update the wheel listener options to passive: true,
capture: true in the document.addEventListener and the matching
removeEventListener so the handler no longer prevents default scrolling while
still preventing the event from reaching parent bubble-phase listeners.

---

Nitpick comments:
In `@src/components/message/MessageItem.tsx`:
- Around line 442-446: The hard-coded toolbar bounds (toolbarTop, toolbarLeft,
toolbarRight, toolbarBottom) based on a fixed 90x32 box are incorrect; instead
obtain the actual toolbar DOM rect from the rendered MessageActions element (use
a ref or data-attribute and call getBoundingClientRect()) and compute overlap
against that rect and msgRect. Update the overlap-detection logic where
toolbarTop/Left/Right/Bottom are computed (and the similar logic around lines
1060-1111) to read toolbarRect = toolbarRef.current.getBoundingClientRect(),
then use toolbarRect.top/left/right/bottom for collision checks; ensure the ref
is attached to the MessageActions root and handle missing ref defensively
(fallback to previous msgRect-based calculation).

In `@src/components/message/WhisperMessage.tsx`:
- Around line 116-126: The JSX in WhisperMessage (component WhisperMessage)
contains a redundant ternary using isOutgoing that renders the same fragment for
both branches; simplify by removing the conditional and render the single
fragment once (the from <span className="font-semibold">{sender}</span> to <span
className="font-semibold">{recipient}</span>) so you keep sender and recipient
usage but eliminate the duplicated branches and any unused isOutgoing-specific
code.

In `@src/components/ui/UserSettings.tsx`:
- Line 1326: The Save button label is inconsistent between mobile and desktop in
the UserSettings component: update the conditional rendering that uses
hasUnsavedChanges so both mobile and desktop views display the same text (either
"Save" or "Save Changes"); locate the JSX in UserSettings.tsx where
hasUnsavedChanges is used (the mobile instance around the earlier conditional
and the desktop instance around the later conditional) and change one of them to
match the other so the label is consistent across breakpoints.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 02bcdf12-d50a-456d-86ae-4310e3665b69

📥 Commits

Reviewing files that changed from the base of the PR and between 6284c1c and f870b8a.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (29)
  • .github/workflows/publish.yaml
  • package.json
  • src/components/layout/ChatArea.tsx
  • src/components/message/ActionMessage.tsx
  • src/components/message/CollapsibleMessage.tsx
  • src/components/message/EventMessage.tsx
  • src/components/message/InviteMessage.tsx
  • src/components/message/MessageActions.tsx
  • src/components/message/MessageAvatar.tsx
  • src/components/message/MessageHeader.tsx
  • src/components/message/MessageItem.tsx
  • src/components/message/MessageReactions.tsx
  • src/components/message/MessageReply.tsx
  • src/components/message/SwipeableMessage.tsx
  • src/components/message/WhisperMessage.tsx
  • src/components/ui/ReactionPopover.tsx
  • src/components/ui/ReplyBadge.tsx
  • src/components/ui/ScrollToBottomButton.tsx
  • src/components/ui/UserProfileModal.tsx
  • src/components/ui/UserSettings.tsx
  • src/index.css
  • src/lib/eventGrouping.ts
  • src/lib/ircUtils.tsx
  • src/store/index.ts
  • tailwind.config.js
  • tests/components/CollapsibleMessage.test.tsx
  • tests/components/MessageHeader.test.tsx
  • tests/components/MetadataDisplay.test.tsx
  • tests/lib/ircMarkdown.test.ts
💤 Files with no reviewable changes (2)
  • src/components/ui/ReplyBadge.tsx
  • src/store/index.ts

Comment on lines +45 to +60
useLayoutEffect(() => {
if (!contentRef.current) return;

const element = contentRef.current;
const computedStyle = window.getComputedStyle(element);
const lineHeight = Number.parseFloat(computedStyle.lineHeight) || 16;
const maxHeight = lineHeight * maxLines;
const element = contentRef.current;
const computedStyle = window.getComputedStyle(element);
const lineHeight = Number.parseFloat(computedStyle.lineHeight) || 16;
const maxHeight = lineHeight * maxLines;

const fullHeight = element.scrollHeight;
setContentHeight(fullHeight);
setCollapsedMaxHeight(`${lineHeight * maxLines}px`);
const fullHeight = element.scrollHeight;
setContentHeight(fullHeight);
setCollapsedMaxHeight(`${lineHeight * maxLines}px`);

const needs = fullHeight > maxHeight;
setNeedsCollapsing(needs);
onNeedsCollapsing?.(needs);
}, [maxLines, onNeedsCollapsing]);
const needs = fullHeight > maxHeight;
setNeedsCollapsing(needs);
onNeedsCollapsing?.(needs);
}, [maxLines, onNeedsCollapsing]);
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.

⚠️ Potential issue | 🟡 Minor

Missing content in useLayoutEffect dependency array.

The effect measures contentRef.current.scrollHeight which depends on the rendered content. If content changes without maxLines or onNeedsCollapsing changing, the collapse state won't be recalculated.

🔧 Suggested fix
     }, [maxLines, onNeedsCollapsing]);
+    // Note: content is intentionally omitted to avoid re-measuring on every content change.
+    // The parent component should remount CollapsibleMessage if content changes significantly.

Or if content changes should trigger recalculation:

-    }, [maxLines, onNeedsCollapsing]);
+    }, [content, maxLines, onNeedsCollapsing]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/message/CollapsibleMessage.tsx` around lines 45 - 60, The
effect in useLayoutEffect (which reads contentRef.current.scrollHeight and
updates setContentHeight, setCollapsedMaxHeight, setNeedsCollapsing and calls
onNeedsCollapsing) must re-run when the rendered content changes; add the
rendered content value (the prop/state named content) to the dependency array
alongside maxLines and onNeedsCollapsing so the measurement is recalculated
whenever content updates.

Comment on lines +492 to 505
const privateChat = server?.privateChats?.find(
(pc) => pc.username === message.userId,
);
if (privateChat) {
return `pm-${privateChat.id}`;
}
return "none";
}

// For channels, find the user in the channel
const server = state.servers.find((s) => s.id === serverId);
const channel = server?.channels.find((c) => c.id === channelId);
const user = channel?.users.find(
(user) => user.username === message.userId.split("-")[0],
(user) => user.username === message.userId,
);
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.

⚠️ Potential issue | 🟡 Minor

Normalize nickname matching in all lookup branches.

The selector is case-insensitive in the no-channel channel-user fallback, but these PM and channel lookups switch back to strict equality. If the stored nick casing differs from message.userId, userKey falls back to "none" and the avatar/display-name/metadata path stops resolving. Reuse the same normalization here as well.

Suggested fix
+          const normalizedUserId = message.userId.toLowerCase();
           const privateChat = server?.privateChats?.find(
-            (pc) => pc.username === message.userId,
+            (pc) => pc.username.toLowerCase() === normalizedUserId,
           );
@@
         const channel = server?.channels.find((c) => c.id === channelId);
         const user = channel?.users.find(
-          (user) => user.username === message.userId,
+          (user) => user.username.toLowerCase() === message.userId.toLowerCase(),
         );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/message/MessageItem.tsx` around lines 492 - 505, The PM and
channel user lookups use strict equality against message.userId which causes
casing mismatches; update the privateChat and channel?.users.find predicates to
normalize both sides (e.g., compare lowercased or otherwise normalized username
and message.userId) similar to the no-channel fallback so that privateChat,
channel user resolution, and resulting userKey no longer fall back to "none" due
to case differences; ensure you change the predicates used in
server?.privateChats?.find and channel?.users.find (and any user variable
comparisons) to use the same normalization function.

Comment thread src/components/ui/ReactionPopover.tsx
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/publish.yaml:
- Around line 19-20: The collect-version job currently sets permissions.contents
to read which prevents the "Compute previous release tag and generate notes"
step from calling the REST endpoint; change the job's permissions block for the
collect-version job so contents is set to write (permissions: contents: write)
to allow POST /repos/{owner}/{repo}/releases/generate-notes to succeed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 77fbb372-49a5-46a2-b86a-99ca87fda4e7

📥 Commits

Reviewing files that changed from the base of the PR and between f870b8a and 4565e78.

📒 Files selected for processing (2)
  • .github/workflows/publish.yaml
  • biome.json
✅ Files skipped from review due to trivial changes (1)
  • biome.json

Comment thread .github/workflows/publish.yaml Outdated
Comment thread .github/workflows/publish.yaml Outdated
@matheusfillipe matheusfillipe merged commit e2de5d3 into main Mar 13, 2026
3 checks passed
@matheusfillipe matheusfillipe deleted the fix/ui-fixes branch March 13, 2026 00:07
@coderabbitai coderabbitai Bot mentioned this pull request Apr 9, 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.

2 participants