The server kept voice and images on /v1 (models scope, which the app's token
already asks for); /me's services and scopes decide what is offered. Rust
sends the recording to /v1/audio/transcriptions, reads /v1/audio/speech back
as a data: URL (CSP media-src data:), and for /v1/images/generations takes
only the id of each of the person's own files from the answer and fetches
/v1/files/<id>/content from the app's own server. The microphone is allowed
only within seconds of the page's own Dictate (voice_arm); WebKitGTK's
media stream is switched on through its settings getter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Files -> Server writes through /api/v2/files/{upload,mkdir,rename} and
DELETE /files (the trash); upload is a Rust command (library_upload) that
picks files in the system dialog and sends them multipart in the field
`files`. A chat's menu opens Tools and skills..., the web's chat-settings
switches over GET/POST /chats/{id}/scope and /allow/clear. A popover can
open at a context menu's point.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The account's memory as the web's Settings -> Memory keeps it: add, change,
remove, over /api/v2/memories. Ctrl+K asks the server's search once two
letters are typed and lists the hits below the titles, the matched ranges
drawn as <mark> elements over plain text.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The server's /api/v2 sets `pinned` on PATCH /chats/{id} and makes, renames and deletes
folders. The sidebar lists the pinned chats first, then the folders (nested, folded as the
server keeps them, each counting its chats), then the rest by day; searching lists every
match flat. A chat's menu pins it and moves it (to no folder, any folder, or a new one);
a folder's menu renames it, makes one inside it, deletes it (its chats stay). Tests F8.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The server's /api/v2 now has PATCH /me {language, theme} and /me lists its languages and
themes. The theme is the account's and choosing one saves it there (a refusal is said);
a server whose /me lists no themes keeps the earlier choice in the app. The language is the
account's unless Settings chose this computer's or another (kept in the app, as the phone
does); a change of the account's opens the window again in it, once.
Fixtures re-pinned to the server's dev (f7f2ff2), with the new /api/v2 parity ones.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Appearance in Settings chooses the language (the system's, or one of the nine the app has;
the window opens again in it, its replies and shells let go first) and the theme (the
account's, or Moria or Shire, drawn at once and the window painted in it). Kept in
localStorage on this computer: /api/v2 has no PATCH /me for either yet, so the account's
own stay as they are. i18n.js reads the choice and carries each language's own name from
the server's catalogue. Tests E5.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Showing the panel connects a frame later (the panel needs its width first). A shell that ended
before that frame -- reopened and closed by the server within one frame, as headless CI's slow
frames made happen -- was quietly replaced by a new one, against "no reconnect of its own".
The frame now connects only if no shell was open as the showing began. The cause of the
B3 failures on GitHub since 94e903e; a test makes it happen every time.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Where the account has another computer linked (GET /devices), a chip before the folder
chooses it; this computer stays the default. Its folder is one its agent chats have worked
in, or a path typed in; the Terminal before the first message opens there too. Tests D4;
failing UI tests are also GitHub annotations.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Archive was one-way in the app. Archived (sidebar, under Files) reads
GET /api/v2/chats?archived=true, newest first and paged; a row opens the
chat, Restore chat PATCHes archived:false and reads the sidebar again, and
the row's menu deletes. An open archived chat says so in its header, with
Restore chat there and in its menu. The words are the web's own ("Archived",
"Restore chat"). Tests: D2 at 1280 and 390 px.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the server's /api/v2 (3.4.0, server dev 5d7b79c):
- The activity panel reads GET /chats/{chat}/tree beside the activity and
draws ui/js/tree.js's rows in a Subagents section, the server panel's words
and layout: nested by parent, position, phase, context %, state, model,
mode + request options, messages and the last one, open slots, the ended
ones folded and dimmed. A team's members are no longer drawn again among
the tasks. The stream's `tree` frame redraws it.
- A subagent's conversation (/subagents/{id}/transcript, read again every 3 s
while it works) and its findings open read-only in the panel; Stop asks,
then POSTs /stop. With an agent chat's device away: the kept tree with the
server's note, and its 409 sentence for the rest.
- A new agent chat's Subagents chip from /models?kind=agent&device= (each
model's `subagents`, `agent_subagents.offered` and `device_runs`); an open
agent chat's from its own `subagents`, as before.
- An approval item's `subagent` names it on the card.
- Progress keys are the spec's own ({{field}}), as the server's catalogues
have them; SERVER_PENDING is gone, every sentence is translated.
tests/ui reads the server's contract fixtures (scripts/fetch-fixtures.sh ->
tests/ui/fixtures, SOURCE says the commit): status frames, tree, tree frame,
transcript, findings, models?kind=agent, a subagent's approval; six C4 cases.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ui/js/tree.js draws subagents.md §12's data: the main agent at the root, each
subagent under its parent in the order added (name, id, position, state in
team.read.states' words, model, mode with request options, context % and a
meter, its progress line, its room messages and the last one), open slots, and
the ended ones folded apart, dimmed, with how each ended; open its session,
its findings, stop a live one. Not wired into the activity panel yet: the
server's /api/v2 does not carry the tree, the transcript or the findings yet.
tests/ui: the spec's own example tree, drawn and acted on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A job of its own on ubuntu-24.04: Node 20, playwright-core 1.65.0 installed
into the runner's temp directory for the run (the repository keeps no
package.json), its own Chromium with the system libraries it needs, then
node tests/ui/run.mjs. Nothing is built and no server started.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ui/js/progress.js draws harness spec 3.4.0's `progress` (progress.md) with the
spec's texts as the page's sentences: the thirteen phases, their variants and
field words, the line with its clock in whole seconds (5s, 1m 05s, 1h 02m),
none for stopped and failed, retrying counted down. A live reply takes
`progress` from any stream frame that carries it and ticks once a second; with
no progress (an older server, or null) it draws the status text as before.
The sentences are the server's to translate: gen-i18n.py takes them from its
catalogues once they are there (SERVER_PENDING), English until then, without
failing.
tests/ui: every case of the spec's conformance/progress.json (copied, 3.4.0),
and a live stream's progress, clock, fallback and stopped line.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The closing callback gets the End as it is (End::unauthorized), instead of
lib.rs reading the code back out of the JSON sent to the page -- which left
End::unauthorized unused and CI's -D warnings refused the build.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Terminal panel opens a shell on the device an agent chat works on, in its
folder: for a new agent chat as soon as the device and folder are chosen (the
server's /api/v2/devices/{device}/terminal/ws?directory=, which the chat's first
message takes over -- the same socket carries on), and for an open agent chat
from the header (/api/v2/chats/{chat}/terminal/ws, the web page's shell).
Offered where GET /devices says the device opens a terminal.
The socket is Rust's (src-tauri/src/terminal.rs, tungstenite on the rustls
ureq already brings): the bearer token goes in the handshake and never reaches
the page, which gets the frames as `terminal` events and sends keys, sizes and
close through commands. 4401 signs the app out as a 401 does.
xterm and its fit addon are the server's vendored build, pinned through
fetch-vendor.sh like CodeMirror and loaded on the first open. Send to chat puts
the selection, the last marked command and its output, or the screen into the
composer, never sent. The terminal and the activity panel take turns in the
panel column. Continue in a terminal stays in the chat's menu.
tests/ui: seven B3 cases with a mocked socket, at 1280 and 390 px.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Subagents chip on a new chat follows `/models` -> model.subagents.offered, the same
team_run.offered the web page renders from. The app read /models once, at sign-in, and kept
it while it stayed open in the tray: subagents switched on afterwards (or a model added)
never reached a new chat until a restart. startNew() now re-reads it (at most every 5 s,
keeping the last list if the read fails) and redraws the draft's chips (modelsChanged()).
An open chat already re-reads its own `subagents` with GET /chats/{id}.
tests/ui: A5 cases -- parallel model offered: chip, slider to the server's cap, no note;
one-request model offered named_only: the `own` note; offered false everywhere: no chip;
switched on while open: the next new chat has it; an open chat follows its `subagents`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An agent chat's bash approval left "Waiting for you to allow bash…" with no card: cardOf()
called append(actions, a, b, c) while append() took one array, so `children.flat` threw for
every approval and plan card (questions used el.append and worked). renderCards() had already
unhidden #cards, which stayed empty. append() now takes children as rest arguments too.
- The editable command is `detail` (services/device_link.py puts the fixed "Run a command" in
the title); it is sent back as the item's text only when changed. Non-editable approvals
show the title and the device's input. `purpose` ("It says: …"), `reason` and the asking
subagent (`job`) are shown, as on the web page.
- renderCards() keeps a card already on the screen (by id) instead of drawing every card again
on each `ask` frame, which the server re-sends whenever the countdown changes; a card that
cannot be drawn shows the error instead of an empty box.
- tests/ui/: headless harness with a mocked Tauri bridge; five A1 cases (device bash card
shows and is visible, Allow/edit/Deny post to /chats/{id}/interactions/{card}, redraw keeps
edits, reopen gets the card from the stream's first pass, 390 px).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The chip's popover is one range from 1 to the server's cap (subagents.cap, /models
subagents_cap, else 32) with two handles: two range inputs overlaid on one track, the span
filled, aria-labels Min and Max with value text, keyboard as any slider, Min never past Max,
the values said above it ("Min 1 · Max 4") and the two ends under it. "Fewest"/"Most" are
gone. The hint says what the numbers count: the chat's own agent -- 1 is the agent alone, Max 4
is the agent and up to 3 subagents. Untouched on a new chat it still leaves the server's
defaults (1-4, shown). i18n: Min, Max and the new hint in all eight languages. CHANGELOG.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Models' `subagents {offered, named_only}` and /models' `subagents_cap`; a chat's
`subagents {offered, named_only, cap}` decides the chip for an open chat (an agent chat
gets none where the server says so); `named_only` shows the spec's `own` line in the
popover and marks the chip. GET /chats/{chat}/team stays the fallback for older servers.
- Context: `auto_compact`, `until_compact` and `compactable` from the server (worked out from
`threshold` with an older one); Compact greyed while not compactable.
- "Tasks: N" for a task's nested fold, the server's sentence; the subagent words aligned with
the server's catalogues (Podagenti, Sottoagenti, Podagenci); hint says the same-model rule.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- "Subagents" everywhere a person reads it (chip, popover with Fewest/Most, scheduled task,
panel); offered per the server's `subagents` on a model or chat where it says it, else as
before; the fields' most is the server's cap, else 32. No subagent-model setting.
- Panel: always reachable from the header's button; the line above the composer only while
something runs; Environment for an agent chat; Context with "N until auto-compact" and a
Compact button (POST /chats/{chat}/compact; a server without it is said so); Background
tasks under Running with elapsed time and a stop square each, unfolding to kind, step,
live text and conversation, nested tasks folded under their parent, Finished N folded;
Room and Plan as sections.
- i18n: 24 new sentences x 8 languages, 9 retired; CHANGELOG [Unreleased].
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Re-wired to api/v2/schedules.py and its contract fixtures: description and
next_run(s), pause/resume verbs, runs paged with `before`, a run's result
(GET runs/{id}, Markdown) unfolded under its row, run.model with the switch from
a busy one, waiting and its retry, timing (late, caught up, by hand). The form
serves new and edit: words -> compile -> title, instruction and next runs; the
rule set by hand (days and a time, read back by preview) when the words give
none; notify policy, fresh or continue, model from the scheduling models,
effort and team size. Models on the list: the person's narrowing (PUT
/schedules/models), each free or busy. Live from the `schedules` event.
i18n: 39 new sentences x 8 languages (312 in all); CHANGELOG [Unreleased].
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The app follows GET /api/v2/events instead of asking /schedules every 15 s; each
`schedules` frame goes to the page (the Scheduled page redraws from it) and a
schedule's newly finished last run is told by the server's decision: `notified`
when it already told a device, else its `notify` policy (matters: a result, not
a silent one; always: silent too; never: nothing), and a failure the first time
in a row and on the third, which asks whether to pause. A run is told once, also
when it finished under the id it had while running. Signing out or another
server ends the stream; a server without it is asked again every 5 minutes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
51 sentences of the app's own in all eight languages (gen-i18n.py OWN), the rest from the
server's catalogues; 11 sentences no page uses any more are gone. The title bar's pills stay
inside its height at touch sizes; a failed scheduled run has its own notification words.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Built against the scheduling plan's /api/v2 routes (GET/POST /schedules, /schedules/compile,
PATCH/DELETE /schedules/{id}, POST /schedules/{id}/run); a server without them answers 404 and
the page says so. A new task is words, the server's reading of them (the rule in words, the
instruction, the next five runs), then Save.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Sidebar: New chat, search, Scheduled, Files, the chats of both kinds by day (Today, Earlier),
Settings with the account at the foot; folds away (Ctrl+B), a drawer on a narrow window.
- A new chat is the chat page empty: a greeting, Agent or Chat, and the composer.
- The composer is a card: the text, then attach, the chips (folder and mode for an agent chat,
model with its picture, effort as the model offers it, the team's size), and a round Send that
Stop replaces while a reply is written. An open chat's chips change the chat.
- Messages: the person's in a bubble on the right with attached pictures as thumbnails, a reply
full width; thinking and tool calls as compact rows that unfold.
- Activity: a chip above the composer for every open chat, the panel on the right as before.
- The agent: a chip in the title bar opening its state and the one thing to do about it; "This
computer is not linked yet" with Link this computer right above the composer of an agent chat,
and "refused" only with the server's reason.
- Files: a page with the two trees as tabs. Settings: grouped, the groups listed beside them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The picture command also serves a message's attached file (/attachments/<id>, one plain id,
8 MB at most) as a data: URL, for thumbnails; the page still reaches the server only through
the bridge.
The watch asks /schedules beside the chats and tells, by the schedule's title, of a new run the
server says is worth telling (`notified`); a silent run never, a server without the routes
never.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The sign-in asked for the agent's token only when the CLI had no login for the server on file,
so a computer with an old, refused login got the app's token alone, and the agent stayed
refused. It now asks unless the CLI says the link works, and hands the token over (the CLI's
--token-stdin login replaces the one it had) or gives it up when the link came up meanwhile.
Link this computer is a device login for the agent's scopes alone, handed to the CLI the same
way, with a cancel: `lembas login <server>` only refreshed a login the CLI already had.
A service that ends for want of a login is "not linked"; another reason is "stopped"; only the
server's refusal, with its reason, is "refused".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The start job on d04bc4b saw the app running after 25 s on X11 and Wayland,
then failed in its trap: the document portal had mounted itself at
$XDG_RUNTIME_DIR/doc, and rm cannot remove a mount point. Unmount it first,
and let the cleanup's leftovers be.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The events stream is held while it opens, so a reconnect and the window
showing again cannot open two; while the window is hidden in the tray it is
not reopened (the server polls for each), and showing the window opens it
again. The Thinking switch says 'Think before answering' in the header too:
'Thinking' is the catalogues' word for a reply in progress. The chat's header
restacks by the chat column's own width (a container query at --bp-narrow),
so a chat beside the activity panel keeps its title.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every new sentence in the nine languages: 54 more, of which all but four come
from the server's own catalogues (its activity panel, team setting and
Thinking switch use the same words); the four about continuing in a terminal
are the app's own, in all eight.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The activity panel beside a chat, from /api/v2's activity routes: Context
(used share, tokens against the window, a segmented bar and a row per
category), Outputs (the files written, lines added and removed, each opening
in the viewer), Background tasks (a team's members as a tree, helpers and
commands: state, elapsed time counting on between reads, model, latest step,
what it is writing, its whole conversation read-only, Stop -- a member after
a confirmation, as it takes those under it), the team's room (who to whom,
reports marked, the words through the page's own markdown) and the plan.
The chat follows GET /chats/{chat}/activity/events while it is open: the strip
above the message box sums the agents up and opens the panel; a new version
reads the panel again while it is open, the costly context part on opening,
after a reply and at most every 15 s. The stream is reopened when the server
closes it, and more slowly while it fails. Beside the chat on a wide window
(the viewer then goes over the chat), over it below --bp-wide, a full-height
sheet at --bp-narrow. The header's Activity button, the palette and Escape
open and close it.
An agent chat with a device session (device_session_id) can be continued in a
terminal: the chat's menu and a header button show 'cd <folder> && lembas -s
<id>' to copy, with lembas the CLI as it starts here (on PATH, else the app's
own copy by its path). Shown rather than run: no terminal opens the same way
on every desktop.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
/api/v2/models now says each model's levels, start level and control. A model
with one level shows no effort control; off and one effort is a Thinking
switch (a checkbox with the switch role); more keep the selector. In the
chat's header the switch and selector save at once; in the new-chat form they
start at the model's start level and redraw when the model changes.
Where a team is offered -- an open chat asks GET /chats/{chat}/team; a new
chat on the server needs a model that calls tools and serves two requests at
once -- the Agents fewest/most fields sit beside it. The header saves the pair
on change (the server refuses a pair outside its bounds and says them; the
fields go back); a new chat sends them only when set, else the person's
default stands.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
/api/v2/models names each model's picture (image, readable with the app's
token at /api/v2/models/images/<file>). A command reads it in Rust -- one
plain file name on the app's door, one of the four types the server accepts,
at most its 2 MB -- and hands the page a data: URL, which its policy already
allows. The avatar shows it once it arrives; the coloured initial stays for a
model without one and until then.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Attachments from the file dialog, a drop on the window (Rust holds the dropped paths; the page
never names one) or a paste (the bytes as the raw body of the call), sent at once to
/api/v2/attachments as multipart and shown above the composer.
- Edit message (rewind), Regenerate reply on the last reply, Copy on both.
- The chat's menu (header and a right click in the sidebar): rename, open in the web interface,
archive, delete.
- The chat's model and effort in its header; effort for a new chat. Model avatars coloured as
the server's stable_hue colours them (MD5 of the id, checked against Python), mixed into the
theme's surface so no colour is hard-coded.
- Ctrl+K palette over commands and every chat's title; Ctrl+N, Ctrl+,.
- The agent's new states in the page; 34 new sentences in all eight languages, the server's own
words where it has them; a Czech row that was Slovak; two untranslated strings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Desktop said "running" as soon as `lembas service run` was started, while the CLI was still
dialling, waiting for a terminal that holds the link, or refused by the server -- the reason went
only to agent.log. Now the supervisor reads what the CLI itself writes: `service.json` in its
state directory (connecting, linked, waiting + why, stopped + why) for the pid it started, the
line it printed before that, and its last line when it ends. A service the app did not start is
described the same way.
A run the CLI ends with 0 because trying again will not help (a refused token, no login) is no
longer restarted every second: it waits five minutes, or until linking again starts it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The editor's vendored modules come from server v2.0.0-beta.4 with the
process stub @lezer/lr needs, so languages load and code is highlighted
again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lezer-lr.mjs imports esm.sh's /node/process.mjs, which the rewrite left
as an absolute path: a 404 in the app, so every parsing language failed.
It becomes ./node-process.mjs, copied from the server at the same ref,
and an absolute import surviving the rewrite now fails the script.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The app's device login asks for `app models` and, where discovery says
login.device.agent_scope, `agent_scope=models memory link` in the same
approval. The agent's token goes to the bundled CLI on stdin
(`lembas login --token-stdin --server <url> --json`) and is never kept
here; if the CLI does not take it, it is given up on the server. A
computer linked already keeps its link; a server without agent_scope
gets the old two steps.
- The server pane reads GET /api/v2/files and /files/content (mounts open
like folders; `withheld` shown in the server's words). The "not offered
yet" state is gone.
- A show card's file comes from GET /api/v2/chats/{chat}/show?node=&version=
or ?shown= as plain text (new `api_text` command, 1 MB cap); args.path is
read only for agent chats on this computer, locally.
- The gate is link protocol 3 plus app_api 2 (APP_API = 2), not the
server's version number, which says 1.7.2 on dev until its first beta.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>