Files
kjol/CLAUDE.md
2026-07-13 00:53:44 -04:00

5.0 KiB

kjol

kjol ("keel" in Norwegian) is a shared, multilingual base layer factored out of the user's applications so they stay in sync. It is consumed as a git submodule inside each app (at <app>/kjol). Current consumers: cdrateline.com_2.0 and Hotlap — near-identical forks it was extracted from. Scope will grow to more projects and languages.

Golden rules

  1. The framework NEVER imports application code. Wherever kjol needs app-specific behavior, the app injects it (interfaces, registration functions, config structs, callbacks). See Coupling inversions below. If you find yourself wanting to import "<app>/internal/..." from kjol, invert it instead.
  2. Never run git operations here on the user's behalf. The user creates the submodule and makes all commits. You may edit files, build, and test.
  3. kjol is edited in place via the submodule + go.work — there is no publish / go get step. Editing a kjol file takes effect in the consuming app immediately.
  4. When a file exists in both apps and has drifted, reconcile by merging best-of-both.

Organization — by language

Each top-level directory is one language / build root:

kjol/
  go/    all Go. Module `kjol` (go.mod lives in go/). Imports are `kjol/<pkg>`.
  web/   all JS/TS (browser + SSR). No build system of its own; built by go/bundler.
  # future: cpp/  kotlin/  swift/

Language-first, not feature-first. Consequence: the bundler is Go and lives in go/bundler even though it builds web/. Don't "fix" this by splitting it.

go/ — module kjol

Packages, imported as kjol/<name>: appenv basic chrono config csv dbutil finance httputil l4g security snailmail validation bundler, plus the gowasm web-UI engine (vdom wasmruntime rsc wasmdevserver — author components in pure Go compiled to WebAssembly; all stdlib-only), and cmd/{bundle,migrate,loc,passgen,typecheck,wasmgen}. A runnable example lives in cmd/examples/go-wasm-web (its own nested module so its go-chart dep stays out of kjol).

Build / test (run from repo root):

go -C go build ./...
go -C go vet ./...
go -C go test ./...

web/

  • kit/ — Solid.js .tsx component kit. Apps import components as @ui/*.
  • runtime/ — vendored Solid runtime + vendor.json (base entrypoints). The app merges its own vendor.json (chart.js, pdf-lib, ...) on top; kjol's solid-js must resolve first so there is a single reactive instance.
  • icons/ — FontAwesome SVG source kit (the bundler scans usage and generates a per-app registry; the generated file is app-owned, not committed here).
  • styles/theme.css (@theme scaffold + :root fa vars). Brand color/font tokens stay app-side; the app's style.css @imports this.
  • auth/ utils/ hooks/ ssr/ env.ts basic.ts finance.ts superfun.ts types.d.ts — generic TS scaffolding. Apps import as @kjol/*. (Concrete permission constants stay app-side.)

Frontend import aliases (resolved by the bundler and mirrored in each app's tsconfig paths): @ui/*web/kit, @kjol/*web/, @appgen/* → the app's generated dir (e.g. the FA faIcons registry — app-owned, gitignored, regenerated each build). The kit's own imports of sibling components stay relative (./Buttons.tsx).

Consumption (per app)

  • go.work at the app root:
    use (
        .
        ./kjol/go
    )
    
    then vendor with go work vendor (not go mod vendor).
  • Startup wiring the app performs (this is how the inversions get their app-side halves): dbutil.RegisterAll(models.Tables), l4g.SetDatabaseWriter(...), dbutil.Init(dbutil.ConnConfig{...}), snailmail.Configure(snailmail.Settings{...}), a thin config.Load wrapper over config.Load[T], and an app-side internal/httpauth for the session/authn middleware.

Coupling inversions (how the framework stays app-agnostic)

Package Inversion
dbutil table names via Register/RegisterAll (not the app's models.Tables)
l4g owns the Entry type; DB persistence via SetDatabaseWriter(func(Entry) error)
config generic Load[T](file, *T) error; each app defines its own config struct
dbutil.ConnConfig, snailmail.Settings DB / mail credentials injected, never read from app config
appenv compile-time environment via build tags (-tags staging / -tags production); the bundler reads appenv.Environment for the JS __ENV_TYPE__ define
httputil.CorsMiddleware(CorsConfig{...}) allowed domains + bundle-version source injected

Stays app-side (never moves into kjol)

Domain models/repository/handlers-api, migrations, pages/routes/layouts, brand UI (TopBar/AppSidebar/TransitionOverlay), concrete permission constants, the app config struct, embed.go + wwwroot/, and generated artifacts (faIcons registry, routes.gen.ts, public_pages.gen.go).

Provenance

Big-bang extraction from cdrateline + Hotlap. The detailed migration plan lives on the author's machine at ~/.claude/plans/foamy-humming-tulip.md.