Skip to content

feat(new-channels): typing indicators#2245

Merged
synoet merged 8 commits intomainfrom
synoet/new-channels-typing-indicators
Mar 30, 2026
Merged

feat(new-channels): typing indicators#2245
synoet merged 8 commits intomainfrom
synoet/new-channels-typing-indicators

Conversation

@synoet
Copy link
Copy Markdown
Contributor

@synoet synoet commented Mar 30, 2026

No description provided.

@synoet synoet requested a review from a team as a code owner March 30, 2026 13:21
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 30, 2026

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added real-time typing indicators displaying which users are actively composing messages in channels and threads, with varying text for single, dual, and multiple typists
    • Implemented typing state tracking that activates on keystroke and automatically deactivates after inactivity periods

Walkthrough

Implements typing indicators for channels and threads by adding local typing state tracking via a reusable tracker module, integrating typing event mutations into channel and thread message inputs, and displaying typing status indicators in thread UI.

Changes

Cohort / File(s) Summary
Typing State Management
js/app/packages/channel/Input/create-typing-tracker.ts
New Solid.js helper module exporting createTypingTracker that manages local typing state with inactivity timeout (2000ms), coordinating onStartTyping/onStopTyping callbacks and exposing keystroke()/stop() methods for external control.
Typing Indicator Component
js/app/packages/channel/Thread/ThreadTypingIndicator.tsx, js/app/packages/channel/Thread/index.ts, js/app/packages/channel/Thread/types.ts
New component displaying thread typing status with animated dots and count-based text formatting. Queries typing users via getTypingUsersForChannel(channelId, threadId) and renders only when users are actively typing. Added isNewestThread optional prop to ThreadProps.
Input Layer Updates
js/app/packages/channel/Input/ChannelInput.tsx, js/app/packages/channel/Input/types.ts
Extended ChannelInput with createTypingTracker integration, triggering onStartTyping/onStopTyping callbacks on text changes and message submission. Updated InputCallbacks type with optional typing lifecycle handlers.
Channel & Thread Typing Integration
js/app/packages/channel/Channel/Channel.tsx, js/app/packages/channel/Thread/ThreadReplyInput.tsx, js/app/packages/channel/Thread/ChannelThread.tsx
Wired usePostTypingUpdateMutation hook into Channel and ThreadReplyInput to dispatch typing events. Connected ChannelThread to render ThreadTypingIndicator for newest thread, passing computed isNewestThread prop.
🚥 Pre-merge checks | ✅ 1 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive No pull request description was provided by the author, making it impossible to evaluate whether any description content relates to the changeset. Add a pull request description explaining the typing indicators feature, its purpose, and any relevant implementation details or testing notes.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title follows conventional commits format with 'feat:' prefix, is under 72 characters (37 chars), and clearly describes the main change: adding typing indicators functionality.

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


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

@synoet synoet changed the title feat(new-channels): typing indicators" feat(new-channels): typing indicators Mar 30, 2026
@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 30, 2026

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

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

Inline comments:
In `@js/app/packages/channel/Thread/ThreadTypingIndicator.tsx`:
- Around line 16-28: Replace the switch in the createMemo that builds typingText
with ts-pattern's match: import match from 'ts-pattern' and use
match(typingUsers().length).with(0, () => '' ).with(1, () =>
`${idToDisplayName(typingUsers()[0])} is typing`).with(2, () =>
`${idToDisplayName(typingUsers()[0])} and ${idToDisplayName(typingUsers()[1])}
are typing`).otherwise(() => 'Multiple people are typing'); keep references to
createMemo, typingUsers, and idToDisplayName so behavior remains identical and
ensure the import for match is added at the top.

