feat(app): replace Dashboard with profile dropdown popover #231

Description

@jeonghun-jj-lee

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.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)
  • Server-side widget routes (/amicode/widgets, /amicode/widget-frame, /amicode/widget-code, /amicode/dashboard) remain functional
  • 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; rolerole; orgaffiliation; research_areafocus; 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 keybindmod+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-dependentprofile.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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    hitlNeeds a human decision/review

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
       blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
      }
      } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
      })();
      (function(){
      try {
      var __m = "github.com";
      var __re = new RegExp('^' + "github\\.com" + '
      
      Skip to content

      feat(app): replace Dashboard with profile dropdown popover #231

      Description

      @jeonghun-jj-lee

      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.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)
      • Server-side widget routes (/amicode/widgets, /amicode/widget-frame, /amicode/widget-code, /amicode/dashboard) remain functional
      • 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; rolerole; orgaffiliation; research_areafocus; 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 keybindmod+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-dependentprofile.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.

      Metadata

      Metadata

      Assignees

      No one assigned

        Labels

        hitlNeeds a human decision/review

        Type

        No type

        Projects

        No projects

          Milestone

          No milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
          Skip to content

          feat(app): replace Dashboard with profile dropdown popover #231

          Description

          @jeonghun-jj-lee

          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.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)
          • Server-side widget routes (/amicode/widgets, /amicode/widget-frame, /amicode/widget-code, /amicode/dashboard) remain functional
          • 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; rolerole; orgaffiliation; research_areafocus; 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 keybindmod+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-dependentprofile.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.

          Metadata

          Metadata

          Assignees

          No one assigned

            Labels

            hitlNeeds a human decision/review

            Type

            No type

            Projects

            No projects

              Milestone

              No milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
              Skip to content

              feat(app): replace Dashboard with profile dropdown popover #231

              Description

              @jeonghun-jj-lee

              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.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)
              • Server-side widget routes (/amicode/widgets, /amicode/widget-frame, /amicode/widget-code, /amicode/dashboard) remain functional
              • 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; rolerole; orgaffiliation; research_areafocus; 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 keybindmod+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-dependentprofile.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.

              Metadata

              Metadata

              Assignees

              No one assigned

                Labels

                hitlNeeds a human decision/review

                Type

                No type

                Projects

                No projects

                  Milestone

                  No milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions

                  , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
                  Skip to content

                  feat(app): replace Dashboard with profile dropdown popover #231

                  Description

                  @jeonghun-jj-lee

                  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.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)
                  • Server-side widget routes (/amicode/widgets, /amicode/widget-frame, /amicode/widget-code, /amicode/dashboard) remain functional
                  • 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; rolerole; orgaffiliation; research_areafocus; 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 keybindmod+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-dependentprofile.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.

                  Metadata

                  Metadata

                  Assignees

                  No one assigned

                    Labels

                    hitlNeeds a human decision/review

                    Type

                    No type

                    Projects

                    No projects

                      Milestone

                      No milestone

                      Relationships

                      None yet

                      Development

                      No branches or pull requests

                      Issue actions

                      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                      Skip to content

                      feat(app): replace Dashboard with profile dropdown popover #231

                      Description

                      @jeonghun-jj-lee

                      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.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)
                      • Server-side widget routes (/amicode/widgets, /amicode/widget-frame, /amicode/widget-code, /amicode/dashboard) remain functional
                      • 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; rolerole; orgaffiliation; research_areafocus; 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 keybindmod+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-dependentprofile.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.

                      Metadata

                      Metadata

                      Assignees

                      No one assigned

                        Labels

                        hitlNeeds a human decision/review

                        Type

                        No type

                        Projects

                        No projects

                          Milestone

                          No milestone

                          Relationships

                          None yet

                          Development

                          No branches or pull requests

                          Issue actions

                          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                          Skip to content

                          feat(app): replace Dashboard with profile dropdown popover #231

                          Description

                          @jeonghun-jj-lee

                          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.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)
                          • Server-side widget routes (/amicode/widgets, /amicode/widget-frame, /amicode/widget-code, /amicode/dashboard) remain functional
                          • 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; rolerole; orgaffiliation; research_areafocus; 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 keybindmod+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-dependentprofile.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.

                          Metadata

                          Metadata

                          Assignees

                          No one assigned

                            Labels

                            hitlNeeds a human decision/review

                            Type

                            No type

                            Projects

                            No projects

                              Milestone

                              No milestone

                              Relationships

                              None yet

                              Development

                              No branches or pull requests

                              Issue actions

                              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
                              Skip to content

                              feat(app): replace Dashboard with profile dropdown popover #231

                              Description

                              @jeonghun-jj-lee

                              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.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)
                              • Server-side widget routes (/amicode/widgets, /amicode/widget-frame, /amicode/widget-code, /amicode/dashboard) remain functional
                              • 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; rolerole; orgaffiliation; research_areafocus; 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 keybindmod+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-dependentprofile.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.

                              Metadata

                              Metadata

                              Assignees

                              No one assigned

                                Labels

                                hitlNeeds a human decision/review

                                Type

                                No type

                                Projects

                                No projects

                                  Milestone

                                  No milestone

                                  Relationships

                                  None yet

                                  Development

                                  No branches or pull requests

                                  Issue actions