feat: conversations on the left, the transcript on the right
The transcript was duplicated across both panes, which was the actual complaint. The left column is now the list of conversations and the right pane is the transcript of the selected one, so the panes show different things. - the open conversation is marked with a check icon and an "Open" tag - each row carries its own transcript as its detail, so moving the selection previews that conversation - conversations are ordered most-recently-updated first, which keeps the one being chatted in at row zero, where Vicinae's selection reset lands - Return now adapts: with text in the search bar it sends, with an empty bar it opens the highlighted conversation (sending an empty prompt was a no-op anyway) - the search bar accessory dropdown is gone. The left column is the conversation switcher now, so the dropdown only duplicated it. Speaker icons in the transcript are not possible, so the labels are plain text. MdImage renders images block-level, centred, and capped at 200px tall, and all three candidate sources were tried in a live probe -- icon://person, a raw icon slug, and a file:// asset path -- and each one fell back to rendering its alt text as a caption. markdown.ts records the reason so nobody retries it blind. Verified on screen: one conversation row with an Open tag and a check icon, the full transcript on the right, and the action bar reading "Open Conversation" while the search bar is empty. typecheck, eslint, 109 tests and vici build all pass.
This commit is contained in:
@@ -54,12 +54,11 @@ To change the model later, either edit the **Model** preference directly, or run
|
||||
| Keys | Action |
|
||||
| --- | --- |
|
||||
| `↩` | Send the message in the search bar |
|
||||
| `Tab` | Jump to the conversation switcher in the search bar |
|
||||
| `↩` with an empty bar | Open the highlighted conversation |
|
||||
| `⌘ N` | New chat |
|
||||
| `⌘ ⇧ C` | Copy the highlighted message |
|
||||
| `⌘ ⌫` | Delete a conversation (in **Chat Sessions**) |
|
||||
|
||||
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.
|
||||
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. The `Tab` shortcut went with it: the left column *is* the conversation switcher now, so an accessory dropdown would only duplicate it.
|
||||
|
||||
### Suggested global shortcuts
|
||||
|
||||
@@ -140,19 +139,15 @@ src/
|
||||
|
||||
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 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.
|
||||
**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 the left column is a conversation switcher — the builtin fuzzy filter would hide rows 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.
|
||||
**The two panes show different things on purpose.** Left is the list of conversations; right is the transcript of one. Both are required rather than a layout preference: `Detail` accepts no typed input (`onSearchTextChange` exists only on `List`, `Grid`, `Form` and `Dropdown`), and list rows are a fixed single line, so full message text cannot render in the list. Input needs a `List`; full text needs a `Detail`.
|
||||
|
||||
**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.
|
||||
**Return adapts to the search bar.** With text in it, `↩` sends. Empty — where sending would be a no-op anyway — `↩` opens the highlighted conversation. Every row carries that conversation's transcript as its detail, so moving the selection previews it.
|
||||
|
||||
**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.
|
||||
**Conversations are ordered most-recently-updated first.** Vicinae resets list selection to the first row on every model change (`GenericListView.selectFirstOnReset`), and streaming changes the model on every flush. Ordering by recency keeps the conversation being chatted in at row zero, which is where the selection lands.
|
||||
|
||||
**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.
|
||||
|
||||
**Both panes are required, not a layout preference.** `Detail` accepts no typed input — `onSearchTextChange` exists only on `List`, `Grid`, `Form` and `Dropdown` — so a detail-only chat command would have nothing to type into. And list rows are a fixed single line, so full message text cannot render in the list. Input needs a `List`; full text needs a `Detail`. Dropping either pane breaks the feature.
|
||||
|
||||
**Speakers are marked with emoji, not icon images.** `👤` and `✨` prefix each message in the transcript. Vicinae's markdown renderer is minimal and there is no reliable way to inline an `icon://` reference into markdown, whereas plain text always renders.
|
||||
**The transcript is text-only.** Speaker labels are plain `You` / `Lam`, with the question dimmed as a blockquote and the answer as body text. Icons are not possible there: `MdImage` renders images block-level, centred, and capped at 200px tall, and every candidate source was tried — `icon://person`, a raw icon slug, and a `file://` asset path — and each fell back to rendering its alt text as a caption.
|
||||
|
||||
**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