# kjol-website — the kjol website

The landing page and documentation for the whole codebase. It is also the thing it
documents: every page runs the code it describes, and there is not a screenshot of a
component anywhere on the site.

It is **one server running two entirely different front-ends**, and that is the point of
it. kjol has two web layers — one written in Go and compiled to WebAssembly, one written
in Solid and bundled by a Go toolchain — and the only honest way to document both is to
build the site out of both.

## Run it

```sh
cd go/cmd/kjol-website
go run ./server -build     # cold build, then exit
go run ./server            # build, then SSR + /rsc + hot reload at http://localhost:8085
```

`go run ./server` performs that same build on every save and hot-swaps the result into
the browser, so day to day it is the only command you need. A `.go` save rebuilds the
wasm and swaps it in without a reload or a flash, preserving page state; a `.css` save
recompiles only Tailwind; a compile error lands in a browser overlay rather than in a
terminal you were not looking at.

## The shape of the site

| Path | Rendered by | What it is |
|---|---|---|
| `/`, `/about` | Go → WebAssembly, SSR'd | The landing page. The **Layers** menu is the site's primary navigation. |
| `/wasm/*` | Go → WebAssembly | **Kjol Wasm Web** — the gowasm engine: SSR + hydration, server components, data fetching, and the whole `webui` kit on one page (`/wasm/components`). |
| `/js/*` | Solid → esbuild, client-rendered | **Kjol JS Web** — the Solid kit, all of it on one page (`/js/components`), with a sidebar that jumps to each group. |
| `/js/ssr` | Solid → **goja, at request time** | A public page server-rendered with live data injected — the ISR path. |

Crossing between `/wasm` and `/js` is a real page load. They are different binaries, and
pretending otherwise would mean shipping both to every visitor.

## Layout

```
app/          Kjol Wasm Web — pages as plain Go functions returning a *VNode
  pages.go       Deps + Shell + the public/app layouts + the landing page
  layers.go      the Layers, as data. MIRRORED in frontend/src/layers.ts.
  docs.go        the docs chrome; docsNav() is the sidebar AND the index
  components.go  THE WHOLE KIT, on one page. componentGroups() is the single source
                 for both the sections and the sidebar that jumps to them.
  table.go       the AutoTable's data + controller (shared with its tests)
  chart.go data.go server_counter.go
  *.gen.go       GENERATED by kjol/cmd/wasmgen (routes, layout dispatch, RSC stubs)
wasm/         the js/wasm client entry (main_native.go is a host stub)
css/app.css   its Tailwind entry — kjol/tw scans the .go files for class names

frontend/     Kjol JS Web — .tsx pages written against @ui/*
  css/style.css   brand ONLY. kjol's theme.css is prepended by the bundler, and it
                  is the one that does `@import "tailwindcss"`.
  vendor/         pdf-lib + pdfjs-dist. @ui/AutoTable imports them at the TOP LEVEL,
                  so a bundle without them does not degrade — it fails to evaluate.
  src/app.ts      the SPA entry. It is .ts, not .tsx, because the bundler resolves
                  the entry as src/app.ts and nothing else — so it can hold no JSX.
  src/layers.ts   the Layers again. Keep in step with app/layers.go.

server/       ONE Go server: serves wwwroot, SSRs the wasm routes, hosts /rsc,
              mounts the Solid SPA at /js/*, serves the SSR'd public pages, and
              carries the -build flag.
build/        the build pipeline, in Go rather than a shell script, so the cold build
              and the watch loop call the SAME functions and cannot drift. It is a
              library, not a command: the server imports it, and Go will not let you
              import a main — hence `go run ./server -build` rather than `./build`.
internal/handlers/  the app side of the public-page inversion: kjol generates the
              registry; this owns the type and the document shell.
wwwroot/      both layers write here. They never collide: app.css / app.wasm for one,
              bundle.min.* for the other. One static dir, one server.
```

## Theming

One theme, both layers. The kits are themed by **semantic tokens** — components say
`bg-surface`, `text-ink`, `border-line` and never name a colour — so dark mode
re-points about a dozen CSS variables and not one component knows it happened.

The choice is stored under a single `kjol-theme` key that **both** front-ends read, so
switching to dark in `/wasm` and walking over to `/js` keeps it dark. A ten-line boot
script in the document head applies the class before first paint; without it every
dark-mode reader would get a white page until the bundle landed, and then have it
snatched away.

The only places a `dark:` variant survives are the two a re-pointed token cannot fix: a
coloured tint (a `red-50` wash is invisible on a near-black surface) and a fill that has
to invert (the neutral button, whose label must go dark when the fill goes pale).

## Adding things

**A Kjol Wasm Web page:** write the function, mark it `//gowasm:page /wasm/thing layout=app`,
build. `wasmgen` regenerates the routing. Add `static` to have it server-rendered.

**A component:** add a section to `app/components.go` (Go) or `frontend/src/pages/Components.tsx`
(Solid), and one entry to `componentGroups()` / `COMPONENT_GROUPS`. That single list drives the
sections, the sidebar that jumps to them, and the index — so none of the three can drift.

**A layer:** one entry in `app/layers.go` *and* one in `frontend/src/layers.ts`. They are
two files because nothing is upstream of both a WebAssembly binary and an esbuild bundle;
keeping each to a flat list of plain data is what makes that duplication survivable.

## How it maps onto the engine

| Package | Role | This app's use |
|---|---|---|
| `kjol/vdom` | neutral virtual DOM (native + wasm): `VNode`, `Signal`, `RenderHTML` | pages build `*VNode`; the server SSRs with `vdom.RenderHTML` |
| `kjol/wasmruntime` | wasm client runtime: reconcile, `Run`/`Hydrate`, router, fetch | `wasm/main.go` calls `Hydrate`/`Run` |
| `kjol/rsc` | stateless server components over HTTP | `//gowasm:server` + its generated client stub |
| `kjol/wasmdevserver` | reusable dev server: SSR, `/rsc`, hot reload, error overlay | `server/main.go` fills a `wasmdevserver.Config` |
| `kjol/webui` | the Go component kit | every `/wasm/*` page |
| `kjol/jsbundler` | TSX → Solid → esbuild, the Tailwind driver, the SSR bake | `build.JS`, and the ISR render at request time |
| `kjol/jsruntime` | the Solid kit, the vendored runtime, the icons, `theme.css` | everything under `/js/*` |
| `kjol/tw` | the Tailwind v4 engine, in Go | both stylesheets — it scans `.go` for one and `.tsx` for the other |

The **golden rule** holds throughout: no kjol package imports application code. The app
injects `Build`, `Render` and `Document` into `wasmdevserver`; it owns the `publicPage`
type that kjol's generated registry is written against. The dependency only ever points
one way.

## Its own module

`go.mod` declares module `kjolwebsite` with `replace kjol => ../..`, so its dependencies —
go-chart for the server-drawn charts, plus esbuild and goja by way of the bundler — stay
out of kjol, whose engine packages are stdlib-only. `go build ./...` at the kjol root
does not descend into this nested module; build it from here.
