b602657450
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>
67 lines
2.7 KiB
Python
67 lines
2.7 KiB
Python
"""Dialogs and toasts, checked without a runtime.
|
|
|
|
There is no JavaScript test runner here and hard rule 1 keeps Node out of the
|
|
project, so the behaviour is driven by hand under a DOM stub before committing.
|
|
What can be pinned in the suite are the invariants the file states about itself
|
|
-- and in particular the one that would look like an improvement to somebody
|
|
tidying up later.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
import lembas
|
|
|
|
ROOT = Path(lembas.__file__).parent
|
|
SOURCE = (ROOT / "web/static/js/ui.js").read_text(encoding="utf-8")
|
|
TEMPLATES = ROOT / "web/templates"
|
|
|
|
|
|
def test_the_dialogs_never_fall_back_to_the_browsers_own():
|
|
"""`window.confirm` and `window.prompt` cannot be styled, ignore the theme
|
|
and block the tab. Putting one back is the thing this module exists to
|
|
prevent."""
|
|
assert "window.confirm(" not in SOURCE
|
|
assert "window.prompt(" not in SOURCE
|
|
|
|
|
|
def test_no_template_uses_htmx_s_own_prompt():
|
|
"""htmx's hx-prompt 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 native box appears
|
|
regardless. `data-prompt` exists because of that, and an hx-prompt slipped
|
|
in later would summon the grey box back with nothing to catch it.
|
|
|
|
hx-confirm is fine and is used widely: that one fires *before*, and ui.js
|
|
intercepts it.
|
|
"""
|
|
# The attribute, not the word: the comment beside `data-prompt` names
|
|
# hx-prompt in order to say why it is not being used.
|
|
offenders = [
|
|
path.relative_to(TEMPLATES)
|
|
for path in TEMPLATES.rglob("*.html")
|
|
if 'hx-prompt="' in path.read_text(encoding="utf-8")
|
|
]
|
|
assert not offenders, f"hx-prompt summons window.prompt: {offenders}"
|
|
|
|
|
|
def test_the_prompt_answer_is_json_encoded_rather_than_concatenated():
|
|
"""A folder called `"` would otherwise produce hx-vals that does not parse,
|
|
and htmx would send the request with the field missing rather than with the
|
|
name -- a rename that silently does nothing."""
|
|
start = SOURCE.index("[data-prompt]")
|
|
block = SOURCE[start : SOURCE.index("data-confirm", start)]
|
|
assert "JSON.stringify" in block
|
|
|
|
|
|
def test_every_prompt_button_names_a_field_or_takes_the_default():
|
|
"""The field name is what the route reads. A button with a field the route
|
|
does not look at posts nothing and looks exactly like one that works."""
|
|
for path in TEMPLATES.rglob("*.html"):
|
|
text = path.read_text(encoding="utf-8")
|
|
if "data-prompt=" not in text:
|
|
continue
|
|
# Either an explicit field, or the "name" default the handler applies.
|
|
assert "data-prompt-field" in text or "/api/folders" in text, path
|