Skip to main content

weft-frontend

Read when the user wants a page, app or site for the program, and before dispatching the frontend-builder: the verified scaffold commands, the default stack (pnpm, SvelteKit, PostgreSQL, BetterAuth, shadcn-svelte), calling the program's own routes and signal doors, the three variables that say where the program is, one shared PostgreSQL, where the api token lives, pictures as links, the build and its Dockerfile, and the shape for a site whose people each bring their own accounts or settings (the manager routes, the instance header and token, the settings page).

Source facts

Repository
WeaveMindAI/weft
Last source activity
October 20, 2026 at 17:35
Detected SKILL.md language
English
Stars
2,017
Forks
223

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
weft-frontend
description
Read when the user wants a page, app or site for the program, and before dispatching the frontend-builder: the verified scaffold commands, the default stack (pnpm, SvelteKit, PostgreSQL, BetterAuth, shadcn-svelte), calling the program's own routes and signal doors, the three variables that say where the program is, one shared PostgreSQL, where the api token lives, pictures as links, the build and its Dockerfile, and the shape for a site whose people each bring their own accounts or settings (the manager routes, the instance header and token, the settings page).
# The frontend [the frontend] is the pages where a person starts a run, answers a parked question, or reads what came out. It lives in `front/`, with its own toolchain, as a client of [the program], never a second backend. [the program] is the weft graph in `src/`, with its `Route` nodes (the `weft-api` skill) and its signals (the `weft-consumers` skill). The `frontend-builder` specialist builds [the frontend] and reads this skill; Tangle reads it whenever the request touches [the frontend]. ## The default stack If the user names a stack, that stack wins: you never override a technology the user asked for. If they name nothing, you build with these and ask no question about them: - **pnpm**, the package manager, and the only one you use. - **SvelteKit**, the framework (Svelte). - **PostgreSQL**, the database. - **BetterAuth**, the authentication. - **shadcn-svelte**, the components. You add a piece only when the request needs it. ## Starting the project Every command below runs without a prompt; this exact chain has been run and builds in under three seconds. You run it from the project root, one line at a time, each with a timeout of at most 60 seconds: ```bash pnpm dlx sv@0.17.0 create front --template minimal --types ts --no-add-ons --no-install pnpm dlx sv@0.17.0 add tailwindcss="plugins:none" --no-install --cwd front --no-git-check --no-download-check pnpm --dir front add clsx tailwind-merge && pnpm --dir front add -D tw-animate-css shadcn-svelte@1.6.1 && pnpm --dir front install ``` The Tailwind add-on writes `src/routes/layout.css`. `shadcn-svelte init` cannot be made quiet (it asks before it touches the stylesheet whatever flags it gets), so you never run it: you write the three files it would have written, then add components with `--yes --overwrite` (without `--overwrite`, `add` still asks "overwrite all existing files?" as soon as one of its files is there): `front/components.json`: ```json { "$schema": "https://shadcn-svelte.com/schema.json", "tailwind": { "css": "src/routes/layout.css", "baseColor": "zinc" }, "aliases": { "components": "$lib/components", "utils": "$lib/utils", "ui": "$lib/components/ui", "hooks": "$lib/hooks", "lib": "$lib" }, "typescript": true, "registry": "https://shadcn-svelte.com/registry" } ``` `front/src/lib/utils.ts`: ```ts import { clsx, type ClassValue } from 'clsx'; import { twMerge } from 'tailwind-merge'; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } export type WithoutChild<T> = T extends { child?: unknown } ? Omit<T, 'child'> : T; export type WithoutChildren<T> = T extends { children?: unknown } ? Omit<T, 'children'> : T; export type WithoutChildrenOrChild<T> = WithoutChildren<WithoutChild<T>>; export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & { ref?: U | null }; ``` `front/src/routes/layout.css`, replacing the one line the Tailwind add-on wrote. These are the zinc colours `init` writes; the components' classes (`bg-destructive`, `border-input`, `ring-ring`) name them, so without this block a destructive button renders with no colour at all: ```css @import 'tailwindcss'; @import "tw-animate-css"; @import "shadcn-svelte/tailwind.css"; @custom-variant dark (&:is(.dark *)); :root { --background: oklch(1 0 0); --foreground: oklch(0.141 0.005 285.823); --card: oklch(1 0 0); --card-foreground: oklch(0.141 0.005 285.823); --popover: oklch(1 0 0); --popover-foreground: oklch(0.141 0.005 285.823); --primary: oklch(0.21 0.006 285.885); --primary-foreground: oklch(0.985 0 0); --secondary: oklch(0.967 0.001 286.375); --secondary-foreground: oklch(0.21 0.006 285.885); --muted: oklch(0.967 0.001 286.375); --muted-foreground: oklch(0.552 0.016 285.938); --accent: oklch(0.967 0.001 286.375); --accent-foreground: oklch(0.21 0.006 285.885); --destructive: oklch(0.577 0.245 27.325); --border: oklch(0.92 0.004 286.32); --input: oklch(0.92 0.004 286.32); --ring: oklch(0.705 0.015 286.067); --chart-1: oklch(0.87 0 0); --chart-2: oklch(0.556 0 0); --chart-3: oklch(0.439 0 0); --chart-4: oklch(0.371 0 0); --chart-5: oklch(0.269 0 0); --radius: 0.625rem; --sidebar: oklch(0.985 0 0); --sidebar-foreground: oklch(0.141 0.005 285.823); --sidebar-primary: oklch(0.21 0.006 285.885); --sidebar-primary-foreground: oklch(0.985 0 0); --sidebar-accent: oklch(0.967 0.001 286.375); --sidebar-accent-foreground: oklch(0.21 0.006 285.885); --sidebar-border: oklch(0.92 0.004 286.32); --sidebar-ring: oklch(0.705 0.015 286.067); } .dark { --background: oklch(0.141 0.005 285.823); --foreground: oklch(0.985 0 0); --card: oklch(0.21 0.006 285.885); --card-foreground: oklch(0.985 0 0); --popover: oklch(0.21 0.006 285.885); --popover-foreground: oklch(0.985 0 0); --primary: oklch(0.92 0.004 286.32); --primary-foreground: oklch(0.21 0.006 285.885); --secondary: oklch(0.274 0.006 286.033); --secondary-foreground: oklch(0.985 0 0); --muted: oklch(0.274 0.006 286.033); --muted-foreground: oklch(0.705 0.015 286.067); --accent: oklch(0.274 0.006 286.033); --accent-foreground: oklch(0.985 0 0); --destructive: oklch(0.704 0.191 22.216); --border: oklch(1 0 0 / 10%); --input: oklch(1 0 0 / 15%); --ring: oklch(0.552 0.016 285.938); --chart-1: oklch(0.87 0 0); --chart-2: oklch(0.556 0 0); --chart-3: oklch(0.439 0 0); --chart-4: oklch(0.371 0 0); --chart-5: oklch(0.269 0 0); --sidebar: oklch(0.21 0.006 285.885); --sidebar-foreground: oklch(0.985 0 0); --sidebar-primary: oklch(0.488 0.243 264.376); --sidebar-primary-foreground: oklch(0.985 0 0); --sidebar-accent: oklch(0.274 0.006 286.033); --sidebar-accent-foreground: oklch(0.985 0 0); --sidebar-border: oklch(1 0 0 / 10%); --sidebar-ring: oklch(0.552 0.016 285.938); } @theme inline { --color-sidebar-ring: var(--sidebar-ring); --color-sidebar-border: var(--sidebar-border); --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); --color-sidebar-accent: var(--sidebar-accent); --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); --color-sidebar-primary: var(--sidebar-primary); --color-sidebar-foreground: var(--sidebar-foreground); --color-sidebar: var(--sidebar); --color-chart-5: var(--chart-5); --color-chart-4: var(--chart-4); --color-chart-3: var(--chart-3); --color-chart-2: var(--chart-2); --color-chart-1: var(--chart-1); --color-ring: var(--ring); --color-input: var(--input); --color-border: var(--border); --color-destructive: var(--destructive); --color-accent-foreground: var(--accent-foreground); --color-accent: var(--accent); --color-muted-foreground: var(--muted-foreground); --color-muted: var(--muted); --color-secondary-foreground: var(--secondary-foreground); --color-secondary: var(--secondary); --color-primary-foreground: var(--primary-foreground); --color-primary: var(--primary); --color-popover-foreground: var(--popover-foreground); --color-popover: var(--popover); --color-card-foreground: var(--card-foreground); --color-card: var(--card); --color-foreground: var(--foreground); --color-background: var(--background); --radius-sm: calc(var(--radius) * 0.6); --radius-md: calc(var(--radius) * 0.8); --radius-lg: var(--radius); --radius-xl: calc(var(--radius) * 1.4); --radius-2xl: calc(var(--radius) * 1.8); --radius-3xl: calc(var(--radius) * 2.2); --radius-4xl: calc(var(--radius) * 2.6); } @layer base { * { @apply border-border outline-ring/50; } body { @apply bg-background text-foreground; } } ``` Then, still from the project root (`--dir front` runs pnpm as if it started in `front/`; `pnpm dlx` does not take it, so shadcn-svelte gets its own `--cwd front`): ```bash pnpm dlx shadcn-svelte@1.6.1 add button card input --yes --overwrite --no-deps-install --cwd front && pnpm --dir front install pnpm --dir front run build ``` Every command here starts at the project root and names `front/` with a flag, never a `cd front && ...`: the project's command rules (`.codex/rules/tangle.rules`) do not allow `cd`, so a line that starts with one runs in the sandbox, where the network is closed and pnpm cannot fetch. If any command asks a question anyway, you kill it and report what it asked; you never answer it. A build takes seconds: you give it 30, and if it overruns, you kill it and report the output. ## Reaching the program's own infrastructure [the program] sometimes runs infrastructure of its own: a database, a cache, a broker, whatever its author gave it. When [the frontend] needs to speak to one of those directly, in that thing's own protocol, it goes through [the door]. [the door] is a piece of [the program]'s infrastructure that its node declared reachable from this machine. You find them yourself: ```` weft infra list-doors ```` Each line is a node, one of its endpoints, and the address it answers on (`db.sql 127.0.0.1:30080`). Nothing is listed unless the node that runs it declared it reachable, so the listing answers "what is reachable from here". Empty means nothing is, and that is not a thing you can change from here. A door that is listed answers only once its infrastructure is running, and nothing starts a piece the program itself never touches (a database only the site's sign-in uses): if yours does not answer, ask the orchestrator to run `weft infra start`. **A door is an address, not a key.** Being reachable and being allowed in are different questions, and the listing only answers the first: nearly everything worth reaching still asks you to prove who you are. So a listed door with no credential is not something to work around, it is the second half of the job, and you ask the orchestrator for it rather than hunting for it yourself. Where the credential comes from is the node's business, and the node says so on its card in the graph: the readouts there are how a piece of infrastructure hands out or resets what it needs, and the node's own description in the catalog names what it offers. If you want values the node shows on its card (a database's user, or a masked `secret` line like its password) in [the frontend]'s server environment, one command writes them all, `weft infra env <node> --into <server env file> --set DATABASE_USER=User --set DATABASE_PASSWORD=Password --set DATABASE_NAME=Database`, each `NAME=Label` taking the card item under that label (`weft infra show <node>` lists the labels, and `--as <NAME>` is the short form for the card's only secret). It prints the plain values it wrote and never a secret's, so a secret never passes through you. Plain values it cannot write (the door's host and port, from `weft infra list-doors`) you add to the same file yourself. The file is server-side only, never anywhere a browser can read. A secret the node hands out once (a database's password) has usually been taken by the time anyone looks, so expect `env` to say it was handed over already. The flow is the same every time: read the card (`weft infra show <node>` lists its items and its buttons), press the button that issues a new one if the secret is gone (`weft infra press <node> <action>`), then write the values with `weft infra env`. The button's warning (`weft infra show` prints it) says what a new secret cuts off; if something already uses the old one, cutting it off is the user's call, so you ask the orchestrator before pressing. Run each of these as one plain command from the project folder, exactly as written above: no pipe, no redirection, nothing joined to it but another allowed command, and `--into front/.env` for the path. The project's command rules (`.codex/rules/tangle.rules`) let `weft infra ...` run without asking only when every command on the line is one they allow. If one asks anyway, you are a subagent and the approval is refused: put the exact command in your report and go on with the rest, and the orchestrator runs it and tells you when the file is written. Nobody goes round a refusal by reading the secret
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub