# 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.

> Use this when
>
> Reach for this layer when you need a
>
> typed, server-rendered web UI
>
> over your services — an internal admin panel, an operations console, or a customer-facing app — that cannot drift from your oRPC contracts. Use
>
> `@netscript/fresh`
>
> (the meta-framework) to build pages/islands in any Fresh app; use
>
> `apps/dashboard`
>
> (the scaffolded app) as the worked reference to copy patterns from. For the data boundary the UI renders see
>
> Services & contracts
>
> ; to add or regenerate UI pieces see
>
> Customize the Fresh UI
>
> .

## Two layers, one design grammar

**The meta-framework vs. the scaffolded app**

| 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/. |

> Don't conflate the package with the app
>
> @netscript/fresh
>
> is a
>
> meta-framework you import
>
> — it exposes targeted subpaths (
>
> /server
>
> ,
>
> /builders
>
> ,
>
> /route
>
> ,
>
> /query
>
> ,
>
> /form
>
> ,
>
> /defer
>
> ,
>
> /streams
>
> ,
>
> /interactive
>
> ,
>
> /vite
>
> ,
>
> /testing
>
> ,
>
> /error
>
> ) and can power
>
> any
>
> Fresh app, not just the scaffold.
>
> apps/dashboard
>
> is the
>
> app that consumes it
>
> . When this page says "the dashboard," it means the scaffolded app; when it says "
>
> @netscript/fresh
>
> ," it means the reusable engine.

## 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](https://rickylabs.github.io/netscript/reference/fresh/); the table below is the map.

**@netscript/fresh subpath exports (the meta-framework surface)**

| Name | Type | Description |
| --- | --- | --- |
| `@netscript/fresh/server` | `bootstrap` | defineFreshApp(...) — the baseline app bootstrap (static files, file-system routes) so every NetScript Fresh app boots identically. |
| `@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. |

> Distinct from the back-end stream producer
>
> The Fresh-side
>
> @netscript/fresh/streams
>
> client
>
> consumes
>
> durable streams over HTTP/SSE in the browser. The
>
> producer
>
> runtime — the thing that writes stream state — is real and lives server-side in
>
> @netscript/plugin-streams-core
>
> (
>
> createDurableStream
>
> ), served as an Aspire service on
>
> :4437
>
> . See
>
> Streams
>
> for the producer/consumer split.

## The scaffolded app: `apps/dashboard`

Every NetScript workspace ships `apps/dashboard` — a [Fresh](https://fresh.deno.dev/) 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.

**apps/dashboard layout (verbatim from a fresh scaffold)**

| Name | Type | Description |
| --- | --- | --- |
| `main.ts` | `entry` | App entry: export const app = defineFreshApp({ name: 'dashboard' }). Reads PORT (default 8010) and logs a startup banner. |
| `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. |

> Two ports, one app
>
> The dashboard's own dev server reads
>
> PORT
>
> and defaults to
>
> 8010
>
> (
>
> http://localhost:8010
>
> , health at
>
> /health
>
> ). That is distinct from the
>
> Aspire dashboard
>
> at
>
> https://localhost:18888
>
> , which orchestrates the whole resource graph. Bring the platform up first with
>
> cd aspire && aspire start
>
> (Postgres/Redis + every service), or run the app standalone with
>
> deno task --cwd apps/dashboard dev
>
> .

## 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.

```ts
// 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.

```tsx
// 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 };
```

```tsx
// 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>
);
```

> Islands are the only client code
>
> Fresh ships zero JavaScript by default. Anything under
>
> islands/
>
> is the
>
> only
>
> code hydrated in the browser; routes and components render on the server. The
>
> users
>
> showcase loads its list on the server, dehydrates the query cache, and the
>
> ServiceShowcaseLab
>
> island re-hydrates it for interactive refetch and optimistic status updates — the contract-to-pixel proof end to end.

## 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:

**What the registry ships**

| 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):

**Fresh UI CLI commands**

| 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. |

> DataGrid is a runtime export, not a ui:add block
>
> The generic
>
> DataGrid
>
> component — a typed
>
> columns
>
> /
>
> rows
>
> contract with
>
> strong
>
> /
>
> num
>
> cell variants and plain, button, or Fresh-navigation rows — is exported from the
>
> @netscript/fresh-ui
>
> package root, so you
>
> import it directly
>
> rather than
>
> ui:add
>
> -ing it. It is the runtime successor to the copy-source
>
> data-table
>
> block; both still ship, so reach for
>
> DataGrid
>
> when you want a templated grid you do not have to own and copy in. See the
>
> reference
>
> for its full API.

> Copy-source ownership tradeoff
>
> Because
>
> ui:add
>
> copies code into your repo, you own it: customize freely, but you do
>
> not
>
> get automatic upstream updates. Re-running
>
> ui:add … --force
>
> re-copies the source — and overwrites your local edits — so treat foundation files as yours and keep heavy customization in your own components. Full options live behind
>
> netscript ui:init --help
>
> /
>
> netscript ui:add --help
>
> and in the
>
> @netscript/fresh-ui reference
>
> .

## 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

**Fresh UI runtime surface**

| 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.

[Learn — Build a service  Guided tutorial: contract → users service on :3001 → typed client → the island that renders it in the dashboard.](https://rickylabs.github.io/netscript/netscript/tutorials/storefront/02-catalog-service/)

[Do — Customize the Fresh UI  Task recipe: ui:init / ui:add, the copy-source ownership model, and editing the design tokens.](https://rickylabs.github.io/netscript/netscript/web-layer/how-to/customize-fresh-ui/)

[Look up — @netscript/fresh reference  The meta-framework's full generated API: defineFreshApp, definePage, the query/route/form/defer/streams builders, and the server runtime.](https://rickylabs.github.io/netscript/netscript/reference/fresh/)

[Look up — @netscript/fresh-ui reference  The component registry, foundation set, design tokens, and the ui:init / ui:add surface.](https://rickylabs.github.io/netscript/netscript/reference/fresh-ui/)

[Telemetry & logging](https://rickylabs.github.io/netscript/netscript/observability/telemetry/) [Authentication](https://rickylabs.github.io/netscript/netscript/identity-access/auth/)
