a plugin declares its chat directory; email stops being hardcoded here

ChatPanelWrapper had one plugin's screen id compiled into a shared platform
package: `workspace.id === 'email'` → `{ context: 'email' }`. its own comment
said the general fix was a way for a plugin to DECLARE its chat context, and
that a second hardcoded id would be the signal it was needed. that fix exists
(`declared`), the email plugin now passes it, so the branch is gone.

it also could not fire any more, which is the part worth keeping. a plugin's
workspace key is `screens/plugin/<name>`, so email's id became `plugin/email`
the day it was extracted and the test silently stopped matching. a hardcoded id
does not fail loudly when the thing it names moves — it just stops being true,
and nothing said so for eight days.

two things on the backend half:

resolveEmailCwd now installs the email plugin's chat/CLAUDE.md. an email chat
runs in the account's directory, not the plugin's chats directory, so the
`plugin` branch never reaches it — the brief follows the chat rather than the
chat moving to the brief. a second plugin wanting this is the signal that
"which directory does this chat run in" belongs in the manifest.

and it verifies the mailbox exists before joining it. `accountEmail` comes from
the client (the mailbox picker's `?account=`) and this function ends in mkdirSync
— so an unrecognised value did not fail, it CREATED a directory named after
whatever arrived and ran the turn in it. the list's unified view uses the
sentinel `all`, which is the value most likely to show up by accident. the panel
drops it; this is the half that does not depend on the panel remembering to.

docs/claude-md-touchpoints.md updated: the copier now has two callers.

unrelated and NOT from this change: ModelSelector's "an unstarted composer gets
a default" fails on master too — verified by stashing these files and rerunning.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
This commit is contained in:
2026-08-26 18:43:13 +00:00
co-authored by Claude Opus 5
parent d58652bf14
commit be210c3ed4
3 changed files with 77 additions and 51 deletions
+20 -1
View File
@@ -200,9 +200,28 @@ async function resolveEmailCwd(ownerEmail: string, accountEmail?: string): Promi
// Found on disk, never queried. Email is a plugin and the platform may not import its queries; the
// directory layout is already the platform's, so reading it answers the question with no coupling and
// nothing to throw when the plugin is not installed.
const account = accountEmail?.trim() || findEmailAccountName(ownerEmail);
//
// The named mailbox has to EXIST before it is joined. `accountEmail` reaches here from the client — it is
// the mailbox picker's `?account=` — and this function ends in `mkdirSync`, so an unrecognised value does
// not fail, it CREATES a directory named after whatever was sent and runs the turn in it. The list's
// unified view uses the sentinel `all`, which is the value most likely to arrive by accident; the panel
// drops it, and this is the half that does not depend on the panel remembering to.
//
// Falling back to the most recently synced account is the same answer the unified list is mostly showing.
const requested = accountEmail?.trim();
const known = requested && existsSync(join(getEmailAccountsDir(ownerEmail), requested, 'emails.db'));
if (requested && !known) {
console.warn(`[chat] email: no mailbox named ${requested} — using the most recently synced one instead`);
}
const account = (known ? requested : null) ?? findEmailAccountName(ownerEmail);
const dir = account ? join(getEmailAccountsDir(ownerEmail), account) : getEmailAccountsDir(ownerEmail);
mkdirSync(dir, { recursive: true });
// The same brief a plugin's own chats get, written where an email chat actually runs.
//
// `installPluginChatBrief` is not reached by the `plugin` branch below, because an email chat does not
// run in the plugin's chats directory — it runs in the ACCOUNT's, beside `emails.db`, which is the whole
// reason this branch exists. The brief follows the chat rather than the chat moving to the brief.
await installPluginChatBrief('email', dir);
return dir;
}