LOSOS

LOSOS

Linked Object Shell / Operating System
5.9 KB gzipped · no build step · no dependencies

Your data is a URL. LOSOS is a 6KB reactive framework for building apps on linked data. Edit a value — it auto-saves via HTTP PUT. Open the same URL on another device — it live-syncs via WebSocket. No API layer. No state management. The URL is the API.

You edit auto-saves other devices update screen refreshes

html.js
2.5 KB
store.js
1.9 KB
lion
1.5 KB

5.9 KB total · zero dependencies · read the docs →


A Complete Pane in 50 Lines

No JSX. No compiler. No VDOM. Tagged template literals with surgical DOM patching.

ALICE
BOB

Add tasks in either panel — both sync instantly via CRDT. No server needed.

import { createStore, html, render, keyed } from './losos.js'

var data = {
  '@id': '#this', '@type': 'Tracker',
  'title': 'My Tasks',
  'issue': [
    { '@id': '#1', 'summary': 'Try editing this text',         'status': 'NEEDS-ACTION' },
    { '@id': '#2', 'summary': 'Click the circle to complete',  'status': 'NEEDS-ACTION' },
    { '@id': '#3', 'summary': 'Add a task below',              'status': 'NEEDS-ACTION' },
    { '@id': '#4', 'summary': 'This is reactive linked data',  'status': 'COMPLETED' }
  ]
}

var store = createStore(data, { debounce: 500 })
var root = store.get('#this')
var el = document.getElementById('live-demo')
var now = new Date().toLocaleTimeString()

function isDone(i) { return i['status'] === 'COMPLETED' }

function renderDemo() {
  var issues = store.propAll(root, 'issue')
  var active = issues.filter(i => !isDone(i))
  var done = issues.filter(isDone)

  render(el, html`
    <h2>${data['title']}</h2>
    <p>${active.length} tasks remaining &middot; ${now}</p>
    <input placeholder="Add a task..." onkeydown="${addTask}" />
    ${keyed(active, i => i['@id'], i => html`
      <div>
        <span onclick="${() => store.set(i, 'status', 'COMPLETED')}">○</span>
        ${i['summary']}
      </div>
    `)}
    ${done.length ? html`
      <div>COMPLETED</div>
      ${keyed(done, i => i['@id'], i => html`
        <div>
          <span onclick="${() => store.set(i, 'status', 'NEEDS-ACTION')}">✓</span>
          ${i['summary']}
        </div>
      `)}
    ` : null}
  `)
}

store.onChange(renderDemo)
renderDemo()
setInterval(() => { now = new Date().toLocaleTimeString(); renderDemo() }, 1000)
{
  "@context": {
    "ical":   "http://www.w3.org/2002/12/cal/ical#",
    "wf":     "http://www.w3.org/2005/01/wf/flow#",
    "dct":    "http://purl.org/dc/terms/",
    "title":  "dct:title",
    "summary": "ical:summary",
    "status": "ical:status",
    "issue":  "wf:issue",
    "Tracker": "wf:Tracker",
    "Vtodo":  "ical:Vtodo"
  },
  "@id":   "#this",
  "@type": "Tracker",
  "title": "My Tasks",
  "issue": []
}

// Looks like JSON. Is linked data.
// @context maps flat keys to standard URIs.
// Every node has an @id — a global, dereferenceable ref.
<!-- The entire app shell -->

<script type="application/ld+json" src="data.jsonld"></script>

<script type="module" data-pane src="panes/my-pane.js"></script>
<script src="https://unpkg.com/xlogin"></script>

<div id="losos"></div>
<script type="module"
  src="losos/shell.js">
</script>

What You Get


Comparison

LOSOS includes persistence, auth, and live sync. Others don't.

LOSOSPreactSvelteSolid.jslit-html
Size (gzip)5.9 KB4.5 KB~2 KB*7 KB3.5 KB
Build stepNoJSXYesJSXNo
Auto-persistBuilt in
Live syncWebSocket
Authxlogin
Linked DataJSON-LD
Keyed listsYesYesYesYesYes
Shadow DOMNoNoNoNoRequired

* Svelte runtime is ~2 KB but requires a compiler. Total shipped JS per app is higher.


How It Works

Data

JSON-LD with flat context aliases. Reads like plain JSON. @context maps keys to standard URIs. No @graph. Every node has an @id — a global, dereferenceable ref.

Store

createStore(data, { url, authFetch, debounce }) gives you a reactive JSON-LD graph. Every set, push, remove call marks dirty, schedules a debounced PUT, and notifies listeners. On first save, the store discovers WebSocket support via the Updates-Via header and subscribes automatically.

Templates

Tagged template literals. First render builds DOM. Subsequent renders with the same template shape patch only the ${} holes. Event handlers via onclick="${fn}". Conditional rendering via ${condition ? html`...` : null}.

Panes

An ES module with canHandle(subject, store) and render(subject, store, container). The shell loads all <script data-pane> tags, checks canHandle against the data’s @type, builds a tab bar, and renders the first match. Add more panes by adding more script tags.

The URI is the Router

Every JSON-LD node has a URI. Navigate to a URI, the shell loads the data, finds matching panes, renders. The @id is the route. The @type picks the view. No router library needed — linked data already solved routing.


Data-first. Reactive. Tiny.

Examples Docs Try the demo LLM Skill