cc-haha/desktop/src/main.tsx
程序员阿江(Relakkes) e070c4a0d0 Make upgrade failures diagnosable without deleting user state
Desktop startup can fail before React mounts on older WebViews, so an HTML-level watchdog now renders startup diagnostics even when the module bundle never reaches the app code. Persistent provider migration now imports legacy root provider config into cc-haha-owned storage without deleting the old source file, and plugin marketplace cleanup refuses obvious corrupted cache roots or outside paths.

Constraint: User explicitly accepted the current reviewed state for landing despite remaining review concerns.

Constraint: Global ~/.claude state is user-owned and protected; automatic repair must avoid deleting shared config, transcripts, skills, MCP, plugins, OAuth, adapters, and teams.

Rejected: Tell users to delete ~/.claude or ~/.claude/cc-haha | unsafe because it can destroy user-owned Claude state and still may not fix WebView compatibility failures.

Confidence: medium

Scope-risk: moderate

Directive: Do not weaken protected-path checks; future deletion paths should validate real paths and symlink behavior before recursive rm.

Tested: bun test src/utils/plugins/installedPluginsManager.test.ts src/utils/plugins/marketplaceManager.test.ts

Tested: bun run check:server

Tested: cd desktop && bun run test -- --run src/main.test.tsx index-html.test.ts vite-config.test.ts src/theme/globals.test.ts

Tested: cd desktop && bun run build

Tested: bun run check:coverage

Not-tested: live macOS 12/Safari 15 WKWebView startup on an affected machine.

Not-tested: H5 diagnostic URL redaction and symlink-escape hardening are known follow-up risks from review.
2026-05-14 18:04:47 +08:00

67 lines
1.8 KiB
TypeScript

import React from 'react'
import ReactDOM from 'react-dom/client'
import './theme/globals.css'
import { initializeAppZoom } from './lib/appZoom'
import { runDesktopPersistenceMigrations } from './lib/persistenceMigrations'
declare global {
interface Window {
__CC_HAHA_BOOTSTRAPPED__?: boolean
__CC_HAHA_SHOW_STARTUP_ERROR__?: (reason: unknown) => void
}
}
type DesktopBootstrapModules = [
{ App: React.ComponentType },
{ ErrorBoundary: React.ComponentType<{ children: React.ReactNode }> },
{ installClientDiagnosticsCapture: () => void },
{ initializeTheme: () => void },
]
function loadDesktopBootstrapModules() {
return Promise.all([
import('./App'),
import('./components/ErrorBoundary'),
import('./lib/diagnosticsCapture'),
import('./stores/uiStore'),
])
}
export async function bootstrapDesktopApp(
root: HTMLElement | null = document.getElementById('root'),
loadModules: () => Promise<DesktopBootstrapModules> = loadDesktopBootstrapModules,
) {
try {
const [{ App }, { ErrorBoundary }, { installClientDiagnosticsCapture }, { initializeTheme }] = await loadModules()
initializeTheme()
installClientDiagnosticsCapture()
if (!root) {
throw new Error('Desktop root element not found')
}
ReactDOM.createRoot(root).render(
<React.StrictMode>
<ErrorBoundary>
<App />
</ErrorBoundary>
</React.StrictMode>,
)
window.__CC_HAHA_BOOTSTRAPPED__ = true
} catch (error) {
console.error('[desktop] Failed to bootstrap app', error)
if (root) {
if (window.__CC_HAHA_SHOW_STARTUP_ERROR__) {
window.__CC_HAHA_SHOW_STARTUP_ERROR__(error)
} else {
root.textContent = error instanceof Error ? error.message : String(error)
}
}
}
}
runDesktopPersistenceMigrations()
void initializeAppZoom()
void bootstrapDesktopApp()