Architecture
How LOSOS works — from data to screen and back.
The layers
┌─────────────────────────────────┐
│ Apps (panes) │ todo, chat, pokedex...
├─────────────────────────────────┤
│ Shell │ shell.js — tabs, pane routing
├─────────────────────────────────┤
│ Extensions │ xlogin (auth), store-crdt (sync)
├─────────────────────────────────┤
│ Kernel │ store.js + html.js + lion
│ │ reactivity, rendering, data
├─────────────────────────────────┤
│ Data layer │ JSON-LD at URLs
│ │ HTTP PUT/GET, WebSocket, Solid pods
└─────────────────────────────────┘
The kernel is what you can't remove — store, html, lion. Everything else is pluggable. The shell is one possible UI. xlogin is one possible auth driver.
The reactive flow
store.set() → dirty tracking → debounced PUT → WebSocket pub
↓ ↓
onChange fires ← other clients reload ← sub
↓
renderApp() → html`` patches DOM
- You call
store.set(node, 'status', 'COMPLETED') - The store marks the data dirty and starts a debounce timer
- The store calls all
onChangelisteners — yourrenderApp()runs render()compares the new template values to the previous ones and patches only what changed- After the debounce, the store PUTs the JSON-LD to the server
- The server sends a WebSocket
pubmessage to other clients - Other clients
reload()and re-render
Data model
LOSOS uses JSON-LD — JSON with a @context that maps short keys to standard URIs. It looks like plain JSON but is linked data.
{
"@context": {
"dct": "http://purl.org/dc/terms/",
"title": "dct:title"
},
"@id": "#this",
"@type": "Tracker",
"title": "My Tasks"
}
Key principles:
- Every node has an
@id— a global, dereferenceable URI @typedetermines which pane renders it@contextmaps short keys to full URIs — no custom formats- Data lives at a URL.
store.save()PUTs it back. The URL is the API.
The URI is the router
There is no router library. Every JSON-LD node has a URI. Navigate to a URI, the shell loads the data, finds matching panes, renders. @id is the route. @type picks the view.
Pane discovery
The shell scans for <script data-pane> tags and loads each module. For each pane, it calls canHandle(subject, store). Panes that return true get a tab. The first match renders immediately.
This means data chooses its view. Add a new @type, add a pane that handles it. No configuration, no routing table.
Persistence
The store auto-saves via HTTP PUT. It discovers WebSocket support via the Updates-Via response header on the first save. If present, it connects and subscribes for live updates.
createStore(data, {
url: 'https://pod.example/data.jsonld',
authFetch: window.xlogin.authFetch,
debounce: 800
})
// store.set() → waits 800ms → PUT to url
// PUT response has Updates-Via header → WebSocket connects
// Other clients get pub → reload() → re-render
Why not a virtual DOM?
LOSOS uses tagged template literals. The template strings are the same object reference on every render (JavaScript interns template literals). So render() can check: same strings? Patch only the ${} holes. Different strings? Full rebuild.
This is faster than a virtual DOM for the size of apps LOSOS targets. No diffing, no reconciliation, no fiber scheduler. Just a for loop comparing values by reference.
For lists, keyed() matches items by key and skips items whose template values haven't changed. In a 50-item list where 1 item changes, 49 skip entirely.
What LOSOS is not
- Not a general-purpose framework — it's for apps where data lives at URLs
- Not a replacement for React/Svelte — those solve different problems at different scale
- Not server-rendered — it's a client-side framework for mutable linked data
- Not a build tool — no compilation, no bundling, no tree-shaking. Copy and use.
Future
- Data declares its view — a
ui:defaultPanetriple on@type. The pane URL lives in the data itself. - User preferences — choose your pane per type. Alice uses kanban, Bob uses list. Same data.
- Pane registry — community-shared panes. One line to add a calendar view, a kanban board, a chart.
- JSON Schema forms — auto-generate edit forms from
$schema. Validate on save. - Offline — service worker queues PUTs when disconnected, replays on reconnect.
- Conflict resolution — ETags and
If-Matchfor safe concurrent editing.