Skip to content

Implement IRCv3 reaction#63

Merged
ValwareIRC merged 24 commits into
mainfrom
feat/message-reactions
Sep 28, 2025
Merged

Implement IRCv3 reaction#63
ValwareIRC merged 24 commits into
mainfrom
feat/message-reactions

Conversation

@ValwareIRC
Copy link
Copy Markdown
Contributor

@ValwareIRC ValwareIRC commented Sep 28, 2025

Summary by CodeRabbit

  • New Features

    • React to messages with emoji; view counts and who reacted.
    • Quick reaction picker modal to add/remove reactions.
    • Click irc:// and ircs:// links to open a pre-filled server join dialog.
  • Improvements

    • Auto-reconnect to previously saved servers on startup.
    • More accurate typing indicators and unread/mention status in private chats.
    • Outgoing messages include stable message IDs for better reaction/tracking.
  • Chores

    • Updated tooling/configuration schema and dev-tool versions.

- Remove problematic IRC link handling code that was causing parse errors
- Fix syntax issues in MessageItem component by properly structuring conditional returns
- Add missing imports (useEffect, useRef, useState, ircColors, uuidv4, platform, hooks)
- Reorganize imports for better consistency
- Clean up import organization
- Fix EnhancedLinkWrapper component formatting
- Implement handleIrcLinkClick function to connect to IRC servers from irc:// and ircs:// links
- Update MessageItem component to accept onIrcLinkClick prop
- Pass handleIrcLinkClick to all EnhancedLinkWrapper components in MessageItem
- Fix parseInt radix warning in linting
- Update biome.json schema version to 2.2.4
- Run biome migrate to fix configuration format
- Format all files according to current biome rules
- Change IRC link clicks to open the 'Connect to server' modal with pre-filled server details
- Remove automatic connection logic
- Add toggleAddServerModal to useStore destructuring
- Parse IRC URL and populate modal with host, port, and current username
- Parse IRC URLs with proper handling of channels in pathname or hash
- Extract nick and password from URL query parameters
- Reuse existing server connections to avoid duplicates
- Clean trailing punctuation from URLs in chat text
- Connect directly instead of opening modal for better UX
- Fix linting issues with void return in forEach callback
- Update @biomejs/biome from 2.2.0 to 2.2.4 in package.json
- Update biome.json schema version to 2.2.4
- Run biome migrate to update configuration
- Format all files to match CI expectations
- Remove automatic connection logic from IRC link clicks
- Open the 'Connect to server' modal with pre-filled server details
- Add toggleAddServerModal back to useStore destructuring
- Make handleIrcLinkClick synchronous since no async operations
- Fix default ports (6697 for ircs, 6667 for irc)
- Add ReactionModal component with emoji selector
- Update Message type to track reactions with user information
- Implement react button on messages that opens emoji modal
- Send TAGMSG with draft/react tag when reacting to messages
- Handle incoming reaction TAGMSG to update message reactions
- Display reactions below messages with emoji, count, and user tooltips
- Support reaction toggling (click same emoji to remove)

The reaction feature allows users to react to messages with emojis,
with reactions being synchronized across all clients in the channel
using the IRC draft/react TAGMSG extension.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Sep 28, 2025

Warning

Rate limit exceeded

@ValwareIRC has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 9 minutes and 20 seconds before requesting another review.

⌛ 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 43bf1a8 and 2d204b1.

📒 Files selected for processing (2)
  • src/components/layout/ChatArea.tsx (14 hunks)
  • src/store/index.ts (3 hunks)

Walkthrough

Adds message reactions (data model, store handling, and UI modal), propagates msgid on messages, forwards IRC/IRCS link clicks to prefill server-join UI, and bumps Biome tooling from 2.2.0 → 2.2.4.

Changes

