Build an app

An app on Domicile is web code in an app window: no address bar, its own icon in the launcher, and a window that tiles like any other. There are two kinds.

A web appAn app module
IsA websiteA JavaScript module on the machine
ReachesWhat any site doesFiles, processes and D-Bus too
Ships asA URLA directory, and a desktop entry
ExamplesAny site with a manifestDomicile's Settings and History

A web app

Any site is an app already:

domicile open --app https://excalidraw.com/      # open it oncedomicile install-app https://excalidraw.com/     # add it to the launcher

install-app names and draws it from the site's web app manifest. It shares the browser's profile, extensions and site permissions. Links that leave its site open in a browser window. Design: WEB-APPS.md.

An app module

An app module exports main, which Domicile calls with an element to draw in and the desktop, as it calls a shell's Shell. With React, mount is a main; in plain JavaScript, main draws into the element itself:

// src/main.tsx: a note, kept in ~/notes.txt.import { useAppDomicile } from "@domicile-desktop/react/domicile-provider";import { mount } from "@domicile-desktop/react/mount";import { useEffect, useState } from "react";const PATH = "notes.txt";const Notes = () => {  const { files } = useAppDomicile();  const [text, setText] = useState<string | undefined>(undefined);  useEffect(() => {    files.readText(PATH).then(      (read) => setText(read.unwrapOr("")),      (error: unknown) => console.error(error),    );  }, [files]);  return text === undefined ? null : (    <textarea      defaultValue={text}      onBlur={(event) => {        files.write(PATH, event.currentTarget.value).catch(console.error);      }}      style={{ blockSize: "100vh", inlineSize: "100%" }}    />  );};export const main = mount(Notes);
  • useAppDomicile(), or main's second argument, is what the app may use: files, processes and dbus (System access). A relative path starts at the home folder. An app hears no desktop events and cannot touch windows, input or the lock.
  • Each call answers a Result: Ok with the answer, Err with why.
  • prefers-color-scheme and prefers-reduced-motion follow the desktop.

Build it

Build one module, as a shell is built (Packaging):

// vite.config.tsimport { defineConfig } from "vite";export default defineConfig({  base: "./",  build: {    outDir: "dist",    rollupOptions: {      input: "src/main.tsx",      output: { entryFileNames: "notes.js" },      preserveEntrySignatures: "exports-only",    },  },});
bun add @domicile-desktop/react@next @domicile-desktop/sdk@next react react-dombun add -d vitebunx vite builddomicile open --app ./dist/notes.js
  • The module's directory is the app: every file under it is served, and nothing outside it is.
  • Its storage (localStorage, IndexedDB) is its own, kept by that directory.
  • Several windows of one app share it. Coordinate them with BroadcastChannel.

Ship it

Install the directory anywhere, with a desktop entry so the launcher lists it:

[Desktop Entry]Type=ApplicationName=NotesIcon=accessories-text-editorExec=domicile open --app /usr/share/notes/notes.js

Design and security: APP-MODULES.md.

This page on GitHub