The web app4 of 12
The transcript
On this page
The transcript is the panel on the right of the stage. It shows what was said in this call, and it is where you type or attach something instead of speaking.
Screenshot placeholder: the transcript panel open — a white header strip reading "Transcript", a column of alternating bubbles (hers white on the left labelled "Kotoba", yours purple on the right), and a composer at the foot with a paperclip, a one-line text box and a send arrow.
Open it with the Toggle transcript button in the dock.
What is in it
Bubbles, oldest first, scrolled to the newest whenever a line arrives
(components/TranscriptPanel.tsx:45-47). Hers sit left and carry a small coral Kotoba label; yours
sit right (TranscriptPanel.tsx:206-232).
A bubble can carry three things (TranscriptPanel.tsx:21-25):
- Text.
- An image — either a thumbnail of what you attached, or a keepsake she recalled, fetched from the backend. A recalled image carries a description of what it is of, because calling it "attachment" would be untrue.
- A file chip — an attachment with nothing to show. It is drawn as its own row with a document icon, never folded into the message text, so the transcript she is sent stays the words and the filename stays a label.
Before anything is drawn, every line goes through one normalisation funnel that strips invisible and
bidirectional control characters (lib/transcript.ts:30-32). Her replies quote web pages and external
tool servers, and a transcript line can deceive exactly the way a terminal row can.
Her filler is stripped from the displayed text only, shape-anchored so a real word starting with
"m" survives (lib/transcript.ts:41-46).
The two voice transports deliver a turn differently — one hands over a whole utterance as a single
event, the other hands over sentence fragments — and the panel reconciles both into one bubble per
turn (lib/transcript.ts:1-11, 66-88). When a correction arrives, only the last line is
rewritten: walking further back would rewrite a turn you have already read
(lib/transcript.ts:50-62).
Typing
The composer only works during a live call. With no call, the box is disabled and reads "Start the
call to chat" (TranscriptPanel.tsx:56, 320-322). It works while you are muted — mute stops the
microphone, not the conversation.
Enter sends. Shift+Enter makes a new line (TranscriptPanel.tsx:313-318).
Attaching a file
The paperclip takes images, PDFs and text files. The picker's filter, the panel's own check and the
rejection message are all built from one list, so a format that is pickable can never be one that is
then rejected (lib/attachments.ts):
| Kind | Accepted |
|---|---|
| Images | any image/*, plus .png .jpg .jpeg .gif .webp .bmp by extension |
| Documents | .pdf |
| Text | .txt .md .csv .tsv .json .log .yml .yaml, plus text/plain, text/markdown, text/csv, application/json |
Anything else is refused in the browser with "Only images, PDF, or text files (…)".
The limits, and where each one is enforced
| Limit | Value | Enforced in |
|---|---|---|
| Image or PDF size | 10 MB | TranscriptPanel.tsx:58, 75-76 |
| Text file size | 1 MB | TranscriptPanel.tsx:59, 75-76 |
| Text content actually sent | first 20 000 characters, then …(truncated) | CompanionExperience.tsx:504-505 |
| PDF length | about 5 pages | CompanionExperience.tsx:516-524 |
| Encoded upload size | 14 000 000 characters of data URL | api/src/kotoba/server.py:842-843 |
| Files per message | 4 | api/src/kotoba/core/attachments.py:20 |
The three paths differ, and it is worth knowing which one you are on:
- A text file is read in the browser and pasted into the message as
File "name": …contents. It never leaves as a file (CompanionExperience.tsx:498-513). - A PDF is counted in the browser first. The page count is read out of the raw bytes; more than
about five and she says so and sends nothing (
CompanionExperience.tsx:516-524). - Images and PDFs are uploaded, base64, to the session's attachment slot and picked up by the next
turn (
CompanionExperience.tsx:534-546).
The fifth file in one message is refused by the backend with the sentence that fits — "I can only
carry 4 files in one message — send this one and I'll take the next lot after it" — and that
sentence is carried out to the transcript rather than replaced with generic advice, because "try
again" cannot work when the answer is "send this one first" (server.py:852-857,
CompanionExperience.tsx:547-552).
The file is stashed before the turn fires. The other order loses the attachment
(CompanionExperience.tsx:527).
A staged attachment shows as a chip above the composer with a thumbnail (images) or an extension
badge, and a × to remove it. The filename in that chip is scrubbed before it is drawn: a downloaded
file's name came from wherever the file did (TranscriptPanel.tsx:271-296).
What the transcript is not
It is not a history browser. The lines live in the page's own memory for as long as the page is open. Reloading the tab starts an empty transcript, and there is no control anywhere in the app for opening a past conversation. See Sessions and history.