Cohort / File(s) Summary
Tooling bump
biome.json, package.json
Bump Biome schema and devDependency from 2.2.02.2.4.
Chat UI & link handling
src/components/layout/ChatArea.tsx, src/components/layout/...MessageItem.tsx, src/components/layout/...EnhancedLinkWrapper.tsx
Wire ReactionModal into ChatArea; add reaction state/handlers and onReactClick; forward onIrcLinkClick from EnhancedLinkWrapper to ChatArea; parse irc:///ircs:// clicks to prefill server-join UI; outgoing private message echo uses reactions.
Reaction modal component
src/components/ui/ReactionModal.tsx
Add ReactionModal component with props isOpen, onClose, onSelectEmoji; renders emoji grid and Cancel; selecting an emoji calls onSelectEmoji then onClose.
Store: msgid & reactions, saved servers
src/store/index.ts
Add msgid to incoming messages; replace reactsreactions; handle +draft/react to toggle reactions by user on target messages; clear typing indicators on arrival; update unread/private flows; add connectToSavedServers initialization/action.
Types: reactions model
src/types/index.ts
Add Reaction interface (emoji, userId); add optional msgid?: string to Message; replace reacts: string[] with reactions: Reaction[].

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as User
  participant MI as MessageItem
  participant CA as ChatArea
  participant RM as ReactionModal
  participant ST as Store

  U->>MI: Click react button
  MI->>CA: onReactClick(message, anchorEl)
  CA->>RM: open modal (isOpen = true)
  U->>RM: Select emoji
  RM-->>CA: onSelectEmoji(emoji)
  CA->>ST: dispatch +draft/react (msgid, emoji)
  ST-->>CA: updated message.reactions
  CA-->>MI: rerender message with reactions
Loading
sequenceDiagram
  autonumber
  participant U as User
  participant EL as EnhancedLinkWrapper
  participant CA as ChatArea
  participant UI as ServerAddUI

  U->>EL: Click `irc://` or `ircs://` link
  EL->>CA: onIrcLinkClick(url)
  CA->>CA: parse host/port/channel/nick
  CA->>UI: open server-add modal prefilled with params
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

I’m a rabbit in the code-lined glen,
I hop on msgids now and then 🐇
I sprinkle emojis, tiny and bright,
Links lead to servers, guessed just right.
Thump—reactions bloom, and tests take flight! ✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title “Implement IRCv3 reaction” directly reflects the primary change of adding IRCv3-compatible reaction support across the UI, data store, and type definitions, and it is concise without extraneous detail or generic wording.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

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.

@github-actions
Copy link
Copy Markdown

Pages Preview
Preview URL: https://feat-message-reactions.obsidianirc.pages.dev

Automated deployment preview for the PR in the Cloudflare Pages.

- Format reaction display code in ChatArea.tsx
- Format reaction handling code in store/index.ts
- Format ReactionModal component
- Center ReactionModal on screen with backdrop overlay
- Add msgid field to Message interface to store IRC message ID
- Update message creation to capture msgid from IRC message tags
- Use msgid in TAGMSG reactions instead of internal message ID
- Remove position-based modal positioning logic

The emoji picker now appears centered on screen, and reactions
use the proper IRC msgid for the +reply tag as required by the
draft/react TAGMSG specification.
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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 67c72de and 30eb47a.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (6)
  • biome.json (1 hunks)
  • package.json (1 hunks)
  • src/components/layout/ChatArea.tsx (13 hunks)
  • src/components/ui/ReactionModal.tsx (1 hunks)
  • src/store/index.ts (3 hunks)
  • src/types/index.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/store/index.ts (1)
src/types/index.ts (2)
  • Channel (35-46)
  • PrivateChat (48-55)
src/components/layout/ChatArea.tsx (3)
src/lib/ircUtils.tsx (1)
  • mircToHtml (167-279)
src/hooks/useTabCompletion.ts (1)
  • useTabCompletion (31-154)
src/lib/ircClient.ts (1)
  • ircClient (561-561)
🔇 Additional comments (2)
package.json (1)

44-44: Biome devDependency bump LGTM. Keeping @biomejs/biome at 2.2.4 aligns tooling with the updated schema and doesn’t perturb other dependencies, so this looks good.

