From 1455976689e6975d66914e350dc2c1d32070ade7 Mon Sep 17 00:00:00 2001 From: Aaron Iker Date: Thu, 8 Jan 2026 03:03:26 +0100 Subject: [PATCH 1/9] fix: avatar not loaded fallback --- .../src/components/dialog-edit-project.tsx | 32 +++++----- packages/app/src/components/project-icon.tsx | 58 +++++++++++++++++++ packages/ui/src/components/avatar.css | 2 +- 3 files changed, 73 insertions(+), 19 deletions(-) create mode 100644 packages/app/src/components/project-icon.tsx diff --git a/packages/app/src/components/dialog-edit-project.tsx b/packages/app/src/components/dialog-edit-project.tsx index 27ce3903cd..8d389cef7c 100644 --- a/packages/app/src/components/dialog-edit-project.tsx +++ b/packages/app/src/components/dialog-edit-project.tsx @@ -3,11 +3,12 @@ import { useDialog } from "@opencode-ai/ui/context/dialog" import { Dialog } from "@opencode-ai/ui/dialog" import { TextField } from "@opencode-ai/ui/text-field" import { Icon } from "@opencode-ai/ui/icon" +import { Avatar } from "@opencode-ai/ui/avatar" import { createMemo, createSignal, For, Show } from "solid-js" import { createStore } from "solid-js/store" import { useGlobalSDK } from "@/context/global-sdk" import { type LocalProject, getAvatarColors } from "@/context/layout" -import { Avatar } from "@opencode-ai/ui/avatar" +import { ProjectIcon, isValidImageFile } from "@/components/project-icon" const AVATAR_COLOR_KEYS = ["pink", "mint", "orange", "purple", "cyan", "lime"] as const @@ -33,9 +34,11 @@ export function DialogEditProject(props: { project: LocalProject }) { const [dragOver, setDragOver] = createSignal(false) function handleFileSelect(file: File) { - if (!file.type.startsWith("image/")) return + if (!isValidImageFile(file)) return const reader = new FileReader() - reader.onload = (e) => setStore("iconUrl", e.target?.result as string) + reader.onload = (e) => { + setStore("iconUrl", e.target?.result as string) + } reader.readAsDataURL(file) } @@ -98,7 +101,7 @@ export function DialogEditProject(props: { project: LocalProject }) {
document.getElementById("icon-upload")?.click()} > - - -
- } - > - Project icon - +
+ + {/* Portal mount target - content rendered here from DirectoryLayout */} +
+
+ ) +} + +// Re-export for use in DirectoryLayout +export { ToolbarSession } from "./session" diff --git a/packages/app/src/components/session/session-header.tsx b/packages/app/src/components/toolbar/session.tsx similarity index 58% rename from packages/app/src/components/session/session-header.tsx rename to packages/app/src/components/toolbar/session.tsx index 4958ad2c35..ab3c52ca4c 100644 --- a/packages/app/src/components/session/session-header.tsx +++ b/packages/app/src/components/toolbar/session.tsx @@ -1,4 +1,4 @@ -import { createMemo, createResource, Show } from "solid-js" +import { ComponentProps, createMemo, createResource, Show, Component } from "solid-js" import { A, useNavigate, useParams } from "@solidjs/router" import { useLayout } from "@/context/layout" import { useCommand } from "@/context/command" @@ -22,7 +22,7 @@ import { SessionMcpIndicator } from "@/components/session-mcp-indicator" import type { Session } from "@opencode-ai/sdk/v2/client" import { same } from "@/utils/same" -export function SessionHeader() { +export const ToolbarSession: Component> = ({ class: className, ...props }) => { const globalSDK = useGlobalSDK() const layout = useLayout() const params = useParams() @@ -56,7 +56,7 @@ export function SessionHeader() { } return ( -
+
-
-
+
+
+ + x.title} + value={(x) => x.id} + onSelect={(session) => { + // Only navigate if selecting a different session than current parent + const currentParent = parentSession() + if (session && currentParent && session.id !== currentParent.id) { + navigateToSession(session) + } + }} + class="text-14-regular text-text-base max-w-[calc(100vw-180px)] md:max-w-md" + variant="ghost" + /> +
/
+
+ + + +
+
+ +
+ + + +
+
+
+ ) +} diff --git a/packages/app/src/components/toolbar/index.tsx b/packages/app/src/components/toolbar/index.tsx index dd7b649452..b16e8e001b 100644 --- a/packages/app/src/components/toolbar/index.tsx +++ b/packages/app/src/components/toolbar/index.tsx @@ -8,7 +8,6 @@ import { usePlatform } from "@/context/platform" const IS_MAC = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform) -// ID for the portal mount target export const TOOLBAR_PORTAL_ID = "toolbar-content-portal" export const Toolbar: Component> = ({ class: className, ...props }) => { @@ -21,7 +20,7 @@ export const Toolbar: Component> = ({ class: className, .. classList={{ "pl-[80px]": IS_MAC && platform.platform !== "web", "pl-2": !IS_MAC || platform.platform === "web", - "py-2 mx-px bg-background-base border-b border-border-weak-base flex items-center justify-between w-full border-box relative": true, + "py-2 min-h-[41px] mx-px bg-background-base border-b border-border-weak-base flex items-center justify-between w-full border-box relative": true, ...(className ? { [className]: true } : {}), }} data-tauri-drag-region @@ -59,21 +58,9 @@ export const Toolbar: Component> = ({ class: className, .. - -
) } -// Re-export for use in DirectoryLayout export { ToolbarSession } from "./session" diff --git a/packages/app/src/components/toolbar/session.tsx b/packages/app/src/components/toolbar/session.tsx index 6134552fb3..442e78679a 100644 --- a/packages/app/src/components/toolbar/session.tsx +++ b/packages/app/src/components/toolbar/session.tsx @@ -1,256 +1,147 @@ -import { ComponentProps, createMemo, createResource, Show, Component } from "solid-js" -import { A, useNavigate, useParams } from "@solidjs/router" +import { createMemo, createResource, Show, Component, type ComponentProps } from "solid-js" +import { useParams } from "@solidjs/router" import { useLayout } from "@/context/layout" import { useCommand } from "@/context/command" import { useServer } from "@/context/server" import { useDialog } from "@opencode-ai/ui/context/dialog" import { useSync } from "@/context/sync" import { useGlobalSDK } from "@/context/global-sdk" -import { getFilename } from "@opencode-ai/util/path" -import { base64Decode, base64Encode } from "@opencode-ai/util/encode" +import { base64Decode } from "@opencode-ai/util/encode" import { iife } from "@opencode-ai/util/iife" import { Icon } from "@opencode-ai/ui/icon" import { IconButton } from "@opencode-ai/ui/icon-button" import { Button } from "@opencode-ai/ui/button" import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip" -import { Select } from "@opencode-ai/ui/select" import { Popover } from "@opencode-ai/ui/popover" import { TextField } from "@opencode-ai/ui/text-field" import { DialogSelectServer } from "@/components/dialog-select-server" import { SessionLspIndicator } from "@/components/session-lsp-indicator" import { SessionMcpIndicator } from "@/components/session-mcp-indicator" -import type { Session } from "@opencode-ai/sdk/v2/client" -import { same } from "@/utils/same" -export const ToolbarSession: Component> = ({ class: className, ...props }) => { +export const ToolbarSession: Component> = ({ class: className, ...props }) => { const globalSDK = useGlobalSDK() const layout = useLayout() const params = useParams() - const navigate = useNavigate() const command = useCommand() const server = useServer() const dialog = useDialog() const sync = useSync() const projectDirectory = createMemo(() => base64Decode(params.dir ?? "")) - - const sessions = createMemo(() => (sync.data.session ?? []).filter((s) => !s.parentID)) const currentSession = createMemo(() => sync.data.session.find((s) => s.id === params.id)) - const parentSession = createMemo(() => { - const current = currentSession() - if (!current?.parentID) return undefined - return sync.data.session.find((s) => s.id === current.parentID) - }) const shareEnabled = createMemo(() => sync.data.config.share !== "disabled") - const worktrees = createMemo(() => layout.projects.list().map((p) => p.worktree), [], { equals: same }) - - function navigateToProject(directory: string) { - navigate(`/${base64Encode(directory)}`) - } - - function navigateToSession(session: Session | undefined) { - if (!session) return - // Only navigate if we're actually changing to a different session - if (session.id === params.id) return - navigate(`/${params.dir}/session/${session.id}`) - } return ( -
-
-
-
- - - x.title} - value={(x) => x.id} - onSelect={(session) => { - // Only navigate if selecting a different session than current parent - const currentParent = parentSession() - if (session && currentParent && session.id !== currentParent.id) { - navigateToSession(session) - } - }} - class="text-14-regular text-text-base max-w-[calc(100vw-180px)] md:max-w-md" - variant="ghost" - /> -
/
-
- - - -
-
- -
- - - -
-
- -
- -
- - - + /> + + {server.name} + + + +
+
+ -
- - - - - } - > - {iife(() => { - const [url] = createResource( - () => currentSession(), - async (session) => { - if (!session) return - let shareURL = session.share?.url - if (!shareURL) { - shareURL = await globalSDK.client.session - .share({ sessionID: session.id, directory: projectDirectory() }) - .then((r) => r.data?.share?.url) - .catch((e) => { - console.error("Failed to share session", e) - return undefined - }) - } - return shareURL - }, - { initialValue: "" }, - ) - return ( - - {(shareUrl) => } - - ) - })} - +
- + + + + + } + > + {iife(() => { + const [url] = createResource( + () => currentSession(), + async (session) => { + if (!session) return + let shareURL = session.share?.url + if (!shareURL) { + shareURL = await globalSDK.client.session + .share({ sessionID: session.id, directory: projectDirectory() }) + .then((r) => r.data?.share?.url) + .catch((e) => { + console.error("Failed to share session", e) + return undefined + }) + } + return shareURL + }, + { initialValue: "" }, + ) + return ( + + {(shareUrl) => } + + ) + })} + + + ) } diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index f2e3953a4f..c2f76d2de7 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -40,7 +40,14 @@ import { extractPromptFromParts } from "@/utils/prompt" import { ConstrainDragYAxis, getDraggableId } from "@/utils/solid-dnd" import { usePermission } from "@/context/permission" import { showToast } from "@opencode-ai/ui/toast" -import { SessionContextTab, SortableTab, FileVisual, SortableTerminalTab, NewSessionView } from "@/components/session" +import { + SessionHeader, + SessionContextTab, + SortableTab, + FileVisual, + SortableTerminalTab, + NewSessionView, +} from "@/components/session" import { usePlatform } from "@/context/platform" import { same } from "@/utils/same" @@ -769,6 +776,7 @@ export default function Page() { return (
+
{/* Mobile tab bar - only shown on mobile when there are diffs */} 0}>