@design.estate/dees-catalog 3.93.0 → 3.95.0

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 (63) hide show
  1. package/dist_bundle/bundle.js +2868 -1376
  2. package/dist_ts_web/00_commitinfo_data.js +1 -1
  3. package/dist_ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.d.ts +6 -3
  4. package/dist_ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.demo.js +34 -4
  5. package/dist_ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.js +49 -45
  6. package/dist_ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.d.ts +13 -0
  7. package/dist_ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.demo.js +5 -2
  8. package/dist_ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.js +118 -3
  9. package/dist_ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.d.ts +25 -3
  10. package/dist_ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.js +156 -48
  11. package/dist_ts_web/elements/00group-harness/dees-harness-permission-card/dees-harness-permission-card.d.ts +2 -0
  12. package/dist_ts_web/elements/00group-harness/dees-harness-permission-card/dees-harness-permission-card.js +9 -5
  13. package/dist_ts_web/elements/00group-harness/dees-harness-question-card/dees-harness-question-card.d.ts +31 -0
  14. package/dist_ts_web/elements/00group-harness/dees-harness-question-card/dees-harness-question-card.demo.d.ts +1 -0
  15. package/dist_ts_web/elements/00group-harness/dees-harness-question-card/dees-harness-question-card.demo.js +69 -0
  16. package/dist_ts_web/elements/00group-harness/dees-harness-question-card/dees-harness-question-card.js +382 -0
  17. package/dist_ts_web/elements/00group-harness/dees-harness-question-card/index.d.ts +1 -0
  18. package/dist_ts_web/elements/00group-harness/dees-harness-question-card/index.js +2 -0
  19. package/dist_ts_web/elements/00group-harness/index.d.ts +1 -0
  20. package/dist_ts_web/elements/00group-harness/index.js +2 -1
  21. package/dist_ts_web/elements/00group-harness/interfaces.d.ts +33 -0
  22. package/dist_ts_web/elements/00group-harness/toolregistry.js +9 -1
  23. package/dist_ts_web/elements/00group-layout/dees-mosaic/dees-mosaic.d.ts +49 -0
  24. package/dist_ts_web/elements/00group-layout/dees-mosaic/dees-mosaic.demo.d.ts +1 -0
  25. package/dist_ts_web/elements/00group-layout/dees-mosaic/dees-mosaic.demo.js +82 -0
  26. package/dist_ts_web/elements/00group-layout/dees-mosaic/dees-mosaic.js +622 -0
  27. package/dist_ts_web/elements/00group-layout/dees-mosaic/index.d.ts +3 -0
  28. package/dist_ts_web/elements/00group-layout/dees-mosaic/index.js +4 -0
  29. package/dist_ts_web/elements/00group-layout/dees-mosaic/interaction.d.ts +37 -0
  30. package/dist_ts_web/elements/00group-layout/dees-mosaic/interaction.js +174 -0
  31. package/dist_ts_web/elements/00group-layout/dees-mosaic/tree.d.ts +33 -0
  32. package/dist_ts_web/elements/00group-layout/dees-mosaic/tree.js +199 -0
  33. package/dist_ts_web/elements/00group-layout/dees-mosaic/types.d.ts +52 -0
  34. package/dist_ts_web/elements/00group-layout/dees-mosaic/types.js +8 -0
  35. package/dist_ts_web/elements/00group-layout/index.d.ts +1 -0
  36. package/dist_ts_web/elements/00group-layout/index.js +2 -1
  37. package/dist_ts_web/elements/00theme.js +6 -1
  38. package/dist_watch/bundle.js +2869 -1377
  39. package/dist_watch/bundle.js.map +4 -4
  40. package/package.json +1 -1
  41. package/readme.md +41 -6
  42. package/readme.playbook.md +1 -1
  43. package/ts_web/00_commitinfo_data.ts +1 -1
  44. package/ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.demo.ts +37 -3
  45. package/ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.ts +31 -44
  46. package/ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.demo.ts +4 -1
  47. package/ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.ts +99 -0
  48. package/ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.ts +148 -46
  49. package/ts_web/elements/00group-harness/dees-harness-permission-card/dees-harness-permission-card.ts +9 -4
  50. package/ts_web/elements/00group-harness/dees-harness-question-card/dees-harness-question-card.demo.ts +70 -0
  51. package/ts_web/elements/00group-harness/dees-harness-question-card/dees-harness-question-card.ts +319 -0
  52. package/ts_web/elements/00group-harness/dees-harness-question-card/index.ts +1 -0
  53. package/ts_web/elements/00group-harness/index.ts +1 -0
  54. package/ts_web/elements/00group-harness/interfaces.ts +36 -0
  55. package/ts_web/elements/00group-harness/toolregistry.ts +8 -0
  56. package/ts_web/elements/00group-layout/dees-mosaic/dees-mosaic.demo.ts +87 -0
  57. package/ts_web/elements/00group-layout/dees-mosaic/dees-mosaic.ts +562 -0
  58. package/ts_web/elements/00group-layout/dees-mosaic/index.ts +3 -0
  59. package/ts_web/elements/00group-layout/dees-mosaic/interaction.ts +187 -0
  60. package/ts_web/elements/00group-layout/dees-mosaic/tree.ts +239 -0
  61. package/ts_web/elements/00group-layout/dees-mosaic/types.ts +63 -0
  62. package/ts_web/elements/00group-layout/index.ts +1 -0
  63. package/ts_web/elements/00theme.ts +7 -0
