Fresh UI & design
A design system earns its keep when the person — or agent — changing the UI can open every file involved. NetScript's Fresh UI layer is copy-source: components are installed into your repo as readable TypeScript and CSS you own, driven by design tokens you can edit, with a live gallery inside your own scaffolded app showing every primitive rendered with your tokens. Nothing in the visual layer hides behind a package boundary.
Why copy-source, concretely
The recurring failure in agent-edited UIs is not bad taste — it is a boundary. Ask for "a
destructive variant of our button" against a conventional installed component library and the agent
cannot edit the component (it lives in an immutable package), so it wraps it, overrides its styles
from outside, or hand-writes a lookalike div. Repeat that a few times and the codebase carries
three button dialects, none of them token-driven, and the next change costs more turns than the
first.
Here, netscript ui:add button copies the component's source and its CSS into
apps/dashboard/components/ui/, wired to the token layer in assets/tokens.json. The variant is
an edit to a file that is already in the repo, in the same grammar as every other component — and
the (design) route group in the scaffolded app renders the result immediately, next to every
other primitive, styled by the same tokens. One vocabulary, visible in one place, owned by you.
The rest of this page maps that layer end to end — and disentangles the two things it is easiest to conflate on the way in.
NetScript gives you a front end with the same contracts-first rigor as the back end — and it
does so in two distinct layers that are easy to conflate. There is @netscript/fresh, a real
meta-framework (a published package of Fresh runtime extensions, route/island builders, a
TanStack-Query bridge, forms, and a streams client), and there is the scaffolded dashboard app
(apps/dashboard) that the meta-framework powers. The package is the reusable engine; the app is
one fully-wired showcase built on top of it. Both ship in every workspace, but they live at
different addresses and you reach for them at different moments.
Two layers, one design grammar
| Name | Type | Description |
|---|---|---|
@netscript/fresh |
meta-framework (package) |
A published Deno package: Fresh runtime extensions, the definePage/island builders, a TanStack-Query bridge, forms, defer primitives, a streams client, and route contracts. Reusable across any Fresh app you build. Reference: /reference/fresh/. |
apps/dashboard |
scaffolded app |
One concrete Fresh 2.x application generated into your repo, built ON @netscript/fresh and wired to your users service. The worked showcase you copy patterns from — not a finished product UI. |
@netscript/fresh-ui |
component registry |
The copy-source component library + design tokens installed into apps/dashboard/components/ui/ via netscript ui:init / ui:add. You own the copied files. Reference: /reference/fresh-ui/. |
The meta-framework: @netscript/fresh
@netscript/fresh is a published package (Fresh runtime extensions, builders, forms, defer primitives, and route contracts for NetScript) built on @fresh/core 2.x + Preact. You consume it
through targeted subpath exports rather than one fat barrel — import only the surface you need.
The full generated API for every subpath lives at the
@netscript/fresh reference; the table below is the map.
| Name | Type | Description |
|---|---|---|
@netscript/fresh/server |
bootstrap |
defineFreshApp |
@netscript/fresh/builders |
page builder |
The definePage() / route-reference builders that bind a typed route, declare server-loaded layers, and wire telemetry. |
@netscript/fresh/route |
route contracts |
Route-reference primitives (createRouteReference) for typed, generated route patterns consumed via router.ts. |
@netscript/fresh/query |
TanStack bridge |
QueryIsland, useQuery, useMutation, useQueryClient — the TanStack-Query hydration bridge over your contract-derived query factories. |
@netscript/fresh/form |
forms |
Typed form primitives for server-validated, contract-aware form handling. |
@netscript/fresh/defer |
streaming defer |
Defer primitives for streaming/deferred server rendering of slower layers. |
@netscript/fresh/streams |
streams client |
The Fresh-side durable-stream client for consuming HTTP/SSE durable streams in the browser (the producer runtime lives in @netscript/plugin-streams-core). |
@netscript/fresh/interactive |
interactivity |
Interactive island runtime helpers used by the hydrated client islands. |
@netscript/fresh/vite |
build |
The Vite integration the dev/build pipeline uses. |
@netscript/fresh/testing |
testing |
Test helpers for exercising pages, islands, and route contracts. |
@netscript/fresh/error |
diagnostics |
Typed error/diagnostics surface for the Fresh runtime. |
The scaffolded app: apps/dashboard
Every NetScript workspace ships apps/dashboard — a Fresh 2.x
application built with Preact, Tailwind CSS v4, and Vite. It is a workspace member registered in the
root deno.json and orchestrated by Aspire alongside your services. It boots through
defineFreshApp from @netscript/fresh/server, the meta-framework's baseline bootstrap, so every
NetScript Fresh app starts the same way. It is not a placeholder: the scaffold wires it directly
to your oRPC contracts so the dashboard renders typed, server-prefetched data from the same users
service the back end implements.
| Name | Type | Description |
|---|---|---|
main.ts |
entry |
App entry: export const app = defineFreshApp |
router.ts |
routing |
Stable route entrypoint. Re-exports generated routePatterns + routes and builds typed appRoutes via createRouteReference. |
routes/ |
pages |
File-system routes: index.tsx, dashboard.tsx, health.tsx, examples/service/, examples/telemetry/, the (design) system pages, plus _app.tsx / _layout.tsx shells. |
islands/ |
interactivity |
Client-hydrated Preact islands (e.g. ThemeToggle, SidebarToggle, Toast under islands/ui/). |
components/ui/ |
design system |
The copy-source component library you own (@netscript/fresh-ui): button, card, data-table, form-field, badge, and more (tsx + matching CSS in assets/ui/). |
lib/ |
service wiring |
example-service.ts builds a typed oRPC client + query factories from your contract; cn.ts, public-types.ts. |
assets/ |
styling |
design.css, tokens.css/json, theme-bridge.css, and per-component CSS — the Tailwind v4 + design-token layer. |
.generated/ |
generated |
manifest.ts + routes.ts produced by the Fresh route generator; consume via router.ts, never directly. |
Contract-driven by default
The dashboard does not hand-roll fetch calls. apps/dashboard/lib/example-service.ts imports your
UsersContractV1 and turns it into a typed client and TanStack-Query factories through the NetScript
SDK — the same contract object the users service implements, so the UI cannot drift from the API.
// apps/dashboard/lib/example-service.ts
import { createServiceClient } from '@netscript/sdk/client';
import { createQueryFactories } from '@netscript/sdk/query';
import { bridgeInvalidation } from '@netscript/sdk/query-client';
import { UsersContractV1 } from '@plugin-smoke/contracts';
export const exampleServiceName = 'users';
export const exampleServiceClient = createServiceClient<typeof UsersContractV1>({
contract: UsersContractV1,
serviceName: exampleServiceName,
routerName: 'users',
});
// queryOptions / mutationOptions derived straight from the contract.
export const exampleServiceQueries = createQueryFactories({
service: { contract: UsersContractV1, client: exampleServiceClient },
}).service;
A route is declared with the meta-framework's definePage() builder (@netscript/fresh/builders)
— it binds a typed route reference, declares server-loaded layers, and wires telemetry — while an
island consumes the query factories above through the @netscript/fresh/query bridge for
hydration, optimistic mutations, and cache invalidation on the client.
// definePage() declares a typed page: route + server-loaded layers + telemetry.
import { appRoutes } from '@app/router.ts';
import { definePage } from '@app/utils.ts';
import { ServiceExampleLabPanel } from './(_components)/lab-panel.tsx';
import { loadServiceShowcaseData } from './(_shared)/service-showcase.ts';
export const serviceExamplePage = definePage()
.withRoute(appRoutes.serviceExample)
.withPolicy('balanced')
.withTelemetry({ enabled: true, spanName: 'scaffold.examples.users' })
.withMeta(() => ({ title: 'users example', description: 'Backed by the users service.' }))
.withLayer('lab', ServiceExampleLabPanel, { loader: loadServiceShowcaseData })
.build();
export const { default: page } = serviceExamplePage;
export { page as default };
// Client-hydrated island: typed query + optimistic mutation over the contract.
import { QueryIsland, useMutation, useQuery, useQueryClient } from '@netscript/fresh/query';
import { exampleServiceQueries, exampleServiceListInvalidation } from '@app/lib/example-service.ts';
const Lab = (props: { input: { status?: string } }) => {
const queryClient = useQueryClient();
const { data, refetch, isRefetching } = useQuery({
...exampleServiceQueries.list.queryOptions(props.input),
staleTime: 15_000,
});
const advance = useMutation({
...exampleServiceQueries.updateStatus.mutationOptions(),
onSettled: () => queryClient.invalidateQueries(exampleServiceListInvalidation),
});
return <button type='button' onClick={() => void refetch()}>{isRefetching ? '…' : 'Refresh'}</button>;
};
export default (props: { input: { status?: string } }) => (
<QueryIsland><Lab {...props} /></QueryIsland>
);
The design system & owned components
The scaffold installs the NetScript Fresh UI foundation (@netscript/fresh-ui) into
apps/dashboard/components/ui/ and its CSS into assets/ui/, driven by design tokens in
assets/tokens.json / tokens.css. Because the components are copied into your repo, editing the
UI is editing your own files — there is no framework component you cannot open. The bundled
(design) route group renders a live token, component, and composition gallery so you can see every
primitive in your project.
Astro's documentation answers "what does this look like?" with one-click browser sandboxes;
NetScript answers it from inside your repo — the (design) gallery is a live preview of the
components you actually installed, rendered with the tokens you actually set, served by your own
app. When you edit a token or a copied component, the gallery is where you watch the whole
vocabulary absorb the change.
What ships falls into four groups, each installable by registry id (ui:add <name>) or as a named
collection. The AI/workspace group is worth calling out: it is a finite, token-driven component
vocabulary for agent-facing surfaces — message threads, tool-call cards, citations, charts — that
an application can map structured model output onto, so generated UI lands in your design language
instead of ad-hoc markup:
| Name | Type | Description |
|---|---|---|
Interactive namespaces |
@netscript/fresh-ui/interactive |
Stateful, accessible compound primitives: Accordion, Dialog, Drawer, Popover, Sheet, Tabs, Tooltip, and the headless Combobox seam (useCombobox) that powers the command palette and autocompletes. |
L0 primitives |
@netscript/fresh-ui/primitives |
Small platform-contract helpers: Show (wrapper-free conditional render), VisuallyHidden and its SrOnly alias, and Icon — a first-class stroke-SVG icon primitive also re-exported from the package root. |
AI / workspace primitives |
ui:add |
The agent-surface set: avatar, citation-chip, code-block, model-selector, tool-call-card, chart-block, donut, prompt-input (auto-grows to fit its content), message (renderInline + TypingIndicator), and dropzone (drag, clipboard-paste, and multi-file ingest) — plus the foundation set (button, card, badge, data-table, forms, feedback, dashboard blocks). |
Command & search utilities |
ui:add |
command-palette (.ns-cmdk — the ⌘K modal that wraps the Combobox) and search (.ns-search — a compact nav affordance with a ⌘K hint that opens it). |
You manage that library with two CLI commands (run from the workspace root):
| Name | Type | Description |
|---|---|---|
netscript ui:init |
install foundation |
Installs the Fresh UI foundation set into an app workspace. The scaffold runs the equivalent for you; run it once when adding UI to an app that lacks it. |
netscript ui:add |
add an item |
Copies one registry item or a named collection into components/ui/, wires its CSS, and merges any required deno.json imports. Example: netscript ui:add data-table. |
Scope
The scaffolded dashboard is a working, contract-wired showcase — not a finished product UI. It
ships the users example (server-prefetched list + optimistic status mutation), a telemetry
example, a CRUD example, a health page, and the design gallery. Treat these as the canonical patterns
to copy from, not features to ship as-is; build your real screens by following the same
definePage + island + query-factory shape against @netscript/fresh. The meta-framework is the
durable surface — the app is one application of it.
Endpoints & ports
| Name | Type | Description |
|---|---|---|
:8010 |
port |
Dashboard dev server (Deno.env.get('PORT') || '8010'). Standalone: deno task --cwd apps/dashboard dev. |
/health |
HTTP |
App health route logged at startup (http://localhost:8010/health). |
:18888 |
Aspire |
Aspire dashboard that orchestrates the dashboard alongside services/plugins; token printed by aspire start. |
:3001 |
upstream |
The users service the dashboard's typed client calls — same UsersContractV1, no drift. |
:4437 |
streams |
The durable-streams Aspire service the @netscript/fresh/streams client consumes over HTTP/SSE (producer runtime in @netscript/plugin-streams-core). |
Where to go next
This hub is intentionally thin — the full generated APIs live in the reference. Pick the lane that matches what you're doing.