Frontend¶
The frontend (regel-gui) is a Vue 3 application built with the Quasar framework as a
plain SPA — src-ssr/ was removed in v2026.07.0 (see
Changelog & Roadmap). State is held mainly in a Pinia regel
store (plus a small layout store for UI-only state), the domain is modelled as plain
JavaScript classes, and both network views are drawn with D3.
Directory layout¶
gui/
├── index.html # Favicon links, web manifest, theme-color
├── public/
│ ├── favicon.ico, icons/ # Favicon set (SVG, PNG 16–512, apple-touch, maskable)
│ ├── site.webmanifest # Name, icons, theme colour
│ └── changelog.json # Generated by scripts/generate-changelog.mjs
└── src/
├── boot/ # fonts.js, i18n.js, axios.js (in that order)
├── css/ # quasar.variables.scss (IOU tokens), app.scss (--iou-* classes)
├── router/routes.js # task, sources, interpretation, visualization,
│ # executable, execute — client-side routes
├── pages/ # Thin <q-page> wrappers, one per route, each
│ ├── TaskDefinitionPage.vue # rendering the matching view below
│ ├── SourceCollectionPage.vue
│ ├── InterpretationPage.vue
│ ├── VisualizationPage.vue
│ ├── ExecutablePage.vue # "Coming soon" — see Roadmap
│ ├── ExecutePage.vue # "Coming soon" — see Roadmap
│ └── ErrorNotFound.vue
├── views/ # The step UIs the pages render
│ ├── TaskDefinitionView.vue
│ ├── SourceCollectionView.vue
│ ├── InterpretationView.vue # with InterpretationStatusBar
│ ├── SourceView.vue # interpretation pane
│ ├── FramesView.vue # interpretation pane, holds the frame network
│ ├── interpretation/FrameListView.vue # frame list used by the visualization view
│ └── visualization/ # VisualizationView.vue, VisualizationPanel.vue
├── components/ # Editor panels, forms, lists, networks, dialogs —
│ # including FrameDetailsPanel.vue and
│ # InterpretationStatusBar.vue
├── model/ # Domain classes (see below); model/viz/ for the diagram
├── stores/ # regel.js (domain state), layout.js (UI state)
├── services/ApiServices.js # All calls to the backend / Python services
├── helpers/ # annotating, underlining, importExport, config,
│ # frameRelations, forceSimulation, ...
└── i18n/ # vue-i18n message catalogue (en-US)
helpers/frameRelations.js describes how frames refer to each other: roleDefinitions (the
roles of acts and claim-duties, in display order), framesIn (a frame plus every frame in its
subdivision), rolesOf, usesOf and usagesOf (the acts and claim-duties that use a frame).
The visualization view, FrameDetailsPanel, NodePanel and model/viz/network.js build on
it. helpers/config.js holds the icons and colours per frame type, and getFrameColor(frame)
picks the Quasar colour for a frame — a fact takes the colour of its subtype when it has
exactly one.
Network views¶
The editor draws two different networks:
| Component | Where | What it shows |
|---|---|---|
Network.vue → ForceDirectedGraph.vue |
Interpretation (FramesView) |
Every frame, built by model/network.js, filterable by frame type through NetworkMenu |
NodeLinkDiagram.vue |
Visualization (VisualizationPanel) |
Acts and claim-duties only, built by model/viz/network.js, with d3-zoom pan and zoom |
In the visualization view, VisualizationView pairs the diagram with FrameListView and
FrameDetailsPanel, which lists a selected frame's roles and where it is used.
The domain model¶
The model/ classes are the source of truth for what an interpretation is. They are plain
classes (not Vue reactive objects) with toFlatObject() / fromFlatObject() methods for
serialisation.
classDiagram
class Task
class SourceDocument
class Sentence
class Snippet
class Annotation
class Fact
class Act
class Claimduty
class BooleanConstruct
class Comment
SourceDocument "1" --> "*" Sentence
Sentence "1" --> "*" Snippet
Snippet "1" --> "*" Annotation
Annotation --> Fact : frame
Annotation --> Act : frame
Annotation --> Claimduty : frame
Act --> Fact : action/actor/object/recipient
Act --> BooleanConstruct : precondition
Claimduty --> Fact : duty/claimant/holder
Fact --> BooleanConstruct : subdivision
BooleanConstruct --> BooleanConstruct : children
Fact "1" --> "*" Comment
| Class | Responsibility |
|---|---|
Task |
Task and interpretation IRIs, editor, label, description |
SourceDocument |
Parses Choppr JSON-LD into a sentence tree; owns annotation lookup/cleanup helpers |
Sentence |
A node in the document tree: text, children, selected/collapsed/visible flags, snippets |
Snippet |
A character range within a sentence; holds the annotations covering it |
Annotation |
Links one or more snippets to one frame |
Fact |
A fact frame: short/full name, subtypes, subdivision, comments |
Act |
An act relation: action, actor, object, recipient, precondition, creates, terminates |
Claimduty |
A claim-duty relation: duty, claimant, holder |
BooleanConstruct |
AND/OR/NOT tree used for preconditions and fact subdivisions; isEmpty is true when no level of the tree holds a frame |
Comment |
A note on a frame: content, author, created/edited timestamps |
frame.js |
frameTypes — the catalogue of frame types and fact subtypes |
Refactoring note in the code
Fact, Act, and Claimduty share several methods (deleteReferencesToFrame, role
handling, toFlatObject/fromFlatObject). The code contains a Frame base class stub
and TODOs to unify them; today each frame type implements these independently.
State management¶
A single Pinia store, useRegelStore (in stores/regel.js), holds the entire editor state:
the list of frames, which frame and boolean-construct node are being edited, the loaded source
documents, the current annotation, the available sources/tasks from TriplyDB, and the state of
the visualization — network (the diagram's model), networkZoomTransform, and
selectedNode, which is null until a node is selected (for a fact, an object { frame }).
The current step is not in the store: it is the route.
Its actions cover the full lifecycle:
- Frame management —
addNewFrame,setFrameBeingEdited,removeFrame(which also strips every reference to the deleted frame and its annotations),createNewFrameViaNlp. - Sources —
addSource,addSourceFromTriply,createSourceDocFromJsonLD,readAvailableSourcesInTripleStore. - Tasks —
addTaskFromTriply,readAvailableTasksInTripleStore,saveInterpretationTriply. - Import/export —
saveInterpretationAsJson,saveInterpretationAsTrig,loadInterpretation,loadInterpretationFromRDF.
When saving, the store merges the persisted frames with those still open in the editor, dedupes
by id, and hands the result to convertInterpretationToJson.
Annotation internals¶
Two helpers carry the annotation logic:
helpers/annotating.jsturns a raw browserSelection(or a character range) into the set of snippets it covers, handling right-to-left selections and cross-sentence spans, and splits snippets so a highlight becomes its own snippet (splitAndReturnSelectedSnippets).helpers/underlining.jscomputes the vertical position of each annotation's underline so overlapping annotations stack without colliding.
helpers/importExport.js holds convertInterpretationToJson and
parseJsonToInterpretation, which translate between the model classes and the on-disk JSON,
re-linking frame references by id and reconstructing snippets and comments on load.
Calling the services¶
services/ApiServices.js is the only place the frontend talks to the network. Its functions
map one-to-one onto the routed endpoints:
| Function | Endpoint | Purpose |
|---|---|---|
fetchNlpPrediction |
POST /api/predict |
NLP suggestions |
convertToRDF |
POST /api/process_and_save |
JSON → RDF (wrap-up) |
convertRDFToJSON |
POST /api/process_graph |
RDF → JSON (unwrap) |
getSourceList |
GET /api/getSources |
List sources |
getSourceFromTriply |
POST /api/getSource |
Fetch one source |
getTasksFromTriply |
POST /api/getTasksFromTriply |
List tasks |
getTaskFromTriply |
POST /api/getTask |
Fetch one task |
saveTask |
POST /api/saveTaskAtTriply |
Save a task |
See API Endpoints for request/response details.
Styling¶
The look follows the IOU house style. css/quasar.variables.scss defines the IOU tokens and maps
Quasar's brand colours onto them:
| Quasar colour | IOU token | Value |
|---|---|---|
primary |
$iou-primary |
#154273 |
secondary |
$iou-primary-light |
#2a5a8a |
dark |
$iou-primary-dark |
#0d2f4f |
accent |
$iou-accent |
#e17000 (light #f58220, dark #b35900) |
positive |
$iou-compliant |
#007a3d |
negative |
$iou-non-compliant |
#cc0000 |
info |
$iou-in-progress |
#275fa0 |
warning |
$iou-acc |
#f59e0b |
The same file sets the typography family, border radii, button weight and shadows, the separator colour, and dark-blue tooltips.
css/app.scss re-exposes the tokens as CSS custom properties (--iou-primary,
--iou-accent, --iou-mono, ...) and defines the shared classes the views use: .iou-header,
.iou-tabs/.iou-tab, .iou-pane with __head, __toolbar and __body, .iou-card,
.iou-eyebrow, .iou-badge--acc/.iou-badge--prod, .iou-admonition, .iou-table, the
.frame-form__* layout of the frame forms, and .q-tooltip.iou-tip.
Fonts. Roboto comes from Quasar's roboto-font extra (quasar.config.js). Roboto Mono,
used for hashes and versions, is self-hosted: boot/fonts.js imports weights 400 and 700 from
@fontsource/roboto-mono, so the app makes no Google Fonts request. fonts is the first boot
file, before i18n and axios.
Branding. package.json sets productName to Norm Editor - RONL, which becomes the page
title. index.html links the favicon set in public/ (favicon.ico, icons/favicon.svg,
PNGs from 16 to 192 px, an apple-touch icon) and public/site.webmanifest (short name
Norm Editor, 192 and 512 px icons plus a maskable one, theme colour #154273, standalone
display), and sets the theme-color meta tag to #154273.