Component Reference¶
MerMEId MeLODy consists of three web components, an orchestration script, and a converter module. The web components communicate exclusively through custom DOM events dispatched on the window object — there are no direct references between components, which keeps each component independently testable and replaceable.
All events follow the naming convention <component-name>:<action> and are dispatched on window with bubbles: true and composed: true so they cross shadow DOM boundaries:
Event Map¶
┌──────────────────────────────────┐
│ adwlm-filesystem-manager │
│ │
User selects ───► │ dispatches: entity-to-edit │──────────────────────────►┐
file in tree │ dispatches: repository-selected │─────────────────────────►┐│
│ dispatches: clear-entity-editor │ ││
│ dispatches: reload-indexes │───────────────────────►┐ ││
└──────────────────────────────────┘ │ ││
▲ │ ││
│ entity-to-save │ ││
│ entity-to-delete │ ││
│ unsaved-changes │ ││
┌──────────────────────────────────┐ │ ││
│ adwlm-entity-editor │ │ ││
│ │◄────────────────────────┘ ││
│ User edits form ────────────────►│ consumes: entity-to-edit ││
│ Save button ────────────────────►│ dispatches: entity-to-save││
└──────────────────────────────────┘ ││
││
┌──────────────────────────────────┐ ││
│ adwlm-entity-search │◄───────────────────────────┘│
│ │ consumes: repository-selected
│ Fetches index TTLs from URL ─────│ consumes: reload-indexes ◄─┘
│ SPARQL query via Oxigraph │
│ Click ─────────────────────────►│ dispatches: entity-selected
└──────────────────────────────────┘
Orchestration Layer (js/index.js)¶
This is the top-level script that bootstraps the application. It is not a web component — it runs as a plain ES module loaded from index.html.
Beyond startup, js/index.js is also responsible for wiring the Output Panel tabs:
- XML tab — listens for serialised entity data and invokes the appropriate converter from
modules/rdf-xml-converter/to produce MEI/XML output - RDF tab — displays the raw Turtle from
shacl-form.serialize() - Preview tab — renders the entity read-only in a second
shacl-formindata-viewmode
Filesystem Manager¶
adwlm-filesystem-manager
File: modules/filesystem-manager/index.js
The left panel component. Manages the repository tree, the staged files queue, and all Git operations exposed to the user.
Responsibilities¶
- Display and navigate the repository tree (lazily loaded via
sl-tree) - Clone, remove, rename, and synchronise repositories
- Stage, unstage, and push (commit + push) modified files
- Highlight unsaved and unshared files in the tree using colour indicators
- Load the selected
.ttlfile and dispatch it to the entity editor - Respond to save requests from the entity editor
Key Properties¶
| Property | Type | Description |
|---|---|---|
_selected_repository_path |
String |
Absolute path of the active repository (e.g. /my-catalogue) |
_file_path |
String |
Relative path of the currently open file |
_staged_files |
Array |
Relative paths of files staged for the next commit |
entity_to_save |
Object |
Set externally to trigger a file save; observed via updated() |
Events Dispatched¶
| Event | When | Payload |
|---|---|---|
adwlm-filesystem-manager:entity-to-edit |
File selected in the repository tree | { contents: String, path: String } |
adwlm-filesystem-manager:repository-selected |
Repository clicked or auto-selected on load | { repositoryPath: String } |
adwlm-filesystem-manager:clear-entity-editor |
Currently open entity file deleted | — |
adwlm-filesystem-manager:reload-indexes |
After a push or pull completes | — |
Events Consumed¶
| Event | Source | Effect |
|---|---|---|
adwlm-entity-editor:entity-to-save |
Entity editor | Saves the file to the virtual FS and stages it |
adwlm-entity-editor:unsaved-changes |
Entity editor | Blocks synchronise if unsaved changes are present |
adwlm-entity-editor:entity-to-delete |
Entity editor | Deletes the entity file from the virtual FS |
entity-selected |
Search (via window) |
Navigates the tree to the given file path |
Repository Tree¶
The tree is lazily loaded. When a sl-tree-item[lazy] node is expanded, the sl-lazy-load event fires and _generate_folder_tree() reads the contents of that folder from the virtual filesystem. Each tree item carries its path in data-* attributes:
data-entry-type:repo-folder,folder, orfiledata-entry-absolute-path: e.g./my-catalogue/persons/12345.ttldata-entry-relative-path: e.g.persons/12345.ttl
Conflict Detection¶
Before a push or pull, VirtualFilesystem.canPullSafely() is called. It fetches the remote HEAD, finds the common ancestor commit, and checks whether any locally staged files have also been modified remotely. If conflicts are detected, the operation is blocked and the user is warned.
VirtualFilesystem¶
File: modules/filesystem-manager/virtual-filesystem/index.js
A sub-module of adwlm-filesystem-manager. Wraps isomorphic-git and LightningFS to provide a Git-backed virtual filesystem that persists in the browser's IndexedDB.
Key Methods¶
| Method | Description |
|---|---|
add_repository(metadata) |
Clones a remote repository into the virtual FS (git clone --depth 1) and stores credentials |
remove_repository(path) |
Removes the remote and recursively deletes all files from the virtual FS |
list_repository_names() |
Lists top-level directory names in the virtual FS root (one per cloned repo) |
list_branches(metadata) |
Fetches the remote's branch list via git.listServerRefs |
list_entries_from_workdir(repo, folder) |
Lists files and subdirectories for a given folder using git.walk(WORKDIR) |
read_file(repo, path) |
Reads a file from the working directory as a string |
save_and_stage_file(repo, contents, path) |
Writes a file and calls git add + git updateIndex |
list_staged_files(repo) |
Compares TREE and STAGE to find files with staged changes |
commit_and_push_file(repo, staged, selected) |
Commits the selected staged files and pushes to the remote |
pull(repo) |
Pulls from the remote (fast-forward), restoring staged files afterward |
canPullSafely(repo, changedFiles) |
Detects conflicts between local staged files and remote changes before pull/push |
unstageFile(repo, path) |
Resets the index entry and restores the file from HEAD |
Entity Editor¶
adwlm-entity-editor
File: modules/entity-editor/index.js
The main editing area. Wraps the shacl-form component and manages the lifecycle of the currently open entity.
Responsibilities¶
- Render the
shacl-formfor the currently selected entity - Load the correct SHACL shape for the entity type (patched with the repository namespace)
- Track unsaved changes and guard against accidental navigation
- Dispatch save requests back to the filesystem manager
- Handle "quick add" — creating a new linked entity from within an open form
- Show linked entities in a draggable, floating side panel (read-only preview)
Key Properties¶
| Property | Type | Description |
|---|---|---|
entity_to_edit |
Object |
Currently open entity: { contents, path, entity_iri, entity_type, shapesUrl } |
entity_type_definitions |
Array |
Entity type definitions loaded from editor-default.ttl |
_hasUnsavedChanges |
Boolean |
Set to true on any change event from shacl-form |
_selected_repository_path |
String |
Kept in sync with the filesystem manager via events |
_cachedConfig |
Object |
Cached result of configuration/config.json — cleared on repository change |
Events Dispatched¶
| Event | When | Payload |
|---|---|---|
adwlm-entity-editor:entity-to-save |
Save button clicked | { entity_iri, rdf_contents, json_ld_contents, path, shapesUrl } |
adwlm-entity-editor:unsaved-changes |
Unsaved-changes state changes | { hasUnsavedChanges: Boolean } |
adwlm-entity-editor:cached-config |
Repository config.json read |
{ datasetBaseUrl, projectDomain } |
adwlm-quick-add:entity-to-save |
Quick-add form saved | same payload as entity-to-save |
Events Consumed¶
| Event | Source | Effect |
|---|---|---|
adwlm-filesystem-manager:entity-to-edit |
Filesystem manager | Loads the entity Turtle into the form |
adwlm-filesystem-manager:clear-entity-editor |
Filesystem manager | Clears the current form |
adwlm-filesystem-manager:repository-selected |
Filesystem manager | Updates repository path; clears config cache |
adwlm-entity-types-dialog:entity-to-add |
Entity types dialog | Creates a new empty entity of the selected type |
shacl-form:quick-add |
shacl-form (shadow DOM) |
Opens the quick-add dialog for a linked entity field |
Entity ID Generation¶
New entity IRIs are generated at creation time using the Web Crypto API:
_generate_entity_id() {
let array = new Uint32Array(1);
self.crypto.getRandomValues(array);
return array[0]; // random 32-bit unsigned integer
}
This produces paths like persons/2847362910.ttl and IRIs like urn:uuid:persons/2847362910.
Side Panel (Linked Entity Preview)¶
When a user clicks a link inside a shacl-form field (links are rendered as <a class="uuid">), the editor intercepts the click using composedPath() to pierce the shadow DOM, reads the linked .ttl file from the virtual filesystem, and renders it in a floating, draggable panel using a second shacl-form instance in read-only data-view mode.
Repository Configuration Cache¶
On first load of a repository, the editor reads configuration/config.json. The result is cached in _cachedConfig and cleared when a different repository is selected. This avoids repeated filesystem reads for every entity opened in the same session.
Entity Search¶
adwlm-entity-search
File: modules/entity-search/index.js
The search panel. Provides full-text search across all entity labels, alternative labels and classifications in the active repository.
Responsibilities¶
- Fetches pre-built dataset index files (one
.ttlper entity type) fromdatasetBaseUrl - Parses all index files into an in-memory Oxigraph RDF store
- Runs multiple SPARQL queries to extract entity metadata: labels, types, composers, classifications, and alternative labels
- Filters results by label text (including alternative labels and classifications) and/or entity type
- On selection, dispatches
entity-selectedto navigate the filesystem manager tree to the matching file
Events Dispatched¶
| Event | When | Payload |
|---|---|---|
entity-selected |
Search result clicked | { path: String } — navigates the tree |
Events Consumed¶
| Event | Source | Effect |
|---|---|---|
adwlm-entity-editor:cached-config |
Entity editor | Sets the dataset URL and loads indexes |
adwlm-filesystem-manager:reload-indexes |
Filesystem manager | Clears the Oxigraph store and reloads all index files |
How Indexes Work¶
The search component does not index files from the local virtual filesystem. Instead, it fetches pre-built aggregated index files from the datasetBaseUrl — a published static URL, typically GitHub/GitLab Pages. These files are generated by a CI/CD pipeline when changes are pushed to the repository.
Implication: the search index reflects the last pushed state, not local unsaved changes. After a push, the filesystem manager dispatches adwlm-filesystem-manager:reload-indexes to trigger a fresh reload of all indexes.
SPARQL Queries¶
The search component executes three separate SPARQL queries against the Oxigraph store and combines the results:
Query 1: Main Data (Subject, Label, Type, Composer)¶
SELECT DISTINCT ?subject ?label ?type ?composer WHERE {
?subject <http://www.w3.org/2004/02/skos/core#prefLabel> ?title .
?subject a ?type .
OPTIONAL {
?subject <https://schema.org/composer> ?composer .
}
bind(coalesce(concat(?composer, ": ", ?title), ?title) AS ?label) .
}
ORDER BY ?label
This query extracts the primary label and type for each entity. If a composer is present, the label is formatted as "composer: title" (e.g., "Bach, Johann Sebastian: BWV 1001"). Otherwise, only the title is used.
Query 2: Classifications¶
SELECT ?subject ?classification WHERE {
?subject <http://www.w3.org/2004/02/skos/core#broader> ?classification .
}
Retrieves the broader (parent) classifications for each entity. These are stored in a classificationsMap indexed by subject.
Query 3: Alternative Labels¶
SELECT ?subject ?altlabel WHERE {
?subject <http://www.w3.org/2004/02/skos/core#altLabel> ?altlabel .
}
Retrieves alternative labels for each entity. These are stored in an altLabelsMap indexed by subject.
How Results are Combined¶
After executing all three queries, the results are merged into a single array of entry objects:
{
subject: "urn:uuid:persons/2847362910",
label: "Bach, Johann Sebastian: Suite in G Major",
type: "Work",
classifications: ["concert", "..."],
altlabels: ["Alternative Name", "Alternate Title"],
composer: "Bach, Johann Sebastian"
}
RDF-XML Converter¶
Directory: modules/rdf-xml-converter/
Converts entity data from JSON-LD (produced by shacl-form.serialize("application/ld+json")) to MEI/XML or TEI/XML for the XML output tab.
Structure:
converters/— one JavaScript module per entity type (e.g.person-converter.js). Each converter receives the JSON-LD object and returns an MEI/XML string.templates/— MEI/XML or TEI/XML template strings (one per entity type) used as output scaffolding.types/index.js— maps entity type IRIs to their converter module.
How it works:
- The form content changes →
adwlm-entity-editorcallsshacl-form.serialize("application/ld+json") js/index.jsreceives the JSON-LD and looks up the correct converter by entity type IRI- The converter fills in the MEI/XML template and returns the result
- The XML tab displays the result
Each converter is independent. Adding a new entity type requires only a new converter and template — no changes to shared orchestration code.