@wowyuarm/dsh-agent-team 0.1.9 → 0.1.11

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 (119) hide show
  1. package/README.md +17 -11
  2. package/README.zh.md +17 -11
  3. package/package.json +61 -47
  4. package/packages/agent-team/README.md +6 -4
  5. package/packages/agent-team/README.zh.md +6 -3
  6. package/packages/agent-team/lib/context-management.js +48 -55
  7. package/packages/agent-team/lib/context-projection.js +22 -8
  8. package/packages/agent-team/lib/context-source.js +163 -23
  9. package/packages/agent-team/lib/index.js +349 -176
  10. package/packages/agent-team/lib/invariant.js +38 -4
  11. package/packages/agent-team/lib/ledger.js +658 -294
  12. package/packages/agent-team/lib/member-context.js +6 -2
  13. package/packages/agent-team/lib/member-time-context.js +141 -0
  14. package/packages/agent-team/lib/pressure-policy.js +34 -11
  15. package/packages/agent-team/lib/progress-nudge.js +12 -4
  16. package/packages/agent-team/lib/session-event-cursor.js +116 -0
  17. package/packages/agent-team/lib/session-remediation.js +493 -0
  18. package/packages/agent-team/lib/spec.js +49 -36
  19. package/packages/agent-team/lib/stored-session-reader.js +138 -0
  20. package/packages/agent-team/lib/time-format.js +56 -0
  21. package/packages/agent-team/lib/typert.host.js +163 -63
  22. package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
  23. package/packages/agent-team/lib/typert.remote-client.js +105 -32
  24. package/packages/agent-team/lib/types/context-management.d.ts +13 -1
  25. package/packages/agent-team/lib/types/context-management.d.ts.map +1 -1
  26. package/packages/agent-team/lib/types/context-projection.d.ts +8 -0
  27. package/packages/agent-team/lib/types/context-projection.d.ts.map +1 -1
  28. package/packages/agent-team/lib/types/context-source.d.ts +70 -36
  29. package/packages/agent-team/lib/types/context-source.d.ts.map +1 -1
  30. package/packages/agent-team/lib/types/index.d.ts +110 -9
  31. package/packages/agent-team/lib/types/index.d.ts.map +1 -1
  32. package/packages/agent-team/lib/types/invariant.d.ts.map +1 -1
  33. package/packages/agent-team/lib/types/ledger.d.ts +205 -7
  34. package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
  35. package/packages/agent-team/lib/types/member-context.d.ts.map +1 -1
  36. package/packages/agent-team/lib/types/member-time-context.d.ts +83 -0
  37. package/packages/agent-team/lib/types/member-time-context.d.ts.map +1 -0
  38. package/packages/agent-team/lib/types/pressure-policy.d.ts +18 -0
  39. package/packages/agent-team/lib/types/pressure-policy.d.ts.map +1 -1
  40. package/packages/agent-team/lib/types/progress-nudge.d.ts.map +1 -1
  41. package/packages/agent-team/lib/types/session-event-cursor.d.ts +119 -0
  42. package/packages/agent-team/lib/types/session-event-cursor.d.ts.map +1 -0
  43. package/packages/agent-team/lib/types/session-remediation.d.ts +172 -0
  44. package/packages/agent-team/lib/types/session-remediation.d.ts.map +1 -0
  45. package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
  46. package/packages/agent-team/lib/types/stored-session-reader.d.ts +92 -0
  47. package/packages/agent-team/lib/types/stored-session-reader.d.ts.map +1 -0
  48. package/packages/agent-team/lib/types/time-format.d.ts +28 -0
  49. package/packages/agent-team/lib/types/time-format.d.ts.map +1 -0
  50. package/packages/agent-team/lib/types/types/entities.d.ts +36 -2
  51. package/packages/agent-team/lib/types/types/entities.d.ts.map +1 -1
  52. package/packages/agent-team/lib/types/types/operations.d.ts +45 -19
  53. package/packages/agent-team/lib/types/types/operations.d.ts.map +1 -1
  54. package/packages/agent-team/lib/types/types/requests-results.d.ts +50 -3
  55. package/packages/agent-team/lib/types/types/requests-results.d.ts.map +1 -1
  56. package/packages/agent-team/preset/team-member/agent.cordis.yml +21 -4
  57. package/packages/client-agent-team/README.md +1 -1
  58. package/packages/client-agent-team/README.zh.md +1 -1
  59. package/packages/client-agent-team/lib/client.js +1121 -402
  60. package/packages/client-agent-team/lib/client.js.map +1 -1
  61. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
  62. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +24 -4
  63. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
  64. package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +42 -22
  65. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -1
  66. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +13 -24
  67. package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts +11 -5
  68. package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -1
  69. package/packages/client-agent-team/lib/types/client/TeamComposer.js +15 -9
  70. package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts +1 -1
  71. package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts.map +1 -1
  72. package/packages/client-agent-team/lib/types/client/TeamConversation.js +8 -1
  73. package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts +21 -0
  74. package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts.map +1 -0
  75. package/packages/client-agent-team/lib/types/client/TeamInboxPage.js +71 -0
  76. package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts.map +1 -1
  77. package/packages/client-agent-team/lib/types/client/TeamMemberEditor.js +8 -24
  78. package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts +9 -2
  79. package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
  80. package/packages/client-agent-team/lib/types/client/TeamMessage.js +13 -6
  81. package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts +8 -0
  82. package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts.map +1 -1
  83. package/packages/client-agent-team/lib/types/client/TeamPresenceDot.js +21 -1
  84. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
  85. package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +106 -32
  86. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts +1 -1
  87. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts.map +1 -1
  88. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +50 -7
  89. package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
  90. package/packages/client-agent-team/lib/types/client/index.js +25 -9
  91. package/packages/client-agent-team/lib/types/client/locales.d.ts +18 -0
  92. package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
  93. package/packages/client-agent-team/lib/types/client/locales.js +18 -0
  94. package/packages/client-agent-team/lib/types/client/navigation.d.ts +10 -0
  95. package/packages/client-agent-team/lib/types/client/navigation.d.ts.map +1 -1
  96. package/packages/client-agent-team/lib/types/client/navigation.js +18 -7
  97. package/packages/client-agent-team/lib/types/client/scope-coverage.d.ts +41 -0
  98. package/packages/client-agent-team/lib/types/client/scope-coverage.d.ts.map +1 -0
  99. package/packages/client-agent-team/lib/types/client/scope-coverage.js +64 -0
  100. package/packages/client-agent-team/lib/types/client/slots.d.ts +8 -2
  101. package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
  102. package/packages/client-agent-team/lib/types/client/team-changes.d.ts +26 -1
  103. package/packages/client-agent-team/lib/types/client/team-changes.d.ts.map +1 -1
  104. package/packages/client-agent-team/lib/types/client/team-changes.js +66 -15
  105. package/packages/client-agent-team/lib/types/client/team-dialog-save.d.ts +20 -0
  106. package/packages/client-agent-team/lib/types/client/team-dialog-save.d.ts.map +1 -0
  107. package/packages/client-agent-team/lib/types/client/team-dialog-save.js +36 -0
  108. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +9 -0
  109. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts.map +1 -1
  110. package/packages/client-agent-team/lib/types/client/team-formatters.js +34 -6
  111. package/packages/tool-agent-team/README.md +8 -6
  112. package/packages/tool-agent-team/README.zh.md +8 -6
  113. package/packages/tool-agent-team/lib/context-tools.js +18 -14
  114. package/packages/tool-agent-team/lib/host-access.js +20 -0
  115. package/packages/tool-agent-team/lib/index.js +314 -123
  116. package/packages/tool-agent-team/lib/types/context-tools.d.ts.map +1 -1
  117. package/packages/tool-agent-team/lib/types/host-access.d.ts +14 -0
  118. package/packages/tool-agent-team/lib/types/host-access.d.ts.map +1 -0
  119. package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"TeamAgentsPanel.d.ts","sourceRoot":"","sources":["../../../src/client/TeamAgentsPanel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,yBAAyB,EACzB,2BAA2B,EAE5B,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kDAAkD,CAAA;AAEnF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAYlD,UAAU,oBAAoB;IAC5B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAA;IACrD,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,kBAAkB,CAAC,CAAA;IAC/D,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAC,WAAW,CAAC,CAAA;IACjD,QAAQ,CAAC,YAAY,EAAE,gBAAgB,CAAC,cAAc,CAAC,CAAA;IACvD,QAAQ,CAAC,aAAa,EAAE,gBAAgB,CAAC,eAAe,CAAC,CAAA;IACzD,QAAQ,CAAC,aAAa,EAAE,gBAAgB,CAAC,eAAe,CAAC,CAAA;IACzD,QAAQ,CAAC,UAAU,EAAE,gBAAgB,CAAC,YAAY,CAAC,CAAA;IACnD,8EAA8E;IAC9E,QAAQ,CAAC,eAAe,CAAC,EAAE,2BAA2B,CAAC,QAAQ,CAAC,CAAC,WAAW,CAAC,CAAA;IAC7E,QAAQ,CAAC,iBAAiB,EAAE,gBAAgB,CAAC,mBAAmB,CAAC,CAAA;IACjE,QAAQ,CAAC,gBAAgB,EAAE,CAAC,OAAO,EAAE,yBAAyB,EAAE,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAA;IAC1F,QAAQ,CAAC,CAAC,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAA;CAClC;AAED,wBAAgB,eAAe,CAAC,EAAE,WAAW,EAAE,WAAW,EAAE,gBAAgB,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,UAAU,EAAE,eAAe,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,CAAC,EAAE,EAAE,oBAAoB,+BA2N/N"}
