Docs menuBuild an app
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 app | An app module | |
|---|---|---|
| Is | A website | A JavaScript module on the machine |
| Reaches | What any site does | Files, processes and D-Bus too |
| Ships as | A URL | A directory, and a desktop entry |
| Examples | Any site with a manifest | Domicile'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 launcherinstall-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(), ormain's second argument, is what the app may use:files,processesanddbus(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:Okwith the answer,Errwith why. prefers-color-schemeandprefers-reduced-motionfollow 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.jsDesign and security: APP-MODULES.md.