feat: pick the focus and the allowed paths from a directory listing
Release / release (push) Successful in 6s
Release / release (push) Successful in 6s
Both scope controls were free-text boxes, and the vocabulary they asked the user for is paths relative to the session folder. "Which folder is `../shared`" is the one question a text box cannot answer, so the panel now shows the Host's own directory: the `uiWorkspace.listDirectory` primitive the workspace picker's browser drives, one level of child folders, a breadcrumb trail, and a hidden-entries toggle. The path field stays editable inside the picker, because the Host lists folders and an extra may legitimately name a file. Picks are stored in the form that keeps their meaning. A folder at `/work/root/src/shared` is declared `src/shared` when the session started in `/work/root`, and `../shared` going up; only a path with nothing shared — another drive, another tree — stays absolute. The Focus row shows that declared form, with the resolved path in its title, which is what the agent will see. `Set` is gone with the box it committed: the panel writes on pick, as every other control here already did. `Clear` stays, because empty means the session folder — the schema's own default rather than an override naming the same place. The card was 380px wide and grew without bound, a single column of prose taller than the viewport. It is 760px now, with a two-column grid that `auto-fit` collapses to one on a narrow card, the reference prose folded into `<details>`, and a scroll-bounded body. `Modal` sizes its own card from a hashed class in the primitives' stylesheet, so this bundle installs one small stylesheet of its own and outranks that rule with a doubled class selector — no `!important`, and nothing reaching into a class name it does not own. Two fixes came with it. The footer's "Deployment mode:" line read a slot prop that never carried the mode, so it said "ask" in every deployment; it reads the settings document now. And `uiWorkspace` is resolved with `ctx.get` at the call rather than injected, because the panel is still the only way to declare scope in a deployment with no directory browser: it keeps the panel and says what it cannot do, instead of losing the button.
This commit is contained in:
1 parent
ef3df2d939
commit
5d06829118
4 files changed
+1046
-140
No files matched your search
@@ -152,6 +152,17 @@ notes that the preset picker's guide dialog cannot show for a preset no core
|
||||
package ships. Writes go to the profile patch, so reopening a chat gives back the
|
||||
scope it had.
|
||||
|
||||
Both controls are a **directory listing**, not a text box: the panel's vocabulary
|
||||
is paths relative to the session folder, and "which folder is `../shared`" is the
|
||||
one question free text cannot answer. Pressing it opens a second dialog over the
|
||||
panel, shaped like the host's own workspace browser — one level of child folders,
|
||||
a breadcrumb trail, a hidden-entries toggle — driven through
|
||||
`uiWorkspace.listDirectory`, the same Host primitive that browser uses. A pick is
|
||||
stored in the form that keeps its meaning: `/work/root/src/shared` becomes
|
||||
`src/shared` when the session started in `/work/root`. The picker's own path field
|
||||
stays editable, because the Host lists folders and an extra may legitimately name
|
||||
a file.
|
||||
|
||||
Because the panel owns widening, `facet action=add` is the *other* path and the
|
||||
narrower one: it is gated on an approval channel, and a deployment with none — the
|
||||
`danger-full-access` permission preset sets `approval: never` — refuses it before
|
||||
@@ -297,7 +308,7 @@ lib/scope.js the pure half — argument extraction, text, reason
|
||||
lib/guard.js preset row: rule, binding, tool, pre-execute guard
|
||||
presets/facet.patch.yml the `facet` agent preset
|
||||
presets/standard-compaction.patch.yml optional: the same budget for `standard`
|
||||
test/ 73 tests
|
||||
test/ 78 tests
|
||||
```
|
||||
|
||||
`client/client.js` is a browser half rather than a module the host imports, and
|
||||
@@ -322,6 +333,26 @@ refuses a path that is not under a volatile node, and refusing is exactly right
|
||||
the panel is the only writer, and a plugin that could not serve the namespace
|
||||
would be a control that silently stored nothing.
|
||||
|
||||
The listing reaches the panel through `ctx.get('uiWorkspace')` rather than through
|
||||
an injection, and the injection's `available()` is asked on every render. Both are
|
||||
deliberate: `ctx.inject` would make the whole context button conditional on a
|
||||
service the plugin does not own, and the panel is still the only way to declare
|
||||
scope in a deployment with no directory browser — it would simply have no listing
|
||||
to offer, which is what it says instead. The service is resolved at the call
|
||||
because the renderer memoizes a registration's inject result, so a boolean read
|
||||
there would freeze at whatever the first render saw.
|
||||
|
||||
The two dialogs are wider than the primitive's 380px card, which is fixed in the
|
||||
primitives' own stylesheet. `client/client.js` installs one small stylesheet of
|
||||
its own — the widths and a row hover — and passes each card a class through
|
||||
`Modal`'s `className`. Every selector in it doubles the class it names, so it
|
||||
outranks the single hashed class the primitive applies whatever order the two
|
||||
stylesheets land in; there is no `!important` and nothing reaching into a class
|
||||
name this bundle does not own. The panel's body is a two-column grid that
|
||||
`auto-fit` collapses to one on a narrow card, and each dialog scrolls a bounded
|
||||
body rather than growing past the viewport — the panel used to be a tall single
|
||||
column of prose on a 380px card, which is what it looked like.
|
||||
|
||||
## Also in this repository
|
||||
|
||||
- [`facet-preset.md`](../facet-preset.md) — the design note this was built from,
|
||||
|
||||
Reference in new issue
Block a user