@design.estate/dees-catalog 4.0.3 → 4.1.1

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.
@@ -0,0 +1,211 @@
1
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
2
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
3
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
4
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
5
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
6
+ var _, done = false;
7
+ for (var i = decorators.length - 1; i >= 0; i--) {
8
+ var context = {};
9
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
10
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
11
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
12
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
13
+ if (kind === "accessor") {
14
+ if (result === void 0) continue;
15
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
16
+ if (_ = accept(result.get)) descriptor.get = _;
17
+ if (_ = accept(result.set)) descriptor.set = _;
18
+ if (_ = accept(result.init)) initializers.unshift(_);
19
+ }
20
+ else if (_ = accept(result)) {
21
+ if (kind === "field") initializers.unshift(_);
22
+ else descriptor[key] = _;
23
+ }
24
+ }
25
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
26
+ done = true;
27
+ };
28
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
29
+ var useValue = arguments.length > 2;
30
+ for (var i = 0; i < initializers.length; i++) {
31
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
32
+ }
33
+ return useValue ? value : void 0;
34
+ };
35
+ import { DeesElement, html, css, customElement, property, cssManager, } from '@design.estate/dees-element';
36
+ import { themeDefaultStyles } from '../../00theme.js';
37
+ import { DeesHarnessToolRegistry, parseMcpName } from '../toolregistry.js';
38
+ import '../../00group-utility/dees-icon/dees-icon.js';
39
+ /**
40
+ * Fullscreen view of one tool card, appended to document.body by
41
+ * DeesHarnessToolCard so it escapes every containment context
42
+ * (content-visibility, transforms) that would trap a position: fixed layer
43
+ * inside the transcript. Emits `tool-fullscreen-close` on Escape, backdrop
44
+ * press, or the minimize button; the owning card removes it and flashes.
45
+ */
46
+ let DeesHarnessToolFullscreen = (() => {
47
+ let _classDecorators = [customElement('dees-harness-tool-fullscreen')];
48
+ let _classDescriptor;
49
+ let _classExtraInitializers = [];
50
+ let _classThis;
51
+ let _classSuper = DeesElement;
52
+ let _call_decorators;
53
+ let _call_initializers = [];
54
+ let _call_extraInitializers = [];
55
+ let _toolRegistry_decorators;
56
+ let _toolRegistry_initializers = [];
57
+ let _toolRegistry_extraInitializers = [];
58
+ var DeesHarnessToolFullscreen = class extends _classSuper {
59
+ static { _classThis = this; }
60
+ static {
61
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
62
+ _call_decorators = [property({ attribute: false })];
63
+ _toolRegistry_decorators = [property({ attribute: false })];
64
+ __esDecorate(this, null, _call_decorators, { kind: "accessor", name: "call", static: false, private: false, access: { has: obj => "call" in obj, get: obj => obj.call, set: (obj, value) => { obj.call = value; } }, metadata: _metadata }, _call_initializers, _call_extraInitializers);
65
+ __esDecorate(this, null, _toolRegistry_decorators, { kind: "accessor", name: "toolRegistry", static: false, private: false, access: { has: obj => "toolRegistry" in obj, get: obj => obj.toolRegistry, set: (obj, value) => { obj.toolRegistry = value; } }, metadata: _metadata }, _toolRegistry_initializers, _toolRegistry_extraInitializers);
66
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
67
+ DeesHarnessToolFullscreen = _classThis = _classDescriptor.value;
68
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
69
+ }
70
+ #call_accessor_storage = __runInitializers(this, _call_initializers, { id: '', name: '', status: 'pending' });
71
+ get call() { return this.#call_accessor_storage; }
72
+ set call(value) { this.#call_accessor_storage = value; }
73
+ #toolRegistry_accessor_storage = (__runInitializers(this, _call_extraInitializers), __runInitializers(this, _toolRegistry_initializers, DeesHarnessToolRegistry.default));
74
+ get toolRegistry() { return this.#toolRegistry_accessor_storage; }
75
+ set toolRegistry(value) { this.#toolRegistry_accessor_storage = value; }
76
+ static styles = [
77
+ themeDefaultStyles,
78
+ cssManager.defaultStyles,
79
+ css `
80
+ :host {
81
+ position: fixed;
82
+ inset: 0;
83
+ z-index: 4000;
84
+ display: grid;
85
+ place-items: center;
86
+ padding: 4vh 3vw;
87
+ background: color-mix(in srgb, var(--dees-color-bg-primary) 55%, transparent);
88
+ backdrop-filter: blur(6px);
89
+ font-family: var(--dees-font-family);
90
+ }
91
+
92
+ .panel {
93
+ display: flex;
94
+ flex-direction: column;
95
+ width: min(1200px, 100%);
96
+ height: 100%;
97
+ min-height: 0;
98
+ border: 1px solid var(--dees-color-border-default);
99
+ border-radius: var(--dees-radius-lg);
100
+ background: var(--dees-color-bg-primary);
101
+ box-shadow: 0 24px 64px rgba(0, 0, 0, 0.4);
102
+ overflow: hidden;
103
+ }
104
+
105
+ .panelHeader {
106
+ display: flex;
107
+ flex: 0 0 auto;
108
+ align-items: center;
109
+ gap: var(--dees-spacing-sm);
110
+ padding: var(--dees-spacing-sm) var(--dees-spacing-md);
111
+ border-bottom: 1px solid var(--dees-color-border-subtle);
112
+ color: var(--dees-color-text-primary);
113
+ }
114
+
115
+ .panelTitle {
116
+ min-width: 0;
117
+ overflow: hidden;
118
+ text-overflow: ellipsis;
119
+ white-space: nowrap;
120
+ font-size: var(--dees-font-control-size-sm, 12px);
121
+ font-weight: 650;
122
+ }
123
+
124
+ .panelClose {
125
+ display: grid;
126
+ place-items: center;
127
+ margin-left: auto;
128
+ padding: 6px;
129
+ border: 0;
130
+ border-radius: var(--dees-radius-md);
131
+ background: transparent;
132
+ color: var(--dees-color-text-secondary);
133
+ cursor: pointer;
134
+ }
135
+
136
+ .panelClose:hover {
137
+ background: var(--dees-color-hover);
138
+ color: var(--dees-color-text-primary);
139
+ }
140
+
141
+ .panelBody {
142
+ flex: 1;
143
+ min-height: 0;
144
+ overflow: auto;
145
+ padding: var(--dees-spacing-md);
146
+ scrollbar-width: thin;
147
+ scrollbar-color: var(--dees-color-scrollbar-thumb) transparent;
148
+ }
149
+
150
+ .panelBody dees-harness-tool-card {
151
+ display: block;
152
+ }
153
+ `,
154
+ ];
155
+ render() {
156
+ const parsed = this.call.mcp ? undefined : parseMcpName(this.call.name);
157
+ const enriched = parsed
158
+ ? { ...this.call, mcp: { serverName: parsed.serverName, toolName: parsed.toolName } }
159
+ : this.call;
160
+ const descriptor = this.toolRegistry.resolve(enriched);
161
+ const label = typeof descriptor.label === 'function' ? descriptor.label(enriched) : descriptor.label;
162
+ const subtitle = descriptor.subtitle?.(enriched) ?? '';
163
+ return html `
164
+ <div class="panel" @click=${(event) => event.stopPropagation()}>
165
+ <header class="panelHeader">
166
+ <dees-icon .icon=${descriptor.icon} iconSize="15"></dees-icon>
167
+ <span class="panelTitle">${label}${subtitle ? ` · ${subtitle}` : ''}</span>
168
+ <button class="panelClose" type="button" @click=${this.emitClose}>
169
+ <dees-icon icon="lucide:Minimize2" iconSize="15"></dees-icon>
170
+ </button>
171
+ </header>
172
+ <div class="panelBody">
173
+ <dees-harness-tool-card
174
+ .call=${this.call}
175
+ .toolRegistry=${this.toolRegistry}
176
+ .expanded=${true}
177
+ .outputExpanded=${true}
178
+ uncapped
179
+ ></dees-harness-tool-card>
180
+ </div>
181
+ </div>
182
+ `;
183
+ }
184
+ firstUpdated() {
185
+ window.addEventListener('keydown', this.handleKeydown);
186
+ this.addEventListener('click', (event) => {
187
+ if (event.target === this)
188
+ this.emitClose();
189
+ });
190
+ }
191
+ handleKeydown = (__runInitializers(this, _toolRegistry_extraInitializers), (event) => {
192
+ if (event.key === 'Escape') {
193
+ event.preventDefault();
194
+ this.emitClose();
195
+ }
196
+ });
197
+ emitClose = () => {
198
+ this.dispatchEvent(new CustomEvent('tool-fullscreen-close', { bubbles: false }));
199
+ };
200
+ async disconnectedCallback() {
201
+ window.removeEventListener('keydown', this.handleKeydown);
202
+ await super.disconnectedCallback();
203
+ }
204
+ static {
205
+ __runInitializers(_classThis, _classExtraInitializers);
206
+ }
207
+ };
208
+ return DeesHarnessToolFullscreen = _classThis;
209
+ })();
210
+ export { DeesHarnessToolFullscreen };
211
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVlcy1oYXJuZXNzLXRvb2wtZnVsbHNjcmVlbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3RzX3dlYi9lbGVtZW50cy8wMGdyb3VwLWhhcm5lc3MvZGVlcy1oYXJuZXNzLXRvb2wtY2FyZC9kZWVzLWhhcm5lc3MtdG9vbC1mdWxsc2NyZWVuLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSxPQUFPLEVBQ0wsV0FBVyxFQUNYLElBQUksRUFDSixHQUFHLEVBQ0gsYUFBYSxFQUViLFFBQVEsRUFDUixVQUFVLEdBQ1gsTUFBTSw2QkFBNkIsQ0FBQztBQUNyQyxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUV0RCxPQUFPLEVBQUUsdUJBQXVCLEVBQUUsWUFBWSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDM0UsT0FBTyw4Q0FBOEMsQ0FBQztBQVF0RDs7Ozs7O0dBTUc7SUFFVSx5QkFBeUI7NEJBRHJDLGFBQWEsQ0FBQyw4QkFBOEIsQ0FBQzs7OztzQkFDQyxXQUFXOzs7Ozs7O3lDQUFuQixTQUFRLFdBQVc7Ozs7Z0NBQ3ZELFFBQVEsQ0FBQyxFQUFFLFNBQVMsRUFBRSxLQUFLLEVBQUUsQ0FBQzt3Q0FHOUIsUUFBUSxDQUFDLEVBQUUsU0FBUyxFQUFFLEtBQUssRUFBRSxDQUFDO1lBRi9CLGlLQUFTLElBQUksNkJBQUosSUFBSSxtRkFBNkQ7WUFHMUUseUxBQVMsWUFBWSw2QkFBWixZQUFZLG1HQUE0RDtZQUxuRiw2S0EySUM7Ozs7UUF6SUMscUVBQWtDLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFBRSxJQUFJLEVBQUUsRUFBRSxFQUFFLE1BQU0sRUFBRSxTQUFTLEVBQUUsRUFBQztRQUExRSxJQUFTLElBQUksMENBQTZEO1FBQTFFLElBQVMsSUFBSSxnREFBNkQ7UUFHMUUsd0lBQWlELHVCQUF1QixDQUFDLE9BQU8sR0FBQztRQUFq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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@design.estate/dees-catalog",
3
- "version": "4.0.3",
3
+ "version": "4.1.1",
4
4
  "private": false,
5
5
  "description": "A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.",
6
6
  "main": "dist_ts_web/index.js",
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@design.estate/dees-catalog',
6
- version: '4.0.3',
6
+ version: '4.1.1',
7
7
  description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.'
8
8
  }
@@ -81,6 +81,13 @@ export class DeesHarnessMessage extends DeesElement {
81
81
  boundary instead of widening the whole conversation; scrollable
82
82
  payload blocks inside keep their own horizontal scrollbars. */
83
83
  overflow-x: clip;
84
+ /* Virtualized rendering: cards outside the viewport skip style,
85
+ layout, and paint entirely and re-render when scrolled back —
86
+ state and DOM stay, so expansion and selection survive. The
87
+ 'auto' keyword keeps the last rendered size as the placeholder,
88
+ holding the scrollbar steady. */
89
+ content-visibility: auto;
90
+ contain-intrinsic-size: auto 96px;
84
91
  font-family: var(--dees-font-family);
85
92
  /* entry: new boxes make space for themselves while fading in.
86
93
  A keyframes animation (not a transition) so the parent tree's
@@ -22,6 +22,7 @@ import { harnessCardStyles, harnessPillStyles, harnessMonoStyles } from '../harn
22
22
  import { harnessFormatDuration, harnessTruncate } from '../harness.helpers.js';
23
23
  import '../dees-harness-content-blocks/dees-harness-content-blocks.js';
24
24
  import '../../00group-dataview/dees-dataview-codebox/dees-dataview-codebox.js';
25
+ import './dees-harness-tool-fullscreen.js';
25
26
  import '../../00group-feedback/dees-badge/dees-badge.js';
26
27
  import '../../00group-utility/dees-icon/dees-icon.js';
27
28
 
@@ -54,6 +55,24 @@ export class DeesHarnessToolCard extends DeesElement {
54
55
  @state()
55
56
  accessor outputExpanded: boolean = false;
56
57
 
58
+ /** Body height cap; taller content scrolls in place and offers fullscreen. */
59
+ @property({ type: Number })
60
+ accessor bodyCapPx: number = 360;
61
+
62
+ /** Set on the card inside the fullscreen modal: no cap, no maximize bar. */
63
+ @property({ type: Boolean })
64
+ accessor uncapped: boolean = false;
65
+
66
+ @state()
67
+ accessor bodyOverflowing: boolean = false;
68
+
69
+ @state()
70
+ accessor returnFlash: boolean = false;
71
+
72
+ private bodyResizeObserver: ResizeObserver | null = null;
73
+ private observedBodyInner: Element | null = null;
74
+ private fullscreenPortal: HTMLElementTagNameMap['dees-harness-tool-fullscreen'] | null = null;
75
+
57
76
  public static styles = [
58
77
  themeDefaultStyles,
59
78
  cssManager.defaultStyles,
@@ -180,6 +199,59 @@ export class DeesHarnessToolCard extends DeesElement {
180
199
  dees-dataview-codebox {
181
200
  font-family: var(--dees-font-family);
182
201
  }
202
+
203
+ /* Three body modes: collapsed (no body), normal (content fits the
204
+ cap), capped (content taller than the cap scrolls inside the frame
205
+ and offers the fullscreen modal). */
206
+ .bodyClipFrame {
207
+ position: relative;
208
+ overflow-y: auto;
209
+ overflow-x: hidden;
210
+ scrollbar-width: thin;
211
+ scrollbar-color: var(--dees-color-scrollbar-thumb) transparent;
212
+ }
213
+
214
+ .maximizeBar {
215
+ display: flex;
216
+ align-items: center;
217
+ justify-content: center;
218
+ gap: var(--dees-spacing-xs);
219
+ width: 100%;
220
+ padding: var(--dees-spacing-xs) var(--dees-spacing-md);
221
+ border: 0;
222
+ border-top: 1px solid var(--dees-color-border-subtle);
223
+ background: transparent;
224
+ color: var(--dees-color-link);
225
+ cursor: pointer;
226
+ font-family: inherit;
227
+ font-size: var(--dees-font-control-size-sm, 12px);
228
+ }
229
+
230
+ .maximizeBar:hover {
231
+ background: var(--dees-color-hover);
232
+ color: var(--dees-color-link-hover);
233
+ }
234
+
235
+ /* Locates the card after minimizing: a brief highlight pulse. */
236
+ .harness-card.returnFlash {
237
+ animation: harness-card-return-flash 1.4s var(--dees-ease-standard);
238
+ }
239
+
240
+ @keyframes harness-card-return-flash {
241
+ 0%, 55% {
242
+ box-shadow: 0 0 0 2px var(--dees-color-accent-primary);
243
+ }
244
+ 100% {
245
+ box-shadow: 0 0 0 0 transparent;
246
+ }
247
+ }
248
+
249
+ @media (prefers-reduced-motion: reduce) {
250
+ .harness-card.returnFlash {
251
+ animation: none;
252
+ box-shadow: 0 0 0 2px var(--dees-color-accent-primary);
253
+ }
254
+ }
183
255
  `,
184
256
  ];
185
257
 
@@ -210,7 +282,13 @@ export class DeesHarnessToolCard extends DeesElement {
210
282
  const statusTone = call.isError ? 'error' : call.status === 'completed' ? 'success' : call.status;
211
283
  const duration = harnessFormatDuration(call.startedAt, call.finishedAt);
212
284
  return html`
213
- <div class="harness-card status-${call.isError ? 'error' : call.status} ${descriptor.destructive ? 'destructive' : ''}">
285
+ <div class="harness-card status-${call.isError ? 'error' : call.status} ${descriptor.destructive ? 'destructive' : ''} ${this.returnFlash ? 'returnFlash' : ''}"
286
+ @animationend=${(event: AnimationEvent) => {
287
+ if (event.animationName === 'harness-card-return-flash') {
288
+ this.returnFlash = false;
289
+ }
290
+ }}
291
+ >
214
292
  <button
215
293
  class="header"
216
294
  type="button"
@@ -234,11 +312,81 @@ export class DeesHarnessToolCard extends DeesElement {
234
312
  </span>
235
313
  </span>
236
314
  </button>
237
- ${this.isOpen ? html`<div class="body">${this.renderBody(call)}</div>` : ''}
315
+ ${this.isOpen
316
+ ? html`
317
+ <div
318
+ class="bodyClipFrame"
319
+ style=${this.uncapped ? '' : `max-height: ${this.bodyCapPx}px;`}
320
+ >
321
+ <div class="body bodyInner">${this.renderBody(call)}</div>
322
+ </div>
323
+ ${this.bodyOverflowing && !this.uncapped
324
+ ? html`
325
+ <button class="maximizeBar" type="button" @click=${this.openFullscreen}>
326
+ <dees-icon icon="lucide:Maximize2" iconSize="12"></dees-icon>
327
+ Show everything
328
+ </button>
329
+ `
330
+ : ''}
331
+ `
332
+ : ''}
238
333
  </div>
239
334
  `;
240
335
  }
241
336
 
337
+ private readonly openFullscreen = (): void => {
338
+ if (this.fullscreenPortal) return;
339
+ // Appended to document.body: content-visibility containment in the
340
+ // transcript would trap a position: fixed layer inside this card.
341
+ const portal = document.createElement('dees-harness-tool-fullscreen');
342
+ portal.call = this.enrichedCall;
343
+ portal.toolRegistry = this.toolRegistry;
344
+ portal.addEventListener('tool-fullscreen-close', this.closeFullscreen, { once: true });
345
+ document.body.append(portal);
346
+ this.fullscreenPortal = portal;
347
+ };
348
+
349
+ private readonly closeFullscreen = (): void => {
350
+ const portal = this.fullscreenPortal;
351
+ if (!portal) return;
352
+ this.fullscreenPortal = null;
353
+ portal.remove();
354
+ // The flash locates the card the user was just reading in fullscreen.
355
+ this.returnFlash = true;
356
+ requestAnimationFrame(() => {
357
+ this.scrollIntoView({ block: 'center', behavior: 'smooth' });
358
+ });
359
+ };
360
+
361
+ public updated(changedProperties: Map<PropertyKey, unknown>): void {
362
+ super.updated(changedProperties);
363
+ if (this.uncapped) return;
364
+ const bodyInner = this.shadowRoot?.querySelector('.bodyInner') ?? null;
365
+ if (bodyInner === this.observedBodyInner) return;
366
+ this.bodyResizeObserver?.disconnect();
367
+ this.observedBodyInner = bodyInner;
368
+ if (!bodyInner) {
369
+ this.bodyOverflowing = false;
370
+ return;
371
+ }
372
+ this.bodyResizeObserver ??= new ResizeObserver(() => {
373
+ const inner = this.observedBodyInner;
374
+ if (!inner) return;
375
+ // small slack so a few px never trigger the cap chrome
376
+ this.bodyOverflowing = inner.scrollHeight > this.bodyCapPx + 24;
377
+ });
378
+ this.bodyResizeObserver.observe(bodyInner);
379
+ }
380
+
381
+ public async disconnectedCallback(): Promise<void> {
382
+ this.fullscreenPortal?.remove();
383
+ this.fullscreenPortal = null;
384
+ this.bodyResizeObserver?.disconnect();
385
+ this.bodyResizeObserver = null;
386
+ this.observedBodyInner = null;
387
+ await super.disconnectedCallback();
388
+ }
389
+
242
390
  private renderMcpBadges(call: IHarnessToolCall): TemplateResult {
243
391
  if (!call.mcp) return html``;
244
392
  const annotations = call.mcp.annotations;
@@ -0,0 +1,168 @@
1
+ import {
2
+ DeesElement,
3
+ html,
4
+ css,
5
+ customElement,
6
+ type TemplateResult,
7
+ property,
8
+ cssManager,
9
+ } from '@design.estate/dees-element';
10
+ import { themeDefaultStyles } from '../../00theme.js';
11
+ import type { IHarnessToolCall } from '../interfaces.js';
12
+ import { DeesHarnessToolRegistry, parseMcpName } from '../toolregistry.js';
13
+ import '../../00group-utility/dees-icon/dees-icon.js';
14
+
15
+ declare global {
16
+ interface HTMLElementTagNameMap {
17
+ 'dees-harness-tool-fullscreen': DeesHarnessToolFullscreen;
18
+ }
19
+ }
20
+
21
+ /**
22
+ * Fullscreen view of one tool card, appended to document.body by
23
+ * DeesHarnessToolCard so it escapes every containment context
24
+ * (content-visibility, transforms) that would trap a position: fixed layer
25
+ * inside the transcript. Emits `tool-fullscreen-close` on Escape, backdrop
26
+ * press, or the minimize button; the owning card removes it and flashes.
27
+ */
28
+ @customElement('dees-harness-tool-fullscreen')
29
+ export class DeesHarnessToolFullscreen extends DeesElement {
30
+ @property({ attribute: false })
31
+ accessor call: IHarnessToolCall = { id: '', name: '', status: 'pending' };
32
+
33
+ @property({ attribute: false })
34
+ accessor toolRegistry: DeesHarnessToolRegistry = DeesHarnessToolRegistry.default;
35
+
36
+ public static styles = [
37
+ themeDefaultStyles,
38
+ cssManager.defaultStyles,
39
+ css`
40
+ :host {
41
+ position: fixed;
42
+ inset: 0;
43
+ z-index: 4000;
44
+ display: grid;
45
+ place-items: center;
46
+ padding: 4vh 3vw;
47
+ background: color-mix(in srgb, var(--dees-color-bg-primary) 55%, transparent);
48
+ backdrop-filter: blur(6px);
49
+ font-family: var(--dees-font-family);
50
+ }
51
+
52
+ .panel {
53
+ display: flex;
54
+ flex-direction: column;
55
+ width: min(1200px, 100%);
56
+ height: 100%;
57
+ min-height: 0;
58
+ border: 1px solid var(--dees-color-border-default);
59
+ border-radius: var(--dees-radius-lg);
60
+ background: var(--dees-color-bg-primary);
61
+ box-shadow: 0 24px 64px rgba(0, 0, 0, 0.4);
62
+ overflow: hidden;
63
+ }
64
+
65
+ .panelHeader {
66
+ display: flex;
67
+ flex: 0 0 auto;
68
+ align-items: center;
69
+ gap: var(--dees-spacing-sm);
70
+ padding: var(--dees-spacing-sm) var(--dees-spacing-md);
71
+ border-bottom: 1px solid var(--dees-color-border-subtle);
72
+ color: var(--dees-color-text-primary);
73
+ }
74
+
75
+ .panelTitle {
76
+ min-width: 0;
77
+ overflow: hidden;
78
+ text-overflow: ellipsis;
79
+ white-space: nowrap;
80
+ font-size: var(--dees-font-control-size-sm, 12px);
81
+ font-weight: 650;
82
+ }
83
+
84
+ .panelClose {
85
+ display: grid;
86
+ place-items: center;
87
+ margin-left: auto;
88
+ padding: 6px;
89
+ border: 0;
90
+ border-radius: var(--dees-radius-md);
91
+ background: transparent;
92
+ color: var(--dees-color-text-secondary);
93
+ cursor: pointer;
94
+ }
95
+
96
+ .panelClose:hover {
97
+ background: var(--dees-color-hover);
98
+ color: var(--dees-color-text-primary);
99
+ }
100
+
101
+ .panelBody {
102
+ flex: 1;
103
+ min-height: 0;
104
+ overflow: auto;
105
+ padding: var(--dees-spacing-md);
106
+ scrollbar-width: thin;
107
+ scrollbar-color: var(--dees-color-scrollbar-thumb) transparent;
108
+ }
109
+
110
+ .panelBody dees-harness-tool-card {
111
+ display: block;
112
+ }
113
+ `,
114
+ ];
115
+
116
+ public render(): TemplateResult {
117
+ const parsed = this.call.mcp ? undefined : parseMcpName(this.call.name);
118
+ const enriched = parsed
119
+ ? { ...this.call, mcp: { serverName: parsed.serverName, toolName: parsed.toolName } }
120
+ : this.call;
121
+ const descriptor = this.toolRegistry.resolve(enriched);
122
+ const label = typeof descriptor.label === 'function' ? descriptor.label(enriched) : descriptor.label;
123
+ const subtitle = descriptor.subtitle?.(enriched) ?? '';
124
+ return html`
125
+ <div class="panel" @click=${(event: Event) => event.stopPropagation()}>
126
+ <header class="panelHeader">
127
+ <dees-icon .icon=${descriptor.icon} iconSize="15"></dees-icon>
128
+ <span class="panelTitle">${label}${subtitle ? ` · ${subtitle}` : ''}</span>
129
+ <button class="panelClose" type="button" @click=${this.emitClose}>
130
+ <dees-icon icon="lucide:Minimize2" iconSize="15"></dees-icon>
131
+ </button>
132
+ </header>
133
+ <div class="panelBody">
134
+ <dees-harness-tool-card
135
+ .call=${this.call}
136
+ .toolRegistry=${this.toolRegistry}
137
+ .expanded=${true}
138
+ .outputExpanded=${true}
139
+ uncapped
140
+ ></dees-harness-tool-card>
141
+ </div>
142
+ </div>
143
+ `;
144
+ }
145
+
146
+ public firstUpdated(): void {
147
+ window.addEventListener('keydown', this.handleKeydown);
148
+ this.addEventListener('click', (event) => {
149
+ if (event.target === this) this.emitClose();
150
+ });
151
+ }
152
+
153
+ private readonly handleKeydown = (event: KeyboardEvent): void => {
154
+ if (event.key === 'Escape') {
155
+ event.preventDefault();
156
+ this.emitClose();
157
+ }
158
+ };
159
+
160
+ private readonly emitClose = (): void => {
161
+ this.dispatchEvent(new CustomEvent('tool-fullscreen-close', { bubbles: false }));
162
+ };
163
+
164
+ public async disconnectedCallback(): Promise<void> {
165
+ window.removeEventListener('keydown', this.handleKeydown);
166
+ await super.disconnectedCallback();
167
+ }
168
+ }