@design.estate/dees-catalog 12.1.0 → 12.2.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 (28) hide show
  1. package/dist_bundle/bundle.js +1675 -1221
  2. package/dist_bundle/bundle.js.map +1 -1
  3. package/dist_ts_web/00_commitinfo_data.js +3 -3
  4. package/dist_ts_web/elements/00group-harness/dees-harness-session-sidebar/dees-harness-session-sidebar.js +3 -107
  5. package/dist_ts_web/elements/00group-harness/dees-harness-sidebar/dees-harness-sidebar.d.ts +70 -0
  6. package/dist_ts_web/elements/00group-harness/dees-harness-sidebar/dees-harness-sidebar.demo.d.ts +1 -0
  7. package/dist_ts_web/elements/00group-harness/dees-harness-sidebar/dees-harness-sidebar.demo.js +78 -0
  8. package/dist_ts_web/elements/00group-harness/dees-harness-sidebar/dees-harness-sidebar.js +261 -0
  9. package/dist_ts_web/elements/00group-harness/dees-harness-sidebar/index.d.ts +1 -0
  10. package/dist_ts_web/elements/00group-harness/dees-harness-sidebar/index.js +2 -0
  11. package/dist_ts_web/elements/00group-harness/dees-terminal-view/dees-terminal-view.d.ts +79 -6
  12. package/dist_ts_web/elements/00group-harness/dees-terminal-view/dees-terminal-view.demo.js +38 -7
  13. package/dist_ts_web/elements/00group-harness/dees-terminal-view/dees-terminal-view.js +211 -20
  14. package/dist_ts_web/elements/00group-harness/harness.styles.d.ts +10 -0
  15. package/dist_ts_web/elements/00group-harness/harness.styles.js +116 -1
  16. package/dist_ts_web/elements/00group-harness/index.d.ts +1 -0
  17. package/dist_ts_web/elements/00group-harness/index.js +2 -1
  18. package/package.json +3 -3
  19. package/readme.md +46 -0
  20. package/ts_web/00_commitinfo_data.ts +3 -3
  21. package/ts_web/elements/00group-harness/dees-harness-session-sidebar/dees-harness-session-sidebar.ts +2 -106
  22. package/ts_web/elements/00group-harness/dees-harness-sidebar/dees-harness-sidebar.demo.ts +87 -0
  23. package/ts_web/elements/00group-harness/dees-harness-sidebar/dees-harness-sidebar.ts +245 -0
  24. package/ts_web/elements/00group-harness/dees-harness-sidebar/index.ts +1 -0
  25. package/ts_web/elements/00group-harness/dees-terminal-view/dees-terminal-view.demo.ts +48 -12
  26. package/ts_web/elements/00group-harness/dees-terminal-view/dees-terminal-view.ts +241 -20
  27. package/ts_web/elements/00group-harness/harness.styles.ts +116 -0
  28. package/ts_web/elements/00group-harness/index.ts +1 -0
