Files
kjol/go/cmd/kjol-web/README.md

6.7 KiB

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

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.