feat: message-per-row transcript, session resume, drop the composer (#4)
This commit was merged in pull request #4.
This commit is contained in:
@@ -54,13 +54,12 @@ To change the model later, either edit the **Model** preference directly, or run
|
||||
| Keys | Action |
|
||||
| --- | --- |
|
||||
| `↩` | Send the message in the search bar |
|
||||
| `⌘ ↩` | Compose a longer, multi-line message |
|
||||
| `Tab` | Jump to the conversation switcher in the search bar |
|
||||
| `⌘ N` | New chat |
|
||||
| `⌘ ⇧ C` | Copy the highlighted answer |
|
||||
| `⌘ ⇧ C` | Copy the highlighted message |
|
||||
| `⌘ ⌫` | Delete a conversation (in **Chat Sessions**) |
|
||||
|
||||
Inside the composer: `↩` inserts a new line, `⌘ ↩` sends.
|
||||
Prompts are single line. An earlier `⌘ ↩` multi-line composer was removed — see [Architecture](#architecture) for why a `Form.TextArea` cannot be laid out cleanly in a launcher.
|
||||
|
||||
### Suggested global shortcuts
|
||||
|
||||
@@ -132,20 +131,24 @@ src/
|
||||
sessions.ts # LocalStorage persistence
|
||||
history.ts # turns -> API messages
|
||||
location.ts # time/place, derived locally
|
||||
markdown.ts # turn -> detail-panel markdown
|
||||
thread.ts # turns -> flat message list
|
||||
markdown.ts # messages -> detail-panel markdown
|
||||
preferences.ts # preferences, normalised
|
||||
hooks/useChat.ts # the chat state machine
|
||||
components/ChatView.tsx # the chat surface
|
||||
components/ComposeForm.tsx
|
||||
```
|
||||
|
||||
A few decisions worth knowing about before you change things:
|
||||
|
||||
**The search bar is the composer.** It is the only always-focused text input in a `List`, which is what makes "hotkey, then just type" work. It is also single-line and there is no API to change that, which is the entire reason `ComposeForm` exists. `filtering` is off, because this list is a transcript — the builtin fuzzy filter would hide turns as you type a question.
|
||||
**The search bar is the composer.** It is the only always-focused text input in a `List`, which is what makes "hotkey, then just type" work. It is single-line and there is no API to change that. A multi-line `Form.TextArea` composer was built and then removed: Vicinae always renders it inside `FormField`, which reserves two ninths of the width for a label gutter and two ninths as an empty right spacer, so it shows up as a small box floating in a large empty form. `filtering` is off, because this list is a transcript — the builtin fuzzy filter would hide messages as you type a question.
|
||||
|
||||
**Every transcript row shares one primary action**, so `↩` always sends whatever is in the search bar regardless of which row is selected. That is what makes the window feel like a single input box rather than a menu.
|
||||
|
||||
**Turns render newest first**, so the answer you just asked for is nearest the search bar instead of below the fold.
|
||||
**Rows are messages, not turns.** Storage keeps a question and its answer together because that is what gets replayed to the model. `thread.ts` flattens that into one message per row so each side can carry its own icon and weight.
|
||||
|
||||
**The user's line is dimmed by putting it in `subtitle`.** List rows are a fixed single line, and the renderer draws `title` in the foreground colour and `subtitle` in a muted one. That is the only lever the extension API offers for varying text weight, so questions go in the subtitle and answers in the title.
|
||||
|
||||
**Messages render oldest first, and the detail panel shows the whole thread.** Vicinae resets list selection to the first row on every model change, and streaming changes the model on every flush, so the selected row cannot be relied on to stay put while an answer arrives. Showing the thread keeps the live answer on screen regardless.
|
||||
|
||||
**Streamed deltas are buffered in refs and flushed to state on a timer** (`FLUSH_INTERVAL_MS`). Re-rendering the transcript per token makes the launcher visibly janky.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user