@@ -0,0 +1,319 @@
1
+ import { demoFunc } from './dees-harness-question-card.demo.js';
2
+ import {
3
+ DeesElement,
4
+ html,
5
+ css,
6
+ customElement,
7
+ type TemplateResult,
8
+ property,
9
+ state,
10
+ cssManager,
11
+ } from '@design.estate/dees-element';
12
+ import { themeDefaultStyles } from '../../00theme.js';
13
+ import type { IHarnessQuestionRequest } from '../interfaces.js';
14
+ import '../../00group-button/dees-button/dees-button.js';
15
+ import '../../00group-utility/dees-icon/dees-icon.js';
16
+
17
+ declare global {
18
+ interface HTMLElementTagNameMap {
19
+ 'dees-harness-question-card': DeesHarnessQuestionCard;
20
+ }
21
+ }
22
+
23
+ /**
24
+ * A question the agent asks mid-run: option buttons (single-select answers
25
+ * immediately, multi-select collects then confirms) plus an optional
26
+ * free-text answer. Emits `harness-question-response` (bubbles, composed).
27
+ * Answered cards persist showing the chosen answer(s) — the host sets
28
+ * `response`/`respondedAt` on the request and keeps it in the array.
29
+ */
30
+ @customElement('dees-harness-question-card')
31
+ export class DeesHarnessQuestionCard extends DeesElement {
32
+ public static demo = demoFunc;
33
+ public static demoGroups = ['Agent Chat'];
34
+
35
+ @property({ attribute: false })
36
+ accessor request: IHarnessQuestionRequest = { id: '', question: '' };
37
+
38
+ @property({ type: Boolean })
39
+ accessor busy: boolean = false;
40
+
41
+ /** local optimistic answer; request.response (host echo) takes precedence */
42
+ @state()
43
+ accessor answered: string[] | undefined = undefined;
44
+
45
+ @state()
46
+ accessor selected: ReadonlySet<string> = new Set<string>();
47
+
48
+ @state()
49
+ accessor customText: string = '';
50
+
51
+ public get effectiveResponse(): string[] | undefined {
52
+ return this.request.response ?? this.answered;
53
+ }
54
+
55
+ public static styles = [
56
+ themeDefaultStyles,
57
+ cssManager.defaultStyles,
58
+ css`
59
+ :host {
60
+ display: block;
61
+ font-family: var(--dees-font-family);
62
+ interpolate-size: allow-keywords;
63
+ overflow: clip;
64
+ animation: harness-entry var(--dees-transition-default) var(--dees-ease-out);
65
+ }
66
+
67
+ @keyframes harness-entry {
68
+ from {
69
+ height: 0;
70
+ margin-top: 0;
71
+ opacity: 0;
72
+ }
73
+ }
74
+
75
+ @media (prefers-reduced-motion: reduce) {
76
+ :host {
77
+ animation: none;
78
+ }
79
+ }
80
+
81
+ .card {
82
+ padding: var(--dees-spacing-sm) var(--dees-spacing-md);
83
+ border: 1px solid var(--dees-color-border-default);
84
+ border-left: 2px solid var(--dees-color-accent-primary);
85
+ border-radius: var(--dees-radius-lg);
86
+ corner-shape: var(--dees-corner-shape);
87
+ background: var(--dees-color-bg-secondary);
88
+ }
89
+
90
+ .titleRow {
91
+ display: flex;
92
+ align-items: flex-start;
93
+ gap: var(--dees-spacing-sm);
94
+ margin-bottom: var(--dees-spacing-sm);
95
+ }
96
+
97
+ .titleRow dees-icon {
98
+ flex: 0 0 auto;
99
+ margin-top: 1px;
100
+ color: var(--dees-color-accent-primary);
101
+ }
102
+
103
+ .questionText {
104
+ min-width: 0;
105
+ color: var(--dees-color-text-primary);
106
+ font-size: var(--dees-font-control-size, 13px);
107
+ font-weight: 600;
108
+ line-height: 1.4;
109
+ overflow-wrap: anywhere;
110
+ }
111
+
112
+ .options {
113
+ display: block;
114
+ }
115
+
116
+ .options > * + * {
117
+ margin-top: var(--dees-spacing-xs);
118
+ }
119
+
120
+ .option {
121
+ width: 100%;
122
+ display: block;
123
+ padding: var(--dees-spacing-xs) var(--dees-spacing-sm);
124
+ border: 1px solid var(--dees-color-border-subtle);
125
+ border-radius: var(--dees-radius-md);
126
+ corner-shape: var(--dees-corner-shape);
127
+ background: var(--dees-color-bg-primary);
128
+ color: var(--dees-color-text-primary);
129
+ cursor: pointer;
130
+ font-family: inherit;
131
+ font-size: var(--dees-font-control-size-sm, 12px);
132
+ text-align: left;
133
+ transition:
134
+ border-color var(--dees-transition-fast) var(--dees-ease-standard),
135
+ background var(--dees-transition-fast) var(--dees-ease-standard);
136
+ }
137
+
138
+ .option:hover:not(:disabled) {
139
+ border-color: var(--dees-color-accent-primary);
140
+ }
141
+
142
+ .option.selected {
143
+ border-color: var(--dees-color-accent-primary);
144
+ background: color-mix(in srgb, var(--dees-color-accent-primary) 10%, var(--dees-color-bg-primary));
145
+ }
146
+
147
+ .option:disabled {
148
+ cursor: default;
149
+ opacity: 0.6;
150
+ }
151
+
152
+ .optionLabel {
153
+ font-weight: 500;
154
+ }
155
+
156
+ .optionDescription {
157
+ margin-top: 1px;
158
+ color: var(--dees-color-text-secondary);
159
+ font-size: var(--dees-font-caption1-size, 11px);
160
+ }
161
+
162
+ .customRow {
163
+ display: flex;
164
+ gap: var(--dees-spacing-xs);
165
+ margin-top: var(--dees-spacing-sm);
166
+ }
167
+
168
+ .customRow input {
169
+ flex: 1;
170
+ min-width: 0;
171
+ height: var(--dees-control-height-sm);
172
+ padding: 0 var(--dees-spacing-sm);
173
+ border: 1px solid var(--dees-color-border-default);
174
+ border-radius: var(--dees-radius-md);
175
+ corner-shape: var(--dees-corner-shape);
176
+ background: var(--dees-color-bg-primary);
177
+ color: var(--dees-color-text-primary);
178
+ font-family: inherit;
179
+ font-size: var(--dees-font-control-size-sm, 12px);
180
+ outline: none;
181
+ }
182
+
183
+ .customRow input:focus {
184
+ border-color: var(--dees-color-accent-primary);
185
+ box-shadow: 0 0 0 3px var(--dees-color-focus-ring);
186
+ }
187
+
188
+ .confirmRow {
189
+ display: flex;
190
+ justify-content: flex-end;
191
+ margin-top: var(--dees-spacing-sm);
192
+ }
193
+
194
+ .answeredNote {
195
+ display: flex;
196
+ align-items: baseline;
197
+ gap: var(--dees-spacing-sm);
198
+ color: var(--dees-color-text-secondary);
199
+ font-size: var(--dees-font-control-size-sm, 12px);
200
+ }
201
+
202
+ .answeredNote .answerText {
203
+ color: var(--dees-color-text-primary);
204
+ font-weight: 500;
205
+ overflow-wrap: anywhere;
206
+ }
207
+ `,
208
+ ];
209
+
210
+ public render(): TemplateResult {
211
+ const request = this.request;
212
+ const response = this.effectiveResponse;
213
+ const disabled = this.busy || response !== undefined;
214
+ return html`
215
+ <div class="card">
216
+ <div class="titleRow">
217
+ <dees-icon icon="lucide:MessageCircleQuestionMark" iconSize="15"></dees-icon>
218
+ <span class="questionText">${request.question}</span>
219
+ </div>
220
+ ${response
221
+ ? html`
222
+ <div class="answeredNote">
223
+ Answered:
224
+ <span class="answerText">${response.join(', ') || '—'}</span>
225
+ </div>
226
+ `
227
+ : html`
228
+ ${request.options?.length
229
+ ? html`
230
+ <div class="options" role=${request.multiSelect ? 'group' : 'radiogroup'}>
231
+ ${request.options.map((option) => html`
232
+ <button
233
+ class="option ${this.selected.has(option.label) ? 'selected' : ''}"
234
+ type="button"
235
+ ?disabled=${disabled}
236
+ @click=${() => this.handleOptionClick(option.label)}
237
+ >
238
+ <div class="optionLabel">${option.label}</div>
239
+ ${option.description
240
+ ? html`<div class="optionDescription">${option.description}</div>`
241
+ : ''}
242
+ </button>
243
+ `)}
244
+ </div>
245
+ `
246
+ : ''}
247
+ ${request.allowCustom !== false
248
+ ? html`
249
+ <div class="customRow">
250
+ <input
251
+ placeholder="Answer in your own words…"
252
+ .value=${this.customText}
253
+ ?disabled=${disabled}
254
+ @input=${(event: Event) => {
255
+ this.customText = (event.currentTarget as HTMLInputElement).value;
256
+ }}
257
+ @keydown=${(event: KeyboardEvent) => {
258
+ if (event.key === 'Enter' && this.customText.trim()) {
259
+ this.respond([...this.selected, this.customText.trim()]);
260
+ }
261
+ }}
262
+ />
263
+ <dees-button
264
+ size="sm"
265
+ ?disabled=${disabled || (!this.customText.trim() && !this.selected.size)}
266
+ @clicked=${() => {
267
+ const answers = [...this.selected];
268
+ if (this.customText.trim()) answers.push(this.customText.trim());
269
+ this.respond(answers);
270
+ }}
271
+ >Send Answer</dees-button>
272
+ </div>
273
+ `
274
+ : ''}
275
+ ${request.multiSelect && request.allowCustom === false
276
+ ? html`
277
+ <div class="confirmRow">
278
+ <dees-button
279
+ type="accent"
280
+ size="sm"
281
+ ?disabled=${disabled || !this.selected.size}
282
+ @clicked=${() => this.respond([...this.selected])}
283
+ >Confirm</dees-button>
284
+ </div>
285
+ `
286
+ : ''}
287
+ `}
288
+ </div>
289
+ `;
290
+ }
291
+
292
+ private handleOptionClick(label: string): void {
293
+ if (this.busy || this.effectiveResponse !== undefined) return;
294
+ if (this.request.multiSelect) {
295
+ const next = new Set(this.selected);
296
+ if (next.has(label)) next.delete(label);
297
+ else next.add(label);
298
+ this.selected = next;
299
+ return;
300
+ }
301
+ this.respond([label]);
302
+ }
303
+
304
+ private respond(answers: string[]): void {
305
+ if (this.busy || this.effectiveResponse !== undefined || !answers.length) return;
306
+ this.answered = answers;
307
+ this.dispatchEvent(
308
+ new CustomEvent('harness-question-response', {
309
+ detail: {
310
+ requestId: this.request.id,
311
+ answers,
312
+ request: this.request,
313
+ },
314
+ bubbles: true,
315
+ composed: true,
316
+ }),
317
+ );
318
+ }
319
+ }
@@ -0,0 +1 @@
1
+ export * from './dees-harness-question-card.js';
@@ -13,6 +13,7 @@ export * from './dees-harness-composer/index.js';
13
13
  export * from './dees-harness-content-blocks/index.js';
