@things-factory/ai-assistant 10.1.207 → 10.1.210

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 (181) hide show
  1. package/client/components/assistant-chat.ts +238 -9
  2. package/client/components/assistant-dock.ts +9 -4
  3. package/client/components/assistant-invite.ts +138 -0
  4. package/client/components/assistant-session-manager.ts +53 -12
  5. package/client/components/proposal-request.test.ts +10 -0
  6. package/client/components/roster-identity.test.ts +2 -12
  7. package/client/components/roster-identity.ts +0 -6
  8. package/client/setup-assistant-dock.ts +1 -0
  9. package/client/utils/assistant-session-actions.ts +3 -1
  10. package/client/utils/assistant-session-admin.ts +9 -2
  11. package/client/utils/assistant-session-controller.ts +1 -1
  12. package/client/utils/assistant-typing.test.ts +33 -0
  13. package/client/utils/assistant-typing.ts +53 -0
  14. package/dist-client/components/assistant-chat.d.ts +28 -0
  15. package/dist-client/components/assistant-chat.js +259 -8
  16. package/dist-client/components/assistant-chat.js.map +1 -1
  17. package/dist-client/components/assistant-dock.js +12 -4
  18. package/dist-client/components/assistant-dock.js.map +1 -1
  19. package/dist-client/components/assistant-invite.d.ts +39 -0
  20. package/dist-client/components/assistant-invite.js +150 -0
  21. package/dist-client/components/assistant-invite.js.map +1 -0
  22. package/dist-client/components/assistant-session-manager.d.ts +5 -1
  23. package/dist-client/components/assistant-session-manager.js +40 -10
  24. package/dist-client/components/assistant-session-manager.js.map +1 -1
  25. package/dist-client/components/proposal-request.test.js +8 -0
  26. package/dist-client/components/proposal-request.test.js.map +1 -1
  27. package/dist-client/components/roster-identity.d.ts +0 -2
  28. package/dist-client/components/roster-identity.js +0 -7
  29. package/dist-client/components/roster-identity.js.map +1 -1
  30. package/dist-client/components/roster-identity.test.js +2 -13
  31. package/dist-client/components/roster-identity.test.js.map +1 -1
  32. package/dist-client/setup-assistant-dock.js +1 -0
  33. package/dist-client/setup-assistant-dock.js.map +1 -1
  34. package/dist-client/tsconfig.tsbuildinfo +1 -1
  35. package/dist-client/utils/assistant-session-actions.d.ts +1 -0
  36. package/dist-client/utils/assistant-session-actions.js +3 -1
  37. package/dist-client/utils/assistant-session-actions.js.map +1 -1
  38. package/dist-client/utils/assistant-session-admin.d.ts +5 -2
  39. package/dist-client/utils/assistant-session-admin.js +3 -0
  40. package/dist-client/utils/assistant-session-admin.js.map +1 -1
  41. package/dist-client/utils/assistant-session-controller.d.ts +1 -1
  42. package/dist-client/utils/assistant-session-controller.js.map +1 -1
  43. package/dist-client/utils/assistant-typing.d.ts +20 -0
  44. package/dist-client/utils/assistant-typing.js +48 -0
  45. package/dist-client/utils/assistant-typing.js.map +1 -0
  46. package/dist-client/utils/assistant-typing.test.d.ts +1 -0
  47. package/dist-client/utils/assistant-typing.test.js +30 -0
  48. package/dist-client/utils/assistant-typing.test.js.map +1 -0
  49. package/dist-server/service/assistant-chat-resolver.js +5 -12
  50. package/dist-server/service/assistant-chat-resolver.js.map +1 -1
  51. package/dist-server/service/assistant-mode/index.d.ts +1 -1
  52. package/dist-server/service/assistant-mode/index.js +4 -1
  53. package/dist-server/service/assistant-mode/index.js.map +1 -1
  54. package/dist-server/service/assistant-mode/mode-categories.js +9 -2
  55. package/dist-server/service/assistant-mode/mode-categories.js.map +1 -1
  56. package/dist-server/service/assistant-mode/screen-rules.d.ts +19 -0
  57. package/dist-server/service/assistant-mode/screen-rules.js +40 -6
  58. package/dist-server/service/assistant-mode/screen-rules.js.map +1 -1
  59. package/dist-server/service/chat-message/chat-message-room.js +3 -1
  60. package/dist-server/service/chat-message/chat-message-room.js.map +1 -1
  61. package/dist-server/service/chat-message/chat-typing-subscription.d.ts +10 -0
  62. package/dist-server/service/chat-message/chat-typing-subscription.js +68 -0
  63. package/dist-server/service/chat-message/chat-typing-subscription.js.map +1 -0
  64. package/dist-server/service/chat-message/chat-typing.d.ts +28 -0
  65. package/dist-server/service/chat-message/chat-typing.js +40 -0
  66. package/dist-server/service/chat-message/chat-typing.js.map +1 -0
  67. package/dist-server/service/chat-session/attachment-tool-result.d.ts +1 -0
  68. package/dist-server/service/chat-session/attachment-tool-result.js +6 -0
  69. package/dist-server/service/chat-session/attachment-tool-result.js.map +1 -1
  70. package/dist-server/service/chat-session/attachment-tools.js +2 -2
  71. package/dist-server/service/chat-session/attachment-tools.js.map +1 -1
  72. package/dist-server/service/chat-session/chat-session.d.ts +2 -1
  73. package/dist-server/service/chat-session/chat-session.js +1 -1
  74. package/dist-server/service/chat-session/chat-session.js.map +1 -1
  75. package/dist-server/service/chat-session/session-activity-broadcast.d.ts +12 -0
  76. package/dist-server/service/chat-session/session-activity-broadcast.js +15 -0
  77. package/dist-server/service/chat-session/session-activity-broadcast.js.map +1 -1
  78. package/dist-server/service/chat-session/session-activity-publish.d.ts +2 -0
  79. package/dist-server/service/chat-session/session-activity-publish.js +5 -2
  80. package/dist-server/service/chat-session/session-activity-publish.js.map +1 -1
  81. package/dist-server/service/chat-session/session-visibility.d.ts +17 -2
  82. package/dist-server/service/chat-session/session-visibility.js +28 -3
  83. package/dist-server/service/chat-session/session-visibility.js.map +1 -1
  84. package/dist-server/service/chat-session/session-write-gate.d.ts +1 -1
  85. package/dist-server/service/chat-session/session-write-gate.js +1 -1
  86. package/dist-server/service/chat-session/session-write-gate.js.map +1 -1
  87. package/dist-server/service/chat-session-participant/index.d.ts +1 -0
  88. package/dist-server/service/chat-session-participant/index.js +4 -1
  89. package/dist-server/service/chat-session-participant/index.js.map +1 -1
  90. package/dist-server/service/chat-session-participant/invite-notification.d.ts +16 -1
  91. package/dist-server/service/chat-session-participant/invite-notification.js +34 -5
  92. package/dist-server/service/chat-session-participant/invite-notification.js.map +1 -1
  93. package/dist-server/service/chat-session-participant/mention-invite.d.ts +30 -4
  94. package/dist-server/service/chat-session-participant/mention-invite.js +78 -31
  95. package/dist-server/service/chat-session-participant/mention-invite.js.map +1 -1
  96. package/dist-server/service/chat-session-participant/read-roster.js +5 -2
  97. package/dist-server/service/chat-session-participant/read-roster.js.map +1 -1
  98. package/dist-server/service/chat-session-resolver.d.ts +27 -0
  99. package/dist-server/service/chat-session-resolver.js +226 -25
  100. package/dist-server/service/chat-session-resolver.js.map +1 -1
  101. package/dist-server/service/data-entry/entry-target.d.ts +19 -0
  102. package/dist-server/service/data-entry/entry-target.js +20 -5
  103. package/dist-server/service/data-entry/entry-target.js.map +1 -1
  104. package/dist-server/service/data-entry/entry-tools.js +2 -2
  105. package/dist-server/service/data-entry/entry-tools.js.map +1 -1
  106. package/dist-server/service/data-entry/entry-update.d.ts +2 -1
  107. package/dist-server/service/data-entry/entry-update.js +5 -3
  108. package/dist-server/service/data-entry/entry-update.js.map +1 -1
  109. package/dist-server/service/data-read/list-read.d.ts +10 -0
  110. package/dist-server/service/data-read/list-read.js +12 -0
  111. package/dist-server/service/data-read/list-read.js.map +1 -1
  112. package/dist-server/service/data-read/read-target.d.ts +7 -0
  113. package/dist-server/service/data-read/read-target.js +2 -2
  114. package/dist-server/service/data-read/read-target.js.map +1 -1
  115. package/dist-server/service/data-read/read-tools.js +1 -1
  116. package/dist-server/service/data-read/read-tools.js.map +1 -1
  117. package/dist-server/service/index.d.ts +4 -2
  118. package/dist-server/service/index.js +9 -1
  119. package/dist-server/service/index.js.map +1 -1
  120. package/dist-server/service/testing/conversation-script.d.ts +1 -1
  121. package/dist-server/service/testing/conversation-script.js.map +1 -1
  122. package/dist-server/service/testing/mode-conformance.js +4 -0
  123. package/dist-server/service/testing/mode-conformance.js.map +1 -1
  124. package/dist-server/tsconfig.tsbuildinfo +1 -1
  125. package/package.json +9 -9
  126. package/server/explicit-graphql-types.test.ts +1 -1
  127. package/server/service/assistant-chat-resolver.ts +7 -12
  128. package/server/service/assistant-mode/index.ts +1 -1
  129. package/server/service/assistant-mode/mode-categories.ts +11 -3
  130. package/server/service/assistant-mode/mode-front.test.ts +53 -1
  131. package/server/service/assistant-mode/resolve.test.ts +2 -1
  132. package/server/service/assistant-mode/screen-rules.test.ts +41 -3
  133. package/server/service/assistant-mode/screen-rules.ts +44 -5
  134. package/server/service/chat-message/chat-message-room.test.ts +7 -2
  135. package/server/service/chat-message/chat-message-room.ts +3 -1
  136. package/server/service/chat-message/chat-typing-subscription.ts +52 -0
  137. package/server/service/chat-message/chat-typing.test.ts +45 -0
  138. package/server/service/chat-message/chat-typing.ts +48 -0
  139. package/server/service/chat-message/fold-history-window.test.ts +1 -1
  140. package/server/service/chat-session/attachment-tool-result.test.ts +9 -1
  141. package/server/service/chat-session/attachment-tool-result.ts +7 -0
  142. package/server/service/chat-session/attachment-tools.ts +3 -3
  143. package/server/service/chat-session/chat-session.ts +3 -2
  144. package/server/service/chat-session/group-room.test.ts +233 -0
  145. package/server/service/chat-session/session-activity-broadcast.ts +20 -0
  146. package/server/service/chat-session/session-activity-publish.ts +6 -1
  147. package/server/service/chat-session/session-activity-receivers.test.ts +26 -0
  148. package/server/service/chat-session/session-visibility.test.ts +32 -1
  149. package/server/service/chat-session/session-visibility.ts +33 -4
  150. package/server/service/chat-session/session-write-gate.test.ts +7 -1
  151. package/server/service/chat-session/session-write-gate.ts +2 -2
  152. package/server/service/chat-session-participant/index.ts +2 -0
  153. package/server/service/chat-session-participant/invite-notification.test.ts +36 -1
  154. package/server/service/chat-session-participant/invite-notification.ts +43 -5
  155. package/server/service/chat-session-participant/mention-invite.test.ts +49 -1
  156. package/server/service/chat-session-participant/mention-invite.ts +82 -28
  157. package/server/service/chat-session-participant/read-roster.test.ts +13 -4
  158. package/server/service/chat-session-participant/read-roster.ts +6 -3
  159. package/server/service/chat-session-resolver.ts +204 -27
  160. package/server/service/data-entry/data-entry-update.test.ts +19 -0
  161. package/server/service/data-entry/data-entry.test.ts +65 -1
  162. package/server/service/data-entry/entry-target.ts +36 -5
  163. package/server/service/data-entry/entry-tools.ts +2 -2
  164. package/server/service/data-entry/entry-update.ts +6 -3
  165. package/server/service/data-read/list-missing.test.ts +49 -0
  166. package/server/service/data-read/list-read.ts +18 -0
  167. package/server/service/data-read/read-target.ts +10 -3
  168. package/server/service/data-read/read-tools.ts +1 -1
  169. package/server/service/data-read/record-keys.test.ts +36 -0
  170. package/server/service/index.ts +6 -1
  171. package/server/service/testing/conversation-script.ts +1 -1
  172. package/server/service/testing/mode-conformance.ts +2 -0
  173. package/test/assistant-dock.test.ts +5 -1
  174. package/test/assistant-session-manager.test.ts +4 -2
  175. package/test/open-doors.test.ts +10 -5
  176. package/translations/en.json +29 -4
  177. package/translations/es.json +29 -4
  178. package/translations/ja.json +29 -4
  179. package/translations/ko.json +30 -5
  180. package/translations/ms.json +29 -4
  181. package/translations/zh.json +29 -4
