From 0caddef86db953e2fd32d12941eb60c1079003d7 Mon Sep 17 00:00:00 2001 From: "Claude (Chronicler #83 - The Compiler)" Date: Mon, 13 Apr 2026 01:31:44 -0500 Subject: [PATCH] Fix async error handling + build.sh copy/inject separation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit wrapper.tsx: - Added error state — shows graceful message instead of silent vanish - useEffect catch sets error string, not null data - refresh() catch sets error string, not empty catch - Error UI shows gray card with message in widget slot build.sh: - ALWAYS copies TSX files (even on reinstall — fixes stale component bug) - Separated copy step from injection step - ErrorBoundary upgrade path: removes bare , replaces with wrapped version - Imports added independently (not as one sed block) - Renumbered sections for clarity Co-Authored-By: Claude Opus 4.6 (1M context) --- .../MSG-2026-04-13-async-error-handling.md | 70 ++++++++++++ ...MSG-2026-04-13-dev-panel-manual-changes.md | 100 ++++++++++++++++++ .../blueprint-extension/build.sh | 40 +++++-- .../views/server/wrapper.tsx | 23 +++- 4 files changed, 218 insertions(+), 15 deletions(-) create mode 100644 docs/code-bridge/archive/MSG-2026-04-13-async-error-handling.md create mode 100644 docs/code-bridge/archive/MSG-2026-04-13-dev-panel-manual-changes.md diff --git a/docs/code-bridge/archive/MSG-2026-04-13-async-error-handling.md b/docs/code-bridge/archive/MSG-2026-04-13-async-error-handling.md new file mode 100644 index 0000000..36e8d7a --- /dev/null +++ b/docs/code-bridge/archive/MSG-2026-04-13-async-error-handling.md @@ -0,0 +1,70 @@ +# MSG-2026-04-13-async-error-handling + +**From:** Chronicler #85 +**Date:** 2026-04-13 +**Priority:** HIGH — required before live panel deploy +**Status:** OPEN + +## Context + +Gemini flagged async error handling as a blind spot in our ErrorBoundary approach. +ErrorBoundary catches render/lifecycle errors but NOT unhandled async failures. + +## Current Problems in wrapper.tsx + +**1. useEffect catch (line ~49):** +```tsx +.catch(() => setData(null)) +.finally(() => setLoading(false)) +``` +On failure: `data = null` → `if (!data) return null` → widget vanishes silently. +User sees nothing, no explanation. + +**2. refresh() catch (line ~59):** +```tsx +} catch {} +``` +Completely empty. Fails silently, user has no idea the refresh failed. + +## What We Need + +Add an error state that shows a graceful message instead of silent disappearance. + +Suggested approach — add `const [error, setError] = useState(null)` then: + +**useEffect:** +```tsx +.catch(() => setError('Unable to load modpack status.')) +.finally(() => setLoading(false)) +``` + +**refresh:** +```tsx +} catch { + setError('Check failed. Try again.'); +} +``` + +**In render (before the main return):** +```tsx +if (error) return ( +
{error}
+); +``` + +This way the widget slot always shows *something* — either data, loading, error message, or the ErrorBoundary fallback. No silent disappearance. + +## Gemini's Exact Words + +> "If an async API call fails and you don't have a .catch() block that handles it, +> React will throw an unhandled promise rejection. Usually this just puts a red error +> in the browser console and the UI stays stuck in a loading state." +> "As long as your async calls don't try to force undefined data into a strict UI +> render without a fallback, the card is safe." + +## After Code Pushes + +Chronicler deploys to live panel immediately after. + +--- +*— Chronicler #85* diff --git a/docs/code-bridge/archive/MSG-2026-04-13-dev-panel-manual-changes.md b/docs/code-bridge/archive/MSG-2026-04-13-dev-panel-manual-changes.md new file mode 100644 index 0000000..24b441f --- /dev/null +++ b/docs/code-bridge/archive/MSG-2026-04-13-dev-panel-manual-changes.md @@ -0,0 +1,100 @@ +# MSG-2026-04-13-dev-panel-manual-changes + +**From:** Chronicler #85 +**Date:** 2026-04-13 +**Priority:** HIGH — repo does not reflect Dev Panel state +**Status:** OPEN + +## What I Did on Dev Panel That's Not in the Repo + +During Dev Panel validation I made several manual fixes that exposed gaps in how +Blueprint handles the extension package. These need to be properly encoded in the +repo so live panel deploy and future installs work correctly. + +--- + +### 1. AfterInformation.tsx — ErrorBoundary wrapper added manually + +Blueprint's `blueprint -install` skipped the ErrorBoundary injection because +`ModpackVersionCard` was already present (the "already present" check in build.sh +only looks for the import, not whether it's wrapped). + +I manually patched the file directly: +```tsx +// Before (on server): + + +// After (manually patched): + +``` + +**The build.sh injection logic needs to handle the update case** — if +`ModpackVersionCard` is present but NOT wrapped in `ModpackErrorBoundary`, +it should add the wrapper. Currently it just skips entirely. + +--- + +### 2. wrapper.tsx never replaced ModpackVersionCard.tsx via build.sh + +build.sh copies `wrapper.tsx → ModpackVersionCard.tsx` but skips if +`ModpackVersionCard` import already exists in `AfterInformation.tsx`. +The "already present" check is too broad — it prevents the component file +itself from being updated on subsequent deploys. + +I manually copied: `wrapper.tsx → resources/scripts/components/server/ModpackVersionCard.tsx` + +**build.sh needs to always copy wrapper.tsx regardless of injection state.** +Separate the "copy the component file" step from the "inject into AfterInformation" step. + +--- + +### 3. Blueprint extension package was stale + +After `blueprint -install`, Blueprint restores files from its own internal +package — overwriting anything we deployed. I had to manually copy updated files +INTO the Blueprint extension package: + +``` +.blueprint/extensions/modpackchecker/build.sh ← copied v1.1.0 +.blueprint/extensions/modpackchecker/views/server/ErrorBoundary.tsx ← copied +.blueprint/extensions/modpackchecker/views/server/wrapper.tsx ← copied +.blueprint/extensions/modpackchecker/routes/client.php ← copied +.blueprint/extensions/modpackchecker/routers/client.php ← copied +``` + +**The `.conf` Blueprint archive needs to be rebuilt to include all v1.1.0 files.** +Otherwise every `blueprint -install` will regress to stale files and require +manual re-copying after each install. + +--- + +### 4. Routes deployed to 3 locations manually + +`routes/client.php` exists in the repo but Blueprint wasn't picking it up. +Had to copy to all three locations Blueprint uses: +``` +.blueprint/extensions/modpackchecker/routes/client.php +.blueprint/extensions/modpackchecker/routers/client.php +routes/blueprint/client/modpackchecker.php +``` + +--- + +## Summary of What Needs to Be Fixed in the Repo + +1. **build.sh** — separate component copy from injection; always copy wrapper.tsx; + add ErrorBoundary wrapper detection for update path +2. **Blueprint .conf archive** — rebuild to include v1.1.0 files so fresh installs + don't regress (this may require Blueprint-specific tooling) +3. **Deployment docs** — document that after `blueprint -install`, manually copy + wrapper.tsx and routes until the .conf is rebuilt + +## What's Currently Working on Dev Panel + +Despite all of the above, Dev Panel IS working correctly right now because I +manually fixed everything. The widget loads zero-click, ErrorBoundary is in place, +routes are registered. But it's fragile — another `blueprint -install` would +regress it. + +--- +*— Chronicler #85* diff --git a/services/modpack-version-checker/blueprint-extension/build.sh b/services/modpack-version-checker/blueprint-extension/build.sh index 8b7c43e..70ba440 100755 --- a/services/modpack-version-checker/blueprint-extension/build.sh +++ b/services/modpack-version-checker/blueprint-extension/build.sh @@ -46,53 +46,73 @@ if [ "$NODE_MAJOR_VERSION" -ge 17 ]; then fi # =========================================== -# 1. CONSOLE WIDGET + ERROR BOUNDARY +# 1. COPY TSX COMPONENTS (always — even on reinstall) # =========================================== echo "" -echo "--- Console Widget ---" +echo "--- Copy Components ---" if [ -f "$EXT_DIR/views/server/wrapper.tsx" ]; then cp "$EXT_DIR/views/server/wrapper.tsx" resources/scripts/components/server/ModpackVersionCard.tsx echo "✓ Copied ModpackVersionCard.tsx" else - echo "⚠ wrapper.tsx not found, skipping console widget" + echo "⚠ wrapper.tsx not found" fi if [ -f "$EXT_DIR/views/server/ErrorBoundary.tsx" ]; then cp "$EXT_DIR/views/server/ErrorBoundary.tsx" resources/scripts/components/server/ModpackErrorBoundary.tsx echo "✓ Copied ModpackErrorBoundary.tsx" else - echo "⚠ ErrorBoundary.tsx not found, skipping error boundary" + echo "⚠ ErrorBoundary.tsx not found" fi -# Inject into AfterInformation.tsx (wrapped in ErrorBoundary) +# =========================================== +# 2. INJECT INTO AFTERINFORMATION.TSX +# =========================================== +echo "" +echo "--- Console Widget Injection ---" + AFTER_INFO="resources/scripts/blueprint/components/Server/Terminal/AfterInformation.tsx" if [ -f "$AFTER_INFO" ]; then + # Step A: Add imports if missing if ! grep -q "ModpackVersionCard" "$AFTER_INFO" 2>/dev/null; then - sed -i '/\/\* blueprint\/import \*\//a import ModpackVersionCard from "@/components/server/ModpackVersionCard";\nimport ModpackErrorBoundary from "@/components/server/ModpackErrorBoundary";' "$AFTER_INFO" + sed -i '/\/\* blueprint\/import \*\//a import ModpackVersionCard from "@/components/server/ModpackVersionCard";' "$AFTER_INFO" + echo "✓ Added ModpackVersionCard import" + fi + if ! grep -q "ModpackErrorBoundary" "$AFTER_INFO" 2>/dev/null; then + sed -i '/\/\* blueprint\/import \*\//a import ModpackErrorBoundary from "@/components/server/ModpackErrorBoundary";' "$AFTER_INFO" + echo "✓ Added ModpackErrorBoundary import" + fi + + # Step B: Add wrapped component if not present + if ! grep -q "ModpackErrorBoundary" "$AFTER_INFO" 2>/dev/null || ! grep -q "" "$AFTER_INFO" 2>/dev/null; then + # Remove bare if it exists (upgrade path) + sed -i 's|||g' "$AFTER_INFO" + # Inject wrapped version sed -i 's|{/\* blueprint/react \*/}|{/* blueprint/react */}\n |' "$AFTER_INFO" - echo "✓ Injected ModpackVersionCard (with ErrorBoundary) into AfterInformation.tsx" + echo "✓ Injected wrapped ModpackVersionCard into AfterInformation.tsx" else - echo "○ ModpackVersionCard already present in AfterInformation.tsx" + echo "○ ModpackVersionCard (with ErrorBoundary) already present" fi else echo "⚠ AfterInformation.tsx not found, skipping injection" fi # =========================================== -# 2. DASHBOARD BADGE INJECTION +# 3. DASHBOARD BADGE # =========================================== echo "" echo "--- Dashboard Badge ---" +# Always copy the component file if [ -f "$EXT_DIR/views/dashboard/UpdateBadge.tsx" ]; then mkdir -p resources/scripts/components/dashboard cp "$EXT_DIR/views/dashboard/UpdateBadge.tsx" resources/scripts/components/dashboard/UpdateBadge.tsx echo "✓ Copied UpdateBadge.tsx" else - echo "⚠ UpdateBadge.tsx not found, skipping dashboard badge" + echo "⚠ UpdateBadge.tsx not found" fi +# Inject into ServerRow.tsx only if not already present if ! grep -q "UpdateBadge" resources/scripts/components/dashboard/ServerRow.tsx 2>/dev/null; then sed -i '1i import UpdateBadge from "@/components/dashboard/UpdateBadge";' resources/scripts/components/dashboard/ServerRow.tsx sed -i 's|{server.name}

