@venturewild/workspace 0.6.9 → 0.6.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@venturewild/workspace",
3
- "version": "0.6.9",
3
+ "version": "0.6.11",
4
4
  "description": "Claude Code Web — Replit/Lovable-style chat-first browser UI that wraps the AI agent already installed on your machine.",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -33,7 +33,7 @@ import {
33
33
  import { PairingStore } from './pairing.mjs';
34
34
  import { verifyGoogleVouch, emailMatches } from './google-vouch.mjs';
35
35
  import { listDir, readFile, fullTree, workspaceSummary, safeResolve } from './fs.mjs';
36
- import { browseDir, browseRoots } from './lobby-browse.mjs';
36
+ import { browseDir, browseRoots, listDrives } from './lobby-browse.mjs';
37
37
  import { InboxWatcher } from './inbox.mjs';
38
38
  import { ActivityBus } from './activity.mjs';
39
39
  import { createWorkspacePresence } from './workspace-presence.mjs';
@@ -2194,7 +2194,11 @@ export async function createServer(overrides = {}) {
2194
2194
  const reqPath = c.req.query('path');
2195
2195
  try {
2196
2196
  const listing = browseDir(reqPath && reqPath.trim() ? reqPath.trim() : home);
2197
- return c.json({ ...listing, roots: browseRoots({ home, workspacesHome }) });
2197
+ return c.json({
2198
+ ...listing,
2199
+ roots: browseRoots({ home, workspacesHome }),
2200
+ drives: listDrives(), // "This PC" — jump to any drive (D:, E:, …)
2201
+ });
2198
2202
  } catch (e) {
2199
2203
  return c.json({ error: e.code || 'browse_failed', message: String(e.message || e) }, 400);
2200
2204
  }
@@ -3047,19 +3051,35 @@ export async function createServer(overrides = {}) {
3047
3051
  });
3048
3052
 
3049
3053
  // --- frontend bundle (built by `npm run build:web`) ---
3054
+ // Cache strategy so an AUTO-UPDATED server's new UI is picked up WITHOUT a hard
3055
+ // refresh (the whole point of "seamless like OneDrive"): the SPA shell
3056
+ // (index.html) must always be revalidated — `no-cache` — so the browser never
3057
+ // keeps loading a stale bundle reference after the server updates. Vite's
3058
+ // /assets/* files are CONTENT-HASHED (a new build = a new filename), so they're
3059
+ // safe to cache forever (`immutable`). Without this, index.html had NO cache
3060
+ // headers and browsers heuristically cached it, stranding users on the old UI.
3050
3061
  if (existsSync(config.webDir)) {
3051
3062
  app.use(
3052
3063
  '/*',
3053
3064
  serveStatic({
3054
3065
  root: path.relative(process.cwd(), config.webDir),
3066
+ onFound: (filePath, c) => {
3067
+ // Separator-agnostic (Windows serves backslash paths to onFound).
3068
+ if (/[\\/]assets[\\/]/.test(filePath)) {
3069
+ c.header('Cache-Control', 'public, max-age=31536000, immutable');
3070
+ } else {
3071
+ // index.html + any other top-level shell file → always revalidate.
3072
+ c.header('Cache-Control', 'no-cache');
3073
+ }
3074
+ },
3055
3075
  }),
3056
3076
  );
3057
- // SPA fallback
3077
+ // SPA fallback (client-side routes) — same no-cache shell rule.
3058
3078
  app.notFound((c) => {
3059
3079
  const indexHtmlPath = path.join(config.webDir, 'index.html');
3060
3080
  if (existsSync(indexHtmlPath)) {
3061
3081
  return new Response(readFileSync(indexHtmlPath), {
3062
- headers: { 'content-type': 'text/html' },
3082
+ headers: { 'content-type': 'text/html', 'cache-control': 'no-cache' },
3063
3083
  });
3064
3084
  }
3065
3085
  return c.text('wild-workspace: frontend not built; run `npm run build`', 200);
@@ -22,6 +22,24 @@ function isDirSafe(p) {
22
22
  }
23
23
  }
24
24
 
25
+ // The filesystem roots ("This PC"): on Windows each drive (C:\, D:\, …) is a
26
+ // SEPARATE root with no common parent — without this, the picker is trapped on
27
+ // the home drive (you could never browse to D:). We probe C–Z (A/B are legacy
28
+ // floppies; probing them can stall on some setups) and keep the ones that exist.
29
+ // On POSIX there's a single root, `/`.
30
+ export function listDrives() {
31
+ if (process.platform !== 'win32') {
32
+ return isDirSafe('/') ? [{ name: '/', dir: '/' }] : [];
33
+ }
34
+ const drives = [];
35
+ for (let code = 'C'.charCodeAt(0); code <= 'Z'.charCodeAt(0); code += 1) {
36
+ const letter = String.fromCharCode(code);
37
+ const root = `${letter}:\\`;
38
+ if (isDirSafe(root)) drives.push({ name: `${letter}:`, dir: root });
39
+ }
40
+ return drives;
41
+ }
42
+
25
43
  // Quick-access roots offered beside the listing — only the ones that actually
26
44
  // exist, de-duped by resolved path (Home/Desktop/Documents can coincide).
27
45
  export function browseRoots({ home, workspacesHome } = {}) {
@@ -77,7 +95,10 @@ export function browseDir(target) {
77
95
  }
78
96
  const entries = dirents
79
97
  .filter((d) => {
80
- if (d.name.startsWith('.')) return false; // hidden / dotfolders
98
+ if (d.name.startsWith('.')) return false; // hidden / dotfolders (POSIX + tooling)
99
+ // Windows system clutter that shows at every drive root ($RECYCLE.BIN,
100
+ // $WinREAgent, System Volume Information) — not pickable workspaces.
101
+ if (d.name.startsWith('$') || d.name === 'System Volume Information') return false;
81
102
  if (d.isDirectory()) return true;
82
103
  // Follow symlinks: a link to a directory is still a pickable folder.
83
104
  if (d.isSymbolicLink()) return isDirSafe(path.join(abs, d.name));