@things-factory/ai-assistant 10.1.154 → 10.1.155

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 (121) hide show
  1. package/client/bootstrap.ts +27 -0
  2. package/client/components/assistant-chat.ts +229 -178
  3. package/client/components/assistant-dock.ts +53 -48
  4. package/client/components/assistant-session-manager.ts +82 -100
  5. package/client/components/assistant-session-toolbar.ts +11 -3
  6. package/client/components/chat-input-builder.ts +8 -2
  7. package/client/settings/agent-settings-model.ts +164 -0
  8. package/client/settings/ai-agent-setting-let.ts +684 -0
  9. package/client/settings/ai-usage-setting-let.ts +252 -0
  10. package/client/settings/usage-model.ts +46 -0
  11. package/client/setup-assistant-dock.ts +2 -3
  12. package/client/utils/agent-choice.ts +56 -0
  13. package/client/utils/assistant-navigation.test.ts +14 -8
  14. package/client/utils/assistant-navigation.ts +8 -3
  15. package/client/utils/composer-frame.ts +28 -2
  16. package/client/utils/menu-room.ts +33 -0
  17. package/dist-client/bootstrap.d.ts +8 -0
  18. package/dist-client/bootstrap.js +26 -0
  19. package/dist-client/bootstrap.js.map +1 -0
  20. package/dist-client/components/assistant-chat.d.ts +32 -4
  21. package/dist-client/components/assistant-chat.js +234 -175
  22. package/dist-client/components/assistant-chat.js.map +1 -1
  23. package/dist-client/components/assistant-dock.d.ts +6 -8
  24. package/dist-client/components/assistant-dock.js +52 -60
  25. package/dist-client/components/assistant-dock.js.map +1 -1
  26. package/dist-client/components/assistant-session-manager.d.ts +8 -7
  27. package/dist-client/components/assistant-session-manager.js +84 -124
  28. package/dist-client/components/assistant-session-manager.js.map +1 -1
  29. package/dist-client/components/assistant-session-toolbar.d.ts +1 -0
  30. package/dist-client/components/assistant-session-toolbar.js +10 -3
  31. package/dist-client/components/assistant-session-toolbar.js.map +1 -1
  32. package/dist-client/components/chat-input-builder.d.ts +4 -0
  33. package/dist-client/components/chat-input-builder.js +4 -2
  34. package/dist-client/components/chat-input-builder.js.map +1 -1
  35. package/dist-client/settings/agent-settings-model.d.ts +93 -0
  36. package/dist-client/settings/agent-settings-model.js +95 -0
  37. package/dist-client/settings/agent-settings-model.js.map +1 -0
  38. package/dist-client/settings/ai-agent-setting-let.d.ts +51 -0
  39. package/dist-client/settings/ai-agent-setting-let.js +672 -0
  40. package/dist-client/settings/ai-agent-setting-let.js.map +1 -0
  41. package/dist-client/settings/ai-usage-setting-let.d.ts +26 -0
  42. package/dist-client/settings/ai-usage-setting-let.js +276 -0
  43. package/dist-client/settings/ai-usage-setting-let.js.map +1 -0
  44. package/dist-client/settings/usage-model.d.ts +32 -0
  45. package/dist-client/settings/usage-model.js +25 -0
  46. package/dist-client/settings/usage-model.js.map +1 -0
  47. package/dist-client/setup-assistant-dock.d.ts +0 -1
  48. package/dist-client/setup-assistant-dock.js +0 -1
  49. package/dist-client/setup-assistant-dock.js.map +1 -1
  50. package/dist-client/tsconfig.tsbuildinfo +1 -1
  51. package/dist-client/utils/agent-choice.d.ts +24 -0
  52. package/dist-client/utils/agent-choice.js +38 -0
  53. package/dist-client/utils/agent-choice.js.map +1 -0
  54. package/dist-client/utils/assistant-navigation.d.ts +8 -3
  55. package/dist-client/utils/assistant-navigation.js +7 -2
  56. package/dist-client/utils/assistant-navigation.js.map +1 -1
  57. package/dist-client/utils/assistant-navigation.test.js +14 -8
  58. package/dist-client/utils/assistant-navigation.test.js.map +1 -1
  59. package/dist-client/utils/composer-frame.d.ts +23 -2
  60. package/dist-client/utils/composer-frame.js +20 -2
  61. package/dist-client/utils/composer-frame.js.map +1 -1
  62. package/dist-client/utils/menu-room.d.ts +13 -0
  63. package/dist-client/utils/menu-room.js +26 -0
  64. package/dist-client/utils/menu-room.js.map +1 -0
  65. package/dist-server/service/ai-agent-resolver.d.ts +82 -0
  66. package/dist-server/service/ai-agent-resolver.js +417 -0
  67. package/dist-server/service/ai-agent-resolver.js.map +1 -0
  68. package/dist-server/service/assistant-capability-resolver.d.ts +1 -1
  69. package/dist-server/service/assistant-capability-resolver.js +7 -7
  70. package/dist-server/service/assistant-capability-resolver.js.map +1 -1
  71. package/dist-server/service/assistant-chat-resolver.d.ts +2 -0
  72. package/dist-server/service/assistant-chat-resolver.js +32 -15
  73. package/dist-server/service/assistant-chat-resolver.js.map +1 -1
  74. package/dist-server/service/assistant-mode/resolve.d.ts +0 -1
  75. package/dist-server/service/assistant-mode/resolve.js +0 -1
  76. package/dist-server/service/assistant-mode/resolve.js.map +1 -1
  77. package/dist-server/service/assistant-mode/types.d.ts +1 -2
  78. package/dist-server/service/assistant-mode/types.js.map +1 -1
  79. package/dist-server/service/chat-session/chat-session.d.ts +2 -0
  80. package/dist-server/service/chat-session/chat-session.js +5 -0
  81. package/dist-server/service/chat-session/chat-session.js.map +1 -1
  82. package/dist-server/service/index.d.ts +2 -1
  83. package/dist-server/service/index.js +3 -0
  84. package/dist-server/service/index.js.map +1 -1
  85. package/dist-server/service/testing/mode-conformance.js +0 -1
  86. package/dist-server/service/testing/mode-conformance.js.map +1 -1
  87. package/dist-server/tsconfig.tsbuildinfo +1 -1
  88. package/package.json +9 -7
  89. package/server/service/ai-agent-resolver.ts +330 -0
  90. package/server/service/assistant-capability-resolver.ts +7 -7
  91. package/server/service/assistant-chat-resolver.ts +32 -24
  92. package/server/service/assistant-mode/resolve.test.ts +0 -2
  93. package/server/service/assistant-mode/resolve.ts +0 -2
  94. package/server/service/assistant-mode/types.ts +1 -2
  95. package/server/service/chat-session/chat-session.ts +5 -0
  96. package/server/service/index.ts +3 -0
  97. package/server/service/testing/mode-conformance.ts +0 -1
  98. package/server/tool-registry-boot-report.test.ts +2 -0
  99. package/test/ai-agent-settings.test.ts +123 -0
  100. package/test/assistant-dock.test.ts +3 -2
  101. package/test/assistant-session-manager.test.ts +10 -4
  102. package/test/assistant-session-toolbar.test.ts +8 -0
  103. package/test/composer-frame.test.ts +40 -2
  104. package/test/menu-room.test.ts +33 -0
  105. package/test/open-doors.test.ts +3 -1
  106. package/test/session-discovery.test.ts +7 -6
  107. package/things-factory.config.js +2 -0
  108. package/translations/en.json +78 -12
  109. package/translations/es.json +78 -12
  110. package/translations/ja.json +78 -12
  111. package/translations/ko.json +78 -12
  112. package/translations/ms.json +78 -12
  113. package/translations/zh.json +78 -12
  114. package/client/components/chat-triggers.test.ts +0 -72
  115. package/client/components/chat-triggers.ts +0 -62
  116. package/dist-client/components/chat-triggers.d.ts +0 -16
  117. package/dist-client/components/chat-triggers.js +0 -42
  118. package/dist-client/components/chat-triggers.js.map +0 -1
  119. package/dist-client/components/chat-triggers.test.d.ts +0 -1
  120. package/dist-client/components/chat-triggers.test.js +0 -61
  121. package/dist-client/components/chat-triggers.test.js.map +0 -1
