diff --git a/packages/app/src/app.tsx b/packages/app/src/app.tsx index ee64beef395..383cf7df641 100644 --- a/packages/app/src/app.tsx +++ b/packages/app/src/app.tsx @@ -108,16 +108,14 @@ function DraftRoute() { function ResolvedDraftRoute(props: { draft: DraftTab }) { const global = useGlobal() const conn = createMemo(() => global.servers.list().find((item) => ServerConnection.key(item) === props.draft.server)) - const directory = () => props.draft.directory - const serverKey = () => props.draft.server return ( - - - - - + + + + + @@ -221,7 +219,7 @@ function DesktopCommands() { } type ServerScopedShellProps = ParentProps<{ - directory?: () => string | undefined + directory?: string serverScoped?: JSX.Element }> diff --git a/packages/app/src/components/debug-bar.tsx b/packages/app/src/components/debug-bar.tsx index 9465374cbb0..67cd0da0e92 100644 --- a/packages/app/src/components/debug-bar.tsx +++ b/packages/app/src/components/debug-bar.tsx @@ -1,5 +1,5 @@ import { useIsRouting, useLocation } from "@solidjs/router" -import { batch, createEffect, onCleanup, onMount } from "solid-js" +import { batch, createEffect, onCleanup, onMount, Show } from "solid-js" import { createStore } from "solid-js/store" import { makeEventListener } from "@solid-primitives/event-listener" import { Tooltip } from "@opencode-ai/ui/tooltip" @@ -571,7 +571,7 @@ export function DebugBar(props: { inline?: boolean } = {}) { value={language.t(`debugBar.direction.${language.direction()}`)} onClick={() => language.setDirection(language.direction() === "rtl" ? "ltr" : "rtl")} /> - {platform.setForceFocus && ( + void toggleFocus()} /> - )} + ) diff --git a/packages/app/src/components/dialog-connect-provider.tsx b/packages/app/src/components/dialog-connect-provider.tsx index 8a860ac29d7..eb2c8f5ac87 100644 --- a/packages/app/src/components/dialog-connect-provider.tsx +++ b/packages/app/src/components/dialog-connect-provider.tsx @@ -12,7 +12,7 @@ import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2" import { DialogBody, DialogHeader, DialogTitle, DialogV2 } from "@opencode-ai/ui/v2/dialog-v2" import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2" import { showToast } from "@/utils/toast" -import { type Accessor, type Component, createMemo, createUniqueId, For, Match, onMount, Show, Switch } from "solid-js" +import { type Component, createMemo, createUniqueId, For, Match, onMount, Show, Switch } from "solid-js" import { createStore } from "solid-js/store" import { useParams } from "@solidjs/router" import { ExternalLink } from "@/components/external-link" @@ -41,7 +41,7 @@ export function useProviderConnectController(options: { onBack?: () => void } = } export const DialogConnectProvider: Component<{ - directory?: Accessor + directory?: string controller?: ReturnType }> = (props) => { const fallback = useProviderConnectController() @@ -137,13 +137,9 @@ export const DialogConnectProvider: Component<{ ) } -function ProviderPicker(props: { - directory?: Accessor - onSelect: (provider: string) => void - onPrepare?: () => void -}) { +function ProviderPicker(props: { directory?: string; onSelect: (provider: string) => void; onPrepare?: () => void }) { const settings = useSettings() - const integrations = useIntegrations(() => props.directory?.()) + const integrations = useIntegrations(() => props.directory) if (settings.general.newLayoutDesigns()) return const language = useLanguage() @@ -364,7 +360,7 @@ function ProviderPickerV2(props: { function ProviderConnection(props: { provider: string - directory?: Accessor + directory?: string onBack: () => void setBack: (handler: () => void) => void }) { @@ -374,8 +370,8 @@ function ProviderConnection(props: { const language = useLanguage() const settings = useSettings() const newLayout = settings.general.newLayoutDesigns - const providers = useProviders(() => props.directory?.()) - const directory = () => props.directory?.() ?? decode64(params.dir) + const providers = useProviders(() => props.directory) + const directory = () => props.directory ?? decode64(params.dir) const controller = createProviderConnectionController({ provider: () => props.provider, diff --git a/packages/app/src/components/dialog-manage-models.tsx b/packages/app/src/components/dialog-manage-models.tsx index d6c5e991867..660d5d4f217 100644 --- a/packages/app/src/components/dialog-manage-models.tsx +++ b/packages/app/src/components/dialog-manage-models.tsx @@ -31,7 +31,7 @@ export const DialogManageModels: Component = () => { const directory = () => decode64(local.slug()) const handleConnectProvider = () => { - void dialog.show(() => ) + void dialog.show(() => ) } const providerRank = (id: string) => popularProviders.indexOf(id) const providerList = (providerID: string) => local.model.list().filter((x) => x.provider.id === providerID) @@ -123,7 +123,7 @@ export const DialogManageModelsV2: Component = () => { const directory = () => decode64(local.slug()) const handleConnectProvider = () => { - void dialog.show(() => ) + void dialog.show(() => ) } const providerList = (providerID: string) => local.model.list().filter((x) => x.provider.id === providerID) const providerVisible = (providerID: string) => diff --git a/packages/app/src/components/dialog-release-notes.tsx b/packages/app/src/components/dialog-release-notes.tsx index d0a35b71beb..3827b9e9e7b 100644 --- a/packages/app/src/components/dialog-release-notes.tsx +++ b/packages/app/src/components/dialog-release-notes.tsx @@ -1,4 +1,4 @@ -import { createSignal } from "solid-js" +import { createSignal, Index, Show } from "solid-js" import { Dialog } from "@opencode-ai/ui/dialog" import { Button } from "@opencode-ai/ui/button" import { useDialog } from "@opencode-ai/ui/context/dialog" @@ -83,61 +83,71 @@ export function DialogReleaseNotes(props: { highlights: Highlight[] }) { {/* Bottom section - buttons and indicators (fixed position) */}
- {isLast() ? ( + + {language.t("dialog.releaseNotes.action.next")} + + } + > - ) : ( - - )} +
- {paged() && ( +
- {props.highlights.map((_, i) => ( - - ))} + onClick={() => setIndex(i)} + > +
+ + )} +
- )} +
{/* Right side - Media content (edge to edge) */} - {feature()?.media && ( -
- {feature()!.media!.type === "image" ? ( - {feature()!.media!.alt - ) : ( -
- )} + + {(media) => ( +
+ + } + > + {media().alt + +
+ )} +
) diff --git a/packages/app/src/components/dialog-select-model-unpaid-v2.tsx b/packages/app/src/components/dialog-select-model-unpaid-v2.tsx index c3a2aca0319..e63ee35a800 100644 --- a/packages/app/src/components/dialog-select-model-unpaid-v2.tsx +++ b/packages/app/src/components/dialog-select-model-unpaid-v2.tsx @@ -37,7 +37,7 @@ export const DialogSelectModelUnpaidV2: Component<{ model?: ModelState }> = (pro void import("./dialog-connect-provider").then((x) => { const controller = x.useProviderConnectController() controller.select(provider) - void dialog.show(() => ) + void dialog.show(() => ) }) } diff --git a/packages/app/src/components/dialog-select-model-unpaid.tsx b/packages/app/src/components/dialog-select-model-unpaid.tsx index 4611a36c950..00ca16cb666 100644 --- a/packages/app/src/components/dialog-select-model-unpaid.tsx +++ b/packages/app/src/components/dialog-select-model-unpaid.tsx @@ -26,7 +26,7 @@ export const DialogSelectModelUnpaid: Component<{ model?: ModelState }> = (props void import("./dialog-connect-provider").then((x) => { const controller = x.useProviderConnectController() controller.select(provider) - void dialog.show(() => ) + void dialog.show(() => ) }) } diff --git a/packages/app/src/components/dialog-select-model.tsx b/packages/app/src/components/dialog-select-model.tsx index 9066f72434e..d01ac91aa42 100644 --- a/packages/app/src/components/dialog-select-model.tsx +++ b/packages/app/src/components/dialog-select-model.tsx @@ -148,7 +148,7 @@ export function ModelSelectorPopover(props: { const handleConnectProvider = () => { close("provider") void import("./dialog-connect-provider").then((x) => { - void dialog.show(() => ) + void dialog.show(() => ) }) } const language = useLanguage() @@ -240,7 +240,7 @@ export function ModelSelectorPopoverV2(props: { trigger={props.trigger} models={controller.models} groups={controller.groups} - current={controller.current} + current={controller.current()} select={controller.select} onManage={() => { void import("./dialog-manage-models").then((module) => { @@ -295,7 +295,7 @@ function ModelSelectorPopoverV2View(props: { trigger: ModelSelectorTrigger models: (search: string) => ModelItem[] groups: (models: ModelItem[]) => { category: string; items: ModelItem[] }[] - current: () => string | undefined + current: string | undefined select: (item: ModelItem) => void onManage: () => void onClose: () => void @@ -310,7 +310,7 @@ function ModelSelectorPopoverV2View(props: { const groups = createMemo(() => props.groups(models())) const keys = () => [...models().map(modelKey), manageKey] const initialActive = () => { - const selected = props.current() + const selected = props.current const options = keys() if (selected && options.includes(selected)) return selected return options[0] ?? "" @@ -453,7 +453,7 @@ function ModelSelectorPopoverV2View(props: { {group.items[0].provider.name} - + {(item) => ( { @@ -529,7 +529,7 @@ export const DialogSelectModel: Component<{ provider?: string; model?: ModelStat const provider = () => { void import("./dialog-connect-provider").then((x) => { - void dialog.show(() => ) + void dialog.show(() => ) }) } diff --git a/packages/app/src/components/server/server-row.tsx b/packages/app/src/components/server/server-row.tsx index 8060b0a7087..0c98bc9c18c 100644 --- a/packages/app/src/components/server/server-row.tsx +++ b/packages/app/src/components/server/server-row.tsx @@ -96,13 +96,16 @@ export function ServerRow(props: ServerRowProps) { {(conn) => (
- {conn().http.username ? ( + {language.t("server.row.noUsername")}} + > {conn().http.username} - ) : ( - {language.t("server.row.noUsername")} - )} + - {conn().http.password && ••••••••} + + •••••••• +
)}
diff --git a/packages/app/src/components/session/session-sortable-tab-v2.tsx b/packages/app/src/components/session/session-sortable-tab-v2.tsx index e109e5d2eca..a78c286cf03 100644 --- a/packages/app/src/components/session/session-sortable-tab-v2.tsx +++ b/packages/app/src/components/session/session-sortable-tab-v2.tsx @@ -12,7 +12,7 @@ import { FileVisual } from "./session-sortable-tab" export function SortableTabV2(props: { tab: string - index: () => number + index: number temporary?: boolean onTabClose: (tab: string) => void onTabDoubleClick?: (tab: string) => void @@ -26,7 +26,7 @@ export function SortableTabV2(props: { return props.tab }, get index() { - return props.index() + return props.index }, }) const path = createMemo(() => file.pathFromTab(props.tab)) diff --git a/packages/app/src/components/session/session-sortable-terminal-tab-v2.tsx b/packages/app/src/components/session/session-sortable-terminal-tab-v2.tsx index a27b29668e5..8a695b9d4fb 100644 --- a/packages/app/src/components/session/session-sortable-terminal-tab-v2.tsx +++ b/packages/app/src/components/session/session-sortable-terminal-tab-v2.tsx @@ -14,7 +14,7 @@ import { focusTerminalById } from "@/pages/session/helpers" export function SortableTerminalTabV2(props: { terminal: LocalPTY - index: () => number + index: number newLayout: boolean onClose?: () => void }): JSX.Element { @@ -25,7 +25,7 @@ export function SortableTerminalTabV2(props: { return props.terminal.id }, get index() { - return props.index() + return props.index }, }) const [store, setStore] = createStore({ diff --git a/packages/app/src/components/settings-keybinds.tsx b/packages/app/src/components/settings-keybinds.tsx index 57d71c0d6d4..5e4631addd2 100644 --- a/packages/app/src/components/settings-keybinds.tsx +++ b/packages/app/src/components/settings-keybinds.tsx @@ -1,5 +1,6 @@ import { Component, For, Show, createMemo, lazy, onCleanup, onMount } from "solid-js" import { createStore } from "solid-js/store" +import { Dynamic } from "solid-js/web" import { makeEventListener } from "@solid-primitives/event-listener" import { Button } from "@opencode-ai/ui/button" import { Icon } from "@opencode-ai/ui/icon" @@ -424,9 +425,9 @@ function SettingsKeybindsV2() { filtered={controller.catalog.filtered} title={controller.catalog.title} keybind={controller.catalog.keybind} - active={controller.capture.active} + active={controller.capture.active()} onCapture={controller.capture.toggle} - hasOverrides={controller.settings.hasOverrides} + hasOverrides={controller.settings.hasOverrides()} onReset={controller.settings.reset} /> ) @@ -437,9 +438,9 @@ function SettingsKeybindsV2View(props: { filtered: (query: string) => Map title: (id: string) => string keybind: (id: string) => string - active: () => string | null + active: string | null onCapture: (id: string) => void - hasOverrides: () => boolean + hasOverrides: boolean onReset: () => void }) { const language = useLanguage() @@ -455,7 +456,7 @@ function SettingsKeybindsV2View(props: {

{language.t("settings.shortcuts.title")}

{language.t("settings.shortcuts.description")} - + {language.t("settings.shortcuts.reset.button")} @@ -501,12 +502,12 @@ function SettingsKeybindsV2View(props: { data-keybind-id={id} classList={{ "settings-v2-keybind-button": true, - "settings-v2-keybind-button--active": props.active() === id, + "settings-v2-keybind-button--active": props.active === id, }} onClick={() => props.onCapture(id)} > {language.t("settings.shortcuts.pressKeys")} @@ -677,8 +678,6 @@ export const SettingsKeybinds: Component<{ v2?: boolean }> = (props) => { ) - const List = props.v2 ? SettingsListV2 : SettingsList - const groups = (
= (props) => { > {language.t(groupKey[group])} - + {(id) => (
@@ -738,7 +737,7 @@ export const SettingsKeybinds: Component<{ v2?: boolean }> = (props) => {
)}
-
+
)} diff --git a/packages/app/src/components/settings-server-picker.tsx b/packages/app/src/components/settings-server-picker.tsx index 16fb3ac34d5..b73ad0d23cd 100644 --- a/packages/app/src/components/settings-server-picker.tsx +++ b/packages/app/src/components/settings-server-picker.tsx @@ -20,8 +20,8 @@ export function SettingsServerDataScope(props: ParentProps<{ server: ServerConne const serverCtx = () => global.ensureServerCtx(props.server) return ( - props.server}> - props.server}> + + {props.children} diff --git a/packages/app/src/components/settings-v2/dialog-settings-v2.tsx b/packages/app/src/components/settings-v2/dialog-settings-v2.tsx index 1595d3385e3..da7a3c81d9d 100644 --- a/packages/app/src/components/settings-v2/dialog-settings-v2.tsx +++ b/packages/app/src/components/settings-v2/dialog-settings-v2.tsx @@ -149,7 +149,7 @@ export const DialogSettings: Component<{ - + diff --git a/packages/app/src/components/settings-v2/providers.tsx b/packages/app/src/components/settings-v2/providers.tsx index 35a1588aad8..496b5ff8bd4 100644 --- a/packages/app/src/components/settings-v2/providers.tsx +++ b/packages/app/src/components/settings-v2/providers.tsx @@ -4,7 +4,7 @@ import { useDialog } from "@opencode-ai/ui/context/dialog" import { ProviderIcon } from "@opencode-ai/ui/provider-icon" import { showToast } from "@/utils/toast" import { popularProviders, useProviders } from "@/hooks/use-providers" -import { createMemo, type Accessor, type Component, For, Show } from "solid-js" +import { createMemo, type Component, For, Show } from "solid-js" import { useLanguage } from "@/context/language" import { useServerSDK } from "@/context/server-sdk" import { useServerSync } from "@/context/server-sync" @@ -32,14 +32,14 @@ const PROVIDER_NOTES = [ const PROVIDER_ICON_SIZE = 16 export const SettingsProvidersV2: Component<{ - directory: Accessor + directory: string | undefined onBack?: () => void }> = (props) => { const dialog = useDialog() const language = useLanguage() const serverSdk = useServerSDK() const serverSync = useServerSync() - const providers = useProviders(props.directory) + const providers = useProviders(() => props.directory) const providerConnect = useProviderConnectController({ onBack: props.onBack }) const connect = (provider?: string) => { @@ -105,7 +105,7 @@ export const SettingsProvidersV2: Component<{ } const disconnect = async (providerID: string, name: string) => { - const location = props.directory() ? { directory: props.directory() } : undefined + const location = props.directory ? { directory: props.directory } : undefined await serverSdk() .api.integration.get({ integrationID: providerID, location }) .then(async (integration) => { diff --git a/packages/app/src/components/status-popover-body.tsx b/packages/app/src/components/status-popover-body.tsx index 1a65bd38d86..cfb1232c67e 100644 --- a/packages/app/src/components/status-popover-body.tsx +++ b/packages/app/src/components/status-popover-body.tsx @@ -1,16 +1,7 @@ import { Switch } from "@opencode-ai/ui/switch" import { Tabs } from "@opencode-ai/ui/tabs" import { showToast } from "@/utils/toast" -import { - type Accessor, - createEffect, - createMemo, - createResource, - For, - type JSXElement, - onCleanup, - Show, -} from "solid-js" +import { createEffect, createMemo, createResource, For, type JSXElement, onCleanup, Show } from "solid-js" import { createStore } from "solid-js/store" import { useLanguage } from "@/context/language" import { usePlatform } from "@/context/platform" @@ -109,7 +100,7 @@ type ServerStatusItem = { onSelect: () => void } -export function StatusPopoverBody(props: { shown: Accessor }) { +export function StatusPopoverBody(props: { shown: boolean }) { const sync = useSync() const sdk = useSDK() const global = useGlobal() @@ -125,10 +116,6 @@ export function StatusPopoverBody(props: { shown: Accessor }) { }) } - createEffect(() => { - if (!props.shown()) return - }) - let dialogRun = 0 let dialogDead = false onCleanup(() => { @@ -147,7 +134,7 @@ export function StatusPopoverBody(props: { shown: Accessor }) { const lspItems = createMemo(() => sync().data.lsp ?? []) const lspCount = createMemo(() => lspItems().length) const [pluginList] = createResource( - () => (props.shown() ? sdk().directory : undefined), + () => (props.shown ? sdk().directory : undefined), (directory) => sdk() .api.plugin.list({ location: { directory } }) diff --git a/packages/app/src/components/status-popover.tsx b/packages/app/src/components/status-popover.tsx index 3d3b03fa38f..189be5ce2d5 100644 --- a/packages/app/src/components/status-popover.tsx +++ b/packages/app/src/components/status-popover.tsx @@ -74,7 +74,7 @@ export function StatusPopover() {
} > - + @@ -114,7 +114,7 @@ function DirectoryStatusPopover() { onOpenChange: setShown, body: () => ( - + ), })) diff --git a/packages/app/src/components/terminal.tsx b/packages/app/src/components/terminal.tsx index d603614689a..b6bc7ebfe17 100644 --- a/packages/app/src/components/terminal.tsx +++ b/packages/app/src/components/terminal.tsx @@ -175,6 +175,7 @@ export const Terminal = (props: TerminalProps) => { const settings = useSettings() const theme = useTheme() const language = useLanguage() + // Intentional mount-time capture: the imperative xterm/WebSocket lifecycle needs stable values, and Terminal remounts when the SDK scope changes. const directory = sdk().directory const url = sdk().url let container!: HTMLDivElement diff --git a/packages/app/src/components/titlebar-tab-nav.tsx b/packages/app/src/components/titlebar-tab-nav.tsx index 2862a793530..6e36dc53ded 100644 --- a/packages/app/src/components/titlebar-tab-nav.tsx +++ b/packages/app/src/components/titlebar-tab-nav.tsx @@ -22,14 +22,14 @@ export function TabNavItem(props: { ref?: Ref href: string server: ServerConnection.Key - session: () => SessionInfo | undefined + session: SessionInfo | undefined fallbackTitle?: string onRename: (title: string) => Promise onClose: () => void onNavigate: () => void active?: boolean forceTruncate?: boolean - suppressNavigation?: () => boolean + suppressNavigation?: boolean dragging?: boolean pressed?: boolean hidden?: boolean @@ -52,22 +52,22 @@ export function TabNavItem(props: { if (conn) return global.ensureServerCtx(conn) }) const project = createMemo(() => { - const session = props.session() + const session = props.session if (!session) return return projectForSession(session, serverCtx()?.projects.list() ?? []) }) const title = createMemo(() => { - const session = props.session() + const session = props.session return session ? sessionLabel(session) : props.fallbackTitle }) const projectName = createMemo(() => { - const session = props.session() + const session = props.session if (!session) return return displayName(project() ?? { worktree: session.location.directory }) }) const previewPath = createMemo(() => { - const session = props.session() + const session = props.session if (!session) return const home = serverCtx()?.sync.data.path.home return home ? session.location.directory.replace(home, "~") : session.location.directory @@ -80,7 +80,7 @@ export function TabNavItem(props: { }) const [popoverOpen, setPopoverOpen] = createSignal(false) - const previewBlocked = () => !!props.dragging || editing() || !!props.pressed || !props.session() + const previewBlocked = () => !!props.dragging || editing() || !!props.pressed || !props.session const measureTitleOverflow = () => { if (!titleEl || editing()) { @@ -121,7 +121,7 @@ export function TabNavItem(props: { const closeRename = async (save: boolean) => { if (rename.isPending || !editing()) return - const original = props.session()?.title ?? "" + const original = props.session?.title ?? "" const next = (titleEl.textContent ?? "").trim() titleEl.scrollLeft = 0 @@ -146,7 +146,7 @@ export function TabNavItem(props: { event.preventDefault() event.stopPropagation() if (!canOpenTabRename(props.dragging, editing(), rename.isPending)) return - const session = props.session() + const session = props.session if (!session) return titleEl.textContent = session.title ?? "" setEditing(true) @@ -213,7 +213,7 @@ export function TabNavItem(props: { // Navigate on mousedown to shave the press-release delay off tab switches. if (event.button !== 0) return if (editing()) return - if (props.suppressNavigation?.()) return + if (props.suppressNavigation) return props.onNavigate() }} onClick={(event) => { @@ -221,14 +221,14 @@ export function TabNavItem(props: { // Mouse navigation already happened on mousedown; detail 0 means keyboard activation. if (event.detail > 0) return if (editing()) return - if (props.suppressNavigation?.()) return + if (props.suppressNavigation) return props.onNavigate() }} class="flex h-full min-w-0 flex-1 flex-row items-center gap-1.5 text-[13px] font-medium text-v2-text-text-faint group-data-[active='true']:text-v2-text-text-base group-data-[editing='true']:text-v2-text-text-base [-webkit-user-drag:none]" >
@@ -211,7 +211,7 @@ function DraftTabSlot(props: { export function TitlebarTabStrip(props: { tabs: Tab[] - currentTab: () => Tab | undefined + currentTab: Tab | undefined forceTruncate: boolean onNavigate: (tab: Tab, el?: HTMLDivElement) => void onClose: (tab: Tab) => void @@ -248,7 +248,7 @@ export function TitlebarTabStrip(props: { ]) function selectAdjacentTab(offset: -1 | 1) { - const current = props.currentTab() + const current = props.currentTab const key = adjacentTabKey(visibleTabIds(), current ? tabKey(current) : undefined, offset) const next = props.tabs.find((tab) => tabKey(tab) === key) if (next) props.onNavigate(next) @@ -350,10 +350,10 @@ export function TitlebarTabStrip(props: { props.currentTab() === tab} + index={visibleIndex()} + active={props.currentTab === tab} forceTruncate={props.forceTruncate} - serverCtx={serverCtx} + serverCtx={serverCtx()} onVisibleChange={(visible) => setVisibility(id, visible)} onNavigate={(element) => { ref = element @@ -368,8 +368,8 @@ export function TitlebarTabStrip(props: { props.currentTab() === tab} + index={visibleIndex()} + active={props.currentTab === tab} title={language.t("command.session.new")} onNavigate={(element) => { ref = element diff --git a/packages/app/src/components/titlebar.tsx b/packages/app/src/components/titlebar.tsx index 3bb2951ea57..7b0a1ae93b7 100644 --- a/packages/app/src/components/titlebar.tsx +++ b/packages/app/src/components/titlebar.tsx @@ -46,8 +46,8 @@ const windowsControlsBaseWidth = 138 // 3 native Windows caption buttons at 46px const macTrafficLightsBaseWidth = 84 export type TitlebarUpdate = { - version: () => string | undefined - installing: () => boolean + version: string | undefined + installing: boolean install: () => void } @@ -121,8 +121,8 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl const hasProjects = createMemo(() => layout.projects.list().length > 0) const nav = createMemo(() => (useV2Titlebar() ? settings.general.showNavigation() : true)) const updateState = createMemo(() => { - const installing = props.update?.installing() ?? false - const version = props.update?.version() + const installing = props.update?.installing ?? false + const version = props.update?.version return { visible: version !== undefined || installing, installing, @@ -392,7 +392,7 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl { @@ -657,12 +657,10 @@ function ChannelIndicator(props: { debugTools?: { visible: boolean; toggle: () = } return ( - <> - {["local", "beta", "dev"].includes(channel) && ( -
- {channel.toUpperCase()} -
- )} - + +
+ {channel.toUpperCase()} +
+
) } diff --git a/packages/app/src/components/windows-app-menu.tsx b/packages/app/src/components/windows-app-menu.tsx index b6088c57f43..57d8f9269d6 100644 --- a/packages/app/src/components/windows-app-menu.tsx +++ b/packages/app/src/components/windows-app-menu.tsx @@ -1,4 +1,4 @@ -import { Show, type JSX } from "solid-js" +import { For, Show, type JSX } from "solid-js" import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu" import { Icon } from "@opencode-ai/ui/icon" import { IconButton } from "@opencode-ai/ui/icon-button" @@ -50,7 +50,20 @@ export function WindowsAppMenu(props: { return ( - {props.variant === "v2" ? ( + + } + >
- ) : ( - - )} +
OpenCode - {DESKTOP_MENU.filter((menu) => desktopMenuVisible(menu, "windows")).map((menu) => ( - - {menu.items - ?.filter((entry) => desktopMenuVisible(entry, "windows")) - .map((entry) => - entry.type === "separator" ? ( - - ) : ( - runEntry(entry)} - /> - ), - )} - - ))} + desktopMenuVisible(menu, "windows"))}> + {(menu) => ( + + desktopMenuVisible(entry, "windows"))}> + {(entry) => { + // Static menu data: an early return keeps the union narrowing a Show fallback would lose. + if (entry.type === "separator") return + return ( + runEntry(entry)} + /> + ) + }} + + + )} + diff --git a/packages/app/src/context/models.tsx b/packages/app/src/context/models.tsx index a80cf2e5804..4eb18f38bf6 100644 --- a/packages/app/src/context/models.tsx +++ b/packages/app/src/context/models.tsx @@ -1,4 +1,4 @@ -import { type Accessor, createMemo, createResource } from "solid-js" +import { createMemo, createResource } from "solid-js" import { createStore } from "solid-js/store" import { DateTime } from "luxon" import { filter, firstBy, flat, groupBy, mapValues, pipe, uniqueBy, values } from "remeda" @@ -25,8 +25,8 @@ function modelKey(model: ModelKey) { export const { use: useModels, provider: ModelsProvider } = createSimpleContext({ name: "Models", gate: false, - init: (props: { directory?: Accessor } = {}) => { - const providers = useProviders(() => props.directory?.()) + init: (props: { directory?: string } = {}) => { + const providers = useProviders(() => props.directory) const [store, setStore, _, ready] = persisted( Persist.global("model", ["model.v1"]), diff --git a/packages/app/src/context/sdk.tsx b/packages/app/src/context/sdk.tsx index d9051f5fbe0..880691ce5f2 100644 --- a/packages/app/src/context/sdk.tsx +++ b/packages/app/src/context/sdk.tsx @@ -6,12 +6,9 @@ export type { DirectorySDK } from "./server-sdk" const context = createSimpleContext({ name: "SDK", // Resolves the directory-scoped SDK reactively from the (possibly changing) server. - init: (props: { directory: string | Accessor }) => { + init: (props: { directory: string }) => { const serverSDK = useServerSDK() - return createMemo(() => { - const directory = typeof props.directory === "function" ? props.directory() : props.directory - return serverSDK().ensureDirSdkContext(directory) - }) + return createMemo(() => serverSDK().ensureDirSdkContext(props.directory)) }, }) diff --git a/packages/app/src/context/server-sdk.tsx b/packages/app/src/context/server-sdk.tsx index 1345631a1bc..447ead4aa36 100644 --- a/packages/app/src/context/server-sdk.tsx +++ b/packages/app/src/context/server-sdk.tsx @@ -343,13 +343,13 @@ export const { use: useServerSDK, provider: ServerSDKProvider } = createSimpleCo name: "ServerSDK", // Returns an accessor so the resolved server can change reactively (e.g. a // /new-session draft retargeting its server) without re-instantiating the subtree. - init: (props: { server?: Accessor }) => { + init: (props: { server?: ServerConnection.Any }) => { const global = useGlobal() const language = useLanguage() const server = useServer() return createMemo(() => { - const conn = props.server?.() ?? server.current + const conn = props.server ?? server.current if (!conn) throw new Error(language.t("error.serverSDK.noServerAvailable")) return global.ensureServerCtx(conn).sdk }) diff --git a/packages/app/src/context/server-sync.tsx b/packages/app/src/context/server-sync.tsx index 58408abbdd1..d1908043f49 100644 --- a/packages/app/src/context/server-sync.tsx +++ b/packages/app/src/context/server-sync.tsx @@ -804,13 +804,13 @@ export const { use: useServerSync, provider: ServerSyncProvider } = createSimple name: "ServerSync", // Returns an accessor so the resolved server can change reactively without // re-instantiating the subtree (mirrors useServerSDK). - init: (props: { server?: Accessor }) => { + init: (props: { server?: ServerConnection.Any }) => { const global = useGlobal() const language = useLanguage() const server = useServer() return createMemo(() => { - const conn = props.server?.() ?? server.current + const conn = props.server ?? server.current if (!conn) throw new Error(language.t("error.serverSDK.noServerAvailable")) return global.ensureServerCtx(conn).sync }) diff --git a/packages/app/src/pages/directory-layout.tsx b/packages/app/src/pages/directory-layout.tsx index a0b35763965..7df092aceb2 100644 --- a/packages/app/src/pages/directory-layout.tsx +++ b/packages/app/src/pages/directory-layout.tsx @@ -2,7 +2,7 @@ import { DataProvider } from "@opencode-ai/session-ui/context" import { showToast } from "@/utils/toast" import { base64Encode } from "@opencode-ai/core/util/encode" import { useLocation, useNavigate, useParams } from "@solidjs/router" -import { type Accessor, createEffect, createMemo, createResource, onCleanup, type ParentProps, Show } from "solid-js" +import { createEffect, createMemo, createResource, onCleanup, type ParentProps, Show } from "solid-js" import { useLanguage } from "@/context/language" import { LocalProvider } from "@/context/local" import { SDKProvider } from "@/context/sdk" @@ -15,9 +15,9 @@ import { useServerSync } from "@/context/server-sync" export function DirectoryDataProvider( props: ParentProps<{ - directory: string | Accessor + directory: string draftID?: string - server?: Accessor + server?: ServerConnection.Key }>, ) { const location = useLocation() @@ -25,17 +25,16 @@ export function DirectoryDataProvider( const params = useParams() const sync = useSync() const serverSync = useServerSync() - const directory = () => (typeof props.directory === "function" ? props.directory() : props.directory) + const directory = () => props.directory const slug = createMemo(() => base64Encode(directory())) const href = (sessionID: string) => { - const server = props.server?.() - if (server) return sessionHref(server, sessionID) + if (props.server) return sessionHref(props.server, sessionID) return `/${slug()}/session/${sessionID}` } createEffect(() => { // A draft lives at /new-session?draftId=… and has no directory segment to normalize. - if (props.draftID || props.server?.()) return + if (props.draftID || props.server) return const next = sync().data.path.directory if (!next || next === directory()) return const path = location.pathname.slice(slug().length + 1) diff --git a/packages/app/src/pages/home.tsx b/packages/app/src/pages/home.tsx index 9f3d43d7709..d2d94e00a6b 100644 --- a/packages/app/src/pages/home.tsx +++ b/packages/app/src/pages/home.tsx @@ -39,8 +39,8 @@ export function Home() { > scroll.viewport.update(event.currentTarget.scrollTop)} onWheel={scroll.viewport.containOuterWheel} diff --git a/packages/app/src/pages/home/home-projects-view.tsx b/packages/app/src/pages/home/home-projects-view.tsx index a7dd6ee907f..270bba49cb8 100644 --- a/packages/app/src/pages/home/home-projects-view.tsx +++ b/packages/app/src/pages/home/home-projects-view.tsx @@ -1,4 +1,4 @@ -import { type Accessor, createMemo, For, type JSX, onCleanup, Show, splitProps } from "solid-js" +import { createMemo, For, type JSX, onCleanup, Show, splitProps } from "solid-js" import { createStore } from "solid-js/store" import { DragDropProvider, PointerSensor } from "@dnd-kit/solid" import { isSortable, useSortable } from "@dnd-kit/solid/sortable" @@ -29,16 +29,16 @@ const projectContextMenuID = (server: ServerConnection.Any, directory: string) = export type HomeProjectsViewProps = { language: ReturnType - servers: Accessor - projects: Accessor - recentlyClosed: Accessor - selection: Accessor - homedir: Accessor + servers: ServerConnection.Any[] + projects: LocalProject[] + recentlyClosed: LocalProject[] + selection: HomeProjectSelection + homedir: string serverHealth: (server: ServerConnection.Any) => ServerHealth | undefined projectsForServer: (server: ServerConnection.Any) => LocalProject[] collapsed: (server: ServerConnection.Any) => boolean - canDefaultServer: Accessor - defaultServerKey: Accessor + canDefaultServer: boolean + defaultServerKey: ServerConnection.Key | null | undefined canRevealProject: (server: ServerConnection.Any) => boolean unseenCount: (server: ServerConnection.Any, project: LocalProject) => number onWheel: (event: WheelEvent) => void @@ -81,9 +81,7 @@ export function HomeProjectsView(props: HomeProjectsViewProps) { >
{props.language.t("home.projects")}
- 0)} - > + 0)}> } - disabled={props.serverHealth(props.servers()[0])?.healthy === false} - onClick={() => props.onChooseProject(props.servers()[0])} + disabled={props.serverHealth(props.servers[0])?.healthy === false} + onClick={() => props.onChooseProject(props.servers[0])} aria-label={props.language.t("home.project.add")} /> @@ -100,25 +98,20 @@ export function HomeProjectsView(props: HomeProjectsViewProps) {
1} + when={props.servers.length > 1} fallback={
0} - fallback={} + when={props.projects.length > 0} + fallback={} > - +
} >
- + {(item) => { const projects = () => props.projectsForServer(item) const healthy = () => !!props.serverHealth(item)?.healthy @@ -130,7 +123,7 @@ export function HomeProjectsView(props: HomeProjectsViewProps) { server={item} {...props} {...contextMenuProps} - selected={props.selection().server === ServerConnection.key(item) && !props.selection().directory} + selected={props.selection.server === ServerConnection.key(item) && !props.selection.directory} collapsed={collapsed()} health={props.serverHealth(item)} /> @@ -277,8 +270,8 @@ function HomeServerRow(props: { props.onSetDefaultServer(props.server)} @@ -339,7 +332,7 @@ function HomeProjectList(props: HomeProjectListProps) { const source = event.operation.source if (event.canceled || !isSortable(source)) return if (source.initialIndex !== source.index) props.onMoveProject(props.server, source.id.toString(), source.index) - if (props.selection().server !== ServerConnection.key(props.server)) + if (props.selection.server !== ServerConnection.key(props.server)) props.onSelectProject(props.server, source.id.toString()) }} > @@ -350,7 +343,7 @@ function HomeProjectList(props: HomeProjectListProps) { row's sortable unregisters on unmount) and discarding animations. String keys keep row elements alive and move them on reorder. */} project.worktree)}> - {(worktree, index) => } + {(worktree, index) => }
@@ -360,7 +353,7 @@ function HomeProjectList(props: HomeProjectListProps) { function HomeProjectSlot( props: HomeProjectListProps & { worktree: string - index: () => number + index: number }, ) { const initial = props.items.find((item) => item.worktree === props.worktree) @@ -376,10 +369,9 @@ function HomeProjectSlot( project={project()} server={props.server} index={props.index} - serverSelected={props.selection().server === ServerConnection.key(props.server)} + serverSelected={props.selection.server === ServerConnection.key(props.server)} selected={ - props.selection().server === ServerConnection.key(props.server) && - props.selection().directory === props.worktree + props.selection.server === ServerConnection.key(props.server) && props.selection.directory === props.worktree } unseen={props.unseenCount(props.server, project())} /> @@ -425,7 +417,7 @@ function HomeRecentlyClosedRow( ) { const unreachable = () => props.serverHealth(props.server)?.healthy === false const path = () => { - const home = props.homedir() + const home = props.homedir const worktree = props.project.worktree if (home && (worktree === home || worktree.startsWith(`${home}/`))) return `~${worktree.slice(home.length)}` return worktree @@ -451,7 +443,7 @@ function HomeProjectRow( HomeProjectsContextMenuProps & { project: LocalProject server: ServerConnection.Any - index: () => number + index: number serverSelected: boolean selected: boolean unseen: number @@ -464,7 +456,7 @@ function HomeProjectRow( return props.project.worktree }, get index() { - return props.index() + return props.index }, }) let pointerDownSelected: boolean | undefined diff --git a/packages/app/src/pages/home/home-projects.tsx b/packages/app/src/pages/home/home-projects.tsx index 32495403bcf..34c02a78479 100644 --- a/packages/app/src/pages/home/home-projects.tsx +++ b/packages/app/src/pages/home/home-projects.tsx @@ -6,16 +6,16 @@ export function HomeProjects(props: { projects: HomeProjectsController; scroll: return ( export function HomeSessionStatusController(props: { - server: Accessor + server: ServerConnection.Key record: HomeSessionRecord isOpenTab: (record: HomeSessionRecord) => boolean render: (state: { unread: Accessor; loading: Accessor; open: Accessor }) => JSX.Element }) { const avatar = useSessionTabAvatarState( - props.server, + () => props.server, () => props.record.session.location.directory, () => props.record.session.id, ) diff --git a/packages/app/src/pages/home/home-sessions-view.tsx b/packages/app/src/pages/home/home-sessions-view.tsx index eef36843b3c..062cb606089 100644 --- a/packages/app/src/pages/home/home-sessions-view.tsx +++ b/packages/app/src/pages/home/home-sessions-view.tsx @@ -1,5 +1,5 @@ import type { SessionInfo } from "@opencode-ai/client/promise" -import { type Accessor, createMemo, For, Show, Suspense } from "solid-js" +import { createMemo, For, Show, Suspense } from "solid-js" import { Spinner } from "@opencode-ai/ui/spinner" import { ScrollView } from "@opencode-ai/ui/scroll-view" import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2" @@ -38,17 +38,17 @@ function isBackgroundOpen(event: MouseEvent) { export type HomeSessionsViewProps = { language: ReturnType - groups: Accessor - showProjectName: Accessor - server: Accessor - canCreateSession: Accessor - searchValue: Accessor - searchPlaceholder: Accessor - searchOpen: Accessor - searchLoading: Accessor - searchResults: Accessor - searchActive: Accessor - searchNoResultsLabel: Accessor + groups: HomeSessionGroup[] + showProjectName: boolean + server: ServerConnection.Key + canCreateSession: boolean + searchValue: string + searchPlaceholder: string + searchOpen: boolean + searchLoading: boolean + searchResults: HomeSessionRecord[] + searchActive: string + searchNoResultsLabel: string titleOpacity: (id: HomeSessionGroup["id"]) => number isOpenTab: (record: HomeSessionRecord) => boolean onCreateSession: () => void @@ -81,7 +81,7 @@ export function HomeSessionsView(props: HomeSessionsViewProps) {
- 0 && props.canCreateSession()}> + 0 && props.canCreateSession}>
0} + when={props.groups.length > 0} fallback={ } >
- + {(group, index) => ( <>
{(record) => }
@@ -205,7 +205,7 @@ function HomeSessionSearch(props: HomeSessionsViewProps) { return (
- +
@@ -225,7 +225,7 @@ function HomeSessionSearch(props: HomeSessionsViewProps) { } > 0} + when={props.searchResults.length > 0} fallback={

- {props.searchNoResultsLabel()} + {props.searchNoResultsLabel}

} > @@ -248,12 +248,12 @@ function HomeSessionSearch(props: HomeSessionsViewProps) {

- + {(record) => ( )} @@ -280,16 +280,14 @@ function HomeSessionSearch(props: HomeSessionsViewProps) { relative z-20 min-w-0 flex-1 border-0 bg-transparent outline-0 text-v2-text-text-base [font-weight:440] placeholder:text-v2-text-text-faint `} - value={props.searchValue()} - placeholder={props.searchPlaceholder()} - aria-label={props.searchPlaceholder()} - aria-expanded={props.searchOpen()} + value={props.searchValue} + placeholder={props.searchPlaceholder} + aria-label={props.searchPlaceholder} + aria-expanded={props.searchOpen} aria-controls={HOME_SESSION_SEARCH_RESULTS_ID} aria-autocomplete="list" aria-activedescendant={ - props.searchActive() && props.searchOpen() - ? `home-session-search-option-${props.searchActive()}` - : undefined + props.searchActive && props.searchOpen ? `home-session-search-option-${props.searchActive}` : undefined } onFocus={props.onSearchFocus} onInput={(event) => props.onSearchInput(event.currentTarget.value)} @@ -300,7 +298,7 @@ function HomeSessionSearch(props: HomeSessionsViewProps) { event.currentTarget.blur() return } - if (!props.searchOpen() || props.searchResults().length === 0) return + if (!props.searchOpen || props.searchResults.length === 0) return if (event.altKey || event.metaKey) return if (event.key === "ArrowDown") { event.preventDefault() @@ -318,14 +316,14 @@ function HomeSessionSearch(props: HomeSessionsViewProps) { } }} /> - + } - aria-label={props.searchPlaceholder()} + aria-label={props.searchPlaceholder} onClick={() => { props.onSearchClose() props.onSearchFocus() @@ -345,7 +343,7 @@ function HomeSessionSearchResultRow( }, ) { const title = createMemo(() => sessionLabel(props.record.session)) - const showProjectName = () => props.showProjectName() && props.record.projectName + const showProjectName = () => props.showProjectName && props.record.projectName const key = () => homeSessionSearchKey(props.record) return ( @@ -416,7 +414,7 @@ function HomeSessionGroupHeader(props: { function HomeSessionRow(props: HomeSessionsViewProps & { record: HomeSessionRecord }) { const title = createMemo(() => sessionLabel(props.record.session)) - const showProjectName = () => props.showProjectName() && props.record.projectName + const showProjectName = () => props.showProjectName && props.record.projectName return (
setV2Toast(true)) + onMount(() => setV2Toast(true)) const update: TitlebarUpdate = { - version: () => { + get version() { const state = platform.updater?.state() - if (state?.status !== "ready") return + if (state?.status !== "ready") return undefined return state.version }, - installing: () => platform.updater?.state().status === "installing", + get installing() { + return platform.updater?.state().status === "installing" + }, install: () => void platform.updater?.install(), } @@ -40,7 +42,9 @@ export default function Layout(props: ParentProps) {
{props.children}
- {import.meta.env.DEV && state.debugTools && } + + +
) diff --git a/packages/app/src/pages/new-session.tsx b/packages/app/src/pages/new-session.tsx index 8ceced9e840..cbe2a91a4d9 100644 --- a/packages/app/src/pages/new-session.tsx +++ b/packages/app/src/pages/new-session.tsx @@ -56,7 +56,7 @@ export default function NewSessionPage() { return (
{suspendUntilPromptReady()} - +
diff --git a/packages/app/src/pages/new-session/new-session-view.tsx b/packages/app/src/pages/new-session/new-session-view.tsx index 62acdeadf43..4d881f27a21 100644 --- a/packages/app/src/pages/new-session/new-session-view.tsx +++ b/packages/app/src/pages/new-session/new-session-view.tsx @@ -3,7 +3,7 @@ import { Tooltip } from "@opencode-ai/ui/tooltip" import { Icon } from "@opencode-ai/ui/v2/icon" import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2" import { WordmarkV2 } from "@opencode-ai/ui/v2/wordmark-v2" -import { Show, createMemo, createSignal, type Accessor } from "solid-js" +import { Show, createMemo, createSignal } from "solid-js" import { createStore } from "solid-js/store" import { Portal } from "solid-js/web" import createPresence from "solid-presence" @@ -85,14 +85,14 @@ export function NewSessionView(props: { ) } -export function NewSessionStatus(props: { mount: Accessor; visible: Accessor }) { +export function NewSessionStatus(props: { mount: HTMLElement | null; visible: boolean }) { const language = useLanguage() return ( - + {(mount) => ( - + @@ -127,7 +127,7 @@ function ProviderTip() { }) const openProviders = () => { void import("@/components/dialog-connect-provider").then(({ DialogConnectProvider }) => { - void dialog.show(() => sdk().directory} />) + void dialog.show(() => ) }) } diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 1a8eaaf58a9..f99d6bac56d 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -165,7 +165,7 @@ export function TargetSessionRouteContent() { return ( // Settings must keep the target-server SDK, sync, and models context and remain registered // when session content falls back to the route error boundary. - params.id}> + @@ -185,17 +185,15 @@ export function SessionRouteErrorBoundary( const settings = useSettings() return ( - settings.general.newLayoutDesigns() ? ( + fallback={(error) => ( + }> - ) : ( - - ) - } + + )} > {props.children} @@ -255,7 +253,6 @@ function ResolvedTargetSessionRoute() { () => sync().session.lineage, ) const directory = createMemo(() => current()?.session.location.directory) - const targetDirectory = () => directory()! createEffect(() => { const session = current() @@ -272,11 +269,13 @@ function ResolvedTargetSessionRoute() { // the terminal. Same-workspace tab switches keep it open because warm // targets resolve synchronously from the sync cache. - - - - - + {(dir) => ( + + + + + + )} ) } @@ -294,9 +293,7 @@ function TargetSessionPage() { ) } -function TargetServerScopedProviders( - props: ParentProps<{ directory?: () => string | undefined; sessionID?: () => string | undefined }>, -) { +function TargetServerScopedProviders(props: ParentProps<{ directory?: string; sessionID?: string }>) { return ( <> @@ -305,10 +302,10 @@ function TargetServerScopedProviders( ) } -function MarkSessionNotificationsViewed(props: { sessionID?: () => string | undefined }) { +function MarkSessionNotificationsViewed(props: { sessionID?: string }) { const notification = useNotification() createEffect(() => { - const sessionID = props.sessionID?.() + const sessionID = props.sessionID if (!notification.ready() || !sessionID) return if (notification.session.unseenCount(sessionID) === 0) return notification.session.markViewed(sessionID) @@ -1257,8 +1254,8 @@ export default function Page() { setTree("reviewScroll", el)} @@ -1291,8 +1288,12 @@ export default function Page() { get empty() { return reviewEmptyV2() }, - diffs: reviewDiffs, - diffsReady: reviewReady, + get diffs() { + return reviewDiffs() + }, + get diffsReady() { + return reviewReady() + }, get diffVersion() { return vcsQuery.dataUpdatedAt }, @@ -2113,11 +2114,11 @@ export default function Page() { onScheduleScrollState={scheduleScrollState} onAutoScrollHandleScroll={autoScroll.handleScroll} onMarkScrollGesture={markScrollGesture} - hasScrollGesture={hasScrollGesture} + hasScrollGesture={hasScrollGesture()} onUserScroll={markUserScroll} onHistoryScroll={onHistoryScroll} onAutoScrollInteraction={autoScroll.handleInteraction} - shouldAnchorBottom={() => + shouldAnchorBottom={ !location.hash && !store.messageId && !ui.pendingMessage && !autoScroll.userScrolled() } centered={centered()} @@ -2301,7 +2302,14 @@ export default function Page() { width: sessionPanelWidth(), }} > - {settings.general.newLayoutDesigns() ? ( + + {sessionPanelContent()} + + } + > {(_) => ( @@ -2309,11 +2317,7 @@ export default function Page() { )} - ) : ( - - {sessionPanelContent()} - - )} +
size.start()}> @@ -2337,13 +2341,13 @@ export default function Page() { hasReview() || reviewV2State.sidebarOpened()} - reviewCount={reviewCount} + canReview={canReview()} + diffs={reviewDiffs()} + diffsReady={reviewReady()} + empty={reviewEmptyText()} + hasReview={hasReview()} + reviewHasFocusableContent={hasReview() || reviewV2State.sidebarOpened()} + reviewCount={reviewCount()} reviewPanel={reviewPanelV2} reviewSidebarToggle={(disabled) => ( - {(item) => - item() === doneToken ? ( - - ) : item() === totalToken ? ( - - ) : ( - {item()} - ) - } + {(item) => ( + {item()}}> + + + + + + + + )}
0)}> Cycle progress ({step()}/3 done) - {[0, 1, 2, 3].map((value) => ( - - ))} + + {(value) => ( + + )} +
diff --git a/packages/app/src/pages/session/review-tab.tsx b/packages/app/src/pages/session/review-tab.tsx index 5b175eccb9b..e4d6f9a0f10 100644 --- a/packages/app/src/pages/session/review-tab.tsx +++ b/packages/app/src/pages/session/review-tab.tsx @@ -19,8 +19,8 @@ type ReviewDiff = FileDiffInfo export interface SessionReviewTabProps { title?: JSX.Element empty?: JSX.Element - diffs: () => ReviewDiff[] - view: () => ReturnType["view"]> + diffs: ReviewDiff[] + view: ReturnType["view"]> diffStyle: DiffStyle onDiffStyleChange?: (style: DiffStyle) => void onViewFile?: (file: string) => void @@ -77,7 +77,7 @@ export function SessionReviewTab(props: SessionReviewTabProps) { if (!el || !layout.ready() || userInteracted) return if (el.clientHeight === 0 || el.clientWidth === 0) return - const s = props.view().scroll("review") + const s = props.view.scroll("review") if (!s || (s.x === 0 && s.y === 0)) return const maxY = Math.max(0, el.scrollHeight - el.clientHeight) @@ -111,14 +111,14 @@ export function SessionReviewTab(props: SessionReviewTabProps) { if (!layout.ready()) return if (el.clientHeight === 0 || el.clientWidth === 0) return - props.view().setScroll("review", { + props.view.setScroll("review", { x: el.scrollLeft, y: el.scrollTop, }) } createEffect(() => { - props.diffs().length + props.diffs.length props.diffStyle if (!layout.ready()) return queueRestore() @@ -145,14 +145,14 @@ export function SessionReviewTab(props: SessionReviewTabProps) { }} onScroll={handleScroll} onDiffRendered={queueRestore} - open={props.view().review.open()} - onOpenChange={props.view().review.setOpen} + open={props.view.review.open()} + onOpenChange={props.view.review.setOpen} classes={{ root: props.classes?.root ?? "pr-3", header: props.classes?.header ?? "px-3", container: props.classes?.container ?? "pl-3", }} - diffs={props.diffs()} + diffs={props.diffs} diffStyle={props.diffStyle} onDiffStyleChange={props.onDiffStyleChange} onViewFile={props.onViewFile} diff --git a/packages/app/src/pages/session/session-side-panel.tsx b/packages/app/src/pages/session/session-side-panel.tsx index 1e687453163..6ce8265078b 100644 --- a/packages/app/src/pages/session/session-side-panel.tsx +++ b/packages/app/src/pages/session/session-side-panel.tsx @@ -65,13 +65,13 @@ function renderDiff(value: ReviewDiff): value is RenderDiff { } export function SessionSidePanel(props: { - canReview: () => boolean - diffs: () => ReviewDiff[] - diffsReady: () => boolean - empty: () => string - hasReview: () => boolean - reviewHasFocusableContent: () => boolean - reviewCount: () => number + canReview: boolean + diffs: ReviewDiff[] + diffsReady: boolean + empty: string + hasReview: boolean + reviewHasFocusableContent: boolean + reviewCount: number reviewPanel: () => JSX.Element reviewSidebarToggle?: (disabled: boolean) => JSX.Element fileBrowserState?: SessionFileBrowserState @@ -113,7 +113,7 @@ export function SessionSidePanel(props: { }) const treeWidth = createMemo(() => (fileOpen() ? `${fileTreeWidth()}px` : "0px")) - const diffs = createMemo(() => props.diffs().filter(renderDiff)) + const diffs = createMemo(() => props.diffs.filter(renderDiff)) const diffFiles = createMemo(() => diffs().map((d) => d.file)) const kinds = createMemo(() => { const merge = (a: "add" | "del" | "mix" | undefined, b: "add" | "del" | "mix") => { @@ -177,7 +177,7 @@ export function SessionSidePanel(props: { pathFromTab: file.pathFromTab, normalizeTab, review: reviewTab, - hasReview: props.canReview, + hasReview: () => props.canReview, fileBrowser: () => !!props.fileBrowserState, }) const contextOpen = tabState.contextOpen @@ -348,7 +348,7 @@ export function SessionSidePanel(props: { onCleanup(stop) }} > - +
{language.t("session.tab.review")}
- -
{props.reviewCount()}
+ +
{props.reviewCount}
@@ -463,12 +463,12 @@ export function SessionSidePanel(props: {
- +
@@ -559,14 +559,14 @@ export function SessionSidePanel(props: {
)}
- + - {props.hasReview() - ? language.t("session.review.filesChanged", { count: props.reviewCount() }) + {props.hasReview + ? language.t("session.review.filesChanged", { count: props.reviewCount }) : language.t("session.tab.review")} @@ -611,7 +611,7 @@ export function SessionSidePanel(props: { fallback={ tabs().all().indexOf(tab)} + index={tabs().all().indexOf(tab)} temporary={temporaryTab() === tab} onTabClose={tabs().close} onTabDoubleClick={temporaryTab() === tab ? openTab : undefined} @@ -691,12 +691,12 @@ export function SessionSidePanel(props: {
- +
@@ -782,11 +782,11 @@ export function SessionSidePanel(props: { when={settings.general.newLayoutDesigns()} fallback={ <> - {props.reviewCount()} {language.plural("session.review.change", props.reviewCount())} + {props.reviewCount} {language.plural("session.review.change", props.reviewCount)} } > - {language.t("session.review.filesChanged", { count: props.reviewCount() })} + {language.t("session.review.filesChanged", { count: props.reviewCount })} @@ -796,9 +796,9 @@ export function SessionSidePanel(props: { - + {language.t("common.loading")} diff --git a/packages/app/src/pages/session/terminal-panel-v2.tsx b/packages/app/src/pages/session/terminal-panel-v2.tsx index 6a92e24ea61..9622525aaa9 100644 --- a/packages/app/src/pages/session/terminal-panel-v2.tsx +++ b/packages/app/src/pages/session/terminal-panel-v2.tsx @@ -273,7 +273,7 @@ export function TerminalPanelV2(props: { stacked?: boolean } = {}) { > {(pty, index) => ( - + )}
diff --git a/packages/app/src/pages/session/timeline/message-timeline.tsx b/packages/app/src/pages/session/timeline/message-timeline.tsx index bd3195bafd6..525863fc8d0 100644 --- a/packages/app/src/pages/session/timeline/message-timeline.tsx +++ b/packages/app/src/pages/session/timeline/message-timeline.tsx @@ -396,11 +396,11 @@ type MessageTimelineProps = { onScheduleScrollState: (el: HTMLDivElement) => void onAutoScrollHandleScroll: () => void onMarkScrollGesture: (target?: EventTarget | null) => void - hasScrollGesture: () => boolean + hasScrollGesture: boolean onUserScroll: () => void onHistoryScroll: () => void onAutoScrollInteraction: (event: MouseEvent) => void - shouldAnchorBottom: () => boolean + shouldAnchorBottom: boolean centered: boolean setContentRef: (el: HTMLDivElement) => void userMessages: UserMessage[] @@ -437,7 +437,7 @@ function MessageTimelineView( const ownerSessionKey = props.data.sessionKey() const cached = timelineCache.get(ownerSessionKey) const initialMeasurements = cached?.measurements - const coldBottomMount = !initialMeasurements?.length && props.shouldAnchorBottom() + const coldBottomMount = !initialMeasurements?.length && props.shouldAnchorBottom const [listRoot, setListRoot] = createSignal() const sessionID = props.data.sessionID @@ -601,7 +601,7 @@ function MessageTimelineView( }, getScrollElement: () => listRoot() ?? null, observeElementOffset: observeElementOffsetReconnectAware, - initialOffset: () => (props.shouldAnchorBottom() ? Number.MAX_SAFE_INTEGER : 0), + initialOffset: () => (props.shouldAnchorBottom ? Number.MAX_SAFE_INTEGER : 0), initialMeasurementsCache: initialMeasurements, estimateSize: () => timelineFallbackItemSize, scrollToFn: (offset, options, instance) => { @@ -639,11 +639,11 @@ function MessageTimelineView( const resizeItem = virtualizer.resizeItem let resizeAnchorScheduled = false const anchorResizedBottom = () => { - if (resizeAnchorScheduled || props.hasScrollGesture()) return + if (resizeAnchorScheduled || props.hasScrollGesture) return resizeAnchorScheduled = true queueMicrotask(() => { resizeAnchorScheduled = false - if (!props.shouldAnchorBottom() || props.hasScrollGesture()) return + if (!props.shouldAnchorBottom || props.hasScrollGesture) return virtualizer.scrollToEnd() }) } @@ -668,10 +668,10 @@ function MessageTimelineView( }) } resizeItem(index, size) - if (root && props.shouldAnchorBottom()) anchorResizedBottom() + if (root && props.shouldAnchorBottom) anchorResizedBottom() } virtualizer.shouldAdjustScrollPositionOnItemSizeChange = (item) => { - if (props.shouldAnchorBottom()) return false + if (props.shouldAnchorBottom) return false const first = virtualizer.range?.startIndex return first !== undefined && item.index < first } @@ -692,18 +692,18 @@ function MessageTimelineView( let overscanFrame: number | undefined onMount(() => { overscanFrame = requestAnimationFrame(() => { - if (props.shouldAnchorBottom()) virtualizer.scrollToEnd() + if (props.shouldAnchorBottom) virtualizer.scrollToEnd() overscanFrame = requestAnimationFrame(() => { overscanFrame = undefined if (renderOverscan() < 20) setRenderOverscan(20) - if (props.shouldAnchorBottom()) virtualizer.scrollToEnd() + if (props.shouldAnchorBottom) virtualizer.scrollToEnd() }) }) }) const maybeAnchorBottom = () => { if (timelineRows().length === 0) return - if (!props.shouldAnchorBottom() || props.hasScrollGesture()) return + if (!props.shouldAnchorBottom || props.hasScrollGesture) return if (resizePinFrame !== undefined) cancelAnimationFrame(resizePinFrame) clearPrependAnchor() if (prependAnchorFrame !== undefined) cancelAnimationFrame(prependAnchorFrame) @@ -815,7 +815,7 @@ function MessageTimelineView( if (prependLoading) updatePrependAnchor() props.onScheduleScrollState(event.currentTarget) props.onHistoryScroll() - if (!props.hasScrollGesture()) return + if (!props.hasScrollGesture) return props.onUserScroll() props.onAutoScrollHandleScroll() props.onMarkScrollGesture(event.currentTarget) @@ -972,21 +972,21 @@ function MessageTimelineView( ) } - function TimelineRowFrame(input: { row: Accessor; children: JSX.Element }) { + function TimelineRowFrame(input: { row: FramedTimelineRow; children: JSX.Element }) { const anchor = () => { - const row = input.row() + const row = input.row return row._tag === "CommentStrip" || (row._tag === "UserMessage" && row.anchor) } const previousAssistantPart = () => { - const row = input.row() + const row = input.row return row._tag === "AssistantPart" && row.previousAssistantPart } return (
MessageComment.fromPart(part) ?? []), ) return ( - +
@@ -1060,7 +1060,7 @@ function MessageTimelineView( return getMsgParts(userMessageRow().userMessageID).flatMap((part) => MessageComment.fromPart(part) ?? []) }) return ( - + {(message) => (
@@ -1091,7 +1091,7 @@ function MessageTimelineView( } const directory = () => getFilename(operation().directory) return ( - +
> return ( - +
> return ( - +
> return ( - +
> return ( - +
@@ -1187,7 +1187,7 @@ function MessageTimelineView( sync().project?.vcs === "git" && sessionStatus().type === "idle" return ( - +
> return ( - +
{errorRow().text} diff --git a/packages/app/src/pages/session/v2/review-panel-v2.tsx b/packages/app/src/pages/session/v2/review-panel-v2.tsx index b29afdd6e81..3b890c6fd89 100644 --- a/packages/app/src/pages/session/v2/review-panel-v2.tsx +++ b/packages/app/src/pages/session/v2/review-panel-v2.tsx @@ -35,8 +35,8 @@ type ReviewDiff = FileDiffInfo export type ReviewPanelV2Props = { title?: JSX.Element empty?: JSX.Element - diffs: () => ReviewDiff[] - diffsReady: () => boolean + diffs: ReviewDiff[] + diffsReady: boolean diffVersion?: number loadDiff?: (path: string, version?: number) => Promise activeFile?: string @@ -56,7 +56,7 @@ export type ReviewPanelV2Props = { export function ReviewPanelV2(props: ReviewPanelV2Props) { const sdk = useSDK() - const diffs = createMemo(() => props.diffs().filter(filterRenderableDiff)) + const diffs = createMemo(() => props.diffs.filter(filterRenderableDiff)) const filteredFiles = createMemo(() => filterReviewFiles( diffs().map((diff) => diff.file), @@ -122,11 +122,11 @@ export function ReviewPanelV2(props: ReviewPanelV2Props) { state={props.state} diffsReady={props.diffsReady} onSelectFile={props.onSelectFile} - diffs={diffs} - filteredFiles={filteredFiles} - searching={searching} - kinds={treeKinds} - activeDiff={activeDiff} + diffs={diffs()} + filteredFiles={filteredFiles()} + searching={searching()} + kinds={treeKinds()} + activeDiff={activeDiff()} /> } activeFile={activeDiff()} @@ -170,19 +170,19 @@ export function ReviewPanelV2(props: ReviewPanelV2Props) { function ReviewPanelV2Sidebar(props: { title?: JSX.Element state: ReviewPanelV2State - diffsReady: () => boolean + diffsReady: boolean onSelectFile: (path: string) => void - diffs: () => RenderDiff[] - filteredFiles: () => string[] - searching: () => boolean - kinds: () => ReturnType - activeDiff: () => string | undefined + diffs: RenderDiff[] + filteredFiles: string[] + searching: boolean + kinds: ReturnType + activeDiff: string | undefined }) { const language = useLanguage() const [explicitHighlight, setExplicitHighlight] = createSignal() const highlightedPath = createMemo(() => { - if (!props.searching()) return undefined - const files = props.filteredFiles() + if (!props.searching) return undefined + const files = props.filteredFiles if (files.length === 0) return undefined const explicit = explicitHighlight() if (explicit && files.includes(explicit)) return explicit @@ -190,8 +190,8 @@ function ReviewPanelV2Sidebar(props: { }) const onFilterKeyDown = (event: KeyboardEvent & { currentTarget: HTMLInputElement }) => { - if (!props.searching()) return - applyFileListKeyDown(event, props.filteredFiles(), highlightedPath(), { + if (!props.searching) return + applyFileListKeyDown(event, props.filteredFiles, highlightedPath(), { onHighlight: setExplicitHighlight, onSelect: props.onSelectFile, }) @@ -202,7 +202,7 @@ function ReviewPanelV2Sidebar(props: { open={props.state.sidebarOpened()} transition={props.state.sidebarTransition()} title={props.title} - stats={} + stats={} filter={props.state.filter()} onFilterChange={props.state.setFilter} onFilterKeyDown={onFilterKeyDown} @@ -212,7 +212,7 @@ function ReviewPanelV2Sidebar(props: { maxWidth={SESSION_REVIEW_V2_SIDEBAR_WIDTH_MAX} > {language.t("common.loading")} @@ -221,25 +221,25 @@ function ReviewPanelV2Sidebar(props: { } > props.onSelectFile(node.path)} /> } > 0} + when={props.filteredFiles.length > 0} fallback={
{language.t("palette.empty")}
} > { setExplicitHighlight(path) diff --git a/packages/app/src/pages/target-session-route.tsx b/packages/app/src/pages/target-session-route.tsx index e8fa16a0c0d..c736709b4c7 100644 --- a/packages/app/src/pages/target-session-route.tsx +++ b/packages/app/src/pages/target-session-route.tsx @@ -17,8 +17,8 @@ export default function TargetSessionRoute() { return ( - - + + diff --git a/packages/session-ui/src/components/file-search.tsx b/packages/session-ui/src/components/file-search.tsx index 181ff9fea8f..e00ea929839 100644 --- a/packages/session-ui/src/components/file-search.tsx +++ b/packages/session-ui/src/components/file-search.tsx @@ -3,10 +3,10 @@ import { useI18n } from "@opencode-ai/ui/context/i18n" import { Icon } from "@opencode-ai/ui/icon" export function FileSearchBar(props: { - pos: () => { top: number; right: number } - query: () => string - index: () => number - count: () => number + pos: { top: number; right: number } + query: string + index: number + count: number setInput: (el: HTMLInputElement) => void onInput: (value: string) => void onKeyDown: (event: KeyboardEvent) => void @@ -21,8 +21,8 @@ export function FileSearchBar(props: {
e.stopPropagation()} > @@ -30,19 +30,19 @@ export function FileSearchBar(props: { props.onInput(e.currentTarget.value)} onKeyDown={(e) => props.onKeyDown(e as KeyboardEvent)} />
- {props.count() ? `${props.index() + 1}/${props.count()}` : "0/0"} + {props.count ? `${props.index + 1}/${props.count}` : "0/0"}
- {loaded()} • {session().title || session().id} • {state.messages.length} message + {loaded()} • {session.title || session.id} • {state.messages.length} message {state.messages.length === 1 ? "" : "s"}
@@ -2048,7 +2050,7 @@ function Playground() { {(msg) => (
- {reducedMotion() && ( + - )} + {/* Matches context-tool-group-trigger layout from message-part.tsx */} { return (
- {samples.map((item) => ( - - ))} + {(item) => }
) }, diff --git a/packages/ui/src/components/app-icon.stories.tsx b/packages/ui/src/components/app-icon.stories.tsx index 24460b6da22..66e516e4226 100644 --- a/packages/ui/src/components/app-icon.stories.tsx +++ b/packages/ui/src/components/app-icon.stories.tsx @@ -1,4 +1,5 @@ // @ts-nocheck +import { For } from "solid-js" import { iconNames } from "./app-icons/types" import * as mod from "./app-icon" import { create } from "../storybook/scaffold" @@ -58,12 +59,14 @@ export const AllIcons = { "grid-template-columns": "repeat(auto-fill, minmax(72px, 1fr))", }} > - {iconNames.map((id) => ( -
- -
{id}
-
- ))} + + {(id) => ( +
+ +
{id}
+
+ )} +
), } diff --git a/packages/ui/src/components/card.tsx b/packages/ui/src/components/card.tsx index 917644348f8..da82d4f4575 100644 --- a/packages/ui/src/components/card.tsx +++ b/packages/ui/src/components/card.tsx @@ -1,4 +1,4 @@ -import { type ComponentProps, splitProps } from "solid-js" +import { type ComponentProps, Show, splitProps } from "solid-js" import { Icon, type IconProps } from "./icon" type Variant = "normal" | "error" | "warning" | "success" | "info" @@ -80,11 +80,11 @@ export function CardTitle(props: CardTitleProps) { [split.class ?? ""]: !!split.class, }} > - {show() ? ( + - ) : null} + {split.children}
) diff --git a/packages/ui/src/components/file-icon.stories.tsx b/packages/ui/src/components/file-icon.stories.tsx index 937328502f7..e49a43be111 100644 --- a/packages/ui/src/components/file-icon.stories.tsx +++ b/packages/ui/src/components/file-icon.stories.tsx @@ -1,4 +1,5 @@ // @ts-nocheck +import { For } from "solid-js" import * as mod from "./file-icon" import { create } from "../storybook/scaffold" @@ -82,12 +83,14 @@ export const Samples = { "grid-template-columns": "repeat(auto-fill, minmax(120px, 1fr))", }} > - {items.map((node) => ( -
- -
{node.path}
-
- ))} + + {(node) => ( +
+ +
{node.path}
+
+ )} +
) }, diff --git a/packages/ui/src/components/icon.stories.tsx b/packages/ui/src/components/icon.stories.tsx index 60c53129881..6134a0b8ee6 100644 --- a/packages/ui/src/components/icon.stories.tsx +++ b/packages/ui/src/components/icon.stories.tsx @@ -1,4 +1,5 @@ // @ts-nocheck +import { For } from "solid-js" import * as mod from "./icon" import { create } from "../storybook/scaffold" @@ -160,12 +161,14 @@ export const Gallery = { "grid-template-columns": "repeat(auto-fill, minmax(88px, 1fr))", }} > - {names.map((name) => ( -
- -
{name}
-
- ))} + + {(name) => ( +
+ +
{name}
+
+ )} +
), } diff --git a/packages/ui/src/components/list.tsx b/packages/ui/src/components/list.tsx index cc5fc0ce5dc..2863c5593b6 100644 --- a/packages/ui/src/components/list.tsx +++ b/packages/ui/src/components/list.tsx @@ -367,9 +367,11 @@ export function List(props: ListProps & { ref?: (ref: ListRef) => void }) )} - {props.divider && (i() !== group.items.length - 1 || (showAdd() && isLastGroup())) && ( + - )} + ) if (props.itemWrapper) return props.itemWrapper(item, node) diff --git a/packages/ui/src/components/provider-icon.stories.tsx b/packages/ui/src/components/provider-icon.stories.tsx index e7fc39967bf..87d5961812d 100644 --- a/packages/ui/src/components/provider-icon.stories.tsx +++ b/packages/ui/src/components/provider-icon.stories.tsx @@ -1,4 +1,5 @@ // @ts-nocheck +import { For } from "solid-js" import { iconNames } from "./provider-icons/types" import * as mod from "./provider-icon" import { create } from "../storybook/scaffold" @@ -58,12 +59,14 @@ export const AllIcons = { "grid-template-columns": "repeat(auto-fill, minmax(80px, 1fr))", }} > - {iconNames.map((id) => ( -
- -
{id}
-
- ))} + + {(id) => ( +
+ +
{id}
+
+ )} +
), } diff --git a/packages/ui/src/components/scroll-view.tsx b/packages/ui/src/components/scroll-view.tsx index 5116d94f9ea..1e933c34b2c 100644 --- a/packages/ui/src/components/scroll-view.tsx +++ b/packages/ui/src/components/scroll-view.tsx @@ -1,14 +1,4 @@ -import { - createEffect, - createMemo, - mergeProps, - onCleanup, - onMount, - Show, - splitProps, - type Accessor, - type ComponentProps, -} from "solid-js" +import { createEffect, mergeProps, onCleanup, onMount, Show, splitProps, type ComponentProps } from "solid-js" import { Portal } from "solid-js/web" import { createResizeObserver } from "@solid-primitives/resize-observer" import { createStore } from "solid-js/store" @@ -27,9 +17,9 @@ export interface ScrollViewProps extends ComponentProps<"div"> { * */ thumbVisibility?: ScrollViewThumbVisibility /** Mount the thumb into an external track. Scroll metrics still come from this ScrollView. */ - thumbContainer?: HTMLElement | Accessor + thumbContainer?: HTMLElement /** Element whose hover reveals the thumb. Defaults to the ScrollView root when unset. */ - thumbHoverTarget?: HTMLElement | Accessor + thumbHoverTarget?: HTMLElement } export const scrollKey = (event: Pick) => { @@ -128,13 +118,8 @@ export function ScrollView(props: ScrollViewProps) { let viewportRef!: HTMLDivElement let thumbRef!: HTMLDivElement - const resolveEl = (value: HTMLElement | Accessor | undefined) => { - if (typeof value === "function") return value() - return value - } - - const thumbMount = createMemo(() => resolveEl(local.thumbContainer)) - const thumbHover = createMemo(() => resolveEl(local.thumbHoverTarget)) + const thumbMount = () => local.thumbContainer + const thumbHover = () => local.thumbHoverTarget const hoverRoot = () => !local.thumbHoverTarget && !local.thumbContainer const [state, setState] = createStore({ diff --git a/packages/ui/src/components/text-reveal.stories.tsx b/packages/ui/src/components/text-reveal.stories.tsx index af25d190c75..cb64f992ed1 100644 --- a/packages/ui/src/components/text-reveal.stories.tsx +++ b/packages/ui/src/components/text-reveal.stories.tsx @@ -1,5 +1,5 @@ // @ts-nocheck -import { onCleanup } from "solid-js" +import { For, onCleanup } from "solid-js" import { createStore } from "solid-js/store" import { TextReveal } from "./text-reveal" @@ -181,11 +181,13 @@ export const Playground = {
- {TEXTS.map((t, i) => ( - - ))} + + {(t, i) => ( + + )} +
diff --git a/packages/ui/src/components/thinking-heading.stories.tsx b/packages/ui/src/components/thinking-heading.stories.tsx index 12a06b4d833..a2d7fc12ca3 100644 --- a/packages/ui/src/components/thinking-heading.stories.tsx +++ b/packages/ui/src/components/thinking-heading.stories.tsx @@ -1,5 +1,5 @@ // @ts-nocheck -import { createEffect, on, onMount, onCleanup } from "solid-js" +import { createEffect, For, on, onMount, onCleanup } from "solid-js" import { createStore } from "solid-js/store" import { TextShimmer } from "./text-shimmer" import { TextReveal } from "./text-reveal" @@ -667,22 +667,24 @@ export const Playground = {
- {VARIANTS.map((v) => ( -
- {v.label} - - - - + + {(v) => ( +
+ {v.label} + + + + + - -
- ))} +
+ )} +
{/* ── Sliders ──────────────────────────────────────── */} @@ -824,17 +826,19 @@ export const Playground = {
- {HEADINGS.map((h, i) => ( - - ))} + + {(h, i) => ( + + )} +
- {opts.actions!.map((action) => ( - - ))} + + {(action) => ( + + )} + diff --git a/packages/ui/src/v2/components/button-v2.stories.tsx b/packages/ui/src/v2/components/button-v2.stories.tsx index fbb38cc5a22..ca2578257a1 100644 --- a/packages/ui/src/v2/components/button-v2.stories.tsx +++ b/packages/ui/src/v2/components/button-v2.stories.tsx @@ -1,3 +1,4 @@ +import { For } from "solid-js" import { ButtonV2 } from "./button-v2" const docs = `### Overview @@ -123,30 +124,34 @@ export const AllStates = { const toTitleCase = (value: string) => value.charAt(0).toUpperCase() + value.slice(1) return (
- {variants.map((variant) => ( -
-
- {variant} + + {(variant) => ( +
+
+ {variant} +
+
+ + {(state) => ( + + {toTitleCase(state)} + + )} + +
-
- {states.map((state) => ( - - {toTitleCase(state)} - - ))} -
-
- ))} + )} +
) }, diff --git a/packages/ui/src/v2/components/dialog-v2.tsx b/packages/ui/src/v2/components/dialog-v2.tsx index 8f083f92968..0094f6f72e4 100644 --- a/packages/ui/src/v2/components/dialog-v2.tsx +++ b/packages/ui/src/v2/components/dialog-v2.tsx @@ -59,7 +59,7 @@ export function DialogHeader(props: DialogHeaderProps) { return (
{local.children} - {!hideClose() && ( + - )} +
) } diff --git a/packages/ui/src/v2/components/icon-button-v2.stories.tsx b/packages/ui/src/v2/components/icon-button-v2.stories.tsx index e32100103f9..eb6f3107fa3 100644 --- a/packages/ui/src/v2/components/icon-button-v2.stories.tsx +++ b/packages/ui/src/v2/components/icon-button-v2.stories.tsx @@ -1,3 +1,4 @@ +import { For } from "solid-js" import { IconButtonV2 } from "./icon-button-v2" const docs = `### Overview @@ -82,23 +83,27 @@ export const AllStates = { return (
- {variants.map((variant) => ( -
-
- {variant} + + {(variant) => ( +
+
+ {variant} +
+
+ + {(state) => ( + + )} + +
-
- {states.map((state) => ( - - ))} -
-
- ))} + )} +
) }, diff --git a/packages/ui/src/v2/components/toast-v2.tsx b/packages/ui/src/v2/components/toast-v2.tsx index dccf3034fc8..ae757e0d138 100644 --- a/packages/ui/src/v2/components/toast-v2.tsx +++ b/packages/ui/src/v2/components/toast-v2.tsx @@ -239,6 +239,8 @@ function createToastV2Actions(entry: ActiveToastV2) { if (!entry.options.actions?.length) return undefined return ( + {/* Static map, not : this JSX is created imperatively outside any Solid + root, where a computation would never be disposed. */} {entry.options.actions.map((action, index) => (