The composer decides what a chat is, and the topbar stops trying
The mode select in the topbar posted with hx-post against a route that only answers PATCH, so every change returned 405 and the mode never moved. htmx shows nothing when a request fails, so the control looked like it worked: the select stayed where you put it and the server ignored you. It has never worked. Two more of the same kind. A mode could not be chosen at all until the chat existed, so reaching Plan meant sending something in Manual first and letting the model answer under the wrong rules. And the project directory box was real and submitted, but unlabelled and squeezed to a few characters by the select beside it, so it read as broken -- which is how it was reported. So the kind, the connection, the directory and the mode move out of the strip above the text and into one toolbar row beneath it, where attach and send already are. The directory becomes a button that opens a browser over SFTP, because a path is something you would rather find than spell. `scan_dir` is new beside `list_dir`: a picker has to tell a directory from a file before it can draw the row, and `list_dir` backs a tool whose contract is a list of names and must not change under a model mid-conversation. Browsing is a person clicking, not a model calling, so it does not pass through policy.py -- the same argument the terminal panel rests on. It does mean Manual mode has a second exception now. Also: .chip was two components with one name, and the attachment card won, so the Chat/Agent pills silently wore its padding. --radius-md was used twice and declared nowhere, so both fell back to 0. .btn.is-active has been set by syncToggles since the terminal landed and styled by nothing. Enter-to-send ignored isComposing, so committing an IME candidate sent the message. The terminal had five colours of a sixteen-colour palette, with fallbacks from a palette that no longer exists. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -25,47 +25,22 @@
|
||||
|
||||
<h1 class="topbar__title">
|
||||
<span id="chat-title">{{ chat.title if chat else "New chat" }}</span>
|
||||
</h1>
|
||||
|
||||
{# The mode is the one agent setting that changes mid-chat: it decides
|
||||
what gets asked about, not what the conversation is. In the header
|
||||
rather than the settings panel because it is looked at constantly --
|
||||
it is the difference between being interrupted and not. #}
|
||||
{% if chat and chat.kind == "agent" %}
|
||||
<form class="agent-bar" hx-post="/api/chats/{{ chat.id }}" hx-swap="none"
|
||||
hx-trigger="change">
|
||||
<span class="agent-bar__where" title="{{ chat.project_dir }}">
|
||||
{{ icon("server", "icon--sm") }}
|
||||
{{ agent_profile.name if agent_profile else "connection missing" }}
|
||||
</span>
|
||||
<select class="select select--sm" name="agent_mode" aria-label="Mode">
|
||||
{% for value, label, hint in agent_modes %}
|
||||
<option value="{{ value }}" title="{{ hint }}"
|
||||
{{ 'selected' if value == chat.agent_mode }}>{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
<div class="topbar__actions">
|
||||
{#
|
||||
A link, not a script: the flag lives in the URL, so it survives a
|
||||
reload and can be bookmarked. On an existing temporary chat the same
|
||||
corner explains what temporary means and offers the way out -- without
|
||||
one, a conversation that turns out to matter is destroyed a day later
|
||||
with no recourse.
|
||||
#}
|
||||
{# Beside the title because it describes the chat rather than acting on
|
||||
it. The way out of it is Keep, in the overflow menu. #}
|
||||
{% if chat and chat.temporary %}
|
||||
<span class="badge badge--warning"
|
||||
title="Not listed in the sidebar, and removed 24 hours after the last message.">
|
||||
Temporary
|
||||
</span>
|
||||
<button class="btn btn--sm" type="button"
|
||||
hx-post="/api/chats/{{ chat.id }}/keep" hx-swap="none"
|
||||
title="Keep this chat and list it in the sidebar">
|
||||
{{ icon("pin", "icon--sm") }} Keep
|
||||
</button>
|
||||
{% elif not chat and can.get("chat.create") %}
|
||||
{% endif %}
|
||||
</h1>
|
||||
|
||||
<div class="topbar__actions">
|
||||
{#
|
||||
A link, not a script: the flag lives in the URL, so it survives a
|
||||
reload and can be bookmarked.
|
||||
#}
|
||||
{% if not chat and can.get("chat.create") %}
|
||||
<a class="btn btn--icon {{ 'is-active' if starting_temporary }}"
|
||||
href="{{ '/chat' if starting_temporary else '/chat?temporary=1' }}"
|
||||
aria-label="Temporary chat"
|
||||
@@ -82,25 +57,6 @@
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if chat and (can.get("chat.system_prompt") or can.get("chat.params")) %}
|
||||
<button class="btn btn--icon" type="button" aria-label="Chat settings"
|
||||
title="Chat settings" data-toggle="#chat-settings">
|
||||
{{ icon("sliders") }}
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
{% if chat and messages %}
|
||||
<button class="btn btn--icon" type="button" aria-label="Compact this chat"
|
||||
title="Summarise the earlier messages so they stop taking up context"
|
||||
hx-post="/api/chats/{{ chat.id }}/compact"
|
||||
hx-target="#thread" hx-swap="innerHTML"
|
||||
hx-confirm="Summarise everything before the last reply? The messages stay in the transcript; they just stop being sent to the model."
|
||||
data-confirm-title="Compact this chat"
|
||||
data-confirm-label="Compact">
|
||||
{{ icon("archive") }}
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
{% if terminal_enabled %}
|
||||
{# To the left of the inspector, and never open beside it: see the
|
||||
toggle group in app.js. #}
|
||||
@@ -118,6 +74,68 @@
|
||||
{{ icon("search") }}
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
{#
|
||||
Everything else about this chat, behind one button.
|
||||
|
||||
The bar carried eight controls, which is a toolbar nobody can read at
|
||||
a glance. What is left in it is what you reach for while writing --
|
||||
the model, and the two panels. These three are occasional, and every
|
||||
one of them is also a slash command now.
|
||||
#}
|
||||
{% if chat %}
|
||||
<div class="picker" data-picker>
|
||||
<button class="btn btn--icon" type="button" data-picker-toggle
|
||||
aria-haspopup="menu" aria-expanded="false"
|
||||
aria-label="More" title="More">
|
||||
{{ icon("dots") }}
|
||||
</button>
|
||||
|
||||
<div class="picker__menu picker__menu--compact" data-picker-menu role="menu"
|
||||
hidden aria-label="More">
|
||||
{% if chat.temporary %}
|
||||
<button class="picker__option" type="button" role="menuitem"
|
||||
hx-post="/api/chats/{{ chat.id }}/keep" hx-swap="none">
|
||||
{{ icon("pin", "icon--sm") }}
|
||||
<span class="picker__option-body">
|
||||
<span class="picker__option-name">Keep this chat</span>
|
||||
<span class="picker__option-note">
|
||||
Temporary — removed 24 hours after the last message
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
{% if can.get("chat.system_prompt") or can.get("chat.params") %}
|
||||
<button class="picker__option" type="button" role="menuitem"
|
||||
data-toggle="#chat-settings">
|
||||
{{ icon("sliders", "icon--sm") }}
|
||||
<span class="picker__option-body">
|
||||
<span class="picker__option-name">Chat settings</span>
|
||||
<span class="picker__option-note">Prompt, knowledge, sampling</span>
|
||||
</span>
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
{% if messages %}
|
||||
<button class="picker__option" type="button" role="menuitem"
|
||||
hx-post="/api/chats/{{ chat.id }}/compact"
|
||||
hx-target="#thread" hx-swap="innerHTML"
|
||||
hx-confirm="Summarise everything before the last reply? The messages stay in the transcript; they just stop being sent to the model."
|
||||
data-confirm-title="Compact this chat"
|
||||
data-confirm-label="Compact">
|
||||
{{ icon("archive", "icon--sm") }}
|
||||
<span class="picker__option-body">
|
||||
<span class="picker__option-name">Compact</span>
|
||||
<span class="picker__option-note">
|
||||
Summarise the earlier turns so they stop costing context
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user