@diegopetrucci/pi-extensions 0.1.72 → 0.1.73

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 (53) hide show
  1. package/README.md +2 -2
  2. package/extensions/agent-workflow-audit/index.ts +41 -8
  3. package/extensions/agent-workflow-audit/package.json +1 -1
  4. package/extensions/annotate-git-diff/README.md +7 -3
  5. package/extensions/annotate-git-diff/git.ts +3 -2
  6. package/extensions/annotate-git-diff/index.ts +366 -81
  7. package/extensions/annotate-git-diff/package.json +2 -2
  8. package/extensions/annotate-git-diff/quiet-glimpse.ts +4 -2
  9. package/extensions/annotate-git-diff/types.ts +6 -0
  10. package/extensions/annotate-git-diff/web/app.js +54 -4
  11. package/extensions/annotate-git-diff/web/index.html +2 -2
  12. package/extensions/annotate-last-message/README.md +9 -1
  13. package/extensions/annotate-last-message/index.ts +53 -13
  14. package/extensions/annotate-last-message/package.json +2 -1
  15. package/extensions/annotate-last-message/quiet-glimpse.ts +4 -2
  16. package/extensions/annotate-last-message/theme.ts +247 -0
  17. package/extensions/annotate-last-message/ui.ts +28 -2
  18. package/extensions/annotate-last-message/web/app.js +351 -162
  19. package/extensions/annotate-last-message/web/index.html +153 -24
  20. package/extensions/annotate-last-message/web/md-renderer.js +458 -0
  21. package/extensions/code-reviewer/index.ts +12 -1
  22. package/extensions/code-reviewer/package.json +2 -1
  23. package/extensions/context-cap/README.md +33 -4
  24. package/extensions/context-cap/index.ts +271 -68
  25. package/extensions/context-cap/package.json +1 -1
  26. package/extensions/context-inspector/README.md +2 -0
  27. package/extensions/context-inspector/index.ts +90 -9
  28. package/extensions/context-inspector/package.json +1 -1
  29. package/extensions/contrarian/index.ts +21 -15
  30. package/extensions/contrarian/package.json +1 -1
  31. package/extensions/dynamic-context-pruning/README.md +17 -5
  32. package/extensions/dynamic-context-pruning/index.ts +17 -4
  33. package/extensions/dynamic-context-pruning/package.json +1 -1
  34. package/extensions/fast/README.md +24 -3
  35. package/extensions/fast/index.ts +2 -2
  36. package/extensions/fast/package.json +1 -1
  37. package/extensions/git-footer/README.md +1 -0
  38. package/extensions/git-footer/package.json +1 -1
  39. package/extensions/librarian/index.ts +7 -0
  40. package/extensions/librarian/package.json +2 -1
  41. package/extensions/minimal-footer/README.md +16 -4
  42. package/extensions/minimal-footer/index.ts +263 -14
  43. package/extensions/minimal-footer/openai-usage.ts +62 -8
  44. package/extensions/minimal-footer/package.json +1 -1
  45. package/extensions/oracle/index.ts +19 -18
  46. package/extensions/oracle/package.json +1 -1
  47. package/extensions/permission-gate/README.md +6 -0
  48. package/extensions/permission-gate/index.ts +37 -16
  49. package/extensions/permission-gate/package.json +1 -1
  50. package/extensions/quiet-tools/README.md +1 -1
  51. package/extensions/quiet-tools/package.json +1 -1
  52. package/extensions/triage-comments/package.json +2 -1
  53. package/package.json +4 -4
@@ -1,211 +1,400 @@
1
1
  /* global window, document */
2
2
 
3
- const messageData = JSON.parse(document.getElementById("annotate-last-message-data").textContent || "{}");
3
+ // Markdown parsing helpers are provided by the md-renderer.js script that
4
+ // ui.ts inlines before this file. Access them via globalThis.__tlhMdRenderer.
4
5
 
6
+ const messageData = JSON.parse(
7
+ document.getElementById("annotate-last-message-data").textContent || "{}",
8
+ );
5
9
  if (!Array.isArray(messageData.lines)) messageData.lines = [];
6
10
  if (!Array.isArray(messageData.sections)) messageData.sections = [];
7
11
 
8
12
  const state = {
9
- overallComment: "",
10
- inlineComments: new Map(),
11
- sectionComments: new Map(),
13
+ overallComment: "",
14
+ inlineComments: new Map(),
15
+ sectionComments: new Map(),
12
16
  };