In `@js/app/packages/core/service.ts`:
- Around line 248-253: The conditional type aliases (e.g., ClientFunctionArgs)
use `any` which violates the TS guideline; update each alias to avoid `any` by
inferring all type parameters or using `unknown`/constrained inference where
appropriate — for example change ClientFunctionArgs from T extends
FunctionDefinition<any, infer Args, any, any> to T extends
FunctionDefinition<infer N, infer Args, infer R, infer M> ? Args : never (or use
unknown for parameters you intentionally don't depend on). Apply the same
pattern to the other touched aliases at the same area so no `any` remains.

In `@js/app/packages/core/util/maybeResult.ts`:
- Around line 309-314: Replace the uses of `any` with `unknown` in the
`ResultType` utility: change the constraint `T extends MaybeResult<any, any>` to
`T extends MaybeResult<unknown, unknown>` and the conditional match `T extends
MaybeResult<any, infer R>` to `T extends MaybeResult<unknown, infer R>` so you
preserve type inference for `R` while avoiding `any` (keep `NonNullable<R>`
as-is); this targets the `ResultType` type and the `MaybeResult` generics.

In `@js/app/packages/queries/soup/normalized-cache/operations.test.ts`:
- Around line 226-237: The test uses unsafe casts (filter as any) when accessing
indexed filter shapes; replace those casts with a proper typed interface for the
filter object and use it for indexed access. Define a local type (e.g.,
FilterMap or NormalizedFilterShape) that describes the keys
('document_filters','chat_filters','channel_filters','project_filters', plus the
dynamic filterKey) and their inner mapping types (mapping idKey to string[]),
then annotate the test variables (filter, otherFilters handling) with that type
and access values without any casts so Object.values((filter as
FilterMap)[key])[0] is strongly typed; update references to filter, filterKey,
idKey, otherFilters accordingly.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 776223c5-84d4-4826-ac0e-c457dc45ed98

📥 Commits

Reviewing files that changed from the base of the PR and between 87d05d8 and b0d0b78.

📒 Files selected for processing (12)
  • js/app/packages/channel/Channel/Channel.tsx
  • js/app/packages/channel/Input/ChannelInput.tsx
  • js/app/packages/channel/Input/create-typing-tracker.ts
  • js/app/packages/channel/Input/types.ts
  • js/app/packages/channel/Thread/ChannelThread.tsx
  • js/app/packages/channel/Thread/ThreadReplyInput.tsx
  • js/app/packages/channel/Thread/ThreadTypingIndicator.tsx
  • js/app/packages/channel/Thread/index.ts
  • js/app/packages/channel/Thread/types.ts
  • js/app/packages/core/service.ts
  • js/app/packages/core/util/maybeResult.ts
  • js/app/packages/queries/soup/normalized-cache/operations.test.ts

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

♻️ Duplicate comments (1)
js/app/packages/channel/Thread/ThreadTypingIndicator.tsx (1)

71-82: 🧹 Nitpick | 🔵 Trivial

Use match from ts-pattern for the switch statement.

Per coding guidelines, prefer match from ts-pattern for switch/case logic.

♻️ Proposed refactor
+import { match } from 'ts-pattern';
 import { getTypingUsersForChannel } from '@queries/channel/typing';
 function getThreadTypingIndicatorText(userIds: string[]): string {
-  switch (userIds.length) {
-    case 0:
-      return '';
-    case 1:
-      return `${idToDisplayName(userIds[0])} is typing`;
-    case 2:
-      return `${idToDisplayName(userIds[0])} and ${idToDisplayName(userIds[1])} are typing`;
-    default:
-      return 'Multiple people are typing';
-  }
+  return match(userIds.length)
+    .with(0, () => '')
+    .with(1, () => `${idToDisplayName(userIds[0])} is typing`)
+    .with(2, () => `${idToDisplayName(userIds[0])} and ${idToDisplayName(userIds[1])} are typing`)
+    .otherwise(() => 'Multiple people are typing');
 }

As per coding guidelines: "For exhaustive switch statements use match from ts-pattern".

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

In `@js/app/packages/channel/Thread/ThreadTypingIndicator.tsx` around lines 71 -
82, Replace the switch/case in getThreadTypingIndicatorText with ts-pattern's
match: import { match } from 'ts-pattern', call match(userIds.length).with(0, ()
=> ''), .with(1, () => `${idToDisplayName(userIds[0])} is typing`), .with(2, ()
=> `${idToDisplayName(userIds[0])} and ${idToDisplayName(userIds[1])} are
typing`), and .otherwise(() => 'Multiple people are typing'); ensure
idToDisplayName remains referenced and the match is exhaustive via .otherwise.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@js/app/packages/channel/Channel/Channel.tsx`:
- Around line 289-291: The isNewestThread computation redundantly calls
messageIndex().keys twice; instead reuse the local keys accessor and compare
item.id to the last element via keys().at(-1). Update the isNewestThread arrow
function (which currently references keys and item.id) to return item.id ===
keys().at(-1) so you avoid the extra messageIndex().keys invocation and simplify
the logic.
- Line 298: The prop isNewestThread is being passed as a evaluated boolean
(isNewestThread()) which loses reactivity; instead pass the accessor itself
(isNewestThread) from the caller and update ChannelThread's prop type to accept
an Accessor<boolean> (isNewestThread?: Accessor<boolean>), then inside
ChannelThread (where Show or any conditional uses it) call
props.isNewestThread() to read the current value. Locate the prop usage in the
Channel component (isNewestThread={isNewestThread()}), the ChannelThread
component declaration and its props type, and update them accordingly so the
reactive accessor is forwarded and invoked inside ChannelThread.

---

Duplicate comments:
In `@js/app/packages/channel/Thread/ThreadTypingIndicator.tsx`:
- Around line 71-82: Replace the switch/case in getThreadTypingIndicatorText
with ts-pattern's match: import { match } from 'ts-pattern', call
match(userIds.length).with(0, () => ''), .with(1, () =>
`${idToDisplayName(userIds[0])} is typing`), .with(2, () =>
`${idToDisplayName(userIds[0])} and ${idToDisplayName(userIds[1])} are typing`),
and .otherwise(() => 'Multiple people are typing'); ensure idToDisplayName
remains referenced and the match is exhaustive via .otherwise.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f2fe4f77-72ec-40fa-bbdc-02b99947fe80

📥 Commits

Reviewing files that changed from the base of the PR and between b0d0b78 and ff0f452.

📒 Files selected for processing (2)
  • js/app/packages/channel/Channel/Channel.tsx
  • js/app/packages/channel/Thread/ThreadTypingIndicator.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.

♻️ Duplicate comments (1)
js/app/packages/channel/Channel/Channel.tsx (1)

297-297: ⚠️ Potential issue | 🟡 Minor

Pass the accessor to maintain reactivity.

isNewestThread is computed reactively based on messageIndex().keys, which updates when messages are added/removed. Passing isNewestThread() (evaluated) loses reactivity—the boolean becomes stale if the message list changes after render.

Pass the accessor and invoke it inside ChannelThread:

Suggested fix
-                          isNewestThread={isNewestThread()}
+                          isNewestThread={isNewestThread}

Then update ChannelThread's prop type to isNewestThread?: Accessor<boolean> and call props.isNewestThread?.() where used.

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

In `@js/app/packages/channel/Channel/Channel.tsx` at line 297, The prop currently
passes the evaluated boolean by calling isNewestThread() which loses reactivity
when messageIndex().keys changes; instead pass the accessor itself to
ChannelThread (pass isNewestThread) and update ChannelThread's prop type to
accept isNewestThread?: Accessor<boolean>, then invoke the accessor where needed
inside ChannelThread as props.isNewestThread?.() so the value updates when
messageIndex().keys changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@js/app/packages/channel/Channel/Channel.tsx`:
- Line 297: The prop currently passes the evaluated boolean by calling
isNewestThread() which loses reactivity when messageIndex().keys changes;
instead pass the accessor itself to ChannelThread (pass isNewestThread) and
update ChannelThread's prop type to accept isNewestThread?: Accessor<boolean>,
then invoke the accessor where needed inside ChannelThread as
props.isNewestThread?.() so the value updates when messageIndex().keys changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8b25a90e-d3f1-4d85-a0b0-286958d925f2

📥 Commits

Reviewing files that changed from the base of the PR and between ff0f452 and 8d1425f.

📒 Files selected for processing (3)
  • js/app/packages/channel/Channel/Channel.tsx
  • js/app/packages/channel/Input/create-typing-tracker.ts
  • js/app/packages/channel/Thread/ThreadTypingIndicator.tsx

@synoet synoet merged commit bd26906 into main Mar 30, 2026
22 checks passed
@synoet synoet deleted the synoet/new-channels-typing-indicators branch March 30, 2026 14:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant