Narrow a chat before it starts, and find a file rather than spell it
Six things, all found by using the thing rather than by reading it. The scope menu only appeared once a chat existed, on the reasoning that there was no row to post to. True, and the wrong conclusion: the harness puts a tool's guidance in front of the model the moment the tool is offered, so the menu could not be reached until after the model had been told how to keep notes and handed the tools to do it -- and switching it off then does not un-send that turn. It is on the new-chat screen now and writes nothing: `_scope_context` builds a stand-in Chat, which is `draft.as_chat`'s trick again, and the switches ride along with the first message. Checked means on and a browser submits only the ticked boxes, so every gate also renders a hidden input naming it and `start_chat` subtracts one list from the other; inverting the control would read backwards under a menu that says everything is on unless you say otherwise. Only the off ones are written, because absent means on and one representation of it is what keeps "why is this off?" to a single answer. Nothing is validated against the offered set, since scope_json narrows after every gate -- naming a gate that was never offered switches off something that was not on. Then the scheduling instructions, audited against a 4B model on this machine rather than against my own reading of them. Ten realistic requests, ten compiled, twice over -- so the prompt is sound. What was not sound was `describe`, which built a phrase by joining fragments and read "Every the 1st at 09:00" for the commonest monthly schedule there is, and "Every of January" for a month with no day. That string is the whole of what somebody sees before approving a schedule and the whole of what the model is told about its own chat, so a phrase nobody can parse is a review step nobody performs. It reads as English now, collapses Monday-to-Friday to "every weekday" and seven days to "every day", and every case in the test is a rule that model actually produced. The one mistake it made was naming Wednesday for "every other tuesday", so the weekday numbering is spelled out rather than left as "0-6, Monday is 0": getting that wrong is the error here that still looks like a working schedule. Roughly one call in six also came back empty -- a local runner swapping models under the request will do that -- so an unusable reply is asked for once more before giving up. Not on an LLMError: an endpoint that refused will refuse again, and the reader is better served by the form than by waiting twice for the same answer. Canvas asked for a typed path, which was the last control in the application expecting somebody to remember an absolute path on another machine -- the same complaint the folder page's directory field answered with a picker. /browse takes pick=file and the same fragment makes files buttons, because a second copy of that listing is a second place for the path arithmetic to be got subtly differently. The button carries data-canvas-open rather than an hx-post since the path is not known until the dialog closes, and ui.js posts it through htmx.ajax so the response lands in the panel exactly as every other canvas action's does. The key is `agent:<path>`, so a file opened by hand and one opened by the model are one tab rather than two spellings of it. The tabs already existed and already closed; they now square off at the bottom and the active one takes the body's background, so which is selected is structural rather than a tint nobody can see in a theme they did not choose. Highlighting was already there for every language named and is checked for fifteen of them. Three smaller ones. Tabs kept their scroll position, so switching from a long panel to a short one left the browser clamping to that panel's bottom: the end of it above a screen of nothing, which reads as a page that failed to load. Nothing in CSS can reset a scroll position. The sidebar's footer and the composer sit either side of one vertical edge and were both content-sized, so their top borders met it at different heights and read as one line that had been broken -- `--footer-height` is a calc of the pieces the footer is built from, applied as a min-height to both, which is exactly what `--header-height` already does at the top of the shell. And "Add a workflow" sat flush against the list it adds to, stated as an adjacency because `.btn-row` is right to carry no margin everywhere else it appears. Both pieces of JavaScript were driven under a DOM stub before committing, which is how the tab listener's delegation and the canvas button's six behaviours were checked at all -- `node --check` parses a file that does nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -37,25 +37,29 @@
|
||||
{% include "chat/_canvas_tabs.html" %}
|
||||
|
||||
{#
|
||||
Opening one by hand. A path box rather than a file browser: the model opens
|
||||
what it touches, which is the path this feature is really for, and a second
|
||||
directory browser beside the one the composer already has would be a lot of
|
||||
interface for the rarer case. A relative path resolves against the project
|
||||
directory, exactly as it does for the model.
|
||||
Opening one by hand.
|
||||
|
||||
This was a path box, and it was the one control left in the application that
|
||||
asked somebody to remember an absolute path on another machine -- the same
|
||||
complaint the folder page's directory field answered with a picker. The model
|
||||
opens what it touches, which is still the commonest way a file gets here, but
|
||||
"open that other file" should not mean typing it out.
|
||||
|
||||
The dialog itself still takes a typed path, so nothing has been removed: what
|
||||
has gone is having to type one when you would rather look.
|
||||
#}
|
||||
<div class="canvas__open">
|
||||
{% if canvas_agent %}
|
||||
{# Its own form. A second control named `key` in the same one -- the Scratch
|
||||
button below -- would send two values for one field, and which of them the
|
||||
server took would be an accident. #}
|
||||
<form class="canvas__open-form"
|
||||
hx-post="/api/chats/{{ chat.id }}/canvas/tabs"
|
||||
hx-target="#canvas-inner" hx-swap="innerHTML">
|
||||
<input class="input input--mono canvas__path" type="text" name="key"
|
||||
placeholder="agent:path/to/file" aria-label="Open a file"
|
||||
autocomplete="off" spellcheck="false">
|
||||
<button class="btn btn--sm" type="submit">Open</button>
|
||||
</form>
|
||||
{# The profile is what the browse endpoint is hung off, so the button carries
|
||||
it. `data-canvas-open` rather than an `hx-` verb because the path is not
|
||||
known until the dialog closes -- app.js posts it once it is. #}
|
||||
<button class="btn btn--sm" type="button"
|
||||
data-canvas-open
|
||||
data-profile="{{ agent_profile_id }}"
|
||||
data-dir="{{ agent_dir }}"
|
||||
data-chat="{{ chat.id }}">
|
||||
{{ icon("folder-open", "icon--sm") }} Open a file
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
<button class="btn btn--sm" type="button"
|
||||
|
||||
@@ -188,10 +188,23 @@
|
||||
element carrying `name` has to be the element carrying the request,
|
||||
which is what tests/conftest.py:control_named exists to pin.
|
||||
|
||||
Only on an existing chat -- there is no row to write to before one
|
||||
exists, and a switch that went nowhere is worse than no switch.
|
||||
It is on the new-chat screen too, and there the switches post
|
||||
nothing: they are plain checkboxes carried by the first message.
|
||||
That matters more than it sounds. The harness puts a tool's guidance
|
||||
in front of the model the moment the tool is offered, so a menu that
|
||||
only appeared once a chat existed was one you could not reach until
|
||||
after the model had been told how to keep notes and given the tools
|
||||
to do it. Switching it off then does not un-send that turn.
|
||||
|
||||
Checked means ON, which is the natural reading -- but a browser
|
||||
submits only the *ticked* boxes, and this feature needs to know which
|
||||
ones were unticked. So every gate also has a hidden input naming it,
|
||||
always submitted, and `start_chat` subtracts one list from the other.
|
||||
The alternative, inverting the control so ticking means "off", reads
|
||||
backwards in a menu that says "everything is on unless you say
|
||||
otherwise". Still no JavaScript.
|
||||
#}
|
||||
{% set has_scope = chat and (scope_families or scope_skills or scope_allow) %}
|
||||
{% set has_scope = scope_families or scope_skills or scope_allow %}
|
||||
{% if has_scope %}
|
||||
<div class="picker picker--up" data-picker>
|
||||
<button class="btn btn--icon composer__btn" type="button" data-picker-toggle
|
||||
@@ -212,10 +225,16 @@
|
||||
<p class="picker__group">Tools</p>
|
||||
{% for family in scope_families %}
|
||||
<label class="picker__option picker__option--toggle">
|
||||
{% if scope_prospective %}
|
||||
<input type="hidden" name="scope_all" value="{{ family.gate }}">
|
||||
<input type="checkbox" name="scope_on" value="{{ family.gate }}"
|
||||
{{ 'checked' if family.on }}>
|
||||
{% else %}
|
||||
<input type="checkbox" name="on" value="true"
|
||||
{{ 'checked' if family.on }}
|
||||
hx-post="/api/chats/{{ chat.id }}/scope" hx-swap="none"
|
||||
hx-vals='{"kind": "family", "name": "{{ family.gate }}"}'>
|
||||
{% endif %}
|
||||
<span class="picker__option-body">
|
||||
<span class="picker__option-name">{{ family.label }}</span>
|
||||
</span>
|
||||
@@ -227,10 +246,16 @@
|
||||
<p class="picker__group">Skills</p>
|
||||
{% for skill in scope_skills %}
|
||||
<label class="picker__option picker__option--toggle">
|
||||
{% if scope_prospective %}
|
||||
<input type="hidden" name="scope_skill_all" value="{{ skill.name }}">
|
||||
<input type="checkbox" name="scope_skill_on" value="{{ skill.name }}"
|
||||
{{ 'checked' if skill.on }}>
|
||||
{% else %}
|
||||
<input type="checkbox" name="on" value="true"
|
||||
{{ 'checked' if skill.on }}
|
||||
hx-post="/api/chats/{{ chat.id }}/scope" hx-swap="none"
|
||||
hx-vals='{"kind": "skill", "name": "{{ skill.name }}"}'>
|
||||
{% endif %}
|
||||
<span class="picker__option-body">
|
||||
<span class="picker__option-name">{{ skill.name }}</span>
|
||||
{% if skill.description %}
|
||||
|
||||
Reference in New Issue
Block a user