@@ -0,0 +1,261 @@
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 { demoFunc } from './dees-harness-sidebar.demo.js';
36
+ import { DeesElement, html, css, customElement, property, state, cssManager, directives, } from '@design.estate/dees-element';
37
+ import { themeDefaultStyles } from '../../00theme.js';
38
+ import { harnessSidebarStyles } from '../harness.styles.js';
39
+ import '../../00group-utility/dees-icon/dees-icon.js';
40
+ const { repeat } = directives;
41
+ /** Every section body arrives through `slot="section-<key>"`. */
42
+ const sectionSlotPrefix = 'section-';
43
+ const sectionSlotName = (keyArg) => `${sectionSlotPrefix}${keyArg}`;
44
+ const sameKeys = (leftArg, rightArg) => leftArg.size === rightArg.size && [...leftArg].every((keyArg) => rightArg.has(keyArg));
45
+ /**
46
+ * Sidebar shell: the panel chrome of `dees-harness-session-sidebar` with
47
+ * host-declared sections instead of session-specific ones. It owns the
48
+ * single-open accordion and nothing else — every section body is slotted
49
+ * content, so the host keeps its own state, updates, and presentation. A
50
+ * section whose slot holds no content is not offered at all, and the host
51
+ * mounts and unmounts the element to show or hide the panel.
52
+ */
53
+ let DeesHarnessSidebar = (() => {
54
+ let _classDecorators = [customElement('dees-harness-sidebar')];
55
+ let _classDescriptor;
56
+ let _classExtraInitializers = [];
57
+ let _classThis;
58
+ let _classSuper = DeesElement;
59
+ let _sections_decorators;
60
+ let _sections_initializers = [];
61
+ let _sections_extraInitializers = [];
62
+ let _expandedSection_decorators;
63
+ let _expandedSection_initializers = [];
64
+ let _expandedSection_extraInitializers = [];
65
+ let _disabled_decorators;
66
+ let _disabled_initializers = [];
67
+ let _disabled_extraInitializers = [];
68
+ let _populatedSections_decorators;
69
+ let _populatedSections_initializers = [];
70
+ let _populatedSections_extraInitializers = [];
71
+ var DeesHarnessSidebar = class extends _classSuper {
72
+ static { _classThis = this; }
73
+ static {
74
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
75
+ _sections_decorators = [property({ attribute: false })];
76
+ _expandedSection_decorators = [property()];
77
+ _disabled_decorators = [property({ type: Boolean })];
78
+ _populatedSections_decorators = [state()];
79
+ __esDecorate(this, null, _sections_decorators, { kind: "accessor", name: "sections", static: false, private: false, access: { has: obj => "sections" in obj, get: obj => obj.sections, set: (obj, value) => { obj.sections = value; } }, metadata: _metadata }, _sections_initializers, _sections_extraInitializers);
80
+ __esDecorate(this, null, _expandedSection_decorators, { kind: "accessor", name: "expandedSection", static: false, private: false, access: { has: obj => "expandedSection" in obj, get: obj => obj.expandedSection, set: (obj, value) => { obj.expandedSection = value; } }, metadata: _metadata }, _expandedSection_initializers, _expandedSection_extraInitializers);
81
+ __esDecorate(this, null, _disabled_decorators, { kind: "accessor", name: "disabled", static: false, private: false, access: { has: obj => "disabled" in obj, get: obj => obj.disabled, set: (obj, value) => { obj.disabled = value; } }, metadata: _metadata }, _disabled_initializers, _disabled_extraInitializers);
82
+ __esDecorate(this, null, _populatedSections_decorators, { kind: "accessor", name: "populatedSections", static: false, private: false, access: { has: obj => "populatedSections" in obj, get: obj => obj.populatedSections, set: (obj, value) => { obj.populatedSections = value; } }, metadata: _metadata }, _populatedSections_initializers, _populatedSections_extraInitializers);
83
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
84
+ DeesHarnessSidebar = _classThis = _classDescriptor.value;
85
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
86
+ }
87
+ static demo = demoFunc;
88
+ static demoGroups = ['Agent Chat'];
89
+ #sections_accessor_storage = __runInitializers(this, _sections_initializers, []);
90
+ /** Sections in render order; each one's body is slotted as `section-<key>`. */
91
+ get sections() { return this.#sections_accessor_storage; }
92
+ set sections(value) { this.#sections_accessor_storage = value; }
93
+ #expandedSection_accessor_storage = (__runInitializers(this, _sections_extraInitializers), __runInitializers(this, _expandedSection_initializers, undefined));
94
+ /**
95
+ * Key of the open section. Host-settable and kept in sync with what the user
96
+ * opens; it defaults to the first section that has content, and `undefined`
97
+ * leaves the panel collapsed once a section has been opened. A declared
98
+ * section without content renders collapsed and opens as soon as its content
99
+ * arrives, so the key survives an asynchronous body.
100
+ */
101
+ get expandedSection() { return this.#expandedSection_accessor_storage; }
102
+ set expandedSection(value) { this.#expandedSection_accessor_storage = value; }
103
+ #disabled_accessor_storage = (__runInitializers(this, _expandedSection_extraInitializers), __runInitializers(this, _disabled_initializers, false));
104
+ /** Freezes the accordion: headings stay visible but cannot be toggled. */
105
+ get disabled() { return this.#disabled_accessor_storage; }
106
+ set disabled(value) { this.#disabled_accessor_storage = value; }
107
+ #populatedSections_accessor_storage = (__runInitializers(this, _disabled_extraInitializers), __runInitializers(this, _populatedSections_initializers, new Set()));
108
+ get populatedSections() { return this.#populatedSections_accessor_storage; }
109
+ set populatedSections(value) { this.#populatedSections_accessor_storage = value; }
110
+ sectionStateInitialized = (__runInitializers(this, _populatedSections_extraInitializers), false);
111
+ static styles = [
112
+ themeDefaultStyles,
113
+ cssManager.defaultStyles,
114
+ harnessSidebarStyles,
115
+ css `
116
+ :host {
117
+ background: var(--dees-color-bg-canvas);
118
+ }
119
+
120
+ /* A section without content is not offered at all: the hidden attribute
121
+ alone would still be laid out, because the shared chrome's display
122
+ declaration wins over the user-agent rule. The divider then follows the
123
+ last visible section instead of the last declared one. */
124
+ .section[hidden] {
125
+ display: none;
126
+ }
127
+
128
+ .section:not(:has(~ .section:not([hidden]))) {
129
+ border-bottom: 0;
130
+ }
131
+
132
+ /* Section bodies stay as compact as the session panel's; slotted content
133
+ inherits this and can override it per element. */
134
+ .sectionContent {
135
+ font-size: var(--dees-font-caption1-size, 11px);
136
+ }
137
+
138
+ .sectionHeading:disabled {
139
+ opacity: 0.62;
140
+ cursor: default;
141
+ }
142
+
143
+ .sectionHeading:disabled:hover {
144
+ background: var(--dees-color-bg-canvas);
145
+ }
146
+ `,
147
+ ];
148
+ willUpdate(changedPropertiesArg) {
149
+ if (changedPropertiesArg.has('expandedSection'))
150
+ this.sectionStateInitialized = true;
151
+ const populated = this.populatedSectionsInOrder;
152
+ // Slot assignment is only known after the first render, so an empty panel
153
+ // never overrides a host-set section.
154
+ if (populated.length === 0)
155
+ return;
156
+ if (!this.sectionStateInitialized) {
157
+ this.expandedSection = populated[0].key;
158
+ this.sectionStateInitialized = true;
159
+ return;
160
+ }
161
+ const open = this.expandedSection;
162
+ // A section the host withdrew from `sections` can never open again, so the
163
+ // panel moves on. A declared section whose content is momentarily missing
164
+ // keeps the key and renders collapsed until the content is back, so an
165
+ // asynchronous body never costs the host or the user their choice.
166
+ if (open !== undefined && !this.sections.some((section) => section.key === open)) {
167
+ this.expandedSection = populated[0].key;
168
+ }
169
+ }
170
+ render() {
171
+ return html `
172
+ <div class="sidebar">
173
+ ${repeat(this.sections, (section) => section.key, (section) => this.renderSection(section))}
174
+ </div>
175
+ `;
176
+ }
177
+ /** Declared sections that currently have slotted content, in render order. */
178
+ get populatedSectionsInOrder() {
179
+ return this.sections.filter((section) => this.populatedSections.has(section.key));
180
+ }
181
+ /**
182
+ * The heading row is the element's own; the body is the host's, and it stays
183
+ * mounted while collapsed so it keeps its state and scroll position.
184
+ */
185
+ renderSection(sectionArg) {
186
+ const populated = this.populatedSections.has(sectionArg.key);
187
+ const expanded = populated && this.expandedSection === sectionArg.key;
188
+ const contentId = `harness-sidebar-section-${sectionArg.key}`;
189
+ return html `
190
+ <section
191
+ class=${`section ${expanded ? 'expanded' : ''}`}
192
+ data-section=${sectionArg.key}
193
+ ?hidden=${!populated}
194
+ >
195
+ ${populated ? html `
196
+ <button
197
+ class="sectionHeading"
198
+ type="button"
199
+ aria-expanded=${String(expanded)}
200
+ aria-controls=${contentId}
201
+ ?disabled=${this.disabled}
202
+ @click=${() => this.toggleSection(sectionArg.key)}
203
+ >
204
+ <dees-icon
205
+ icon=${expanded ? 'lucide:ChevronDown' : 'lucide:ChevronRight'}
206
+ iconSize="13"
207
+ ></dees-icon>
208
+ ${sectionArg.icon ? html `<dees-icon icon=${sectionArg.icon} iconSize="13"></dees-icon>` : ''}
209
+ <span class="sectionHeadingText">${sectionArg.label}</span>
210
+ ${sectionArg.badge ? html `<span class="sectionSummary">${sectionArg.badge}</span>` : ''}
211
+ </button>
212
+ ` : ''}
213
+ <div
214
+ id=${contentId}
215
+ class="sectionContent"
216
+ role="region"
217
+ aria-label=${sectionArg.label}
218
+ aria-hidden=${String(!expanded)}
219
+ ?hidden=${!expanded}
220
+ >
221
+ <slot
222
+ name=${sectionSlotName(sectionArg.key)}
223
+ @slotchange=${this.handleSectionSlotChange}
224
+ ></slot>
225
+ </div>
226
+ </section>
227
+ `;
228
+ }
229
+ /**
230
+ * A section is offered only while its slot holds content, so the panel
231
+ * recomputes from its live slots whenever the host fills or empties one.
232
+ */
233
+ handleSectionSlotChange = () => {
234
+ const populated = new Set();
235
+ const sectionSlots = this.renderRoot.querySelectorAll(`slot[name^="${sectionSlotPrefix}"]`);
236
+ for (const slot of sectionSlots) {
237
+ if (slot.assignedElements({ flatten: true }).length > 0) {
238
+ populated.add(slot.name.slice(sectionSlotPrefix.length));
239
+ }
240
+ }
241
+ if (sameKeys(this.populatedSections, populated))
242
+ return;
243
+ this.populatedSections = populated;
244
+ };
245
+ toggleSection(keyArg) {
246
+ if (this.disabled)
247
+ return;
248
+ const expandedSection = this.expandedSection === keyArg ? undefined : keyArg;
249
+ this.sectionStateInitialized = true;
250
+ this.expandedSection = expandedSection;
251
+ const detail = { expandedSection };
252
+ this.dispatchEvent(new CustomEvent('harness-sidebar-section-change', { detail, bubbles: true, composed: true }));
253
+ }
254
+ static {
255
+ __runInitializers(_classThis, _classExtraInitializers);
256
+ }
257
+ };
258
+ return DeesHarnessSidebar = _classThis;
259
+ })();
260
+ export { DeesHarnessSidebar };
261
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVlcy1oYXJuZXNzLXNpZGViYXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi90c193ZWIvZWxlbWVudHMvMDBncm91cC1oYXJuZXNzL2RlZXMtaGFybmVzcy1zaWRlYmFyL2RlZXMtaGFybmVzcy1zaWRlYmFyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSxPQUFPLEVBQUUsUUFBUSxFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFDMUQsT0FBTyxFQUNMLFdBQVcsRUFDWCxJQUFJLEVBQ0osR0FBRyxFQUNILGFBQWEsRUFFYixRQUFRLEVBQ1IsS0FBSyxFQUNMLFVBQVUsRUFDVixVQUFVLEdBQ1gsTUFBTSw2QkFBNkIsQ0FBQztBQUNyQyxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUN0RCxPQUFPLEVBQUUsb0JBQW9CLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUM1RCxPQUFPLDhDQUE4QyxDQUFDO0FBRXRELE1BQU0sRUFBRSxNQUFNLEVBQUUsR0FBRyxVQUFVLENBQUM7QUFFOUIsaUVBQWlFO0FBQ2pFLE1BQU0saUJBQWlCLEdBQUcsVUFBVSxDQUFDO0FBQ3JDLE1BQU0sZUFBZSxHQUFHLENBQUMsTUFBYyxFQUFVLEVBQUUsQ0FBQyxHQUFHLGlCQUFpQixHQUFHLE1BQU0sRUFBRSxDQUFDO0FBRXBGLE1BQU0sUUFBUSxHQUFHLENBQUMsT0FBNEIsRUFBRSxRQUE2QixFQUFXLEVBQUUsQ0FDeEYsT0FBTyxDQUFDLElBQUksS0FBSyxRQUFRLENBQUMsSUFBSSxJQUFJLENBQUMsR0FBRyxPQUFPLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxNQUFNLEVBQUUsRUFBRSxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQztBQStCekY7Ozs7Ozs7R0FPRztJQUVVLGtCQUFrQjs0QkFEOUIsYUFBYSxDQUFD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
@@ -0,0 +1 @@
1
+ export * from './dees-harness-sidebar.js';
@@ -0,0 +1,2 @@
1
+ export * from './dees-harness-sidebar.js';
2
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi90c193ZWIvZWxlbWVudHMvMDBncm91cC1oYXJuZXNzL2RlZXMtaGFybmVzcy1zaWRlYmFyL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLGNBQWMsMkJBQTJCLENBQUMifQ==
@@ -8,7 +8,10 @@ declare global {
8
8
  export interface ITerminalInputDetail {
9
9
  data: string;
10
10
  }
11
- /** Detail of `terminal-resize`, fired after the terminal refits its container. */
11
+ /**
12
+ * A terminal grid size. It is the detail of `terminal-resize`, fired after the
13
+ * terminal refits its container, and the size `restore()` paints a snapshot at.
14
+ */
12
15
  export interface ITerminalResizeDetail {
13
16
  rows: number;
14
17
  cols: number;
@@ -35,9 +38,10 @@ export declare const maxPendingWriteBytes: number;
35
38
  * every refit so the host can negotiate a PTY size. Output that arrives while
36
39
  * no terminal is live is buffered up to `maxPendingWriteBytes` and replayed in
37
40
  * order on the next connection, so a replay may start before the element is
38
- * ready and survives being detached. xterm loads through DeesServiceLibLoader;
39
- * CSP-locked hosts can pre-register bundled modules with
40
- * `provideXtermModules()` to avoid the CDN.
41
+ * ready and survives being detached. `restore()` paints a serialized snapshot
42
+ * invisibly at the grid it was taken at, then fits it to the container. xterm
43
+ * loads through DeesServiceLibLoader; CSP-locked hosts can pre-register
44
+ * bundled modules with `provideXtermModules()` to avoid the CDN.
41
45
  */
42
46
  export declare class DeesTerminalView extends DeesElement {
43
47
  static demo: () => TemplateResult<1>;
@@ -50,10 +54,16 @@ export declare class DeesTerminalView extends DeesElement {
50
54
  private resizeObserver;
51
55
  private inputDisposable;
52
56
  private lastReportedSize;
57
+ private pendingRestore;
53
58
  private pendingWrites;
54
59
  private pendingWriteBytes;
55
60
  private pendingWritesDropped;
56
61
  private setupPromise;
62
+ /**
63
+ * The restore handed to the live terminal whose snapshot is not parsed yet. While it is set the
64
+ * grid belongs to the snapshot: the terminal stays invisible and nothing refits it.
65
+ */
66
+ private activeRestore;
57
67
  /**
58
68
  * Set only by an explicit `focus()` that arrived before the terminal existed. Nothing else sets
59
69
  * it, so a re-render, a write, or a resize can never move the caret into this element.
@@ -75,8 +85,48 @@ export declare class DeesTerminalView extends DeesElement {
75
85
  connectedCallback(): Promise<void>;
76
86
  /** Feed remote output into the terminal; safe to call before setup finishes. */
77
87
  write(dataArg: string | Uint8Array): void;
78
- /** Reset the screen and scrollback, e.g. before a full replay. */
88
+ /**
89
+ * Reset the screen and scrollback, e.g. before a full replay. Before the terminal exists it also
90
+ * replaces a buffered `restore()`, whose promise then resolves as superseded.
91
+ */
79
92
  clear(): void;
93
+ /**
94
+ * Replace the terminal state with a serialized snapshot, e.g. from xterm's serialize addon, and
95
+ * show it only once it is complete. A snapshot is exact only in a grid of the size it was
96
+ * serialized at, so the terminal is fully reset (screen, scrollback, modes, alternate buffer),
97
+ * set to `sizeArg`, fed `dataArg` while invisible, then fitted to its container and revealed.
98
+ * `terminal-resize` fires at the end when the fitted grid differs from `sizeArg`, which is the
99
+ * host's cue to resize the PTY so the application redraws at the new size. Until then
100
+ * `currentSize` reports `sizeArg`, so the closing event, or this promise, is what a host
101
+ * negotiates a PTY size on.
102
+ *
103
+ * The restore is part of the output stream: writes issued after it are applied after the
104
+ * snapshot, in order, at the fitted size. Like `write()` it is safe before the terminal exists
105
+ * and while the element is detached; a buffered restore replaces everything buffered before
106
+ * it. The terminal keeps its focus and keeps forwarding input while the restore runs.
107
+ *
108
+ * The promise resolves once the snapshot is on screen, and also, without revealing anything,
109
+ * when a newer `restore()` supersedes this one or `clear()` replaces it while it is buffered.
110
+ * It rejects, without ever surfacing as an unhandled rejection, when the element is
111
+ * disconnected while the snapshot is being parsed (the terminal is disposed with it, as with
112
+ * any live output), when output buffered after it pushes it out of `maxPendingWriteBytes`, and
113
+ * when xterm refuses the data (`terminal-error` carries the same error). Buffered output is
114
+ * dropped oldest first and the restore is the oldest entry, so a buffered snapshot that alone
115
+ * exceeds the bound is dropped by the next buffered write of any size. An invalid size is a
116
+ * programming error: the promise rejects with a `RangeError` and nothing changes; unlike the
117
+ * reasons above this one is not pre-handled, so a `restore()` nobody observes reports it as an
118
+ * unhandled rejection.
119
+ *
120
+ * There is a fourth outcome: a restore that is still buffered when the element is disconnected
121
+ * stays pending, because the snapshot survives the move like buffered output and is applied on
122
+ * the next connection. A host that discards the element instead of reattaching it must not await
123
+ * that promise.
124
+ *
125
+ * @param dataArg The serialized terminal state.
126
+ * @param sizeArg The grid the snapshot was serialized at: integer `cols` in [2, 65535] and
127
+ * `rows` in [1, 65535].
128
+ */
129
+ restore(dataArg: string | Uint8Array, sizeArg: ITerminalResizeDetail): Promise<void>;
80
130
  /**
81
131
  * Moves the caret into the terminal's input surface, so the next keystroke goes to the pty
82
132
  * rather than to whatever the page focused last. This is the call a host makes when the user
@@ -94,14 +144,37 @@ export declare class DeesTerminalView extends DeesElement {
94
144
  focus(optionsArg?: FocusOptions): void;
95
145
  /** True while an explicit `focus()` is waiting for the terminal to come up. */
96
146
  get focusPending(): boolean;
97
- /** Current grid size, or undefined while no terminal is live. */
147
+ /**
148
+ * Current grid size, or undefined while no terminal is live. While a snapshot is being parsed
149
+ * this is the grid the snapshot was serialized at; the closing `terminal-resize`, or the promise
150
+ * `restore()` returned, is the grid to negotiate a PTY size on.
151
+ */
98
152
  get currentSize(): ITerminalResizeDetail | undefined;
99
153
  private createReadyPromise;
100
154
  private bufferWrite;
155
+ /** Hands the buffered output over and leaves the buffer empty. */
156
+ private takePendingOutput;
157
+ /** Empties the buffer ahead of a reset; a buffered restore is superseded by it. */
158
+ private discardPendingOutput;
101
159
  private flushPendingWrites;
160
+ /**
161
+ * Queues a restore into xterm's write stream. Resetting and resizing happen in the callback of
162
+ * the reset, so they take effect exactly at the restore's position in the stream, and the
163
+ * restore ends in the callback of its snapshot, before xterm parses the next chunk.
164
+ */
165
+ private startRestore;
166
+ private applyRestoreSize;
167
+ private finishRestore;
168
+ /**
169
+ * Hands the grid back to the container: one refit, then the terminal is revealed. The refit
170
+ * dispatches `terminal-resize` synchronously, and a restore its listener starts keeps the
171
+ * terminal hidden.
172
+ */
173
+ private endRestore;
102
174
  /** xterm throws at its own flow-control watermark; that has to reach the host. */
103
175
  private writeToTerminal;
104
176
  private reportError;
177
+ private get terminalHost();
105
178
  private ensureTerminal;
106
179
  private setupTerminal;
107
180
  private createTerminal;
@@ -1,8 +1,34 @@
1
1
  import { html } from '@design.estate/dees-element';
2
2
  import { demoPage } from '../../../demos/templates.js';
3
+ // A serialized terminal state as a host would receive it on reattach, e.g. from xterm's
4
+ // serialize addon: styled rows at the grid the PTY had when it was taken.
5
+ const sampleSnapshotSize = { cols: 64, rows: 12 };
6
+ const samplePrompt = '\x1b[1;32m➜\x1b[0m \x1b[36mdees-catalog\x1b[0m ';
7
+ const sampleSnapshot = [
8
+ `${samplePrompt}pnpm build`,
9
+ '\x1b[2mtsbuild \x1b[0m compiled the web folder',
10
+ '\x1b[2mtsbundle\x1b[0m wrote dist_bundle/bundle.js',
11
+ '',
12
+ `${samplePrompt}pnpm test`,
13
+ ' \x1b[32m✔\x1b[0m restore() sizes, parses, fits and reveals',
14
+ ' \x1b[32m✔\x1b[0m writes during a restore follow the snapshot',
15
+ ' \x1b[32m✔\x1b[0m a newer restore supersedes an older one',
16
+ '',
17
+ samplePrompt,
18
+ ].join('\r\n');
19
+ const restoreSample = (event) => {
20
+ const view = event.currentTarget
21
+ .closest('.terminalDemoStack')
22
+ ?.querySelector('dees-terminal-view');
23
+ if (!view)
24
+ return;
25
+ // A click handler's promise is dropped by the DOM, so both outcomes are settled here: leaving
26
+ // the demo page mid-restore rejects, and nobody would be left to observe it.
27
+ view.restore(sampleSnapshot, sampleSnapshotSize).then(() => view.focus(), () => undefined);
28
+ };
3
29
  // A loopback demo: keystrokes echo straight back into the terminal so the
4
30
  // component is explorable without a live PTY behind it.
5
- export const demoFunc = () => demoPage('Terminal', 'A terminal surface with local keyboard echo. Type to try the input and resize behavior.', html `
31
+ export const demoFunc = () => demoPage('Terminal', 'A terminal surface with local keyboard echo. Type to try the input and resize behavior, or restore a serialized snapshot: it is painted invisibly at its own grid and appears fitted, in one step.', html `
6
32
  <style>
7
33
  .terminalDemo {
8
34
  height: 380px;
@@ -11,17 +37,22 @@ export const demoFunc = () => demoPage('Terminal', 'A terminal surface with loca
11
37
  overflow: hidden;
12
38
  }
13
39
  </style>
14
- <div class="terminalDemo">
15
- <dees-terminal-view
16
- @terminal-input=${(event) => {
40
+ <div class="terminalDemoStack dees-demo-stack">
41
+ <div class="dees-demo-row">
42
+ <dees-button @clicked=${restoreSample}>Restore a snapshot</dees-button>
43
+ </div>
44
+ <div class="terminalDemo">
45
+ <dees-terminal-view
46
+ @terminal-input=${(event) => {
17
47
  const view = event.currentTarget;
18
48
  const data = event.detail.data;
19
49
  view.write(data === '\r' ? '\r\n' : data);
20
50
  }}
21
- @terminal-resize=${(event) => {
51
+ @terminal-resize=${(event) => {
22
52
  console.log('terminal size', event.detail);
23
53
  }}
24
- ></dees-terminal-view>
54
+ ></dees-terminal-view>
55
+ </div>
25
56
  </div>
26
57
  `);
27
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVlcy10ZXJtaW5hbC12aWV3LmRlbW8uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi90c193ZWIvZWxlbWVudHMvMDBncm91cC1oYXJuZXNzL2RlZXMtdGVybWluYWwtdmlldy9kZWVzLXRlcm1pbmFsLXZpZXcuZGVtby50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsSUFBSSxFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUFDbkQsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLDZCQUE2QixDQUFDO0FBRXZELDBFQUEwRTtBQUMxRSx3REFBd0Q7QUFDeEQsTUFBTSxDQUFDLE1BQU0sUUFBUSxHQUFHLEdBQUcsRUFBRSxDQUFDLFFBQVEsQ0FBQyxVQUFVLEVBQUUseUZBQXlGLEVBQUUsSUFBSSxDQUFBOzs7Ozs7Ozs7Ozt3QkFXMUgsQ0FBQyxLQUFvQyxFQUFFLEVBQUU7SUFDekQsTUFBTSxJQUFJLEdBQUcsS0FBSyxDQUFDLGFBQTRELENBQUM7SUFDaEYsTUFBTSxJQUFJLEdBQUcsS0FBSyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUM7SUFDL0IsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLEtBQUssSUFBSSxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDO0FBQzVDLENBQUM7eUJBQ2tCLENBQUMsS0FBa0QsRUFBRSxFQUFFO0lBQ3hFLE9BQU8sQ0FBQyxHQUFHLENBQUMsZUFBZSxFQUFFLEtBQUssQ0FBQyxNQUFNLENBQUMsQ0FBQztBQUM3QyxDQUFDOzs7Q0FHTixDQUFDLENBQUMifQ==
58
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVlcy10ZXJtaW5hbC12aWV3LmRlbW8uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi90c193ZWIvZWxlbWVudHMvMDBncm91cC1oYXJuZXNzL2RlZXMtdGVybWluYWwtdmlldy9kZWVzLXRlcm1pbmFsLXZpZXcuZGVtby50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsSUFBSSxFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUFDbkQsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLDZCQUE2QixDQUFDO0FBR3ZELHdGQUF3RjtBQUN4RiwwRUFBMEU7QUFDMUUsTUFBTSxrQkFBa0IsR0FBMEIsRUFBRSxJQUFJLEVBQUUsRUFBRSxFQUFFLElBQUksRUFBRSxFQUFFLEVBQUUsQ0FBQztBQUN6RSxNQUFNLFlBQVksR0FBRyxpREFBaUQsQ0FBQztBQUN2RSxNQUFNLGNBQWMsR0FBRztJQUNyQixHQUFHLFlBQVksWUFBWTtJQUMzQixnREFBZ0Q7SUFDaEQsb0RBQW9EO0lBQ3BELEVBQUU7SUFDRixHQUFHLFlBQVksV0FBVztJQUMxQiw2REFBNkQ7SUFDN0QsK0RBQStEO0lBQy9ELDJEQUEyRDtJQUMzRCxFQUFFO0lBQ0YsWUFBWTtDQUNiLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDO0FBRWYsTUFBTSxhQUFhLEdBQUcsQ0FBQyxLQUFZLEVBQVEsRUFBRTtJQUMzQyxNQUFNLElBQUksR0FBSSxLQUFLLENBQUMsYUFBNkI7U0FDOUMsT0FBTyxDQUFDLG9CQUFvQixDQUFDO1FBQzlCLEVBQUUsYUFBYSxDQUFDLG9CQUFvQixDQUFDLENBQUM7SUFDeEMsSUFBSSxDQUFDLElBQUk7UUFBRSxPQUFPO0lBQ2xCLDhGQUE4RjtJQUM5Riw2RUFBNkU7SUFDN0UsSUFBSSxDQUFDLE9BQU8sQ0FBQyxjQUFjLEVBQUUsa0JBQWtCLENBQUMsQ0FBQyxJQUFJLENBQ25ELEdBQUcsRUFBRSxDQUFDLElBQUksQ0FBQyxLQUFLLEVBQUUsRUFDbEIsR0FBRyxFQUFFLENBQUMsU0FBUyxDQUNoQixDQUFDO0FBQ0osQ0FBQyxDQUFDO0FBRUYsMEVBQTBFO0FBQzFFLHdEQUF3RDtBQUN4RCxNQUFNLENBQUMsTUFBTSxRQUFRLEdBQUcsR0FBRyxFQUFFLENBQUMsUUFBUSxDQUFDLFVBQVUsRUFBRSxvTUFBb00sRUFBRSxJQUFJLENBQUE7Ozs7Ozs7Ozs7OzhCQVcvTixhQUFhOzs7OzBCQUlqQixDQUFDLEtBQW9DLEVBQUUsRUFBRTtJQUN6RCxNQUFNLElBQUksR0FBRyxLQUFLLENBQUMsYUFBNEQsQ0FBQztJQUNoRixNQUFNLElBQUksR0FBRyxLQUFLLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQztJQUMvQixJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksS0FBSyxJQUFJLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUM7QUFDNUMsQ0FBQzsyQkFDa0IsQ0FBQyxLQUF5QyxFQUFFLEVBQUU7SUFDL0QsT0FBTyxDQUFDLEdBQUcsQ0FBQyxlQUFlLEVBQUUsS0FBSyxDQUFDLE1BQU0sQ0FBQyxDQUFDO0FBQzdDLENBQUM7Ozs7Q0FJUixDQUFDLENBQUMifQ==