Files
layonara-forge/HANDOFF.md
T
plenarius cbe51a6e67 feat: complete UI/UX overhaul with Impeccable design system
Replace Inter/Baskerville with self-hosted Manrope/Alegreya/JetBrains Mono
variable fonts. Migrate all colors from hex to OKLCH tokens (30+ CSS custom
properties) with full dark/light mode support. Replace Unicode emoji with
lucide-react SVG icons throughout. Convert all page layouts to inline styles
(Tailwind CSS 4 flex/grid classes unreliable in this project). Code-split
routes via React.lazy (760KB → 15KB initial shell + 10 lazy chunks).

Add global styles: scrollbar theming, selection color, input/button bases,
:focus-visible ring, prefers-reduced-motion. Setup wizard gets 4-phase
indicator with numbered circles, PathInput and StatusDot components.
Toast container gets aria-live="polite". Tab close buttons changed to
proper <button> elements with aria-labels.

All 8 pages (Dashboard, Editor, Build, Server, Toolset, Repos, Settings,
Setup) rewritten with consistent card/section/button patterns.
2026-04-21 03:06:29 -04:00

453 lines
23 KiB
Markdown

# Layonara Forge — Agent Handoff Document
## What Is This
Layonara Forge is a purpose-built NWN (Neverwinter Nights) Development IDE that runs as a local web application in Docker. It lets contributors fork, clone, edit, build, and run a complete Layonara NWNX NWN server with zero native tooling — only Docker required. The project name was chosen during brainstorming: "Forge" evokes crafting/building in a fantasy context.
## Project Location
- **Forge codebase**: `/home/jmg/dev/layonara/layonara-forge/`
- **Design spec**: `/home/jmg/dev/docs/superpowers/specs/2026-04-20-layonara-forge-design.md`
- **Implementation plans**: `/home/jmg/dev/docs/superpowers/plans/2026-04-20-forge-plan-{1..6}-*.md`
- **Gitea integration plan**: See Cursor plan file `gitea_+_forge_integration_8e0df077.plan.md`
- **Design context**: `.impeccable.md` at project root (personality: arcane, precise, deep)
## Architecture
```
Host Machine
├── Browser → localhost:3000 (Forge UI)
├── NWN Toolset → writes GFFs to modules/temp0/
├── ~/layonara-workspace/ (repos, server data, config)
└── Docker Socket
Docker Network
├── layonara-forge (Node.js + React, serves UI, manages everything via Docker socket)
├── layonara-builder (ephemeral: nasher, nwn_script_comp, layonara_nwn, cmake, gcc)
├── layonara-nwserver (ghcr.io/plenarius/unified — NWN:EE + NWNX)
└── layonara-mariadb (mariadb:10.11 — game database)
```
The Forge container manages sibling containers via the Docker socket (Portainer pattern). Contributors clone one repo, set two paths in `.env`, run `docker compose up`, and open a browser.
## Tech Stack
- **Backend**: Node.js 20 + Express 5 + TypeScript (ES modules)
- **Frontend**: React 19 + Vite 6 + Tailwind CSS 4 (utility classes unreliable — inline styles used for layout)
- **Icons**: lucide-react (SVG icons throughout)
- **Fonts**: Self-hosted variable fonts via @fontsource-variable (Manrope, Alegreya, JetBrains Mono)
- **Code editor**: Monaco Editor with NWScript Monarch tokenizer
- **NWScript LSP**: Forked `layonara/nwscript-ee-language-server` connected via WebSocket JSON-RPC
- **Terminal**: xterm.js with child_process.spawn shell sessions
- **Docker API**: dockerode
- **Git**: simple-git (named import: `import { simpleGit } from "simple-git"`)
- **Git provider**: Gitea at `https://gitea.layonara.com` (NOT GitHub — see Gitea section)
- **Real-time**: WebSocket via ws library
## What's Built (55 commits + UI overhaul session)
All 6 implementation plans are complete. The codebase compiles and both Docker images build successfully. A complete UI/UX overhaul was performed (see "UI/UX Overhaul" section below).
### Backend Services (`packages/backend/src/services/`)
| Service | File | Purpose |
| ------------ | ------------------------- | ------------------------------------------------------------------ |
| WebSocket | `ws.service.ts` | Event broadcasting to all connected clients |
| Workspace | `workspace.service.ts` | Directory structure management, forge.json config |
| Docker | `docker.service.ts` | Container CRUD, image pulls, ephemeral container runs |
| Build | `build.service.ts` | Module compile/pack, hot-reload, hak builds, NWNX builds |
| Server | `server.service.ts` | NWN server stack lifecycle (start/stop/restart MariaDB + nwserver) |
| Toolset | `toolset.service.ts` | temp0/ file watcher, GFF→JSON conversion, change management |
| Editor | `editor.service.ts` | File CRUD, directory trees, workspace search |
| Git | `git.service.ts` | Clone, pull, commit, push, diff, upstream polling |
| Git Provider | `git-provider.service.ts` | Gitea API (fork, PR, token validation) |
| Terminal | `terminal.service.ts` | Shell session management via child_process |
| LSP | `lsp.service.ts` | NWScript language server process management |
### Backend Routes (`packages/backend/src/routes/`)
| Route | Prefix | Endpoints |
| --------- | ---------------- | ----------------------------------------------------------------- |
| workspace | `/api/workspace` | GET /config, PUT /config, POST /init |
| docker | `/api/docker` | containers, pull, start/stop/restart, logs |
| build | `/api/build` | module/compile, module/pack, deploy, compile-single, haks, nwnx |
| server | `/api/server` | status, start, stop, restart, generate-config, seed-db, sql |
| toolset | `/api/toolset` | status, start, stop, changes, apply, apply-all, discard |
| editor | `/api/editor` | tree, file CRUD, search, resref, tlk, 2da, gff-schema |
| github | `/api/github` | validate-pat (actually Gitea token), fork, forks, pr, prs |
| repos | `/api/repos` | clone, list (/), status (/:repo/status), pull, commit, push, diff |
| terminal | `/api/terminal` | sessions CRUD |
### Frontend Pages (`packages/frontend/src/pages/`)
| Page | Route | Purpose |
| --------- | ----------- | ----------------------------------------------------------- |
| Dashboard | `/` | Server status, repo summary, quick actions (3-column cards) |
| Editor | `/editor` | Monaco editor with file explorer, tabs, GFF visual editors |
| Build | `/build` | Module/hak/NWNX build sections with streaming output |
| Server | `/server` | Controls, log viewer with filter, SQL console |
| Toolset | `/toolset` | temp0/ watcher status, change table, diff viewer |
| Repos | `/repos` | Git status cards, commit dialog, PR creation |
| Settings | `/settings` | PAT, theme, editable paths, Docker images, shortcuts, reset |
| Setup | `/setup` | 4-phase onboarding wizard with 10 steps |
### Special Features
- **NWScript syntax highlighting**: Monarch tokenizer with keyword/type/comment/string/preprocessor rules
- **SQL highlighting in NWScript strings**: Detects `NWNX_SQL_PrepareQuery()` calls, highlights SQL keywords in teal
- **Resref auto-lookup**: Backend indexes all GFF JSON files, hover on resref strings shows the item/creature/area
- **TLK preview**: Hover on integer literals shows the TLK string (handles 16777216 custom offset)
- **2DA intellisense**: Parses 2da files, provides completion for `Get2DAString` calls
- **Visual GFF editors**: Form-based editors for .uti, .utc, .are, .dlg, .utp, .utm JSON files
- **Conventional commit enforcement**: Type dropdown (feat/fix/refactor/etc), rejects malformed messages
- **Dark/light theme**: OKLCH CSS custom properties toggled via `light` class on root element
## UI/UX Overhaul (April 21, 2026 session)
A complete design overhaul was performed using the [Impeccable](https://impeccable.style/) design skill system. The design context is documented in `.impeccable.md`.
### Design System
**Personality**: Arcane, precise, deep — a craftsman's workbench.
**Fonts** (all self-hosted via `@fontsource-variable`, no Google Fonts):
- **Body/UI**: Manrope Variable — warm geometric sans
- **Headings**: Alegreya Variable — calligraphic serif with manuscript roots
- **Code/mono**: JetBrains Mono Variable
**Color palette** (full OKLCH, 30+ tokens in `globals.css`):
- Surfaces tinted toward amber (hue 65) — "warm darks, not cold ones"
- 3-level depth: `--forge-bg``--forge-surface``--forge-surface-raised`
- Accent: evolved gold `oklch(58% 0.155 65)` with hover and subtle variants
- Semantic colors: success (forest green, hue 150), danger (brick red, hue 25), warning (golden, hue 80), info (steel blue, hue 230)
- Each semantic color has base, bg, and border variants for both dark and light modes
- Log panels: dedicated `--forge-log-bg` / `--forge-log-text` tokens
**Icons**: lucide-react SVG icons throughout (Code2, Wrench, Hammer, Play, GitBranch, Settings, Sun/Moon, Terminal, etc.)
**Type scale** (fixed rem for IDE density):
- `--text-xs` (11px) through `--text-2xl` (28px), ~1.25 ratio
### What Changed
**Foundation**:
- Replaced Inter font with Manrope Variable, Baskerville with Alegreya Variable
- Removed Google Fonts `<link>` — all fonts bundled as npm deps
- Full OKLCH palette replacing all hex values (~60 hard-coded colors replaced)
- All Tailwind semantic color classes (`green-400`, `red-500/20`, etc.) replaced with forge tokens
- Global CSS: scrollbar theming, selection color, input/button base styles, `:focus-visible` ring, `prefers-reduced-motion`
**IDE Shell** (`IDELayout.tsx`):
- Lucide SVG icons replacing Unicode emoji in nav rail
- Removed 3px left border stripe (impeccable anti-pattern ban)
- Sidebar only shows on `/editor` route (was showing on all pages)
- All layout uses inline styles (Tailwind flex classes were not reliably applying)
- Terminal toggle bar with Terminal/Chevron icons
**All 8 pages rewritten** with consistent patterns:
- Card containers: `--forge-surface` bg, `--forge-border`, `0.75rem` radius
- Section headers: uppercase, `--text-xs`, icon + label
- Buttons: accent primary, outline secondary, danger for destructive
- Status badges: semantic colors with dots
- All inline styles (Tailwind utility classes unreliable for layout in this project)
**Setup wizard**:
- 4-phase indicator (Environment → Authentication → Repositories → Finalize) with numbered circles + connecting lines, matching James's work app wizard pattern
- Steps reordered: Workspace + NWN Home before Gitea Token
- PathInput component with folder icon for path fields
- StatusDot component replacing emoji (✅❌⏳) with styled HTML elements
- Navigation: ghost "← Back" left, accent "Next →" right, border-top separator
**Performance**:
- Routes code-split via `React.lazy()` — 10 chunks instead of 1 (760KB → initial 15KB app shell)
- Page chunks: Editor 98KB, Setup 16KB, Repos 13KB, others 5-8KB each
**Accessibility**:
- `:focus-visible` outline on all interactive elements
- `aria-label="Main navigation"` on nav
- Tab close button changed from `<span>` to `<button aria-label="Close tab">`
- Toast container has `aria-live="polite"` + `role="status"`
- `window.confirm()` guards on destructive actions (Discard All, Reset Setup)
- SetupGuard shows "Loading Forge…" instead of blank screen
### Important: Tailwind CSS 4 Quirk
Tailwind CSS 4 utility classes for layout (`flex`, `flex-1`, `items-center`, etc.) do NOT reliably apply in this project. All critical layout uses **inline styles** instead. This is a conscious decision, not laziness. The Tailwind `@import "tailwindcss"` is still loaded and works for some utilities (`rounded`, `overflow-hidden`, etc.) but **do not rely on Tailwind classes for flex/grid layout**. Use inline `style={{}}` props.
## Docker Images
### layonara-forge (563MB)
- Base: `node:20-slim`
- Multi-stage build: builder stage compiles TS + Vite, production stage has only runtime deps
- Serves React frontend as static files from Express
- The Dockerfile is at repo root: `Dockerfile`
### layonara-builder (577MB)
- Base: `ubuntu:24.04`
- All tools installed from **pre-built GitHub Release binaries** (no Nim compilation)
- The Dockerfile is at `builder/Dockerfile`
- Tools: nwn_gff, nwn_script_comp, nasher, layonara_nwn, cmake, gcc, git
**Critical**: The builder Dockerfile downloads pre-built binaries from:
- `layonara/neverwinter.nim` releases (nwn_gff, nwn_script_comp, etc.)
- `squattingmonk/nasher.nim` releases (nasher)
- `plenarius/layonara_nwn` releases (layonara_nwn)
If any of these release URLs break, the builder image won't build.
## Gitea Infrastructure
GitHub is no longer the primary git provider for contributors. Gitea is self-hosted on xandrial.
### Setup
- **Gitea URL**: `https://gitea.layonara.com`
- **Host**: xandrial (159.69.30.129, Hetzner CPX41)
- **Managed by**: Coolify on leanthar, service UUID `xo2yy8rml79lkzmf92cgeory`
- **Database**: PostgreSQL 16 sidecar in same Coolify service
- **Auth**: Authentik OIDC SSO (same login as Nextcloud and email)
- **SSH**: Port 2222 for git-over-SSH
- **Admin account**: `orth`
### Repos on Gitea
| Repo | Branch | Push Mirror → GitHub |
| --------------------- | ------- | -------------------- |
| `layonara/nwn-module` | `ee` | Yes, sync on commit |
| `layonara/nwn-haks` | `64bit` | Yes, sync on commit |
### NOT on Gitea
- `plenarius/unified` (NWNX) stays on GitHub — read-only, no contributions through Forge
### Branch Protection
- `ee` on nwn-module: only `orth` can push directly
- `64bit` on nwn-haks: only `orth` can push directly
- Contributors must fork within the layonara org and PR
### Push Mirrors
Each Gitea repo has a push mirror configured to sync to the corresponding GitHub repo. This triggers existing GitHub CI/CD and Discord bot webhooks. Commit attribution is preserved (it's in the git objects).
### How the Forge Connects
- `GIT_PROVIDER_URL` env var (default: `https://gitea.layonara.com`)
- Backend uses `git-provider.service.ts` which calls Gitea API at `$GIT_PROVIDER_URL/api/v1`
- Clone URLs: `https://<token>@gitea.layonara.com/<user>/<repo>.git`
- The `unified` repo clones from GitHub directly (no token needed, public repo)
## Forked Dependencies
### layonara/neverwinter.nim (fork of niv/neverwinter.nim)
- **Purpose**: Adds `-n` (no entry point) and `-E` (all errors) flags to `nwn_script_comp`
- **Cherry-picked**: PRs #152 and #153 from cgtudor's branches
- **Release workflow**: `.github/workflows/release.yml` — builds on tag push, creates GitHub Release with pre-built linux tarball
- **Current release**: `v2.1.2-layonara`
- **Upstream status**: Waiting for niv to merge the PRs. When merged, this fork can be retired.
- **Remote setup**: `origin` = layonara fork, `upstream` = niv/neverwinter.nim (push disabled: `no_push`)
### layonara/nwscript-ee-language-server (fork of PhilippeChab/nwscript-ee-language-server)
- **Purpose**: Migrates diagnostics from `nwnsc` to `nwn_script_comp`
- **Merged**: PR #77 (cgtudor's tdn-hack branch) with conflict resolution
- **Status**: Waiting for upstream compiler PRs to merge, then PhilippeChab can merge PR #77, then this fork can be retired.
- **Included in Forge**: As a git submodule at `lsp/nwscript-language-server/`
- **Remote setup**: `origin` = layonara fork, `upstream` = PhilippeChab (push disabled: `no_push`)
### plenarius/layonara_nwn
- **Not a fork**: James's own repo
- **Release workflow added**: `.github/workflows/release.yml` — builds on tag push
- **Current release**: `v0.1.1`
## Known Issues & Incomplete Work
### TypeScript
- All backend TS errors are resolved (Express 5 `*path` returns `string[]`, simple-git uses named import)
- Frontend uses `"moduleResolution": "bundler"` and `"noImplicitAny": false` in tsconfig to avoid strict-mode issues from subagent-generated code
- Frontend build script is `"build": "vite build"` (skips `tsc -b` which fights with bundler resolution)
### Not Yet Tested End-to-End
- Full setup wizard flow against live Gitea (the UI renders but hasn't been walked through with real forking/cloning)
- Module build → server start → connect with NWN client
- Toolset temp0/ watcher with real GFF files
- Hot-reload pipeline with a real nasher build
- Push mirrors actually triggering GitHub CI
- LSP with real `.nss` files (the bridge architecture is built but URI mapping may need tuning)
### Remaining UI Work
- **GFF visual editors** (ItemEditor, CreatureEditor, AreaEditor, DialogEditor) still use Tailwind classes — may need inline style conversion
- **CommitDialog** component styling could be improved
- **FileExplorer** sidebar styling uses older patterns (Tailwind classes for layout)
- **EditorTabs** uses older patterns
- **SearchPanel** uses older patterns
- **ErrorBoundary** component not styled
- **Light mode** needs visual verification — all tokens have light variants but the overall look hasn't been tested
- The vendor bundle is still 598KB (Monaco dominates) — could be split further with `manualChunks`
### Database
- `db/schema.sql` contains the full schema from James's local dev DB (`nwn_dev`) + seed data for cnr_misc and pwdata
- DM row insertion happens during setup wizard (contributor provides CD key)
- Architecture is ready for richer seed data (production dump) but not implemented yet
### Infrastructure
- Gitea on xandrial needs monitoring/backup strategy (no backup service configured yet)
- The Gitea PostgreSQL database should be backed up regularly
## Key Conventions
- **NEVER close GitHub issues** without explicit permission — state changes fire Discord webhooks to the player community
- **NEVER push to `nwnxee/unified`** — James's fork is `plenarius/unified`
- **No pushing layonara-forge to any remote** until James says so — everything is local
- **Conventional commits**: `feat:`, `fix:`, `refactor:`, `docs:`, `chore:`
- **NWN resref limit**: 16 characters max for all filenames
- **Express 5**: Uses `*path` for catch-all/wildcard routes, NOT bare `*`
- **simple-git**: Use named import `import { simpleGit } from "simple-git"`, NOT default import
- **Inline styles for layout**: Do NOT use Tailwind classes for flex/grid layout — they don't reliably apply. Use inline `style={{}}` props.
- **CSS variables for all colors**: Never use hex values. Use `var(--forge-*)` tokens from `globals.css`.
- **lucide-react for icons**: Never use Unicode emoji for UI icons. Import from `lucide-react`.
## Environment Variables
### Forge Container
| Var | Default | Purpose |
| ------------------ | ---------------------------- | -------------------------------------------- |
| `WORKSPACE_PATH` | `/workspace` | Where repos, server data, config live |
| `NWN_HOME_PATH` | `/nwn-home` | NWN documents directory (for Toolset temp0/) |
| `GIT_PROVIDER_URL` | `https://gitea.layonara.com` | Gitea instance URL |
| `PORT` | `3000` | HTTP server port |
### Coolify
| Var | Location | Purpose |
| ------------------- | ---------------- | --------------------------------- |
| `COOLIFY_API_TOKEN` | `~/.env.coolify` | API access to Coolify on leanthar |
| `COOLIFY_URL` | `~/.env.coolify` | `https://leanthar.layonara.com` |
### Gitea
| Item | Value |
| ----------------------------- | ---------------------------------------------------------------------------- |
| API token | `eb79a92cea7dad657a0c81ddd2290a1be95057e2` (orth's token, name: forge-setup) |
| Gitea service UUID in Coolify | `xo2yy8rml79lkzmf92cgeory` |
## File Structure
```
layonara-forge/
├── Dockerfile # Forge image (multi-stage Node.js build)
├── docker-compose.yml # Forge container with socket + workspace mounts
├── .env.example # WORKSPACE_PATH, NWN_HOME_PATH, GIT_PROVIDER_URL
├── .impeccable.md # Design context (personality, palette, principles)
├── .agents/skills/ # Impeccable design skills (18 commands)
├── builder/
│ └── Dockerfile # Builder image (pre-built binaries, no Nim)
├── db/
│ └── schema.sql # MariaDB schema + seed data
├── lsp/
│ └── nwscript-language-server/ # Git submodule (forked LSP)
├── packages/
│ ├── backend/
│ │ └── src/
│ │ ├── index.ts # Express + WS + upgrade handlers
│ │ ├── config/ # repos.ts, env-template.ts
│ │ ├── gff/ # GFF schema definitions (6 types)
│ │ ├── nwscript/ # resref-index, tlk-index, twoda-index
│ │ ├── routes/ # All API routes
│ │ └── services/ # All backend services
│ └── frontend/
│ └── src/
│ ├── App.tsx # Router with SetupGuard + React.lazy routes
│ ├── components/ # editor/, gff/, terminal/, Toast, etc.
│ ├── hooks/ # useWebSocket, useEditorState, useTheme, useLspClient
│ ├── layouts/ # IDELayout (inline styles), SetupLayout
│ ├── lib/ # lspClient.ts (JSON-RPC bridge)
│ ├── pages/ # All page components (inline styles, lucide icons)
│ ├── services/ # api.ts
│ └── styles/ # globals.css (OKLCH tokens, font imports, global styles)
```
## Running Locally
### Native (dev mode, fastest iteration)
```bash
cd /home/jmg/dev/layonara/layonara-forge
WORKSPACE_PATH=/tmp/forge-test NWN_HOME_PATH=/home/jmg/dev/nwn/local-server/home GIT_PROVIDER_URL=https://gitea.layonara.com npm run dev
# Frontend: http://localhost:5173 (proxies to backend)
# Backend: http://localhost:3000
```
**Important**: If you run without `WORKSPACE_PATH`, it defaults to `/workspace` which doesn't exist natively. The File Explorer will show "Repository not cloned" and repos will show as uncloned.
### Docker (production mode)
```bash
cd /home/jmg/dev/layonara/layonara-forge
cp .env.example .env # edit paths
docker compose up -d
# Open http://localhost:3000
```
### Building Docker images
```bash
docker build -t layonara-builder builder/ # ~45 seconds
docker build -t layonara-forge . # ~2 minutes
```
## Current State (as of UI overhaul session end)
- All code is local — nothing has been pushed to any remote for the layonara-forge repo itself
- Frontend build passes clean (no TS errors, no lint issues)
- All 8 pages styled with consistent design system (cards, icons, tokens)
- Setup wizard has 4-phase indicator, path inputs, status dots
- Routes are code-split (10 chunks)
- 28 frontend files modified in the UI overhaul
- The `layonara/neverwinter.nim` and `layonara/nwscript-ee-language-server` forks are on GitHub with changes pushed
- `plenarius/layonara_nwn` has a release workflow added and v0.1.1 release published
## Priority for Next Session
1. **Integration test the setup wizard** against live Gitea (fork, clone, build cycle) — needs `git` installed or Docker environment
2. **Test module build → server start → NWN client connection**
3. **Test Toolset temp0/ sync** with real GFF files
4. **Polish remaining components** (GFF editors, CommitDialog, FileExplorer, EditorTabs, SearchPanel — still use Tailwind classes)
5. **Test light mode** visually
6. **Set up Gitea backup** on xandrial