@pablopunk/webr 0.3.10 → 0.3.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.
@@ -345,6 +345,10 @@ async function setProjectIcon(project, png) {
345
345
  body: png
346
346
  })).ok) throw new Error("Could not save this icon.");
347
347
  }
348
+ async function fetchIconCandidates(project) {
349
+ const response = await fetch(`/api/projects/${encodeURIComponent(project.machineId ?? "local")}/${encodeURIComponent(project.id)}/icon-candidates`);
350
+ return response.ok ? await response.json() : [];
351
+ }
348
352
  async function resetProjectIcon(project) {
349
353
  if (!(await fetch(projectIconUrl(project), { method: "DELETE" })).ok) throw new Error("Could not reset this icon.");
350
354
  }
@@ -2457,6 +2461,22 @@ function ProjectIconDialog({ project, open, onClose, onError }) {
2457
2461
  const picker = useRef(null);
2458
2462
  const [dragging, setDragging] = useState(false);
2459
2463
  const [busy, setBusy] = useState(false);
2464
+ const [candidates, setCandidates] = useState([]);
2465
+ useEffect(() => {
2466
+ if (!open) return;
2467
+ let current = true;
2468
+ fetchIconCandidates(project).then((found) => {
2469
+ if (current) setCandidates(found);
2470
+ }).catch(() => {});
2471
+ return () => {
2472
+ current = false;
2473
+ setCandidates([]);
2474
+ };
2475
+ }, [
2476
+ open,
2477
+ project.id,
2478
+ project.machineId
2479
+ ]);
2460
2480
  const run = async (action) => {
2461
2481
  setBusy(true);
2462
2482
  try {
@@ -2471,6 +2491,7 @@ function ProjectIconDialog({ project, open, onClose, onError }) {
2471
2491
  const upload = (file) => {
2472
2492
  if (file) run(async () => setProjectIcon(project, await squarePng(file)));
2473
2493
  };
2494
+ const choose = (candidate) => void run(async () => setProjectIcon(project, await squarePng(await (await fetch(candidate.url)).blob())));
2474
2495
  return /* @__PURE__ */ jsx(Dialog.Root, {
2475
2496
  open,
2476
2497
  onOpenChange: (next) => {
@@ -2483,6 +2504,10 @@ function ProjectIconDialog({ project, open, onClose, onError }) {
2483
2504
  className: "dialog-message",
2484
2505
  children: ["Change icon for ", project.name]
2485
2506
  }),
2507
+ /* @__PURE__ */ jsxs("div", {
2508
+ className: "icon-current",
2509
+ children: [/* @__PURE__ */ jsx(ProjectIcon, { project }), /* @__PURE__ */ jsx("span", { children: "Current icon" })]
2510
+ }),
2486
2511
  /* @__PURE__ */ jsxs("button", {
2487
2512
  type: "button",
2488
2513
  className: `icon-dropzone ${dragging ? "is-dragging" : ""}`,
@@ -2513,6 +2538,24 @@ function ProjectIconDialog({ project, open, onClose, onError }) {
2513
2538
  event.target.value = "";
2514
2539
  }
2515
2540
  }),
2541
+ candidates.length > 0 && /* @__PURE__ */ jsx("div", {
2542
+ className: "icon-candidates",
2543
+ role: "group",
2544
+ "aria-label": "Images found in this project",
2545
+ children: candidates.map((candidate) => /* @__PURE__ */ jsx("button", {
2546
+ type: "button",
2547
+ className: "icon-candidate",
2548
+ disabled: busy,
2549
+ title: candidate.name,
2550
+ "aria-label": `Use ${candidate.name}`,
2551
+ onClick: () => choose(candidate),
2552
+ children: /* @__PURE__ */ jsx("img", {
2553
+ src: candidate.url,
2554
+ alt: "",
2555
+ loading: "lazy"
2556
+ })
2557
+ }, candidate.url))
2558
+ }),
2516
2559
  /* @__PURE__ */ jsxs("div", {
2517
2560
  className: "dialog-actions",
2518
2561
  children: [/* @__PURE__ */ jsx("button", {
@@ -1,6 +1,6 @@
1
1
  import { S as createAstro, V as __exportAll, i as renderComponent, u as renderTemplate } from "./server_dfdLOyV2.mjs";
2
2
  import { t as createComponent } from "./compiler_BnrQvXXD.mjs";
3
- import { n as $$Base, t as App } from "./App_BbzCRPiT.mjs";
3
+ import { n as $$Base, t as App } from "./App_DDDJNk_6.mjs";
4
4
  //#region src/pages/threads/[id].astro
5
5
  var _id__exports = /* @__PURE__ */ __exportAll({
6
6
  default: () => $$Id,
@@ -1,6 +1,6 @@
1
1
  import { S as createAstro, V as __exportAll, i as renderComponent, u as renderTemplate } from "./server_dfdLOyV2.mjs";
2
2
  import { t as createComponent } from "./compiler_BnrQvXXD.mjs";
3
- import { n as $$Base, t as App } from "./App_BbzCRPiT.mjs";
3
+ import { n as $$Base, t as App } from "./App_DDDJNk_6.mjs";
4
4
  //#region src/pages/sessions/[session]/tabs/[tab].astro
5
5
  var _tab__exports = /* @__PURE__ */ __exportAll({
6
6
  default: () => $$Tab,
@@ -1,6 +1,6 @@
1
1
  import { S as createAstro, V as __exportAll, i as renderComponent, u as renderTemplate } from "./server_dfdLOyV2.mjs";
2
2
  import { t as createComponent } from "./compiler_BnrQvXXD.mjs";
3
- import { n as $$Base, t as App } from "./App_BbzCRPiT.mjs";
3
+ import { n as $$Base, t as App } from "./App_DDDJNk_6.mjs";
4
4
  //#region src/pages/new.astro
5
5
  var new_exports = /* @__PURE__ */ __exportAll({
6
6
  default: () => $$New,
@@ -1,6 +1,6 @@
1
1
  import { S as createAstro, V as __exportAll, i as renderComponent, u as renderTemplate } from "./server_dfdLOyV2.mjs";
2
2
  import { t as createComponent } from "./compiler_BnrQvXXD.mjs";
3
- import { n as $$Base, t as App } from "./App_BbzCRPiT.mjs";
3
+ import { n as $$Base, t as App } from "./App_DDDJNk_6.mjs";
4
4
  //#region src/pages/settings.astro
5
5
  var settings_exports = /* @__PURE__ */ __exportAll({
6
6
  default: () => $$Settings,
@@ -2426,10 +2426,10 @@ var renderers = [Object.assign({
2426
2426
  //#endregion
2427
2427
  //#region \0virtual:astro:pages
2428
2428
  var _page0 = () => import("./chunks/node_C8FqXy_n.mjs").then((n) => n.t);
2429
- var _page1 = () => import("./chunks/new_n3R-7e9Z.mjs");
2430
- var _page2 = () => import("./chunks/_tab__O2UOM0DH.mjs");
2431
- var _page3 = () => import("./chunks/settings_CP_J5pGZ.mjs");
2432
- var _page4 = () => import("./chunks/_id___SMAFJxg.mjs");
2429
+ var _page1 = () => import("./chunks/new_C-ziQti8.mjs");
2430
+ var _page2 = () => import("./chunks/_tab__D95X1O-n.mjs");
2431
+ var _page3 = () => import("./chunks/settings_BYiZ6OG8.mjs");
2432
+ var _page4 = () => import("./chunks/_id__Bg5VthsE.mjs");
2433
2433
  var _page5 = () => import("./chunks/index_Bvv1gpI0.mjs");
2434
2434
  var pageMap = /* @__PURE__ */ new Map([
2435
2435
  ["node_modules/.pnpm/astro@7.3.5_@types+node@22.20.4_tsx@4.23.15_yaml@2.9.1/node_modules/astro/dist/assets/endpoint/node.js", _page0],
@@ -2441,7 +2441,7 @@ var pageMap = /* @__PURE__ */ new Map([
2441
2441
  ]);
2442
2442
  //#endregion
2443
2443
  //#region \0virtual:astro:manifest
2444
- var _manifest = deserializeManifest({"rootDir":"file:///home/runner/work/webr/webr/","cacheDir":"file:///home/runner/work/webr/webr/node_modules/.astro/","outDir":"file:///home/runner/work/webr/webr/dist/","srcDir":"file:///home/runner/work/webr/webr/src/","publicDir":"file:///home/runner/work/webr/webr/public/","buildClientDir":"file:///home/runner/work/webr/webr/dist/client/","buildServerDir":"file:///home/runner/work/webr/webr/dist/server/","adapterName":"@astrojs/node","assetsDir":"_astro","routes":[{"file":"","links":[],"scripts":[],"styles":[],"routeData":{"type":"page","component":"_server-islands.astro","params":["name"],"segments":[[{"content":"_server-islands","dynamic":false,"spread":false}],[{"content":"name","dynamic":true,"spread":false}]],"pattern":"^\\/_server-islands\\/([^/]+?)\\/?$","prerender":false,"isIndex":false,"fallbackRoutes":[],"route":"/_server-islands/[name]","origin":"internal","distURL":[],"_meta":{"trailingSlash":"ignore"}}},{"file":"","links":[],"scripts":[],"styles":[],"routeData":{"route":"/_image","component":"node_modules/.pnpm/astro@7.3.5_@types+node@22.20.4_tsx@4.23.15_yaml@2.9.1/node_modules/astro/dist/assets/endpoint/node.js","params":[],"pathname":"/_image","pattern":"^\\/_image\\/?$","segments":[[{"content":"_image","dynamic":false,"spread":false}]],"type":"endpoint","prerender":false,"fallbackRoutes":[],"distURL":[],"isIndex":false,"origin":"internal","_meta":{"trailingSlash":"ignore"}}},{"file":"","links":[],"scripts":[],"styles":[{"type":"external","src":"_astro/App.CJakfIju.css"}],"routeData":{"route":"/new","isIndex":false,"type":"page","pattern":"^\\/new\\/?$","segments":[[{"content":"new","dynamic":false,"spread":false}]],"params":[],"component":"src/pages/new.astro","pathname":"/new","prerender":false,"fallbackRoutes":[],"distURL":[],"origin":"project","_meta":{"trailingSlash":"ignore"}}},{"file":"","links":[],"scripts":[],"styles":[{"type":"external","src":"_astro/App.CJakfIju.css"}],"routeData":{"route":"/sessions/[session]/tabs/[tab]","isIndex":false,"type":"page","pattern":"^\\/sessions\\/([^/]+?)\\/tabs\\/([^/]+?)\\/?$","segments":[[{"content":"sessions","dynamic":false,"spread":false}],[{"content":"session","dynamic":true,"spread":false}],[{"content":"tabs","dynamic":false,"spread":false}],[{"content":"tab","dynamic":true,"spread":false}]],"params":["session","tab"],"component":"src/pages/sessions/[session]/tabs/[tab].astro","prerender":false,"fallbackRoutes":[],"distURL":[],"origin":"project","_meta":{"trailingSlash":"ignore"}}},{"file":"","links":[],"scripts":[],"styles":[{"type":"external","src":"_astro/App.CJakfIju.css"}],"routeData":{"route":"/settings","isIndex":false,"type":"page","pattern":"^\\/settings\\/?$","segments":[[{"content":"settings","dynamic":false,"spread":false}]],"params":[],"component":"src/pages/settings.astro","pathname":"/settings","prerender":false,"fallbackRoutes":[],"distURL":[],"origin":"project","_meta":{"trailingSlash":"ignore"}}},{"file":"","links":[],"scripts":[],"styles":[{"type":"external","src":"_astro/App.CJakfIju.css"}],"routeData":{"route":"/threads/[id]","isIndex":false,"type":"page","pattern":"^\\/threads\\/([^/]+?)\\/?$","segments":[[{"content":"threads","dynamic":false,"spread":false}],[{"content":"id","dynamic":true,"spread":false}]],"params":["id"],"component":"src/pages/threads/[id].astro","prerender":false,"fallbackRoutes":[],"distURL":[],"origin":"project","_meta":{"trailingSlash":"ignore"}}},{"file":"","links":[],"scripts":[],"styles":[],"routeData":{"route":"/","isIndex":true,"type":"page","pattern":"^\\/$","segments":[],"params":[],"component":"src/pages/index.astro","pathname":"/","prerender":false,"fallbackRoutes":[],"distURL":[],"origin":"project","_meta":{"trailingSlash":"ignore"}}}],"serverLike":true,"middlewareMode":"classic","base":"/","trailingSlash":"ignore","compressHTML":"jsx","componentMetadata":[["/home/runner/work/webr/webr/src/pages/new.astro",{"propagation":"none","containsHead":true}],["/home/runner/work/webr/webr/src/pages/sessions/[session]/tabs/[tab].astro",{"propagation":"none","containsHead":true}],["/home/runner/work/webr/webr/src/pages/settings.astro",{"propagation":"none","containsHead":true}],["/home/runner/work/webr/webr/src/pages/threads/[id].astro",{"propagation":"none","containsHead":true}]],"renderers":[],"clientDirectives":[["idle","(()=>{var l=(n,t)=>{let i=async()=>{await(await n())()},e=typeof t.value==\"object\"?t.value:void 0,s={timeout:e==null?void 0:e.timeout};\"requestIdleCallback\"in window?window.requestIdleCallback(i,s):setTimeout(i,s.timeout||200)};(self.Astro||(self.Astro={})).idle=l;window.dispatchEvent(new Event(\"astro:idle\"));})();"],["load","(()=>{var e=async t=>{await(await t())()};(self.Astro||(self.Astro={})).load=e;window.dispatchEvent(new Event(\"astro:load\"));})();"],["media","(()=>{var n=(a,t)=>{let i=async()=>{await(await a())()};if(t.value){let e=matchMedia(t.value);e.matches?i():e.addEventListener(\"change\",i,{once:!0})}};(self.Astro||(self.Astro={})).media=n;window.dispatchEvent(new Event(\"astro:media\"));})();"],["only","(()=>{var e=async t=>{await(await t())()};(self.Astro||(self.Astro={})).only=e;window.dispatchEvent(new Event(\"astro:only\"));})();"],["visible","(()=>{var a=(s,i,o)=>{let r=async()=>{await(await s())()},t=typeof i.value==\"object\"?i.value:void 0,c={rootMargin:t==null?void 0:t.rootMargin},n=new IntersectionObserver(e=>{for(let l of e)if(l.isIntersecting){n.disconnect(),r();break}},c);for(let e of o.children)n.observe(e)};(self.Astro||(self.Astro={})).visible=a;window.dispatchEvent(new Event(\"astro:visible\"));})();"]],"entryModules":{"\u0000noop-middleware":"virtual_astro_middleware.mjs","\u0000virtual:astro:server-island-manifest":"chunks/_virtual_astro_server-island-manifest_C1Q2srgE.mjs","\u0000virtual:astro:session-driver":"chunks/_virtual_astro_session-driver_Dp7RXjOf.mjs","\u0000virtual:astro:actions/noop-entrypoint":"chunks/noop-entrypoint_Z3zFhrGC.mjs","/home/runner/work/webr/webr/node_modules/.pnpm/@astrojs+react@7.0.0_@types+node@22.20.4_@types+react-dom@19.3.0_@types+react@19.3.0__@_02aa85dceaccf3d763325676dd8540a8/node_modules/@astrojs/react/dist/vnode-children.js":"chunks/vnode-children_BSi0H4PL.mjs","@astrojs/node/server.js":"entry.mjs","\u0000virtual:astro:page:src/pages/threads/[id]@_@astro":"chunks/_id___SMAFJxg.mjs","\u0000virtual:astro:page:src/pages/sessions/[session]/tabs/[tab]@_@astro":"chunks/_tab__O2UOM0DH.mjs","\u0000virtual:astro:page:src/pages/index@_@astro":"chunks/index_Bvv1gpI0.mjs","\u0000virtual:astro:page:src/pages/new@_@astro":"chunks/new_n3R-7e9Z.mjs","\u0000virtual:astro:page:node_modules/.pnpm/astro@7.3.5_@types+node@22.20.4_tsx@4.23.15_yaml@2.9.1/node_modules/astro/dist/assets/endpoint/node@_@js":"chunks/node_C8FqXy_n.mjs","\u0000virtual:astro:page:src/pages/settings@_@astro":"chunks/settings_CP_J5pGZ.mjs","/home/runner/work/webr/webr/node_modules/.pnpm/astro@7.3.5_@types+node@22.20.4_tsx@4.23.15_yaml@2.9.1/node_modules/astro/dist/assets/services/sharp.js":"chunks/sharp_8J8zk4Wd.mjs","/home/runner/work/webr/webr/src/components/App.tsx":"_astro/App.bmHm9djw.js","/home/runner/work/webr/webr/node_modules/.pnpm/astro@7.3.5_@types+node@22.20.4_tsx@4.23.15_yaml@2.9.1/node_modules/astro/components/ClientRouter.astro?astro&type=script&index=0&lang.ts":"_astro/ClientRouter.astro_astro_type_script_index_0_lang.CTgtIFyY.js","@astrojs/react/client.js":"_astro/client.BDkzSRUN.js","/home/runner/work/webr/webr/node_modules/.pnpm/@xterm+addon-fit@0.10.0_@xterm+xterm@5.5.0/node_modules/@xterm/addon-fit/lib/addon-fit.js":"_astro/addon-fit.jgKqzwky.js","/home/runner/work/webr/webr/node_modules/.pnpm/@xterm+addon-web-links@0.11.0_@xterm+xterm@5.5.0/node_modules/@xterm/addon-web-links/lib/addon-web-links.js":"_astro/addon-web-links.C9lyQRxI.js","/home/runner/work/webr/webr/node_modules/.pnpm/@xterm+addon-webgl@0.18.0_@xterm+xterm@5.5.0/node_modules/@xterm/addon-webgl/lib/addon-webgl.js":"_astro/addon-webgl.Bd5ox92E.js","/home/runner/work/webr/webr/node_modules/.pnpm/@xterm+xterm@5.5.0/node_modules/@xterm/xterm/lib/xterm.js":"_astro/xterm.B-l5AIf6.js","astro:scripts/before-hydration.js":""},"inlinedScripts":[],"assets":["/apple-touch-icon.png","/brand-mark.png","/favicon-16.png","/favicon-32.png","/favicon.ico","/icon-192.png","/icon-512.png","/icon-maskable-512.png","/manifest.webmanifest","/_astro/App.bmHm9djw.js","/_astro/ClientRouter.astro_astro_type_script_index_0_lang.CTgtIFyY.js","/_astro/addon-fit.jgKqzwky.js","/_astro/addon-web-links.C9lyQRxI.js","/_astro/addon-webgl.Bd5ox92E.js","/_astro/client.BDkzSRUN.js","/_astro/client.Bbg9OBwT.js","/_astro/client.CLSsMr2g.js","/_astro/xterm.B-l5AIf6.js","/_astro/App.CJakfIju.css","/_astro/jetbrains-mono.CFJaiWaY.woff2","/_astro/symbols-nerd.CEdkKAK8.woff2"],"buildFormat":"directory","checkOrigin":true,"actionBodySizeLimit":1048576,"serverIslandBodySizeLimit":1048576,"allowedDomains":[],"key":"wMBFmhe+pn2DnaCSXSWVokwTbr5rGkrH+IRu6kzYs+A=","sessionConfig":{"driver":"unstorage/drivers/fs-lite","options":{"base":"/home/runner/work/webr/webr/node_modules/.astro/sessions"}},"image":{},"devToolbar":{"enabled":false,"debugInfoOutput":""},"logLevel":"info","shouldInjectCspMetaTags":false});
2444
+ var _manifest = deserializeManifest({"rootDir":"file:///home/runner/work/webr/webr/","cacheDir":"file:///home/runner/work/webr/webr/node_modules/.astro/","outDir":"file:///home/runner/work/webr/webr/dist/","srcDir":"file:///home/runner/work/webr/webr/src/","publicDir":"file:///home/runner/work/webr/webr/public/","buildClientDir":"file:///home/runner/work/webr/webr/dist/client/","buildServerDir":"file:///home/runner/work/webr/webr/dist/server/","adapterName":"@astrojs/node","assetsDir":"_astro","routes":[{"file":"","links":[],"scripts":[],"styles":[],"routeData":{"type":"page","component":"_server-islands.astro","params":["name"],"segments":[[{"content":"_server-islands","dynamic":false,"spread":false}],[{"content":"name","dynamic":true,"spread":false}]],"pattern":"^\\/_server-islands\\/([^/]+?)\\/?$","prerender":false,"isIndex":false,"fallbackRoutes":[],"route":"/_server-islands/[name]","origin":"internal","distURL":[],"_meta":{"trailingSlash":"ignore"}}},{"file":"","links":[],"scripts":[],"styles":[],"routeData":{"route":"/_image","component":"node_modules/.pnpm/astro@7.3.5_@types+node@22.20.4_tsx@4.23.15_yaml@2.9.1/node_modules/astro/dist/assets/endpoint/node.js","params":[],"pathname":"/_image","pattern":"^\\/_image\\/?$","segments":[[{"content":"_image","dynamic":false,"spread":false}]],"type":"endpoint","prerender":false,"fallbackRoutes":[],"distURL":[],"isIndex":false,"origin":"internal","_meta":{"trailingSlash":"ignore"}}},{"file":"","links":[],"scripts":[],"styles":[{"type":"external","src":"_astro/App.8h0zS6BR.css"}],"routeData":{"route":"/new","isIndex":false,"type":"page","pattern":"^\\/new\\/?$","segments":[[{"content":"new","dynamic":false,"spread":false}]],"params":[],"component":"src/pages/new.astro","pathname":"/new","prerender":false,"fallbackRoutes":[],"distURL":[],"origin":"project","_meta":{"trailingSlash":"ignore"}}},{"file":"","links":[],"scripts":[],"styles":[{"type":"external","src":"_astro/App.8h0zS6BR.css"}],"routeData":{"route":"/sessions/[session]/tabs/[tab]","isIndex":false,"type":"page","pattern":"^\\/sessions\\/([^/]+?)\\/tabs\\/([^/]+?)\\/?$","segments":[[{"content":"sessions","dynamic":false,"spread":false}],[{"content":"session","dynamic":true,"spread":false}],[{"content":"tabs","dynamic":false,"spread":false}],[{"content":"tab","dynamic":true,"spread":false}]],"params":["session","tab"],"component":"src/pages/sessions/[session]/tabs/[tab].astro","prerender":false,"fallbackRoutes":[],"distURL":[],"origin":"project","_meta":{"trailingSlash":"ignore"}}},{"file":"","links":[],"scripts":[],"styles":[{"type":"external","src":"_astro/App.8h0zS6BR.css"}],"routeData":{"route":"/settings","isIndex":false,"type":"page","pattern":"^\\/settings\\/?$","segments":[[{"content":"settings","dynamic":false,"spread":false}]],"params":[],"component":"src/pages/settings.astro","pathname":"/settings","prerender":false,"fallbackRoutes":[],"distURL":[],"origin":"project","_meta":{"trailingSlash":"ignore"}}},{"file":"","links":[],"scripts":[],"styles":[{"type":"external","src":"_astro/App.8h0zS6BR.css"}],"routeData":{"route":"/threads/[id]","isIndex":false,"type":"page","pattern":"^\\/threads\\/([^/]+?)\\/?$","segments":[[{"content":"threads","dynamic":false,"spread":false}],[{"content":"id","dynamic":true,"spread":false}]],"params":["id"],"component":"src/pages/threads/[id].astro","prerender":false,"fallbackRoutes":[],"distURL":[],"origin":"project","_meta":{"trailingSlash":"ignore"}}},{"file":"","links":[],"scripts":[],"styles":[],"routeData":{"route":"/","isIndex":true,"type":"page","pattern":"^\\/$","segments":[],"params":[],"component":"src/pages/index.astro","pathname":"/","prerender":false,"fallbackRoutes":[],"distURL":[],"origin":"project","_meta":{"trailingSlash":"ignore"}}}],"serverLike":true,"middlewareMode":"classic","base":"/","trailingSlash":"ignore","compressHTML":"jsx","componentMetadata":[["/home/runner/work/webr/webr/src/pages/new.astro",{"propagation":"none","containsHead":true}],["/home/runner/work/webr/webr/src/pages/sessions/[session]/tabs/[tab].astro",{"propagation":"none","containsHead":true}],["/home/runner/work/webr/webr/src/pages/settings.astro",{"propagation":"none","containsHead":true}],["/home/runner/work/webr/webr/src/pages/threads/[id].astro",{"propagation":"none","containsHead":true}]],"renderers":[],"clientDirectives":[["idle","(()=>{var l=(n,t)=>{let i=async()=>{await(await n())()},e=typeof t.value==\"object\"?t.value:void 0,s={timeout:e==null?void 0:e.timeout};\"requestIdleCallback\"in window?window.requestIdleCallback(i,s):setTimeout(i,s.timeout||200)};(self.Astro||(self.Astro={})).idle=l;window.dispatchEvent(new Event(\"astro:idle\"));})();"],["load","(()=>{var e=async t=>{await(await t())()};(self.Astro||(self.Astro={})).load=e;window.dispatchEvent(new Event(\"astro:load\"));})();"],["media","(()=>{var n=(a,t)=>{let i=async()=>{await(await a())()};if(t.value){let e=matchMedia(t.value);e.matches?i():e.addEventListener(\"change\",i,{once:!0})}};(self.Astro||(self.Astro={})).media=n;window.dispatchEvent(new Event(\"astro:media\"));})();"],["only","(()=>{var e=async t=>{await(await t())()};(self.Astro||(self.Astro={})).only=e;window.dispatchEvent(new Event(\"astro:only\"));})();"],["visible","(()=>{var a=(s,i,o)=>{let r=async()=>{await(await s())()},t=typeof i.value==\"object\"?i.value:void 0,c={rootMargin:t==null?void 0:t.rootMargin},n=new IntersectionObserver(e=>{for(let l of e)if(l.isIntersecting){n.disconnect(),r();break}},c);for(let e of o.children)n.observe(e)};(self.Astro||(self.Astro={})).visible=a;window.dispatchEvent(new Event(\"astro:visible\"));})();"]],"entryModules":{"\u0000noop-middleware":"virtual_astro_middleware.mjs","\u0000virtual:astro:server-island-manifest":"chunks/_virtual_astro_server-island-manifest_C1Q2srgE.mjs","\u0000virtual:astro:session-driver":"chunks/_virtual_astro_session-driver_Dp7RXjOf.mjs","\u0000virtual:astro:actions/noop-entrypoint":"chunks/noop-entrypoint_Z3zFhrGC.mjs","/home/runner/work/webr/webr/node_modules/.pnpm/@astrojs+react@7.0.0_@types+node@22.20.4_@types+react-dom@19.3.0_@types+react@19.3.0__@_02aa85dceaccf3d763325676dd8540a8/node_modules/@astrojs/react/dist/vnode-children.js":"chunks/vnode-children_BSi0H4PL.mjs","@astrojs/node/server.js":"entry.mjs","\u0000virtual:astro:page:src/pages/threads/[id]@_@astro":"chunks/_id__Bg5VthsE.mjs","\u0000virtual:astro:page:src/pages/sessions/[session]/tabs/[tab]@_@astro":"chunks/_tab__D95X1O-n.mjs","\u0000virtual:astro:page:src/pages/index@_@astro":"chunks/index_Bvv1gpI0.mjs","\u0000virtual:astro:page:src/pages/new@_@astro":"chunks/new_C-ziQti8.mjs","\u0000virtual:astro:page:node_modules/.pnpm/astro@7.3.5_@types+node@22.20.4_tsx@4.23.15_yaml@2.9.1/node_modules/astro/dist/assets/endpoint/node@_@js":"chunks/node_C8FqXy_n.mjs","\u0000virtual:astro:page:src/pages/settings@_@astro":"chunks/settings_BYiZ6OG8.mjs","/home/runner/work/webr/webr/node_modules/.pnpm/astro@7.3.5_@types+node@22.20.4_tsx@4.23.15_yaml@2.9.1/node_modules/astro/dist/assets/services/sharp.js":"chunks/sharp_8J8zk4Wd.mjs","/home/runner/work/webr/webr/src/components/App.tsx":"_astro/App.CCpaClja.js","/home/runner/work/webr/webr/node_modules/.pnpm/astro@7.3.5_@types+node@22.20.4_tsx@4.23.15_yaml@2.9.1/node_modules/astro/components/ClientRouter.astro?astro&type=script&index=0&lang.ts":"_astro/ClientRouter.astro_astro_type_script_index_0_lang.CTgtIFyY.js","@astrojs/react/client.js":"_astro/client.BDkzSRUN.js","/home/runner/work/webr/webr/node_modules/.pnpm/@xterm+addon-fit@0.10.0_@xterm+xterm@5.5.0/node_modules/@xterm/addon-fit/lib/addon-fit.js":"_astro/addon-fit.jgKqzwky.js","/home/runner/work/webr/webr/node_modules/.pnpm/@xterm+addon-web-links@0.11.0_@xterm+xterm@5.5.0/node_modules/@xterm/addon-web-links/lib/addon-web-links.js":"_astro/addon-web-links.C9lyQRxI.js","/home/runner/work/webr/webr/node_modules/.pnpm/@xterm+addon-webgl@0.18.0_@xterm+xterm@5.5.0/node_modules/@xterm/addon-webgl/lib/addon-webgl.js":"_astro/addon-webgl.Bd5ox92E.js","/home/runner/work/webr/webr/node_modules/.pnpm/@xterm+xterm@5.5.0/node_modules/@xterm/xterm/lib/xterm.js":"_astro/xterm.B-l5AIf6.js","astro:scripts/before-hydration.js":""},"inlinedScripts":[],"assets":["/apple-touch-icon.png","/brand-mark.png","/favicon-16.png","/favicon-32.png","/favicon.ico","/icon-192.png","/icon-512.png","/icon-maskable-512.png","/manifest.webmanifest","/_astro/App.CCpaClja.js","/_astro/ClientRouter.astro_astro_type_script_index_0_lang.CTgtIFyY.js","/_astro/addon-fit.jgKqzwky.js","/_astro/addon-web-links.C9lyQRxI.js","/_astro/addon-webgl.Bd5ox92E.js","/_astro/client.BDkzSRUN.js","/_astro/client.Bbg9OBwT.js","/_astro/client.CLSsMr2g.js","/_astro/xterm.B-l5AIf6.js","/_astro/App.8h0zS6BR.css","/_astro/jetbrains-mono.CFJaiWaY.woff2","/_astro/symbols-nerd.CEdkKAK8.woff2"],"buildFormat":"directory","checkOrigin":true,"actionBodySizeLimit":1048576,"serverIslandBodySizeLimit":1048576,"allowedDomains":[],"key":"zhV/mlRUF6aAPRL/kGMDJ+5B47sw/eEY3vjPTu56EeA=","sessionConfig":{"driver":"unstorage/drivers/fs-lite","options":{"base":"/home/runner/work/webr/webr/node_modules/.astro/sessions"}},"image":{},"devToolbar":{"enabled":false,"debugInfoOutput":""},"logLevel":"info","shouldInjectCspMetaTags":false});
2445
2445
  var manifestRoutes = _manifest.routes;
2446
2446
  var manifest = Object.assign(_manifest, {
2447
2447
  renderers,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pablopunk/webr",
3
- "version": "0.3.10",
3
+ "version": "0.3.11",
4
4
  "description": "Control your Herdr agents from anywhere.",
5
5
  "keywords": [
6
6
  "herdr",
@@ -111,6 +111,19 @@ export async function createHost(manager: RuntimeManager, origin: string, ssr?:
111
111
  if (icon.contentType === 'image/svg+xml') reply.header('Content-Security-Policy', "sandbox; default-src 'none'; style-src 'unsafe-inline'");
112
112
  return reply.type(icon.contentType).send(icon.bytes);
113
113
  });
114
+ const candidateParams = z.object({ machineId: opaqueId, projectId: opaqueId });
115
+ app.get('/api/projects/:machineId/:projectId/icon-candidates', async (request) => {
116
+ const { machineId, projectId } = candidateParams.parse(request.params);
117
+ const found = await manager.supervisors.get(machineId)?.target.iconCandidates?.(projectId) ?? [];
118
+ return found.map((candidate, index) => ({ name: candidate.name, url: `/api/projects/${encodeURIComponent(machineId)}/${encodeURIComponent(projectId)}/icon-candidates/${index}` }));
119
+ });
120
+ app.get('/api/projects/:machineId/:projectId/icon-candidates/:index', async (request, reply) => {
121
+ const { machineId, projectId, index } = candidateParams.extend({ index: z.coerce.number().int().min(0).max(99) }).parse(request.params);
122
+ const candidate = (await manager.supervisors.get(machineId)?.target.iconCandidates?.(projectId))?.[index];
123
+ if (!candidate) return reply.code(404).send();
124
+ if (candidate.contentType === 'image/svg+xml') reply.header('Content-Security-Policy', "sandbox; default-src 'none'; style-src 'unsafe-inline'");
125
+ return reply.type(candidate.contentType).send(candidate.bytes);
126
+ });
114
127
  app.post('/api/threads', async (request, reply) => {
115
128
  const input = launchInput.parse(request.body);
116
129
  const key = z.uuid().parse(request.headers['idempotency-key']);
@@ -3,7 +3,7 @@ import type { NativeSnapshot } from '../protocol/native';
3
3
  import type { TerminalFrame, TerminalStream } from '../terminal/cli';
4
4
  import type { LaunchInput } from '../../shared/runtime';
5
5
  import type { WorktreeEntry } from './herdr-actions';
6
- import type { Icon } from '../transport/icons';
6
+ import type { Icon, IconCandidate } from '../transport/icons';
7
7
 
8
8
  export type TerminalDirection = 'right' | 'down';
9
9
  export type LaunchLocation = { projectId: string; localId?: string; logicalId?: string; path: string; workspaceId: string };
@@ -22,6 +22,7 @@ export type TargetAdapter = {
22
22
  catalog(projectId?: string): Promise<Machine>;
23
23
  canLaunch?(input: LaunchInput): boolean;
24
24
  icon?(projectId: string): Promise<Icon | undefined>;
25
+ iconCandidates?(projectId: string): Promise<IconCandidate[]>;
25
26
  worktrees?(projectId: string): Promise<WorktreeEntry[]>;
26
27
  openWorktree?(projectId: string, path: string): Promise<void>;
27
28
  suggestDirectories?(prefix: string): Promise<string[]>;
@@ -13,7 +13,7 @@ import { harnessName } from '../../lib/models';
13
13
  import { acceptsModelFlag, herdrAgentKinds, isHerdrAgentKind } from '../../shared/agent-kinds';
14
14
  import { modelListings, parseModelListing } from './model-discovery';
15
15
  import { HerdrActions } from '../runtime/herdr-actions';
16
- import { readApprovedIcon, type Icon } from './icons';
16
+ import { listIconCandidates, readApprovedIcon, type Icon, type IconCandidate } from './icons';
17
17
  import { targetFingerprint } from './identity';
18
18
  import { scopedProjectId } from '../../shared/projects';
19
19
  import { validateInstalledSchema } from '../protocol/validate';
@@ -41,6 +41,7 @@ export class HerdrTarget implements TargetAdapter {
41
41
  private connecting?: Promise<SocketApi>;
42
42
  private streams = new Set<ReturnType<typeof openCliStream>>();
43
43
  private icons = new Map<string, { expires: number; value: Promise<Icon | undefined> }>();
44
+ private candidates = new Map<string, { expires: number; value: Promise<IconCandidate[]> }>();
44
45
  constructor(private profile: TargetProfile, private dependencies: Dependencies = { process: boundedProcess, cli: openCliStream }) {
45
46
  if (profile.transport === 'ssh') this.storeUpload = (fileName, bytes) => uploadOverSsh(profile.host!, fileName, bytes);
46
47
  this.id = profile.id; this.name = profile.name; this.session = profile.session;
@@ -149,6 +150,14 @@ export class HerdrTarget implements TargetAdapter {
149
150
  const value = readApprovedIcon(location.path);
150
151
  this.icons.set(projectId, { expires: Date.now() + 60_000, value }); return value;
151
152
  }
153
+ async iconCandidates(projectId: string) {
154
+ const location = this.locations.find((location) => location.projectId === projectId);
155
+ if (this.profile.transport !== 'local' || !location) return [];
156
+ const cached = this.candidates.get(projectId);
157
+ if (cached && cached.expires > Date.now()) return cached.value;
158
+ const value = listIconCandidates(location.path);
159
+ this.candidates.set(projectId, { expires: Date.now() + 60_000, value }); return value;
160
+ }
152
161
  openTerminal: TargetAdapter['openTerminal'] = (terminalId, mode, cols, rows, takeover, onFrame, onClose) => {
153
162
  if (!this.compatible || mode === 'control' && !this.writable || mode === 'observe' && takeover) throw new Error('terminal_control_unavailable');
154
163
  const command = this.command(['terminal', 'session', mode, terminalId, '--cols', String(cols), '--rows', String(rows), ...(takeover ? ['--takeover'] : [])]);
@@ -5,6 +5,8 @@ const contentTypes: Record<string, string> = { '.ico': 'image/x-icon', '.png': '
5
5
  const maxBytes = 2 * 1024 * 1024;
6
6
  const directories = ['build/Icon.icon/Assets', '', 'public', 'static', 'assets', 'web', 'web/public', 'app/public', 'src/assets', 'src/app', 'frontend/public'];
7
7
  export type Icon = { bytes: Buffer; contentType: string };
8
+ export type IconCandidate = Icon & { name: string; rank: number };
9
+ const maxCandidates = 12;
8
10
 
9
11
  function withinRoot(root: string, path: string) {
10
12
  const part = relative(root, path);
@@ -24,7 +26,7 @@ function rank(name: string) {
24
26
  return Infinity;
25
27
  }
26
28
 
27
- async function iconInDirectory(root: string, directory: string): Promise<Icon | undefined> {
29
+ async function* iconsInDirectory(root: string, directory: string): AsyncGenerator<IconCandidate> {
28
30
  let actual: string;
29
31
  try { actual = await realpath(directory); } catch { return; }
30
32
  if (!withinRoot(root, actual)) return;
@@ -40,7 +42,7 @@ async function iconInDirectory(root: string, directory: string): Promise<Icon |
40
42
  const info = await stat(file);
41
43
  if (!info.isFile() || info.size <= 0 || info.size > maxBytes) continue;
42
44
  const bytes = await readFile(file);
43
- if (bytes.length <= maxBytes) return { bytes, contentType: contentTypes[extname(entry.name).toLowerCase()] };
45
+ if (bytes.length <= maxBytes) yield { bytes, contentType: contentTypes[extname(entry.name).toLowerCase()], name: relative(root, file), rank: rank(entry.name) };
44
46
  } catch {}
45
47
  }
46
48
  }
@@ -63,11 +65,27 @@ async function sourceDirectories(root: string) {
63
65
  } catch { return []; }
64
66
  }
65
67
 
68
+ async function searchDirectories(root: string) {
69
+ return [...directories.map((part) => join(root, part)), ...await sourceDirectories(root), ...await appDirectories(root)];
70
+ }
71
+
66
72
  export async function readApprovedIcon(path: string): Promise<Icon | undefined> {
67
73
  let root: string;
68
74
  try { root = await realpath(path); } catch { return; }
69
- for (const directory of [...directories.map((part) => join(root, part)), ...await sourceDirectories(root), ...await appDirectories(root)]) {
70
- const icon = await iconInDirectory(root, directory);
71
- if (icon) return icon;
75
+ for (const directory of await searchDirectories(root)) {
76
+ for await (const icon of iconsInDirectory(root, directory)) return icon;
77
+ }
78
+ }
79
+
80
+ export async function listIconCandidates(path: string): Promise<IconCandidate[]> {
81
+ let root: string;
82
+ try { root = await realpath(path); } catch { return []; }
83
+ const seen = new Set<string>(); const found: IconCandidate[] = [];
84
+ for (const directory of await searchDirectories(root)) {
85
+ for await (const icon of iconsInDirectory(root, directory)) {
86
+ if (seen.has(icon.name)) continue;
87
+ seen.add(icon.name); found.push(icon);
88
+ }
72
89
  }
90
+ return found.sort((a, b) => a.rank - b.rank).slice(0, maxCandidates);
73
91
  }