The extras control accepts a file — the field and its label said so — and the listing under it could not show one, because the Host primitive behind the picker browses folders and nothing else. So a grant for a single file meant typing its name exactly, which is the one thing a listing exists to avoid. The Host has a second lister and it does return files: `remote.workspaceFiles.list`, the same listing behind the sidebar's own file tree, confined to the session's workspace. The extras picker now takes that one while the level is inside the session folder — which is where an extra usually is — and falls back to the folder lister above it, saying so in the dialog rather than letting file rows vanish. The focus picker never offers files: a focus is a folder, so a file row there would offer something that cannot be chosen. The two listings normalise into one level shape, so the picker does not care which answered, and a row now carries its type: a folder is a way down, a file is a thing to take. Clicking a file fills the path field and marks the row, and the footer adds it in declared form. Pressing Enter on a name the level already lists is no longer reported as a failure — the path is exactly what the dialog is for. Everything stays additive: with no workspace file service, or above the session folder, the picker lists folders exactly as before.
This commit is contained in:
1 parent
a40040ec17
commit
c49acfdb2c
4 files changed
+590
-82
No files matched your search
@@ -173,14 +173,32 @@ 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 — which is why that action reads **Add path or file** and its field **Path
|
||||
or file**, rather than promising a folder the list would then fail to show.
|
||||
panel, shaped like the host's own workspace browser — one level of children, a
|
||||
breadcrumb trail, a hidden-entries toggle, and a file-type glyph per row. 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 listing is driven by **two Host listers**, because they answer different
|
||||
questions and the extras need both:
|
||||
|
||||
| lister | returns | reach |
|
||||
|---|---|---|
|
||||
| `uiWorkspace.listDirectory` | folders | anywhere on the machine |
|
||||
| `remote.workspaceFiles.list` | files and folders | the session's workspace only |
|
||||
|
||||
The extras picker takes the file listing while the level is inside the session
|
||||
folder, which is how a file is chosen by clicking its name instead of typing it —
|
||||
the same listing behind the sidebar's own file tree, applied one level at a time.
|
||||
Above the session folder the Host confines it, and the level falls back to folders
|
||||
and says so in the dialog: a file outside the workspace is still added by typing
|
||||
its path, which is why the action reads **Add path or file** and its field **Path
|
||||
or file**. The focus picker never offers files at all — a focus is a folder, so a
|
||||
file row there would offer something that cannot be chosen.
|
||||
|
||||
A folder row is a way down; a file row is a thing to take. Clicking a file fills
|
||||
the path field and marks the row, and the footer's action then adds it in declared
|
||||
form; pressing Enter on a name the level already lists is not an error, because
|
||||
the path is exactly what this dialog is for.
|
||||
|
||||
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
|
||||
@@ -246,7 +264,7 @@ Honest boundaries, because each one is a decision rather than an oversight:
|
||||
|
||||
Verified:
|
||||
|
||||
- 88 tests (`npm test`) over the scope algebra, the policy state, the guard, and
|
||||
- 93 tests (`npm test`) over the scope algebra, the policy state, the guard, and
|
||||
the browser half — including that containment is decided on canonical targets
|
||||
(a symlink inside the facet pointing outside it is refused), that a prefix is
|
||||
not a parent (`/a/room` does not contain `/a/roommate`), that a malformed tool
|
||||
@@ -256,8 +274,9 @@ Verified:
|
||||
declared scope rather than the session directory, that a search naming no path
|
||||
is refused when the directory it defaults to is outside the facet, that a
|
||||
delegated session inherits its parent's root at any depth and is not a way back
|
||||
out of a self-narrowing, and that every `facet` result satisfies the tool's own
|
||||
declared output schema.
|
||||
out of a self-narrowing, that both listings normalise into one picker level and
|
||||
that a file listing the Host confines still leaves a browsable one, and that
|
||||
every `facet` result satisfies the tool's own declared output schema.
|
||||
- The bundle's patch layers parse, merge and land correctly against the real
|
||||
profile: `dsh --profile web --patch ./cordis.patch.yml --patch
|
||||
./presets/facet.patch.yml --dump-config` shows `facet-policy`, `preset-facet`,
|
||||
@@ -335,7 +354,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/ 88 tests
|
||||
test/ 93 tests
|
||||
```
|
||||
|
||||
`client/client.js` is a browser half rather than a module the host imports, and
|
||||
@@ -360,14 +379,16 @@ 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 listings reach the panel through `ctx.get` rather than through an injection —
|
||||
`uiWorkspace` for folders and `remote.workspaceFiles` for files — and each
|
||||
`available()` is asked on every render. Both are deliberate: `ctx.inject` would
|
||||
make the whole context button conditional on services 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 services are 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. Both packages are named in this bundle's `dsh.client.inject`, so
|
||||
a profile that installs this one gets the services its dialog asks for.
|
||||
|
||||
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
|
||||
|
||||
Reference in new issue
Block a user