# Tangled
`svfe` is the web frontend for [yumak](https://git.klbr.net/ptr.pet/yumak) (`knot-static`), a static [tangled](https://tangled.org) knot. it lives in [`web/`](web/) and is built with sveltekit (svelte 5) and tailwind 4.
https://tangled.org
- every read is a plain `GET /xrpc/...` against the knot serving the app, so a static knot serves the whole read path straight from nginx.
- writes (issues, pull requests, comments, attachments, settings) go to the user's pds, the knot's write cgi, or the repo's spindle.
- [`svfe-pages`](https://git.klbr.net/ptr.pet/svfe-pages) runs after each knot activation to prerender repo and user pages into static html, so pages work without js and hydrate into the spa when js is on.
The next-generation social coding platform.
[`git.klbr.net`](https://git.klbr.net) runs `svfe` and hosts this repo (`did:plc:4xstu3hkbaavp4kjddqtvqi2`).
## Overview
## how `svfe` and `svfe-pages` fit together
This monorepo contains the majority of Tangled's open source code. Notably, it
includes:
building `packages.<system>.svfe-web` produces two outputs in `$out`:
* [knot](https://tangled.org/tangled.org/core/tree/master/knot2) for git hosting and operations,
* [spindle](https://tangled.org/tangled.org/core/tree/master/spindle) for continuous integration,
* [bobbin](https://tangled.org/tangled.org/core/tree/master/bobbin), the indexing service,
- `build/` — the client spa bundle (`index.html`, `/_app/immutable/*`, `/fonts/*`). nginx serves assets from here and falls back to `/index.html` for any route that wasn't prerendered (such as deep commit history or repos over their disk budget).
- `build-server/` — the sveltekit ssr bundle (`build-server/index.js`) with its production `node_modules`. `svfe-pages` imports this bundle in worker processes to render pages against the knot's local xrpc surface.
... and more.
you can serve `build/` on its own as a pure client spa in front of a knot, or pair it with `svfe-pages` as a `knot-static` hook so pages are prerendered on disk.
## Bugs
## installation & nixos example
Please file any issues about this code or the hosted service on the [issue
tracker](https://tangled.org/tangled.org/core/issues). You may also use our
[Discord](https://chat.tangled.org) for the same.
flake inputs:
## Security
```nix
inputs.knot-static.url = "git+https://git.klbr.net/did:plc:amxsvho4opuwe53msnkbwa5j?ref=main";
inputs.svfe.url = "git+https://git.klbr.net/did:plc:4xstu3hkbaavp4kjddqtvqi2?ref=main";
inputs.svfe-pages.url = "git+https://git.klbr.net/did:plc:qaxvrxxd6ubibxniuzaktvon?ref=main";
```
If you've identified a security issue in Tangled, please email
[security@tangled.org](mailto:security@tangled.org) with the details!
wiring `svfe` and `svfe-pages` into `services.knot-static` on nixos:
## Contributing
```nix
let
system = pkgs.stdenv.hostPlatform.system;
svfe = inputs.svfe.packages.${system}.svfe-web;
svfe-pages = inputs.svfe-pages.packages.${system}.svfe-pages;
knot = config.services.knot-static;
inner = "http://${knot.listen}";
pages = "/var/lib/svfe-pages";
in
{
services.knot-static = {
enable = true;
hostname = "git.klbr.net";
settings.index.links = "https://git.klbr.net";
PRs welcome! But please file a [proposal for bigger
changes](https://tangled.org/docs/contributing/guide/#proposals-for-bigger-changes).
We have strict [rules for commit
messages](https://tangled.org/docs/contributing/guide/#commit-guidelines) (or
skim `git log`).
# prerender repo and user pages after every activation
settings.hooks = [
{
name = "svfe-pages";
command = lib.getExe svfe-pages;
args = [
"--server" "${svfe}/build-server"
"--out" pages
"--read" inner
"--workers" "4"
];
}
];
We require a Developer Certificate of Origin (DCO) `Signed-off-by:` line in
commits. [Read more
here](https://tangled.org/docs/contributing/guide/#developer-certificate-of-origin-dco).
nginx.virtualHost.locations = {
# serve prerendered html first, then knot routes, then the client spa
"/" = {
proxyPass = null;
root = "${pages}/www";
extraConfig = ''
gzip_static always;
gunzip on;
if ($arg_page ~ "^0*([2-9]|[1-9][0-9]+)$") {
set $page $1;
rewrite ^(.*)$ "$1~page-$page?" last;
}
add_header Cache-Control "no-cache";
try_files $uri.html $uri/index.html @knot;
'';
};
"@knot" = {
proxyPass = inner;
extraConfig = ''
proxy_intercept_errors on;
error_page 404 = @spa;
'';
};
"@spa" = {
root = "${svfe}/build";
extraConfig = ''
gzip_static on;
try_files $uri /index.html =404;
'';
};
"/fonts/" = {
root = "${svfe}/build";
extraConfig = ''
gzip_static on;
add_header Cache-Control "public, max-age=604800";
'';
};
"/_app/" = {
root = "${svfe}/build";
extraConfig = ''
gzip_static on;
add_header Cache-Control "no-cache";
'';
};
"^~ /_app/immutable/" = {
root = "${pages}/www";
extraConfig = ''
gzip_static on;
expires max;
try_files $uri @immutable;
'';
};
"@immutable" = {
root = "${svfe}/build";
extraConfig = ''
gzip_static on;
expires max;
'';
};
};
};
}
```
## Legal
if your knot isn't on `https://git.klbr.net`, override `origin` so the oauth client id matches your domain:
The contents of this repository are licensed under MIT.
```nix
svfe = inputs.svfe.packages.${system}.svfe-web.override {
origin = "https://knot.example.com";
};
```
## development
inside [`web/`](web/), you need node 24, pnpm 11 (`corepack enable`), and bun 1.4+:
```sh
cd web
pnpm install
pnpm dev # http://127.0.0.1:5173
```
by default the app reads from the origin serving it. to point local dev at a deployed knot, set `VITE_BOBBIN_URL` in `web/.env`:
```sh
VITE_BOBBIN_URL=https://git.klbr.net
```
other build-time settings live in [`web/src/lib/config.ts`](web/src/lib/config.ts).
```
pnpm dev dev server on :5173
pnpm check svelte-check + typescript
pnpm lint prettier, eslint, test-id check
pnpm test unit tests (vitest)
pnpm storybook component stories on :6006
pnpm build client app into build/, ssr bundle into build-server/
pnpm codegen regenerate lexicon types
```
## credits
forked from [tangled](https://tangled.org)'s monorepo (`web/`). self-hosted fonts are [host grotesk](https://github.com/farukgobal/Host-Grotesk) and [kode mono](https://github.com/isaozler/kode-mono) (both ofl-1.1).