biome.json (1)

2-2: Schema URL matches upgraded tooling. Pointing to the 2.2.4 schema keeps the config in lockstep with the new Biome release, so no issues here.

Comment thread src/components/layout/ChatArea.tsx
Comment thread src/store/index.ts
Comment on lines +1438 to +1474
const messages = getChannelMessages(server.id, channel.id);
const messageIndex = messages.findIndex((m) => m.id === replyMessageId);
if (messageIndex === -1) return;

const message = messages[messageIndex];
const existingReactionIndex = message.reactions.findIndex(
(r) => r.emoji === emoji && r.userId === `${server.id}-${sender}`,
);

useStore.setState((state) => {
const updatedMessages = [...messages];
if (existingReactionIndex === -1) {
// Add new reaction
updatedMessages[messageIndex] = {
...message,
reactions: [
...message.reactions,
{ emoji, userId: `${server.id}-${sender}` },
],
};
} else {
// Remove existing reaction (toggle behavior)
updatedMessages[messageIndex] = {
...message,
reactions: message.reactions.filter(
(_, i) => i !== existingReactionIndex,
),
};
}

const key = `${server.id}-${channel.id}`;
return {
messages: {
...state.messages,
[key]: updatedMessages,
},
};
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 | 🔴 Critical

Fix reaction lookup to match msgid.

+draft/react messages carry the parent msgid, but the handler searches messages by our local id. As soon as the server echoes a reaction, messageIndex stays -1, so nothing is added or toggled. Reactions never show up in the UI. Please key the lookup against msgid (falling back to id for safety) so the new feature actually works.

Apply this diff:

-    const messageIndex = messages.findIndex((m) => m.id === replyMessageId);
+    const messageIndex = messages.findIndex(
+      (m) => m.msgid === replyMessageId || m.id === replyMessageId,
+    );
📝 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
const messages = getChannelMessages(server.id, channel.id);
const messageIndex = messages.findIndex((m) => m.id === replyMessageId);
if (messageIndex === -1) return;
const message = messages[messageIndex];
const existingReactionIndex = message.reactions.findIndex(
(r) => r.emoji === emoji && r.userId === `${server.id}-${sender}`,
);
useStore.setState((state) => {
const updatedMessages = [...messages];
if (existingReactionIndex === -1) {
// Add new reaction
updatedMessages[messageIndex] = {
...message,
reactions: [
...message.reactions,
{ emoji, userId: `${server.id}-${sender}` },
],
};
} else {
// Remove existing reaction (toggle behavior)
updatedMessages[messageIndex] = {
...message,
reactions: message.reactions.filter(
(_, i) => i !== existingReactionIndex,
),
};
}
const key = `${server.id}-${channel.id}`;
return {
messages: {
...state.messages,
[key]: updatedMessages,
},
};
const messages = getChannelMessages(server.id, channel.id);
const messageIndex = messages.findIndex(
(m) => m.msgid === replyMessageId || m.id === replyMessageId,
);
if (messageIndex === -1) return;
const message = messages[messageIndex];
const existingReactionIndex = message.reactions.findIndex(
(r) => r.emoji === emoji && r.userId === `${server.id}-${sender}`,
);
useStore.setState((state) => {
const updatedMessages = [...messages];
if (existingReactionIndex === -1) {
// Add new reaction
updatedMessages[messageIndex] = {
...message,
reactions: [
...message.reactions,
{ emoji, userId: `${server.id}-${sender}` },
],
};
} else {
// Remove existing reaction (toggle behavior)
updatedMessages[messageIndex] = {
...message,
reactions: message.reactions.filter(
(_, i) => i !== existingReactionIndex,
),
};
}
const key = `${server.id}-${channel.id}`;
return {
messages: {
...state.messages,
[key]: updatedMessages,
},
};
});
🤖 Prompt for AI Agents
In src/store/index.ts around lines 1438 to 1474, the handler currently finds the
target message by comparing message.id to replyMessageId which fails for
+draft/react events that carry the parent msgid; change the lookup to match
m.msgid === replyMessageId and fall back to m.id === replyMessageId (e.g.,
findIndex(m => m.msgid === replyMessageId || m.id === replyMessageId)), leaving
the rest of the reaction toggle logic unchanged so reactions are applied to the
correct message.

…es with msgid

- Change TAGMSG to use +reply instead of reply for msgid reference
- Only show react button for messages that have msgid stored
- Ensure msgid is properly stored from IRC message tags
- Reactions now require msgid to be present on target message

This ensures that reactions can only be sent for messages that have
msgid, and the +reply tag properly references the target message's msgid
as required by the IRC draft/react specification.
- Remove extra closing div tag causing JSX structure corruption
- Add missing React hooks (useState, useEffect, useRef) to imports
- Import missing dependencies (useTabCompletion, useMediaQuery, uuidv4, platform, ircColors)
- Fix lastTypingTime variable declaration from const to let
- Correct store and Message type import paths
- Ensure reaction badges display properly at bottom-left of messages with hover tooltips
- Remove unused useKeyboardShortcuts import
- Apply proper import sorting and formatting
- Fix reaction tooltip formatting (quotes, parentheses, line breaks)
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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 30eb47a and 690f4fa.

📒 Files selected for processing (1)
  • src/components/layout/ChatArea.tsx (13 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/components/layout/ChatArea.tsx (2)
src/lib/ircUtils.tsx (1)
  • mircToHtml (167-279)
src/lib/ircClient.ts (1)
  • ircClient (561-561)
🪛 GitHub Actions: Lint and Tests
src/components/layout/ChatArea.tsx

[error] 29-31: This import is unused.


[error] 393-404: File content differs from formatting output. Run the formatter to fix.

🔇 Additional comments (2)
src/components/layout/ChatArea.tsx (2)

1088-1095: Re-enable reactions in private chats.

This still only targets channels, so reacting in a DM silently drops the TAGMSG—exactly what the earlier review called out. Please locate the matching private chat and fall back to its username when no channel is found:

-    const channel = server?.channels.find(
-      (c) => c.id === reactionModal.message?.channelId,
-    );
-      if (server && channel) {
-        const tagMsg = `@+draft/react=${emoji};+draft/reply=${reactionModal.message.msgid} TAGMSG ${channel.name}`;
+    const channel = server?.channels.find(
+      (c) => c.id === reactionModal.message?.channelId,
+    );
+    const privateChat = server?.privateChats?.find(
+      (pc) => pc.id === reactionModal.message?.channelId,
+    );
+    const target = channel?.name ?? privateChat?.username;
+
+    if (server && target) {
+      const tagMsg = `@+draft/react=${emoji};+draft/reply=${reactionModal.message.msgid} TAGMSG ${target}`;
         ircClient.sendRaw(server.id, tagMsg);
       }

488-492: Fix the IRC default port handling.

When an IRC/IRCS URL omits the explicit port, you’re defaulting to 8000/443, which will fail against standard networks. The usual defaults are 6667 for plain IRC and 6697 for TLS. Please update the fallback logic accordingly.

⛔ Skipped due to learnings
Learnt from: ValwareIRC
PR: ObsidianIRC/ObsidianIRC#62
File: src/components/layout/ChatArea.tsx:0-0
Timestamp: 2025-09-28T19:37:34.200Z
Learning: ObsidianIRC uses websockets for IRC connections, not traditional IRC protocol. Default ports are 443 for secure connections (ircs://) and 8000 for plain connections (irc://), not the standard IRC ports 6667/6697.

Comment thread src/components/layout/ChatArea.tsx Outdated
Comment thread src/components/layout/ChatArea.tsx Outdated
- Move reply and react buttons back inside message container with proper hover visibility
- Add relative positioning to message content container for absolute reaction positioning
- Ensure reactions display at bottom-left of message content with proper tooltips
- Fix incoming reaction display functionality
- Change mtags.reply to mtags['+draft/reply'] for IRC draft/react spec compliance
- Change message.id comparison to message.msgid for proper message matching
- Ensure incoming TAGMSG reactions are properly added to messages with matching msgid
- Move reaction badges below message content instead of absolute positioning
- Use flex layout with margin-top for proper spacing
- Remove absolute positioning that was causing text overlap
- Maintain hover tooltips and reaction functionality
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: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 564d672 and c0618a5.

📒 Files selected for processing (1)
  • src/components/layout/ChatArea.tsx (14 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/components/layout/ChatArea.tsx (3)
src/lib/ircUtils.tsx (1)
  • mircToHtml (167-279)
src/hooks/useTabCompletion.ts (1)
  • useTabCompletion (31-154)
src/lib/ircClient.ts (1)
  • ircClient (561-561)
🔇 Additional comments (2)
src/components/layout/ChatArea.tsx (2)

1087-1099: Reactions still ignore private messages.

handleReactionSelect only targets channels, so reacting inside a DM silently does nothing. Resolve the private chat like we do elsewhere and send the TAGMSG to that nickname.

-      const channel = server?.channels.find(
-        (c) => c.id === reactionModal.message?.channelId,
-      );
-      if (server && channel) {
-        const tagMsg = `@+draft/react=${emoji};+draft/reply=${reactionModal.message.msgid} TAGMSG ${channel.name}`;
+      const channel = server?.channels.find(
+        (c) => c.id === reactionModal.message?.channelId,
+      );
+      const privateChat = server?.privateChats?.find(
+        (pc) => pc.id === reactionModal.message?.channelId,
+      );
+      const target = channel?.name ?? privateChat?.username;
+
+      if (server && target) {
+        const tagMsg = `@+draft/react=${emoji};+draft/reply=${reactionModal.message.msgid} TAGMSG ${target}`;
         ircClient.sendRaw(server.id, tagMsg);
       }

490-525: Fix default IRC port selection.

We default TLS connections to 443 and plain IRC to 8000, but the established defaults are 6697 (IRCS) and 6667 (IRC). Using the wrong ports will fail to connect to most networks when the URL omits :port.

-  const port = urlObj.port
-    ? Number.parseInt(urlObj.port, 10)
-    : scheme === "ircs"
-      ? 443
-      : 8000;
+  const port = urlObj.port
+    ? Number.parseInt(urlObj.port, 10)
+    : scheme === "ircs"
+      ? 6697
+      : 6667;
⛔ Skipped due to learnings
Learnt from: ValwareIRC
PR: ObsidianIRC/ObsidianIRC#62
File: src/components/layout/ChatArea.tsx:0-0
Timestamp: 2025-09-28T19:37:34.200Z
Learning: ObsidianIRC uses websockets for IRC connections, not traditional IRC protocol. Default ports are 443 for secure connections (ircs://) and 8000 for plain connections (irc://), not the standard IRC ports 6667/6697.

Comment on lines +124 to +132
if (
(matches[index].startsWith("ircs://") ||
matches[index].startsWith("irc://")) &&
onIrcLinkClick
) {
e.preventDefault();
onIrcLinkClick(matches[index]);
}
}}
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Sep 28, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Allow uppercase IRC schemes in link handler.

startsWith("irc://") is case-sensitive, so IRC:// or IRCS:// links won’t trigger the join modal. Normalize the scheme before comparing so we honor the URI spec’s case-insensitive requirement.

-                if (
-                  (matches[index].startsWith("ircs://") ||
-                    matches[index].startsWith("irc://")) &&
+                const normalized = matches[index].toLowerCase();
+                if (
+                  (normalized.startsWith("ircs://") ||
+                    normalized.startsWith("irc://")) &&
                   onIrcLinkClick
                 ) {
📝 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
if (
(matches[index].startsWith("ircs://") ||
matches[index].startsWith("irc://")) &&
onIrcLinkClick
) {
e.preventDefault();
onIrcLinkClick(matches[index]);
}
}}
const normalized = matches[index].toLowerCase();
if (
(normalized.startsWith("ircs://") ||
normalized.startsWith("irc://")) &&
onIrcLinkClick
) {
e.preventDefault();
onIrcLinkClick(matches[index]);
}
🤖 Prompt for AI Agents
In src/components/layout/ChatArea.tsx around lines 124 to 132, the link handler
checks for "ircs://" or "irc://" using case-sensitive startsWith, so uppercase
variants like "IRC://" are ignored; normalize the link's scheme to lowercase
before comparing (e.g., call toLowerCase() on matches[index] or its scheme
substring) and then check startsWith("ircs://") or startsWith("irc://"),
preserving the existing e.preventDefault() and onIrcLinkClick call when matched.

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.

oh how did this get in this PR

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.

@ValwareIRC It looks like the IRC link handling functionality got mixed into this reactions PR! The EnhancedLinkWrapper changes that add onIrcLinkClick support and the handleIrcLinkClick function seem like they should be in a separate PR focused on IRC link handling, rather than bundled with the reaction feature.

Would you like to:

  1. Move the IRC link handling code to a separate PR, or
  2. Remove it from this PR to keep the scope focused on just reactions?

The reactions functionality itself looks solid - it's just the link handling that seems out of place for this PR's objectives.

🐰💭

- Add unreact TAGMSG handler in store for +draft/unreact messages
- Modify ChatArea reaction selection to send appropriate react/unreact messages
- Enable users to both add and remove emoji reactions with proper IRC protocol compliance
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: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c0618a5 and 43bf1a8.

📒 Files selected for processing (2)
  • src/components/layout/ChatArea.tsx (14 hunks)
  • src/store/index.ts (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/store/index.ts (1)
src/types/index.ts (2)
  • Channel (35-46)
  • PrivateChat (48-55)
src/components/layout/ChatArea.tsx (3)
src/lib/ircUtils.tsx (1)
  • mircToHtml (167-279)
src/hooks/useTabCompletion.ts (1)
  • useTabCompletion (31-154)
src/lib/ircClient.ts (1)
  • ircClient (561-561)
🔇 Additional comments (3)
src/store/index.ts (1)

1438-1475: Reactions still can’t bind to messages without a msgid fallback.

Line [1439] (and the unreact block at Line [1501]) only compare m.msgid against replyMessageId. As noted earlier, +draft/react events carry the parent msgid, but our history (and even freshly received messages while msgid support is flaky) may only have id. Without the fallback, messageIndex stays -1 and reactions never stick. Please fall back to the local id in both blocks.

-    const messageIndex = messages.findIndex((m) => m.msgid === replyMessageId);
+    const messageIndex = messages.findIndex(
+      (m) => m.msgid === replyMessageId || m.id === replyMessageId,
+    );

Apply the same change inside the unreact handler.

Also applies to: 1499-1527

src/components/layout/ChatArea.tsx (2)

124-132: IRC links are still case-sensitive.

Line [125] keeps checking startsWith("irc://") and startsWith("ircs://"), so IRC:// / IRCS:// links silently fall through. Normalize the matched URI before comparing so we satisfy the scheme’s case-insensitivity.

-                if (
-                  (matches[index].startsWith("ircs://") ||
-                    matches[index].startsWith("irc://")) &&
+                const normalized = matches[index].toLowerCase();
+                if (
+                  (normalized.startsWith("ircs://") ||
+                    normalized.startsWith("irc://")) &&

1087-1113: DM reactions never fire.

Line [1092] only looks for a channel object, so privateChat targets always fall through and we never emit the TAGMSG. Resolve the private chat (username) and reuse it as the target; the same logic should apply to both react and unreact paths.

-      const channel = server?.channels.find(
-        (c) => c.id === reactionModal.message?.channelId,
-      );
-      if (server && channel) {
+      const channel = server?.channels.find(
+        (c) => c.id === reactionModal.message?.channelId,
+      );
+      const privateChat = server?.privateChats?.find(
+        (pc) => pc.id === reactionModal.message?.channelId,
+      );
+      const target = channel?.name ?? privateChat?.username;
+
+      if (server && target) {
         // Check if user has already reacted with this emoji
         const existingReaction = reactionModal.message.reactions.find(
@@
-          const tagMsg = `@+draft/unreact=${emoji};+draft/reply=${reactionModal.message.msgid} TAGMSG ${channel.name}`;
+          const tagMsg = `@+draft/unreact=${emoji};+draft/reply=${reactionModal.message.msgid} TAGMSG ${target}`;
@@
-          const tagMsg = `@+draft/react=${emoji};+draft/reply=${reactionModal.message.msgid} TAGMSG ${channel.name}`;
+          const tagMsg = `@+draft/react=${emoji};+draft/reply=${reactionModal.message.msgid} TAGMSG ${target}`;

Mirror the privateChat / target handling inside the unreact branch as well.

Comment thread src/store/index.ts
Comment on lines 897 to 909
const newMessage = {
id: replyId ? replyId : uuidv4(),
msgid: mtags?.msgid,
content: message,
timestamp,
userId: response.sender,
channelId: channel.id,
serverId: server.id,
type: "message" as const,
reacts: [],
reactions: [],
replyMessage: replyMessage,
mentioned: [], // Add logic for mentions if needed
};
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 | 🔴 Critical

Stop copying the parent reply id into new message IDs.

Line [899] currently sets id to the replied-to msgid, so every reply reuses its parent’s identifier. That breaks React keys (duplicates), makes replies overwrite each other in state, and still doesn’t give the reply a unique identifier. Instead, derive the id from the message’s own msgid (fall back to uuidv4() when absent) and keep replyMessage referencing the parent.

-      const newMessage = {
-        id: replyId ? replyId : uuidv4(),
+      const messageId = mtags?.msgid ?? uuidv4();
+      const newMessage = {
+        id: messageId,
         msgid: mtags?.msgid,
📝 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
const newMessage = {
id: replyId ? replyId : uuidv4(),
msgid: mtags?.msgid,
content: message,
timestamp,
userId: response.sender,
channelId: channel.id,
serverId: server.id,
type: "message" as const,
reacts: [],
reactions: [],
replyMessage: replyMessage,
mentioned: [], // Add logic for mentions if needed
};
const messageId = mtags?.msgid ?? uuidv4();
const newMessage = {
id: messageId,
msgid: mtags?.msgid,
content: message,
timestamp,
userId: response.sender,
channelId: channel.id,
serverId: server.id,
type: "message" as const,
reactions: [],
replyMessage: replyMessage,
mentioned: [], // Add logic for mentions if needed
};
🤖 Prompt for AI Agents
In src/store/index.ts around lines 897 to 909, the new message id is incorrectly
set to the parent reply id (replyId) which causes duplicate ids; change the id
assignment to derive from the message's own msgid (use mtags?.msgid) and fall
back to uuidv4() when absent (i.e. id = mtags?.msgid ?? uuidv4()), remove using
replyId for id, and keep replyMessage pointing to the parent reply so replies
remain linked but have unique ids.

- Display red X button on reactions made by current user
- X button appears on hover with smooth opacity transition
- Clicking X button immediately removes the reaction
- Clicking reaction badge toggles between add/remove based on user state
- Enhanced reaction UI with better user interaction feedback
- Store reaction userIds as just the username instead of serverId-username
- Update all reaction checking logic to match new userId format
- Fix tooltip display to show usernames directly without parsing
- Ensure consistent user identification across react/unreact operations
- Format arrow function in ChatArea.tsx reaction checking
- Format array/object literals in store TAGMSG handlers
@ValwareIRC ValwareIRC merged commit ae40fc8 into main Sep 28, 2025
4 checks passed
zocram4cc pushed a commit to zocram4cc/ObsidianIRC that referenced this pull request Feb 17, 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