- TypeScript 52.1%
- JavaScript 41.7%
- SCSS 3.7%
- CSS 2.3%
- Shell 0.1%
Track uploaded and existing worksheet assets, report unused files by default, and permit explicit deletion only after a 30-day grace period and fresh reference checks. Protect versions, publications, trash and active editor history without exposing inaccessible records through the hold endpoint. Fail closed on missing publication files, preserve concurrently renewed holds, and batch large editor histories within both endpoint limits. Document runtime modes, CLI commands, deployment, backups and retention limits in the backend docs and environment examples. Validation: full npm run check:release, including 546 unit tests, 130 offline browser tests, 43 backend browser tests, backend integration (56 asset checks), static checks and production builds. Tests use temporary backend data; no production cleanup or backup configuration was changed. |
||
|---|---|---|
| .forgejo/workflows | ||
| .github/workflows | ||
| .vscode | ||
| assets | ||
| backend | ||
| containers | ||
| docs | ||
| examples | ||
| frontend-checks@e74a5e7684 | ||
| licenses | ||
| messages | ||
| project.inlang | ||
| public | ||
| scripts | ||
| services | ||
| src | ||
| test | ||
| tests | ||
| .env.development | ||
| .env.template | ||
| .gitignore | ||
| .gitmodules | ||
| AGENTS.md | ||
| biome.jsonc | ||
| CHANGELOG.md | ||
| CONTRIBUTING.md | ||
| flake.lock | ||
| flake.nix | ||
| frontend-checks.config.mjs | ||
| index.html | ||
| LICENSE | ||
| mise.toml | ||
| package-lock.json | ||
| package.json | ||
| paraglide.config.js | ||
| playwright.config.ts | ||
| README.md | ||
| renovate.json | ||
| SECURITY.md | ||
| THIRD_PARTY_NOTICES.md | ||
| tsconfig.app.json | ||
| tsconfig.json | ||
| tsconfig.node.json | ||
| tsconfig.types.json | ||
| vite.config.ts | ||
| vitest.config.ts | ||
einfachmalab
HTML worksheet, class test, and exam editor built with SolidJS, Vite, Paraglide i18n, and XLSX export. Runs standalone in the browser via localStorage or connected to a PocketBase backend and a Playwright PDF service.
Worksheets compile to a custom-element vocabulary (<exercise-*>, <field-*>) rendered by a framework-free runtime in assets/. Exported files load the runtime from assets.dencla.de or bundle it inline for single-file offline use.
See it in action
From the worksheet library to an edited block, A4 print layout, PDF download, and an Excalidraw drawing embedded in the worksheet. This 30-second walkthrough uses the German interface and demo data on an isolated local test server.
Selected highlights — click a screenshot to see it at full size:
| Worksheet library and sharing | Edit blocks with a live preview | Draw directly with Excalidraw |
|---|---|---|
![]() |
![]() |
![]() |
Capture details: README media.
Quickstart
Nix MicroVM Sandbox (recommended)
Host prerequisites: KVM access and Nix flakes.
# Host setup (once)
sudo usermod -aG kvm $USER
mkdir -p ~/.config/nix && echo "experimental-features = nix-command flakes" >> ~/.config/nix/nix.conf
cp .env.template .env
# Start VM (forwards ports 5173, 4173, 8090, 8091, 5180; SSH on port 2200 as user dev)
nix run --impure
# Inside VM
mise run setup
mise run dev # http://localhost:5173 (starts Vite, PocketBase, and PDF service)
Local Development (without VM)
Requires Node 26.12.1 (mise install), npm, mise and unzip. Setup installs or verifies PocketBase and Chromium:
mise run setup # installs/verifies backend/pocketbase and Chromium too
mise run dev
Development Logins
| Target | Login | Password |
|---|---|---|
| Editor (seeded user) | test@einfachmalab.local |
testtest1234 |
PocketBase Admin (:5173/_/ or :8090/_/) |
dev@einfachmalab.local |
devdevdev123 |
Common Tasks (mise run <task>)
| Task | Description |
|---|---|
setup |
Install dependencies, compile i18n, verify PocketBase/Chromium, create .env from template |
dev |
Start Vite dev server (:5173), PocketBase (:8090), and PDF service (:8091) |
check |
Sequential release gate: lint, licenses, static checks, strict types, unit, offline/backend E2E, integration and build |
lint |
Check formatting and code style with Biome |
typecheck |
Type-check TypeScript code with tsc -b |
check:frontend |
Static a11y, contrast, CSS module, token, and layer checks via @dencla/frontend-checks |
test:unit |
Run unit tests with Vitest |
test |
Run Playwright end-to-end tests |
test:backend |
Run integration and browser tests with temporary PocketBase data and an isolated PDF service |
build |
Create production bundle in dist/ |
release:prepare <version> |
Set the version, update the changelog, commit, and tag v<version> (no build, no push) |
release:deploy <tag> |
Verify and build the tagged clean checkout into deploy/ and release/einfachmalab-<tag>.tar.zst |
test:containers |
Build and probe all roles rootless from deploy/ |
Repository Structure
| Path | Purpose |
|---|---|
src/api/ |
SDK/HTTP transport, authentication and document/file persistence |
src/core/ |
Document model, renderer, sandboxed preview overlay, importers, and exporters |
src/blocks/ |
Worksheet building blocks (exercises, text, figures, headers) |
src/fields/ |
Exercise answer fields (in-place, replaces, beside) |
src/components/ |
Editor shell, library, inspector, and navigation UI |
src/store/ |
SolidJS reactive stores, commands, shortcuts, and navigation state |
src/legal/ |
Legal notice and privacy policy Markdown templates and local texts |
src/styles/ |
SCSS design tokens, theme mixins, and layout rules |
assets/ |
Standalone rendering runtime (assets/arbeitsblatt/), fonts, and vendored libraries |
backend/ |
PocketBase schema migrations, hooks, and integration tests |
services/pdf/ |
Playwright-based headless Chromium PDF rendering service |
examples/ |
Sample worksheets and reference documents |
tests/ |
Unit (tests/unit/) and E2E (tests/e2e/) test suites |
docs/ |
Architecture, data model, and deployment specifications |
Key Architecture Concepts
- Sandboxed Double-Buffered Preview (
src/core/preview/): The preview iframe is sandboxed withoutallow-same-origin. The editor interacts exclusively viapostMessage. Structural changes render in a hidden background iframe and swap onws:readyto preserve scroll and focus. - Layout Stability: Switching between task and solution modes never causes pagination reflow. In-place answers overlay allocated task space, and solution elements maintain fixed dimensions.
- Flat Block Hierarchy: Document blocks exist in a flat array where parent-child relationships and contiguous subtree ordering are validated by invariant checks (
src/core/worksheet/nesting.ts).
Betrieb
Debian 13: rootless Pod einfachmalab (UID 10011), Domain ab.dencla.de,
Profil solidjs-pb. Rollen web (127.0.0.1:11180 → 8080), pb
(127.0.0.1:11190 → 8090), pdf (nur im Pod: 127.0.0.1:8091).
Persistente Daten: /srv/einfachmalab/pb_data; Konfiguration:
/etc/apps/einfachmalab/{pb,pdf}.env, Beispiele unter containers/env/.
SMTP wird in PocketBase konfiguriert (sys-einfachmalab, mail.d0b.de:465).
mise run release:deploy v<version> liefert das mit Manifest und Prüfsummen versehene
Bundle für app deploy (Releases); mise run test:containers (nach Erstellen von deploy/) prüft alle Rollen rootless.
Das Server-Repo d0b-infra verwaltet app, zentrale Caddy-Snippets und den Host.
Entwicklung, CI und PDF nutzen Node 26; PDF verwendet Debian-Chromium.
Besonderheiten: opake Vorschau, Veröffentlichungen unter /p/* mit Sandbox-CSP,
Runtime-CORS und separater öffentlicher Assetspiegel. Vollständige Abläufe und
Abnahme: Deployment. Die Vertragsprüfung
unterscheidet gemeinsame Regeln und Profile; der Abnahmestand
hält bestandene Prüfungen und offene Integrationspunkte fest.
Contributing and verification
Read CONTRIBUTING.md, AGENTS.md and the coding standards. Run npm run check:release for the complete sequential release gate, including isolated backend tests. Heavy tasks must not run concurrently on the 4 GB VM. Private vulnerability reports: SECURITY.md.
License
GPL-3.0-or-later for application code, runtime, and examples. See LICENSE. Third-party libraries, fonts, and assets retain their upstream licenses; see THIRD_PARTY_NOTICES.md. Builds, deployment bundles and offline HTML include their notices.



