Skip to content

Build your first app

An app is a small, persistent UI you open again and again — a habit tracker, a dashboard, a board. In Lucidos you don't scaffold one by hand: you describe it to the Lucidos Agent in chat, it builds a working version in seconds, and you iterate by talking. This tutorial walks through that loop end to end.

Prerequisite

A running Lucidos workspace with an LLM provider configured — see the Quickstart.

1. Describe what you want

Open a chat and say what the app is for, in plain language:

"Make me a habit tracker — a few daily habits I can check off, and it should remember what I checked each day."

The agent first decides whether an app is even the right answer. A one-off question ("summarize today's emails") is just answered or saved to an artifact; a recurring UI you'll come back to is what an app is for. If your request leaves the scope open, expect at most a question or two — typically:

  • What's the smallest version that's useful? (scope)
  • What should it remember between visits? (storage)

Answer those and it moves straight to building — no long design phase.

2. It scaffolds a working version

The agent creates the smallest thing that demonstrates the idea: a working index.html plus a manifest.json (the app's name, description, and icon). The app shows up in your Apps panel and opens in the Canvas — the right-hand side on desktop, a swipe away on mobile.

What you get by default is native to Lucidos, not a bare HTML form. Every new app:

  • Inherits your theme — it follows your light/dark setting automatically, because the scaffold loads the SDK assets in order (/api/v1/sdk-prefs.js, then /api/v1/sdk-iframe.css, then /api/v1/sdk.js) and calls lucidos.ui.applyPreferences() + lucidos.ui.watchPreferences().
  • Uses theme variables, never hardcoded colorsvar(--bg-primary), var(--text-primary), var(--accent), var(--border-color), … so it looks right in both modes.
  • Reuses Lucidos's component classes.action-btn (with the additive .action-btn-confirm / .action-btn-danger / .action-btn-secondary variants), .list-row, .label, and friends, so controls match the host shell.
  • Sizes in rem, so it respects your font-size / UI-scale preference.

A minimal app the agent might start from looks like this:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Habit Tracker</title>
    <script src="/api/v1/sdk-prefs.js"></script>
    <link rel="stylesheet" href="/api/v1/sdk-iframe.css">
    <script src="/api/v1/sdk.js"></script>
  </head>
  <body>
    <div class="panel">
      <button class="action-btn">Add habit</button>
    </div>
    <script>
      lucidos.ui.applyPreferences();   // apply your theme on load
      lucidos.ui.watchPreferences();   // re-apply when you change it
      // app code…
    </script>
  </body>
</html>

3. Where the files live

An app is just files in your workspace under data/apps/<id>/:

data/apps/habit-tracker/
  manifest.json     # user-facing name, description, icon (NOT in the LLM's context)
  index.html        # the UI
  knowhow/          # optional: how-to docs the agent loads when the app is active
  intents/          # optional: stable statements of what you want
  scripts/          # optional: helper code
  triggers/         # optional: app-specific automation

The app's code lives here. The app's data lives separately, under data/artifacts/<id>/ — for the habit tracker, something like data/artifacts/habit-tracker/data.json. Keep them apart: the code is versioned source, the data is your evolving state.

The app persists state through the SDK rather than its own backend:

// Paths are relative to data/ — so app data is "artifacts/<id>/...".
await lucidos.data.write('artifacts/habit-tracker/data.json', JSON.stringify(state));

const raw = await lucidos.data.read('artifacts/habit-tracker/data.json'); // returns a STRING
const state = JSON.parse(raw);

For anything that talks to an external service, the app calls lucidos.proxy(name).fetch(path, init) and the credential stays server-side — never pasted into the page.

4. Iterate by talking

This is the heart of the loop. To change the app, just say what you want:

"Add a weekly streak count at the top."

For small edits the agent changes the files directly and the open app reloads automatically when its turn finishes — you don't refresh anything. Prefer small, visible changes you can react to each round rather than one giant rewrite.

For heavier work — a multi-file refactor, a real new feature — the agent can spawn an app coding-agent thread: an isolated worktree narrowed to that one app folder. That produces a reviewable change you Apply when you're happy with it (no engine restart for app changes). While it's in progress you can preview the in-flight version before applying.

5. The finishing bar

Before calling an app done, the agent self-checks that it looks like it belongs in Lucidos: works in both light and dark, no hardcoded colors, no px sizing beyond 1px borders, real component classes instead of plain <button>s, one clear focal point, and generous whitespace. A cheap-looking app is treated as a defect, not a "good enough."

Common pitfalls

  • Storing data in apps/<id>/. App data belongs in artifacts/<id>/; the app folder is source.
  • Forgetting the artifacts/ prefix in lucidos.data.* paths — they're relative to data/, so it's artifacts/<id>/data.json, not <id>/data.json.
  • Hardcoding colors or sizing in px — both ignore the user's theme and scale.
  • Using the browser's alert / confirm / prompt or a native <select> — reach for lucidos.ui.toast / confirm / prompt / Select, which the host shell renders themed, above the app.
  • Creating an app for a one-shot. If you only want the answer once, just ask.

Going deeper

The authoritative engine guides are the source of truth for everything above:

  • Building an App (system-knowhow/building-an-app.md) — when an app is the right fit, the visual-quality bar, and how to iterate.
  • JS SDK (system-knowhow/js-sdk.md) — the full lucidos.* surface: data, proxy, ui, events, theme variables, and component classes.

Next: automate part of it with a trigger →