1
+ {"version":3,"file":"TeamAgentsPanel.d.ts","sourceRoot":"","sources":["../../../src/client/TeamAgentsPanel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,yBAAyB,EACzB,2BAA2B,EAE5B,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kDAAkD,CAAA;AAEnF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAalD,UAAU,oBAAoB;IAC5B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAA;IACrD,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,kBAAkB,CAAC,CAAA;IAC/D,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAC,WAAW,CAAC,CAAA;IACjD,QAAQ,CAAC,YAAY,EAAE,gBAAgB,CAAC,cAAc,CAAC,CAAA;IACvD,QAAQ,CAAC,aAAa,EAAE,gBAAgB,CAAC,eAAe,CAAC,CAAA;IACzD,QAAQ,CAAC,aAAa,EAAE,gBAAgB,CAAC,eAAe,CAAC,CAAA;IACzD,QAAQ,CAAC,UAAU,EAAE,gBAAgB,CAAC,YAAY,CAAC,CAAA;IACnD,8EAA8E;IAC9E,QAAQ,CAAC,eAAe,CAAC,EAAE,2BAA2B,CAAC,QAAQ,CAAC,CAAC,WAAW,CAAC,CAAA;IAC7E,QAAQ,CAAC,iBAAiB,EAAE,gBAAgB,CAAC,mBAAmB,CAAC,CAAA;IACjE,QAAQ,CAAC,gBAAgB,EAAE,CAAC,OAAO,EAAE,yBAAyB,EAAE,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAA;IAC1F,QAAQ,CAAC,CAAC,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAA;CAClC;AAED,wBAAgB,eAAe,CAAC,EAAE,WAAW,EAAE,WAAW,EAAE,gBAAgB,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,UAAU,EAAE,eAAe,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,CAAC,EAAE,EAAE,oBAAoB,+BAwO/N"}
@@ -6,6 +6,7 @@ import { SortableRow, useSidebarRowDrag } from "./sidebar-drag.js";
6
6
  import { moveSidebarItem, useSidebarOrder } from "./sidebar-order.js";
7
7
  import { useSidebarSectionOpen, setSidebarSectionOpen } from "./sidebar-sections.js";
8
8
  import { mintRequestId } from "./requests.js";
9
+ import { diagnosticText, restartOffered } from "./TeamPresenceDot.js";
9
10
  import { TeamRowMenu } from "./TeamRowMenu.js";
10
11
  import { TeamSidebarSection } from "./TeamSidebarSection.js";
11
12
  import { AgentEditorDialog, ModelPickerField, sameModel } from "./TeamMemberEditor.js";
@@ -87,8 +88,12 @@ export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, ad
87
88
  }
88
89
  }
89
90
  }, []);
91
+ // Only the first refresh owns the loading surface; presence wakes (Agent
92
+ // running/idle) ride the dedicated scope and refresh rows in place.
93
+ const loadedRef = useRef(false);
90
94
  const refresh = useCallback(async () => {
91
- setLoading(true);
95
+ if (!loadedRef.current)
96
+ setLoading(true);
92
97
  const result = await loadMembers({ workspaceId });
93
98
  if (result.ok) {
94
99
  // Archived Members are hidden from every surface; the row disappears
@@ -97,6 +102,7 @@ export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, ad
97
102
  setMembers(next);
98
103
  followRollover(next);
99
104
  setError(undefined);
105
+ loadedRef.current = true;
100
106
  }
101
107
  else {
102
108
  setError(result.error.message);
@@ -113,6 +119,15 @@ export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, ad
113
119
  // rides every workspace invalidation.
114
120
  void refresh();
115
121
  }), [subscribeChanges, refresh, workspaceId]);
