@levu/snap 0.3.6 → 0.3.7

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.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.3.7] - 2026-02-26
9
+
10
+ ### Fixed
11
+ - Fixed Enter-submit race in multiline prompt where pasted multi-line input could lose the final line and submit only the first line.
12
+ - Enter submit now waits briefly for the corresponding readline `line` event (with fallback timeout), preserving pending paste buffer content.
13
+ - Prompt rendering now prefers live readline buffer content so pending pasted lines are visible before submit.
14
+ - Added regression coverage for the “first line only after multiline paste” scenario.
15
+
8
16
  ## [0.3.6] - 2026-02-26
9
17
 
10
18
  ### Fixed
@@ -33,7 +33,13 @@ export const createMultilineTextPrompt = () => {
33
33
  let expectingGhosttySequenceEcho = false;
34
34
  let bracketPasteActive = false;
35
35
  let bracketPasteProbe = '';
36
+ let pendingEnterSubmit = false;
37
+ let pendingEnterSubmitTimer;
36
38
  const cleanup = () => {
39
+ if (pendingEnterSubmitTimer) {
40
+ clearTimeout(pendingEnterSubmitTimer);
41
+ pendingEnterSubmitTimer = undefined;
42
+ }
37
43
  if (keypressListener) {
38
44
  input.off('keypress', keypressListener);
39
45
  }
@@ -110,6 +116,19 @@ export const createMultilineTextPrompt = () => {
110
116
  const buildSubmitValue = () => {
111
117
  return normalizeGhosttyInlineTokens(lines.concat(getLiveLine()).join('\n'));
112
118
  };
119
+ const absorbLine = (line) => {
120
+ if (line.trim() === '') {
121
+ if (currentLine !== '') {
122
+ lines.push(currentLine);
123
+ currentLine = '';
124
+ }
125
+ return;
126
+ }
127
+ if (currentLine !== '') {
128
+ lines.push(currentLine);
129
+ }
130
+ currentLine = line;
131
+ };
113
132
  const insertNewline = () => {
114
133
  lines.push(getLiveLine());
115
134
  currentLine = '';
@@ -117,7 +136,7 @@ export const createMultilineTextPrompt = () => {
117
136
  output.write(`\n${pc.dim('> ')}`);
118
137
  };
119
138
  const showPrompt = () => {
120
- output.write(`\n${pc.dim('> ')}${currentLine}`);
139
+ output.write(`\n${pc.dim('> ')}${getLiveLine()}`);
121
140
  };
122
141
  const BRACKET_PASTE_START = '\u001b[200~';
123
142
  const BRACKET_PASTE_END = '\u001b[201~';
@@ -181,6 +200,16 @@ export const createMultilineTextPrompt = () => {
181
200
  rl.on('line', (line) => {
182
201
  if (cancelled)
183
202
  return;
203
+ if (pendingEnterSubmit) {
204
+ pendingEnterSubmit = false;
205
+ if (pendingEnterSubmitTimer) {
206
+ clearTimeout(pendingEnterSubmitTimer);
207
+ pendingEnterSubmitTimer = undefined;
208
+ }
209
+ absorbLine(line);
210
+ submit(buildSubmitValue());
211
+ return;
212
+ }
184
213
  if (ignoreNextLineEvent) {
185
214
  ignoreNextLineEvent = false;
186
215
  return;
@@ -209,20 +238,7 @@ export const createMultilineTextPrompt = () => {
209
238
  return;
210
239
  }
211
240
  lastEnterTime = now;
212
- if (line.trim() === '') {
213
- // Empty line - add to lines
214
- if (currentLine !== '') {
215
- lines.push(currentLine);
216
- currentLine = '';
217
- }
218
- }
219
- else {
220
- // Non-empty line
221
- if (currentLine !== '') {
222
- lines.push(currentLine);
223
- }
224
- currentLine = line;
225
- }
241
+ absorbLine(line);
226
242
  showPrompt();
227
243
  });
228
244
  // Handle SIGINT (Ctrl+C)
@@ -303,13 +319,23 @@ export const createMultilineTextPrompt = () => {
303
319
  insertNewline();
304
320
  }
305
321
  else if (key.name === 'enter' || key.name === 'return') {
306
- ignoreNextLineEvent = true;
307
322
  if (key.shift || key.alt) {
323
+ ignoreNextLineEvent = true;
308
324
  // Shift+Enter / Alt+Enter inserts a new line.
309
325
  insertNewline();
310
326
  return;
311
327
  }
312
- submit(buildSubmitValue());
328
+ pendingEnterSubmit = true;
329
+ if (pendingEnterSubmitTimer) {
330
+ clearTimeout(pendingEnterSubmitTimer);
331
+ }
332
+ pendingEnterSubmitTimer = setTimeout(() => {
333
+ if (!pendingEnterSubmit || cancelled)
334
+ return;
335
+ pendingEnterSubmit = false;
336
+ pendingEnterSubmitTimer = undefined;
337
+ submit(buildSubmitValue());
338
+ }, 20);
313
339
  }
314
340
  };
315
341
  input.on('keypress', keypressListener);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@levu/snap",
3
- "version": "0.3.6",
3
+ "version": "0.3.7",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "snap": "./dist/cli-entry.js"