Pinned models that know which side you are on

Reported: a pinned model always opened an ordinary chat, even with Agents
selected in the sidebar. They now carry `&kind=agent` with the switch -- a
preselection like `?model=` itself, so the new-chat screen still decides and
nothing is fixed until the first message is sent.

They sit above the tree the switch swaps, so this is the same shape as the New
chat button a few commits ago and gets the same treatment: their own partial,
arriving out of band. The group is rendered even when nothing is pinned,
because a block that vanished when the last model was unpinned would leave that
fragment with nowhere to land -- and htmx says nothing at all when a target is
missing, which is the silent failure this codebase keeps cataloguing.
`.nav-group--pinned:empty` stops the empty one taking room.

Chasing it turned up something else. The shortcuts came from `_chat_context`,
which only the chat pages build -- so the library, connections, settings and
folder pages carried the sidebar without them. A shortcut that is there on one
page and gone on the next. They come from `sidebar_context` now, where they
belong: it is sidebar content, and it is what the fragment route has.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jaroslav Beneš
2026-08-04 12:53:47 +02:00
co-authored by Claude Opus 5
parent 5984d90fb0
commit 9db4e03795
6 changed files with 127 additions and 17 deletions
+4
View File
@@ -923,6 +923,10 @@ body.is-resizing .canvas__body { pointer-events: none; }
/* --- Sidebar navigation ---------------------------------------------------- */
.nav-group { margin-bottom: var(--sp-4); }
.nav-group:last-child { margin-bottom: 0; }
/* The pinned group is rendered even with nothing in it, so the Chat/Agent
switch has a target to swap out of band -- htmx says nothing when a target is
missing. Empty, it must take no room. */
.nav-group--pinned:empty { display: none; margin: 0; }
.nav-group__label {
display: flex;
align-items: center;
@@ -0,0 +1,30 @@
{% from "_macros.html" import model_avatar %}
{#
Shortcuts to start a chat with a particular model. These link rather than
post, so no chat exists until something is actually said.
Its own partial, and always rendered even when empty, because the Chat/Agent
switch changes what they link to and they sit above the tree the switch
swaps -- so they arrive out of band, exactly as the New chat button does. An
empty div is what lets that swap find its target: a block that vanished when
the last model was unpinned would leave the out-of-band fragment with nowhere
to land, and htmx says nothing when a target is missing.
#}
<div class="nav-group nav-group--pinned" id="sidebar-pinned"
{% if oob %}hx-swap-oob="true"{% endif %}>
{% if pinned_models and can.get("chat.create") %}
<div class="nav-group__label">Pinned models</div>
{% for model in pinned_models %}
{# Carries the side the switch is on. Picking a pinned model on the Agent
side used to open an ordinary chat, which is the fork silently ignoring the
one choice already made. `?kind=` is a preselection like `?model=` itself:
the new-chat screen still decides, and nothing is fixed until the first
message is sent. #}
<a class="nav-item nav-item--model"
href="/chat?model={{ model.model_id }}{{ '&kind=agent' if sidebar_kind == 'agent' else '' }}">
{{ model_avatar(model, cls="nav-item__avatar") }}
<span class="nav-item__label">{{ model.label }}</span>
</a>
{% endfor %}
{% endif %}
</div>
@@ -56,6 +56,7 @@
{% if oob %}
{% with oob = true %}
{% include "partials/_sidebar_actions.html" %}
{% include "partials/_sidebar_pinned.html" %}
{% endwith %}
{% endif %}
+1 -14
View File
@@ -20,20 +20,7 @@
hx-swap="none"></div>
<nav class="sidebar__scroll" aria-label="Chats">
{% if pinned_models and can.get("chat.create") %}
{# Shortcuts to start a chat with a particular model. These link rather than
post, so no chat exists until something is actually said. #}
<div class="nav-group">
<div class="nav-group__label">Pinned models</div>
{% for model in pinned_models %}
<a class="nav-item nav-item--model" href="/chat?model={{ model.model_id }}">
{{ model_avatar(model, cls="nav-item__avatar") }}
<span class="nav-item__label">{{ model.label }}</span>
</a>
{% endfor %}
</div>
{% endif %}
{% include "partials/_sidebar_pinned.html" %}
{% include "partials/_sidebar_tree.html" %}
</nav>