You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Problem — The Home/Dashboard page occupies the primary titlebar button and the entire landing view but delivers low value for its weight. The widget grid, chrome strip controls (defaults capsule, sessions flyout, connections popover, settings), and legacy card layout are going away. The one piece users want quick access to — their identity — has no lightweight surface outside the full-screen dashboard.
Approach — Kill the entire Home page view and replace the titlebar button with a profile popover: a compact, inline-editable identity card (avatar, name, role, affiliation, research area, bio, outbound links) implemented as a Kobalte Popover anchored to the titlebar. Server-side widget infrastructure stays intact for future side-panel work. App launch goes straight to a blank chat. Onboarding data flows into profile.json so the dropdown is populated immediately after onboarding completes.
Scope — in: remove Home page + chrome strip + widget UI components + mod+b keybind; add profile button + popover with inline editing + avatar upload + link pills; add role/github/custom_link/description fields to profile endpoint; bridge onboarding events into profile.json; change app launch to blank chat. out: widget side-panel (future), chrome strip relocation (accessible via command palette/status bar), stats/remembers/platform pills.
Assumptions — The server-side widget routes remain useful for the future side-panel and for the amicode_author_widget tool's preview (pinning becomes a no-op until the panel ships). Settings and connections are accessible elsewhere (command palette, status bar) and don't need immediate relocation. The onboarding events stream (events.jsonl) is the source of truth for collected identity data; profile.json is the serving layer the UI reads from.
Acceptance Criteria
Dashboard removal
The Home page view no longer renders; no route, no toggle state, no mod+b keybind
App launches to a blank chat tab (no landing page, no redirect to Home)
Deleted UI files: widget-grid.tsx, widget-frame.tsx, widget-bridge.ts, widget-tokens.ts, widget-allowlist.ts, home-cards.tsx; Home page exports from home.tsx; Defaults capsule and Sessions flyout components
Profile popover
A person-icon button appears in the titlebar at the former Home button position (left of tab strip)
Clicking the button opens a Kobalte Popover (bottom-start, gutter 8px) showing the profile identity card
The popover participates in chrome-dropdown coordination (auto-closes other flyouts)
The card displays: avatar tile (rounded square, 48px), name, role, affiliation, research area, and short bio
Avatar fallback: accent-yellow rounded square with bold initials (first + last) when no photo; person silhouette when no name
Avatar upload: clicking the tile opens a local file picker; the uploaded image is stored under ~/.amico/ and served back
Three link pills at the bottom: Google Scholar, GitHub, Custom — each with a recognizable icon
Filled pills open the URL externally; empty pills render faint/disabled
Custom link pill shows user-defined label as tooltip on hover
Pencil button (top-right) toggles edit mode: all fields become inputs, link pills show URL inputs, avatar shows upload overlay
Affiliation editing retains the institution autocomplete with logo resolution
Edits save via POST /amicode/profile on confirm; Escape cancels
Empty state (no profile): placeholder avatar + placeholder text in all fields, card opens in edit mode with name auto-focused
Profile endpoint changes
GET /amicode/profile response includes new fields: role, description, github, custom_link: {url, label}
POST /amicode/profile accepts new fields: role, description, github, custom_link_url, custom_link_label
New endpoint POST /amicode/profile-avatar accepts a file upload, stores it, returns the serving path
Onboarding → profile.json bridge
ENTITY_FIELDS.profile in onboarding.ts expanded to include description, research_area, experiment_kind (currently silently dropped by sanitizePayload)
When onboarding_completed fires, the collected profile fields are written to ~/.amico/profile.json so the dropdown is pre-populated without waiting for the distiller
Field mapping: onboarding name → profile name; role → role; org → affiliation; research_area → focus; description (handoff) → description
The bridge is additive (merges into existing profile.json, never clobbers fields already set by the inline editor)
goals, intent, and experiment_kind are recorded in events.jsonl (for the distiller/vault) but do NOT flow to profile.json (they have no dropdown field)
Testing Decisions
Extend existing profile endpoint tests (if any) to cover new fields (role, description, github, custom_link) and the avatar upload endpoint. Add a test that verifies the onboarding-completion bridge writes the expected fields to profile.json. UI components are manually tested via the running app — no new E2E test infrastructure required for this change.
Key Decisions
Widget server infra kept — the registry, manifest parser, runtime, frame HTML, and dashboard state manager all remain. Only the client-side rendering layer (grid, frame component, bridge, tokens, allowlist) is deleted. This makes the future side-panel a UI-only addition.
Kobalte Popover over manual positioning — cleaner API, auto-positioning, accessibility built-in. Integrates with chrome-dropdown via announceChromeDropdown("profile").
Avatar stored locally — ~/.amico/profile-avatar.<ext>, not a URL. Served by the existing static file serving or a dedicated route. No external image hosting dependency.
Three fixed link slots — Scholar, GitHub, Custom. Not a dynamic list. The custom slot has a URL + label (tooltip). This keeps the UI predictable and avoids unbounded link management.
No keybind — mod+b is removed entirely, not reassigned. The profile dropdown is button-only.
Initials fallback on accent yellow — brand-colored tile with dark initials. Falls further back to person silhouette icon when no name is available at all.
Onboarding bridge is eager, not distiller-dependent — profile.json is written synchronously on onboarding_completed so the dropdown works immediately. The distiller still materializes the richer vault PROFILE.md in the background (for Amico context), but the UI-facing profile.json doesn't wait for it.
Bridge is additive merge — if the user already edited their profile via the dropdown (e.g. set affiliation before onboarding completed), those values are preserved. Onboarding values only fill in empty fields.
Constraints & Invariants
The profile popover must not break when the profile endpoint is unreachable (show "Profile unavailable" fallback text)
The amicode_author_widget tool must continue to work (preview in chat renders; pin-to-dashboard is a no-op until side panel ships)
No new npm dependencies beyond what Kobalte Popover already provides
Institution autocomplete and logo resolution reuse the existing implementation from the About You card
sanitizePayload must never store credential-looking values (the existing SECRET_RE guard applies to all new fields)
The onboarding bridge must not regress the distiller path — events.jsonl remains the source of truth for the vault materialization
Prior Art
AboutYouCard in @opencode-ai/ui/amicode/home-cards.tsx — the inline editing UX, institution autocomplete, logo resolution, clipboard bridge
GlobalConnectionsPopover in app/src/components/status-popover.tsx — Kobalte Popover pattern with chrome-dropdown coordination
chrome-dropdown.ts in app/src/utils/ — the one-at-a-time flyout coordination signal
profile.ts in opencode/src/server/amicode/ (and its twin in amicode/packages/extension/src/amicode_service/profile.ts) — the profile data source and save endpoint
onboarding.ts in amicode/packages/extension/opencode-plugin/ — the events stream, ENTITY_FIELDS, sanitizePayload, and triggerOnboardingDistill
Source
Design brainstormed in session 2026-08-22.
Notes
The widget grid may return as a side-panel feature — the server infra is preserved specifically for that path
Chrome strip controls (defaults capsule, sessions flyout, connections popover, settings) are removed from the UI but remain accessible via command palette and status bar; relocation to other surfaces is a separate future decision
The profile endpoint exists in TWO codebases: harmoniqs/opencode (the fork, serves the web app) and harmoniqs/amicode (the extension, serves the VS Code webview). The onboarding.ts bridge lives in the amicode extension only. Implementation must update both profile services to accept the new fields, and add the bridge in the amicode extension's onboarding_completed handler.
Important
Problem — The Home/Dashboard page occupies the primary titlebar button and the entire landing view but delivers low value for its weight. The widget grid, chrome strip controls (defaults capsule, sessions flyout, connections popover, settings), and legacy card layout are going away. The one piece users want quick access to — their identity — has no lightweight surface outside the full-screen dashboard.
Approach — Kill the entire Home page view and replace the titlebar button with a profile popover: a compact, inline-editable identity card (avatar, name, role, affiliation, research area, bio, outbound links) implemented as a Kobalte Popover anchored to the titlebar. Server-side widget infrastructure stays intact for future side-panel work. App launch goes straight to a blank chat. Onboarding data flows into
profile.jsonso the dropdown is populated immediately after onboarding completes.Scope — in: remove Home page + chrome strip + widget UI components +
mod+bkeybind; add profile button + popover with inline editing + avatar upload + link pills; addrole/github/custom_link/descriptionfields to profile endpoint; bridge onboarding events intoprofile.json; change app launch to blank chat. out: widget side-panel (future), chrome strip relocation (accessible via command palette/status bar), stats/remembers/platform pills.Assumptions — The server-side widget routes remain useful for the future side-panel and for the
amicode_author_widgettool's preview (pinning becomes a no-op until the panel ships). Settings and connections are accessible elsewhere (command palette, status bar) and don't need immediate relocation. The onboarding events stream (events.jsonl) is the source of truth for collected identity data;profile.jsonis the serving layer the UI reads from.Acceptance Criteria
Dashboard removal
mod+bkeybind/amicode/widgets,/amicode/widget-frame,/amicode/widget-code,/amicode/dashboard) remain functionalwidget-grid.tsx,widget-frame.tsx,widget-bridge.ts,widget-tokens.ts,widget-allowlist.ts,home-cards.tsx; Home page exports fromhome.tsx; Defaults capsule and Sessions flyout componentsProfile popover
~/.amico/and served backPOST /amicode/profileon confirm; Escape cancelsProfile endpoint changes
GET /amicode/profileresponse includes new fields:role,description,github,custom_link: {url, label}POST /amicode/profileaccepts new fields:role,description,github,custom_link_url,custom_link_labelPOST /amicode/profile-avataraccepts a file upload, stores it, returns the serving pathOnboarding → profile.json bridge
ENTITY_FIELDS.profileinonboarding.tsexpanded to includedescription,research_area,experiment_kind(currently silently dropped bysanitizePayload)onboarding_completedfires, the collected profile fields are written to~/.amico/profile.jsonso the dropdown is pre-populated without waiting for the distillername→ profilename;role→role;org→affiliation;research_area→focus;description(handoff) →descriptionprofile.json, never clobbers fields already set by the inline editor)goals,intent, andexperiment_kindare recorded in events.jsonl (for the distiller/vault) but do NOT flow toprofile.json(they have no dropdown field)Testing Decisions
Extend existing profile endpoint tests (if any) to cover new fields (
role,description,github,custom_link) and the avatar upload endpoint. Add a test that verifies the onboarding-completion bridge writes the expected fields toprofile.json. UI components are manually tested via the running app — no new E2E test infrastructure required for this change.Key Decisions
announceChromeDropdown("profile").~/.amico/profile-avatar.<ext>, not a URL. Served by the existing static file serving or a dedicated route. No external image hosting dependency.mod+bis removed entirely, not reassigned. The profile dropdown is button-only.profile.jsonis written synchronously ononboarding_completedso the dropdown works immediately. The distiller still materializes the richer vaultPROFILE.mdin the background (for Amico context), but the UI-facingprofile.jsondoesn't wait for it.Constraints & Invariants
amicode_author_widgettool must continue to work (preview in chat renders; pin-to-dashboard is a no-op until side panel ships)sanitizePayloadmust never store credential-looking values (the existingSECRET_REguard applies to all new fields)events.jsonlremains the source of truth for the vault materializationPrior Art
AboutYouCardin@opencode-ai/ui/amicode/home-cards.tsx— the inline editing UX, institution autocomplete, logo resolution, clipboard bridgeGlobalConnectionsPopoverinapp/src/components/status-popover.tsx— Kobalte Popover pattern with chrome-dropdown coordinationchrome-dropdown.tsinapp/src/utils/— the one-at-a-time flyout coordination signalprofile.tsinopencode/src/server/amicode/(and its twin inamicode/packages/extension/src/amicode_service/profile.ts) — the profile data source and save endpointonboarding.tsinamicode/packages/extension/opencode-plugin/— the events stream,ENTITY_FIELDS,sanitizePayload, andtriggerOnboardingDistillSource
Design brainstormed in session 2026-08-22.
Notes
harmoniqs/opencode(the fork, serves the web app) andharmoniqs/amicode(the extension, serves the VS Code webview). Theonboarding.tsbridge lives in the amicode extension only. Implementation must update both profile services to accept the new fields, and add the bridge in the amicode extension'sonboarding_completedhandler.