@hachej/boring-workspace 0.1.10 → 0.1.13

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/dist/server.js CHANGED
@@ -162,7 +162,7 @@ data: ${JSON.stringify({ v: UI_BRIDGE_PROTOCOL_VERSION })}
162
162
 
163
163
  // src/server/ui-control/tools/uiTools.ts
164
164
  import { access } from "fs/promises";
165
- import { resolve, isAbsolute, relative } from "path";
165
+ import { resolve, isAbsolute, relative, win32 } from "path";
166
166
  function makeError(message) {
167
167
  return {
168
168
  content: [{ type: "text", text: message }],
@@ -174,16 +174,37 @@ function getPathParam(kind, params) {
174
174
  const raw = kind === "navigateToLine" ? params.file : params.path;
175
175
  return typeof raw === "string" && raw.length > 0 ? raw : void 0;
176
176
  }
177
- async function validatePath(workspaceRoot, relPath) {
178
- if (isAbsolute(relPath)) {
177
+ function isPathAbsolute(filePath) {
178
+ return isAbsolute(filePath) || win32.isAbsolute(filePath);
179
+ }
180
+ function isOutsideWorkspaceRel(rel) {
181
+ return rel === ".." || rel.startsWith("../") || rel.startsWith("..\\") || isPathAbsolute(rel);
182
+ }
183
+ function validatePathSyntax(relPath, workspaceRoot) {
184
+ const rootHint = workspaceRoot ? ` (${workspaceRoot})` : "";
185
+ if (isPathAbsolute(relPath)) {
179
186
  return {
180
187
  ok: false,
181
- reason: `path "${relPath}" is absolute \u2014 pass a path relative to the workspace root (${workspaceRoot}).`
188
+ reason: `path "${relPath}" is absolute \u2014 pass a path relative to the workspace root${rootHint}.`
182
189
  };
183
190
  }
191
+ if (relPath.includes("\0")) {
192
+ return { ok: false, reason: `path "${relPath}" contains a null byte.` };
193
+ }
194
+ if (relPath.split(/[\\/]+/).includes("..")) {
195
+ return {
196
+ ok: false,
197
+ reason: `path "${relPath}" escapes the workspace root${rootHint}.`
198
+ };
199
+ }
200
+ return { ok: true };
201
+ }
202
+ async function validatePath(workspaceRoot, relPath) {
203
+ const syntax = validatePathSyntax(relPath, workspaceRoot);
204
+ if (!syntax.ok) return syntax;
184
205
  const resolved = resolve(workspaceRoot, relPath);
185
206
  const rel = relative(workspaceRoot, resolved);
186
- if (rel.startsWith("..") || isAbsolute(rel)) {
207
+ if (isOutsideWorkspaceRel(rel)) {
187
208
  return {
188
209
  ok: false,
189
210
  reason: `path "${relPath}" escapes the workspace root (${workspaceRoot}).`
@@ -286,13 +307,14 @@ function createExecUiTool(uiBridge, opts = {}) {
286
307
  " auto-opens if collapsed. Path must be relative to the",
287
308
  " workspace root (e.g. `src/foo.ts`, not `foo.ts` if it",
288
309
  " lives under src/).",
289
- " Recovery on file-not-found: this tool stat-checks the",
290
- " path server-side and returns an error if it doesn't",
291
- " exist. On that error, immediately call find (or",
292
- " grep) to locate the file, then call exec_ui",
293
- " openFile AGAIN using the EXACT path returned \u2014 don't",
294
- " give up and don't switch to the read tool. Repeat",
295
- " until openFile succeeds or no candidate is found.",
310
+ " Recovery on file-not-found: when the server has local",
311
+ " filesystem access, this tool stat-checks the path and",
312
+ " returns an error if it doesn't exist. On that error,",
313
+ " immediately call find (or grep) to locate the file,",
314
+ " then call exec_ui openFile AGAIN using the EXACT path",
315
+ " returned \u2014 don't give up and don't switch to the read",
316
+ " tool. Repeat until openFile succeeds or no candidate",
317
+ " is found.",
296
318
  " Example: {kind:'openFile', params:{path:'README.md'}}",
297
319
  "",
298
320
  " openPanel params: { id: string, component: string,",
@@ -376,16 +398,20 @@ function createExecUiTool(uiBridge, opts = {}) {
376
398
  return makeError("openSurface: meta must be an object when provided");
377
399
  }
378
400
  }
379
- if (workspaceRoot && PATH_BEARING_KINDS.has(kind)) {
401
+ if (PATH_BEARING_KINDS.has(kind)) {
380
402
  const relPath = getPathParam(kind, cmdParams);
381
403
  if (!relPath) {
382
404
  return makeError(
383
405
  `${kind}: ${kind === "navigateToLine" ? "file" : "path"} param is required`
384
406
  );
385
407
  }
386
- const check = await validatePath(workspaceRoot, relPath);
387
- if (!check.ok) {
388
- return makeError(check.reason);
408
+ const syntax = validatePathSyntax(relPath, workspaceRoot);
409
+ if (!syntax.ok) return makeError(syntax.reason);
410
+ if (workspaceRoot) {
411
+ const check = await validatePath(workspaceRoot, relPath);
412
+ if (!check.ok) {
413
+ return makeError(check.reason);
414
+ }
389
415
  }
390
416
  }
391
417
  try {
package/dist/testing.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { jsx as ka } from "react/jsx-runtime";
2
2
  import * as Pa from "react";
3
3
  import { createElement as ds, useMemo as Tn, useLayoutEffect as fs, isValidElement as ps, cloneElement as ms, useSyncExternalStore as Ji } from "react";
4
- import { i as vs, q as bs, ai as hs } from "./CommandPalette-D5KPKtKA.js";
4
+ import { i as vs, q as bs, ai as hs } from "./CommandPalette-Dme9em28.js";
5
5
  import { d as ys } from "./panel-DnvDNQac.js";
6
6
  import * as Rs from "react-dom/test-utils";
7
7
  import Ba from "react-dom";
@@ -1868,9 +1868,6 @@
1868
1868
  .w-6 {
1869
1869
  width: calc(var(--spacing) * 6);
1870
1870
  }
1871
- .w-7 {
1872
- width: calc(var(--spacing) * 7);
1873
- }
1874
1871
  .w-9 {
1875
1872
  width: calc(var(--spacing) * 9);
1876
1873
  }
@@ -1913,6 +1910,9 @@
1913
1910
  .max-w-2xl {
1914
1911
  max-width: var(--container-2xl);
1915
1912
  }
1913
+ .max-w-20 {
1914
+ max-width: calc(var(--spacing) * 20);
1915
+ }
1916
1916
  .max-w-\[68ch\] {
1917
1917
  max-width: 68ch;
1918
1918
  }
@@ -509,7 +509,7 @@ export declare interface DataCatalogVisualizationState extends DataCatalogResolv
509
509
  title: string;
510
510
  }
511
511
 
512
- export declare function DataExplorer({ adapter, facets: facetConfigs, groupBy, onActivate, getDragPayload, emptyState, searchPlaceholder, searchable, query, pageSize, debounceMs, className, }: DataExplorerProps): JSX.Element;
512
+ export declare function DataExplorer({ adapter, facets: facetConfigs, groupBy, onActivate, getDragPayload, emptyState, searchPlaceholder, searchable, query, onQueryChange, pageSize, debounceMs, className, }: DataExplorerProps): JSX.Element;
513
513
 
514
514
  export declare type DataExplorerProps = {
515
515
  adapter: ExplorerAdapter;
@@ -532,6 +532,8 @@ export declare type DataExplorerProps = {
532
532
  * Useful when an outer chrome already owns a search box.
533
533
  */
534
534
  query?: string;
535
+ /** Called when the toolbar search changes. Use with `query` for controlled per-tab search. */
536
+ onQueryChange?: (query: string) => void;
535
537
  /** Page size and debounce — passed through to useExplorerState. */
536
538
  pageSize?: number;
537
539
  debounceMs?: number;