# kjol-web — 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-web go run ./build # cold build: both halves go run ./server # 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, the `webui` kit, overlays, AutoTable, charts, client fetching. | | `/js/*` | Solid → esbuild, client-rendered | **Kjol JS Web** — the Solid kit: components, forms, AutoTable, theming. | | `/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/ the Go/WASM half — 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 kit.go table.go overlays.go 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/ the Solid half — .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. internal/handlers/ the app side of the public-page inversion: kjol generates the registry; this owns the type and the document shell. buildsteps/ the build, in Go rather than a shell script, so the one-shot build and the dev server's watch loop call the SAME functions and cannot drift. wwwroot/ both halves 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 halves. 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 Go/WASM 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 Solid page:** write the `.tsx`, add it to `routes` in `frontend/src/app.ts` and to `NAV` in `frontend/src/layout/Shell.tsx`. **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 | `buildsteps.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 `kjolweb` 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.