|{server.name}

|' resources/scripts/components/dashboard/ServerRow.tsx diff --git a/services/modpack-version-checker/blueprint-extension/views/server/wrapper.tsx b/services/modpack-version-checker/blueprint-extension/views/server/wrapper.tsx index d94a460..6bfa2a3 100644 --- a/services/modpack-version-checker/blueprint-extension/views/server/wrapper.tsx +++ b/services/modpack-version-checker/blueprint-extension/views/server/wrapper.tsx @@ -35,6 +35,7 @@ const ModpackVersionCard: React.FC = () => { const uuid = ServerContext.useStoreState((state) => state.server.data?.uuid); const [data, setData] = useState(null); const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); const [checking, setChecking] = useState(false); const [showCalibrate, setShowCalibrate] = useState(false); const [releases, setReleases] = useState([]); @@ -44,8 +45,8 @@ const ModpackVersionCard: React.FC = () => { useEffect(() => { if (!uuid) return; http.get(`/api/client/extensions/modpackchecker/servers/${uuid}/status`) - .then((res) => setData(res.data)) - .catch(() => setData(null)) + .then((res) => { setData(res.data); setError(null); }) + .catch(() => setError('Unable to load modpack status.')) .finally(() => setLoading(false)); }, [uuid]); @@ -53,11 +54,14 @@ const ModpackVersionCard: React.FC = () => { const refresh = async () => { if (!uuid || checking) return; setChecking(true); + setError(null); try { await http.post(`/api/client/extensions/modpackchecker/servers/${uuid}/check`); const res = await http.get(`/api/client/extensions/modpackchecker/servers/${uuid}/status`); setData(res.data); - } catch {} + } catch { + setError('Check failed. Try again.'); + } setChecking(false); }; @@ -102,8 +106,17 @@ const ModpackVersionCard: React.FC = () => { }; if (loading) return null; - if (!data) return null; - if (data.is_ignored) return null; + if (data?.is_ignored) return null; + + if (error && !data) return ( +
+ {error} +
+ ); const hasUpdate = data.update_available; const configured = data.configured;