Compare commits
2 Commits
dev
...
fix/16323-
| Author | SHA1 | Date |
|---|---|---|
|
|
5ec45c03d6 | |
|
|
05789d29d2 |
|
|
@ -74,7 +74,9 @@ export const DialogSelectProvider: Component = () => {
|
||||||
<Show when={i.id === "opencode"}>
|
<Show when={i.id === "opencode"}>
|
||||||
<Tag>{language.t("dialog.provider.tag.recommended")}</Tag>
|
<Tag>{language.t("dialog.provider.tag.recommended")}</Tag>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={note(i.id)}>{(value) => <div class="text-14-regular text-text-weak">{value()}</div>}</Show>
|
<Show when={note(i.id)} keyed>
|
||||||
|
{(value) => <div class="text-14-regular text-text-weak">{value}</div>}
|
||||||
|
</Show>
|
||||||
<Show when={i.id === "opencode-go"}>
|
<Show when={i.id === "opencode-go"}>
|
||||||
<Tag>{language.t("dialog.provider.tag.recommended")}</Tag>
|
<Tag>{language.t("dialog.provider.tag.recommended")}</Tag>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
|
||||||
|
|
@ -77,10 +77,10 @@ export const ModelTooltip: Component<{ model: ModelInfo; latest?: boolean; free?
|
||||||
return (
|
return (
|
||||||
<div class="flex flex-col gap-1 py-1">
|
<div class="flex flex-col gap-1 py-1">
|
||||||
<div class="text-13-medium">{title()}</div>
|
<div class="text-13-medium">{title()}</div>
|
||||||
<Show when={inputs()}>
|
<Show when={inputs()} keyed>
|
||||||
{(value) => (
|
{(value) => (
|
||||||
<div class="text-12-regular text-text-invert-base">
|
<div class="text-12-regular text-text-invert-base">
|
||||||
{language.t("model.tooltip.allows", { inputs: value() })}
|
{language.t("model.tooltip.allows", { inputs: value })}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Show>
|
</Show>
|
||||||
|
|
|
||||||
|
|
@ -52,12 +52,10 @@ export const PromptContextItems: Component<ContextItemsProps> = (props) => {
|
||||||
<FileIcon node={{ path: item.path, type: "file" }} class="shrink-0 size-3.5" />
|
<FileIcon node={{ path: item.path, type: "file" }} class="shrink-0 size-3.5" />
|
||||||
<div class="flex items-center text-11-regular min-w-0 font-medium">
|
<div class="flex items-center text-11-regular min-w-0 font-medium">
|
||||||
<span class="text-text-strong whitespace-nowrap">{label}</span>
|
<span class="text-text-strong whitespace-nowrap">{label}</span>
|
||||||
<Show when={item.selection}>
|
<Show when={item.selection} keyed>
|
||||||
{(sel) => (
|
{(sel) => (
|
||||||
<span class="text-text-weak whitespace-nowrap shrink-0">
|
<span class="text-text-weak whitespace-nowrap shrink-0">
|
||||||
{sel().startLine === sel().endLine
|
{sel.startLine === sel.endLine ? `:${sel.startLine}` : `:${sel.startLine}-${sel.endLine}`}
|
||||||
? `:${sel().startLine}`
|
|
||||||
: `:${sel().startLine}-${sel().endLine}`}
|
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</Show>
|
</Show>
|
||||||
|
|
@ -74,8 +72,8 @@ export const PromptContextItems: Component<ContextItemsProps> = (props) => {
|
||||||
aria-label={props.t("prompt.context.removeFile")}
|
aria-label={props.t("prompt.context.removeFile")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Show when={item.comment}>
|
<Show when={item.comment} keyed>
|
||||||
{(comment) => <div class="text-12-regular text-text-strong ml-5 pr-1 truncate">{comment()}</div>}
|
{(comment) => <div class="text-12-regular text-text-strong ml-5 pr-1 truncate">{comment}</div>}
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,7 @@ export function ServerRow(props: ServerRowProps) {
|
||||||
</span>
|
</span>
|
||||||
<Show
|
<Show
|
||||||
when={badge()}
|
when={badge()}
|
||||||
|
keyed
|
||||||
fallback={
|
fallback={
|
||||||
<Show when={props.status?.version}>
|
<Show when={props.status?.version}>
|
||||||
<span ref={versionRef} class={props.versionClass ?? "text-text-weak text-14-regular truncate"}>
|
<span ref={versionRef} class={props.versionClass ?? "text-text-weak text-14-regular truncate"}>
|
||||||
|
|
@ -86,20 +87,20 @@ export function ServerRow(props: ServerRowProps) {
|
||||||
</Show>
|
</Show>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{(badge) => badge()}
|
{(badge) => badge}
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
<Show when={props.showCredentials && props.conn.type === "http" && props.conn}>
|
<Show when={props.showCredentials && props.conn.type === "http" && props.conn} keyed>
|
||||||
{(conn) => (
|
{(conn) => (
|
||||||
<div class="flex flex-row gap-3">
|
<div class="flex flex-row gap-3">
|
||||||
<span>
|
<span>
|
||||||
{conn().http.username ? (
|
{conn.http.username ? (
|
||||||
<span class="text-text-weak">{conn().http.username}</span>
|
<span class="text-text-weak">{conn.http.username}</span>
|
||||||
) : (
|
) : (
|
||||||
<span class="text-text-weaker">no username</span>
|
<span class="text-text-weaker">no username</span>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
{conn().http.password && <span class="text-text-weak">••••••••</span>}
|
{conn.http.password && <span class="text-text-weak">••••••••</span>}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Show>
|
</Show>
|
||||||
|
|
|
||||||
|
|
@ -73,15 +73,15 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
|
||||||
|
|
||||||
const tooltipValue = () => (
|
const tooltipValue = () => (
|
||||||
<div>
|
<div>
|
||||||
<Show when={context()}>
|
<Show when={context()} keyed>
|
||||||
{(ctx) => (
|
{(ctx) => (
|
||||||
<>
|
<>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="text-text-invert-strong">{ctx().total.toLocaleString(language.intl())}</span>
|
<span class="text-text-invert-strong">{ctx.total.toLocaleString(language.intl())}</span>
|
||||||
<span class="text-text-invert-base">{language.t("context.usage.tokens")}</span>
|
<span class="text-text-invert-base">{language.t("context.usage.tokens")}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="text-text-invert-strong">{ctx().usage ?? 0}%</span>
|
<span class="text-text-invert-strong">{ctx.usage ?? 0}%</span>
|
||||||
<span class="text-text-invert-base">{language.t("context.usage.usage")}</span>
|
<span class="text-text-invert-base">{language.t("context.usage.usage")}</span>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -316,12 +316,12 @@ export function SessionContextTab() {
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={systemPrompt()}>
|
<Show when={systemPrompt()} keyed>
|
||||||
{(prompt) => (
|
{(prompt) => (
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<div class="text-12-regular text-text-weak">{language.t("context.systemPrompt.title")}</div>
|
<div class="text-12-regular text-text-weak">{language.t("context.systemPrompt.title")}</div>
|
||||||
<div class="border border-border-base rounded-md bg-surface-base px-3 py-2">
|
<div class="border border-border-base rounded-md bg-surface-base px-3 py-2">
|
||||||
<Markdown text={prompt()} class="text-12-regular" />
|
<Markdown text={prompt} class="text-12-regular" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -356,9 +356,9 @@ export function SessionHeader() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Show when={centerMount()}>
|
<Show when={centerMount()} keyed>
|
||||||
{(mount) => (
|
{(mount) => (
|
||||||
<Portal mount={mount()}>
|
<Portal mount={mount}>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
|
@ -376,18 +376,16 @@ export function SessionHeader() {
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Show when={hotkey()}>
|
<Show when={hotkey()} keyed>
|
||||||
{(keybind) => (
|
{(keybind) => <Keybind class="shrink-0 !border-0 !bg-transparent !shadow-none px-0">{keybind}</Keybind>}
|
||||||
<Keybind class="shrink-0 !border-0 !bg-transparent !shadow-none px-0">{keybind()}</Keybind>
|
|
||||||
)}
|
|
||||||
</Show>
|
</Show>
|
||||||
</Button>
|
</Button>
|
||||||
</Portal>
|
</Portal>
|
||||||
)}
|
)}
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={rightMount()}>
|
<Show when={rightMount()} keyed>
|
||||||
{(mount) => (
|
{(mount) => (
|
||||||
<Portal mount={mount()}>
|
<Portal mount={mount}>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<StatusPopover />
|
<StatusPopover />
|
||||||
<Show when={projectDirectory()}>
|
<Show when={projectDirectory()}>
|
||||||
|
|
|
||||||
|
|
@ -62,14 +62,14 @@ export function NewSessionView(props: NewSessionViewProps) {
|
||||||
<Icon name="branch" size="small" class="mt-0.5 shrink-0" />
|
<Icon name="branch" size="small" class="mt-0.5 shrink-0" />
|
||||||
<div class="text-12-medium text-text-weak select-text leading-5">{label(current())}</div>
|
<div class="text-12-medium text-text-weak select-text leading-5">{label(current())}</div>
|
||||||
</div>
|
</div>
|
||||||
<Show when={sync.project}>
|
<Show when={sync.project} keyed>
|
||||||
{(project) => (
|
{(project) => (
|
||||||
<div class="flex justify-center items-start gap-3 min-h-5">
|
<div class="flex justify-center items-start gap-3 min-h-5">
|
||||||
<Icon name="pencil-line" size="small" class="mt-0.5 shrink-0" />
|
<Icon name="pencil-line" size="small" class="mt-0.5 shrink-0" />
|
||||||
<div class="text-12-medium text-text-weak leading-5">
|
<div class="text-12-medium text-text-weak leading-5">
|
||||||
{language.t("session.new.lastModified")}
|
{language.t("session.new.lastModified")}
|
||||||
<span class="text-text-strong">
|
<span class="text-text-strong">
|
||||||
{DateTime.fromMillis(project().time.updated ?? project().time.created)
|
{DateTime.fromMillis(project.time.updated ?? project.time.created)
|
||||||
.setLocale(language.intl())
|
.setLocale(language.intl())
|
||||||
.toRelative()}
|
.toRelative()}
|
||||||
</span>
|
</span>
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,9 @@ export function SortableTab(props: { tab: string; onTabClose: (tab: string) => v
|
||||||
hideCloseButton
|
hideCloseButton
|
||||||
onMiddleClick={() => props.onTabClose(props.tab)}
|
onMiddleClick={() => props.onTabClose(props.tab)}
|
||||||
>
|
>
|
||||||
<Show when={content()}>{(value) => value()}</Show>
|
<Show when={content()} keyed>
|
||||||
|
{(value) => value}
|
||||||
|
</Show>
|
||||||
</Tabs.Trigger>
|
</Tabs.Trigger>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -473,7 +473,7 @@ export const SettingsGeneral: Component = () => {
|
||||||
|
|
||||||
<SoundsSection />
|
<SoundsSection />
|
||||||
|
|
||||||
{/*<Show when={platform.platform === "desktop" && platform.os === "windows" && platform.getWslEnabled}>
|
{/*<Show when={platform.platform === "desktop" && platform.os === "windows" && platform.getWslEnabled} keyed>
|
||||||
{(_) => {
|
{(_) => {
|
||||||
const [enabledResource, actions] = createResource(() => platform.getWslEnabled?.())
|
const [enabledResource, actions] = createResource(() => platform.getWslEnabled?.())
|
||||||
const enabled = () => (enabledResource.state === "pending" ? undefined : enabledResource.latest)
|
const enabled = () => (enabledResource.state === "pending" ? undefined : enabledResource.latest)
|
||||||
|
|
@ -503,7 +503,7 @@ export const SettingsGeneral: Component = () => {
|
||||||
|
|
||||||
<UpdatesSection />
|
<UpdatesSection />
|
||||||
|
|
||||||
<Show when={linux()}>
|
<Show when={linux()} keyed>
|
||||||
{(_) => {
|
{(_) => {
|
||||||
const [valueResource, actions] = createResource(() => platform.getDisplayBackend?.())
|
const [valueResource, actions] = createResource(() => platform.getDisplayBackend?.())
|
||||||
const value = () => (valueResource.state === "pending" ? undefined : valueResource.latest)
|
const value = () => (valueResource.state === "pending" ? undefined : valueResource.latest)
|
||||||
|
|
|
||||||
|
|
@ -189,8 +189,8 @@ export const SettingsProviders: Component = () => {
|
||||||
<Tag>{language.t("dialog.provider.tag.recommended")}</Tag>
|
<Tag>{language.t("dialog.provider.tag.recommended")}</Tag>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
<Show when={note(item.id)}>
|
<Show when={note(item.id)} keyed>
|
||||||
{(key) => <span class="text-12-regular text-text-weak pl-8">{language.t(key())}</span>}
|
{(key) => <span class="text-12-regular text-text-weak pl-8">{language.t(key)}</span>}
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
|
|
|
||||||
|
|
@ -290,8 +290,8 @@ export const ErrorPage: Component<ErrorPageProps> = (props) => {
|
||||||
</Show>
|
</Show>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
<Show when={store.actionError}>
|
<Show when={store.actionError} keyed>
|
||||||
{(message) => <p class="text-xs text-text-danger-base text-center max-w-2xl">{message()}</p>}
|
{(message) => <p class="text-xs text-text-danger-base text-center max-w-2xl">{message}</p>}
|
||||||
</Show>
|
</Show>
|
||||||
<div class="flex flex-col items-center gap-2">
|
<div class="flex flex-col items-center gap-2">
|
||||||
<div class="flex items-center justify-center gap-1">
|
<div class="flex items-center justify-center gap-1">
|
||||||
|
|
@ -305,10 +305,8 @@ export const ErrorPage: Component<ErrorPageProps> = (props) => {
|
||||||
<Icon name="discord" class="text-text-interactive-base" />
|
<Icon name="discord" class="text-text-interactive-base" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<Show when={platform.version}>
|
<Show when={platform.version} keyed>
|
||||||
{(version) => (
|
{(version) => <p class="text-xs text-text-weak">{language.t("error.page.version", { version })}</p>}
|
||||||
<p class="text-xs text-text-weak">{language.t("error.page.version", { version: version() })}</p>
|
|
||||||
)}
|
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -43,10 +43,10 @@ export const ProjectDragOverlay = (props: {
|
||||||
}): JSX.Element => {
|
}): JSX.Element => {
|
||||||
const project = createMemo(() => props.projects().find((p) => p.worktree === props.activeProject()))
|
const project = createMemo(() => props.projects().find((p) => p.worktree === props.activeProject()))
|
||||||
return (
|
return (
|
||||||
<Show when={project()}>
|
<Show when={project()} keyed>
|
||||||
{(p) => (
|
{(p) => (
|
||||||
<div class="bg-background-base rounded-xl p-1">
|
<div class="bg-background-base rounded-xl p-1">
|
||||||
<ProjectIcon project={p()} />
|
<ProjectIcon project={p} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Show>
|
</Show>
|
||||||
|
|
|
||||||
|
|
@ -76,8 +76,8 @@ export const WorkspaceDragOverlay = (props: {
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Show when={label()}>
|
<Show when={label()} keyed>
|
||||||
{(value) => <div class="bg-background-base rounded-md px-2 py-1 text-14-medium text-text-strong">{value()}</div>}
|
{(value) => <div class="bg-background-base rounded-md px-2 py-1 text-14-medium text-text-strong">{value}</div>}
|
||||||
</Show>
|
</Show>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -731,12 +731,12 @@ export function MessageTimeline(props: {
|
||||||
class="size-3.5 shrink-0"
|
class="size-3.5 shrink-0"
|
||||||
/>
|
/>
|
||||||
<span class="truncate">{getFilename(comment().path)}</span>
|
<span class="truncate">{getFilename(comment().path)}</span>
|
||||||
<Show when={comment().selection}>
|
<Show when={comment().selection} keyed>
|
||||||
{(selection) => (
|
{(selection) => (
|
||||||
<span class="shrink-0 text-text-weak">
|
<span class="shrink-0 text-text-weak">
|
||||||
{selection().startLine === selection().endLine
|
{selection.startLine === selection.endLine
|
||||||
? `:${selection().startLine}`
|
? `:${selection.startLine}`
|
||||||
: `:${selection().startLine}-${selection().endLine}`}
|
: `:${selection.startLine}-${selection.endLine}`}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</Show>
|
</Show>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
import { describe, test } from "bun:test"
|
||||||
|
import { join, relative, resolve } from "node:path"
|
||||||
|
import * as ts from "typescript"
|
||||||
|
|
||||||
|
const scan = async (dir: string) =>
|
||||||
|
Promise.all(
|
||||||
|
Array.from(new Bun.Glob("**/*.tsx").scanSync({ cwd: dir })).map(async (file) => {
|
||||||
|
const full = join(dir, file)
|
||||||
|
return {
|
||||||
|
file: full,
|
||||||
|
text: await Bun.file(full).text(),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const find = (file: string, text: string, root: string) => {
|
||||||
|
const hits: string[] = []
|
||||||
|
const source = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX)
|
||||||
|
|
||||||
|
const walk = (node: ts.Node): void => {
|
||||||
|
if (ts.isJsxElement(node) && node.openingElement.tagName.getText(source) === "Show") {
|
||||||
|
const keyed = node.openingElement.attributes.properties.some(
|
||||||
|
(prop) => ts.isJsxAttribute(prop) && prop.name.getText(source) === "keyed",
|
||||||
|
)
|
||||||
|
const child = node.children.find((child) => {
|
||||||
|
if (ts.isJsxText(child)) return child.getText(source).trim() !== ""
|
||||||
|
if (ts.isJsxExpression(child)) return child.expression !== undefined
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
if (
|
||||||
|
!keyed &&
|
||||||
|
child &&
|
||||||
|
ts.isJsxExpression(child) &&
|
||||||
|
child.expression &&
|
||||||
|
ts.isArrowFunction(child.expression) &&
|
||||||
|
child.expression.parameters.length > 0
|
||||||
|
) {
|
||||||
|
hits.push(`${relative(root, file)}:${source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ts.forEachChild(node, walk)
|
||||||
|
}
|
||||||
|
|
||||||
|
walk(source)
|
||||||
|
return hits
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("show keyed guard", () => {
|
||||||
|
test("app and desktop show callbacks are keyed", async () => {
|
||||||
|
const root = resolve(import.meta.dir, "../../..")
|
||||||
|
const hits = (await Promise.all([scan(import.meta.dir), scan(resolve(import.meta.dir, "../../desktop/src"))]))
|
||||||
|
.flat()
|
||||||
|
.flatMap((item) => find(item.file, item.text, root))
|
||||||
|
|
||||||
|
if (hits.length > 0) {
|
||||||
|
throw new Error(`non-keyed <Show> callbacks found:\n${hits.join("\n")}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -482,6 +482,7 @@ function ServerGate(props: { children: (data: ServerReadyData) => JSX.Element })
|
||||||
return (
|
return (
|
||||||
<Show
|
<Show
|
||||||
when={serverData.state !== "pending" && serverData()}
|
when={serverData.state !== "pending" && serverData()}
|
||||||
|
keyed
|
||||||
fallback={
|
fallback={
|
||||||
<div class="h-screen w-screen flex flex-col items-center justify-center bg-background-base">
|
<div class="h-screen w-screen flex flex-col items-center justify-center bg-background-base">
|
||||||
<Splash class="w-16 h-20 opacity-50 animate-pulse" />
|
<Splash class="w-16 h-20 opacity-50 animate-pulse" />
|
||||||
|
|
@ -489,7 +490,7 @@ function ServerGate(props: { children: (data: ServerReadyData) => JSX.Element })
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{(data) => props.children(data())}
|
{(data) => props.children(data)}
|
||||||
</Show>
|
</Show>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue