restructure project, add claudemd
This commit is contained in:
101
CLAUDE.md
Normal file
101
CLAUDE.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# 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 `cmd/{bundle,migrate,loc,passgen,typecheck}`.
|
||||
|
||||
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` `@import`s 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`.
|
||||
Reference in New Issue
Block a user