Interactive islands
The interactive entrypoint of @netscript/fresh is intentionally small. It exposes the
browser-facing seams that the framework owns directly, while route builders, server helpers,
and copy-based UI registry components stay on explicit subpaths. Reach for this surface when an
island needs to read an in-flight promise during render using the Suspense throw-promise
protocol, rather than threading loading state through props.
The interactive entrypoint
@netscript/fresh keeps its interactive runtime narrow on purpose. The package documents this
boundary directly: the entrypoint is limited to package-owned interactive seams, so anything
that is route-shaped, server-shaped, or registry-shaped lives elsewhere and is consumed through
its own path.
Today that surface is two functions built around promises:
usePromise<T>reads a promise during render using the Suspense throw-promise protocol. When the promise is still pending, the call throws the promise so a surrounding Suspense boundary can show its fallback; when the promise is fulfilled, the call returns the resolved value of typeT.resolvedPromise<T>creates a promise that is already primed as fulfilled with a given value. It pairs withusePromise()so a component can be driven with a value that is available synchronously without special-casing the read path.
Because usePromise participates in Suspense, the island that calls it should be rendered inside
a Suspense boundary that supplies a fallback. The deferred and streaming UI surface covers how
those boundaries are wired across the server render; see
Deferred and streaming UI.
Reading a promise inside an island
The example below composes only the two documented symbols. usePromise reads the promise to
get its resolved value, and resolvedPromise provides an already-fulfilled promise for the case
where the value is available up front.
import { resolvedPromise, usePromise } from "@netscript/fresh/interactive";
interface Metric {
label: string;
value: number;
}
function MetricReadout({ data }: { data: Promise<Metric> }) {
// Suspends while `data` is pending; returns the resolved Metric once fulfilled.
const metric = usePromise(data);
return (
<p>
{metric.label}: {metric.value}
</p>
);
}
// An already-fulfilled promise, suitable for synchronous values.
const seeded = resolvedPromise<Metric>({ label: "Sessions", value: 42 });
export default function MetricIsland() {
return <MetricReadout data={seeded} />;
}
API summary
| Symbol | Description |
|---|---|
usePromise<T>(promise: Promise<T>): T |
Read a promise using the Suspense throw-promise protocol. |
resolvedPromise<T>(value: T): Promise<T> |
Create a promise already primed as fulfilled for usePromise(). |
Companion UI helpers
The copy-based registry components of @netscript/fresh-ui live on workspace-local deep paths so
applications can own and evolve them after copy. Its root entrypoint exposes only the supported
helper utilities that are safe to consume as package runtime dependencies, including:
cn(...inputs: ClassValue[]): string— combines clsx and tailwind-merge for class merging.getToast(url: URL): RegistryToast | undefined— reads a toast payload from a URL when redirect-flash query parameters are present.withToast(path: string, toast: RegistryToast): string— appends a toast payload to a relative application path.stripToastFromUrl(url: URL): string— removes all toast query parameters while preserving path and hash.
For the full Web Layer UI surface and the registry copy model, see the pillar hub at /web-layer/.
Related
Wire the Suspense boundaries that usePromise relies on.
Data loading and the query cacheLoad and cache data that islands consume.
The Fresh page modelServer rendering and the page contract.
- Live dashboard tutorial — the flagship walkthrough that brings interactive islands together end to end.