122
+ // Presence transitions commit nothing: the Host wakes only this scope, so
123
+ // the green dots stay live without any catalog refetch.
124
+ useEffect(() => subscribeChanges({ kind: 'presence', workspaceId }, update => {
125
+ if (update.type === 'failed') {
126
+ setError(update.message);
127
+ return;
128
+ }
129
+ void refresh();
130
+ }), [subscribeChanges, refresh, workspaceId]);
116
131
  const closeForm = () => {
117
132
  if (creating)
118
133
  return;
@@ -138,7 +153,7 @@ export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, ad
138
153
  setModel(undefined);
139
154
  setFormOpen(false);
140
155
  if (result.value.status.presence === 'unavailable') {
141
- setError(result.value.status.diagnostic ?? t('statusUnavailable'));
156
+ setError(diagnosticText(result.value.status) || t('statusUnavailable'));
142
157
  }
143
158
  else {
144
159
  setRetryRequest(undefined);
@@ -207,7 +222,12 @@ function AgentRow({ status, current, updateMember, recoverMember, archiveMember,
207
222
  return;
208
223
  }
209
224
  if (result.value.status.availability === 'unavailable') {
210
- setRowAlert(t('restartStillUnavailable', { diagnostic: result.value.status.diagnostic ?? t('statusUnavailable') }));
225
+ const diagnostic = result.value.status.diagnostic;
226
+ if (diagnostic?.class === 'session-refused' && diagnostic.remediable === false) {
227
+ setRowAlert(t('restartRefusedNoRestart', { detail: diagnosticText(result.value.status) }));
228
+ return;
229
+ }
230
+ setRowAlert(t('restartStillUnavailable', { diagnostic: diagnosticText(result.value.status) || t('statusUnavailable') }));
211
231
  return;
212
232
  }
213
233
  setRowAlert(undefined);
@@ -238,7 +258,7 @@ function AgentRow({ status, current, updateMember, recoverMember, archiveMember,
238
258
  return (_jsxs(_Fragment, { children: [_jsxs("div", { className: css.agentRow, "data-menu-open": menuOpen || undefined, children: [_jsxs("button", { type: "button", className: css.agentSelect, "aria-label": t('openAgentSession', { name: status.member.handle }), "aria-current": current ? 'page' : undefined, disabled: status.availability !== 'active', onClick: () => { openMemberSession(status.member.sessionId); }, children: [_jsx(TeamMemberAvatar, { status: status, t: t }), _jsxs("span", { className: css.agentCopy, children: [_jsx("strong", { children: status.member.handle }), _jsx("small", { children: status.member.description })] })] }), _jsx("span", { className: css.rowMenu, children: _jsx(TeamRowMenu, { label: t('actionsAgent', { name: status.member.handle }), items: [
239
259
  { id: 'edit', label: t('editAgent'), icon: _jsx(IconEditOutline16, {}) },
240
260
  ...(status.presence === 'error' ? [{ id: 'resume', label: t('resumeAgent'), icon: _jsx(IconPlayOutline16, {}) }] : []),
241
- ...(status.availability === 'unavailable' ? [{ id: 'restart', label: t('restartAgent'), icon: _jsx(IconRefreshOutline16, {}) }] : []),
261
+ ...(status.availability === 'unavailable' && restartOffered(status) ? [{ id: 'restart', label: t('restartAgent'), icon: _jsx(IconRefreshOutline16, {}) }] : []),
242
262
  { id: 'archive', label: t('archiveAgent'), icon: _jsx(IconArchiveOutline20, { size: 16 }), danger: true },
243
263
  ], onSelect: (id) => {
244
264
  if (id === 'edit')
@@ -1 +1 @@
1
- {"version":3,"file":"TeamChannelPage.d.ts","sourceRoot":"","sources":["../../../src/client/TeamChannelPage.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAA+B,mBAAmB,EAE7D,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kDAAkD,CAAA;AAEnF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAIvD,OAAO,KAAK,EAAgB,cAAc,EAAE,MAAM,aAAa,CAAA;AAe/D,UAAU,oBAAoB;IAC5B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,QAAQ,CAAC,UAAU,EAAE,mBAAmB,CAAA;IACxC,QAAQ,CAAC,YAAY,EAAE,qBAAqB,CAAC,cAAc,CAAC,CAAA;IAC5D,QAAQ,CAAC,gBAAgB,EAAE,qBAAqB,CAAC,kBAAkB,CAAC,CAAA;IACpE,QAAQ,CAAC,WAAW,EAAE,qBAAqB,CAAC,aAAa,CAAC,CAAA;IAC1D,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAA;IAC/B,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,WAAW,EAAE,qBAAqB,CAAC,aAAa,CAAC,CAAA;IAC1D,QAAQ,CAAC,WAAW,EAAE,qBAAqB,CAAC,aAAa,CAAC,CAAA;IAC1D,QAAQ,CAAC,mBAAmB,EAAE,qBAAqB,CAAC,qBAAqB,CAAC,CAAA;IAC1E,QAAQ,CAAC,YAAY,EAAE,qBAAqB,CAAC,cAAc,CAAC,CAAA;IAC5D,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,eAAe,EAAE,qBAAqB,CAAC,iBAAiB,CAAC,CAAA;IAClE,QAAQ,CAAC,cAAc,EAAE,qBAAqB,CAAC,gBAAgB,CAAC,CAAA;IAChE,QAAQ,CAAC,CAAC,EAAE,qBAAqB,CAAC,GAAG,CAAC,CAAA;CACvC;AAmBD,wBAAgB,eAAe,CAAC,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,EAAE,mBAAmB,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,eAAe,EAAE,CAAC,EAAE,EAAE,oBAAoB,+BA8UnR"}
1
+ {"version":3,"file":"TeamChannelPage.d.ts","sourceRoot":"","sources":["../../../src/client/TeamChannelPage.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAA+B,mBAAmB,EAE7D,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kDAAkD,CAAA;AAEnF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAEvD,OAAO,KAAK,EAAgB,cAAc,EAAE,MAAM,aAAa,CAAA;AAe/D,UAAU,oBAAoB;IAC5B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,QAAQ,CAAC,UAAU,EAAE,mBAAmB,CAAA;IACxC,QAAQ,CAAC,YAAY,EAAE,qBAAqB,CAAC,cAAc,CAAC,CAAA;IAC5D,QAAQ,CAAC,gBAAgB,EAAE,qBAAqB,CAAC,kBAAkB,CAAC,CAAA;IACpE,QAAQ,CAAC,WAAW,EAAE,qBAAqB,CAAC,aAAa,CAAC,CAAA;IAC1D,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAA;IAC/B,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,WAAW,EAAE,qBAAqB,CAAC,aAAa,CAAC,CAAA;IAC1D,QAAQ,CAAC,WAAW,EAAE,qBAAqB,CAAC,aAAa,CAAC,CAAA;IAC1D,QAAQ,CAAC,mBAAmB,EAAE,qBAAqB,CAAC,qBAAqB,CAAC,CAAA;IAC1E,QAAQ,CAAC,YAAY,EAAE,qBAAqB,CAAC,cAAc,CAAC,CAAA;IAC5D,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,eAAe,EAAE,qBAAqB,CAAC,iBAAiB,CAAC,CAAA;IAClE,QAAQ,CAAC,cAAc,EAAE,qBAAqB,CAAC,gBAAgB,CAAC,CAAA;IAChE,QAAQ,CAAC,CAAC,EAAE,qBAAqB,CAAC,GAAG,CAAC,CAAA;CACvC;AAmBD,wBAAgB,eAAe,CAAC,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,EAAE,mBAAmB,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,eAAe,EAAE,CAAC,EAAE,EAAE,oBAAoB,+BA4VnR"}
@@ -1,8 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Fragment, useEffect, useRef, useState, useSyncExternalStore } from 'react';
2
+ import { Fragment, useCallback, useEffect, useRef, useState } from 'react';
3
3
  import { Button, IconChevronLeftOutline14, IconChevronRightOutline14, Modal } from '@deepseek-ai/dsh-client-ui-primitives';
4
- import { bytesToBase64 } from "./attachment-preview.js";
5
- import { mintRequestId } from "./requests.js";
4
+ import { mintRequestId, uploadComposerFiles } from "./requests.js";
6
5
  import { TeamComposer } from "./TeamComposer.js";
7
6
  import { TeamPresenceDot } from "./TeamPresenceDot.js";
8
7
  import { TeamMessage } from "./TeamMessage.js";
@@ -45,11 +44,15 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
45
44
  const [managingMembers, setManagingMembers] = useState(false);
46
45
  // The composer draft lives in the keyed draft cache: view switches unmount
47
46
  // this page, and a refresh must not cost the half-written message either.
47
+ // The composer owns the subscription — this page only reads a snapshot when
48
+ // it sends, so typing never re-renders the timeline.
48
49
  const draftKey = `channel:${channelRef}`;
49
- const { draft, recipients } = useSyncExternalStore(drafts.subscribe, () => drafts.getSnapshot(draftKey));
50
50
  const manageTriggerRef = useRef(null);
51
51
  const memberListRef = useRef(null);
52
52
  const mountedRef = useRef(false);
53
+ // Flips after the first successful timeline load; later change wakes
54
+ // refresh in place instead of showing the loading surface again.
55
+ const loadedRef = useRef(false);
53
56
  const refreshSequenceRef = useRef(0);
54
57
  const channelLastItem = view?.items[view.items.length - 1];
55
58
  // Branded-ref navigation for message bodies: channel refs hop directly,
@@ -106,7 +109,10 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
106
109
  return false;
107
110
  const sequence = refreshSequenceRef.current + 1;
108
111
  refreshSequenceRef.current = sequence;
109
- setLoading(true);
112
+ // Only the first refresh owns the loading surface; change wakes refresh
113
+ // the rendered timeline in place instead of flashing it back to skeleton.
114
+ if (!loadedRef.current)
115
+ setLoading(true);
110
116
  if (clearError) {
111
117
  setError(undefined);
112
118
  setStatusMessage(undefined);
@@ -118,8 +124,10 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
118
124
  ]);
119
125
  if (!mountedRef.current || sequence !== refreshSequenceRef.current)
120
126
  return false;
121
- if (loaded.ok)
127
+ if (loaded.ok) {
122
128
  setView(current => current === undefined ? loaded.value : mergeChannelView(current, loaded.value));
129
+ loadedRef.current = true;
130
+ }
123
131
  else
124
132
  setError(loaded.error.message);
125
133
  if (loadedMembers.ok)
@@ -159,6 +167,7 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
159
167
  };
160
168
  useEffect(() => {
161
169
  mountedRef.current = true;
170
+ loadedRef.current = false;
162
171
  setView(undefined);
163
172
  setError(undefined);
164
173
  setLoading(true);
@@ -183,6 +192,17 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
183
192
  }
184
193
  void refreshMembers();
185
194
  }),
195
+ // Presence transitions commit nothing: only the member rows move, so
196
+ // the header presence counts refresh without a timeline refetch.
197
+ subscribeChanges({ kind: 'presence', workspaceId }, update => {
198
+ if (!mountedRef.current)
199
+ return;
200
+ if (update.type === 'failed') {
201
+ setError(update.message);
202
+ return;
203
+ }
204
+ void refreshMembers();
205
+ }),
186
206
  ];
187
207
  return () => {
188
208
  mountedRef.current = false;
@@ -227,7 +247,16 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
227
247
  // Retained across transport failures so a replayed send dedupes on the Host;
228
248
  // definitive outcomes (committed or rejected) start the next send fresh.
229
249
  const pendingSendId = useRef();
250
+ // Editing the draft invalidates the one-shot status line. The setter returns
251
+ // the same state when there is nothing to clear, so a keystroke does not
252
+ // re-render the timeline now that the composer owns the draft.
253
+ const clearSendState = useCallback(() => {
254
+ setStatusMessage(current => current === undefined ? current : undefined);
255
+ }, []);
230
256
  const send = async () => {
257
+ // Read the draft at send time: this page no longer subscribes to it, so a
258
+ // captured render value would be stale after the composer's own edits.
259
+ const { draft, recipients } = drafts.getSnapshot(draftKey);
231
260
  if (pending || draft.trim() === '')
232
261
  return;
233
262
  const recipientIds = [...recipients].sort();
@@ -239,22 +268,13 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
239
268
  try {
240
269
  // Upload chosen files first; any failure aborts the send with the
241
270
  // existing error surface and keeps the chips for a retry.
242
- const attachmentIds = [];
243
- for (const file of pendingFiles) {
244
- const uploaded = await putAttachment({
245
- requestId: mintRequestId(),
246
- workspaceId,
247
- name: file.name,
248
- mediaType: file.type === '' ? undefined : file.type,
249
- bytesBase64: bytesToBase64(new Uint8Array(await file.arrayBuffer())),
250
- });
251
- if (!uploaded.ok) {
252
- pendingSendId.current = undefined;
253
- setError(uploaded.error.message);
254
- return;
255
- }
256
- attachmentIds.push(uploaded.value.attachmentId);
271
+ const upload = await uploadComposerFiles(putAttachment, workspaceId, pendingFiles);
272
+ if (!upload.ok) {
273
+ pendingSendId.current = undefined;
274
+ setError(upload.error);
275
+ return;
257
276
  }
277
+ const attachmentIds = upload.attachmentIds;
258
278
  const request = {
259
279
  requestId, workspaceId,
260
280
  channelRef, body: draft.trim(), recipients: recipientIds,
@@ -306,5 +326,5 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
306
326
  const turnGap = isRunGap(index > 0 ? block.items[index - 1].message.occurredAt : undefined, item.message.occurredAt);
307
327
  // A turn divider already labels the gapped entry; its row stays chrome-free.
308
328
  return _jsxs(Fragment, { children: [turnGap && _jsx(TeamRunDivider, { occurredAt: item.message.occurredAt }), _jsxs(TeamMessage, { senderName: sender, memberId: item.message.sender, human: human, body: item.message.body, attachments: item.message.attachments, loadAttachment: getAttachment, t: t, occurredAt: item.message.occurredAt, mentionNames: mentionNamesOf(item.mentions, mentionHandlesMap), onOpenRef: openRef, onResolveTaskRefs: lookupTaskRefs, grouped: index > 0, showGroupedTime: item.message.topLevel === true && !turnGap, ...(senderStatus === undefined ? {} : { senderTitle: senderStatus.member.description }), children: [item.message.topLevel && item.task !== undefined && item.taskNumber !== undefined && _jsxs("button", { type: "button", className: channelCss.taskFooter, "aria-label": t('openTask', { number: item.taskNumber }), onClick: () => { selectThread(item.thread.threadRef, channelRef, item.task?.taskRef, item.taskNumber); }, children: [_jsx("span", { className: channelCss.taskDot, children: _jsx(TeamStateDot, { size: 8, state: taskStatusDot(item.task.status) }) }), _jsx("span", { className: channelCss.taskNumber, children: t('taskLabel', { number: item.taskNumber }) }), _jsx("span", { className: channelCss.taskStatus, children: formatTaskStatus(item.task.status, t) }), _jsx("span", { className: channelCss.taskCount, children: t('taskMessageCount', { count: item.messageCount }) }), _jsx("span", { className: channelCss.taskArrow, "aria-hidden": "true", children: _jsx(IconChevronRightOutline14, { size: 12 }) })] }), item.message.topLevel && item.task === undefined && _jsxs("button", { type: "button", className: channelCss.taskFooter, "aria-label": t('openThread'), onClick: () => { selectThread(item.thread.threadRef, channelRef); }, children: [_jsx("span", { className: channelCss.taskNumber, children: t('threadLabel') }), _jsx("span", { className: channelCss.taskCount, children: t('taskMessageCount', { count: item.messageCount }) }), _jsx("span", { className: channelCss.taskArrow, "aria-hidden": "true", children: _jsx(IconChevronRightOutline14, { size: 12 }) })] })] })] }, item.message.messageRef);
309
- }) }, `run-${block.items[0].message.messageRef}`))] }) }), channel !== undefined ? _jsx(TeamComposer, { members: channelMembers, recipients: recipients, draft: draft, pending: pending, ...(statusMessage === undefined ? {} : { confirmation: statusMessage }), ...(error === undefined ? {} : { error }), onDraftChange: next => { drafts.writeDraft(draftKey, next); setStatusMessage(undefined); }, onRecipientsChange: next => { drafts.writeRecipients(draftKey, next); setStatusMessage(undefined); }, onSubmit: () => { void send(); }, pendingFiles: pendingFiles, onFilesChange: setPendingFiles, asTask: asTask, onAsTaskChange: setAsTask, t: t }, draftKey) : _jsx("div", {})] });
329
+ }) }, `run-${block.items[0].message.messageRef}`))] }) }), channel !== undefined ? _jsx(TeamComposer, { members: channelMembers, drafts: drafts, draftKey: draftKey, pending: pending, ...(statusMessage === undefined ? {} : { confirmation: statusMessage }), ...(error === undefined ? {} : { error }), onEdit: clearSendState, onSubmit: () => { void send(); }, pendingFiles: pendingFiles, onFilesChange: setPendingFiles, asTask: asTask, onAsTaskChange: setAsTask, t: t }, draftKey) : _jsx("div", {})] });
310
330
  }
