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

23 KiB

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 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)

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)

cd /home/jmg/dev/layonara/layonara-forge
cp .env.example .env  # edit paths
docker compose up -d
# Open http://localhost:3000

Building Docker images

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