13
17
 
14
18
  const elements = {
15
- messageLines: document.getElementById("message-lines"),
16
- overallComment: document.getElementById("overall-comment"),
17
- sectionComments: document.getElementById("section-comments"),
18
- status: document.getElementById("status"),
19
- submitButton: document.getElementById("submit-button"),
20
- cancelButton: document.getElementById("cancel-button"),
19
+ messageLines: document.getElementById("message-lines"),
20
+ overallComment: document.getElementById("overall-comment"),
21
+ sectionComments: document.getElementById("section-comments"),
22
+ status: document.getElementById("status"),
23
+ submitButton: document.getElementById("submit-button"),
24
+ cancelButton: document.getElementById("cancel-button"),
21
25
  };
22
26
 
27
+ // Compute fence context once over the complete source message. Section cards
28
+ // reuse this source-line metadata so a section that starts after a blank line
29
+ // inside a fence remains a code preview instead of reopening markdown parsing.
30
+ const fullMessageFenceState = globalThis.__tlhMdRenderer.applyFenceState(
31
+ messageData.lines.map((line) => line.text),
32
+ );
33
+
23
34
  function feedbackCount() {
24
- let count = state.overallComment.trim().length > 0 ? 1 : 0;
25
- for (const value of state.inlineComments.values()) {
26
- if (value.trim().length > 0) count += 1;
27
- }
28
- for (const value of state.sectionComments.values()) {
29
- if (value.trim().length > 0) count += 1;
30
- }
31
- return count;
35
+ let count = state.overallComment.trim().length > 0 ? 1 : 0;
36
+ for (const value of state.inlineComments.values()) {
37
+ if (value.trim().length > 0) count += 1;
38
+ }
39
+ for (const value of state.sectionComments.values()) {
40
+ if (value.trim().length > 0) count += 1;
41
+ }
42
+ return count;
32
43
  }
33
44
 
34
45
  function setStatus(message, status = "idle") {
35
- elements.status.textContent = message;
36
- elements.status.dataset.state = status;
46
+ elements.status.textContent = message;
47
+ elements.status.dataset.state = status;
37
48
  }
38
49
 
39
50
  function updateSubmitState() {
40
- const count = feedbackCount();
41
- elements.submitButton.disabled = count === 0;
42
- if (count === 0) {
43
- setStatus("Add any feedback you want to send back to the editor.");
44
- return;
45
- }
46
- const noun = count === 1 ? "item" : "items";
47
- setStatus(`Ready to submit ${count} feedback ${noun}.`, "ready");
51
+ const count = feedbackCount();
52
+ elements.submitButton.disabled = count === 0;
53
+ if (count === 0) {
54
+ setStatus("Add any feedback you want to send to the agent.");
55
+ return;
56
+ }
57
+ const noun = count === 1 ? "item" : "items";
58
+ setStatus(`Ready to submit ${count} feedback ${noun}.`, "ready");
48
59
  }
49
60
 
50
61
  function setInlineComment(lineNumber, value) {
51
- state.inlineComments.set(lineNumber, value);
52
- updateSubmitState();
62
+ state.inlineComments.set(lineNumber, value);
63
+ updateSubmitState();
53
64
  }
54
65
 
55
66
  function setSectionComment(sectionId, value) {
56
- state.sectionComments.set(sectionId, value);
57
- updateSubmitState();
67
+ state.sectionComments.set(sectionId, value);
68
+ updateSubmitState();
58
69
  }
59
70
 
