← Files DataARCHIVED FILE
templates/data-app/base/src/DataAppRuntime.jsx
3.94 KB · Sep 30, 2026 · 23:19 UTC
import React, { useEffect, useState } from "react";
import { DataAppShell } from "./DataAppShell.jsx";
import { loadHostedSnapshot } from "./hosted-query-bootstrap.js";
import { createQueryDataStore } from "./query-data-store.js";
import { DataAppLoadingContent } from "./components/DataAppLoadingContent.jsx";
function DataAppLoading() {
return (
<main className="dashboard-shell-loading" aria-busy="true">
<div className="dashboard-shell-loading-topbar" aria-hidden="true">
<div className="dashboard-shell-loading-line is-brand" />
<div className="dashboard-shell-loading-line is-action" />
</div>
<DataAppLoadingContent />
</main>
);
}
// The protected runtime accepts authored content and reviewed data at its boundary.
// Neither the source-build wrapper nor the prebuilt browser bundle owns a second
// copy of the shell, its contexts, or the hosted permission/fetch behavior.
export function DataAppRuntime({
reviewedSnapshot,
createContent,
DashboardContent,
ReportContent,
hosted = globalThis.location?.hostname.endsWith(".chatgpt.site") ?? false,
} = {}) {
const [snapshot, setSnapshot] = useState(hosted ? null : reviewedSnapshot);
const [presentationRecord, setPresentationRecord] = useState({ presentation: {}, revision: 0 });
const [error, setError] = useState(null);
const [queryDataStore, setQueryDataStore] = useState(null);
useEffect(() => {
if (!hosted) return;
const controller = new AbortController();
let active = true;
let store;
Promise.all([
loadHostedSnapshot(reviewedSnapshot, { signal: controller.signal }),
fetch("/api/presentation", { signal: controller.signal }).then(response => {
if (!response.ok) throw new Error("Data app presentation is unavailable.");
return response.json();
}),
])
.then(([{ snapshot: snapshotValue, deferred }, record]) => {
if (!active) return;
if (deferred) {
store = createQueryDataStore(snapshotValue, {
snapshotSha256: snapshotValue._dataAppQueryLoading.snapshotSha256,
onChange: queries => {
if (active) setSnapshot(current => ({ ...current, queries }));
},
});
setQueryDataStore(store);
}
setPresentationRecord(record);
setSnapshot(snapshotValue);
})
.catch((failure) => {
controller.abort();
if (active) setError(failure);
});
return () => {
active = false;
controller.abort();
store?.dispose();
};
}, [hosted]);
if (!snapshot) return error
? <main className="dashboard-shell-loading-error" role="alert">{error.message}</main>
: <DataAppLoading />;
return <ResolvedDataApp
snapshot={snapshot}
createContent={createContent}
DashboardContent={DashboardContent}
ReportContent={ReportContent}
hosted={hosted}
onSnapshotChange={setSnapshot}
presentationRecord={presentationRecord}
queryDataStore={queryDataStore}
/>;
}
function ResolvedDataApp({ snapshot, createContent, DashboardContent, ReportContent,
hosted, onSnapshotChange, presentationRecord, queryDataStore }) {
// Eager apps retain complete module-scope imports. Explicit on-demand apps
// declare dependencies through QueryDataBoundary before reading reviewed rows.
// Keep component identities stable through later data updates and edits.
const [content] = useState(() => createContent
? createContent(snapshot) : { DashboardContent, ReportContent });
const Content = snapshot.surface === "report" ? content.ReportContent : content.DashboardContent;
return (
<DataAppShell
snapshot={snapshot}
hosted={hosted}
onSnapshotChange={onSnapshotChange}
canEdit={!hosted || presentationRecord.canEdit === true}
initialPresentation={presentationRecord.presentation}
initialRevision={presentationRecord.revision}
queryDataStore={queryDataStore}
>
<Content />
</DataAppShell>
);
}
SHA-256: 3008a08c2a79c90a5ea5cb3f91b7f77113dfca1e8c4578bf47c34bd9932bd088