T-RR9V-tabs-carry-entity-icons-and-views
State: planning/proposed · Impact: medium · Complexity: medium
Every document tab draws the same i-lucide-file-text glyph and every activation lands on the
shell’s raw-text stage, so an Agent Manager note opened as a tab would look like a text file and
read like one. A tab needs to carry an opaque icon and a view key the host resolves, which is what
lets Agent Manager open notes as tabs at all. That is the prerequisite for the tree’s edited
indicator, for the preview and Related panes following the active document, and for the split
stage.
| Location | Role today |
|---|---|
packages/ts/app-frame/app/composables/useDocumentTabs.ts#DocumentTab | {key, scopeItem, path, label} and nothing more; key is scopeItem plus path |
packages/ts/app-frame/app/composables/useDocumentTabs.ts#openDocument | Takes {scopeItem, path, label?}, pushes the tab, activates it and publishes the selection |
packages/ts/app-frame/app/components/ShellDocumentTabs.vue | Draws a fixed i-lucide-file-text glyph on every tab; the dot’s hue comes from the scope roster through scopeItemHueStyle |
packages/ts/app-frame/app/composables/useCapabilities.ts#CapabilitySlot | An open string. The frame ships renderers for panel and stage-card, and names lens as a host-defined slot with no renderer |
solutions/determined/packages/shell/app/components/ShellVaultBrowsePane.vue#activate | The only openDocument caller: routes into the notes experience when composed, else opens a tab and pushes /apps/browse |
solutions/determined/packages/shell/app/pages/apps/browse.vue | The fallback stage: reads the active document with polishScanReadFileContent and prints it into a <pre> |
solutions/determined/experiences/agent-pants/app/components/ApComponentTree.vue#activate | Navigates to the note or plugin route and calls selectScopeItem; opens no tab |
solutions/determined/experiences/agent-pants/app/components/ApComponentTree.vue#KIND_ICON | Per-kind glyphs: store, package, file-text, terminal, bot |
solutions/determined/docs/planning/desktop-framework/multi-vault-ux/IMPLEMENTATION.md | Records rule 2b.8 — the frame carries no domain vocabulary, so subjects are scopeItem and slot is an open string |
packages/ts/app-frame/tests/useDocumentTabs.test.ts | Covers open, re-open, activate, close and prune over the four-field tab |
Proposed
Section titled “Proposed”DocumentTab carries optional icon, hue and view — three opaque strings the frame stores,
passes through and never interprets, which is rule 2b.8 held exactly as it stands.
ShellDocumentTabs.vue draws tab.icon when a tab names one and keeps i-lucide-file-text as the
fallback.
A host resolves view through the capability registry at slot: 'lens': the registration whose
id equals the tab’s view supplies the component the stage mounts. browse.vue mounts the
resolved lens and keeps its <pre> reader for a tab that names no view. Agent Manager registers
one lens and opens its notes through openDocument, passing KIND_ICON[kind] and its view key, so
a skill tab carries the skill glyph and activating it renders the note editor rather than raw text.
ShellVaultBrowsePane.vue passes a file-type icon for the tabs it opens.
Approach
Section titled “Approach”useDocumentTabs.ts: addicon?,hue?andview?toDocumentTabandOpenDocumentInputand carry them throughopenDocument.keystaysscopeItempluspath, so re-opening a document that is already open must update the stored fields rather than mint a second tab — settle that in this step and test it.ShellDocumentTabs.vue: rendertab.iconwithi-lucide-file-textas the fallback, and lettab.huewin over the scope roster’s hue where a tab names one.- Name the slot: export a
LENS_CAPABILITY_SLOTconstant besideDEFAULT_CAPABILITY_SLOTandSTAGE_CARD_CAPABILITY_SLOT, and add aresolveLens(view)query returning the registered capability whoseidmatches. Open decision in this step: whether a lens resolves byidalone or also has to match the subject. Match onidalone — a tab already names one document. browse.vue: mount the resolved lens for the active tab, keeping the<pre>path for a tab with no view.- Agent Manager: register the lens in
app/plugins/register.ts, and open notes fromApComponentTree.vue#activatethroughopenDocumentwith the kind icon and the view key. The separateselectScopeItemcall goes:openDocumentalready publishes the selection. - Tests: extend
packages/ts/app-frame/tests/useDocumentTabs.test.tsfor the three fields and the re-open case, and cover lens resolution in the frame’s ownuseCapabilities.test.ts.
| Area | Note |
|---|---|
packages/ts/app-frame/app/composables/useDocumentTabs.ts | DocumentTab and OpenDocumentInput gain icon, hue, view; re-open updates them |
packages/ts/app-frame/app/components/ShellDocumentTabs.vue | Draw tab.icon; prefer tab.hue over the roster hue |
packages/ts/app-frame/app/composables/useCapabilities.ts | Add LENS_CAPABILITY_SLOT and resolveLens(view) |
packages/ts/app-frame/tests/useDocumentTabs.test.ts | Cover the new fields and the re-open update |
packages/ts/app-frame/tests/useCapabilities.test.ts | Cover lens resolution by id |
solutions/determined/packages/shell/app/pages/apps/browse.vue | Mount the resolved lens; <pre> stays the no-view fallback |
solutions/determined/packages/shell/app/components/ShellVaultBrowsePane.vue | Pass a file-type icon on openDocument |
solutions/determined/experiences/agent-pants/app/plugins/register.ts | Register the note lens |
solutions/determined/experiences/agent-pants/app/components/ApComponentTree.vue | activate opens a document tab carrying KIND_ICON[kind] and the view key |
solutions/determined/experiences/agent-pants/app/components/ApComponentTree.test.ts | Assert the tab is opened with the kind icon |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1: Opening a skill, a command, an agent and a plugin from the Agent Manager tree produces
four tabs whose glyphs are the four
KIND_ICONentries for those kinds. - AC-2: Activating an Agent Manager tab renders the note editor, not the
<pre>reader. - AC-3: A tab opened by
ShellVaultBrowsePane.vuewith noviewstill renders in the<pre>reader, unchanged. - AC-4:
openDocumentcalled twice for one{scopeItem, path}with different icons leaves one tab carrying the second icon — asserted inuseDocumentTabs.test.ts. - AC-5:
useDocumentTabs.tsandShellDocumentTabs.vuename no vault, note or kind: the three new fields are read and stored as opaque strings, and rule 2b.8 holds. - AC-6:
moon run :check :test --affectedpasses forapp-frame, the determined shell package and the agent-pants experience.
Out of scope
Section titled “Out of scope”- The tree’s open and dirty indicators, which read this model in T-0XMP-tree-typed-folders-filter-add.
- Two documents side by side (T-HU19-split-primary-stage).
- Git state on a tab (T-ZOQT-tree-and-tabs-show-git-state).
- Tab reordering, pinning and overflow.
- Replacing
browse.vue’s<pre>with a real code surface (T-MVW5-code-editor-package).
Dependencies
Section titled “Dependencies”- none
Discovery context
Section titled “Discovery context”The user’s planning request of 2026-09-07 asked for document tabs to carry the icon of what they
hold, and for an experience to own what a tab renders. Member of milestone M-BMFD. Rule 2b.8 —
the frame stores opaque strings and never domain words — is recorded in
solutions/determined/docs/planning/desktop-framework/multi-vault-ux/IMPLEMENTATION.md and holds
here.