Skip to content

feat(widgets): let the person choose a project folder for widget dev sessions - #612

Merged
mrgoonie merged 8 commits into
mainfrom
feat/538-dev-folder-choice
Oct 7, 2026
Merged

mrgoonie merged 8 commits into
mainfrom
feat/538-dev-folder-choice

Conversation

@mrgoonie

@mrgoonie mrgoonie commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The person can now choose the project folder a widget dev session watches, from the conversation, and Clark can then work in it until the person takes that back.

  • /develop and /develop <folder> (or asking Clark, through the new develop_widget action choose) answer with a host-owned develop command card. It has a row for the proposed folder (Develop this folder), a row to Choose folder…, the folders already chosen, and the node's recent sessions. A stopped session can be developed again.
  • The card itself starts nothing. Each row carries the closed action { kind: "develop-folder", root? }. A press in the person's own client calls the existing person-only POST /widget-dev/sessions, so no agent, widget or machine surface picks a host path for itself.
  • The card shows the folder it grants, and the press grants only that. The proposed path is resolved (links, junctions, ..) before the card is built; the row's label and its press use the resolved folder, and the note shows the given path when it differs. The card says the choice covers every folder inside the folder. A press is kept as a choice only when the pressed path is still the folder itself when pressed, so a link swapped in after the card was drawn starts that session but grants nothing; the outcome line says the folder was not kept, and why. A path that is missing, relative, or a network share or device path gets no Develop button, only a note.
  • Picking a folder works the same way on every platform:
    • The desktop app opens the OS folder dialog through the existing desktop:pickDirectory bridge, but only when the gateway is loopback.
    • In these cases the row asks for the full path on the node's machine and says why:
      • a browser;
      • a desktop app pointed at a remote node (--node-url);
      • a dialog that fails to open, for example on Linux without a file chooser portal.
    • Escape, Cancel or a submit returns focus to the Choose folder… button (DESIGN.md keyboard rules).
  • Chosen folders:
    • A person's start marks the stored session chosenByPerson: true. A whole drive (filesystem or drive root) or the home folder itself is never marked: a session may run there, but Clark keeps no access.
    • checkRoot lets a session Clark starts use a chosen folder or anything inside it, in addition to the widget workspace and the person's own workspace.roots.
    • A chosen root is compared as stored. If its path now resolves elsewhere (for example the folder was replaced by a junction or symlink), the choice is dropped from the grant, so a swapped link cannot widen it.
    • Clark picking a session back up keeps the mark. A session Clark starts never sets it.
    • workspace.roots stays unregistered, so no surface can write it.
  • Forgetting a folder:
    • New person-only POST /widget-dev/chosen-folders/forget { root } → { root, forgotten }, idempotent. It is refused for machine surfaces in isPersonOnlyRoute (WebSocket relay, clarkcant api, MCP) and by the route itself for a request marked with a machine-surface header.
    • /develop forget (or the develop_widget action folders when asked in words or by voice) shows the folders Clark may use, each with a Forget button (new closed action { kind: "develop-folder-forget", root }). A chosen folder that is not found at its path now is listed as "not found now", still with Forget.
    • The answer carries stillCoveredBy when another chosen folder or a configured project root still holds the forgotten one, and the message says Clark may still develop there through it.
    • Forgetting does not stop a running session; the messages say so.
  • develop_widget choose runs only in a turn the person sent, like start, rebuild and place. folders only narrows access and stays open.
    • After a press settles, the row's old badge ("stopped", "you chose") gives way to the status line.
  • When Clark asks for a folder it may not watch, develop_widget start still returns 403 ROOT_NOT_OWNED and starts nothing. Its answer carries the same card as a host block, so the person can start that folder with one press. The refusal text (VI/EN) points to the card and /develop.
  • Docs: EN and VI updates to docs/open-interfaces*.md and docs/widget-development*.md. The official site is updated in docs(widgets): choose a project folder for widget dev sessions with /develop clarkcant-web#134.

Fixes #538

Review follow-up, round 2 (CHANGES_REQUIRED at 356bdc5)

  • B2 at press time: start sets chosenByPerson only when the pressed path is canonical (sameRoot(resolve(pressed), root)). The session view now carries chosenByPerson, and the client says when a press was not kept. A missing proposed path gets no button. Tests cover a swap before the press and a missing path made into a junction later.
  • B4: choose is refused from mcp, relay, cli-api, automation, peer and channel turns, with and without root; folders still works. A test covers each origin.
  • N1: forget answers stillCoveredBy, and the done message and docs say so.
  • N2: marked() lists every mark, with found; the card lists missing ones with a "not found now" badge and Forget.
  • N3: no Develop button for UNC, device or relative paths; the row says why.
  • Test folders are canonicalised (realpathSync.native), so the suites hold on macOS, where the temp folder is reached through a link.

Review follow-up, round 1 (CHANGES_REQUIRED at e17c9ea)

  • B1, a junction widening the grant: fixed as described under "Chosen folders", with a test that swaps the chosen folder for a real junction (symlink off Windows).
  • B2, the card showing one folder and granting a wider one: fixed (resolved label and action, both paths shown, subfolders stated, drive root and home not marked), with tests.
  • B3, no revocation: fixed with the forget route, card action and /develop forget, with tests for the grant being removed and for machine surfaces being refused.
  • S1: the "or ask Clark" claim is now true through develop_widget choose / folders.
  • S2: focus returns to Choose folder… after Escape or Cancel (unit-level and in the Playwright journey).
  • S5: a stale badge is hidden once a press on the row has settled.

