From 25aaadd2477985217b30c37e152174fd315f4341 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Wed, 25 Mar 2026 18:07:57 -0700 Subject: [PATCH 1/2] fix(cad): retry health checks so buttons aren't permanently disabled All CAD shapes (KiCad, FreeCAD, Blender) now retry health checks up to 3 times with 3s delay before disabling the generate button. Prevents transient failures during container startup from permanently greying out the button. Co-Authored-By: Claude Opus 4.6 --- lib/folk-blender.ts | 33 +++++++++++++++++++-------------- lib/folk-freecad.ts | 29 +++++++++++++++++------------ lib/folk-kicad.ts | 29 +++++++++++++++++------------ 3 files changed, 53 insertions(+), 38 deletions(-) diff --git a/lib/folk-blender.ts b/lib/folk-blender.ts index 08766e3..f9b9772 100644 --- a/lib/folk-blender.ts +++ b/lib/folk-blender.ts @@ -384,20 +384,25 @@ export class FolkBlender extends FolkShape { this.#renderResult(); } - // Health check - fetch("/api/blender-gen/health").then(r => r.json()).then((h: any) => { - if (!h.available && this.#generateBtn) { - this.#generateBtn.disabled = true; - this.#generateBtn.title = (h.issues || []).join(", ") || "Blender service unavailable"; - } else if (h.warnings?.length && this.#generateBtn) { - this.#generateBtn.title = h.warnings.join(", "); - } - }).catch(() => { - if (this.#generateBtn) { - this.#generateBtn.disabled = true; - this.#generateBtn.title = "Cannot reach Blender health endpoint"; - } - }); + // Health check with retry (container may still be starting) + const checkHealth = (attempt = 0) => { + fetch("/api/blender-gen/health").then(r => r.json()).then((h: any) => { + if (!h.available && this.#generateBtn) { + if (attempt < 2) { setTimeout(() => checkHealth(attempt + 1), 3000); return; } + this.#generateBtn.disabled = true; + this.#generateBtn.title = (h.issues || []).join(", ") || "Blender service unavailable"; + } else if (h.warnings?.length && this.#generateBtn) { + this.#generateBtn.title = h.warnings.join(", "); + } + }).catch(() => { + if (attempt < 2) { setTimeout(() => checkHealth(attempt + 1), 3000); return; } + if (this.#generateBtn) { + this.#generateBtn.disabled = true; + this.#generateBtn.title = "Cannot reach Blender health endpoint"; + } + }); + }; + checkHealth(); return root; } diff --git a/lib/folk-freecad.ts b/lib/folk-freecad.ts index b2ad8d5..1ff03df 100644 --- a/lib/folk-freecad.ts +++ b/lib/folk-freecad.ts @@ -311,18 +311,23 @@ export class FolkFreeCAD extends FolkShape { this.#renderResult(); } - // Health check - fetch("/api/freecad/health").then(r => r.json()).then((h: any) => { - if (!h.available && this.#generateBtn) { - this.#generateBtn.disabled = true; - this.#generateBtn.title = h.error || "FreeCAD MCP server unavailable"; - } - }).catch(() => { - if (this.#generateBtn) { - this.#generateBtn.disabled = true; - this.#generateBtn.title = "Cannot reach FreeCAD health endpoint"; - } - }); + // Health check with retry (container may still be starting) + const checkHealth = (attempt = 0) => { + fetch("/api/freecad/health").then(r => r.json()).then((h: any) => { + if (!h.available && this.#generateBtn) { + if (attempt < 2) { setTimeout(() => checkHealth(attempt + 1), 3000); return; } + this.#generateBtn.disabled = true; + this.#generateBtn.title = h.error || "FreeCAD MCP server unavailable"; + } + }).catch(() => { + if (attempt < 2) { setTimeout(() => checkHealth(attempt + 1), 3000); return; } + if (this.#generateBtn) { + this.#generateBtn.disabled = true; + this.#generateBtn.title = "Cannot reach FreeCAD health endpoint"; + } + }); + }; + checkHealth(); return root; } diff --git a/lib/folk-kicad.ts b/lib/folk-kicad.ts index 12baaab..7b76483 100644 --- a/lib/folk-kicad.ts +++ b/lib/folk-kicad.ts @@ -397,18 +397,23 @@ export class FolkKiCAD extends FolkShape { this.#showExports(); } - // Health check - fetch("/api/kicad/health").then(r => r.json()).then((h: any) => { - if (!h.available && this.#generateBtn) { - this.#generateBtn.disabled = true; - this.#generateBtn.title = h.error || "KiCAD MCP server unavailable"; - } - }).catch(() => { - if (this.#generateBtn) { - this.#generateBtn.disabled = true; - this.#generateBtn.title = "Cannot reach KiCAD health endpoint"; - } - }); + // Health check with retry (container may still be starting) + const checkHealth = (attempt = 0) => { + fetch("/api/kicad/health").then(r => r.json()).then((h: any) => { + if (!h.available && this.#generateBtn) { + if (attempt < 2) { setTimeout(() => checkHealth(attempt + 1), 3000); return; } + this.#generateBtn.disabled = true; + this.#generateBtn.title = h.error || "KiCAD MCP server unavailable"; + } + }).catch(() => { + if (attempt < 2) { setTimeout(() => checkHealth(attempt + 1), 3000); return; } + if (this.#generateBtn) { + this.#generateBtn.disabled = true; + this.#generateBtn.title = "Cannot reach KiCAD health endpoint"; + } + }); + }; + checkHealth(); return root; } From ad75781efdb550a4bb86a127cfc7735b0c6cd5c6 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Wed, 25 Mar 2026 18:08:51 -0700 Subject: [PATCH 2/2] fix(spaces): set clf and bcrg to permissioned on startup One-shot migration to fix visibility for spaces that were changed by stale client sync. Also imports updateSpaceMeta in index.ts. Co-Authored-By: Claude Opus 4.6 --- server/index.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/server/index.ts b/server/index.ts index b0451e7..9025233 100644 --- a/server/index.ts +++ b/server/index.ts @@ -28,6 +28,7 @@ import { cascadePermissions, listCommunities, deleteCommunity, + updateSpaceMeta, } from "./community-store"; import type { NestPermissions, SpaceRefFilter } from "./community-store"; import { ensureDemoCommunity } from "./seed-demo"; @@ -3696,7 +3697,7 @@ loadAllDocs(syncServer) }) .catch((e) => console.error("[DocStore] Startup load failed:", e)); -// Restore relay mode for encrypted spaces +// Restore relay mode for encrypted spaces + one-shot visibility fixes (async () => { try { const slugs = await listCommunities(); @@ -3712,6 +3713,16 @@ loadAllDocs(syncServer) if (relayCount > 0) { console.log(`[Encryption] ${relayCount} space(s) set to relay mode (encrypted)`); } + + // One-shot: fix spaces that should be permissioned + const fixPermissioned = ["clf", "bcrg"]; + for (const slug of fixPermissioned) { + const data = getDocumentData(slug); + if (data && data.meta?.visibility !== "permissioned") { + updateSpaceMeta(slug, { visibility: "permissioned" }); + console.log(`[VisFix] Set ${slug} to permissioned (was: ${data.meta?.visibility})`); + } + } } catch (e) { console.error("[Encryption] Failed to restore relay modes:", e); }