Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,16 @@ _Avoid_: API key, password
The self-healing SSH local-forward a `client` uses to reach the Canonical Server — one component with two launchers. The extension spawns and supervises it for interactive panels (reconnect with backoff, address candidates probed LAN-before-overlay, health surfaced in the status bar); a headless launcher (`amico fleet tunnel`) serves panel-less consumers such as scheduled jobs. Failures are always visible to its consumer — never an invisible external service.
_Avoid_: port forward (as a concept name), launchd tunnel

### Surfaces

**Home**:
The always-present first tab in the Work Column that renders the user's widget grid — profile cards, run status, problem summaries, and custom agent-authored widgets. The single canonical surface for widgets; replaces the standalone home page. Internally powered by the widget kernel (WidgetGrid, WidgetFrame, the bridge protocol, `/amicode/widgets` + `/amicode/dashboard` endpoints).
_Avoid_: Dashboard (as the surface name), widget panel

**Widget**:
A sandboxed ES-module card rendered in an iframe within Home. Authored by the agent (`amicode_author_widget` tool) or shipped as a builtin. Communicates with the host via the bridge protocol (postMessage). Two size classes: hero (full panel width) and tile (half-width, 2-across). Each has a TOML manifest, a JS module, and optional config fields.
_Avoid_: Card (ambiguous — the UI has many cards), tile (as the concept name — tile is a size class)

### Orthogonal axes

**Domain Pack**:
Expand Down
17 changes: 17 additions & 0 deletions docs/adr/0009-widgets-to-work-column-home-tab.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Widgets move to the Work Column as the "Home" tab; the standalone home page is retired

Status: proposed (2026-08-24)

Glossary update: `CONTEXT.md` (Home, Widget)

The widget grid — profile cards, run status, problem summaries, and custom agent-authored widgets — relocates from the standalone home page into the Work Column as an always-present first tab labeled "Home". The home page route and its dedicated rendering surface are deleted. The widget kernel (WidgetGrid, WidgetFrame, bridge protocol, `/amicode/widgets` + `/amicode/dashboard` endpoints, the `amicode_author_widget` tool, and the TOML manifest system) is reused in full.

**Why:** Two problems converged. (1) The home page was a dead-end surface: navigating to it meant leaving the session, and returning to the session meant leaving your dashboard — researchers never saw their widgets while working. (2) The Work Column is already the unified auxiliary surface (ADR 0006 moved inspectors there for the same reason); widgets are session-contextual status that belongs in the same hierarchy, always a tab-click away during a conversation.

**Conditions of acceptance:** The Home tab renders first in the tab strip, is always present (never closeable), and is the default-active tab when the panel opens. The 2-column widget grid (heroes full-width, tiles 2-across) works at panel widths down to ~300px, collapsing to single-column below that. The add-widget tray renders as a compact name+description list (no live iframe previews). Drag-reorder (Move mode) functions vertically. The "Pin to dashboard" button in the chat preview card is relabeled "Pin to Home". The home page route no longer exists at runtime. All existing widget tests pass unchanged.

**Accepted costs:** The widget grid operates in a narrower viewport than it was designed for — at minimum panel width (~320px), tiles are ~145px wide each, which is tight for content-heavy widgets. Authors of existing widgets may want to test at narrow widths. The add-widget tray loses its live-preview cards (the iframe previews that showed each candidate widget running) in favor of a text list — faster to load and scan, but less visually informative.

**Considered:** (A) New `WidgetColumn` component purpose-built for the panel (rejected: duplicates 500+ lines of tested state management — ordering, drag, visibility, config — that WidgetGrid already handles; more work, more risk, same result); (B) WidgetGrid with a `variant="panel"` prop that branches layout conditionally (rejected: turns a 550-line component into a conditional maze; the actual changes needed are small and subtractive — simplify the tray, add a CSS threshold — not a different mode).

**Flip condition:** If the tab strip becomes too crowded with Home always occupying one slot, revisit whether Home should be a collapsible section within the Review tab rather than a top-level tab. If a future full-width surface is needed (e.g., a true dashboard mode when no session is active), the widget kernel can render there too — the backend doesn't care where the frontend mounts the grid.
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,15 @@ export const createSessionTabs = (input: TabsInput) => {
(input.tabs().active() === SESSION_OPEN_FILE_TAB || input.tabs().all().includes(SESSION_OPEN_FILE_TAB)),
)
const pulseInspectorOpen = createMemo(() => input.tabs().active() === "pulseInspector" || input.tabs().all().includes("pulseInspector"))
const homeOpen = createMemo(() => input.tabs().active() === "home" || input.tabs().all().includes("home"))
const panelTabs = createMemo(
() => {
const seen = new Set<string>()
return input
.tabs()
.all()
.flatMap((tab) => {
if (tab === "context" || tab === "review" || tab === "vault" || tab === SESSION_PREVIEW_TAB || tab === "pulseInspector") return []
if (tab === "context" || tab === "review" || tab === "vault" || tab === "home" || tab === SESSION_PREVIEW_TAB || tab === "pulseInspector") return []
if (tab === SESSION_OPEN_FILE_TAB && !fileBrowser()) return []
const value = input.pathFromTab(tab) ? input.normalizeTab(tab) : tab
if (seen.has(value)) return []
Expand All @@ -68,6 +69,7 @@ export const createSessionTabs = (input: TabsInput) => {
})
const activeTab = createMemo(() => {
const active = input.tabs().active()
if (active === "home") return active
if (active === "context") return active
if (active === "pulseInspector") return active
if (active === SESSION_PREVIEW_TAB && previewOpen()) return active
Expand All @@ -83,7 +85,7 @@ export const createSessionTabs = (input: TabsInput) => {
if (contextOpen()) return "context"
if (pulseInspectorOpen()) return "pulseInspector"
if (review() && hasReview()) return "review"
return "empty"
return "home"
})
const activeFileTab = createMemo(() => {
const active = activeTab()
Expand All @@ -102,6 +104,7 @@ export const createSessionTabs = (input: TabsInput) => {
contextOpen,
previewOpen,
pulseInspectorOpen,
homeOpen,
openFileOpen,
panelTabs,
openedTabs,
Expand Down
Loading
Loading