The web app3 of 12
The main screen
On this page
/ redirects to /app, and /app is the whole product. There is no landing page in this repository:
opening Kotoba in a browser means opening the companion (app/page.tsx:1-14).
Screenshot placeholder:
/appmid-call — the wordmark top left, a green LIVE badge top right, the rounded stage card in the middle with her face on the room backdrop and a "Kotoba" name plate in its bottom-left corner, and the row of round sticker buttons under it with the status pill above them.
What is stacked, and in what order
She is drawn inside one rounded card — the "stage" — and everything else orbits it.
| Layer | What it is | Where |
|---|---|---|
| Backdrop | a still PNG of her room, plus a slow warm-light drift and fourteen drifting dust motes | components/RoomBackdrop.tsx |
| Avatar | the Live2D model on a PixiJS WebGL canvas, pointer-events: none | components/Live2DCanvas.tsx:87-89 |
| Stage chips | the working… pill, the name plate, the notice pill, the "calling…" veil | CompanionExperience.tsx:715-841 |
| Dock | the status pill and the round control buttons, below the card, not over it | components/UIOverlay.tsx |
Two more surfaces are portalled to <body> rather than drawn inside the card, because the stage has
a CSS transform on it and a position: fixed child inside a transformed ancestor is trapped: the
approval card (components/panels/InputRequestPanel.tsx:43-46, 156-157) and the file viewer
(components/panels/FilesPanel.tsx:162-163, 224).
The Live2D runtime is loaded in the browser only. /app renders the whole experience with
ssr: false, because it touches WebGL, window, and a client-only voice provider
(app/app/page.tsx:1-8, 23-25).
Before she is drawn
A full-screen loader covers /app while the model loads. It comes down when the model reports ready,
and it comes down instantly — with no "Ready!" fade — when the model failed or when no model is
installed at all (components/CallLoader.tsx:1-10, 51, CompanionExperience.tsx:626-628). The
loader eats every click, so a model that will never load must not leave it up.
The badge and the status line
Both read from one decider, so they cannot contradict each other or the notice pill
(lib/call-status.ts).
The badge, top right (lib/call-status.ts:27-30):
| Badge | Meaning |
|---|---|
OFFLINE | no call |
RINGING | connecting |
LIVE | connected |
VOICE DOWN | connected, but voice is broken — the session is alive and typing still works |
The status pill above the controls (lib/call-status.ts:34-45), in precedence order:
Focused — working…— work talks over everything.Tap the mic to call KotobaCalling…Mic blocked — she can't hear you, but you can typeVoice is down — she can't hear you, but you can typeYou're muted — she can't hear youHer voice is down — talk or type, captions still workYou're connected — just talk
Losing her hearing outranks mute, because unmuting would fix nothing. Mute outranks losing her speech, because mute is the half you can act on.
The controls
Before a call there are two buttons (UIOverlay.tsx:145-154): Call Kotoba and Settings.
Settings is reachable before a call on purpose — configure her, then call.
During a call the row is (UIOverlay.tsx:156-198):
| Button | Label in the markup | Notes |
|---|---|---|
| Terminal | Toggle terminal | pulses while she is working and the panel is shut; green dot when there are rows to read |
| Files | Toggle files | yellow dot when a file is new or edited and unopened |
| Report | Toggle report | only rendered when a report exists; pulses until opened |
| Transcript | Toggle transcript | |
| Settings | Settings | |
| Mic | Mute microphone / Unmute microphone | a real mute — the call stays live. While ringing it is a disabled spinner labelled Connecting. |
| Leave | Leave call | hangs up |
Mute and Leave are separate controls, divided from the panel buttons by a vertical rule.
How the panels share the space
Two classes of panel (lib/store.ts:178, 297-312):
- Small: transcript, terminal, files. They can be open together. Terminal and Files share one fixed-width rail on the left of the stage and split its height between them; the transcript opens on the right.
- Big: report and settings. Opening one closes everything else, and only one big panel can be open at a time.
The stage card shrinks as panels open, so she is never pushed off-screen
(CompanionExperience.tsx:684-694). Widths, from the source: the left rail is min(30vw, 340px), the
transcript min(34vw, 360px), Settings min(38vw, 480px), the report min(52vw, 680px).
The notice pill
One notice surface, top right of the stage, so there is never a second visual language for "something
is wrong". Which failure gets to use it is decided in one place (lib/notices.ts:78-91), in this
order:
- The events channel is down. Approval cards ride that channel, so while it is down nothing else on screen can be trusted: "Can't reach the backend right now — retrying. Approvals and live progress are paused until it's back."
- Voice. A call that cannot hear or speak is what a person notices first.
- No model installed. Names the folder the backend actually reads and the route back to the face step. Calls, chat and work all still run.
- The model failed to load. Distinct from absence: something that is installed failed to fetch or parse.
A fatal notice is red, the rest are yellow, and a notice with a dismiss control shows a ×
(CompanionExperience.tsx:766-808).
The stage chips
working…— a purple pill, top left of the stage, whenever a background job is running (CompanionExperience.tsx:715-740).- Name plate — bottom left, a dot that pulses green while she is speaking, beside the word
Kotoba(CompanionExperience.tsx:741-764,899-901). calling…— a dark veil with a red pill over the whole stage while ringing (CompanionExperience.tsx:810-841), and the card itself shakes (CompanionExperience.tsx:696).
The call shell writes the name
Kotobaliterally. If you renamed her during setup or in Settings → Personality, the name plate, the transcript's speaker label, the "Call Kotoba" button and the "Tap the mic to call Kotoba" status line all keep sayingKotoba(CompanionExperience.tsx:763,TranscriptPanel.tsx:229,UIOverlay.tsx:147,lib/call-status.ts:36). The rename itself is real — it is written to the backend and it is what she is told she is called, and the setup wizard's own screens use it. This is the shell's labels, not her.
Helper chibis and the plan tab
Two more fixed surfaces appear only when there is something to show: the row of helper chibis in the
bottom-left corner when she delegates work (components/SubagentChibis.tsx), and the plan tab in the
bottom-right corner when a work run has a task list (components/panels/TaskTab.tsx). Both are
described in The plan and the helpers.
What ending a call clears
Leaving a call, or losing the connection, wipes the terminal rows, the emotion, the report title, any
open input card, the helper chibis and the plan (lib/store.ts:293-296). Files are deliberately
kept — they are on disk. The face is reset to neutral, because a call that ended on sad used to
open the next one still wearing it.
