claudescreenfix-hardwicksoftware 1.0.0 → 1.0.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.
Files changed (3) hide show
  1. package/README.md +16 -0
  2. package/index.cjs +96 -10
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -117,6 +117,22 @@ for resize events, we intercept `process.on('SIGWINCH', ...)` and debounce the h
117
117
 
118
118
  bottom line: smooth terminal, no lag, no memory bloat. it just works.
119
119
 
120
+ ## changelog
121
+
122
+ ### v1.0.1 (2025-01-08)
123
+ - **FIXED: typing issue** - keystrokes were getting lost because the fix was intercepting stdin echoes
124
+ - now detects stdin echo writes (single chars, backspace, arrow keys) and passes them through unmodified
125
+ - added typing cooldown detection - clears are deferred during active typing
126
+ - periodic clears now use `setImmediate` to not block the event loop
127
+ - added stdin tracking to properly detect user input activity
128
+ - new config option: `typingCooldownMs` (default 500ms) - how long to wait after typing before allowing clears
129
+
130
+ ### v1.0.0 (2025-01-08)
131
+ - initial release
132
+ - scrollback clearing after 500 renders or 60 seconds
133
+ - SIGWINCH debouncing for tmux/screen users
134
+ - enhanced /clear command to actually clear scrollback
135
+
120
136
  ## known issues
121
137
 
122
138
  - some old terminals don't support `\x1b[3J` but that's pretty rare nowadays
package/index.cjs CHANGED
@@ -13,6 +13,11 @@
13
13
  * - hook stdout.write to inject scrollback clears periodically
14
14
  * - debounce SIGWINCH so resize aint thrashing
15
15
  * - enhance /clear to actually clear scrollback not just the screen
16
+ *
17
+ * FIXED v1.0.1: typing issue where stdin echo was being intercepted
18
+ * - now detects stdin echo writes and passes them through unmodified
19
+ * - uses setImmediate for periodic clears to not interrupt typing
20
+ * - tracks "active typing" window to defer clears during input
16
21
  */
17
22
 
18
23
  const CLEAR_SCROLLBACK = '\x1b[3J';
@@ -26,6 +31,7 @@ const config = {
26
31
  resizeDebounceMs: 150, // how long to wait before firing resize
27
32
  periodicClearMs: 60000, // clear scrollback every 60s
28
33
  clearAfterRenders: 500, // or after 500 render cycles
34
+ typingCooldownMs: 500, // wait this long after typing to clear
29
35
  debug: process.env.CLAUDE_TERMINAL_FIX_DEBUG === '1',
30
36
  disabled: process.env.CLAUDE_TERMINAL_FIX_DISABLED === '1'
31
37
  };
@@ -36,6 +42,9 @@ let lastResizeTime = 0;
36
42
  let resizeTimeout = null;
37
43
  let originalWrite = null;
38
44
  let installed = false;
45
+ let lastTypingTime = 0; // track when user last typed
46
+ let pendingClear = false; // defer clear if typing active
47
+ let clearIntervalId = null;
39
48
 
