A folder that carries something, and a way to name one

A folder was a name and nothing else -- and not even that, since PATCH could
rename one and nothing in the interface ever called it. It now carries a
description, a system prompt, and seeds for the model, the kind and the agent
target, with a settings page behind the row.

The prompt is a fourth rung on the ladder, chat > folder > model > instance,
and it goes above the model deliberately: a model's prompt describes the model
wherever it is used, a folder's describes this piece of work whichever model
is pointed at it. It is read when a reply is built rather than copied when a
chat is made, so editing it reaches the chats already there, and the walk up
the parents is bounded and cycle-safe because it runs on the request path.
`api/pages.py` mirrors the ladder for the settings panel and had to gain the
same rung -- a panel naming the wrong source is worse than one naming none,
because it is believed.

The seeds fill in what the request left empty and nothing it filled in: the
folder says what this work usually needs, the screen in front of somebody says
what they want this time. `ssh_profile_id` is a plain string rather than a
foreign key, for the reason `compacted_through_id` is, so it is validated on
read.

Getting *into* a folder needed fixing too. `/api/chats/start` has accepted a
folder_id since folders existed and nothing ever sent one, so the only route in
was to make the chat elsewhere and move it. There is a New chat here on the row
now, and `?folder=` on the new-chat screen.

Naming is a themed dialog, and deliberately not htmx's hx-prompt: htmx calls
the browser's prompt() synchronously and only then fires htmx:prompt with the
answer already in hand, so intercepting the event cannot supply a different one
and the grey box appears anyway. `data-prompt` follows the data-confirm-button
shape instead -- swallow the click, ask, write the answer into hx-vals,
click again behind a guard. JSON.stringify rather than concatenation, or a
folder called `"` produces hx-vals that does not parse and the rename silently
does nothing. Driven under a DOM stub, and there is a test that no template
brings hx-prompt back.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jaroslav Beneš
2026-08-04 08:30:49 +02:00
parent d7a614c96b
commit ec12c3a981
12 changed files with 821 additions and 16 deletions
+19
View File
@@ -21,6 +21,7 @@ from lembas.db.models import (
ROLE_ASSISTANT,
ROLE_USER,
Chat,
Folder,
Message,
Model,
User,
@@ -95,7 +96,25 @@ def _new_chat(
offered the control -- by which point the model had already answered under
the wrong rules. The reasoning effort is accepted for the same reason, and
wins over the model's default: an explicit choice beats an inherited one.
A folder's own defaults fill in anything the request left empty, and nothing
it filled in. That order is the point: the folder says what this piece of
work usually needs, and the screen in front of somebody says what they want
this time. The folder's system prompt is deliberately not among them -- it
is read at request time so that editing the folder later reaches the chats
already in it.
"""
folder = db.get(Folder, folder_id) if folder_id else None
if folder is not None and folder.user_id != user.id:
folder = None
if folder is not None:
model_id = model_id or folder.model_id
kind = kind or folder.kind
if kind == KIND_AGENT:
ssh_profile_id = ssh_profile_id or folder.ssh_profile_id
project_dir = project_dir or folder.project_dir
agent_mode = agent_mode or folder.agent_mode
chosen = None
if model_id:
match = next(