60
71
  function createInlineEditor(line) {
61
- const container = document.createElement("div");
62
- container.className = "inline-editor";
63
- container.hidden = true;
64
-
65
- const meta = document.createElement("p");
66
- meta.className = "line-meta";
67
- meta.textContent = `Inline note for line ${line.number}`;
68
- container.append(meta);
69
-
70
- const textarea = document.createElement("textarea");
71
- textarea.placeholder = "Explain what should change here, what is unclear, or what planning detail is missing.";
72
- textarea.value = state.inlineComments.get(line.number) || "";
73
- container.append(textarea);
74
-
75
- return { container, textarea };
76
- }
77
-
78
- function createLineRow(line) {
79
- const wrapper = document.createElement("div");
80
- wrapper.className = "message-line";
81
-
82
- const row = document.createElement("div");
83
- row.className = "message-line-row";
84
-
85
- const lineNumber = document.createElement("div");
86
- lineNumber.className = "line-number";
87
- lineNumber.textContent = String(line.number);
88
- row.append(lineNumber);
89
-
90
- const lineText = document.createElement("pre");
91
- lineText.className = "line-text";
92
- lineText.textContent = line.text.length > 0 ? line.text : " ";
93
- row.append(lineText);
94
-
95
- const toggle = document.createElement("button");
96
- toggle.className = "inline-toggle";
97
- toggle.type = "button";
98
- toggle.textContent = "Add inline note";
99
- row.append(toggle);
100
-
101
- const editor = createInlineEditor(line);
102
- const syncToggle = () => {
103
- const hasValue = (state.inlineComments.get(line.number) || "").trim().length > 0;
104
- toggle.dataset.active = hasValue || !editor.container.hidden ? "true" : "false";
105
- toggle.textContent = hasValue ? "Edit inline note" : "Add inline note";
106
- };
107
-
108
- editor.textarea.addEventListener("input", () => {
109
- setInlineComment(line.number, editor.textarea.value);
110
- syncToggle();
111
- });
112
-
113
- toggle.addEventListener("click", () => {
114
- editor.container.hidden = !editor.container.hidden;
115
- syncToggle();
116
- if (!editor.container.hidden) {
117
- editor.textarea.focus();
118
- }
119
- });
120
-
121
- wrapper.append(row);
122
- wrapper.append(editor.container);
123
- syncToggle();
124
- return wrapper;
72
+ const container = document.createElement("div");
73
+ container.className = "inline-editor";
74
+ container.hidden = true;
75
+
76
+ const meta = document.createElement("p");
77
+ meta.className = "line-meta";
78
+ meta.textContent = `Inline note for line ${line.number}`;
79
+ container.append(meta);
80
+
81
+ const textarea = document.createElement("textarea");
82
+ textarea.placeholder =
83
+ "Explain what should change here, what is unclear, or what planning detail is missing.";
84
+ textarea.value = state.inlineComments.get(line.number) || "";
85
+ container.append(textarea);
86
+
87
+ return { container, textarea };
125
88
  }
126
89
 