@@ -1 +1 @@
1
- {"version":3,"file":"TeamChannelsPanel.d.ts","sourceRoot":"","sources":["../../../src/client/TeamChannelsPanel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,yBAAyB,EAEzB,mBAAmB,EAMpB,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kDAAkD,CAAA;AAEnF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAalD,UAAU,sBAAsB;IAC9B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAA;IACrD,QAAQ,CAAC,YAAY,EAAE,gBAAgB,CAAC,cAAc,CAAC,CAAA;IACvD,wGAAwG;IACxG,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,kBAAkB,CAAC,CAAA;IAC/D,QAAQ,CAAC,aAAa,EAAE,gBAAgB,CAAC,eAAe,CAAC,CAAA;IACzD,QAAQ,CAAC,aAAa,EAAE,gBAAgB,CAAC,eAAe,CAAC,CAAA;IACzD,QAAQ,CAAC,cAAc,EAAE,gBAAgB,CAAC,gBAAgB,CAAC,CAAA;IAC3D,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAA;IACrD,QAAQ,CAAC,mBAAmB,EAAE,gBAAgB,CAAC,qBAAqB,CAAC,CAAA;IACrE,QAAQ,CAAC,cAAc,EAAE,SAAS,yBAAyB,EAAE,CAAA;IAC7D,QAAQ,CAAC,kBAAkB,CAAC,EAAE,mBAAmB,CAAA;IACjD,QAAQ,CAAC,aAAa,EAAE,gBAAgB,CAAC,eAAe,CAAC,CAAA;IACzD,QAAQ,CAAC,CAAC,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAA;CAClC;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,+BA+L9D"}
1
+ {"version":3,"file":"TeamChannelsPanel.d.ts","sourceRoot":"","sources":["../../../src/client/TeamChannelsPanel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,yBAAyB,EAEzB,mBAAmB,EAMpB,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kDAAkD,CAAA;AAEnF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAclD,UAAU,sBAAsB;IAC9B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAA;IACrD,QAAQ,CAAC,YAAY,EAAE,gBAAgB,CAAC,cAAc,CAAC,CAAA;IACvD,wGAAwG;IACxG,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,kBAAkB,CAAC,CAAA;IAC/D,QAAQ,CAAC,aAAa,EAAE,gBAAgB,CAAC,eAAe,CAAC,CAAA;IACzD,QAAQ,CAAC,aAAa,EAAE,gBAAgB,CAAC,eAAe,CAAC,CAAA;IACzD,QAAQ,CAAC,cAAc,EAAE,gBAAgB,CAAC,gBAAgB,CAAC,CAAA;IAC3D,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAA;IACrD,QAAQ,CAAC,mBAAmB,EAAE,gBAAgB,CAAC,qBAAqB,CAAC,CAAA;IACrE,QAAQ,CAAC,cAAc,EAAE,SAAS,yBAAyB,EAAE,CAAA;IAC7D,QAAQ,CAAC,kBAAkB,CAAC,EAAE,mBAAmB,CAAA;IACjD,QAAQ,CAAC,aAAa,EAAE,gBAAgB,CAAC,eAAe,CAAC,CAAA;IACzD,QAAQ,CAAC,CAAC,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAA;CAClC;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,+BAmM9D"}
@@ -10,6 +10,7 @@ import { mintRequestId } from "./requests.js";
10
10
  import { TeamRowMenu } from "./TeamRowMenu.js";
11
11
  import { TeamSidebarSection } from "./TeamSidebarSection.js";
12
12
  import { useChannelMembership } from "./team-membership.js";
13
+ import { useEditDialogSave } from "./team-dialog-save.js";
13
14
  import createCss from './create.module.css';
14
15
  import css from './sidebar.module.css';
15
16
  export function TeamChannelsPanel(props) {
@@ -39,8 +40,12 @@ export function TeamChannelsPanel(props) {
39
40
  };
40
41
  const drag = useSidebarRowDrag({ refs: orderedChannelRefs, onCommit: applyMove });
41
42
  const sectionOpen = useSidebarSectionOpen(workspaceId, 'channels');
43
+ // Only the first refresh owns the loading surface; later wakes (workspace
44
+ // catalog changes, member creation) refresh the rendered rows in place.
45
+ const loadedRef = useRef(false);
42
46
  const refresh = useCallback(async () => {
43
- setLoading(true);
47
+ if (!loadedRef.current)
48
+ setLoading(true);
44
49
  const [channelResult, memberResult] = await Promise.all([
45
50
  loadChannels({ workspaceId, limit: 1 }),
46
51
  loadMembers({ workspaceId }),
@@ -53,6 +58,7 @@ export function TeamChannelsPanel(props) {
53
58
  .map(status => status.member.memberId));
54
59
  setSelected(current => new Set([...current].filter(memberId => selectable.has(memberId))));
55
60
  setError(undefined);
61
+ loadedRef.current = true;
56
62
  }
57
63
  else if (!channelResult.ok) {
58
64
  setError(channelResult.error.message);
@@ -193,9 +199,11 @@ function ChannelRow({ channel, members, joinedIds, selected, updateChannel, arch
193
199
  function ChannelEditorDialog({ channel, members, joinedIds, updateChannel, joinChannel, removeChannelMember, onCommitted, onClose, t }) {
194
200
  const [name, setName] = useState(channel.name);
195
201
  const [description, setDescription] = useState(channel.description);
196
- const [saving, setSaving] = useState(false);
197
- const [error, setError] = useState();
198
- const pendingRequest = useRef();
202
+ const { saving, error, pendingRequest, save } = useEditDialogSave({
203
+ save: updateChannel,
204
+ onCommitted,
205
+ onClose,
206
+ });
199
207
  const membership = useChannelMembership({ joinChannel, removeChannelMember }, change => change.memberId, async () => { await onCommitted(); });
200
208
  const dirty = name.trim() !== channel.name || description.trim() !== channel.description;
201
209
  const submit = async (event) => {
@@ -213,26 +221,7 @@ function ChannelEditorDialog({ channel, members, joinedIds, updateChannel, joinC
213
221
  name: normalizedName,
214
222
  description: normalizedDescription,
215
223
  };
216
- pendingRequest.current = request;
217
- setSaving(true);
218
- setError(undefined);
219
- try {
220
- const result = await updateChannel(request);
221
- if (result.ok) {
222
- pendingRequest.current = undefined;
223
- await onCommitted();
224
- onClose();
225
- }
226
- else {
227
- setError(result.error.message);
228
- }
229
- }
230
- catch (cause) {
231
- setError(cause instanceof Error ? cause.message : String(cause));
232
- }
233
- finally {
234
- setSaving(false);
235
- }
224
+ await save(request);
236
225
  };
237
226
  return (_jsx(Modal, { open: true, onClose: onClose, title: t('editChannel'), description: `# ${channel.name}`, closeLabel: t('close'), contentClassName: createCss.dialogContent, footer: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", disabled: saving, onClick: onClose, children: t('cancel') }), _jsx(Button, { type: "submit", form: "team-channel-edit-form", variant: "primary", disabled: saving || !dirty || name.trim() === '', children: saving ? t('editSaving') : t('editSave') })] }), children: _jsxs("form", { id: "team-channel-edit-form", className: createCss.form, onSubmit: event => { void submit(event); }, children: [_jsxs("label", { className: createCss.field, children: [_jsx("span", { children: t('channelName') }), _jsx(Input, { className: createCss.input, value: name, onChange: event => { setName(event.target.value); pendingRequest.current = undefined; }, disabled: saving, autoFocus: true })] }), _jsxs("label", { className: createCss.field, children: [_jsxs("span", { children: [t('channelDescription'), t('optionalSuffix')] }), _jsx(Input, { className: createCss.input, value: description, placeholder: t('agentDescriptionPlaceholder'), onChange: event => { setDescription(event.target.value); pendingRequest.current = undefined; }, disabled: saving })] }), _jsxs("fieldset", { className: createCss.memberPicker, disabled: saving, children: [_jsx("legend", { children: t('channelMembersSection') }), members.map(status => {
238
227
  const joined = joinedIds.has(status.member.memberId);
@@ -1,16 +1,22 @@
1
1
  import type { AgentTeamClientMemberStatus, AgentTeamMemberId } from '@wowyuarm/dsh-agent-team/types';
2
2
  import type { TeamConversationProps } from './slots.ts';
3
- export declare function TeamComposer({ members, followerMemberIds, recipients, draft, pending, confirmation, error, onDraftChange, onRecipientsChange, onSubmit, placeholder, pendingFiles, onFilesChange, asTask, onAsTaskChange, t }: {
3
+ import type { TeamDraftKey, TeamDraftStore } from './drafts.ts';
4
+ export declare function TeamComposer({ members, followerMemberIds, drafts, draftKey, pending, confirmation, error, onEdit, onSubmit, placeholder, pendingFiles, onFilesChange, asTask, onAsTaskChange, t }: {
4
5
  readonly members: readonly AgentTeamClientMemberStatus[];
5
6
  /** Current Thread followers; the Thread surface passes them so they rank above other candidates. */
6
7
  readonly followerMemberIds?: ReadonlySet<AgentTeamMemberId>;
7
- readonly recipients: ReadonlySet<AgentTeamMemberId>;
8
- readonly draft: string;
8
+ /**
9
+ * The draft cache this composer subscribes to: owning the subscription here
10
+ * instead of in the hosting page keeps a keystroke from re-rendering the
11
+ * timeline around it.
12
+ */
13
+ readonly drafts: TeamDraftStore;
14
+ readonly draftKey: TeamDraftKey;
9
15
  readonly pending: boolean;
10
16
  readonly confirmation?: string;
11
17
  readonly error?: string;
12
- readonly onDraftChange: (draft: string) => void;
13
- readonly onRecipientsChange: (recipients: ReadonlySet<AgentTeamMemberId>) => void;
18
+ /** The Human edited the draft: the hosting page drops its one-shot send state. */
19
+ readonly onEdit?: () => void;
14
20
  readonly onSubmit: () => void;
15
21
  /** Conversation-specific prompt; the shared default fits Channel surfaces. */
16
22
  readonly placeholder?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"TeamComposer.d.ts","sourceRoot":"","sources":["../../../src/client/TeamComposer.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,2BAA2B,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAEpG,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAqEvD,wBAAgB,YAAY,CAAC,EAAE,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC,EAAE,EAAE;IAC9N,QAAQ,CAAC,OAAO,EAAE,SAAS,2BAA2B,EAAE,CAAA;IACxD,oGAAoG;IACpG,QAAQ,CAAC,iBAAiB,CAAC,EAAE,WAAW,CAAC,iBAAiB,CAAC,CAAA;IAC3D,QAAQ,CAAC,UAAU,EAAE,WAAW,CAAC,iBAAiB,CAAC,CAAA;IACnD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/C,QAAQ,CAAC,kBAAkB,EAAE,CAAC,UAAU,EAAE,WAAW,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAAA;IACjF,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAA;IAC7B,8EAA8E;IAC9E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,gGAAgG;IAChG,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,IAAI,EAAE,CAAA;IACvC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,IAAI,EAAE,KAAK,IAAI,CAAA;IACzD,gFAAgF;IAChF,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAA;IACnD,QAAQ,CAAC,CAAC,EAAE,qBAAqB,CAAC,GAAG,CAAC,CAAA;CACvC,+BA6SA"}
1
+ {"version":3,"file":"TeamComposer.d.ts","sourceRoot":"","sources":["../../../src/client/TeamComposer.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,2BAA2B,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAEpG,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAqE/D,wBAAgB,YAAY,CAAC,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC,EAAE,EAAE;IAClM,QAAQ,CAAC,OAAO,EAAE,SAAS,2BAA2B,EAAE,CAAA;IACxD,oGAAoG;IACpG,QAAQ,CAAC,iBAAiB,CAAC,EAAE,WAAW,CAAC,iBAAiB,CAAC,CAAA;IAC3D;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAA;IAC/B,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAA;IAC/B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,kFAAkF;IAClF,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAA;IAC7B,8EAA8E;IAC9E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,gGAAgG;IAChG,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,IAAI,EAAE,CAAA;IACvC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,IAAI,EAAE,KAAK,IAAI,CAAA;IACzD,gFAAgF;IAChF,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAA;IACnD,QAAQ,CAAC,CAAC,EAAE,qBAAqB,CAAC,GAAG,CAAC,CAAA;CACvC,+BAyTA"}
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useEffect, useLayoutEffect, useRef, useState } from 'react';
3
- import { Button, IconPlusOutline16, IconSendOutline16, useAnchoredMaxHeight, useDismissOnOutsidePointer } from '@deepseek-ai/dsh-client-ui-primitives';
2
+ import { useEffect, useLayoutEffect, useRef, useState, useSyncExternalStore } from 'react';
3
+ import { IconChecklistOutline14, IconPaperclipOutline16, IconSendOutline16, Tooltip, useAnchoredMaxHeight, useDismissOnOutsidePointer } from '@deepseek-ai/dsh-client-ui-primitives';
4
4
  import { TeamPresenceDot } from "./TeamPresenceDot.js";
5
5
  import css from './composer.module.css';
6
6
  import { formatByteSize } from "./attachment-preview.js";
@@ -56,13 +56,17 @@ function draftPreviewUrl(file) {
56
56
  }
57
57
  return url;
58
58
  }
59
- export function TeamComposer({ members, followerMemberIds, recipients, draft, pending, confirmation, error, onDraftChange, onRecipientsChange, onSubmit, placeholder, pendingFiles, onFilesChange, asTask, onAsTaskChange, t }) {
59
+ export function TeamComposer({ members, followerMemberIds, drafts, draftKey, pending, confirmation, error, onEdit, onSubmit, placeholder, pendingFiles, onFilesChange, asTask, onAsTaskChange, t }) {
60
60
  const inputRef = useRef(null);
61
61
  const fileInputRef = useRef(null);
62
62
  const rootRef = useRef(null);
63
63
  const menuRef = useRef(null);
64
64
  const activeOptionRef = useRef(null);
65
65
  const composingRef = useRef(false);
66
+ // The draft lives in the injected cache so a view switch or refresh does not
67
+ // cost the half-written message. Subscribing here — not in the page that
68
+ // hosts this composer — is what keeps a keystroke off the timeline.
69
+ const { draft, recipients } = useSyncExternalStore(drafts.subscribe, () => drafts.getSnapshot(draftKey));
66
70
  const [mention, setMention] = useState();
67
71
  const [highlight, setHighlight] = useState(0);
68
72
  // @all is a composer-layer expansion of the same eligibility filter a
@@ -128,7 +132,7 @@ export function TeamComposer({ members, followerMemberIds, recipients, draft, pe
128
132
  return member !== undefined && containsMention(nextDraft, member.handle);
129
133
  }));
130
134
  if (next.size !== recipients.size)
131
- onRecipientsChange(next);
135
+ drafts.writeRecipients(draftKey, next);
132
136
  };
133
137
  // Restored drafts may carry recipients that no longer match the text (or
134
138
  // unknown Members); converge on mount and on every state change so the
@@ -147,15 +151,17 @@ export function TeamComposer({ members, followerMemberIds, recipients, draft, pe
147
151
  };
148
152
  const onChange = (event) => {
149
153
  const nextDraft = event.target.value;
150
- onDraftChange(nextDraft);
154
+ drafts.writeDraft(draftKey, nextDraft);
155
+ onEdit?.();
151
156
  pruneRecipients(nextDraft);
152
157
  updateMention(nextDraft, event.target.selectionStart ?? nextDraft.length);
153
158
  };
154
159
  // Both pick kinds share the same commit path: swap the text in, add the
155
160
  // recipients, close the menu, and restore the caret after focus.
156
161
  const commitMention = (nextDraft, nextCaret, nextRecipients) => {
157
- onDraftChange(nextDraft);
158
- onRecipientsChange(nextRecipients);
162
+ drafts.writeDraft(draftKey, nextDraft);
163
+ drafts.writeRecipients(draftKey, nextRecipients);
164
+ onEdit?.();
159
165
  setMention(undefined);
160
166
  setHighlight(0);
161
167
  requestAnimationFrame(() => {
@@ -254,12 +260,12 @@ export function TeamComposer({ members, followerMemberIds, recipients, draft, pe
254
260
  URL.revokeObjectURL(url);
255
261
  onFilesChange(pendingFiles.filter((_, candidate) => candidate !== index));
256
262
  }, children: "\u00D7" })] }, `${file.name}-${index}`);
257
- }) })), _jsxs("div", { className: css.toolbar, children: [onAsTaskChange !== undefined && (_jsx(Button, { size: "sm", variant: "outline", className: asTask === true ? css.asTaskOn : undefined, "aria-pressed": asTask === true, disabled: pending, onClick: () => { onAsTaskChange(asTask !== true); }, children: t('asTask') })), onFilesChange !== undefined && (_jsxs(_Fragment, { children: [_jsx("input", { ref: fileInputRef, type: "file", multiple: true, className: css.fileInput, "aria-hidden": "true", tabIndex: -1, onChange: event => {
263
+ }) })), _jsxs("div", { className: css.toolbar, children: [onFilesChange !== undefined && (_jsxs(_Fragment, { children: [_jsx("input", { ref: fileInputRef, type: "file", multiple: true, className: css.fileInput, "aria-hidden": "true", tabIndex: -1, onChange: event => {
258
264
  const chosen = [...event.target.files ?? []];
259
265
  if (chosen.length > 0 && pendingFiles !== undefined)
260
266
  onFilesChange([...pendingFiles, ...chosen]);
261
267
  event.target.value = '';
262
- } }), _jsx("button", { type: "button", className: css.attachButton, "aria-label": t('attachFiles'), title: t('attachFiles'), disabled: pending, onClick: () => { fileInputRef.current?.click(); }, children: _jsx(IconPlusOutline16, { size: 14 }) })] })), _jsx("button", { type: "submit", className: css.sendButton, "aria-label": pending ? t('sendingMessage') : t('sendMessage'), disabled: pending || draft.trim() === '', onMouseDown: event => {
268
+ } }), _jsx(Tooltip, { label: t('attachFiles'), side: "top", delayMs: 500, children: _jsx("button", { type: "button", className: css.attachButton, "aria-label": t('attachFiles'), disabled: pending, onClick: () => { fileInputRef.current?.click(); }, children: _jsx(IconPaperclipOutline16, { size: 14 }) }) })] })), onAsTaskChange !== undefined && (_jsxs("button", { type: "button", className: asTask === true ? `${css.asTaskPill} ${css.asTaskPillOn}` : css.asTaskPill, "aria-label": t('asTask'), "aria-pressed": asTask === true, title: t('asTask'), disabled: pending, onClick: () => { onAsTaskChange(asTask !== true); }, children: [_jsx(IconChecklistOutline14, { size: 14 }), _jsx("span", { className: css.asTaskLabel, children: t('asTask') })] })), _jsx("button", { type: "submit", className: css.sendButton, "aria-label": pending ? t('sendingMessage') : t('sendMessage'), disabled: pending || draft.trim() === '', onMouseDown: event => {
263
269
  // Keep the composer focused when the send control is clicked.
264
270
  event.preventDefault();
265
271
  inputRef.current?.focus({ preventScroll: true });
@@ -1,3 +1,3 @@
1
1
  import type { TeamConversationProps } from './slots.ts';
2
- export declare function TeamConversation({ t, useWorkspaces, navigation, drafts, putAttachment, getAttachment, loadChannels, readThread, loadThreadHistory, threadObservations, subscribeChanges, loadMembers, sendMessage, joinChannel, removeChannelMember, reply, changeTask, promoteThread, selectThread, selectChannel, backToWorkspace, backToChannels, resolveTaskRefs }: TeamConversationProps): import("react").JSX.Element;
2
+ export declare function TeamConversation({ t, useWorkspaces, navigation, drafts, putAttachment, getAttachment, loadChannels, readThread, loadThreadHistory, threadObservations, subscribeChanges, loadMembers, loadInbox, sendMessage, joinChannel, removeChannelMember, reply, changeTask, promoteThread, selectThread, selectChannel, selectWorkspace, backToWorkspace, backToChannels, resolveTaskRefs }: TeamConversationProps): import("react").JSX.Element;
3
3
  //# sourceMappingURL=TeamConversation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"TeamConversation.d.ts","sourceRoot":"","sources":["../../../src/client/TeamConversation.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAKvD,wBAAgB,gBAAgB,CAAC,EAAE,CAAC,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,UAAU,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,mBAAmB,EAAE,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,cAAc,EAAE,eAAe,EAAE,EAAE,qBAAqB,+BAoB7X"}
1
+ {"version":3,"file":"TeamConversation.d.ts","sourceRoot":"","sources":["../../../src/client/TeamConversation.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAMvD,wBAAgB,gBAAgB,CAAC,EAAE,CAAC,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,UAAU,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,mBAAmB,EAAE,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,eAAe,EAAE,cAAc,EAAE,eAAe,EAAE,EAAE,qBAAqB,+BA0BzZ"}
@@ -1,12 +1,19 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useSyncExternalStore } from 'react';
3
3
  import { TeamChannelPage } from "./TeamChannelPage.js";
4
+ import { TeamInboxPage } from "./TeamInboxPage.js";
4
5
  import { TeamThreadPage } from "./TeamThreadPage.js";
5
6
  import css from './conversation.module.css';
6
- export function TeamConversation({ t, useWorkspaces, navigation, drafts, putAttachment, getAttachment, loadChannels, readThread, loadThreadHistory, threadObservations, subscribeChanges, loadMembers, sendMessage, joinChannel, removeChannelMember, reply, changeTask, promoteThread, selectThread, selectChannel, backToWorkspace, backToChannels, resolveTaskRefs }) {
7
+ export function TeamConversation({ t, useWorkspaces, navigation, drafts, putAttachment, getAttachment, loadChannels, readThread, loadThreadHistory, threadObservations, subscribeChanges, loadMembers, loadInbox, sendMessage, joinChannel, removeChannelMember, reply, changeTask, promoteThread, selectThread, selectChannel, selectWorkspace, backToWorkspace, backToChannels, resolveTaskRefs }) {
7
8
  const navigationState = useSyncExternalStore(navigation.subscribe, navigation.getSnapshot, navigation.getSnapshot);
8
9
  const workspaces = useWorkspaces(state => state.items);
9
10
  const current = workspaces.find(workspace => workspace.workspaceId === navigationState.workspaceId);
11
+ if (navigationState.inbox === true) {
12
+ // The Inbox page is global: it merges every visible Workspace and needs no
13
+ // selected Workspace. Selecting Inbox clears the Channel/Thread faces, so
14
+ // this face owns the seat while the flag stands.
15
+ return _jsx(TeamInboxPage, { useWorkspaces: useWorkspaces, loadInbox: loadInbox, subscribeChanges: subscribeChanges, selectWorkspace: selectWorkspace, selectThread: selectThread, t: t }, "inbox");
16
+ }
10
17
  if (current !== undefined && navigationState.threadRef !== undefined) {
11
18
  return _jsx(TeamThreadPage, { workspaceId: current.workspaceId, putAttachment: putAttachment, threadRef: navigationState.threadRef, backToWorkspace: backToWorkspace, selectChannel: selectChannel, selectThread: selectThread, resolveTaskRefs: resolveTaskRefs, ...(navigationState.channelRef === undefined ? {} : { channelRef: navigationState.channelRef }), ...(navigationState.taskRef === undefined ? {} : { taskRef: navigationState.taskRef }), ...(navigationState.taskNumber === undefined ? {} : { taskNumber: navigationState.taskNumber }), drafts: drafts, getAttachment: getAttachment, readThread: readThread, loadChannels: loadChannels, loadThreadHistory: loadThreadHistory, threadObservations: threadObservations, subscribeChanges: subscribeChanges, loadMembers: loadMembers, reply: reply, changeTask: changeTask, promoteThread: promoteThread, t: t }, navigationState.threadRef);
12
19
  }
@@ -0,0 +1,21 @@
1
+ import type { TeamConversationProps } from './slots.ts';
2
+ interface TeamInboxPageProps {
3
+ readonly useWorkspaces: TeamConversationProps['useWorkspaces'];
4
+ readonly loadInbox: TeamConversationProps['loadInbox'];
5
+ /** Wake source while the page is open: one scope-less subscription, shared with the badge poll. */
6
+ readonly subscribeChanges: TeamConversationProps['subscribeChanges'];
7
+ readonly selectWorkspace: TeamConversationProps['selectWorkspace'];
8
+ readonly selectThread: TeamConversationProps['selectThread'];
9
+ readonly t: TeamConversationProps['t'];
10
+ }
11
+ /**
12
+ * The Human 「提到我」 queue: one direct-only Inbox call per visible Workspace,
13
+ * merged into a single recency-ordered list — newest mention first across every
14
+ * Workspace, since a queue is read by recency rather than by Workspace.
15
+ * Opening the page never acknowledges anything — only a durable Thread read
16
+ * consumes a mention marker, so rows and the badge drop after the Thread is
17
+ * opened through the existing auto-ack path.
18
+ */
19
+ export declare function TeamInboxPage({ useWorkspaces, loadInbox, subscribeChanges, selectWorkspace, selectThread, t }: TeamInboxPageProps): import("react").JSX.Element;
20
+ export {};
21
+ //# sourceMappingURL=TeamInboxPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TeamInboxPage.d.ts","sourceRoot":"","sources":["../../../src/client/TeamInboxPage.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAWvD,UAAU,kBAAkB;IAC1B,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,SAAS,EAAE,qBAAqB,CAAC,WAAW,CAAC,CAAA;IACtD,mGAAmG;IACnG,QAAQ,CAAC,gBAAgB,EAAE,qBAAqB,CAAC,kBAAkB,CAAC,CAAA;IACpE,QAAQ,CAAC,eAAe,EAAE,qBAAqB,CAAC,iBAAiB,CAAC,CAAA;IAClE,QAAQ,CAAC,YAAY,EAAE,qBAAqB,CAAC,cAAc,CAAC,CAAA;IAC5D,QAAQ,CAAC,CAAC,EAAE,qBAAqB,CAAC,GAAG,CAAC,CAAA;CACvC;AAgBD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,EAAE,aAAa,EAAE,SAAS,EAAE,gBAAgB,EAAE,eAAe,EAAE,YAAY,EAAE,CAAC,EAAE,EAAE,kBAAkB,+BAiFjI"}
@@ -0,0 +1,71 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useRef, useState } from 'react';
3
+ import { Button } from '@deepseek-ai/dsh-client-ui-primitives';
4
+ import { formatAbsoluteTime, formatInboxTime } from "./team-formatters.js";
5
+ import css from './conversation.module.css';
6
+ import inboxCss from './inbox.module.css';
7
+ /**
8
+ * Queue order: newest mention first, one total order across Workspaces. Each
9
+ * Workspace slice arrives in the Host's own order (that order is the Host's
10
+ * truncation policy); the merged display order is decided here, where the
11
+ * merge happens, and the ledger sequence breaks ties so the queue never
12
+ * reshuffles two rows that share an instant.
13
+ */
14
+ function compareInboxRows(left, right) {
15
+ const leftAt = Date.parse(left.item.newestOccurredAt);
16
+ const rightAt = Date.parse(right.item.newestOccurredAt);
17
+ const byTime = (Number.isNaN(rightAt) ? 0 : rightAt) - (Number.isNaN(leftAt) ? 0 : leftAt);
18
+ return byTime !== 0 ? byTime : right.item.newestSequence - left.item.newestSequence;
19
+ }
20
+ /**
21
+ * The Human 「提到我」 queue: one direct-only Inbox call per visible Workspace,
22
+ * merged into a single recency-ordered list — newest mention first across every
23
+ * Workspace, since a queue is read by recency rather than by Workspace.
24
+ * Opening the page never acknowledges anything — only a durable Thread read
25
+ * consumes a mention marker, so rows and the badge drop after the Thread is
26
+ * opened through the existing auto-ack path.
27
+ */
28
+ export function TeamInboxPage({ useWorkspaces, loadInbox, subscribeChanges, selectWorkspace, selectThread, t }) {
29
+ const workspaces = useWorkspaces(state => state.items);
30
+ const [rows, setRows] = useState();
31
+ const [loading, setLoading] = useState(true);
32
+ const [error, setError] = useState();
33
+ // Only the first refresh owns the loading surface; later wakes refresh the
34
+ // rendered rows in place instead of flashing them back to skeleton.
35
+ const loadedRef = useRef(false);
36
+ const refresh = useCallback(async () => {
37
+ if (!loadedRef.current)
38
+ setLoading(true);
39
+ const results = await Promise.all(workspaces.map(async (workspace) => {
40
+ const result = await loadInbox({ workspaceId: workspace.workspaceId, directOnly: true, limit: 100 });
41
+ return result.ok
42
+ ? { ok: true, workspaceId: workspace.workspaceId, workspaceTitle: workspace.title, items: result.value.items }
43
+ : { ok: false, message: result.error.message };
44
+ }));
45
+ const failure = results.find(result => !result.ok);
46
+ setRows(results.flatMap(result => result.ok
47
+ ? result.items.map(item => ({ workspaceId: result.workspaceId, workspaceTitle: result.workspaceTitle, item }))
48
+ : []).sort(compareInboxRows));
49
+ setError(failure?.ok === false ? failure.message : undefined);
50
+ loadedRef.current = true;
51
+ setLoading(false);
52
+ }, [loadInbox, workspaces]);
53
+ useEffect(() => { void refresh(); }, [refresh]);
54
+ useEffect(() => subscribeChanges(undefined, update => {
55
+ if (update.type === 'failed') {
56
+ setError(update.message);
57
+ return;
58
+ }
59
+ void refresh();
60
+ }), [subscribeChanges, refresh]);
61
+ const open = (row) => {
62
+ selectWorkspace(row.workspaceId);
63
+ selectThread(row.item.thread.threadRef, row.item.channelRef, row.item.task?.taskRef, row.item.taskNumber);
64
+ };
65
+ // The page rides the shared conversation seat: the same header band, 880px
66
+ // reading column, responsive gutters, and scrollbar gutter as Channel and
67
+ // Thread, so switching surfaces does not shift the content column.
68
+ return _jsxs("main", { className: css.surface, "data-team-inbox": true, children: [_jsx("div", { className: css.surfaceHeader, children: _jsx("header", { className: css.headerRow, children: _jsxs("div", { className: css.headerCopy, children: [_jsx("h1", { children: t('mentionsOfMe') }), rows !== undefined && rows.length > 0 && _jsx("p", { className: inboxCss.headerMeta, children: t('inboxHeaderMeta', { threads: rows.length, mentions: rows.reduce((sum, row) => sum + row.item.directCount, 0) }) })] }) }) }), _jsx("div", { className: css.timeline, children: _jsxs("div", { className: css.timelineContent, children: [loading && rows === undefined && error === undefined && _jsx("div", { className: css.emptySurface, children: _jsxs("p", { className: css.loadingState, children: [_jsx("span", { className: css.loadingMark, "aria-hidden": "true" }), t('loadingInbox')] }) }), !loading && rows === undefined && error !== undefined && _jsxs("div", { className: css.errorState, role: "alert", children: [_jsx("span", { children: error }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => { void refresh(); }, children: t('retry') })] }), rows !== undefined && (rows.length === 0
69
+ ? _jsx("div", { className: css.emptySurface, children: _jsxs("div", { className: css.emptyState, children: [_jsx("strong", { children: t('inboxEmptyTitle') }), _jsx("span", { children: t('inboxEmptyHint') })] }) })
70
+ : _jsx("div", { className: inboxCss.list, children: rows.map(row => (_jsxs("button", { type: "button", className: inboxCss.row, onClick: () => { open(row); }, children: [_jsxs("span", { className: inboxCss.rowCrumb, children: [row.workspaceTitle, " / ", _jsxs("span", { className: inboxCss.rowChannel, children: ["#", row.item.channelName] }), row.item.taskNumber !== undefined && _jsx("span", { className: inboxCss.rowTask, children: t('taskLabel', { number: row.item.taskNumber }) })] }), _jsx("span", { className: inboxCss.rowPreview, children: row.item.previewText }), _jsx("time", { className: inboxCss.rowTime, dateTime: row.item.newestOccurredAt, title: formatAbsoluteTime(row.item.newestOccurredAt), children: formatInboxTime(row.item.newestOccurredAt, t) })] }, `${row.workspaceId} ${row.item.thread.threadRef}`))) })), rows !== undefined && error !== undefined && _jsx("p", { className: css.error, role: "alert", children: error })] }) })] });
71
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"TeamMemberEditor.d.ts","sourceRoot":"","sources":["../../../src/client/TeamMemberEditor.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,2BAA2B,EAAE,uBAAuB,EAAgC,MAAM,gCAAgC,CAAA;AACxI,OAAO,KAAK,EAAiD,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAYjG;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE;IAClF,QAAQ,CAAC,KAAK,EAAE,uBAAuB,GAAG,SAAS,CAAA;IACnD,QAAQ,CAAC,aAAa,EAAE,CAAC,MAAM,EAAE,uBAAuB,GAAG,SAAS,KAAK,IAAI,CAAA;IAC7E,QAAQ,CAAC,UAAU,EAAE,gBAAgB,CAAC,YAAY,CAAC,CAAA;IACnD,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,CAAC,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAA;CAClC,+BA0GA;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE;IAC/F,QAAQ,CAAC,MAAM,EAAE,2BAA2B,CAAA;IAC5C,QAAQ,CAAC,YAAY,EAAE,gBAAgB,CAAC,cAAc,CAAC,CAAA;IACvD,QAAQ,CAAC,UAAU,EAAE,gBAAgB,CAAC,YAAY,CAAC,CAAA;IACnD,QAAQ,CAAC,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAChD,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAA;IAC5B,QAAQ,CAAC,CAAC,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAA;CAClC,+BA0EA;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,uBAAuB,GAAG,SAAS,EAAE,KAAK,EAAE,uBAAuB,GAAG,SAAS,GAAG,OAAO,CAIxH"}
1
+ {"version":3,"file":"TeamMemberEditor.d.ts","sourceRoot":"","sources":["../../../src/client/TeamMemberEditor.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,2BAA2B,EAAE,uBAAuB,EAAgC,MAAM,gCAAgC,CAAA;AACxI,OAAO,KAAK,EAAiD,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAajG;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE;IAClF,QAAQ,CAAC,KAAK,EAAE,uBAAuB,GAAG,SAAS,CAAA;IACnD,QAAQ,CAAC,aAAa,EAAE,CAAC,MAAM,EAAE,uBAAuB,GAAG,SAAS,KAAK,IAAI,CAAA;IAC7E,QAAQ,CAAC,UAAU,EAAE,gBAAgB,CAAC,YAAY,CAAC,CAAA;IACnD,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,CAAC,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAA;CAClC,+BA0GA;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE;IAC/F,QAAQ,CAAC,MAAM,EAAE,2BAA2B,CAAA;IAC5C,QAAQ,CAAC,YAAY,EAAE,gBAAgB,CAAC,cAAc,CAAC,CAAA;IACvD,QAAQ,CAAC,UAAU,EAAE,gBAAgB,CAAC,YAAY,CAAC,CAAA;IACnD,QAAQ,CAAC,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAChD,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAA;IAC5B,QAAQ,CAAC,CAAC,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAA;CAClC,+BA4DA;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,uBAAuB,GAAG,SAAS,EAAE,KAAK,EAAE,uBAAuB,GAAG,SAAS,GAAG,OAAO,CAIxH"}