Angular application for authoring Parsnip IL. Talks to the Parsnip backend via a relative /api/* path so the bundle is deployment-agnostic.
This repo is self-contained — it has its own docker-compose.yml for standalone work, and it also slots in as a submodule of the parsnip superproject when you want to run it alongside the backend and compiler.
src/app/pages/— routed pages.home,overview,parsers(list),components(tabbed Enums / Bitfields / Objects / Switches, parser-scoped),review,export,import,compile.src/app/features/components/— entity feature folders. Each folder has a list, expanded-view, detail, form-modal, bulk-import-modal, and delete-modal where applicable:enums/,bitfields/,objects/,switches/,parsers/(withuser-type-form-modalfor configuration CustomFieldTypes).src/app/shared/—dependency-form-modal,scope-modal,parser-selector,modal,icon,toast.src/app/services/— id-based API clients (object.service,switch.service,enum.service,bitfield.service,configuration.service,parser-api.service), plusparser-context.service(id-only) andtoast.services.src/app/models/— DTOs aligned with backend Scalar spec.src/app/layout/— header/nav chrome.src/styles.scss— shared.data-table+.icon-btnstyles used across every list view.
Handled by src/app/app.routes.ts:
/parsers,/parsers/:parserId/components,/components/:parserId/components/:parserId/{enums|bitfields|objects|switches}/:id/review,/export,/import,/compile,/overview,/
Entity detail routes use integer ids, not names.
Two authoring surfaces. Single-entity forms are the default path; bulk-import is a power-user shortcut.
- Single-entity forms — one modal per entity. Object-field modal carries size selects for
uint/int/bytes/float(f32/f64) /addr(IPv4/IPv6) / user-defined custom types, plususe_custom_scope+ reference-scope controls on both the scalar-reference and list-element-reference branches. Switch form modal carries per-option inputs with minus, element type for list actions, full default-action editing, and edit-mode hydration of options/actions/enums/deps. Parser-detail exposes protocol description, conversion/signature file upload, user-type CRUD, port tuples, and scopes. - Bulk-import modals — objects (
objects-field-bulk-import-modal), switches, bitfield-fields, enums. JSON paste of the full IL shape (inputs, conditional, until, references, elementType). Forward references are allowed; export preflight is the strict gate. - Edit-reopen hydration — modals
forkJointheir hydration GETs and gate submit behind ahydratingsignal so a pre-hydration save can't clobber typed values with stale data. - Atomic saves — object-field advanced children save through
POST /object_fields/<id>/upsert-advanced; switch edits save throughPOST /switches/<id>/upsert-tree. No client-side clear-then-rebuild. - Import / Export —
/importuploads a.pilbundle as a new parser./exportdownloads a single.pilpackage viaPOST /parsers/{id}/generate_ilwith inline preflight diagnostics. - Compile —
/compileuploads a.piltoPOST /parsers/compile, which the backend proxies to the compiler microservice (parsnip compiler +spicyz). Returns the full artifact tree (source +.hlto) as a zipped bundle that auto-downloads on success, plus collapsible panels for the compiler andspicyzlogs. Failure surfaces as an inline 422 with both logs open.
All HTTP calls use the relative base path /api. Two surfaces translate that to the backend:
- Docker / production:
nginx.confproxies/api/* → http://backend:5000/*inside the compose network. The prefix is rewritten before proxy so/api/parsersreaches the backend as/parsers. No CORS dance, same-origin. - Local dev (
bunx ng serve):proxy.conf.json(wired throughangular.json→serve.options.proxyConfig) proxies/api/* → http://localhost:5000/*. Start the backend on port 5000 first.
The upshot: frontend code never constructs absolute backend URLs. Change the backend host by adjusting nginx/proxy config, not application code.
The production path is Dockerfile → multi-stage build with oven/bun:1-alpine (bun install + bun run build) → nginx:alpine serving dist/parsnip/browser on port 80. There is no live dev server in the container path.
docker compose up --buildOpen http://localhost:4200. The standalone compose only starts the frontend container; for a working UI you need a backend reachable at the Docker network alias backend on port 5000. For a full local stack, use the superproject's root-level compose instead.
docker compose up --buildBrings up frontend, backend, and database.
Source is baked at build time. docker compose restart and up without --build will serve the previous bundle even if source has changed. After editing source:
docker compose build --no-cache frontend && docker compose up -d --no-deps frontendVerify the new bundle is actually being served by grepping the served JS for a marker from the new code:
docker exec parsnip-frontend sh -c 'grep -lo "<marker-string>" /usr/share/nginx/html/*.js'Requires the backend running on port 5000 (the proxy config assumes localhost).
bun install
bunx ng serveOpen http://localhost:4200/. The proxy handles /api/* routing; source edits hot-reload.
ng buildArtifacts land in dist/.
ng test # Karma unit tests
ng e2e # no default e2e framework; choose one when adding tests