14
14
  export * from './dees-harness-message/index.js';
15
15
  export * from './dees-harness-permission-card/index.js';
16
+ export * from './dees-harness-question-card/index.js';
16
17
  export * from './dees-harness-message-list/index.js';
17
18
  export * from './dees-harness-reasoning/index.js';
18
19
  export * from './dees-harness-session-list/index.js';
@@ -142,10 +142,46 @@ export interface IHarnessPermissionRequest {
142
142
  /** open set; well-known values today: write | delete | shell | browser */
143
143
  type: string;
144
144
  metadata?: Record<string, unknown>;
145
+ /** set once answered — the card stays in the transcript showing the outcome */
146
+ response?: THarnessPermissionResponse;
147
+ /** epoch ms */
148
+ respondedAt?: number;
145
149
  }
146
150
 
147
151
  export type THarnessPermissionResponse = 'once' | 'always' | 'reject';
148
152
 
153
+ export interface IHarnessQuestionOption {
154
+ label: string;
155
+ description?: string;
156
+ }
157
+
158
+ /**
159
+ * A question the agent asks the user mid-run. Renders inline in the
160
+ * transcript like permissions: pending questions pin to the end, answered
161
+ * ones persist showing the chosen answer(s).
162
+ */
163
+ export interface IHarnessQuestionRequest {
164
+ id: string;
165
+ sessionId?: string;
166
+ /** epoch ms */
167
+ createdAt?: number;
168
+ question: string;
169
+ options?: IHarnessQuestionOption[];
170
+ multiSelect?: boolean;
171
+ /** allow a free-text answer alongside the options (default true) */
172
+ allowCustom?: boolean;
173
+ /** set once answered — the card stays in the transcript showing the answer */
174
+ response?: string[];
175
+ /** epoch ms */
176
+ respondedAt?: number;
177
+ }
178
+
179
+ export interface IHarnessQuestionResponseDetail {
180
+ requestId: string;
181
+ answers: string[];
182
+ request: IHarnessQuestionRequest;
183
+ }
184
+
149
185
  export interface IHarnessPermissionResponseDetail {
150
186
  requestId: string;
151
187
  response: THarnessPermissionResponse;
@@ -163,6 +163,14 @@ const defaultDescriptors: IHarnessToolDescriptor[] = [
163
163
  icon: 'lucide:Bot',
164
164
  subtitle: (call) => inputField(call, 'description') || inputField(call, 'prompt') || harnessSummaryLine(call.input),
165
165
  },
166
+ {
167
+ kind: 'question',
168
+ names: ['askuserquestion', 'ask_user_question', 'ask_question', 'ask_user', 'question'],
169
+ label: 'Question',
170
+ icon: 'lucide:MessageCircleQuestionMark',
171
+ subtitle: (call) => inputField(call, 'question') || inputField(call, 'prompt') || harnessSummaryLine(call.input),
172
+ defaultExpanded: true,
173
+ },
166
174
  {
167
175
  kind: 'todo',
168
176
  names: ['todowrite', 'todo_write', 'todoread'],
@@ -0,0 +1,87 @@
1
+ import { html } from '@design.estate/dees-element';
2
+ import type { DeesMosaic } from './dees-mosaic.js';
3
+ import type { IMosaicLayout } from './types.js';
4
+ import { createMosaicLeaf, mosaicInsert } from './tree.js';
5
+
6
+ const buildDefaultLayout = (): IMosaicLayout => {
7
+ let layout = mosaicInsert({ root: null }, null, 'right', createMosaicLeaf('editor', 'leaf-editor'));
8
+ layout = mosaicInsert(layout, 'leaf-editor', 'right', createMosaicLeaf('chat', 'leaf-chat'));
9
+ layout = mosaicInsert(layout, null, 'bottom', createMosaicLeaf('terminal', 'leaf-terminal'));
10
+ return layout;
11
+ };
12
+
13
+ const surfaceMeta = {
14
+ editor: { title: 'Editor', icon: 'lucide:Code' },
15
+ chat: { title: 'AI Chat', icon: 'lucide:BotMessageSquare' },
16
+ terminal: { title: 'Terminal', icon: 'lucide:SquareTerminal' },
17
+ browser: { title: 'Browser', icon: 'lucide:Globe' },
18
+ };
19
+
20
+ let tileCounter = 0;
21
+
22
+ const addBrowserTile = (event: MouseEvent) => {
23
+ const mosaic = (event.target as HTMLElement).closest('.mosaicDemo')?.querySelector('dees-mosaic') as DeesMosaic | null;
24
+ if (!mosaic) return;
25
+ tileCounter += 1;
26
+ const leaf = createMosaicLeaf('browser', `leaf-browser-${tileCounter}`);
27
+ mosaic.layout = mosaicInsert(mosaic.layout, 'leaf-chat', 'bottom', leaf);
28
+ };
29
+
30
+ export const demoFunc = () => html`
31
+ <style>
32
+ .mosaicDemo {
33
+ display: grid;
34
+ grid-template-rows: auto minmax(0, 1fr);
35
+ gap: 12px;
36
+ height: 640px;
37
+ max-width: 1000px;
38
+ margin: 40px auto;
39
+ }
40
+ .mosaicDemo .surfaceBody {
41
+ display: grid;
42
+ place-content: center;
43
+ gap: 6px;
44
+ height: 100%;
45
+ text-align: center;
46
+ color: var(--dees-color-text-secondary);
47
+ font-size: 12px;
48
+ background: var(--dees-color-bg-primary);
49
+ }
50
+ .mosaicDemo .surfaceBody strong {
51
+ color: var(--dees-color-text-primary);
52
+ font-size: 14px;
53
+ }
54
+ .mosaicDemo .emptyHint {
55
+ color: var(--dees-color-text-secondary);
56
+ font-size: 13px;
57
+ }
58
+ </style>
59
+ <div
60
+ class="mosaicDemo"
61
+ @mosaic-layout-change=${(event: CustomEvent) => {
62
+ const mosaic = (event.target as HTMLElement);
63
+ (mosaic as DeesMosaic).layout = event.detail.layout;
64
+ console.log('mosaic-layout-change', event.detail.reason, event.detail.layout);
65
+ }}
66
+ @mosaic-tile-close=${(event: CustomEvent) => console.log('mosaic-tile-close', event.detail)}
67
+ @mosaic-tile-focus=${(event: CustomEvent) => {
68
+ ((event.target as HTMLElement) as DeesMosaic).focusedLeafId = event.detail.leafId;
69
+ }}
70
+ >
71
+ <div>
72
+ <dees-button @clicked=${addBrowserTile}>Add browser tile below chat</dees-button>
73
+ </div>
74
+ <dees-mosaic
75
+ .layout=${buildDefaultLayout()}
76
+ .surfaces=${surfaceMeta}
77
+ focusedLeafId="leaf-editor"
78
+ >
79
+ <div slot="tile-leaf-editor" class="surfaceBody"><strong>Editor</strong><span>drag my tilebar; resize the shared edges</span></div>
80
+ <div slot="tile-leaf-chat" class="surfaceBody"><strong>AI Chat</strong><span>docked right</span></div>
81
+ <div slot="tile-leaf-terminal" class="surfaceBody"><strong>Terminal</strong><span>stacked from below</span></div>
82
+ <div slot="tile-leaf-browser-1" class="surfaceBody"><strong>Browser 1</strong></div>
83
+ <div slot="tile-leaf-browser-2" class="surfaceBody"><strong>Browser 2</strong></div>
84
+ <div slot="empty" class="emptyHint">Workspace is empty — add a tile.</div>
85
+ </dees-mosaic>
86
+ </div>
87
+ `;