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, one shared PostgreSQL, where the api token lives, pictures as links, the build, and the shape for a site whose people each bring their own accounts or settings (members: the manager routes, the member header and token, their settings page).

跳到安装

来源信息

仓库
WeaveMindAI/weft
最近来源活动
2026年9月26日 20:12
检测到的 SKILL.md 语言
英语
星标
1,982
分支
221

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
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, one shared PostgreSQL, where the api token lives, pictures as links, the build, and the shape for a site whose people each bring their own accounts or settings (members: the manager routes, the member header and token, their 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 cd front && pnpm add clsx tailwind-merge && pnpm add -D tw-animate-css shadcn-svelte@1.6.1 && pnpm 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 in `front/`: ```bash pnpm dlx shadcn-svelte@1.6.1 add button card input --yes --overwrite --no-deps-install && pnpm install pnpm run build ``` 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 `cd` in front, no `&&`, no pipe, and `--into front/.env` for the path. The permission checker approves `weft infra ...` only when the command is written that way; wrapped in anything else, it goes to an automatic check that refuses writing a password into a file. If one is refused anyway, you are a subagent: 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 or copying it onto a command line; if the orchestrator is refused too, it hands the user the exact command. **If you need a door that is not listed, you report it and stop on that part.** Whether a piece of infrastructure is reachable is part of what [the program] is, written in the node that runs it, so it is the orchestrator's to change and never yours. Say which infrastructure you need and why, and the orchestrator either makes it reachable or builds what was missing and dispatches you again. You never go round it. If you catch yourself running `kubectl`, reading a node's source for a password, or opening a shell in a container, stop and write verbatim "Wait. A door is listed or it does not exist." Then report and work on something else meanwhile. **You never stand up your own copy of something [the program] runs**, and you never tell the user the two halves cannot share it. A second database beside the program's, a second cache, a second broker: each is two sources of truth
在 GitHub 查看
这个 SKILL.md 很大,SkillsMP 这里只预览前一段内容。 在 GitHub 查看