127
- function createSectionCard(section) {
128
- const card = document.createElement("div");
129
- card.className = "section-card";
130
-
131
- const title = document.createElement("h3");
132
- const lineRange = section.startLine === section.endLine
133
- ? `line ${section.startLine}`
134
- : `lines ${section.startLine}-${section.endLine}`;
135
- title.textContent = `Section ${section.index}`;
136
- card.append(title);
90
+ // ---------------------------------------------------------------------------
91
+ // Markdown DOM rendering helpers
92
+ // ---------------------------------------------------------------------------
93
+
94
+ /**
95
+ * Render an array of inline tokens into a parent element using
96
+ * createElement/textContent only (no innerHTML).
97
+ */
98
+ function renderInlineTokens(parent, tokens) {
99
+ for (const token of tokens) {
100
+ switch (token.type) {
101
+ case "text":
102
+ parent.append(document.createTextNode(token.text));
103
+ break;
104
+ case "code": {
105
+ const code = document.createElement("code");
106
+ code.textContent = token.text;
107
+ parent.append(code);
108
+ break;
109
+ }
110
+ case "bold": {
111
+ const strong = document.createElement("strong");
112
+ renderInlineTokens(strong, token.children);
113
+ parent.append(strong);
114
+ break;
115
+ }
116
+ case "italic": {
117
+ const em = document.createElement("em");
118
+ renderInlineTokens(em, token.children);
119
+ parent.append(em);
120
+ break;
121
+ }
122
+ case "boldItalic": {
123
+ const strong = document.createElement("strong");
124
+ const em = document.createElement("em");
125
+ renderInlineTokens(em, token.children);
126
+ strong.append(em);
127
+ parent.append(strong);
128
+ break;
129
+ }
130
+ case "strikethrough": {
131
+ const s = document.createElement("s");
132
+ renderInlineTokens(s, token.children);
133
+ parent.append(s);
134
+ break;
135
+ }
136
+ case "link": {
137
+ // Render the link label text inside an <a> element.
138
+ // href is intentionally not set to avoid javascript: URL XSS;
139
+ // the URL is shown as muted text beside the link.
140
+ const a = document.createElement("a");
141
+ renderInlineTokens(a, token.labelTokens);
142
+ const urlSpan = document.createElement("span");
143
+ urlSpan.className = "md-link-url";
144
+ urlSpan.textContent = ` (${token.url})`;
145
+ parent.append(a);
146
+ parent.append(urlSpan);
147
+ break;
148
+ }
149
+ default:
150
+ if (typeof token.text === "string") {
151
+ parent.append(document.createTextNode(token.text));
152
+ }
153
+ }
154
+ }
155
+ }
137
156
 
138
- const meta = document.createElement("p");
139
- meta.className = "section-meta";
140
- meta.textContent = lineRange;
141
- card.append(meta);
157
+ /**
158
+ * Populate a container element with DOM nodes for a classified (non-fence) line.
159
+ * Leading indentation is preserved via text nodes where relevant.
160
+ */
161
+ function renderClassifiedLine(container, classification) {
162
+ switch (classification.type) {
163
+ case "blank":
164
+ container.textContent = " ";
165
+ break;
166
+ case "heading": {
167
+ const h = document.createElement(`h${classification.level}`);
168
+ renderInlineTokens(h, globalThis.__tlhMdRenderer.tokenizeLine(classification.text));
169
+ container.append(h);
170
+ break;
171
+ }
172
+ case "hr":
173
+ container.append(document.createElement("hr"));
174
+ break;
175
+ case "blockquote": {
176
+ const bq = document.createElement("blockquote");
177
+ renderInlineTokens(bq, globalThis.__tlhMdRenderer.tokenizeLine(classification.text));
178
+ container.append(bq);
179
+ break;
180
+ }
181
+ case "ul":
182
+ case "ol": {
183
+ // Preserve leading indentation as a text node.
184
+ if (classification.indent) {
185
+ container.append(document.createTextNode(classification.indent));
186
+ }
187
+ const bullet = document.createElement("span");
188
+ bullet.style.cssText = "color: var(--mdListBullet)";
189
+ bullet.textContent = classification.bullet;
190
+ container.append(bullet);
191
+ container.append(document.createTextNode(" "));
192
+ renderInlineTokens(container, globalThis.__tlhMdRenderer.tokenizeLine(classification.text));
193
+ break;
194
+ }
195
+ default: {
196
+ // 'plain' and any unrecognised types
197
+ const lineText = typeof classification.text === "string" ? classification.text : "";
198
+ renderInlineTokens(container, globalThis.__tlhMdRenderer.tokenizeLine(lineText));
199
+ break;
200
+ }
201
+ }
202
+ }
142
203
 
143
- const preview = document.createElement("div");
144
- preview.className = "section-preview";
145
- preview.textContent = section.text;
146
- card.append(preview);
204
+ /**
205
+ * Build the content of a markdown line-text container element.
206
+ * lineType is one of: 'plain' | 'fence-open' | 'fence-body' | 'fence-close'
207
+ */
208
+ function renderLineContent(container, rawText, lineType) {
209
+ if (lineType === "fence-body") {
210
+ // Code-block body: raw text, no inline markdown, code-block styling.
211
+ container.style.cssText = "border-left: 3px solid var(--mdCodeBlockBorder); padding-left: 8px;";
212
+ const code = document.createElement("code");
213
+ code.style.cssText = "display: block; color: var(--mdCodeBlock, var(--text))";
214
+ code.textContent = rawText.length > 0 ? rawText : " ";
215
+ container.append(code);
216
+ return;
217
+ }
218
+
219
+ if (lineType === "fence-open" || lineType === "fence-close") {
220
+ // Fence delimiter: visible, styled as block-edge marker.
221
+ const span = document.createElement("span");
222
+ span.style.cssText = "color: var(--mdCodeBlockBorder)";
223
+ span.textContent = rawText;
224
+ container.append(span);
225
+ return;
226
+ }
227
+
228
+ // Plain line: classify and render inline markdown.
229
+ if (rawText.trim().length === 0) {
230
+ container.textContent = " ";
231
+ return;
232
+ }
233
+
234
+ renderClassifiedLine(container, globalThis.__tlhMdRenderer.classifyLine(rawText));
235
+ }
147
236
 
148
- const textarea = document.createElement("textarea");
149
- textarea.placeholder = "Describe what should change across this section or what larger concern should be addressed.";
150
- textarea.value = state.sectionComments.get(section.id) || "";
151
- textarea.addEventListener("input", () => {
152
- setSectionComment(section.id, textarea.value);
153
- });
154
- card.append(textarea);
237
+ // ---------------------------------------------------------------------------
238
+ // Line row and section card builders
239
+ // ---------------------------------------------------------------------------
240
+
241
+ function createLineRow(line, lineType) {
242
+ const wrapper = document.createElement("div");
243
+ wrapper.className = "message-line";
244
+ const row = document.createElement("div");
245
+ row.className = "message-line-row";
246
+
247
+ const lineNumber = document.createElement("div");
248
+ lineNumber.className = "line-number";
249
+ lineNumber.textContent = String(line.number);
250
+ row.append(lineNumber);
251
+
252
+ // Use a <div> with markdown-content class so heading/blockquote/hr/etc.
253
+ // CSS rules inside .markdown-content apply correctly.
254
+ const lineText = document.createElement("div");
255
+ lineText.className = "line-text markdown-content";
256
+ renderLineContent(lineText, line.text, lineType);
257
+ row.append(lineText);
258
+ wrapper.append(row);
259
+
260
+ const toggle = document.createElement("button");
261
+ toggle.className = "inline-toggle";
262
+ toggle.type = "button";
263
+ toggle.textContent = "Add inline note";
264
+ row.append(toggle);
265
+
266
+ const editor = createInlineEditor(line);
267
+ const syncToggle = () => {
268
+ const hasValue = (state.inlineComments.get(line.number) || "").trim().length > 0;
269
+ toggle.dataset.active = hasValue || !editor.container.hidden ? "true" : "false";
270
+ toggle.textContent = hasValue ? "Edit inline note" : "Add inline note";
271
+ };
272
+
273
+ editor.textarea.addEventListener("input", () => {
274
+ setInlineComment(line.number, editor.textarea.value);
275
+ syncToggle();
276
+ });
277
+
278
+ toggle.addEventListener("click", () => {
279
+ editor.container.hidden = !editor.container.hidden;
280
+ syncToggle();
281
+ if (!editor.container.hidden) {
282
+ editor.textarea.focus();
283
+ }
284
+ });
285
+
286
+ wrapper.append(editor.container);
287
+ syncToggle();
288
+ return wrapper;
289
+ }
155
290
 
156
- return card;
291
+ function createSectionCard(section) {
292
+ const card = document.createElement("div");
293
+ card.className = "section-card";
294
+
295
+ const title = document.createElement("h3");
296
+ const lineRange =
297
+ section.startLine === section.endLine
298
+ ? `line ${section.startLine}`
299
+ : `lines ${section.startLine}-${section.endLine}`;
300
+ title.textContent = `Section ${section.index}`;
301
+ card.append(title);
302
+
303
+ const meta = document.createElement("p");
304
+ meta.className = "section-meta";
305
+ meta.textContent = lineRange;
306
+ card.append(meta);
307
+
308
+ // Render the exact source lines in this section. Do not parse section.text
309
+ // independently: its opening fence may be in an earlier section separated
310
+ // by a blank line.
311
+ const preview = document.createElement("div");
312
+ preview.className = "section-preview markdown-content";
313
+
314
+ const startIndex = Number.isInteger(section.startLine) ? section.startLine - 1 : -1;
315
+ const endIndex = Number.isInteger(section.endLine) ? section.endLine : -1;
316
+ const sourceLines =
317
+ startIndex >= 0 && endIndex >= startIndex && endIndex <= messageData.lines.length
318
+ ? messageData.lines.slice(startIndex, endIndex).map((line) => line.text)
319
+ : typeof section.text === "string"
320
+ ? section.text.split("\n")
321
+ : [];
322
+ for (let idx = 0; idx < sourceLines.length; idx++) {
323
+ const lineDiv = document.createElement("div");
324
+ const sourceIndex = startIndex >= 0 ? startIndex + idx : -1;
325
+ const lineType = fullMessageFenceState[sourceIndex]?.lineType ?? "plain";
326
+ renderLineContent(lineDiv, sourceLines[idx], lineType);
327
+ preview.append(lineDiv);
328
+ }
329
+ card.append(preview);
330
+ const textarea = document.createElement("textarea");
331
+ textarea.placeholder =
332
+ "Describe what should change across this section or what larger concern should be addressed.";
333
+ textarea.value = state.sectionComments.get(section.id) || "";
334
+ textarea.addEventListener("input", () => {
335
+ setSectionComment(section.id, textarea.value);
336
+ });
337
+ card.append(textarea);
338
+
339
+ return card;
157
340
  }
158
341
 
159
342
  function renderMessageLines() {
160
- elements.messageLines.replaceChildren();
161
- for (const line of messageData.lines) {
162
- elements.messageLines.append(createLineRow(line));
163
- }
343
+ elements.messageLines.replaceChildren();
344
+ for (let i = 0; i < messageData.lines.length; i++) {
345
+ elements.messageLines.append(createLineRow(messageData.lines[i], fullMessageFenceState[i].lineType));
346
+ }
164
347
  }
165
348
 
166
349
  function renderSectionComments() {
167
- elements.sectionComments.replaceChildren();
168
- if (messageData.sections.length === 0) {
169
- const empty = document.createElement("p");
170
- empty.className = "empty-hint";
171
- empty.textContent = "This message does not have any non-empty sections to annotate.";
172
- elements.sectionComments.append(empty);
173
- return;
174
- }
175
- for (const section of messageData.sections) {
176
- elements.sectionComments.append(createSectionCard(section));
177
- }
350
+ elements.sectionComments.replaceChildren();
351
+ if (messageData.sections.length === 0) {
352
+ const empty = document.createElement("p");
353
+ empty.className = "empty-hint";
354
+ empty.textContent = "This message does not have any non-empty sections to annotate.";
355
+ elements.sectionComments.append(empty);
356
+ return;
357
+ }
358
+ for (const section of messageData.sections) {
359
+ elements.sectionComments.append(createSectionCard(section));
360
+ }
178
361
  }
179
362
 
180
363
  function collectPayload() {
181
- return {
182
- type: "submit",
183
- overallComment: state.overallComment,
184
- inlineComments: Array.from(state.inlineComments.entries()).map(([line, body]) => ({ line, body })),
185
- sectionComments: Array.from(state.sectionComments.entries()).map(([sectionId, body]) => ({ sectionId, body })),
186
- };
364
+ return {
365
+ type: "submit",
366
+ overallComment: state.overallComment,
367
+ inlineComments: Array.from(state.inlineComments.entries()).map(([line, body]) => ({
368
+ line,
369
+ body,
370
+ })),
371
+ sectionComments: Array.from(state.sectionComments.entries()).map(([sectionId, body]) => ({
372
+ sectionId,
373
+ body,
374
+ })),
375
+ };
187
376
  }
188
377
 
189
378
  function sendPayload(payload) {
190
- window.glimpse?.send?.(payload);
191
- window.glimpse?.close?.();
379
+ window.glimpse?.send?.(payload);
380
+ window.glimpse?.close?.();
192
381
  }
193
382
 
194
383
  function submit() {
195
- if (feedbackCount() === 0) {
196
- setStatus("Add at least one comment before submitting.", "error");
197
- return;
198
- }
199
- sendPayload(collectPayload());
384
+ if (feedbackCount() === 0) {
385
+ setStatus("Add at least one comment before submitting.", "error");
386
+ return;
387
+ }
388
+ sendPayload(collectPayload());
200
389
  }
201
390
 
202
391
  function cancel() {
203
- sendPayload({ type: "cancel" });
392
+ sendPayload({ type: "cancel" });
204
393
  }
205
394
 
206
395
  elements.overallComment.addEventListener("input", () => {
207
- state.overallComment = elements.overallComment.value;
208
- updateSubmitState();
396
+ state.overallComment = elements.overallComment.value;
397
+ updateSubmitState();
209
398
  });
210
399
 
211
400
  elements.submitButton.addEventListener("click", submit);
@@ -213,15 +402,15 @@ elements.submitButton.addEventListener("click", submit);
213
402
  elements.cancelButton.addEventListener("click", cancel);
214
403
 
215
404
  document.addEventListener("keydown", (event) => {
216
- if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
217
- event.preventDefault();
218
- submit();
219
- return;
220
- }
221
- if (event.key === "Escape") {
222
- event.preventDefault();
223
- cancel();
224
- }
405
+ if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
406
+ event.preventDefault();
407
+ submit();
408
+ return;
409
+ }
410
+ if (event.key === "Escape") {
411
+ event.preventDefault();
412
+ cancel();
413
+ }
225
414
  });
226
415
 
227
416
  renderMessageLines();