40
49
  function log(...args) {
41
50
  if (config.debug) {
@@ -43,6 +52,64 @@ function log(...args) {
43
52
  }
44
53
  }
45
54
 
55
+ /**
56
+ * check if user is actively typing (within cooldown window)
57
+ */
58
+ function isTypingActive() {
59
+ return (Date.now() - lastTypingTime) < config.typingCooldownMs;
60
+ }
61
+
62
+ /**
63
+ * detect if this looks like a stdin echo (single printable char or short sequence)
64
+ * stdin echoes are typically: single chars, backspace sequences, arrow key echoes
65
+ */
66
+ function isStdinEcho(chunk) {
67
+ // single printable character (including space)
68
+ if (chunk.length === 1 && chunk.charCodeAt(0) >= 32 && chunk.charCodeAt(0) <= 126) {
69
+ return true;
70
+ }
71
+ // backspace/delete echo (usually 1-3 chars with control codes)
72
+ if (chunk.length <= 4 && (chunk.includes('\b') || chunk.includes('\x7f'))) {
73
+ return true;
74
+ }
75
+ // arrow key echo or cursor movement (short escape sequences)
76
+ if (chunk.length <= 6 && chunk.startsWith('\x1b[') && !chunk.includes('J') && !chunk.includes('H')) {
77
+ return true;
78
+ }
79
+ // enter/newline
80
+ if (chunk === '\n' || chunk === '\r' || chunk === '\r\n') {
81
+ return true;
82
+ }
83
+ return false;
84
+ }
85
+
86
+ /**
87
+ * safe clear - defers if typing active
88
+ */
89
+ function safeClearScrollback() {
90
+ if (isTypingActive()) {
91
+ if (!pendingClear) {
92
+ pendingClear = true;
93
+ log('deferring clear - typing active');
94
+ setTimeout(() => {
95
+ pendingClear = false;
96
+ if (!isTypingActive()) {
97
+ safeClearScrollback();
98
+ }
99
+ }, config.typingCooldownMs);
100
+ }
101
+ return;
102
+ }
103
+
104
+ if (originalWrite && process.stdout.isTTY) {
105
+ // use setImmediate to not block the event loop
106
+ setImmediate(() => {
107
+ log('executing deferred scrollback clear');
108
+ originalWrite(CURSOR_SAVE + CLEAR_SCROLLBACK + CURSOR_RESTORE);
109
+ });
110
+ }
111
+ }
112
+
46
113
  /**
47
114
  * installs the fix - hooks into stdout and sigwinch
48
115
  * call this once at startup, calling again is a no-op
@@ -55,21 +122,41 @@ function install() {
55
122
 
56
123
  originalWrite = process.stdout.write.bind(process.stdout);
57
124
 
125
+ // track stdin to know when user is typing
126
+ if (process.stdin.isTTY) {
127
+ process.stdin.on('data', () => {
128
+ lastTypingTime = Date.now();
129
+ });
130
+ }
131
+
58
132
  // hook stdout.write - this is where the magic happens
59
133
  process.stdout.write = function(chunk, encoding, callback) {
134
+ // CRITICAL FIX: pass stdin echoes through unmodified
135
+ // this prevents the typing issue where keystrokes get lost
60
136
  if (typeof chunk === 'string') {
137
+ // check if this is a stdin echo - if so, pass through immediately
138
+ if (isStdinEcho(chunk)) {
139
+ lastTypingTime = Date.now(); // update typing time
140
+ return originalWrite(chunk, encoding, callback);
141
+ }
142
+
61
143
  renderCount++;
62
144
 
63
145
  // ink clears screen before re-render, we piggyback on that
146
+ // but only if not actively typing
64
147
  if (chunk.includes(CLEAR_SCREEN) || chunk.includes(HOME_CURSOR)) {
65
148
  if (config.clearAfterRenders > 0 && renderCount >= config.clearAfterRenders) {
66
- log('clearing scrollback after ' + renderCount + ' renders');
67
- renderCount = 0;
68
- chunk = CLEAR_SCROLLBACK + chunk;
149
+ if (!isTypingActive()) {
150
+ log('clearing scrollback after ' + renderCount + ' renders');
151
+ renderCount = 0;
152
+ chunk = CLEAR_SCROLLBACK + chunk;
153
+ } else {
154
+ log('skipping render-based clear - typing active');
155
+ }
69
156
  }
70
157
  }
71
158
 
72
- // /clear command should actually clear everything
159
+ // /clear command should actually clear everything (immediate, user-requested)
73
160
  if (chunk.includes('Conversation cleared') || chunk.includes('Chat cleared')) {
74
161
  log('/clear detected, nuking scrollback');
75
162
  chunk = CLEAR_SCROLLBACK + chunk;
@@ -83,17 +170,16 @@ function install() {
83
170
  installResizeDebounce();
84
171
 
85
172
  // periodic cleanup so long sessions dont get cooked
173
+ // uses safeClearScrollback which respects typing activity
86
174
  if (config.periodicClearMs > 0) {
87
- setInterval(() => {
88
- if (process.stdout.isTTY) {
89
- log('periodic scrollback clear');
90
- originalWrite(CURSOR_SAVE + CLEAR_SCROLLBACK + CURSOR_RESTORE);
91
- }
175
+ clearIntervalId = setInterval(() => {
176
+ log('periodic clear check');
177
+ safeClearScrollback();
92
178
  }, config.periodicClearMs);
93
179
  }
94
180
 
95
181
  installed = true;
96
- log('installed successfully');
182
+ log('installed successfully - v1.0.1 with typing fix');
97
183
  }
98
184
 
99
185
  function installResizeDebounce() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudescreenfix-hardwicksoftware",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "fixes the scroll glitch in claude code cli - unbounded scrollback, resize thrashing, all that bs",
5
5
  "main": "index.cjs",
6
6
  "bin": {