Formidable is a desktop app for designing YAML-based form templates, filling them out, and rendering the result through a Handlebars + Markdown pipeline. It's the Wails 3 + Go + Vue 3 successor to the original Electron build β same templates, same field types, same "auditability by design" stance, but a smaller native binary, a proper Go backend, and a real REST API.
π Dedicated to Elly - who lived with strength, humor and clarity. "Sleep, don't weep. My sweet love" from album "9" by Damien Rice
π And to Aaron Swartz, who refused to back down when it mattered. "We are all made of stardust, and we are all made of stories."
Alpha. Working today:
- Template designer + form editor (17 stored field types, full Handlebars rendering, live slideout preview)
- Per-profile context folders, profile switching, VFS resolution
- Internal HTTP server with two surfaces:
- Wiki β
/,/template/{stem},/template/{stem}/form/{datafile} - REST API β full CRUD on collection-enabled templates + Swagger UI at
/api/docs/
- Wiki β
- SQLite-backed per-profile index for sub-millisecond list/search
- Append-only system-level journal recording every FS mutation (groundwork for git/gigot sync)
WIP:
- Frontend journal viewer + git/gigot sync triggers (backend journal records; no UI yet)
- Plugin system (placeholder workspace; not ported)
- Per-template ACL on the API write surface
- Per-user data dir resolver for installed builds (XDG/AppData paths)
Composition root in internal/app/app.go wires every module once at boot.
Backend (internal/modules/)
| Module | Role |
|---|---|
system |
Atomic FS primitives (temp+fsync+rename); journals every mutation |
sfr |
Sanitize-Filename-Read wrapper for safe content r/w |
config |
User config, boot profile, virtual file structure resolution |
journal |
Append-only change log; emits journal:changed |
template |
YAML template load/save/validate; 17 field types in a registry |
storage |
Form .meta.json r/w, image bytes, per-template storage layout |
form |
Storage workspace orchestration (template + storage + config defaults) |
index |
Per-profile SQLite cache of template/form metadata |
dataprovider |
Read-only facade composing index + render β used by wiki/api |
render |
raymond Handlebars + goldmark + chroma; per-target URL strategies |
nav |
formidable:// URL resolver; persists active selection |
wiki |
Runtime HTTP server hosting /, /template/..., /storage/... |
api |
/api/collections/* REST + OpenAPI 3.0.3 spec + Swagger UI |
csv |
CSV import/export helpers |
i18n |
Backend-rooted localization served to vue-i18n |
dialog |
Native file/folder pickers via Wails |
Frontend (frontend/src/)
Vue 3 SPA with workspaces for Templates, Storage, Profiles,
Settings, Information (server lifecycle, monitoring), and
Plugins (placeholder). Calls the backend via Wails service bindings
generated from each module's Service type β no IPC, no window.api
shim. UI strings flow through vue-i18n from internal/modules/i18n/locales/.
Render pipeline
Transport-neutral. Each consumer constructs its own render.Manager
with a (imageURLFunc, formidableLinkURLFunc) pair so the same
Handlebars output can target the in-app slideout (data: URLs +
formidable:// for the Vue interceptor), the wiki HTTP server
(/storage/.../images/... + /template/.../form/...), or future
exports (Azure DevOps Wiki, GitHub Wiki, plain MD) without a single
branch inside the render module.
15 stored types + 4 system/container, all driven from
internal/modules/template/field_registry.go:
- Identity:
guid - Text:
text,textarea - Numeric:
number,range - Boolean:
boolean - Choice:
dropdown,radio,multioption - Date:
date - Collections:
list,table,tags - Media:
image,link - API-linked:
api(selection from a sibling collection with mapped fields) - Containers:
loopstart/loopstop(and theloopervirtual type β repeating sections, max nesting depth 2)
Templates with enable_collection: true AND a guid field become
addressable via /api/collections/{stem}/{guid} and surface in
/api/collections.
GET /api/docs/ for the live Swagger UI; GET /api/openapi.json for
the spec (rebuilt per request from current templates). Endpoints:
GET /api/guid β server-minted UUID v4
GET /api/collections β list collection-enabled templates
GET /api/collections/{tpl} β paged list (limit/offset/q/tags + ETag)
GET /api/collections/{tpl}/count β total
GET /api/collections/{tpl}/design β fields with normalized options
GET /api/collections/{tpl}/{id} β full meta+data (ETag)
HEAD /api/collections/{tpl}/{id} β ETag-only freshness check
POST /api/collections/{tpl} β create (?upsert=true to overwrite)
PUT /api/collections/{tpl}/{id} β replace (?upsert=true to create)
PATCH /api/collections/{tpl}/{id} β shallow-merge (optional If-Match β 412)
PATCH /api/collections/{tpl}/{id}/field/{key} β single-field update
DELETE /api/collections/{tpl}/{id} β 204
POST /api/collections/{tpl}/batch?mode=... β bulk create|replace|merge
GET /api/collections/{tpl}/export.ndjson β full streamed export
GET /api/collections/{tpl}/export.csv β id/filename/title/tags
{id} is always the GUID value (the field whose type: guid),
never the filename. Filenames are derived from the template's
item_field via slugify with collision suffix (brood.meta.json,
brood-2.meta.json, ...) and fall back to <guid>.meta.json.
POST auto-mints a GUID server-side when the body's data[guidKey] is
empty, so callers don't need a UUID library.
raymond (vendored fork in third_party/raymond/) + goldmark + chroma:
Helpers: field, fieldRaw, fieldMeta, tags, stats, plus the
math/comparison families. See internal/modules/render/helpers.go.
Requires Go 1.25+, Node 20+, and the Wails 3 CLI:
go install github.com/wailsapp/wails/v3/cmd/wails3@latestThen via Task:
task dev # build + run; clean exit on window close
task dev:watch # hot-reload via vite + wails3 dev
task build # build the binary into ./bin/Formidable
task package # platform installer (.deb / .rpm / .dmg / .exe)Linux packaging is .deb / .rpm / Arch via nfpm; the binary runs as
/usr/local/bin/Formidable and the .desktop entry as Formidable.
TDD throughout β every behavior has either a unit test or a Gherkin scenario before the implementation lands.
go test -race ./... # whole tree, race detector on
go test -race -v ./internal/modules/api/... # one module, verboseThe api module alone ships 104 godog scenarios + 682 steps; the wiki
module 39; render 88; storage and template each well into the dozens.
Feature files live under each module's features/ folder.
internal/
app/ β composition root (single boot wiring)
log/ β slog setup
modules/ β domain modules (table above)
frontend/
src/ β Vue 3 SPA
bindings/ β generated Wails service stubs
build/ β Wails per-platform targets, nfpm.yaml, appicon.png
third_party/raymond/ β vendored Handlebars fork (whitespace + options-only patches)
Examples/ β sample templates & storage (basic, loopie, recepten)
design/ β port findings, source docs (read-only reference)
main.go β Wails app entry point
Taskfile.yml β task entry points
MIT Β© 2026 Peter van de Pas
- Wails β Go + webview desktop framework
- Vue 3 β frontend framework
- raymond β Handlebars-for-Go (vendored fork)
- goldmark β CommonMark in Go
- chroma β syntax highlighting
- godog β Gherkin BDD for Go
- Swagger UI β bundled API explorer
- modernc.org/sqlite β pure-Go SQLite
- Repo: https://github.com/petervdpas/Formidable2
- Original (Electron) Formidable: https://github.com/petervdpas/Formidable
- GiGot (token-based remote sync, future integration): https://github.com/petervdpas/GiGot