brainHero.test.tsxfeat(agents): AOP-name picker on the Brain hero, defaulting to Deals Adds a second picker beside the administered-user bar (AdministeredUserBar) on the Brain hero, matching the Settings/Playbooks pattern: pick a conversation type by name, and the Agents grid narrows to just the agents filed under it. Applies whether viewing your own agents or, combined with the admin picker, a teammate's -- BrainHeroBelowChat reads useTargetUserId() and hands it into the AOP query the same way brain/playbooks/page.tsx does, never through useAOPs() (that hook writes into the shared Zustand AOP store every CRM surface reads, so scoping IT to a teammate would swap conversation types app-wide for the admin, not just narrow this filter -- its own doc comment says so). No backend change: AgentSummary carries no aopId (the merge across AOPs deliberately discards which one a merged agent was resolved through), but its documentPath does encode where the document is actually filed -- user/playbooks/{aopId}/subagents/... or organisation/playbooks/{orgAopId}/ subagents/.... agentAopIdFromDocumentPath (agent-paths.ts, mirroring the server's userSubagentPath/orgSubagentPath convention) parses it back out. The filter matches against both a user AOP's own id and its orgAopId, since an org-scoped agent's document lives under the org id, not the user one. Defaults to Deals once the AOP list loads, not "every agent" -- a seeded account's classification buckets and background agents otherwise bury the one AOP everyone actually works in. A ref, not a null-check, gates this: the filter is also null right after a user explicitly picks "All conversation types", and re-defaulting there would fight that choice. Resets (both the selection and whether the default has fired) whenever the administered person changes, since a conversation type only makes sense for the person it belongs to. The "New agent" card and the true-empty-state (as opposed to filter-matched-zero) messaging are withheld while a filter or a teammate is selected, for the same reason `duplicate` stays self-only in the org-admin PR this builds on: a create landing wherever firstAopIdForUser picks, not necessarily the filtered AOP, would be a create button that lies about where its result appears. Co-Authored-By: Claude Sonnet 5 <<email>> Claude-Session: https://claude.ai/code/session_01SfYKPYHXdMgrPLuKhXvWedSep 11, 2026, 10:43 AMplaybookAopSection.test.tsxfix(lint): clear the four oxlint findings this branch was carrying CI lints with oxlint under --deny-warnings, so a warning fails the branch the same as an error. Two dead imports, a `filter(Boolean).at(-1)` that is `findLast(Boolean)`, and an `?? {}` fallback inside a spread that never had anything to fall back from. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Sep 1, 2026, 12:44 AMplaybookFileTree.test.tsxfeat(brain): the Brain lands like the agent home, and its playbooks stop drawing their own files `/brain` was a two-column page: a bespoke home in the wide column, chat pinned to the side, and one colour-washed card per conversation type drawing its files with a private tree. Three things came out of that. The Brain looked like a different product from `/home`. The same document looked like a different object here than in the file explorer — a fourth row renderer with its own indent step, icons and type scale. And the card made the COLOUR the object, when the colour is a two-pixel label on it. `/brain` is now the same machinery as `/home`: chat-dominant over the sunlit backdrop, a greeting above the composer, its own context below it, a rail beside it. Everything that dresses a hero — the backdrop, the translucent left rail, the transparent chat column, the suppressed suggestions strip — already read one boolean, so generalising `selectHomeHero` into `selectHeroSurface` ('home' | 'brain' | null) was the whole of it; only the chat's three content slots branch. Below the composer: two destinations (Playbook, Knowledge base) and every agent rendered in place, because a card whose only job is to reveal a grid charges a click for nothing. The rail holds recent files rather than widgets — the question this screen answers is what Cedar has been reading and writing, and a pipeline metric is not that. navigation.section — `page` is derived from the FIRST path segment, so `/brain` and `/brain/knowledge` are the same page while wanting opposite arrangements. The second segment is now mirrored into the store, and `computeArrangement` reads it: the Brain ROOT is the hero, anything under it is a screen. Deliberately not folded into `setNavigationPage`, which clears the open artifact on every change — moving between two sections of one page must not. The file surface moved to `/brain/knowledge`, so `/brain?documentId=` had to move with it. Every in-app call site goes through `brainDocumentPath` now; `BrainLegacyAddressRedirect` forwards the links already in the wild, and is mounted at the ROOT layout because a chat-dominant route renders no page — a redirect inside `/brain` would never run. FileBrowser left modules/agents. It was the agent Output tab; the playbooks wanted the same thing, and a component two surfaces share does not live in one of them. Its scope is now id-free (user / org / conversation, resolved inside), which fixed a live bug: the old mapping fell through `org` to `user`, so an org-namespaced agent's Output tab was rooted in a workspace holding none of its files. HEAD needed this anyway — the rename was swept into 00b5021ba without the import updates, leaving FileBrowser at the new path and two files importing the old. searchForLink now selects `documents.emoji`. Every row has one, and without it the recents rail drew a generic page glyph for a document the file tree drew 🔥 for. A playbook's PLAYBOOK.md is pinned as the first row and opens the COMPOSITE editor; neither the User nor the Org root lists its own copy. The two halves are one document with one editor, and listing them invites editing a half — the old tree hid the file entirely, which hid the most important document in the folder. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Sep 1, 2026, 12:21 AM