DocumentationArchitecture

Architecture

Eutepio rests on three decisions: a local database as the single source of truth, sync on the local network only, and a strict split between shared and personal data.

Offline-first

Every device holds its own complete copy of the library. There is no server that owns the data and no account it lives under. Sync is how you keep the band on the same page during a rehearsal or a gig — not a backup, and not cloud storage.

Consequences:

  • The app works without internet
  • Connection quality has no effect on stage behaviour
  • You have to make your own backups — that is what the .eutbackup format is for
  • Lose the phone without a backup and you lose the library

Storage

The database is Isar (community fork, version 3), an embedded database running inside the app process.
Since 0.2.0 it is not one database but one database per library plus a separate registry:
WhereWhat lives there
RegistryThe list of libraries and your user profile — name, instrument, stage appearance, pedal and MIDI mappings
LibrarySongs, setlists, annotations and that library's own state
The profile is therefore global: switching library does not rearrange your stage or your controls. Details in Multiple libraries.
PlatformDatabase location
Android, iOSapp documents directory
Windows, Linux, macOSapp support directory
Binary attachments are not stored in the database. PDFs, MusicXML and images are written as files into pdfs/, musicxml/ and images/ subfolders; the database only keeps a relative path. Each library has its own attachment root.

Resilience

  • If the database fails to open, the damaged file is set aside under a name ending in .corrupt-<timestamp> and the app starts with a fresh database instead of crashing.
  • The database records the schema version it was last written with. Opening an older build of the app on top of a newer database locks the app on a warning screen instead of corrupting data.
  • The app distinguishes a locked database (another process holds it — it retries) from a corrupted one (it sets it aside), so a locked file is never discarded needlessly.

Data model

Song

The shared part, the one the whole band has a reason to know:

FieldTypeNote
title, artisttextrequired
variantNametextoptional variant, part of the song's identity
keytextmusical key
bpmnumbertempo
timeSignaturetexttime signature
caponumberdefaults to 0
tagslist of textindexed for search
chordProContenttextthe song body in ChordPro
durationSecondsnumberlength
versionnumberincrements when shared fields change
createdAt, updatedAt, deletedAttimestampdeletion is soft — the record stays and is only flagged
On top of that, three identifiers used for sync and backups: syncId (unique), matchKey (derived from title, artist and variant) and contentHash (SHA-256 of the shared fields).

Setlist

name, eventDate, venue, startTime, estimatedDurationMin, songIds (ordered list), breaks, songNotes, customFieldsJson, version, createdAt, deletedAt and a local sortOrder.
A setlist has no syncId — across devices it is matched by name and event date alone. Rename the same setlist on one device and sync will produce a duplicate on the other.

Shared and local fields

Alongside the shared fields, a song carries a set of per-device values that never sync and can never be overwritten by anyone else:
  • privateNotes — your personal notes on the song
  • localTranspose, localTransposeXml — chord and MusicXML transposition
  • fontSizeScale — text size
  • auto-scroll speeds, stored separately for each of the five display modes
  • defaultViewMode, pdfStartPage, pdfFitWidth, musicXmlZoom
  • attachment paths (pdfPath, musicXmlPath, imagePaths) and shelved alternative versions
  • midiJson — the MIDI command sent when the song opens

Annotations are local outright — they never enter sync at all, although you will find them in a backup.

This split is why your guitarist cannot overwrite your transposition or your notes. The singer can keep the same song a third higher and it bothers nobody.

Sync layers

1. Discovery

The leader announces itself on the local network over mDNS/Bonjour under the service _eutepio._tcp. A fallback scan of the local address range runs in parallel so the session can be found on networks where multicast does not work. You can also join by scanning a QR code or typing the IP address by hand.

2. Transport

The leader starts an HTTP server on port 8765. If that port is taken it tries 8766 through 8769 in turn. The server exposes four endpoints — session announcement, join, WebSocket upgrade and a health check. The actual traffic then runs over the WebSocket.

3. Application protocol

Messages are JSON objects with a command key. The leader broadcasts them and members execute them; a member that wants to reach the others sends the message to the leader, who re-stamps it and forwards it. Every message carries the leader's clock stamp and an executeAt time (300 ms in the future by default) so all devices act at the same moment.
The full command list is in the Leader/member model.

What leaves the device

To be precise, because "data never leaves your device" would not be true:

SituationWhat is sentWhere
SyncSongs, setlists and attachments that members requestOther devices on the same local network
Automatic backupA full library backupInto a folder you pick yourself — which may well be one that syncs to a cloud
URL importOnly an HTTP GET to the address you typedThe server you specified
First setlist PDF exportDownloads the Noto Sans fontGoogle Fonts
What is never sent anywhere: no analytics, no crash reports, no telemetry, no account. The app contains no Firebase, Supabase, Sentry or anything comparable.