Frontend Architecture
Stack
- SvelteKit (Svelte 5 with runes —
$state,$derived,$effect,$props,$bindable). - Vite for build and dev server.
- Tailwind CSS for styling, with project-specific design tokens (CSS variables) in
src/lib/styles/. - bits-ui for headless dialog, menubar, and dropdown primitives, wrapped in project-local components under
src/lib/components/bitsui/. - SvelteFlow for diagram rendering, with elkjs for auto-layout.
- Mermaid as the alternative renderer.
- CodeMirror 6 with
codemirror-lang-turtlefor the TTL editors. - Asciidoctor.js for rendering class comments.
- Vitest for unit tests, jsdom for the DOM environment.
Routes
SvelteKit's filesystem routing is used straightforwardly:
/ → Homepage (routes/+page.svelte)
/mainpage → Main editor (left tree + diagram + right class editor)
/changelog → Edit history view
/compare → Compare results view
/migrate → 5-step migration wizard
/shacl/... → SHACL views
The editor listens to URL parameters ?dataset=...&graph=...&package=... to support deep links.
Reactive models
A central pattern: every editable domain object has a reactive wrapper in lib/models/reactive/models/ (e.g. reactive-class.svelte.js, reactive-namespace.svelte.js, reactive-ontology.svelte.js). These wrappers:
- Hold the original DTO and a working copy.
- Track
isModified,isValid, and per-field violations as$derivedrunes. - Expose
save(),reset(), and field accessors. - Drive the inline validation visible in dialogs.
DTO ↔ reactive object mapping lives in lib/models/reactive/mapper/. Whenever you add a new field to the backend that the frontend needs to edit, the chain to update is:
- Backend DTO.
- Frontend type in
lib/models/dto/. - Reactive wrapper in
lib/models/reactive/models/. - DTO ↔ reactive mapper.
- UI component that exposes the field.
Validity rules
lib/models/reactive/validity-rules/validityFunctions.js is the single home for cross-cutting validation (label uniqueness, prefix uniqueness, valid IRIs, valid NCNames, etc.). Reuse what's there before adding a new function.
Backend communication
BackendConnection in lib/api/backend.js is a hand-written class with one method per backend endpoint. Every method:
- Builds the URL from
PUBLIC_BACKEND_URL. - Uses
credentials: "include"so the session cookie will be send. - Returns the raw
Response— callers decide whether to.json(),.text(), or.blob().
When you add a new backend endpoint, add the matching method here. Do not call fetch() directly from a component — the indirection makes mocking in tests possible and keeps URLs in one place.
Shared state
lib/sharedState.svelte.js exports cross-component reactive state, primarily:
editorState.selectedDataset/selectedGraph/selectedPackageUUID/selectedClassUUIDforceReloadTrigger— a "kick everything to refresh" signal used after destructive actionscompareState,migrationState— wizard state passed across pages
Use these instead of inventing per-component prop drilling for global selections.
Svelte script ordering
The repository enforces a specific script-block ordering (imports → props → constants → state → derived → effects → lifecycle → functions). The full rationale and ESLint rule are in frontend/docs/script-structure.md. The custom rule lives at frontend/eslint-rules/rules/svelte-script-order/. When in doubt, run npm run format and follow whatever the auto-fixer produces.
Custom ESLint rules
Three custom rules ship with the project:
copyright-header— enforces the Apache 2.0 header on every source file.svelte-file-structure— order of<script>, markup,<style>.svelte-script-order— order inside<script>blocks.
If you write a new file, the auto-fixer (npm run format) inserts the header for you.