Notes, not changed here:

  • S3, rollback compatibility: a node rolled back to a version before this PR ignores chosenByPerson in the session store, so Clark would lose access to chosen folders (it fails closed). Rolling forward again restores them.
  • S4, SSH or port-forward tunnels: the desktop treats a loopback gateway as "the node is on this machine". A loopback tunnel to a remote node would open the local folder dialog and send a local path, which the node then refuses or resolves on its own disk. The press only ever reaches the person-only route, so this is a UX gap, not a trust gap; a comment in api.ts records it.

Verification

At the pushed head:

  • corepack pnpm verify (invariants, typecheck, lint, all vitest suites): passed, 571 test files passed and 1 skipped, 7696 tests passed.
  • corepack pnpm exec playwright test apps/web/e2e/slash-commands.spec.ts: 5 passed. The /develop journey now also checks that focus returns after Escape, and that /develop forget → Forget takes the choice back.
  • New or updated unit tests:
    • apps/runtime/test/widget-dev-sessions.spec.ts:
      • the card on refusal;
      • a person's start marks the folder;
      • Clark resumes the folder and may use a subfolder;
      • the folder survives a restart;
      • a start by Clark never marks;
      • a chosen folder swapped for a junction grants nothing;
      • the card names the resolved folder, and shows the given path;
      • a drive root and the home folder are not marked;
      • forgetting removes the grant, is idempotent, and is refused for mcp, relay and cli-api;
      • the tool's choose and folders actions;
      • a junction swapped in before the press, and a missing path made into a junction later, start a session without a mark;
      • choose refused for every non-person origin, folders still shown;
      • forgetting a folder inside another chosen one reports stillCoveredBy;
      • a chosen folder moved away is listed as not found and can be forgotten;
      • no button for a missing, relative, UNC or device path.
    • apps/runtime/test/open-interfaces.spec.ts: the WebSocket relay refuses the forget route.
    • apps/runtime/test/slash-commands.spec.ts: /develop with and without dev sessions, with an argument, and /develop forget.
    • packages/conversation-client/test/desktop-compact.spec.ts: the folder dialog bridge, which treats the shell's answer as untrusted.
    • packages/conversation-client/test/develop-folder-card.spec.ts: path entry with a reason per case, a record view with no controls, outcome messages, the Forget row with its badge after a press, the not-kept outcome line, and the still-covered forget message.

Not exercised here: the native OS dialog on macOS and Linux. The bridge already existed; this PR only calls it and falls back to typed entry when it fails.

Overlap

This branch is rebased on #609 and #610, which both touch widget-dev-sessions.ts, its spec and docs/open-interfaces*.md. It is also rebased on #530 (/report); SLASH_COMMANDS, BlockActions and the shell messages were merged so both commands are kept. origin/main was merged in afterwards, up to 0677965.

…sessions

/develop (or /develop <folder>, or asking Clark) answers with a host-owned
develop card. Its rows start a session through the person-only
POST /widget-dev/sessions route: the desktop app opens the OS folder
dialog, and a browser, a node on another machine or a dialog that does not
open asks for the path and says why.

A person's start marks the stored session as chosen by the person; Clark
may then develop that folder, and folders inside it, and keeps the mark
when it picks the session up. When Clark asks for any other folder the
tool starts nothing and returns the same card offering the folder.

Fixes #538
…aw, and let them forget it

A chosen folder is compared as stored, so a link swapped in at its path widens nothing. The folder card names and
starts the folder a path resolves to, says when that differs from the path given, and says the choice covers every
folder inside it. A whole drive or the home folder may run a session but is never kept as chosen.

The person takes a choice back with the person-only POST /widget-dev/chosen-folders/forget, the Forget button on the
card /develop forget answers with, or the card Clark shows through develop_widget's new folders action; choose shows
the folder card when asked in words. Focus returns to "Choose folder" when the path field closes, and a row's old
badge gives way to the status of a settled press.
…older itself

A start marks its folder as the person's choice only when the pressed path is the folder as it resolves now, so a
link swapped in after the card was drawn, or made at a path that was missing, starts that session but grants nothing.
The card offers no button for a path that is not found, not absolute, or a network share or device path, and the
press outcome says when a folder was not kept and why.

develop_widget choose runs only in a turn the person sent. Forget answers stillCoveredBy when another folder Clark
may use still holds the forgotten one, and a chosen folder that is not found at its path now is listed as such, so
the person can still forget it.
@mrgoonie

mrgoonie commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

Review attestation: ready to merge at 3d836d794f3adb685ae947587674f08ffa75a771, reviewed by agent:code-reviewer.

A push to this PR makes this attestation stale; the new head needs its own review.

# Conflicts:
#	docs/manifest.json
@mrgoonie

mrgoonie commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

Review attestation: ready to merge at 513192c798c923864af997e26dd2033f33ec66aa, reviewed by agent:code-reviewer.

A push to this PR makes this attestation stale; the new head needs its own review.

# Conflicts:
#	docs/manifest.json
@mrgoonie

mrgoonie commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

Review attestation: ready to merge at cf9463b7e07a47e80d039a88a2f144abb6c47840, reviewed by agent:code-reviewer.

A push to this PR makes this attestation stale; the new head needs its own review.

@mrgoonie
mrgoonie merged commit e68c26d into main Oct 7, 2026
21 of 22 checks passed
@mrgoonie
mrgoonie deleted the feat/538-dev-folder-choice branch October 7, 2026 22:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(widgets): let the person choose a project folder for widget dev sessions

1 participant