bloby-bot 0.63.0 → 0.65.0
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 +1 -1
- package/supervisor/app-ws.js +0 -10
- package/supervisor/chat/src/hooks/useBlobyChat.ts +0 -11
- package/supervisor/chat/src/hooks/useChat.ts +0 -5
- package/supervisor/index.ts +59 -69
- package/supervisor/shell.ts +120 -0
- package/supervisor/vite-dev.ts +0 -7
- package/supervisor/widget.js +26 -13
- package/supervisor/workspace-guard.js +216 -18
- package/vite.config.ts +33 -2
- package/worker/prompts/prompt-conditions.ts +9 -0
- package/worker/prompts/prompt-fragments.json +8 -0
- package/workspace/client/index.html +1 -0
- package/workspace/client/public/sw.js +4 -9
- package/workspace/client/src/App.tsx +13 -103
- package/workspace/client/src/components/Layout/DashboardLayout.tsx +0 -3
|
@@ -28,80 +28,27 @@ export default function App() {
|
|
|
28
28
|
const [showOnboard, setShowOnboard] = useState(false);
|
|
29
29
|
const [userName, setUserName] = useState('');
|
|
30
30
|
const [botName, setBotName] = useState('Bloby');
|
|
31
|
-
const [rebuildState, setRebuildState] = useState<'idle' | 'rebuilding' | 'error'>('idle');
|
|
32
|
-
const [buildError, setBuildError] = useState('');
|
|
33
31
|
|
|
34
|
-
// ── Seamless reload: splash
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
// 2. When returning from background (> 30s hidden), the splash shows
|
|
41
|
-
// IMMEDIATELY — before Vite's delayed reconnect-reload can fire.
|
|
42
|
-
// If no reload comes within 3s, the splash fades away.
|
|
43
|
-
// 3. Vite full-reloads trigger the splash too (same mechanism).
|
|
32
|
+
// ── Seamless reload: splash before full-reloads ───────────────────
|
|
33
|
+
// Vite's reload-on-reconnect is suppressed by the supervisor-injected
|
|
34
|
+
// workspace-guard (which also owns location.reload wrapping), so the
|
|
35
|
+
// only reloads left are real ones: Vite full-reloads after frontend
|
|
36
|
+
// changes. Show the HTML splash before those so the user sees
|
|
37
|
+
// app → splash → app instead of a white flash.
|
|
44
38
|
useEffect(() => {
|
|
45
39
|
const splash = document.getElementById('splash');
|
|
46
|
-
let hiddenAt = 0;
|
|
47
40
|
|
|
48
|
-
// Show the dark background
|
|
41
|
+
// Show the dark background before the reload unloads the page
|
|
49
42
|
function showSplash() {
|
|
50
43
|
if (!splash) return;
|
|
51
44
|
splash.style.display = 'block';
|
|
52
45
|
splash.style.opacity = '1';
|
|
53
46
|
}
|
|
54
47
|
|
|
55
|
-
// Hide the splash screen with a fade
|
|
56
|
-
function hideSplash() {
|
|
57
|
-
if (!splash || splash.style.display === 'none') return;
|
|
58
|
-
splash.style.opacity = '0';
|
|
59
|
-
splash.addEventListener('transitionend', () => { splash.style.display = 'none'; }, { once: true });
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Wrap location.reload: show splash, wait for paint, then reload.
|
|
63
|
-
// This ensures the dark splash is visible during the brief unload→load gap.
|
|
64
|
-
try {
|
|
65
|
-
const origReload = location.reload.bind(location);
|
|
66
|
-
Object.defineProperty(location, 'reload', {
|
|
67
|
-
configurable: true,
|
|
68
|
-
value: () => {
|
|
69
|
-
showSplash();
|
|
70
|
-
requestAnimationFrame(() => requestAnimationFrame(() => origReload()));
|
|
71
|
-
},
|
|
72
|
-
});
|
|
73
|
-
} catch {
|
|
74
|
-
// location.reload is non-configurable in some browsers — skip override
|
|
75
|
-
}
|
|
76
|
-
|
|
77
48
|
// Vite HMR: show splash before a full-reload
|
|
78
49
|
if (import.meta.hot) {
|
|
79
50
|
import.meta.hot.on('vite:beforeFullReload', () => showSplash());
|
|
80
51
|
}
|
|
81
|
-
|
|
82
|
-
// Freeze-thaw: when returning from background after > 30s,
|
|
83
|
-
// show splash proactively so the user never sees the "working app
|
|
84
|
-
// suddenly yank away" pattern. If Vite decides to full-reload,
|
|
85
|
-
// the splash is already visible. If not, we fade it away after 3s.
|
|
86
|
-
let thawTimer: ReturnType<typeof setTimeout>;
|
|
87
|
-
const BACKGROUND_THRESHOLD = 30_000; // 30 seconds
|
|
88
|
-
|
|
89
|
-
const onVisChange = () => {
|
|
90
|
-
if (document.visibilityState === 'hidden') {
|
|
91
|
-
hiddenAt = Date.now();
|
|
92
|
-
} else if (hiddenAt && Date.now() - hiddenAt > BACKGROUND_THRESHOLD) {
|
|
93
|
-
showSplash();
|
|
94
|
-
// Give Vite 3s to trigger a reload; if it doesn't, hide splash
|
|
95
|
-
clearTimeout(thawTimer);
|
|
96
|
-
thawTimer = setTimeout(hideSplash, 3_000);
|
|
97
|
-
}
|
|
98
|
-
};
|
|
99
|
-
document.addEventListener('visibilitychange', onVisChange);
|
|
100
|
-
|
|
101
|
-
return () => {
|
|
102
|
-
document.removeEventListener('visibilitychange', onVisChange);
|
|
103
|
-
clearTimeout(thawTimer);
|
|
104
|
-
};
|
|
105
52
|
}, []);
|
|
106
53
|
|
|
107
54
|
useEffect(() => {
|
|
@@ -119,39 +66,18 @@ export default function App() {
|
|
|
119
66
|
.catch(() => {});
|
|
120
67
|
}, []);
|
|
121
68
|
|
|
122
|
-
// Listen for
|
|
69
|
+
// Listen for events from Bloby iframes via postMessage.
|
|
70
|
+
// 'bloby:hmr-update' is intentionally ignored: Vite HMR handles hot
|
|
71
|
+
// updates natively, and a manual location.reload() here would kill the
|
|
72
|
+
// chat iframe's WebSocket connection for nothing.
|
|
123
73
|
useEffect(() => {
|
|
124
|
-
let safetyTimer: ReturnType<typeof setTimeout>;
|
|
125
74
|
const handler = (e: MessageEvent) => {
|
|
126
|
-
if (e.data?.type === 'bloby:
|
|
127
|
-
setRebuildState('rebuilding');
|
|
128
|
-
setBuildError('');
|
|
129
|
-
// Safety: auto-reload after 60s in case app:rebuilt message is lost
|
|
130
|
-
clearTimeout(safetyTimer);
|
|
131
|
-
safetyTimer = setTimeout(() => location.reload(), 60_000);
|
|
132
|
-
} else if (e.data?.type === 'bloby:rebuilt') {
|
|
133
|
-
clearTimeout(safetyTimer);
|
|
134
|
-
setRebuildState('idle');
|
|
135
|
-
location.reload();
|
|
136
|
-
} else if (e.data?.type === 'bloby:build-error') {
|
|
137
|
-
clearTimeout(safetyTimer);
|
|
138
|
-
setRebuildState('error');
|
|
139
|
-
setBuildError(e.data.error || 'Build failed');
|
|
140
|
-
setTimeout(() => setRebuildState('idle'), 5000);
|
|
141
|
-
} else if (e.data?.type === 'bloby:onboard-complete') {
|
|
75
|
+
if (e.data?.type === 'bloby:onboard-complete') {
|
|
142
76
|
setShowOnboard(false);
|
|
143
|
-
} else if (e.data?.type === 'bloby:hmr-update') {
|
|
144
|
-
// Vite HMR handles hot updates natively — no manual reload needed.
|
|
145
|
-
// Manual location.reload() here was causing unnecessary full-page refreshes
|
|
146
|
-
// that killed the chat iframe's WebSocket connection.
|
|
147
|
-
|
|
148
77
|
}
|
|
149
78
|
};
|
|
150
79
|
window.addEventListener('message', handler);
|
|
151
|
-
return () =>
|
|
152
|
-
window.removeEventListener('message', handler);
|
|
153
|
-
clearTimeout(safetyTimer);
|
|
154
|
-
};
|
|
80
|
+
return () => window.removeEventListener('message', handler);
|
|
155
81
|
}, []);
|
|
156
82
|
|
|
157
83
|
return (
|
|
@@ -172,22 +98,6 @@ export default function App() {
|
|
|
172
98
|
style={{ position: 'fixed', inset: 0, width: '100vw', height: '100dvh', border: 'none', zIndex: 200 }}
|
|
173
99
|
/>
|
|
174
100
|
)}
|
|
175
|
-
|
|
176
|
-
{rebuildState !== 'idle' && (
|
|
177
|
-
<div className="fixed inset-0 z-[49] flex flex-col items-center justify-center bg-background/90">
|
|
178
|
-
<video
|
|
179
|
-
src="/bloby_tilts.webm"
|
|
180
|
-
autoPlay
|
|
181
|
-
loop
|
|
182
|
-
muted
|
|
183
|
-
playsInline
|
|
184
|
-
className="h-24 w-24 rounded-full object-cover"
|
|
185
|
-
/>
|
|
186
|
-
<p className="mt-4 text-sm text-muted-foreground">
|
|
187
|
-
{rebuildState === 'rebuilding' ? 'Rebuilding app...' : buildError}
|
|
188
|
-
</p>
|
|
189
|
-
</div>
|
|
190
|
-
)}
|
|
191
101
|
</>
|
|
192
102
|
);
|
|
193
103
|
}
|
|
@@ -14,14 +14,11 @@ export default function DashboardLayout({ children, userName, botName = 'Bloby'
|
|
|
14
14
|
|
|
15
15
|
useEffect(() => {
|
|
16
16
|
const check = () => {
|
|
17
|
-
console.log('[health] checking /app/api/health…');
|
|
18
17
|
fetch('/app/api/health', { signal: AbortSignal.timeout(3000) })
|
|
19
18
|
.then((r) => {
|
|
20
|
-
console.log(`[health] response: ${r.status} ok=${r.ok}`);
|
|
21
19
|
setStatus(r.ok ? 'healthy' : 'restarting');
|
|
22
20
|
})
|
|
23
21
|
.catch((err) => {
|
|
24
|
-
console.warn('[health] fetch failed:', err.message ?? err);
|
|
25
22
|
setStatus('restarting');
|
|
26
23
|
});
|
|
27
24
|
};
|