@@ -1,5 +1,6 @@
1
1
  import '@material/web/icon/icon.js';
2
2
  import { LitElement, type PropertyValues, type TemplateResult } from 'lit';
3
+ import '@operato/popup/ox-popup-menu.js';
3
4
  import { type AssistantSessionAdmin } from '../utils/assistant-session-admin.js';
4
5
  /**
5
6
  * The conversations on one anchor, managed - `<ox-assistant-session-manager>`.
@@ -32,18 +33,14 @@ export declare class AssistantSessionManager extends LitElement {
32
33
  anchorId: string;
33
34
  /** The conversation the dock has open - marked in the list. */
34
35
  sessionId: string;
35
- scopeLabel: string;
36
36
  /** The doors. Replaceable for tests; a host normally leaves it. */
37
37
  admin: AssistantSessionAdmin;
38
38
  /** The conversation kind (mode) open in the dock - the list's default scope (§2-6). */
39
39
  kind: string;
40
- /** kind → the words people know it by (the modes' labels), for each row. */
41
- kindLabels: Record<string, string>;
42
40
  private entries;
43
41
  private total;
44
42
  private hiddenCount;
45
43
  private search;
46
- private allKinds;
47
44
  private whose;
48
45
  private visibility;
49
46
  private includeHidden;
