@levu/snap 0.3.7 → 0.3.8

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.8] - 2026-02-26
9
+
10
+ ### Fixed
11
+ - Prevented premature submit during non-bracketed multi-line paste bursts where newline Enter events were treated as user submit before all pasted lines arrived.
12
+ - Added explicit paste-burst detection from raw input stream and suppressed Enter-submit only during the short paste window.
13
+ - Kept bracketed paste behavior intact and avoided false suppression after paste-end marker.
14
+ - Added regression coverage for non-bracketed multi-line paste with delayed second-line arrival.
15
+
8
16
  ## [0.3.7] - 2026-02-26
9
17
 
10
18
  ### Fixed
@@ -35,6 +35,7 @@ export const createMultilineTextPrompt = () => {
35
35
  let bracketPasteProbe = '';
36
36
  let pendingEnterSubmit = false;
37
37
  let pendingEnterSubmitTimer;
38
+ let recentPasteBurstUntil = 0;
38
39
  const cleanup = () => {
39
40
  if (pendingEnterSubmitTimer) {
40
41
  clearTimeout(pendingEnterSubmitTimer);
@@ -141,6 +142,7 @@ export const createMultilineTextPrompt = () => {
141
142
  const BRACKET_PASTE_START = '\u001b[200~';
142
143
  const BRACKET_PASTE_END = '\u001b[201~';
143
144
  const BRACKET_PASTE_PROBE_MAX = 96;
145
+ const PASTE_BURST_WINDOW_MS = 70;
144
146
  const updateBracketPasteState = (chunk) => {
145
147
  if (!chunk)
146
148
  return;
@@ -160,6 +162,20 @@ export const createMultilineTextPrompt = () => {
160
162
  }
161
163
  }
162
164
  };
165
+ const notePossiblePasteBurst = (chunk) => {
166
+ if (!chunk)
167
+ return;
168
+ const normalized = chunk
169
+ .replaceAll(BRACKET_PASTE_START, '')
170
+ .replaceAll(BRACKET_PASTE_END, '');
171
+ if (!normalized)
172
+ return;
173
+ // In raw mode, normal typing usually arrives as single-byte chunks.
174
+ // Multi-byte chunks and newlines are strong signals that input is a paste burst.
175
+ if (normalized.length > 1 || /[\r\n]/.test(normalized)) {
176
+ recentPasteBurstUntil = Math.max(recentPasteBurstUntil, Date.now() + PASTE_BURST_WINDOW_MS);
177
+ }
178
+ };
163
179
  showPrompt();
164
180
  // Handle paste from clipboard
165
181
  const handlePaste = async () => {
@@ -200,12 +216,19 @@ export const createMultilineTextPrompt = () => {
200
216
  rl.on('line', (line) => {
201
217
  if (cancelled)
202
218
  return;
219
+ const now = Date.now();
203
220
  if (pendingEnterSubmit) {
221
+ const likelyPasteFlow = now < recentPasteBurstUntil;
204
222
  pendingEnterSubmit = false;
205
223
  if (pendingEnterSubmitTimer) {
206
224
  clearTimeout(pendingEnterSubmitTimer);
207
225
  pendingEnterSubmitTimer = undefined;
208
226
  }
227
+ if (likelyPasteFlow) {
228
+ absorbLine(line);
229
+ showPrompt();
230
+ return;
231
+ }
209
232
  absorbLine(line);
210
233
  submit(buildSubmitValue());
211
234
  return;
@@ -231,7 +254,6 @@ export const createMultilineTextPrompt = () => {
231
254
  showPrompt();
232
255
  return;
233
256
  }
234
- const now = Date.now();
235
257
  // Check for double Enter to submit
236
258
  if (line === '' && now - lastEnterTime < DOUBLE_ENTER_TIMEOUT) {
237
259
  submit(buildSubmitValue());
@@ -262,6 +284,7 @@ export const createMultilineTextPrompt = () => {
262
284
  return;
263
285
  const text = Buffer.isBuffer(chunk) ? chunk.toString('utf8') : String(chunk ?? '');
264
286
  updateBracketPasteState(text);
287
+ notePossiblePasteBurst(text);
265
288
  };
266
289
  input.on('data', dataListener);
267
290
  keypressListener = async (str, key) => {
@@ -319,6 +342,11 @@ export const createMultilineTextPrompt = () => {
319
342
  insertNewline();
320
343
  }
321
344
  else if (key.name === 'enter' || key.name === 'return') {
345
+ const now = Date.now();
346
+ const likelyPasteReturn = now < recentPasteBurstUntil;
347
+ if (likelyPasteReturn) {
348
+ return;
349
+ }
322
350
  if (key.shift || key.alt) {
323
351
  ignoreNextLineEvent = true;
324
352
  // Shift+Enter / Alt+Enter inserts a new line.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@levu/snap",
3
- "version": "0.3.7",
3
+ "version": "0.3.8",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "snap": "./dist/cli-entry.js"