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;