@@ -52,9 +49,8 @@ export declare class AssistantSessionManager extends LitElement {
52
49
  private loading;
53
50
  private error;
54
51
  private renamingId;
55
- private filtersOpen;
56
- private get hasFilters();
57
- private resetFilters;
52
+ private get filtering();
53
+ private clearFilters;
58
54
  private generation;
59
55
  private searchTimer?;
60
56
  /** Read the list again - the host calls this after it opened or started a conversation. */
@@ -74,6 +70,11 @@ export declare class AssistantSessionManager extends LitElement {
74
70
  private _removeSession;
75
71
  render(): TemplateResult;
76
72
  private renderRow;
73
+ /**
74
+ * A row's actions in the platform's popup menu - one open at a time, closed by a press outside or Escape, placed
75
+ * inside the window. The hand-made menu it replaces stayed open and stacked over the next rows.
76
+ */
77
+ private openRowMenu;
77
78
  /** Before what cannot be taken back - the platform's confirm, [Cancel][OK] (decided 2026-09-25, §7-3). */
78
79
  private ask;
79
80
  private askRemove;
@@ -4,6 +4,8 @@ import { css, html, LitElement, nothing } from 'lit';
4
4
  import { customElement, property, state } from 'lit/decorators.js';
5
5
  import { i18next } from '@operato/i18n';
6
6
  import { OxPrompt } from '@operato/popup';
7
+ import '@operato/popup/ox-popup-menu.js';
8
+ import { OxPopupMenu } from '@operato/popup/ox-popup-menu.js';
7
9
  import { assistantSessionAdmin, sessionActions } from '../utils/assistant-session-admin.js';
8
10
  const PAGE = 30;
9
11
  const t = (key, defaultValue, options = {}) => i18next.t(`ai-assistant.session.${key}`, { defaultValue, ...options });
@@ -39,18 +41,14 @@ let AssistantSessionManager = class AssistantSessionManager extends LitElement {
39
41
  this.anchorId = '';
40
42
  /** The conversation the dock has open - marked in the list. */
41
43
  this.sessionId = '';
42
- this.scopeLabel = '';
43
44
  /** The doors. Replaceable for tests; a host normally leaves it. */
44
45
  this.admin = assistantSessionAdmin;
45
46
  /** The conversation kind (mode) open in the dock - the list's default scope (§2-6). */
46
47
  this.kind = '';
47
- /** kind → the words people know it by (the modes' labels), for each row. */
48
- this.kindLabels = {};
49
48
  this.entries = [];
50
49
  this.total = 0;
51
50
  this.hiddenCount = 0;
52
51
  this.search = '';
53
- this.allKinds = false;
54
52
  this.whose = 'all';
55
53
  this.visibility = 'all';
56
54
  this.includeHidden = false;
@@ -59,33 +57,28 @@ let AssistantSessionManager = class AssistantSessionManager extends LitElement {
59
57
  this.loading = false;
60
58
  this.error = '';
61
59
  this.renamingId = '';
62
- this.filtersOpen = false;
63
60
  this.generation = 0;
64
61
  }
65
62
  static { this.styles = css `
66
- .scope-label { padding: 12px 12px 0; font-size: 14px; font-weight: 600; }
67
- .filter-toggle, .reset { margin: 6px 10px; padding: 7px; font: inherit; background: transparent; color: inherit; border: 1px solid var(--md-sys-color-outline-variant); border-radius: 6px; cursor: pointer; }
68
- .filter-toggle { align-self: flex-end; }
69
- .filters[hidden] { display: none; }
70
- .open-row { display: block; flex: 1; min-width: 0; text-align: start; border: 0; background: transparent; color: inherit; font: inherit; padding: 4px; cursor: pointer; }
71
- .meta { display: block; margin-top: 5px; font-size: 12px; color: var(--md-sys-color-on-surface-variant); }
63
+ /* One scale for the list: name 13, preview 12, meta 11 - the rest of the dock's sizes. */
64
+ .open-row { display: block; flex: 1; min-width: 0; text-align: start; border: 0; background: transparent; color: inherit; font: inherit; padding: 0; cursor: pointer; }
65
+ .meta { display: block; margin-top: 2px; font-size: 11px; color: var(--md-sys-color-on-surface-variant); }
72
66
  /* Open and private must part at a glance - an open row is read by everyone in the domain (2026-09-30). */
73
67
  .audience { display: inline-flex; align-items: center; gap: 3px; vertical-align: middle; }
74
68
  .audience md-icon { --md-icon-size: 14px; }
75
69
  /* The dock's warning colour, fixed across applications - see assistant-dock.ts. */
76
70
  :host { --assistant-open-edge: #c47f00; --assistant-open-surface: #f2b01e; --assistant-open-ink: #2e2100; }
77
- .audience.open { padding: 1px 6px 1px 4px; border-radius: 6px; background: var(--assistant-open-surface); color: var(--assistant-open-ink); font-weight: 600; }
71
+ .audience.open { padding: 0 5px 0 3px; border-radius: 4px; background: var(--assistant-open-surface); color: var(--assistant-open-ink); font-weight: 600; }
72
+ .audience md-icon { --md-icon-size: 12px; }
78
73
  .row.open-audience { box-shadow: inset 4px 0 0 var(--assistant-open-edge); }
79
- .actions summary { cursor: pointer; padding: 8px; font-size: 18px; list-style: none; }
80
- .actions[open] .acts { display: flex; flex-direction: column; position: absolute; right: 10px; top: 40px; z-index: 2; min-width: 180px; padding: 6px; background: var(--md-sys-color-surface-container-lowest); border: 1px solid var(--md-sys-color-outline-variant); border-radius: 8px; box-shadow: 0 4px 16px #0002; }
81
- .actions button.icon { min-height: 40px; padding: 8px 12px; font: inherit; text-align: start; }
82
- .actions button.danger { color: var(--md-sys-color-error, #b42318); }
83
- button:focus-visible, summary:focus-visible { outline: 2px solid var(--md-sys-color-primary); outline-offset: 2px; }
74
+ .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; }
75
+ .more-menu:hover { background: var(--md-sys-color-surface-container-high); color: var(--md-sys-color-on-surface); }
76
+ button:focus-visible { outline: 2px solid var(--md-sys-color-primary); outline-offset: 2px; }
84
77
  :host {
85
78
  display: flex;
86
79
  flex-direction: column;
87
80
  min-height: 0;
88
- font-size: 14px;
81
+ font-size: 13px;
89
82
  color: var(--md-sys-color-on-surface);
90
83
  }
91
84
 
@@ -93,14 +86,14 @@ let AssistantSessionManager = class AssistantSessionManager extends LitElement {
93
86
  display: flex;
94
87
  align-items: center;
95
88
  gap: 6px;
96
- padding: 8px 10px;
97
- border-bottom: 1px solid var(--md-sys-color-outline-variant);
89
+ padding: 8px 10px 6px;
98
90
  }
99
91
 
100
92
  .bar input,
101
93
  .bar select {
102
94
  min-width: 0;
103
- height: 28px;
95
+ height: 26px;
96
+ font-size: 12px;
104
97
  box-sizing: border-box;
105
98
  border: 1px solid var(--md-sys-color-outline-variant);
106
99
  border-radius: 6px;
@@ -122,28 +115,32 @@ let AssistantSessionManager = class AssistantSessionManager extends LitElement {
122
115
  background: transparent;
123
116
  color: var(--md-sys-color-primary);
124
117
  font: inherit;
125
- font-weight: 650;
118
+ font-size: 12px;
119
+ font-weight: 600;
120
+ white-space: nowrap;
126
121
  cursor: pointer;
127
122
  }
128
123
 
124
+ /* Always open, one quiet line under the search - the same small size as the search. */
129
125
  .filters {
130
126
  display: flex;
131
127
  flex-wrap: wrap;
132
128
  align-items: center;
133
- gap: 6px 10px;
134
- padding: 6px 10px;
129
+ gap: 4px 10px;
130
+ padding: 0 10px 8px;
135
131
  border-bottom: 1px solid var(--md-sys-color-outline-variant);
136
- font-size: 11.5px;
132
+ font-size: 11px;
137
133
  color: var(--md-sys-color-on-surface-variant);
138
134
  }
139
135
 
140
136
  .filters select {
141
- height: 26px;
137
+ height: 22px;
142
138
  border: 1px solid var(--md-sys-color-outline-variant);
143
- border-radius: 6px;
139
+ border-radius: 4px;
144
140
  background: var(--md-sys-color-surface-container-lowest);
145
141
  color: inherit;
146
142
  font: inherit;
143
+ padding: 0 4px;
147
144
  }
148
145
 
149
146
  .filters label {
@@ -153,8 +150,18 @@ let AssistantSessionManager = class AssistantSessionManager extends LitElement {
153
150
  cursor: pointer;
154
151
  }
155
152
 
156
- .kind {
153
+ .filters input[type='checkbox'] {
154
+ margin: 0;
155
+ }
156
+
157
+ .filters .clear {
158
+ margin-left: auto;
159
+ border: 0;
160
+ background: none;
161
+ padding: 0;
157
162
  color: var(--md-sys-color-primary);
163
+ font: inherit;
164
+ cursor: pointer;
158
165
  }
159
166
 
160
167
  .list {
@@ -168,7 +175,7 @@ let AssistantSessionManager = class AssistantSessionManager extends LitElement {
168
175
  display: flex;
169
176
  align-items: center;
170
177
  gap: 8px;
171
- padding: 9px 10px;
178
+ padding: 7px 10px;
172
179
  border-bottom: 1px solid var(--md-sys-color-outline-variant);
173
180
  cursor: pointer;
174
181
  }
@@ -216,33 +223,9 @@ let AssistantSessionManager = class AssistantSessionManager extends LitElement {
216
223
  }
217
224
 
218
225
  .prev {
226
+ margin-top: 1px;
219
227
  color: var(--md-sys-color-on-surface-variant);
220
- font-size: 11px;
221
- }
222
-
223
- /* Row actions show on hover and on keyboard focus - the list's body is the names. */
224
- .acts {
225
- display: none;
226
- gap: 2px;
227
- }
228
-
229
- button.icon {
230
- display: inline-flex;
231
- padding: 3px;
232
- border: 0;
233
- border-radius: 6px;
234
- background: none;
235
- color: var(--md-sys-color-on-surface-variant);
236
- cursor: pointer;
237
- }
238
-
239
- button.icon:hover {
240
- background: var(--md-sys-color-surface-container-high);
241
- color: var(--md-sys-color-on-surface);
242
- }
243
-
244
- button.icon.danger:hover {
245
- color: var(--md-sys-color-error);
228
+ font-size: 12px;
246
229
  }
247
230
 
248
231
  .rename {
@@ -323,14 +306,18 @@ let AssistantSessionManager = class AssistantSessionManager extends LitElement {
323
306
  --md-icon-size: 16px;
324
307
  }
325
308
  `; }
326
- get hasFilters() {
327
- return !!this.search.trim() || this.allKinds || this.whose !== 'all' || this.visibility !== 'all' || this.includeHidden || this.unreadOnly;
328
- }
329
- resetFilters() {
309
+ /*
310
+ * The search and the filters stand open together, always (2026-10-12) - a filter behind a "Filters" button is one
311
+ * more thing to find before finding the conversation. The list is this screen's conversations, so there is no kind
312
+ * to choose.
313
+ */
314
+ get filtering() {
315
+ return !!this.search.trim() || this.whose !== 'all' || this.visibility !== 'all' || this.includeHidden || this.unreadOnly;
316
+ }
317
+ clearFilters() {
330
318
  clearTimeout(this.searchTimer);
331
319
  this.refilter(() => {
332
320
  this.search = '';
333
- this.allKinds = false;
334
321
  this.whose = 'all';
335
322
  this.visibility = 'all';
336
323
  this.includeHidden = this.unreadOnly = false;
@@ -356,7 +343,7 @@ let AssistantSessionManager = class AssistantSessionManager extends LitElement {
356
343
  anchorId: this.anchorId,
357
344
  search: this.search,
358
345
  filters: {
359
- kind: this.allKinds ? '' : this.kind,
346
+ kind: this.kind,
360
347
  whose: this.whose,
361
348
  visibility: this.visibility,
362
349
  includeHidden: this.includeHidden,
@@ -462,7 +449,6 @@ let AssistantSessionManager = class AssistantSessionManager extends LitElement {
462
449
  render() {
463
450
  const shown = this.entries.length;
464
451
  return html `
465
- <div class="scope-label">${this.scopeLabel}</div>
466
452
  <div class="bar">
467
453
  <input
468
454
  .value=${this.search}
@@ -476,15 +462,7 @@ let AssistantSessionManager = class AssistantSessionManager extends LitElement {
476
462
  />
477
463
  <button class="new" @click=${() => this.emit('assistant-session-reset')}>+ ${t('new', 'New conversation')}</button>
478
464
  </div>
479
- <button class="filter-toggle" aria-expanded=${this.filtersOpen} @click=${() => (this.filtersOpen = !this.filtersOpen)}>${t('filters', 'Filters')}${this.hasFilters ? ' •' : ''}</button>
480
- <div class="filters" ?hidden=${!this.filtersOpen}>
481
- ${this.kind
482
- ? html `<select aria-label=${t('filter-kind', 'Which kind')} .value=${this.allKinds ? 'all' : 'this'}
483
- @change=${(e) => this.refilter(() => (this.allKinds = e.target.value === 'all'))}>
484
- <option value="this">${this.kindLabels[this.kind] || t('filter-this-kind', 'This conversation')}</option>
485
- <option value="all">${t('filter-all-kinds', 'All kinds here')}</option>
486
- </select>`
487
- : nothing}
465
+ <div class="filters">
488
466
  <select aria-label=${t('filter-whose', 'Whose')} .value=${this.whose}
489
467
  @change=${(e) => this.refilter(() => (this.whose = e.target.value))}>
490
468
  <option value="all">${t('filter-all', 'All')}</option>
@@ -501,13 +479,13 @@ let AssistantSessionManager = class AssistantSessionManager extends LitElement {
501
479
  @change=${(e) => this.refilter(() => (this.unreadOnly = e.target.checked))} />${t('filter-unread', 'Unread')}</label>
502
480
  <label><input type="checkbox" .checked=${this.includeHidden}
503
481
  @change=${(e) => this.refilter(() => (this.includeHidden = e.target.checked))} />${t('filter-hidden', 'Including hidden')}</label>
482
+ ${this.filtering ? html `<button class="clear" @click=${() => this.clearFilters()}>${t('reset-filters', 'Clear search and filters')}</button>` : nothing}
504
483
  </div>
505
- ${this.hasFilters ? html `<button class="reset" @click=${() => this.resetFilters()}>${t('reset-filters', 'Clear search and filters')}</button>` : nothing}
506
484
  <div class="list">
507
485
  ${this.error ? html `<div class="note" role="alert">${this.error}</div>` : nothing}
508
486
  ${this.loading ? html `<div class="note" role="status">${t('loading', 'Loading conversations…')}</div>` : nothing}
509
487
  ${shown === 0 && !this.loading && !this.error
510
- ? html `<div class="empty" role="status">${this.hasFilters ? t('filtered-empty', 'No conversations match these filters here.') : t('scope-empty', 'No conversations for this subject yet.')}</div>`
488
+ ? html `<div class="empty" role="status">${this.filtering ? t('filtered-empty', 'No conversations match these filters here.') : t('scope-empty', 'No conversations yet.')}</div>`
511
489
  : this.entries.map(entry => this.renderRow(entry))}
512
490
  ${shown < this.total
513
491
  ? html `<button class="more" @click=${() => this.loadMore()}>${t('load-more', 'Show more ({rest})', { rest: this.total - shown })}</button>`
@@ -550,45 +528,43 @@ let AssistantSessionManager = class AssistantSessionManager extends LitElement {
550
528
  <span class="dot"></span>
551
529
  <button class="open-row" aria-current=${entry.id === this.sessionId ? 'true' : 'false'} @click=${() => this.emit('assistant-session-select', { sessionId: entry.id, kind: entry.kind })}>
552
530
  <div class="title">${name}</div>
553
- <div class="prev">${this.allKinds && entry.kind ? html `<span class="kind">${this.kindLabels[entry.kind] || entry.kind}</span> · ` : nothing}${entry.lastMessagePreview || ''}</div>
531
+ <div class="prev">${entry.lastMessagePreview || ''}</div>
554
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>
555
533
  </button>
556
- <details class="actions" @keydown=${(e) => { if (e.key === 'Escape') {
557
- e.stopPropagation();
558
- const details = e.currentTarget;
559
- details.open = false;
560
- details.querySelector('summary')?.focus();
561
- } }}>
562
- <summary aria-label=${t('manage', 'Manage conversation')}>⋯</summary>
563
- <span class="acts" @click=${(e) => e.stopPropagation()}>
564
- ${actions.rename
565
- ? html `<button class="icon" title=${t('rename', 'Rename')} aria-label=${t('rename', 'Rename')} @click=${() => (this.renamingId = entry.id)}>
566
- ${t('rename', 'Rename')}
567
- </button>`
568
- : nothing}
569
- ${actions.setVisibility
570
- ? isPrivate
571
- ? html `<button class="icon" title=${t('make-open', 'Make public')} aria-label=${t('make-open', 'Make public')}
572
- @click=${() => this.askOpen(entry, name)}>${t('make-open', 'Make public')}</button>`
573
- : html `<button class="icon" title=${t('make-private', 'Make private')} aria-label=${t('make-private', 'Make private')}
574
- @click=${() => this.setVisibility(entry, 'private')}>${t('make-private', 'Make private')}</button>`
575
- : nothing}
576
- <button
577
- class="icon"
578
- title=${actions.unhide ? t('unhide', 'Show in my list') : t('hide', 'Hide from my list')}
579
- aria-label=${actions.unhide ? t('unhide', 'Show in my list') : t('hide', 'Hide from my list')}
580
- @click=${() => this.setHidden(entry, !entry.hidden)}
581
- >
582
- ${actions.unhide ? t('unhide', 'Show in my list') : t('hide', 'Hide from my list')}
583
- </button>
584
- ${actions.remove
585
- ? html `<button class="icon danger" title=${t('remove', 'Delete')} aria-label=${t('remove', 'Delete')}
586
- @click=${() => this.askRemove(entry, name)}>${t('remove', 'Delete')}</button>`
587
- : nothing}
588
- </span>
589
- </details>
534
+ <button
535
+ class="more-menu"
536
+ aria-label=${t('manage', 'Manage conversation')}
537
+ title=${t('manage', 'Manage conversation')}
538
+ @click=${(e) => this.openRowMenu(e, entry, name)}>⋯</button>
590
539
  </div>`;
591
540
  }
541
+ /**
542
+ * A row's actions in the platform's popup menu - one open at a time, closed by a press outside or Escape, placed
543
+ * inside the window. The hand-made menu it replaces stayed open and stacked over the next rows.
544
+ */
545
+ openRowMenu(event, entry, name) {
546
+ event.stopPropagation();
547
+ const actions = sessionActions(entry);
548
+ const isPrivate = entry.visibility === 'private';
549
+ const at = event.currentTarget.getBoundingClientRect();
550
+ OxPopupMenu.open({
551
+ template: html `
552
+ ${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>`
557
+ : nothing}
558
+ <ox-popup-menuitem
559
+ label=${actions.unhide ? t('unhide', 'Show in my list') : t('hide', 'Hide from my list')}
560
+ @select=${() => this.setHidden(entry, !entry.hidden)}></ox-popup-menuitem>
561
+ ${actions.remove ? html `<ox-popup-menuitem label=${t('remove', 'Delete')} @select=${() => this.askRemove(entry, name)}></ox-popup-menuitem>` : nothing}
562
+ `,
563
+ top: at.bottom,
564
+ right: window.innerWidth - at.right,
565
+ parent: document.body
566
+ });
567
+ }
592
568
  /** Before what cannot be taken back - the platform's confirm, [Cancel][OK] (decided 2026-09-25, §7-3). */
593
569
  async ask(title, text, confirmText) {
594
570
  return !!(await OxPrompt.open({
@@ -622,10 +598,6 @@ __decorate([
622
598
  property({ type: String }),
623
599
  __metadata("design:type", Object)
624
600
  ], AssistantSessionManager.prototype, "sessionId", void 0);
625
- __decorate([
626
- property({ type: String }),
627
- __metadata("design:type", Object)
628
- ], AssistantSessionManager.prototype, "scopeLabel", void 0);
629
601
  __decorate([
630
602
  property({ attribute: false }),
631
603
  __metadata("design:type", Object)
@@ -634,10 +606,6 @@ __decorate([
634
606
  property({ type: String }),
635
607
  __metadata("design:type", Object)
636
608
  ], AssistantSessionManager.prototype, "kind", void 0);
637
- __decorate([
638
- property({ attribute: false }),
639
- __metadata("design:type", Object)
640
- ], AssistantSessionManager.prototype, "kindLabels", void 0);
641
609
  __decorate([
642
610
  state(),
643
611
  __metadata("design:type", Array)
@@ -654,10 +622,6 @@ __decorate([
654
622
  state(),
655
623
  __metadata("design:type", Object)
656
624
  ], AssistantSessionManager.prototype, "search", void 0);
657
- __decorate([
658
- state(),
659
- __metadata("design:type", Object)
660
- ], AssistantSessionManager.prototype, "allKinds", void 0);
661
625
  __decorate([
662
626
  state(),
663
627
  __metadata("design:type", Object)
@@ -690,10 +654,6 @@ __decorate([
690
654
  state(),
691
655
  __metadata("design:type", Object)
692
656
  ], AssistantSessionManager.prototype, "renamingId", void 0);
693
- __decorate([
694
- state(),
695
- __metadata("design:type", Object)
696
- ], AssistantSessionManager.prototype, "filtersOpen", void 0);
697
657
  AssistantSessionManager = __decorate([
698
658
  customElement('ox-assistant-session-manager')
699
659
  ], AssistantSessionManager);