A directory chip that stopped eating the row

The project directory showed its whole path, which on anything real filled the
chip's 16rem basis and pushed the Manual/Edit/Auto/Plan select off the end of
the composer. It shows the directory's own name now, with the full path in the
tooltip -- the leading directories are the part nobody reads, since what you
check before sending is that you are in `myproject` rather than `myproject-old`.

The hidden field still submits the whole path. Shortening a label must never
shorten a value, and there is a test on the row rather than on the markup for
exactly that.

Three CSS rules hold the row together, and none of them is visible from the
markup. `.composer__agent` needed `min-width: 0`: a flex item will not shrink
below its content without it, so the group refused to give and the *last* child
was what fell off -- which is why the mode select was the thing being cut rather
than the path that was too long. `.composer__dir` is capped, being the only
child here whose content is unbounded; a connection name and a mode are both
short and known. And the mode select is `flex: none`, because it is read and
changed constantly and should never be the thing that scrolls out of reach.

`baseName` driven under node against ten paths, trailing slashes and `/`
included. The topbar's copy of the same path was already capped and truncating,
so it is left alone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jaroslav Beneš
2026-08-04 13:17:56 +02:00
parent 9db4e03795
commit b8c9e9a4aa
3 changed files with 101 additions and 7 deletions
+56
View File
@@ -316,3 +316,59 @@ def test_an_unknown_mode_on_the_row_refreshes_to_manual(client: TestClient, db,
db.commit()
agent_session.refresh(db, agent)
assert agent.mode == policy.MODE_MANUAL
# --- The directory chip on the new-chat screen ------------------------------------
def test_the_directory_is_a_hidden_field_carrying_the_whole_path(
client: TestClient, db, registered
):
"""The button shows the directory's own name so it stops eating the row, and
the mode select beside it stops being cut. What is *submitted* has to stay
the full path -- shortening a label must never shorten a value."""
_agent_chat(db) # gives us an enabled feature and a profile to pick
page = client.get("/chat").text
assert 'name="project_dir"' in page
assert "data-dir-value" in page
assert "data-dir-label" in page
# The hidden field is what the form posts; the button is decoration beside
# it. If the name ever moved onto the button, the label would become the
# value and the shortening would reach the server.
field = control_named(page, "project_dir")
assert field.get("type") == "hidden"
assert "data-dir-value" in field
def test_starting_a_chat_stores_the_full_path(client: TestClient, db, registered):
"""The behaviour behind the markup above, asserted on the row."""
from lembas.db.models import Chat as ChatRow
chat = _agent_chat(db)
deep = "/srv/projects/a-rather-long-project-name/services/worker"
client.post(
"/api/chats/start",
data={
"content": "Hello",
"kind": "agent",
"ssh_profile_id": chat.ssh_profile_id,
"project_dir": deep,
},
)
started = db.scalars(select(ChatRow).where(ChatRow.project_dir != "")).all()
assert deep in [c.project_dir for c in started]
def test_the_composer_row_cannot_be_pushed_apart_by_a_path():
"""Three rules that have to hold together, and none of which is visible from
the markup: the group may shrink, the directory is capped, and the mode
never shrinks. The mode select was what fell off the end of the row."""
from pathlib import Path
import lembas
css = (Path(lembas.__file__).parent / "web/static/css/chat.css").read_text(encoding="utf-8")
agent = css[css.index(".composer__agent {") : css.index(".composer__dir {")]
assert "min-width: 0" in agent, "the group cannot shrink below its content"
assert "max-width" in css[css.index(".composer__dir {") : css.index(".composer__dir-path")]
assert 'select[name="agent_mode"] { flex: none; }' in css