leanos/src/system-ui/AppHost.tsx
Thomas Lutz Kolter ee203fd9fb leanOS: initial import (basiert auf NEON NOODLE OS)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-15 14:58:54 +02:00

54 lines
1.7 KiB
TypeScript

import React, { useMemo, useSyncExternalStore } from "react";
import type { AppManifest } from "../kernel/types";
import { makeOSApi } from "../kernel/sdk";
import {
AppErrorBoundary,
getCompiled,
} from "../kernel/appRuntime";
import { onRuntimeVersionChange, runtimeVersionOf } from "../kernel/store";
// Renders a single app instance inside a window body.
// Built-in apps render their component directly; generated apps are compiled.
export function AppHost({ app, windowId }: { app: AppManifest; windowId: string }) {
const version = useSyncExternalStore(
onRuntimeVersionChange,
() => runtimeVersionOf(app.id)
);
const os = useMemo(() => makeOSApi(app.id, windowId), [app.id, windowId]);
// Only meaningful for generated apps; cheap no-op for built-ins (cached).
const compiled = useMemo(
() => (app.builtin ? null : getCompiled(app, version)),
[app, version]
);
if (app.builtin && app.component) {
const C = app.component;
return (
<AppErrorBoundary appId={app.id} appName={app.name}>
<C os={os} windowId={windowId} />
</AppErrorBoundary>
);
}
// generated app
if (!compiled) return null;
if (compiled.error) {
return (
<AppErrorBoundary appId={app.id} appName={app.name}>
<CompileError message={compiled.error} />
</AppErrorBoundary>
);
}
const Generated = compiled.Component!;
return (
<AppErrorBoundary appId={app.id} appName={app.name} key={version}>
<Generated os={os} />
</AppErrorBoundary>
);
}
function CompileError({ message }: { message: string }): React.ReactElement {
// Thrown so the error boundary picks it up and offers self-heal.
throw new Error("Compile error: " + message);
}