@@ -21,7 +21,7 @@ const t = (key, defaultValue, options = {}) => i18next.t(`ai-assistant.session.$
21
21
  * with the toolbar's own events and does not open anything itself:
22
22
  * assistant-session-select { sessionId } open that conversation
23
23
  * assistant-session-reset start a new one
24
- * assistant-session-changed { sessionId, change: 'renamed' | 'hidden' | 'unhidden' | 'removed' | 'visibility' }
24
+ * assistant-session-changed { sessionId, change: 'renamed' | 'hidden' | 'unhidden' | 'removed' | 'visibility' | 'left' }
25
25
  * a row changed - the host refreshes its tabs; if the open
26
26
  * conversation was removed or hidden, it moves off it
27
27
  *
@@ -471,8 +471,9 @@ let AssistantSessionManager = class AssistantSessionManager extends LitElement {
471
471
  </select>
472
472
  <select aria-label=${t('filter-visibility', 'Visibility')} .value=${this.visibility}
473
473
  @change=${(e) => this.refilter(() => (this.visibility = e.target.value))}>
474
- <option value="all">${t('filter-any-visibility', 'Public and private')}</option>
474
+ <option value="all">${t('filter-any-visibility', 'Anyone')}</option>
475
475
  <option value="open">${t('filter-open', 'Public')}</option>
476
+ <option value="group">${t('filter-group', 'Group')}</option>
476
477
  <option value="private">${t('filter-private', 'Private')}</option>
477
478
  </select>
478
479
  <label><input type="checkbox" .checked=${this.unreadOnly}
@@ -502,7 +503,7 @@ let AssistantSessionManager = class AssistantSessionManager extends LitElement {
502
503
  renderRow(entry) {
503
504
  const name = entry.name || t('untitled', 'Untitled conversation');
504
505
  const actions = sessionActions(entry);
505
- const isPrivate = entry.visibility === 'private';
506
+ const isOpen = !entry.visibility || entry.visibility === 'open';
506
507
  if (this.renamingId === entry.id) {
507
508
  return html `<div class="row">
508
509
  <span class="dot"></span>
@@ -523,13 +524,13 @@ let AssistantSessionManager = class AssistantSessionManager extends LitElement {
523
524
  </div>`;
524
525
  }
525
526
  return html `<div
526
- class="row ${entry.unread ? 'unread' : ''} ${entry.hidden ? 'hidden' : ''} ${entry.id === this.sessionId ? 'current' : ''} ${isPrivate ? '' : 'open-audience'}"
527
+ class="row ${entry.unread ? 'unread' : ''} ${entry.hidden ? 'hidden' : ''} ${entry.id === this.sessionId ? 'current' : ''} ${isOpen ? 'open-audience' : ''}"
527
528
  >
528
529
  <span class="dot"></span>
529
530
  <button class="open-row" aria-current=${entry.id === this.sessionId ? 'true' : 'false'} @click=${() => this.emit('assistant-session-select', { sessionId: entry.id, kind: entry.kind })}>
530
531
  <div class="title">${name}</div>
531
532
  <div class="prev">${entry.lastMessagePreview || ''}</div>
532
- <span class="meta"><span class="audience ${isPrivate ? 'private' : 'open'}"><md-icon aria-hidden="true">${isPrivate ? 'lock' : 'public'}</md-icon>${isPrivate ? t('audience-private', 'Participants only') : t('audience-open', 'Everyone in this domain')}</span>${entry.lastMessageAt && Number.isFinite(Date.parse(entry.lastMessageAt)) ? html ` · <time datetime=${entry.lastMessageAt}>${new Intl.DateTimeFormat(i18next.language || 'en', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }).format(new Date(entry.lastMessageAt))}</time>` : nothing}${entry.mine ? html ` · ${t('filter-mine', 'Started by me')}` : nothing}</span>
533
+ <span class="meta">${this.renderAudience(entry.visibility)}${entry.lastMessageAt && Number.isFinite(Date.parse(entry.lastMessageAt)) ? html ` · <time datetime=${entry.lastMessageAt}>${new Intl.DateTimeFormat(i18next.language || 'en', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }).format(new Date(entry.lastMessageAt))}</time>` : nothing}${entry.mine ? html ` · ${t('filter-mine', 'Started by me')}` : nothing}</span>
533
534
  </button>
534
535
  <button
535
536
  class="more-menu"
@@ -545,16 +546,21 @@ let AssistantSessionManager = class AssistantSessionManager extends LitElement {
545
546
  openRowMenu(event, entry, name) {
546
547
  event.stopPropagation();
547
548
  const actions = sessionActions(entry);
548
- const isPrivate = entry.visibility === 'private';
549
+ const current = entry.visibility || 'open';
549
550
  const at = event.currentTarget.getBoundingClientRect();
550
551
  OxPopupMenu.open({
551
552
  template: html `
552
553
  ${actions.rename ? html `<ox-popup-menuitem label=${t('rename', 'Rename')} @select=${() => (this.renamingId = entry.id)}></ox-popup-menuitem>` : nothing}
553
- ${actions.setVisibility
554
- ? isPrivate
555
- ? html `<ox-popup-menuitem label=${t('make-open', 'Make public')} @select=${() => this.askOpen(entry, name)}></ox-popup-menuitem>`
556
- : html `<ox-popup-menuitem label=${t('make-private', 'Make private')} @select=${() => this.setVisibility(entry, 'private')}></ox-popup-menuitem>`
554
+ ${actions.setVisibility && current !== 'open'
555
+ ? html `<ox-popup-menuitem label=${t('make-open', 'Make public')} @select=${() => this.askOpen(entry, name)}></ox-popup-menuitem>`
557
556
  : nothing}
557
+ ${actions.setVisibility && current !== 'group'
558
+ ? html `<ox-popup-menuitem label=${t('make-group', 'Make it a group')} @select=${() => this.setVisibility(entry, 'group')}></ox-popup-menuitem>`
559
+ : nothing}
560
+ ${actions.setVisibility && current !== 'private'
561
+ ? html `<ox-popup-menuitem label=${t('make-private', 'Make private')} @select=${() => this.askPrivate(entry, name)}></ox-popup-menuitem>`
562
+ : nothing}
563
+ ${actions.leave ? html `<ox-popup-menuitem label=${t('leave', 'Leave')} @select=${() => this.askLeave(entry, name)}></ox-popup-menuitem>` : nothing}
558
564
  <ox-popup-menuitem
559
565
  label=${actions.unhide ? t('unhide', 'Show in my list') : t('hide', 'Hide from my list')}
560
566
  @select=${() => this.setHidden(entry, !entry.hidden)}></ox-popup-menuitem>
@@ -585,6 +591,30 @@ let AssistantSessionManager = class AssistantSessionManager extends LitElement {
585
591
  if (ok)
586
592
  this.setVisibility(entry, 'open');
587
593
  }
594
+ /* Private is the owner's alone - the people in it leave, and coming back is being invited again. */
595
+ async askPrivate(entry, name) {
596
+ const ok = await this.ask(t('make-private', 'Make private'), t('confirm-private', 'Make "{name}" private? Only you will read it, and everyone else in it leaves.', { name }), t('make-private', 'Make private'));
597
+ if (ok)
598
+ this.setVisibility(entry, 'private');
599
+ }
600
+ async askLeave(entry, name) {
601
+ const ok = await this.ask(t('leave', 'Leave'), entry.visibility === 'open'
602
+ ? t('confirm-leave-open', 'Leave "{name}"? It stops coming to your list.', { name })
603
+ : t('confirm-leave', 'Leave "{name}"? You will not read it again unless its owner brings you back.', { name }), t('leave', 'Leave'));
604
+ if (ok)
605
+ void this.act(entry, 'left', () => this.admin.leave(entry.id), t('leave-refused', 'you could not leave this conversation.'));
606
+ }
607
+ /** Who reads a conversation, on its row: the same three words wherever a conversation is listed. */
608
+ renderAudience(visibility) {
609
+ const v = visibility || 'open';
610
+ const icon = v === 'open' ? 'public' : v === 'group' ? 'group' : 'lock';
611
+ const label = v === 'open'
612
+ ? t('audience-open', 'Everyone in this domain')
613
+ : v === 'group'
614
+ ? t('audience-group', 'Invited people')
615
+ : t('audience-private', 'Only me');
616
+ return html `<span class="audience ${v}"><md-icon aria-hidden="true">${icon}</md-icon>${label}</span>`;
617
+ }
588
618
  };
589
619
  __decorate([
590
620
  property({ type: String }),
@@ -1 +1 @@
1
- {"version":3,"file":"assistant-session-manager.js","sourceRoot":"","sources":["../../client/components/assistant-session-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport { css, html, LitElement, nothing, type PropertyValues, type TemplateResult } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { i18next } from '@operato/i18n'\nimport { OxPrompt } from '@operato/popup'\nimport '@operato/popup/ox-popup-menu.js'\nimport { OxPopupMenu } from '@operato/popup/ox-popup-menu.js'\n\nimport {\n assistantSessionAdmin,\n sessionActions,\n type AssistantSessionAdmin,\n type AssistantSessionEntry,\n type AssistantSessionFilters\n} from '../utils/assistant-session-admin.js'\n\nconst PAGE = 30\n\n\nconst t = (key: string, defaultValue: string, options: Record<string, unknown> = {}) =>\n i18next.t(`ai-assistant.session.${key}`, { defaultValue, ...options })\n\n/**\n * The conversations on one anchor, managed - `<ox-assistant-session-manager>`.\n *\n * Search, a filter (all / mine / including hidden), paging, and on each row: open, rename, hide or\n * unhide, private ⇄ public, delete. Taken from operato-twin's dock, where it was written first, so every\n * dock gets it by placing this element (typically behind the toolbar's \"All History\").\n *\n * ── What is the host's ──────────────────────────────────────────────────────────\n * Opening and starting a conversation belong to the dock's session controller, so this element asks\n * with the toolbar's own events and does not open anything itself:\n * assistant-session-select { sessionId } open that conversation\n * assistant-session-reset start a new one\n * assistant-session-changed { sessionId, change: 'renamed' | 'hidden' | 'unhidden' | 'removed' | 'visibility' }\n * a row changed - the host refreshes its tabs; if the open\n * conversation was removed or hidden, it moves off it\n *\n * ── What is the server's ────────────────────────────────────────────────────────\n * Who may do what (`sessionActions`): rename any participant, hide anyone for their own list, delete\n * the creator, visibility the owner. The buttons follow those hints; the doors decide again.\n *\n * ── Asking before what cannot be undone ─────────────────────────────────────────\n * Delete, and opening a private conversation to the domain, ask on the row itself - [Cancel][OK].\n * Making it private, hiding and renaming do not: each can be undone from the same row.\n */\n@customElement('ox-assistant-session-manager')\nexport class AssistantSessionManager extends LitElement {\n static styles = css`\n /* One scale for the list: name 13, preview 12, meta 11 - the rest of the dock's sizes. */\n .open-row { display: block; flex: 1; min-width: 0; text-align: start; border: 0; background: transparent; color: inherit; font: inherit; padding: 0; cursor: pointer; }\n .meta { display: block; margin-top: 2px; font-size: 11px; color: var(--md-sys-color-on-surface-variant); }\n /* Open and private must part at a glance - an open row is read by everyone in the domain (2026-09-30). */\n .audience { display: inline-flex; align-items: center; gap: 3px; vertical-align: middle; }\n .audience md-icon { --md-icon-size: 14px; }\n /* The dock's warning colour, fixed across applications - see assistant-dock.ts. */\n :host { --assistant-open-edge: #c47f00; --assistant-open-surface: #f2b01e; --assistant-open-ink: #2e2100; }\n .audience.open { padding: 0 5px 0 3px; border-radius: 4px; background: var(--assistant-open-surface); color: var(--assistant-open-ink); font-weight: 600; }\n .audience md-icon { --md-icon-size: 12px; }\n .row.open-audience { box-shadow: inset 4px 0 0 var(--assistant-open-edge); }\n .more-menu { flex: none; border: 0; border-radius: 4px; padding: 2px 6px; background: none; color: var(--md-sys-color-on-surface-variant); font-size: 14px; line-height: 1; cursor: pointer; }\n .more-menu:hover { background: var(--md-sys-color-surface-container-high); color: var(--md-sys-color-on-surface); }\n button:focus-visible { outline: 2px solid var(--md-sys-color-primary); outline-offset: 2px; }\n :host {\n display: flex;\n flex-direction: column;\n min-height: 0;\n font-size: 13px;\n color: var(--md-sys-color-on-surface);\n }\n\n .bar {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 10px 6px;\n }\n\n .bar input,\n .bar select {\n min-width: 0;\n height: 26px;\n font-size: 12px;\n box-sizing: border-box;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n background: var(--md-sys-color-surface-container-lowest);\n color: inherit;\n font: inherit;\n padding: 0 8px;\n }\n\n .bar input {\n flex: 1;\n }\n\n .new {\n flex: none;\n border: 0;\n border-radius: 6px;\n padding: 4px 8px;\n background: transparent;\n color: var(--md-sys-color-primary);\n font: inherit;\n font-size: 12px;\n font-weight: 600;\n white-space: nowrap;\n cursor: pointer;\n }\n\n /* Always open, one quiet line under the search - the same small size as the search. */\n .filters {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 4px 10px;\n padding: 0 10px 8px;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n font-size: 11px;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n .filters select {\n height: 22px;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 4px;\n background: var(--md-sys-color-surface-container-lowest);\n color: inherit;\n font: inherit;\n padding: 0 4px;\n }\n\n .filters label {\n display: inline-flex;\n align-items: center;\n gap: 3px;\n cursor: pointer;\n }\n\n .filters input[type='checkbox'] {\n margin: 0;\n }\n\n .filters .clear {\n margin-left: auto;\n border: 0;\n background: none;\n padding: 0;\n color: var(--md-sys-color-primary);\n font: inherit;\n cursor: pointer;\n }\n\n .list {\n flex: 1;\n min-height: 0;\n overflow: auto;\n }\n\n .row {\n position: relative;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 7px 10px;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n cursor: pointer;\n }\n\n .row:hover,\n .row.current {\n background: var(--md-sys-color-surface-container);\n }\n\n .row.hidden {\n opacity: 0.6;\n }\n\n .dot {\n flex: none;\n width: 7px;\n height: 7px;\n border-radius: 50%;\n }\n\n .row.unread .dot {\n background: var(--md-sys-color-primary);\n }\n\n .row md-icon {\n --md-icon-size: 16px;\n flex: none;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n .body {\n flex: 1;\n min-width: 0;\n }\n\n .title,\n .prev {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .row.unread .title {\n font-weight: 700;\n }\n\n .prev {\n margin-top: 1px;\n color: var(--md-sys-color-on-surface-variant);\n font-size: 12px;\n }\n\n .rename {\n flex: 1;\n min-width: 0;\n height: 26px;\n border: 1px solid var(--md-sys-color-primary);\n border-radius: 6px;\n padding: 0 6px;\n font: inherit;\n background: var(--md-sys-color-surface-container-lowest);\n color: inherit;\n }\n\n .ask {\n display: grid;\n gap: 6px;\n padding: 10px;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n background: var(--md-sys-color-surface-container);\n }\n\n .ask .buttons {\n display: flex;\n justify-content: flex-end;\n gap: 6px;\n }\n\n .ask button {\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n padding: 4px 10px;\n background: var(--md-sys-color-surface-container-lowest);\n color: inherit;\n font: inherit;\n cursor: pointer;\n }\n\n .ask button.ok {\n border-color: var(--md-sys-color-primary);\n background: var(--md-sys-color-primary);\n color: var(--md-sys-color-on-primary);\n }\n\n .ask button.ok.danger {\n border-color: var(--md-sys-color-error);\n background: var(--md-sys-color-error);\n color: var(--md-sys-color-on-error);\n }\n\n .note,\n .empty {\n padding: 14px 12px;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n [role='alert'] {\n color: var(--md-sys-color-error);\n }\n\n .more,\n .hiddenfilter {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n width: 100%;\n padding: 8px;\n border: 0;\n border-top: 1px solid var(--md-sys-color-outline-variant);\n background: none;\n color: var(--md-sys-color-on-surface-variant);\n font: inherit;\n cursor: pointer;\n }\n\n .hiddenfilter md-icon {\n --md-icon-size: 16px;\n }\n `\n\n /** The anchor whose conversations are listed - the same one the dock's session controller opened. */\n @property({ type: String }) anchorType = ''\n @property({ type: String }) anchorId = ''\n /** The conversation the dock has open - marked in the list. */\n @property({ type: String }) sessionId = ''\n /** The doors. Replaceable for tests; a host normally leaves it. */\n @property({ attribute: false }) admin: AssistantSessionAdmin = assistantSessionAdmin\n /** The conversation kind (mode) open in the dock - the list's default scope (§2-6). */\n @property({ type: String }) kind = ''\n\n @state() private entries: AssistantSessionEntry[] = []\n @state() private total = 0\n @state() private hiddenCount = 0\n @state() private search = ''\n @state() private whose: NonNullable<AssistantSessionFilters['whose']> = 'all'\n @state() private visibility: NonNullable<AssistantSessionFilters['visibility']> = 'all'\n @state() private includeHidden = false\n @state() private unreadOnly = false\n @state() private pageSize = PAGE\n @state() private loading = false\n @state() private error = ''\n @state() private renamingId = ''\n\n /*\n * The search and the filters stand open together, always (2026-10-12) - a filter behind a \"Filters\" button is one\n * more thing to find before finding the conversation. The list is this screen's conversations, so there is no kind\n * to choose.\n */\n private get filtering() {\n return !!this.search.trim() || this.whose !== 'all' || this.visibility !== 'all' || this.includeHidden || this.unreadOnly\n }\n\n private clearFilters() {\n clearTimeout(this.searchTimer)\n this.refilter(() => {\n this.search = ''\n this.whose = 'all'\n this.visibility = 'all'\n this.includeHidden = this.unreadOnly = false\n })\n }\n\n private generation = 0\n private searchTimer?: ReturnType<typeof setTimeout>\n\n /** Read the list again - the host calls this after it opened or started a conversation. */\n async reload(): Promise<void> {\n const generation = ++this.generation\n if (!this.anchorType || !this.anchorId) {\n this.entries = []\n this.total = this.hiddenCount = 0\n return\n }\n\n this.loading = true\n this.error = ''\n try {\n /*\n * Paging reads again from the start rather than appending - live activity reorders the list, and\n * appending a later page to an earlier order duplicates and drops rows.\n */\n const page = await this.admin.inbox({\n anchorType: this.anchorType,\n anchorId: this.anchorId,\n search: this.search,\n filters: {\n kind: this.kind,\n whose: this.whose,\n visibility: this.visibility,\n includeHidden: this.includeHidden,\n unreadOnly: this.unreadOnly\n },\n limit: this.pageSize,\n offset: 0\n })\n if (generation !== this.generation) return\n this.entries = page.entries\n this.total = page.total\n this.hiddenCount = page.hiddenCount\n } catch (error) {\n if (generation !== this.generation) return\n this.entries = []\n this.total = this.hiddenCount = 0\n this.error = (error as Error).message || t('failed', 'could not load the conversation list.')\n } finally {\n if (generation === this.generation) this.loading = false\n }\n }\n\n protected updated(changed: PropertyValues) {\n if (changed.has('anchorType') || changed.has('anchorId') || changed.has('admin') || changed.has('kind')) void this.reload()\n\n /*\n * The rename box takes the keys the moment it opens, with the name selected. `autofocus` works once\n * per document, so the second rename opened a box that ignored the typing (2026-09-24, operato-ecs).\n */\n if (changed.has('renamingId') && this.renamingId) {\n const input = this.renderRoot.querySelector('input.rename') as HTMLInputElement | null\n input?.focus()\n input?.select()\n }\n }\n\n disconnectedCallback() {\n this.generation++\n clearTimeout(this.searchTimer)\n super.disconnectedCallback()\n }\n\n private emit(name: string, detail?: unknown) {\n this.dispatchEvent(new CustomEvent(name, { detail, bubbles: true, composed: true }))\n }\n\n /* ── what the person does ────────────────────────────────────────────────────────── */\n\n private onSearch(value: string) {\n this.search = value\n clearTimeout(this.searchTimer)\n /* Not on every keystroke. A new search starts from the first page. */\n this.searchTimer = setTimeout(() => {\n this.pageSize = PAGE\n void this.reload()\n }, 200)\n }\n\n /** A filter changed - the list starts from its first page again. */\n private refilter(change: () => void) {\n change()\n this.pageSize = PAGE\n void this.reload()\n }\n\n private loadMore() {\n this.pageSize += PAGE\n void this.reload()\n }\n\n /** Runs one door, then reads the list again - on success and on failure alike, so the row shows the truth. */\n private async act(entry: AssistantSessionEntry, change: string, run: () => Promise<boolean>, refused: string) {\n this.renamingId = ''\n try {\n if (!(await run())) this.error = refused\n else this.emit('assistant-session-changed', { sessionId: entry.id, change })\n } catch (error) {\n /* Never pass a failure silently - a person must not believe a conversation was deleted when it was not. */\n this.error = (error as Error).message\n }\n const error = this.error\n await this.reload()\n if (error && !this.error) this.error = error\n }\n\n private rename(entry: AssistantSessionEntry, name: string) {\n const trimmed = name.trim()\n this.renamingId = ''\n if (!trimmed || trimmed === entry.name) return\n void this.act(entry, 'renamed', () => this.admin.rename(entry.id, trimmed), t('rename-refused', 'this conversation could not be renamed.'))\n }\n\n private setHidden(entry: AssistantSessionEntry, hidden: boolean) {\n void this.act(entry, hidden ? 'hidden' : 'unhidden', () => this.admin.hide(entry.id, hidden), t('hide-refused', 'this conversation could not be hidden.'))\n }\n\n private setVisibility(entry: AssistantSessionEntry, visibility: 'open' | 'private') {\n void this.act(\n entry,\n 'visibility',\n () => this.admin.setVisibility(entry.id, visibility),\n t('visibility-refused', 'only the owner of this conversation can change who reads it.')\n )\n }\n\n private _removeSession(entry: AssistantSessionEntry) {\n void this.act(entry, 'removed', () => this.admin.remove(entry.id), t('remove-refused', 'this conversation could not be deleted.'))\n }\n\n /* ── drawing ─────────────────────────────────────────────────────────────────────── */\n\n render(): TemplateResult {\n const shown = this.entries.length\n\n return html`\n <div class=\"bar\">\n <input\n .value=${this.search}\n placeholder=${t('search', 'Search conversations')}\n aria-label=${t('search', 'Search conversations')}\n @input=${(e: Event) => this.onSearch((e.target as HTMLInputElement).value)}\n @keydown=${(e: KeyboardEvent) => { if (e.key === 'Escape' && this.search) { e.stopPropagation(); this.onSearch('') } }}\n />\n <button class=\"new\" @click=${() => this.emit('assistant-session-reset')}>+ ${t('new', 'New conversation')}</button>\n </div>\n <div class=\"filters\">\n <select aria-label=${t('filter-whose', 'Whose')} .value=${this.whose}\n @change=${(e: Event) => this.refilter(() => (this.whose = (e.target as HTMLSelectElement).value as any))}>\n <option value=\"all\">${t('filter-all', 'All')}</option>\n <option value=\"mine\">${t('filter-mine', 'Started by me')}</option>\n <option value=\"participant\">${t('filter-participant', \"I'm in\")}</option>\n </select>\n <select aria-label=${t('filter-visibility', 'Visibility')} .value=${this.visibility}\n @change=${(e: Event) => this.refilter(() => (this.visibility = (e.target as HTMLSelectElement).value as any))}>\n <option value=\"all\">${t('filter-any-visibility', 'Public and private')}</option>\n <option value=\"open\">${t('filter-open', 'Public')}</option>\n <option value=\"private\">${t('filter-private', 'Private')}</option>\n </select>\n <label><input type=\"checkbox\" .checked=${this.unreadOnly}\n @change=${(e: Event) => this.refilter(() => (this.unreadOnly = (e.target as HTMLInputElement).checked))} />${t('filter-unread', 'Unread')}</label>\n <label><input type=\"checkbox\" .checked=${this.includeHidden}\n @change=${(e: Event) => this.refilter(() => (this.includeHidden = (e.target as HTMLInputElement).checked))} />${t('filter-hidden', 'Including hidden')}</label>\n ${this.filtering ? html`<button class=\"clear\" @click=${() => this.clearFilters()}>${t('reset-filters', 'Clear search and filters')}</button>` : nothing}\n </div>\n <div class=\"list\">\n ${this.error ? html`<div class=\"note\" role=\"alert\">${this.error}</div>` : nothing}\n ${this.loading ? html`<div class=\"note\" role=\"status\">${t('loading', 'Loading conversations…')}</div>` : nothing}\n ${shown === 0 && !this.loading && !this.error\n ? html`<div class=\"empty\" role=\"status\">${this.filtering ? t('filtered-empty', 'No conversations match these filters here.') : t('scope-empty', 'No conversations yet.')}</div>`\n : this.entries.map(entry => this.renderRow(entry))}\n ${shown < this.total\n ? html`<button class=\"more\" @click=${() => this.loadMore()}>${t('load-more', 'Show more ({rest})', { rest: this.total - shown })}</button>`\n : nothing}\n </div>\n <!-- A hidden conversation must not be forgotten: say how many, and one press shows them. -->\n ${this.hiddenCount > 0 && !this.includeHidden\n ? html`<button class=\"hiddenfilter\" @click=${() => this.refilter(() => (this.includeHidden = true))}>\n <md-icon>visibility_off</md-icon>${t('hidden-count', '{count} hidden', { count: this.hiddenCount })}\n </button>`\n : nothing}\n `\n }\n\n private renderRow(entry: AssistantSessionEntry): TemplateResult {\n const name = entry.name || t('untitled', 'Untitled conversation')\n const actions = sessionActions(entry)\n const isPrivate = entry.visibility === 'private'\n\n if (this.renamingId === entry.id) {\n return html`<div class=\"row\">\n <span class=\"dot\"></span>\n <input\n class=\"rename\"\n .value=${entry.name || ''}\n aria-label=${t('rename', 'Rename')}\n @keydown=${(e: KeyboardEvent) => {\n if (e.key === 'Enter') this.rename(entry, (e.target as HTMLInputElement).value)\n if (e.key === 'Escape') { e.stopPropagation(); this.renamingId = '' }\n }}\n @blur=${(e: Event) => this.rename(entry, (e.target as HTMLInputElement).value)}\n />\n </div>`\n }\n\n return html`<div\n class=\"row ${entry.unread ? 'unread' : ''} ${entry.hidden ? 'hidden' : ''} ${entry.id === this.sessionId ? 'current' : ''} ${isPrivate ? '' : 'open-audience'}\"\n >\n <span class=\"dot\"></span>\n <button class=\"open-row\" aria-current=${entry.id === this.sessionId ? 'true' : 'false'} @click=${() => this.emit('assistant-session-select', { sessionId: entry.id, kind: entry.kind })}>\n <div class=\"title\">${name}</div>\n <div class=\"prev\">${entry.lastMessagePreview || ''}</div>\n <span class=\"meta\"><span class=\"audience ${isPrivate ? 'private' : 'open'}\"><md-icon aria-hidden=\"true\">${isPrivate ? 'lock' : 'public'}</md-icon>${isPrivate ? t('audience-private', 'Participants only') : t('audience-open', 'Everyone in this domain')}</span>${entry.lastMessageAt && Number.isFinite(Date.parse(entry.lastMessageAt)) ? html` · <time datetime=${entry.lastMessageAt}>${new Intl.DateTimeFormat(i18next.language || 'en', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }).format(new Date(entry.lastMessageAt))}</time>` : nothing}${entry.mine ? html` · ${t('filter-mine', 'Started by me')}` : nothing}</span>\n </button>\n <button\n class=\"more-menu\"\n aria-label=${t('manage', 'Manage conversation')}\n title=${t('manage', 'Manage conversation')}\n @click=${(e: MouseEvent) => this.openRowMenu(e, entry, name)}>⋯</button>\n </div>`\n }\n\n /**\n * A row's actions in the platform's popup menu - one open at a time, closed by a press outside or Escape, placed\n * inside the window. The hand-made menu it replaces stayed open and stacked over the next rows.\n */\n private openRowMenu(event: MouseEvent, entry: AssistantSessionEntry, name: string) {\n event.stopPropagation()\n const actions = sessionActions(entry)\n const isPrivate = entry.visibility === 'private'\n const at = (event.currentTarget as HTMLElement).getBoundingClientRect()\n OxPopupMenu.open({\n template: html`\n ${actions.rename ? html`<ox-popup-menuitem label=${t('rename', 'Rename')} @select=${() => (this.renamingId = entry.id)}></ox-popup-menuitem>` : nothing}\n ${actions.setVisibility\n ? isPrivate\n ? html`<ox-popup-menuitem label=${t('make-open', 'Make public')} @select=${() => this.askOpen(entry, name)}></ox-popup-menuitem>`\n : html`<ox-popup-menuitem label=${t('make-private', 'Make private')} @select=${() => this.setVisibility(entry, 'private')}></ox-popup-menuitem>`\n : nothing}\n <ox-popup-menuitem\n label=${actions.unhide ? t('unhide', 'Show in my list') : t('hide', 'Hide from my list')}\n @select=${() => this.setHidden(entry, !entry.hidden)}></ox-popup-menuitem>\n ${actions.remove ? html`<ox-popup-menuitem label=${t('remove', 'Delete')} @select=${() => this.askRemove(entry, name)}></ox-popup-menuitem>` : nothing}\n `,\n top: at.bottom,\n right: window.innerWidth - at.right,\n parent: document.body\n })\n }\n\n /** Before what cannot be taken back - the platform's confirm, [Cancel][OK] (decided 2026-09-25, §7-3). */\n private async ask(title: string, text: string, confirmText: string): Promise<boolean> {\n return !!(await OxPrompt.open({\n type: 'warning',\n title,\n text,\n confirmButton: { text: confirmText },\n cancelButton: { text: t('cancel', 'Cancel') }\n }))\n }\n\n private async askRemove(entry: AssistantSessionEntry, name: string) {\n const ok = await this.ask(\n t('remove', 'Delete'),\n t('confirm-remove', 'Delete \"{name}\"? It disappears for every participant and cannot be brought back.', { name }),\n t('remove', 'Delete')\n )\n if (ok) this._removeSession(entry)\n }\n\n private async askOpen(entry: AssistantSessionEntry, name: string) {\n const ok = await this.ask(\n t('make-open', 'Make public'),\n t('confirm-open', 'Make \"{name}\" public? Everyone in this domain will be able to read what is already written.', { name }),\n t('make-open', 'Make public')\n )\n if (ok) this.setVisibility(entry, 'open')\n }\n}\n"]}
1
+ {"version":3,"file":"assistant-session-manager.js","sourceRoot":"","sources":["../../client/components/assistant-session-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport { css, html, LitElement, nothing, type PropertyValues, type TemplateResult } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { i18next } from '@operato/i18n'\nimport { OxPrompt } from '@operato/popup'\nimport '@operato/popup/ox-popup-menu.js'\nimport { OxPopupMenu } from '@operato/popup/ox-popup-menu.js'\n\nimport {\n assistantSessionAdmin,\n sessionActions,\n type AssistantSessionAdmin,\n type AssistantSessionEntry,\n type AssistantSessionFilters,\n type SessionVisibility\n} from '../utils/assistant-session-admin.js'\n\nconst PAGE = 30\n\n\nconst t = (key: string, defaultValue: string, options: Record<string, unknown> = {}) =>\n i18next.t(`ai-assistant.session.${key}`, { defaultValue, ...options })\n\n/**\n * The conversations on one anchor, managed - `<ox-assistant-session-manager>`.\n *\n * Search, a filter (all / mine / including hidden), paging, and on each row: open, rename, hide or\n * unhide, private ⇄ public, delete. Taken from operato-twin's dock, where it was written first, so every\n * dock gets it by placing this element (typically behind the toolbar's \"All History\").\n *\n * ── What is the host's ──────────────────────────────────────────────────────────\n * Opening and starting a conversation belong to the dock's session controller, so this element asks\n * with the toolbar's own events and does not open anything itself:\n * assistant-session-select { sessionId } open that conversation\n * assistant-session-reset start a new one\n * assistant-session-changed { sessionId, change: 'renamed' | 'hidden' | 'unhidden' | 'removed' | 'visibility' | 'left' }\n * a row changed - the host refreshes its tabs; if the open\n * conversation was removed or hidden, it moves off it\n *\n * ── What is the server's ────────────────────────────────────────────────────────\n * Who may do what (`sessionActions`): rename any participant, hide anyone for their own list, delete\n * the creator, visibility the owner. The buttons follow those hints; the doors decide again.\n *\n * ── Asking before what cannot be undone ─────────────────────────────────────────\n * Delete, and opening a private conversation to the domain, ask on the row itself - [Cancel][OK].\n * Making it private, hiding and renaming do not: each can be undone from the same row.\n */\n@customElement('ox-assistant-session-manager')\nexport class AssistantSessionManager extends LitElement {\n static styles = css`\n /* One scale for the list: name 13, preview 12, meta 11 - the rest of the dock's sizes. */\n .open-row { display: block; flex: 1; min-width: 0; text-align: start; border: 0; background: transparent; color: inherit; font: inherit; padding: 0; cursor: pointer; }\n .meta { display: block; margin-top: 2px; font-size: 11px; color: var(--md-sys-color-on-surface-variant); }\n /* Open and private must part at a glance - an open row is read by everyone in the domain (2026-09-30). */\n .audience { display: inline-flex; align-items: center; gap: 3px; vertical-align: middle; }\n .audience md-icon { --md-icon-size: 14px; }\n /* The dock's warning colour, fixed across applications - see assistant-dock.ts. */\n :host { --assistant-open-edge: #c47f00; --assistant-open-surface: #f2b01e; --assistant-open-ink: #2e2100; }\n .audience.open { padding: 0 5px 0 3px; border-radius: 4px; background: var(--assistant-open-surface); color: var(--assistant-open-ink); font-weight: 600; }\n .audience md-icon { --md-icon-size: 12px; }\n .row.open-audience { box-shadow: inset 4px 0 0 var(--assistant-open-edge); }\n .more-menu { flex: none; border: 0; border-radius: 4px; padding: 2px 6px; background: none; color: var(--md-sys-color-on-surface-variant); font-size: 14px; line-height: 1; cursor: pointer; }\n .more-menu:hover { background: var(--md-sys-color-surface-container-high); color: var(--md-sys-color-on-surface); }\n button:focus-visible { outline: 2px solid var(--md-sys-color-primary); outline-offset: 2px; }\n :host {\n display: flex;\n flex-direction: column;\n min-height: 0;\n font-size: 13px;\n color: var(--md-sys-color-on-surface);\n }\n\n .bar {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 10px 6px;\n }\n\n .bar input,\n .bar select {\n min-width: 0;\n height: 26px;\n font-size: 12px;\n box-sizing: border-box;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n background: var(--md-sys-color-surface-container-lowest);\n color: inherit;\n font: inherit;\n padding: 0 8px;\n }\n\n .bar input {\n flex: 1;\n }\n\n .new {\n flex: none;\n border: 0;\n border-radius: 6px;\n padding: 4px 8px;\n background: transparent;\n color: var(--md-sys-color-primary);\n font: inherit;\n font-size: 12px;\n font-weight: 600;\n white-space: nowrap;\n cursor: pointer;\n }\n\n /* Always open, one quiet line under the search - the same small size as the search. */\n .filters {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 4px 10px;\n padding: 0 10px 8px;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n font-size: 11px;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n .filters select {\n height: 22px;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 4px;\n background: var(--md-sys-color-surface-container-lowest);\n color: inherit;\n font: inherit;\n padding: 0 4px;\n }\n\n .filters label {\n display: inline-flex;\n align-items: center;\n gap: 3px;\n cursor: pointer;\n }\n\n .filters input[type='checkbox'] {\n margin: 0;\n }\n\n .filters .clear {\n margin-left: auto;\n border: 0;\n background: none;\n padding: 0;\n color: var(--md-sys-color-primary);\n font: inherit;\n cursor: pointer;\n }\n\n .list {\n flex: 1;\n min-height: 0;\n overflow: auto;\n }\n\n .row {\n position: relative;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 7px 10px;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n cursor: pointer;\n }\n\n .row:hover,\n .row.current {\n background: var(--md-sys-color-surface-container);\n }\n\n .row.hidden {\n opacity: 0.6;\n }\n\n .dot {\n flex: none;\n width: 7px;\n height: 7px;\n border-radius: 50%;\n }\n\n .row.unread .dot {\n background: var(--md-sys-color-primary);\n }\n\n .row md-icon {\n --md-icon-size: 16px;\n flex: none;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n .body {\n flex: 1;\n min-width: 0;\n }\n\n .title,\n .prev {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .row.unread .title {\n font-weight: 700;\n }\n\n .prev {\n margin-top: 1px;\n color: var(--md-sys-color-on-surface-variant);\n font-size: 12px;\n }\n\n .rename {\n flex: 1;\n min-width: 0;\n height: 26px;\n border: 1px solid var(--md-sys-color-primary);\n border-radius: 6px;\n padding: 0 6px;\n font: inherit;\n background: var(--md-sys-color-surface-container-lowest);\n color: inherit;\n }\n\n .ask {\n display: grid;\n gap: 6px;\n padding: 10px;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n background: var(--md-sys-color-surface-container);\n }\n\n .ask .buttons {\n display: flex;\n justify-content: flex-end;\n gap: 6px;\n }\n\n .ask button {\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n padding: 4px 10px;\n background: var(--md-sys-color-surface-container-lowest);\n color: inherit;\n font: inherit;\n cursor: pointer;\n }\n\n .ask button.ok {\n border-color: var(--md-sys-color-primary);\n background: var(--md-sys-color-primary);\n color: var(--md-sys-color-on-primary);\n }\n\n .ask button.ok.danger {\n border-color: var(--md-sys-color-error);\n background: var(--md-sys-color-error);\n color: var(--md-sys-color-on-error);\n }\n\n .note,\n .empty {\n padding: 14px 12px;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n [role='alert'] {\n color: var(--md-sys-color-error);\n }\n\n .more,\n .hiddenfilter {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n width: 100%;\n padding: 8px;\n border: 0;\n border-top: 1px solid var(--md-sys-color-outline-variant);\n background: none;\n color: var(--md-sys-color-on-surface-variant);\n font: inherit;\n cursor: pointer;\n }\n\n .hiddenfilter md-icon {\n --md-icon-size: 16px;\n }\n `\n\n /** The anchor whose conversations are listed - the same one the dock's session controller opened. */\n @property({ type: String }) anchorType = ''\n @property({ type: String }) anchorId = ''\n /** The conversation the dock has open - marked in the list. */\n @property({ type: String }) sessionId = ''\n /** The doors. Replaceable for tests; a host normally leaves it. */\n @property({ attribute: false }) admin: AssistantSessionAdmin = assistantSessionAdmin\n /** The conversation kind (mode) open in the dock - the list's default scope (§2-6). */\n @property({ type: String }) kind = ''\n\n @state() private entries: AssistantSessionEntry[] = []\n @state() private total = 0\n @state() private hiddenCount = 0\n @state() private search = ''\n @state() private whose: NonNullable<AssistantSessionFilters['whose']> = 'all'\n @state() private visibility: NonNullable<AssistantSessionFilters['visibility']> = 'all'\n @state() private includeHidden = false\n @state() private unreadOnly = false\n @state() private pageSize = PAGE\n @state() private loading = false\n @state() private error = ''\n @state() private renamingId = ''\n\n /*\n * The search and the filters stand open together, always (2026-10-12) - a filter behind a \"Filters\" button is one\n * more thing to find before finding the conversation. The list is this screen's conversations, so there is no kind\n * to choose.\n */\n private get filtering() {\n return !!this.search.trim() || this.whose !== 'all' || this.visibility !== 'all' || this.includeHidden || this.unreadOnly\n }\n\n private clearFilters() {\n clearTimeout(this.searchTimer)\n this.refilter(() => {\n this.search = ''\n this.whose = 'all'\n this.visibility = 'all'\n this.includeHidden = this.unreadOnly = false\n })\n }\n\n private generation = 0\n private searchTimer?: ReturnType<typeof setTimeout>\n\n /** Read the list again - the host calls this after it opened or started a conversation. */\n async reload(): Promise<void> {\n const generation = ++this.generation\n if (!this.anchorType || !this.anchorId) {\n this.entries = []\n this.total = this.hiddenCount = 0\n return\n }\n\n this.loading = true\n this.error = ''\n try {\n /*\n * Paging reads again from the start rather than appending - live activity reorders the list, and\n * appending a later page to an earlier order duplicates and drops rows.\n */\n const page = await this.admin.inbox({\n anchorType: this.anchorType,\n anchorId: this.anchorId,\n search: this.search,\n filters: {\n kind: this.kind,\n whose: this.whose,\n visibility: this.visibility,\n includeHidden: this.includeHidden,\n unreadOnly: this.unreadOnly\n },\n limit: this.pageSize,\n offset: 0\n })\n if (generation !== this.generation) return\n this.entries = page.entries\n this.total = page.total\n this.hiddenCount = page.hiddenCount\n } catch (error) {\n if (generation !== this.generation) return\n this.entries = []\n this.total = this.hiddenCount = 0\n this.error = (error as Error).message || t('failed', 'could not load the conversation list.')\n } finally {\n if (generation === this.generation) this.loading = false\n }\n }\n\n protected updated(changed: PropertyValues) {\n if (changed.has('anchorType') || changed.has('anchorId') || changed.has('admin') || changed.has('kind')) void this.reload()\n\n /*\n * The rename box takes the keys the moment it opens, with the name selected. `autofocus` works once\n * per document, so the second rename opened a box that ignored the typing (2026-09-24, operato-ecs).\n */\n if (changed.has('renamingId') && this.renamingId) {\n const input = this.renderRoot.querySelector('input.rename') as HTMLInputElement | null\n input?.focus()\n input?.select()\n }\n }\n\n disconnectedCallback() {\n this.generation++\n clearTimeout(this.searchTimer)\n super.disconnectedCallback()\n }\n\n private emit(name: string, detail?: unknown) {\n this.dispatchEvent(new CustomEvent(name, { detail, bubbles: true, composed: true }))\n }\n\n /* ── what the person does ────────────────────────────────────────────────────────── */\n\n private onSearch(value: string) {\n this.search = value\n clearTimeout(this.searchTimer)\n /* Not on every keystroke. A new search starts from the first page. */\n this.searchTimer = setTimeout(() => {\n this.pageSize = PAGE\n void this.reload()\n }, 200)\n }\n\n /** A filter changed - the list starts from its first page again. */\n private refilter(change: () => void) {\n change()\n this.pageSize = PAGE\n void this.reload()\n }\n\n private loadMore() {\n this.pageSize += PAGE\n void this.reload()\n }\n\n /** Runs one door, then reads the list again - on success and on failure alike, so the row shows the truth. */\n private async act(entry: AssistantSessionEntry, change: string, run: () => Promise<boolean>, refused: string) {\n this.renamingId = ''\n try {\n if (!(await run())) this.error = refused\n else this.emit('assistant-session-changed', { sessionId: entry.id, change })\n } catch (error) {\n /* Never pass a failure silently - a person must not believe a conversation was deleted when it was not. */\n this.error = (error as Error).message\n }\n const error = this.error\n await this.reload()\n if (error && !this.error) this.error = error\n }\n\n private rename(entry: AssistantSessionEntry, name: string) {\n const trimmed = name.trim()\n this.renamingId = ''\n if (!trimmed || trimmed === entry.name) return\n void this.act(entry, 'renamed', () => this.admin.rename(entry.id, trimmed), t('rename-refused', 'this conversation could not be renamed.'))\n }\n\n private setHidden(entry: AssistantSessionEntry, hidden: boolean) {\n void this.act(entry, hidden ? 'hidden' : 'unhidden', () => this.admin.hide(entry.id, hidden), t('hide-refused', 'this conversation could not be hidden.'))\n }\n\n private setVisibility(entry: AssistantSessionEntry, visibility: SessionVisibility) {\n void this.act(\n entry,\n 'visibility',\n () => this.admin.setVisibility(entry.id, visibility),\n t('visibility-refused', 'only the owner of this conversation can change who reads it.')\n )\n }\n\n private _removeSession(entry: AssistantSessionEntry) {\n void this.act(entry, 'removed', () => this.admin.remove(entry.id), t('remove-refused', 'this conversation could not be deleted.'))\n }\n\n /* ── drawing ─────────────────────────────────────────────────────────────────────── */\n\n render(): TemplateResult {\n const shown = this.entries.length\n\n return html`\n <div class=\"bar\">\n <input\n .value=${this.search}\n placeholder=${t('search', 'Search conversations')}\n aria-label=${t('search', 'Search conversations')}\n @input=${(e: Event) => this.onSearch((e.target as HTMLInputElement).value)}\n @keydown=${(e: KeyboardEvent) => { if (e.key === 'Escape' && this.search) { e.stopPropagation(); this.onSearch('') } }}\n />\n <button class=\"new\" @click=${() => this.emit('assistant-session-reset')}>+ ${t('new', 'New conversation')}</button>\n </div>\n <div class=\"filters\">\n <select aria-label=${t('filter-whose', 'Whose')} .value=${this.whose}\n @change=${(e: Event) => this.refilter(() => (this.whose = (e.target as HTMLSelectElement).value as any))}>\n <option value=\"all\">${t('filter-all', 'All')}</option>\n <option value=\"mine\">${t('filter-mine', 'Started by me')}</option>\n <option value=\"participant\">${t('filter-participant', \"I'm in\")}</option>\n </select>\n <select aria-label=${t('filter-visibility', 'Visibility')} .value=${this.visibility}\n @change=${(e: Event) => this.refilter(() => (this.visibility = (e.target as HTMLSelectElement).value as any))}>\n <option value=\"all\">${t('filter-any-visibility', 'Anyone')}</option>\n <option value=\"open\">${t('filter-open', 'Public')}</option>\n <option value=\"group\">${t('filter-group', 'Group')}</option>\n <option value=\"private\">${t('filter-private', 'Private')}</option>\n </select>\n <label><input type=\"checkbox\" .checked=${this.unreadOnly}\n @change=${(e: Event) => this.refilter(() => (this.unreadOnly = (e.target as HTMLInputElement).checked))} />${t('filter-unread', 'Unread')}</label>\n <label><input type=\"checkbox\" .checked=${this.includeHidden}\n @change=${(e: Event) => this.refilter(() => (this.includeHidden = (e.target as HTMLInputElement).checked))} />${t('filter-hidden', 'Including hidden')}</label>\n ${this.filtering ? html`<button class=\"clear\" @click=${() => this.clearFilters()}>${t('reset-filters', 'Clear search and filters')}</button>` : nothing}\n </div>\n <div class=\"list\">\n ${this.error ? html`<div class=\"note\" role=\"alert\">${this.error}</div>` : nothing}\n ${this.loading ? html`<div class=\"note\" role=\"status\">${t('loading', 'Loading conversations…')}</div>` : nothing}\n ${shown === 0 && !this.loading && !this.error\n ? html`<div class=\"empty\" role=\"status\">${this.filtering ? t('filtered-empty', 'No conversations match these filters here.') : t('scope-empty', 'No conversations yet.')}</div>`\n : this.entries.map(entry => this.renderRow(entry))}\n ${shown < this.total\n ? html`<button class=\"more\" @click=${() => this.loadMore()}>${t('load-more', 'Show more ({rest})', { rest: this.total - shown })}</button>`\n : nothing}\n </div>\n <!-- A hidden conversation must not be forgotten: say how many, and one press shows them. -->\n ${this.hiddenCount > 0 && !this.includeHidden\n ? html`<button class=\"hiddenfilter\" @click=${() => this.refilter(() => (this.includeHidden = true))}>\n <md-icon>visibility_off</md-icon>${t('hidden-count', '{count} hidden', { count: this.hiddenCount })}\n </button>`\n : nothing}\n `\n }\n\n private renderRow(entry: AssistantSessionEntry): TemplateResult {\n const name = entry.name || t('untitled', 'Untitled conversation')\n const actions = sessionActions(entry)\n const isOpen = !entry.visibility || entry.visibility === 'open'\n\n if (this.renamingId === entry.id) {\n return html`<div class=\"row\">\n <span class=\"dot\"></span>\n <input\n class=\"rename\"\n .value=${entry.name || ''}\n aria-label=${t('rename', 'Rename')}\n @keydown=${(e: KeyboardEvent) => {\n if (e.key === 'Enter') this.rename(entry, (e.target as HTMLInputElement).value)\n if (e.key === 'Escape') { e.stopPropagation(); this.renamingId = '' }\n }}\n @blur=${(e: Event) => this.rename(entry, (e.target as HTMLInputElement).value)}\n />\n </div>`\n }\n\n return html`<div\n class=\"row ${entry.unread ? 'unread' : ''} ${entry.hidden ? 'hidden' : ''} ${entry.id === this.sessionId ? 'current' : ''} ${isOpen ? 'open-audience' : ''}\"\n >\n <span class=\"dot\"></span>\n <button class=\"open-row\" aria-current=${entry.id === this.sessionId ? 'true' : 'false'} @click=${() => this.emit('assistant-session-select', { sessionId: entry.id, kind: entry.kind })}>\n <div class=\"title\">${name}</div>\n <div class=\"prev\">${entry.lastMessagePreview || ''}</div>\n <span class=\"meta\">${this.renderAudience(entry.visibility)}${entry.lastMessageAt && Number.isFinite(Date.parse(entry.lastMessageAt)) ? html` · <time datetime=${entry.lastMessageAt}>${new Intl.DateTimeFormat(i18next.language || 'en', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }).format(new Date(entry.lastMessageAt))}</time>` : nothing}${entry.mine ? html` · ${t('filter-mine', 'Started by me')}` : nothing}</span>\n </button>\n <button\n class=\"more-menu\"\n aria-label=${t('manage', 'Manage conversation')}\n title=${t('manage', 'Manage conversation')}\n @click=${(e: MouseEvent) => this.openRowMenu(e, entry, name)}>⋯</button>\n </div>`\n }\n\n /**\n * A row's actions in the platform's popup menu - one open at a time, closed by a press outside or Escape, placed\n * inside the window. The hand-made menu it replaces stayed open and stacked over the next rows.\n */\n private openRowMenu(event: MouseEvent, entry: AssistantSessionEntry, name: string) {\n event.stopPropagation()\n const actions = sessionActions(entry)\n const current = entry.visibility || 'open'\n const at = (event.currentTarget as HTMLElement).getBoundingClientRect()\n OxPopupMenu.open({\n template: html`\n ${actions.rename ? html`<ox-popup-menuitem label=${t('rename', 'Rename')} @select=${() => (this.renamingId = entry.id)}></ox-popup-menuitem>` : nothing}\n ${actions.setVisibility && current !== 'open'\n ? html`<ox-popup-menuitem label=${t('make-open', 'Make public')} @select=${() => this.askOpen(entry, name)}></ox-popup-menuitem>`\n : nothing}\n ${actions.setVisibility && current !== 'group'\n ? html`<ox-popup-menuitem label=${t('make-group', 'Make it a group')} @select=${() => this.setVisibility(entry, 'group')}></ox-popup-menuitem>`\n : nothing}\n ${actions.setVisibility && current !== 'private'\n ? html`<ox-popup-menuitem label=${t('make-private', 'Make private')} @select=${() => this.askPrivate(entry, name)}></ox-popup-menuitem>`\n : nothing}\n ${actions.leave ? html`<ox-popup-menuitem label=${t('leave', 'Leave')} @select=${() => this.askLeave(entry, name)}></ox-popup-menuitem>` : nothing}\n <ox-popup-menuitem\n label=${actions.unhide ? t('unhide', 'Show in my list') : t('hide', 'Hide from my list')}\n @select=${() => this.setHidden(entry, !entry.hidden)}></ox-popup-menuitem>\n ${actions.remove ? html`<ox-popup-menuitem label=${t('remove', 'Delete')} @select=${() => this.askRemove(entry, name)}></ox-popup-menuitem>` : nothing}\n `,\n top: at.bottom,\n right: window.innerWidth - at.right,\n parent: document.body\n })\n }\n\n /** Before what cannot be taken back - the platform's confirm, [Cancel][OK] (decided 2026-09-25, §7-3). */\n private async ask(title: string, text: string, confirmText: string): Promise<boolean> {\n return !!(await OxPrompt.open({\n type: 'warning',\n title,\n text,\n confirmButton: { text: confirmText },\n cancelButton: { text: t('cancel', 'Cancel') }\n }))\n }\n\n private async askRemove(entry: AssistantSessionEntry, name: string) {\n const ok = await this.ask(\n t('remove', 'Delete'),\n t('confirm-remove', 'Delete \"{name}\"? It disappears for every participant and cannot be brought back.', { name }),\n t('remove', 'Delete')\n )\n if (ok) this._removeSession(entry)\n }\n\n private async askOpen(entry: AssistantSessionEntry, name: string) {\n const ok = await this.ask(\n t('make-open', 'Make public'),\n t('confirm-open', 'Make \"{name}\" public? Everyone in this domain will be able to read what is already written.', { name }),\n t('make-open', 'Make public')\n )\n if (ok) this.setVisibility(entry, 'open')\n }\n\n /* Private is the owner's alone - the people in it leave, and coming back is being invited again. */\n private async askPrivate(entry: AssistantSessionEntry, name: string) {\n const ok = await this.ask(\n t('make-private', 'Make private'),\n t('confirm-private', 'Make \"{name}\" private? Only you will read it, and everyone else in it leaves.', { name }),\n t('make-private', 'Make private')\n )\n if (ok) this.setVisibility(entry, 'private')\n }\n\n private async askLeave(entry: AssistantSessionEntry, name: string) {\n const ok = await this.ask(\n t('leave', 'Leave'),\n entry.visibility === 'open'\n ? t('confirm-leave-open', 'Leave \"{name}\"? It stops coming to your list.', { name })\n : t('confirm-leave', 'Leave \"{name}\"? You will not read it again unless its owner brings you back.', { name }),\n t('leave', 'Leave')\n )\n if (ok) void this.act(entry, 'left', () => this.admin.leave(entry.id), t('leave-refused', 'you could not leave this conversation.'))\n }\n\n /** Who reads a conversation, on its row: the same three words wherever a conversation is listed. */\n private renderAudience(visibility?: string) {\n const v = visibility || 'open'\n const icon = v === 'open' ? 'public' : v === 'group' ? 'group' : 'lock'\n const label =\n v === 'open'\n ? t('audience-open', 'Everyone in this domain')\n : v === 'group'\n ? t('audience-group', 'Invited people')\n : t('audience-private', 'Only me')\n return html`<span class=\"audience ${v}\"><md-icon aria-hidden=\"true\">${icon}</md-icon>${label}</span>`\n }\n}\n"]}
@@ -42,4 +42,12 @@ it('★ a stored answer brings its cards back after a refresh', () => {
42
42
  it('the chat reads stored proposals from history and from the broadcast', () => {
43
43
  expect(chat.split('proposalsJson').length - 1).toBeGreaterThanOrEqual(2);
44
44
  });
45
+ it('a failed page-context handoff never sends the request into the previous conversation', () => {
46
+ const take = body(dock, 'private async takeAsk()');
47
+ expect(take.indexOf('if (!id) return')).toBeGreaterThan(take.indexOf('await this.startCarried(ask)'));
48
+ expect(take.indexOf('if (!id) return')).toBeLessThan(take.indexOf('this.asking ='));
49
+ const carry = body(dock, 'private async startCarried(');
50
+ expect(carry).toContain('if (created.errors?.length) throw');
51
+ expect(carry).toContain('if (attached.errors?.length) throw');
52
+ });
45
53
  //# sourceMappingURL=proposal-request.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"proposal-request.test.js","sourceRoot":"","sources":["../../client/components/proposal-request.test.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * ADR-0097 Q-D4 ④ — the chat asks the server before it asks the host, and a card that was requested and\n * never answered offers \"check result\", never \"run\".\n *\n * The component itself needs a browser; the order of its steps is read from the source, and the rules\n * it follows are tested on their own (`proposal-outcome.test.ts`).\n */\nimport { readFileSync } from 'fs'\nimport { join } from 'path'\n\nimport { remoteMessageToLine } from './chat-echo-dedup'\n\nconst chat = readFileSync(join(__dirname, 'assistant-chat.ts'), 'utf8')\nconst dock = readFileSync(join(__dirname, 'assistant-dock.ts'), 'utf8')\n\nconst body = (source: string, start: string) => {\n const from = source.indexOf(start)\n expect(from).toBeGreaterThan(0)\n return source.slice(from, source.indexOf('\\n }\\n', from))\n}\n\nit('★ the host is asked only after the server granted the request', () => {\n const run = body(chat, 'private async executeProposal(')\n const asked = run.indexOf('await this.requestProposal(key)')\n const dispatched = run.indexOf(\"new CustomEvent('assistant-proposal-execute'\")\n expect(asked).toBeGreaterThan(0)\n expect(dispatched).toBeGreaterThan(asked)\n expect(run.slice(asked, dispatched)).toMatch(/if \\(!granted\\) \\{[\\s\\S]*return\\n/)\n})\n\nit('★ a requested card without an answer checks the result — it never runs again', () => {\n const unconfirmed = chat.slice(chat.indexOf(\"card.state === 'unconfirmed'\"), chat.indexOf(': html`', chat.indexOf(\"card.state === 'unconfirmed'\")))\n expect(unconfirmed).toMatch(/this\\.checkProposal\\(p, key\\)/)\n expect(unconfirmed).not.toMatch(/executeProposal/)\n})\n\nit('★ \"check result\" reports only what the host knows, and runs nothing', () => {\n const check = body(dock, 'private onProposalCheck = async')\n expect(check).toMatch(/this\\.surface\\?\\.proposalOutcome/)\n expect(check).toMatch(/if \\(!outcome\\?\\.verdict\\) \\{[\\s\\S]*return/)\n expect(check).not.toMatch(/runProposal|assistant-proposal-execute/)\n})\n\nit('★ a stored answer brings its cards back after a refresh', () => {\n const line: any = remoteMessageToLine({ id: 'm1', role: 'assistant', content: 'here', proposalsJson: [{ tool: 'proposeFigureCandidate', key: 'k1' }] })\n expect(line.proposals).toEqual([{ tool: 'proposeFigureCandidate', key: 'k1' }])\n expect((remoteMessageToLine({ id: 'm2', role: 'assistant', content: 'plain', proposalsJson: [] }) as any).proposals).toBeUndefined()\n})\n\nit('the chat reads stored proposals from history and from the broadcast', () => {\n expect(chat.split('proposalsJson').length - 1).toBeGreaterThanOrEqual(2)\n})\n"]}
1
+ {"version":3,"file":"proposal-request.test.js","sourceRoot":"","sources":["../../client/components/proposal-request.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAA;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAE3B,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AAEvD,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,mBAAmB,CAAC,EAAE,MAAM,CAAC,CAAA;AACvE,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,mBAAmB,CAAC,EAAE,MAAM,CAAC,CAAA;AAEvE,MAAM,IAAI,GAAG,CAAC,MAAc,EAAE,KAAa,EAAE,EAAE;IAC7C,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;IAClC,MAAM,CAAC,IAAI,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAA;IAC/B,OAAO,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAA;AAC5D,CAAC,CAAA;AAED,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;IACvE,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,gCAAgC,CAAC,CAAA;IACxD,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,iCAAiC,CAAC,CAAA;IAC5D,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,8CAA8C,CAAC,CAAA;IAC9E,MAAM,CAAC,KAAK,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAA;IAChC,MAAM,CAAC,UAAU,CAAC,CAAC,eAAe,CAAC,KAAK,CAAC,CAAA;IACzC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,mCAAmC,CAAC,CAAA;AACnF,CAAC,CAAC,CAAA;AAEF,EAAE,CAAC,8EAA8E,EAAE,GAAG,EAAE;IACtF,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,8BAA8B,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,8BAA8B,CAAC,CAAC,CAAC,CAAA;IACnJ,MAAM,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,+BAA+B,CAAC,CAAA;IAC5D,MAAM,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAA;AACpD,CAAC,CAAC,CAAA;AAEF,EAAE,CAAC,qEAAqE,EAAE,GAAG,EAAE;IAC7E,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,iCAAiC,CAAC,CAAA;IAC3D,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,kCAAkC,CAAC,CAAA;IACzD,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,4CAA4C,CAAC,CAAA;IACnE,MAAM,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,wCAAwC,CAAC,CAAA;AACrE,CAAC,CAAC,CAAA;AAEF,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;IACjE,MAAM,IAAI,GAAQ,mBAAmB,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC,EAAE,IAAI,EAAE,wBAAwB,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAA;IACvJ,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,wBAAwB,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;IAC/E,MAAM,CAAE,mBAAmB,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,EAAE,EAAE,CAAS,CAAC,SAAS,CAAC,CAAC,aAAa,EAAE,CAAA;AACtI,CAAC,CAAC,CAAA;AAEF,EAAE,CAAC,qEAAqE,EAAE,GAAG,EAAE;IAC7E,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAA;AAC1E,CAAC,CAAC,CAAA;AAGF,EAAE,CAAC,sFAAsF,EAAE,GAAG,EAAE;IAC9F,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,yBAAyB,CAAC,CAAA;IAClD,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,8BAA8B,CAAC,CAAC,CAAA;IACrG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,CAAA;IACnF,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,6BAA6B,CAAC,CAAA;IACvD,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,mCAAmC,CAAC,CAAA;IAC5D,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,oCAAoC,CAAC,CAAA;AAC/D,CAAC,CAAC,CAAA","sourcesContent":["/*\n * ADR-0097 Q-D4 ④ — the chat asks the server before it asks the host, and a card that was requested and\n * never answered offers \"check result\", never \"run\".\n *\n * The component itself needs a browser; the order of its steps is read from the source, and the rules\n * it follows are tested on their own (`proposal-outcome.test.ts`).\n */\nimport { readFileSync } from 'fs'\nimport { join } from 'path'\n\nimport { remoteMessageToLine } from './chat-echo-dedup'\n\nconst chat = readFileSync(join(__dirname, 'assistant-chat.ts'), 'utf8')\nconst dock = readFileSync(join(__dirname, 'assistant-dock.ts'), 'utf8')\n\nconst body = (source: string, start: string) => {\n const from = source.indexOf(start)\n expect(from).toBeGreaterThan(0)\n return source.slice(from, source.indexOf('\\n }\\n', from))\n}\n\nit('★ the host is asked only after the server granted the request', () => {\n const run = body(chat, 'private async executeProposal(')\n const asked = run.indexOf('await this.requestProposal(key)')\n const dispatched = run.indexOf(\"new CustomEvent('assistant-proposal-execute'\")\n expect(asked).toBeGreaterThan(0)\n expect(dispatched).toBeGreaterThan(asked)\n expect(run.slice(asked, dispatched)).toMatch(/if \\(!granted\\) \\{[\\s\\S]*return\\n/)\n})\n\nit('★ a requested card without an answer checks the result — it never runs again', () => {\n const unconfirmed = chat.slice(chat.indexOf(\"card.state === 'unconfirmed'\"), chat.indexOf(': html`', chat.indexOf(\"card.state === 'unconfirmed'\")))\n expect(unconfirmed).toMatch(/this\\.checkProposal\\(p, key\\)/)\n expect(unconfirmed).not.toMatch(/executeProposal/)\n})\n\nit('★ \"check result\" reports only what the host knows, and runs nothing', () => {\n const check = body(dock, 'private onProposalCheck = async')\n expect(check).toMatch(/this\\.surface\\?\\.proposalOutcome/)\n expect(check).toMatch(/if \\(!outcome\\?\\.verdict\\) \\{[\\s\\S]*return/)\n expect(check).not.toMatch(/runProposal|assistant-proposal-execute/)\n})\n\nit('★ a stored answer brings its cards back after a refresh', () => {\n const line: any = remoteMessageToLine({ id: 'm1', role: 'assistant', content: 'here', proposalsJson: [{ tool: 'proposeFigureCandidate', key: 'k1' }] })\n expect(line.proposals).toEqual([{ tool: 'proposeFigureCandidate', key: 'k1' }])\n expect((remoteMessageToLine({ id: 'm2', role: 'assistant', content: 'plain', proposalsJson: [] }) as any).proposals).toBeUndefined()\n})\n\nit('the chat reads stored proposals from history and from the broadcast', () => {\n expect(chat.split('proposalsJson').length - 1).toBeGreaterThanOrEqual(2)\n})\n\n\nit('a failed page-context handoff never sends the request into the previous conversation', () => {\n const take = body(dock, 'private async takeAsk()')\n expect(take.indexOf('if (!id) return')).toBeGreaterThan(take.indexOf('await this.startCarried(ask)'))\n expect(take.indexOf('if (!id) return')).toBeLessThan(take.indexOf('this.asking ='))\n const carry = body(dock, 'private async startCarried(')\n expect(carry).toContain('if (created.errors?.length) throw')\n expect(carry).toContain('if (attached.errors?.length) throw')\n})\n"]}
@@ -6,6 +6,4 @@ export interface RosterParticipant {
6
6
  username?: string;
7
7
  };
8
8
  }
9
- /** Missing identity/loading is not evidence that a person lacks membership. Server gates remain authoritative. */
10
- export declare function rosterInviteState(rows: readonly RosterParticipant[], userId: string | undefined, loaded: boolean): 'checking' | 'allowed' | 'denied';
11
9
  export declare function rosterName(person: RosterParticipant, fallback: string): string;
@@ -1,10 +1,3 @@
1
- /** Missing identity/loading is not evidence that a person lacks membership. Server gates remain authoritative. */
2
- export function rosterInviteState(rows, userId, loaded) {
3
- if (!loaded || !userId || rows.some(row => !row.user?.id))
4
- return 'checking';
5
- const role = rows.find(row => row.user?.id === userId)?.role;
6
- return role === 'owner' || role === 'member' ? 'allowed' : 'denied';
7
- }
8
1
  export function rosterName(person, fallback) {
9
2
  return person.user?.name?.trim() || person.user?.username?.trim() || fallback;
10
3
  }
@@ -1 +1 @@
1
- {"version":3,"file":"roster-identity.js","sourceRoot":"","sources":["../../client/components/roster-identity.ts"],"names":[],"mappings":"AACA,kHAAkH;AAClH,MAAM,UAAU,iBAAiB,CAAC,IAAkC,EAAE,MAA0B,EAAE,MAAe;IAC/G,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC;QAAE,OAAO,UAAU,CAAA;IAC5E,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,KAAK,MAAM,CAAC,EAAE,IAAI,CAAA;IAC5D,OAAO,IAAI,KAAK,OAAO,IAAI,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAA;AACrE,CAAC;AACD,MAAM,UAAU,UAAU,CAAC,MAAyB,EAAE,QAAgB;IACpE,OAAO,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,MAAM,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,QAAQ,CAAA;AAC/E,CAAC","sourcesContent":["export interface RosterParticipant { role: string; user?: { id: string; name?: string; username?: string } }\n/** Missing identity/loading is not evidence that a person lacks membership. Server gates remain authoritative. */\nexport function rosterInviteState(rows: readonly RosterParticipant[], userId: string | undefined, loaded: boolean): 'checking' | 'allowed' | 'denied' {\n if (!loaded || !userId || rows.some(row => !row.user?.id)) return 'checking'\n const role = rows.find(row => row.user?.id === userId)?.role\n return role === 'owner' || role === 'member' ? 'allowed' : 'denied'\n}\nexport function rosterName(person: RosterParticipant, fallback: string): string {\n return person.user?.name?.trim() || person.user?.username?.trim() || fallback\n}\n"]}
1
+ {"version":3,"file":"roster-identity.js","sourceRoot":"","sources":["../../client/components/roster-identity.ts"],"names":[],"mappings":"AACA,MAAM,UAAU,UAAU,CAAC,MAAyB,EAAE,QAAgB;IACpE,OAAO,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,MAAM,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,QAAQ,CAAA;AAC/E,CAAC","sourcesContent":["export interface RosterParticipant { role: string; user?: { id: string; name?: string; username?: string } }\nexport function rosterName(person: RosterParticipant, fallback: string): string {\n return person.user?.name?.trim() || person.user?.username?.trim() || fallback\n}\n"]}
@@ -1,16 +1,5 @@
1
- import { rosterInviteState, rosterName } from './roster-identity';
2
- describe('roster identity and invite notice', () => {
3
- it('does not claim denial when participant identities are missing or still loading', () => {
4
- expect(rosterInviteState([{ role: 'owner' }], 'me', true)).toBe('checking');
5
- expect(rosterInviteState([], 'me', false)).toBe('checking');
6
- expect(rosterInviteState([], undefined, true)).toBe('checking');
7
- });
8
- it('uses the current person role rather than another member or a stale loading state', () => {
9
- for (const role of ['owner', 'member'])
10
- expect(rosterInviteState([{ role, user: { id: 'me' } }], 'me', true)).toBe('allowed');
11
- expect(rosterInviteState([{ role: 'viewer', user: { id: 'me' } }, { role: 'owner', user: { id: 'other' } }], 'me', true)).toBe('denied');
12
- expect(rosterInviteState([{ role: 'member', user: { id: 'other' } }], 'me', true)).toBe('denied');
13
- });
1
+ import { rosterName } from './roster-identity';
2
+ describe('roster identity', () => {
14
3
  it('shows a recognizable full name, then username, then an explicit unavailable label', () => {
15
4
  expect(rosterName({ role: 'member', user: { id: 'u', name: ' 정연희 ' } }, 'Unavailable')).toBe('정연희');
16
5
  expect(rosterName({ role: 'member', user: { id: 'u', username: 'yeonhee' } }, 'Unavailable')).toBe('yeonhee');
@@ -1 +1 @@
1
- {"version":3,"file":"roster-identity.test.js","sourceRoot":"","sources":["../../client/components/roster-identity.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACjE,QAAQ,CAAC,mCAAmC,EAAC,GAAE,EAAE;IAChD,EAAE,CAAC,gFAAgF,EAAC,GAAE,EAAE;QACvF,MAAM,CAAC,iBAAiB,CAAC,CAAC,EAAC,IAAI,EAAC,OAAO,EAAC,CAAC,EAAC,IAAI,EAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;QACtE,MAAM,CAAC,iBAAiB,CAAC,EAAE,EAAE,IAAI,EAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;QAC1D,MAAM,CAAC,iBAAiB,CAAC,EAAE,EAAE,SAAS,EAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IAC/D,CAAC,CAAC,CAAA;IACF,EAAE,CAAC,kFAAkF,EAAC,GAAE,EAAE;QACzF,KAAI,MAAM,IAAI,IAAI,CAAC,OAAO,EAAC,QAAQ,CAAC;YAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,EAAC,IAAI,EAAC,IAAI,EAAC,EAAC,EAAE,EAAC,IAAI,EAAC,EAAC,CAAC,EAAC,IAAI,EAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QACjH,MAAM,CAAC,iBAAiB,CAAC,CAAC,EAAC,IAAI,EAAC,QAAQ,EAAC,IAAI,EAAC,EAAC,EAAE,EAAC,IAAI,EAAC,EAAC,EAAC,EAAC,IAAI,EAAC,OAAO,EAAC,IAAI,EAAC,EAAC,EAAE,EAAC,OAAO,EAAC,EAAC,CAAC,EAAC,IAAI,EAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QACrH,MAAM,CAAC,iBAAiB,CAAC,CAAC,EAAC,IAAI,EAAC,QAAQ,EAAC,IAAI,EAAC,EAAC,EAAE,EAAC,OAAO,EAAC,EAAC,CAAC,EAAC,IAAI,EAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IACxF,CAAC,CAAC,CAAA;IACF,EAAE,CAAC,mFAAmF,EAAC,GAAE,EAAE;QAC1F,MAAM,CAAC,UAAU,CAAC,EAAC,IAAI,EAAC,QAAQ,EAAC,IAAI,EAAC,EAAC,EAAE,EAAC,GAAG,EAAC,IAAI,EAAC,OAAO,EAAC,EAAC,EAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACxF,MAAM,CAAC,UAAU,CAAC,EAAC,IAAI,EAAC,QAAQ,EAAC,IAAI,EAAC,EAAC,EAAE,EAAC,GAAG,EAAC,QAAQ,EAAC,SAAS,EAAC,EAAC,EAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QAClG,MAAM,CAAC,UAAU,CAAC,EAAC,IAAI,EAAC,QAAQ,EAAC,EAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;IACtE,CAAC,CAAC,CAAA;AACH,CAAC,CAAC,CAAA","sourcesContent":["import { rosterInviteState, rosterName } from './roster-identity'\ndescribe('roster identity and invite notice',()=>{\n it('does not claim denial when participant identities are missing or still loading',()=>{\n expect(rosterInviteState([{role:'owner'}],'me',true)).toBe('checking')\n expect(rosterInviteState([], 'me',false)).toBe('checking')\n expect(rosterInviteState([], undefined,true)).toBe('checking')\n })\n it('uses the current person role rather than another member or a stale loading state',()=>{\n for(const role of ['owner','member'])expect(rosterInviteState([{role,user:{id:'me'}}],'me',true)).toBe('allowed')\n expect(rosterInviteState([{role:'viewer',user:{id:'me'}},{role:'owner',user:{id:'other'}}],'me',true)).toBe('denied')\n expect(rosterInviteState([{role:'member',user:{id:'other'}}],'me',true)).toBe('denied')\n })\n it('shows a recognizable full name, then username, then an explicit unavailable label',()=>{\n expect(rosterName({role:'member',user:{id:'u',name:' 정연희 '}},'Unavailable')).toBe('정연희')\n expect(rosterName({role:'member',user:{id:'u',username:'yeonhee'}},'Unavailable')).toBe('yeonhee')\n expect(rosterName({role:'member'},'Unavailable')).toBe('Unavailable')\n })\n})\n"]}
1
+ {"version":3,"file":"roster-identity.test.js","sourceRoot":"","sources":["../../client/components/roster-identity.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAC9C,QAAQ,CAAC,iBAAiB,EAAC,GAAE,EAAE;IAC9B,EAAE,CAAC,mFAAmF,EAAC,GAAE,EAAE;QAC1F,MAAM,CAAC,UAAU,CAAC,EAAC,IAAI,EAAC,QAAQ,EAAC,IAAI,EAAC,EAAC,EAAE,EAAC,GAAG,EAAC,IAAI,EAAC,OAAO,EAAC,EAAC,EAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACxF,MAAM,CAAC,UAAU,CAAC,EAAC,IAAI,EAAC,QAAQ,EAAC,IAAI,EAAC,EAAC,EAAE,EAAC,GAAG,EAAC,QAAQ,EAAC,SAAS,EAAC,EAAC,EAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QAClG,MAAM,CAAC,UAAU,CAAC,EAAC,IAAI,EAAC,QAAQ,EAAC,EAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;IACtE,CAAC,CAAC,CAAA;AACH,CAAC,CAAC,CAAA","sourcesContent":["import { rosterName } from './roster-identity'\ndescribe('roster identity',()=>{\n it('shows a recognizable full name, then username, then an explicit unavailable label',()=>{\n expect(rosterName({role:'member',user:{id:'u',name:' 정연희 '}},'Unavailable')).toBe('정연희')\n expect(rosterName({role:'member',user:{id:'u',username:'yeonhee'}},'Unavailable')).toBe('yeonhee')\n expect(rosterName({role:'member'},'Unavailable')).toBe('Unavailable')\n })\n})\n"]}
@@ -142,6 +142,7 @@ export function setupAssistantDock(options = {}) {
142
142
  viewpart: {
143
143
  show: false,
144
144
  persistent: true,
145
+ expansionCompanion: { labelKey: 'ai-assistant.dock.title', defaultLabel: 'Assistant' },
145
146
  level: VIEWPART_LEVEL.TOPMOST,
146
147
  template: html `${dock}`
147
148
  },
@@ -1 +1 @@
1
- {"version":3,"file":"setup-assistant-dock.js","sourceRoot":"","sources":["../client/setup-assistant-dock.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Standing the AI dock up in an application - one call in its bootstrap (design/ai/shared-dock.md §2-1).\n *\n * setupAssistantDock()\n * registerAssistantHome({ ... }) // what the app's own conversation is about\n * registerSubjectSurface('items', ...) // how a page's context.subject becomes a surface (if any)\n *\n * The dock sits in the aside bar above everything, closed at first; the header band's `forum` button opens\n * it and carries how many conversations have something unread. Asks from other screens open it too.\n */\nimport '@material/web/icon/icon.js'\nimport './components/assistant-dock.js'\n\nimport { LitElement, css, html, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { i18next } from '@operato/i18n'\nimport { appendViewpart, openOverlay, TOOL_POSITION, VIEWPART_LEVEL, VIEWPART_POSITION } from '@operato/layout'\nimport { store } from '@operato/shell'\nimport { APPEND_APP_TOOL } from '@things-factory/apptool-base/client/index.js'\n\nimport { ASSISTANT_DOCK_VIEWPART } from './components/assistant-dock.js'\nimport { startSessionActivityFeed, subscribeSessionActivity, unreadSessionCount } from './utils/assistant-activity.js'\n\n@customElement('ox-assistant-dock-button')\nexport class AssistantDockButton extends LitElement {\n /*\n * The band's pill, as its bell draws it (apptool-ui app-header-band `.ctl`): 28px, a 1px edge, a 7px radius, an 18px\n * outlined icon in the muted colour, the count inside the pill. The band shapes bare md-icon tools itself but cannot\n * reach into this element, so the button draws the same shape - a filled 24px mark at full ink stood out on the row\n * (user, 2026-09-25).\n */\n static styles = css`\n :host {\n display: inline-flex;\n }\n\n button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n height: 28px;\n min-width: 28px;\n padding: 0 8px;\n box-sizing: border-box;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 7px;\n background-color: var(--header-band-control-surface, var(--md-sys-color-surface-container-lowest, var(--md-sys-color-surface)));\n color: var(--md-sys-color-on-surface-variant);\n font: inherit;\n cursor: pointer;\n }\n\n button:hover {\n border-color: var(--md-sys-color-primary);\n color: var(--md-sys-color-primary);\n }\n\n button:focus-visible {\n outline: 2px solid var(--md-sys-color-primary);\n outline-offset: 2px;\n }\n\n md-icon {\n --md-icon-size: 18px;\n font-size: 18px;\n font-variation-settings: 'FILL' 0;\n color: inherit;\n }\n\n .count {\n min-width: 15px;\n height: 15px;\n padding: 0 4px;\n box-sizing: border-box;\n border-radius: 8px;\n background-color: var(--header-band-count-background, var(--md-sys-color-error));\n color: var(--header-band-count-color, var(--md-sys-color-on-error));\n font-size: 10px;\n font-weight: 700;\n line-height: 15px;\n text-align: center;\n }\n `\n\n @property({ type: String }) viewpart = ASSISTANT_DOCK_VIEWPART\n @state() private unread = 0\n\n private unsubscribe?: () => void\n\n connectedCallback() {\n super.connectedCallback()\n this.unread = unreadSessionCount()\n this.unsubscribe = subscribeSessionActivity(() => (this.unread = unreadSessionCount()))\n }\n\n disconnectedCallback() {\n this.unsubscribe?.()\n super.disconnectedCallback()\n }\n\n /*\n * Opens, never toggles: after a reload the viewpart can be marked shown while nothing is drawn, and a toggle\n * then closed it - the first press did nothing (found on :3700, 2026-09-25). The dock's own ✕ closes it.\n */\n render() {\n const label = i18next.t('ai-assistant.dock.open', { defaultValue: 'Ask AI' })\n return html`\n <button title=${label} aria-label=${label} @click=${() => openOverlay(this.viewpart, { backdrop: false })}>\n <md-icon aria-hidden=\"true\">forum</md-icon>\n ${this.unread ? html`<span class=\"count\">${this.unread > 99 ? '99+' : this.unread}</span>` : nothing}\n </button>\n `\n }\n}\n\nlet installed = false\n\nexport function setupAssistantDock(options: { viewpart?: string } = {}): void {\n if (installed) return\n installed = true\n const viewpart = options.viewpart ?? ASSISTANT_DOCK_VIEWPART\n\n startSessionActivityFeed()\n\n /*\n * One dock for the application's life. The aside bar drops a closed viewpart's template, so a template that\n * makes the element would make a new dock on every opening - and a new composer, the text being written gone\n * (design/ai/shared-dock.md §2-1, §2-7). The same element is put back instead, as the entity360 dock is.\n */\n const dock = document.createElement('ox-assistant-dock') as HTMLElement & { viewpart: string }\n dock.viewpart = viewpart\n\n appendViewpart({\n name: viewpart,\n /*\n * `persistent` keeps the dock on editors that bleed wide (the board modeller hides the aside bar with\n * `widebleed`) - those are the screens where the conversation authors, so the dock must still open there.\n */\n viewpart: {\n show: false,\n persistent: true,\n level: VIEWPART_LEVEL.TOPMOST,\n template: html`${dock}`\n },\n position: VIEWPART_POSITION.ASIDEBAR\n })\n\n store.dispatch({\n type: APPEND_APP_TOOL,\n tool: {\n name: 'assistant-dock-open',\n position: TOOL_POSITION.REAR,\n template: html`<ox-assistant-dock-button .viewpart=${viewpart}></ox-assistant-dock-button>`\n }\n })\n\n /* askAssistant() from any screen opens the dock. */\n window.addEventListener('assistant-dock-open', () => openOverlay(viewpart, { backdrop: false }))\n}\n"]}
1
+ {"version":3,"file":"setup-assistant-dock.js","sourceRoot":"","sources":["../client/setup-assistant-dock.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Standing the AI dock up in an application - one call in its bootstrap (design/ai/shared-dock.md §2-1).\n *\n * setupAssistantDock()\n * registerAssistantHome({ ... }) // what the app's own conversation is about\n * registerSubjectSurface('items', ...) // how a page's context.subject becomes a surface (if any)\n *\n * The dock sits in the aside bar above everything, closed at first; the header band's `forum` button opens\n * it and carries how many conversations have something unread. Asks from other screens open it too.\n */\nimport '@material/web/icon/icon.js'\nimport './components/assistant-dock.js'\n\nimport { LitElement, css, html, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { i18next } from '@operato/i18n'\nimport { appendViewpart, openOverlay, TOOL_POSITION, VIEWPART_LEVEL, VIEWPART_POSITION } from '@operato/layout'\nimport { store } from '@operato/shell'\nimport { APPEND_APP_TOOL } from '@things-factory/apptool-base/client/index.js'\n\nimport { ASSISTANT_DOCK_VIEWPART } from './components/assistant-dock.js'\nimport { startSessionActivityFeed, subscribeSessionActivity, unreadSessionCount } from './utils/assistant-activity.js'\n\n@customElement('ox-assistant-dock-button')\nexport class AssistantDockButton extends LitElement {\n /*\n * The band's pill, as its bell draws it (apptool-ui app-header-band `.ctl`): 28px, a 1px edge, a 7px radius, an 18px\n * outlined icon in the muted colour, the count inside the pill. The band shapes bare md-icon tools itself but cannot\n * reach into this element, so the button draws the same shape - a filled 24px mark at full ink stood out on the row\n * (user, 2026-09-25).\n */\n static styles = css`\n :host {\n display: inline-flex;\n }\n\n button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n height: 28px;\n min-width: 28px;\n padding: 0 8px;\n box-sizing: border-box;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 7px;\n background-color: var(--header-band-control-surface, var(--md-sys-color-surface-container-lowest, var(--md-sys-color-surface)));\n color: var(--md-sys-color-on-surface-variant);\n font: inherit;\n cursor: pointer;\n }\n\n button:hover {\n border-color: var(--md-sys-color-primary);\n color: var(--md-sys-color-primary);\n }\n\n button:focus-visible {\n outline: 2px solid var(--md-sys-color-primary);\n outline-offset: 2px;\n }\n\n md-icon {\n --md-icon-size: 18px;\n font-size: 18px;\n font-variation-settings: 'FILL' 0;\n color: inherit;\n }\n\n .count {\n min-width: 15px;\n height: 15px;\n padding: 0 4px;\n box-sizing: border-box;\n border-radius: 8px;\n background-color: var(--header-band-count-background, var(--md-sys-color-error));\n color: var(--header-band-count-color, var(--md-sys-color-on-error));\n font-size: 10px;\n font-weight: 700;\n line-height: 15px;\n text-align: center;\n }\n `\n\n @property({ type: String }) viewpart = ASSISTANT_DOCK_VIEWPART\n @state() private unread = 0\n\n private unsubscribe?: () => void\n\n connectedCallback() {\n super.connectedCallback()\n this.unread = unreadSessionCount()\n this.unsubscribe = subscribeSessionActivity(() => (this.unread = unreadSessionCount()))\n }\n\n disconnectedCallback() {\n this.unsubscribe?.()\n super.disconnectedCallback()\n }\n\n /*\n * Opens, never toggles: after a reload the viewpart can be marked shown while nothing is drawn, and a toggle\n * then closed it - the first press did nothing (found on :3700, 2026-09-25). The dock's own ✕ closes it.\n */\n render() {\n const label = i18next.t('ai-assistant.dock.open', { defaultValue: 'Ask AI' })\n return html`\n <button title=${label} aria-label=${label} @click=${() => openOverlay(this.viewpart, { backdrop: false })}>\n <md-icon aria-hidden=\"true\">forum</md-icon>\n ${this.unread ? html`<span class=\"count\">${this.unread > 99 ? '99+' : this.unread}</span>` : nothing}\n </button>\n `\n }\n}\n\nlet installed = false\n\nexport function setupAssistantDock(options: { viewpart?: string } = {}): void {\n if (installed) return\n installed = true\n const viewpart = options.viewpart ?? ASSISTANT_DOCK_VIEWPART\n\n startSessionActivityFeed()\n\n /*\n * One dock for the application's life. The aside bar drops a closed viewpart's template, so a template that\n * makes the element would make a new dock on every opening - and a new composer, the text being written gone\n * (design/ai/shared-dock.md §2-1, §2-7). The same element is put back instead, as the entity360 dock is.\n */\n const dock = document.createElement('ox-assistant-dock') as HTMLElement & { viewpart: string }\n dock.viewpart = viewpart\n\n appendViewpart({\n name: viewpart,\n /*\n * `persistent` keeps the dock on editors that bleed wide (the board modeller hides the aside bar with\n * `widebleed`) - those are the screens where the conversation authors, so the dock must still open there.\n */\n viewpart: {\n show: false,\n persistent: true,\n expansionCompanion: { labelKey: 'ai-assistant.dock.title', defaultLabel: 'Assistant' },\n level: VIEWPART_LEVEL.TOPMOST,\n template: html`${dock}`\n },\n position: VIEWPART_POSITION.ASIDEBAR\n })\n\n store.dispatch({\n type: APPEND_APP_TOOL,\n tool: {\n name: 'assistant-dock-open',\n position: TOOL_POSITION.REAR,\n template: html`<ox-assistant-dock-button .viewpart=${viewpart}></ox-assistant-dock-button>`\n }\n })\n\n /* askAssistant() from any screen opens the dock. */\n window.addEventListener('assistant-dock-open', () => openOverlay(viewpart, { backdrop: false }))\n}\n"]}