jupyterlab_claude_code_extension 1.0.19 → 1.0.21

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/lib/widget.d.ts CHANGED
@@ -13,8 +13,20 @@ export declare class ClaudeCodeSessionsWidget extends Widget {
13
13
  protected onBeforeHide(_msg: Message): void;
14
14
  protected onCloseRequest(msg: Message): void;
15
15
  private _buildShell;
16
- /** Lowercase substring + subsequence match. */
16
+ /** Normalise strings for filter comparison: NFD-decompose, strip combining
17
+ * diacritic marks, lowercase, and collapse separators (`-`, `_`, `.`, `/`,
18
+ * whitespace) entirely. So "foo-bar", "foo_bar", "foo bar", "Foo Bar" all
19
+ * compare equal as "foobar".
20
+ */
21
+ private _normalize;
22
+ /** Strict fuzzy match: substring on normalised strings, with up to 2%
23
+ * Levenshtein tolerance for queries of 6+ chars (so the 98% threshold
24
+ * only relaxes substring strictness when the query is long enough that
25
+ * 2% rounds to a nonzero edit budget - effectively substring-only for
26
+ * short queries).
27
+ */
17
28
  private _fuzzyMatch;
29
+ private _levenshtein;
18
30
  private _matchesFilter;
19
31
  private _showLoading;
20
32
  private _showError;
package/lib/widget.js CHANGED
@@ -151,23 +151,76 @@ export class ClaudeCodeSessionsWidget extends Widget {
151
151
  this._bodyEl = body;
152
152
  this._statusEl = status;
153
153
  }
154
- /** Lowercase substring + subsequence match. */
154
+ /** Normalise strings for filter comparison: NFD-decompose, strip combining
155
+ * diacritic marks, lowercase, and collapse separators (`-`, `_`, `.`, `/`,
156
+ * whitespace) entirely. So "foo-bar", "foo_bar", "foo bar", "Foo Bar" all
157
+ * compare equal as "foobar".
158
+ */
159
+ _normalize(s) {
160
+ return s
161
+ .normalize('NFD')
162
+ .replace(/[̀-ͯ]/g, '')
163
+ .toLowerCase()
164
+ .replace(/[\s\-_./]+/g, '');
165
+ }
166
+ /** Strict fuzzy match: substring on normalised strings, with up to 2%
167
+ * Levenshtein tolerance for queries of 6+ chars (so the 98% threshold
168
+ * only relaxes substring strictness when the query is long enough that
169
+ * 2% rounds to a nonzero edit budget - effectively substring-only for
170
+ * short queries).
171
+ */
155
172
  _fuzzyMatch(haystack, needle) {
156
173
  if (!needle) {
157
174
  return true;
158
175
  }
159
- const h = haystack.toLowerCase();
160
- const n = needle.toLowerCase();
176
+ const h = this._normalize(haystack);
177
+ const n = this._normalize(needle);
178
+ if (!n) {
179
+ return true;
180
+ }
161
181
  if (h.includes(n)) {
162
182
  return true;
163
183
  }
164
- let j = 0;
165
- for (let i = 0; i < h.length && j < n.length; i++) {
166
- if (h[i] === n[j]) {
167
- j += 1;
184
+ const tol = Math.floor(n.length * 0.02);
185
+ if (tol === 0) {
186
+ return false;
187
+ }
188
+ for (let len = n.length - tol; len <= n.length + tol; len += 1) {
189
+ if (len <= 0) {
190
+ continue;
191
+ }
192
+ for (let i = 0; i + len <= h.length; i += 1) {
193
+ if (this._levenshtein(h.slice(i, i + len), n) <= tol) {
194
+ return true;
195
+ }
196
+ }
197
+ }
198
+ return false;
199
+ }
200
+ _levenshtein(a, b) {
201
+ const m = a.length;
202
+ const n = b.length;
203
+ if (m === 0) {
204
+ return n;
205
+ }
206
+ if (n === 0) {
207
+ return m;
208
+ }
209
+ const dp = new Array(n + 1);
210
+ for (let j = 0; j <= n; j += 1) {
211
+ dp[j] = j;
212
+ }
213
+ for (let i = 1; i <= m; i += 1) {
214
+ let prev = dp[0];
215
+ dp[0] = i;
216
+ for (let j = 1; j <= n; j += 1) {
217
+ const tmp = dp[j];
218
+ dp[j] =
219
+ a[i - 1] === b[j - 1] ? prev : 1 + Math.min(prev, dp[j], dp[j - 1]);
220
+ prev = tmp;
168
221
  }
169
222
  }
170
- return j === n.length;
223
+ return dp[n];
171
224
  }
172
225
  _matchesFilter(s) {
173
226
  const q = this._filter.trim();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jupyterlab_claude_code_extension",
3
- "version": "1.0.19",
3
+ "version": "1.0.21",
4
4
  "description": "Browse, resume, and manage your Claude Code CLI sessions from a JupyterLab side panel. One click reactivates the right terminal - no duplicate tabs, live remote-control indicator, and favourites for the projects you keep coming back to.",
5
5
  "keywords": [
6
6
  "jupyter",
package/src/widget.ts CHANGED
@@ -186,23 +186,78 @@ export class ClaudeCodeSessionsWidget extends Widget {
186
186
  this._statusEl = status;
187
187
  }
188
188
 
189
- /** Lowercase substring + subsequence match. */
189
+ /** Normalise strings for filter comparison: NFD-decompose, strip combining
190
+ * diacritic marks, lowercase, and collapse separators (`-`, `_`, `.`, `/`,
191
+ * whitespace) entirely. So "foo-bar", "foo_bar", "foo bar", "Foo Bar" all
192
+ * compare equal as "foobar".
193
+ */
194
+ private _normalize(s: string): string {
195
+ return s
196
+ .normalize('NFD')
197
+ .replace(/[̀-ͯ]/g, '')
198
+ .toLowerCase()
199
+ .replace(/[\s\-_./]+/g, '');
200
+ }
201
+
202
+ /** Strict fuzzy match: substring on normalised strings, with up to 2%
203
+ * Levenshtein tolerance for queries of 6+ chars (so the 98% threshold
204
+ * only relaxes substring strictness when the query is long enough that
205
+ * 2% rounds to a nonzero edit budget - effectively substring-only for
206
+ * short queries).
207
+ */
190
208
  private _fuzzyMatch(haystack: string, needle: string): boolean {
191
209
  if (!needle) {
192
210
  return true;
193
211
  }
194
- const h = haystack.toLowerCase();
195
- const n = needle.toLowerCase();
212
+ const h = this._normalize(haystack);
213
+ const n = this._normalize(needle);
214
+ if (!n) {
215
+ return true;
216
+ }
196
217
  if (h.includes(n)) {
197
218
  return true;
198
219
  }
199
- let j = 0;
200
- for (let i = 0; i < h.length && j < n.length; i++) {
201
- if (h[i] === n[j]) {
202
- j += 1;
220
+ const tol = Math.floor(n.length * 0.02);
221
+ if (tol === 0) {
222
+ return false;
223
+ }
224
+ for (let len = n.length - tol; len <= n.length + tol; len += 1) {
225
+ if (len <= 0) {
226
+ continue;
227
+ }
228
+ for (let i = 0; i + len <= h.length; i += 1) {
229
+ if (this._levenshtein(h.slice(i, i + len), n) <= tol) {
230
+ return true;
231
+ }
232
+ }
233
+ }
234
+ return false;
235
+ }
236
+
237
+ private _levenshtein(a: string, b: string): number {
238
+ const m = a.length;
239
+ const n = b.length;
240
+ if (m === 0) {
241
+ return n;
242
+ }
243
+ if (n === 0) {
244
+ return m;
245
+ }
246
+ const dp: number[] = new Array(n + 1);
247
+ for (let j = 0; j <= n; j += 1) {
248
+ dp[j] = j;
249
+ }
250
+ for (let i = 1; i <= m; i += 1) {
251
+ let prev = dp[0];
252
+ dp[0] = i;
253
+ for (let j = 1; j <= n; j += 1) {
254
+ const tmp = dp[j];
255
+ dp[j] =
256
+ a[i - 1] === b[j - 1] ? prev : 1 + Math.min(prev, dp[j], dp[j - 1]);
257
+ prev = tmp;
203
258
  }
204
259
  }
205
- return j === n.length;
260
+ return dp[n];
206
261
  }
207
262
 
208
263
  private _matchesFilter(s: ISession): boolean {