groupchat 0.0.9 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +93 -39
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -349,15 +349,7 @@ function Header({
349
349
  width: "100%",
350
350
  flexShrink: 0,
351
351
  children: [
352
- /* @__PURE__ */ jsx3(Box3, { children: title || /* @__PURE__ */ jsxs3(Fragment2, { children: [
353
- /* @__PURE__ */ jsxs3(Text3, { color: "cyan", bold: true, children: [
354
- "$",
355
- " "
356
- ] }),
357
- /* @__PURE__ */ jsx3(Text3, { color: "blue", bold: true, children: "groupchat" }),
358
- /* @__PURE__ */ jsx3(Text3, { color: "gray", children: " --session " }),
359
- /* @__PURE__ */ jsx3(Text3, { color: "yellow", children: username || "..." })
360
- ] }) }),
352
+ /* @__PURE__ */ jsx3(Box3, { children: title }),
361
353
  /* @__PURE__ */ jsxs3(Box3, { children: [
362
354
  showStatus && /* @__PURE__ */ jsxs3(Fragment2, { children: [
363
355
  /* @__PURE__ */ jsxs3(Text3, { color: statusColor, children: [
@@ -599,7 +591,9 @@ function Menu({
599
591
  publicChannels,
600
592
  privateChannels,
601
593
  unreadCounts,
602
- aggregatedPresence
594
+ aggregatedPresence,
595
+ isLoadingChannels = false,
596
+ totalUnreadCount = 0
603
597
  }) {
604
598
  const { stdout } = useStdout2();
605
599
  const { navigate } = useNavigation();
@@ -632,8 +626,9 @@ function Menu({
632
626
  }, [menuItems, currentChannel]);
633
627
  useEffect2(() => {
634
628
  if (!stdout) return;
635
- stdout.write(`\x1B]0;Menu\x07`);
636
- }, [stdout]);
629
+ const unreadSuffix = totalUnreadCount > 0 ? ` (${totalUnreadCount})` : "";
630
+ stdout.write(`\x1B]0;Menu${unreadSuffix}\x07`);
631
+ }, [stdout, totalUnreadCount]);
637
632
  useInput3((input, key) => {
638
633
  if (key.escape) {
639
634
  navigate("chat");
@@ -721,7 +716,7 @@ function Menu({
721
716
  }
722
717
  )
723
718
  ] }),
724
- allChannels.length === 0 && /* @__PURE__ */ jsx7(Box6, { children: /* @__PURE__ */ jsx7(Text5, { color: "gray", children: "No channels available" }) })
719
+ allChannels.length === 0 && /* @__PURE__ */ jsx7(Box6, { children: isLoadingChannels ? /* @__PURE__ */ jsx7(Text5, { color: "cyan", children: "Loading channels..." }) : /* @__PURE__ */ jsx7(Text5, { color: "gray", children: "No channels available" }) })
725
720
  ] }),
726
721
  /* @__PURE__ */ jsx7(Box6, { paddingRight: 2, paddingTop: 2, children: /* @__PURE__ */ jsx7(AtAGlance, { presenceState: aggregatedPresence }) })
727
722
  ] }),
@@ -1747,7 +1742,8 @@ function ChatView({
1747
1742
  onTypingStop,
1748
1743
  onCommandSend,
1749
1744
  error,
1750
- token
1745
+ token,
1746
+ totalUnreadCount = 0
1751
1747
  }) {
1752
1748
  const { stdout } = useStdout3();
1753
1749
  const { tooltip, isInputDisabled, handleInputChange, handleSubmit } = useCommandInput({
@@ -1766,8 +1762,9 @@ function ChatView({
1766
1762
  useEffect5(() => {
1767
1763
  if (!stdout) return;
1768
1764
  const prefix = connectionStatus === "connected" ? "\u2022 " : "";
1769
- stdout.write(`\x1B]0;${prefix}#${displayName}\x07`);
1770
- }, [stdout, connectionStatus, displayName]);
1765
+ const unreadSuffix = totalUnreadCount > 0 ? ` (${totalUnreadCount})` : "";
1766
+ stdout.write(`\x1B]0;${prefix}#${displayName}${unreadSuffix}\x07`);
1767
+ }, [stdout, connectionStatus, displayName, totalUnreadCount]);
1771
1768
  return /* @__PURE__ */ jsxs13(Layout, { width: terminalSize.columns, height: terminalSize.rows, topPadding, children: [
1772
1769
  /* @__PURE__ */ jsx14(Layout.Header, { children: /* @__PURE__ */ jsx14(
1773
1770
  Header,
@@ -1866,7 +1863,8 @@ function CreateChannelScreen({
1866
1863
  connectionStatus,
1867
1864
  onLogout,
1868
1865
  onCreateChannel,
1869
- topPadding = 0
1866
+ topPadding = 0,
1867
+ totalUnreadCount = 0
1870
1868
  }) {
1871
1869
  const { stdout } = useStdout4();
1872
1870
  const { navigate } = useNavigation();
@@ -1877,8 +1875,9 @@ function CreateChannelScreen({
1877
1875
  const [error, setError] = useState7(null);
1878
1876
  useEffect6(() => {
1879
1877
  if (!stdout) return;
1880
- stdout.write(`\x1B]0;Create Channel\x07`);
1881
- }, [stdout]);
1878
+ const unreadSuffix = totalUnreadCount > 0 ? ` (${totalUnreadCount})` : "";
1879
+ stdout.write(`\x1B]0;Create Channel${unreadSuffix}\x07`);
1880
+ }, [stdout, totalUnreadCount]);
1882
1881
  useInput5((input, key) => {
1883
1882
  if (key.escape) {
1884
1883
  navigate("menu");
@@ -2189,6 +2188,7 @@ var init_channel_manager = __esm({
2189
2188
  state.realtimeMessages.shift();
2190
2189
  }
2191
2190
  }
2191
+ this.callbacks.onNonActiveChannelMessage?.(channelSlug, message);
2192
2192
  }
2193
2193
  });
2194
2194
  channel.on("presence_state", (payload) => {
@@ -2611,18 +2611,20 @@ var init_channel_manager = __esm({
2611
2611
 
2612
2612
  // src/hooks/use-multi-channel-chat.ts
2613
2613
  import { useState as useState8, useCallback as useCallback3, useRef as useRef3, useEffect as useEffect7 } from "react";
2614
- function useMultiChannelChat(token, currentChannel, onChannelListChanged) {
2615
- const [messages, setMessages] = useState8([]);
2614
+ function useMultiChannelChat(token, currentChannel, onChannelListChanged, incrementUnreadCount) {
2615
+ const [messageCache, setMessageCache] = useState8({});
2616
+ const [subscriberCache, setSubscriberCache] = useState8({});
2616
2617
  const [connectionStatus, setConnectionStatus] = useState8("disconnected");
2617
2618
  const [username, setUsername] = useState8(null);
2618
2619
  const [error, setError] = useState8(null);
2619
2620
  const [typingUsers, setTypingUsers] = useState8([]);
2620
2621
  const [presenceState, setPresenceState] = useState8({});
2621
- const [subscribers, setSubscribers] = useState8([]);
2622
2622
  const [channelsReady, setChannelsReady] = useState8(false);
2623
2623
  const managerRef = useRef3(null);
2624
2624
  const prevChannelRef = useRef3(null);
2625
2625
  const isLoadingHistory = useRef3(false);
2626
+ const messages = messageCache[currentChannel] || [];
2627
+ const subscribers = subscriberCache[currentChannel] || [];
2626
2628
  useEffect7(() => {
2627
2629
  if (!token) {
2628
2630
  if (managerRef.current) {
@@ -2641,7 +2643,13 @@ function useMultiChannelChat(token, currentChannel, onChannelListChanged) {
2641
2643
  token,
2642
2644
  {
2643
2645
  onMessage: (channelSlug, message) => {
2644
- setMessages((prev) => [...prev, message]);
2646
+ setMessageCache((prev) => ({
2647
+ ...prev,
2648
+ [channelSlug]: [...prev[channelSlug] || [], message]
2649
+ }));
2650
+ },
2651
+ onNonActiveChannelMessage: (channelSlug, _message) => {
2652
+ incrementUnreadCount?.(channelSlug);
2645
2653
  },
2646
2654
  onPresenceState: (channelSlug, state) => {
2647
2655
  setPresenceState(state);
@@ -2705,10 +2713,14 @@ function useMultiChannelChat(token, currentChannel, onChannelListChanged) {
2705
2713
  }
2706
2714
  },
2707
2715
  onUserInvitedToChannel: (channelSlug, invitedUsername, invitedUserId, invitedBy) => {
2708
- setSubscribers((prev) => {
2709
- const exists = prev.some((s) => s.user_id === invitedUserId);
2716
+ setSubscriberCache((prev) => {
2717
+ const currentSubs = prev[channelSlug] || [];
2718
+ const exists = currentSubs.some((s) => s.user_id === invitedUserId);
2710
2719
  if (!exists) {
2711
- return [...prev, { username: invitedUsername, user_id: invitedUserId, role: "member" }];
2720
+ return {
2721
+ ...prev,
2722
+ [channelSlug]: [...currentSubs, { username: invitedUsername, user_id: invitedUserId, role: "member" }]
2723
+ };
2712
2724
  }
2713
2725
  return prev;
2714
2726
  });
@@ -2717,7 +2729,13 @@ function useMultiChannelChat(token, currentChannel, onChannelListChanged) {
2717
2729
  setError(`You were removed from ${channelSlug} by ${removedBy}`);
2718
2730
  },
2719
2731
  onUserRemovedFromChannel: (channelSlug, removedUsername, removedBy) => {
2720
- setSubscribers((prev) => prev.filter((s) => s.username !== removedUsername));
2732
+ setSubscriberCache((prev) => {
2733
+ const currentSubs = prev[channelSlug] || [];
2734
+ return {
2735
+ ...prev,
2736
+ [channelSlug]: currentSubs.filter((s) => s.username !== removedUsername)
2737
+ };
2738
+ });
2721
2739
  }
2722
2740
  }
2723
2741
  );
@@ -2768,9 +2786,15 @@ function useMultiChannelChat(token, currentChannel, onChannelListChanged) {
2768
2786
  const history = await manager.fetchHistory(currentChannel);
2769
2787
  if (currentChannel.startsWith("private_room:")) {
2770
2788
  const subs = await manager.fetchSubscribers(currentChannel);
2771
- setSubscribers(subs);
2789
+ setSubscriberCache((prev) => ({
2790
+ ...prev,
2791
+ [currentChannel]: subs
2792
+ }));
2772
2793
  } else {
2773
- setSubscribers([]);
2794
+ setSubscriberCache((prev) => ({
2795
+ ...prev,
2796
+ [currentChannel]: []
2797
+ }));
2774
2798
  }
2775
2799
  const realtimeMessages = manager.getRealtimeMessages(currentChannel);
2776
2800
  const merged = [...history, ...realtimeMessages];
@@ -2781,7 +2805,10 @@ function useMultiChannelChat(token, currentChannel, onChannelListChanged) {
2781
2805
  return true;
2782
2806
  });
2783
2807
  deduplicated.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
2784
- setMessages(deduplicated);
2808
+ setMessageCache((prev) => ({
2809
+ ...prev,
2810
+ [currentChannel]: deduplicated
2811
+ }));
2785
2812
  manager.clearRealtimeMessages(currentChannel);
2786
2813
  const presence = manager.getPresence(currentChannel);
2787
2814
  setPresenceState(presence);
@@ -2947,7 +2974,7 @@ var init_use_agent_detection = __esm({
2947
2974
  });
2948
2975
 
2949
2976
  // src/hooks/use-channels.ts
2950
- import { useState as useState9, useCallback as useCallback5, useEffect as useEffect9 } from "react";
2977
+ import { useState as useState9, useCallback as useCallback5, useEffect as useEffect9, useMemo as useMemo6 } from "react";
2951
2978
  function useChannels(token) {
2952
2979
  const [publicChannels, setPublicChannels] = useState9([]);
2953
2980
  const [privateChannels, setPrivateChannels] = useState9([]);
@@ -2983,6 +3010,21 @@ function useChannels(token) {
2983
3010
  console.error("Failed to refetch unread counts:", err);
2984
3011
  }
2985
3012
  }, [token]);
3013
+ const incrementUnreadCount = useCallback5((channelSlug) => {
3014
+ setUnreadCounts((prev) => ({
3015
+ ...prev,
3016
+ [channelSlug]: (prev[channelSlug] || 0) + 1
3017
+ }));
3018
+ }, []);
3019
+ const clearUnreadCount = useCallback5((channelSlug) => {
3020
+ setUnreadCounts((prev) => ({
3021
+ ...prev,
3022
+ [channelSlug]: 0
3023
+ }));
3024
+ }, []);
3025
+ const totalUnreadCount = useMemo6(() => {
3026
+ return Object.values(unreadCounts).reduce((sum, count) => sum + count, 0);
3027
+ }, [unreadCounts]);
2986
3028
  useEffect9(() => {
2987
3029
  if (token) {
2988
3030
  fetchData();
@@ -2995,7 +3037,10 @@ function useChannels(token) {
2995
3037
  loading,
2996
3038
  error,
2997
3039
  refetch: fetchData,
2998
- refetchUnreadCounts
3040
+ refetchUnreadCounts,
3041
+ incrementUnreadCount,
3042
+ clearUnreadCount,
3043
+ totalUnreadCount
2999
3044
  };
3000
3045
  }
3001
3046
  var init_use_channels = __esm({
@@ -3068,7 +3113,7 @@ function AppContent() {
3068
3113
  }
3069
3114
  checkAuth();
3070
3115
  }, []);
3071
- const { publicChannels, privateChannels, unreadCounts, refetchUnreadCounts, refetch: refetchChannels } = useChannels(token);
3116
+ const { publicChannels, privateChannels, unreadCounts, loading: isLoadingChannels, refetchUnreadCounts, refetch: refetchChannels, incrementUnreadCount, clearUnreadCount, totalUnreadCount } = useChannels(token);
3072
3117
  const {
3073
3118
  messages,
3074
3119
  connectionStatus,
@@ -3083,7 +3128,7 @@ function AppContent() {
3083
3128
  connect,
3084
3129
  disconnect,
3085
3130
  channelManager
3086
- } = useMultiChannelChat(token, currentChannel, refetchChannels);
3131
+ } = useMultiChannelChat(token, currentChannel, refetchChannels, incrementUnreadCount);
3087
3132
  const { users } = usePresence(presenceState, subscribers, currentChannel);
3088
3133
  useAgentDetection(channelManager, connectionStatus === "connected");
3089
3134
  const sendCommand = useCallback6(
@@ -3127,10 +3172,11 @@ function AppContent() {
3127
3172
  }
3128
3173
  if (currentChannel) {
3129
3174
  markChannelAsRead(currentChannel, true);
3175
+ clearUnreadCount(currentChannel);
3130
3176
  }
3131
3177
  prevChannelForMarkAsReadRef.current = currentChannel;
3132
3178
  }
3133
- }, [currentChannel, channelManager, refetchUnreadCounts]);
3179
+ }, [currentChannel, channelManager, refetchUnreadCounts, clearUnreadCount]);
3134
3180
  useEffect10(() => {
3135
3181
  return () => {
3136
3182
  if (currentChannel && channelManager) {
@@ -3143,6 +3189,10 @@ function AppContent() {
3143
3189
  refetchUnreadCounts();
3144
3190
  }
3145
3191
  }, [route, refetchUnreadCounts]);
3192
+ useEffect10(() => {
3193
+ setScrollOffset(0);
3194
+ setIsScrollDetached(false);
3195
+ }, [currentChannel]);
3146
3196
  const handleLogin = useCallback6(async () => {
3147
3197
  setAuthState("authenticating");
3148
3198
  setAuthStatus("Starting login...");
@@ -3271,7 +3321,9 @@ function AppContent() {
3271
3321
  publicChannels,
3272
3322
  privateChannels,
3273
3323
  unreadCounts,
3274
- aggregatedPresence
3324
+ aggregatedPresence,
3325
+ isLoadingChannels,
3326
+ totalUnreadCount
3275
3327
  }
3276
3328
  )
3277
3329
  }
@@ -3294,7 +3346,8 @@ function AppContent() {
3294
3346
  connectionStatus,
3295
3347
  onLogout: handleLogout,
3296
3348
  onCreateChannel: handleCreateChannel,
3297
- topPadding
3349
+ topPadding,
3350
+ totalUnreadCount
3298
3351
  }
3299
3352
  )
3300
3353
  }
@@ -3325,7 +3378,8 @@ function AppContent() {
3325
3378
  onTypingStop: stopTyping,
3326
3379
  onCommandSend: sendCommand,
3327
3380
  error,
3328
- token
3381
+ token,
3382
+ totalUnreadCount
3329
3383
  }
3330
3384
  );
3331
3385
  }
@@ -3517,7 +3571,7 @@ function UpdatePrompt({ updateInfo, onComplete }) {
3517
3571
  // package.json
3518
3572
  var package_default = {
3519
3573
  name: "groupchat",
3520
- version: "0.0.9",
3574
+ version: "0.0.10",
3521
3575
  description: "CLI chat client for Groupchat",
3522
3576
  type: "module",
3523
3577
  main: "./dist/index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "groupchat",
3
- "version": "0.0.9",
3
+ "version": "0.0.10",
4
4
  "description": "CLI chat client for Groupchat",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",