Compare commits

...

4 Commits

Author SHA1 Message Date
Claude 10516cc4cb
chore: update bun.lock after dependency install
https://claude.ai/code/session_011xS58qyP1BjcuCrJcWPxQB
2026-03-30 12:25:10 +00:00
Claude e38e24f743
fix: remove invalid alt/meta properties from KeyEvent check
KeyEvent only has ctrl, not alt or meta. Simplify the modifier check.

https://claude.ai/code/session_011xS58qyP1BjcuCrJcWPxQB
2026-03-30 12:24:41 +00:00
Claude 8bff03f833
fix: show number labels consistently for current item in variant popover
When an item is the current selection, show the number gutter instead of
replacing it with the ● bullet, so users always see which key to press.

https://claude.ai/code/session_011xS58qyP1BjcuCrJcWPxQB
2026-03-30 12:23:47 +00:00
Claude 0b6d955560
feat: add number key shortcuts to variant popover
Add numbered selection (1-9) to the variant dialog so users can quickly
pick a variant by pressing its number key. Also hide the search filter
input since there are typically only a few variants. Up/down navigation
and Enter selection still work as before.

https://claude.ai/code/session_011xS58qyP1BjcuCrJcWPxQB
2026-03-30 12:06:34 +00:00
3 changed files with 75 additions and 33 deletions

View File

@ -1915,7 +1915,7 @@
"@solidjs/router": ["@solidjs/router@0.15.4", "", { "peerDependencies": { "solid-js": "^1.8.6" } }, "sha512-WOpgg9a9T638cR+5FGbFi/IV4l2FpmBs1GpIMSPa0Ce9vyJN7Wts+X2PqMf9IYn0zUj2MlSJtm1gp7/HI/n5TQ=="],
"@solidjs/start": ["@solidjs/start@https://pkg.pr.new/@solidjs/start@dfb2020", { "dependencies": { "@babel/core": "^7.28.3", "@babel/traverse": "^7.28.3", "@babel/types": "^7.28.5", "@solidjs/meta": "^0.29.4", "@tanstack/server-functions-plugin": "1.134.5", "@types/babel__traverse": "^7.28.0", "@types/micromatch": "^4.0.9", "cookie-es": "^2.0.0", "defu": "^6.1.4", "error-stack-parser": "^2.1.4", "es-module-lexer": "^1.7.0", "esbuild": "^0.25.3", "fast-glob": "^3.3.3", "h3": "npm:h3@2.0.1-rc.4", "html-to-image": "^1.11.13", "micromatch": "^4.0.8", "path-to-regexp": "^8.2.0", "pathe": "^2.0.3", "radix3": "^1.1.2", "seroval": "^1.3.2", "seroval-plugins": "^1.2.1", "shiki": "^1.26.1", "solid-js": "^1.9.9", "source-map-js": "^1.2.1", "srvx": "^0.9.1", "terracotta": "^1.0.6", "vite": "7.1.10", "vite-plugin-solid": "^2.11.9", "vitest": "^4.0.10" } }, "sha512-7JjjA49VGNOsMRI8QRUhVudZmv0CnJ18SliSgK1ojszs/c3ijftgVkzvXdkSLN4miDTzbkXewf65D6ZBo6W+GQ=="],
"@solidjs/start": ["@solidjs/start@https://pkg.pr.new/@solidjs/start@dfb2020", { "dependencies": { "@babel/core": "^7.28.3", "@babel/traverse": "^7.28.3", "@babel/types": "^7.28.5", "@solidjs/meta": "^0.29.4", "@tanstack/server-functions-plugin": "1.134.5", "@types/babel__traverse": "^7.28.0", "@types/micromatch": "^4.0.9", "cookie-es": "^2.0.0", "defu": "^6.1.4", "error-stack-parser": "^2.1.4", "es-module-lexer": "^1.7.0", "esbuild": "^0.25.3", "fast-glob": "^3.3.3", "h3": "npm:h3@2.0.1-rc.4", "html-to-image": "^1.11.13", "micromatch": "^4.0.8", "path-to-regexp": "^8.2.0", "pathe": "^2.0.3", "radix3": "^1.1.2", "seroval": "^1.3.2", "seroval-plugins": "^1.2.1", "shiki": "^1.26.1", "solid-js": "^1.9.9", "source-map-js": "^1.2.1", "srvx": "^0.9.1", "terracotta": "^1.0.6", "vite": "7.1.10", "vite-plugin-solid": "^2.11.9", "vitest": "^4.0.10" } }],
"@speed-highlight/core": ["@speed-highlight/core@1.2.14", "", {}, "sha512-G4ewlBNhUtlLvrJTb88d2mdy2KRijzs4UhnlrOSRT4bmjh/IqNElZa3zkrZ+TC47TwtlDWzVLFADljF1Ijp5hA=="],
@ -3041,7 +3041,7 @@
"get-tsconfig": ["get-tsconfig@4.13.6", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw=="],
"ghostty-web": ["ghostty-web@github:anomalyco/ghostty-web#4af877d", {}, "anomalyco-ghostty-web-4af877d", "sha512-fbEK8mtr7ar4ySsF+JUGjhaZrane7dKphanN+SxHt5XXI6yLMAh/Hpf6sNCOyyVa2UlGCd7YpXG/T2v2RUAX+A=="],
"ghostty-web": ["ghostty-web@github:anomalyco/ghostty-web#4af877d", {}, "anomalyco-ghostty-web-4af877d"],
"gifwrap": ["gifwrap@0.10.1", "", { "dependencies": { "image-q": "^4.0.0", "omggif": "^1.0.10" } }, "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw=="],

View File

@ -34,6 +34,8 @@ export function DialogVariant() {
title={"Select variant"}
current={local.model.variant.selected()}
flat={true}
hideFilter={true}
numbered={true}
/>
)
}

View File

@ -21,6 +21,8 @@ export interface DialogSelectProps<T> {
onFilter?: (query: string) => void
onSelect?: (option: DialogSelectOption<T>) => void
skipFilter?: boolean
hideFilter?: boolean
numbered?: boolean
keybind?: {
keybind?: Keybind.Info
title: string
@ -194,6 +196,21 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
if (evt.name === "home") moveTo(0)
if (evt.name === "end") moveTo(flat().length - 1)
if (props.numbered && !evt.ctrl) {
const num = parseInt(evt.name, 10)
if (num >= 1 && num <= 9 && num <= flat().length) {
evt.preventDefault()
evt.stopPropagation()
const index = num - 1
const option = flat()[index]
if (option) {
if (option.onSelect) option.onSelect(dialog)
props.onSelect?.(option)
}
return
}
}
if (evt.name === "return") {
const option = selected()
if (option) {
@ -240,29 +257,31 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
esc
</text>
</box>
<box paddingTop={1}>
<input
onInput={(e) => {
batch(() => {
setStore("filter", e)
props.onFilter?.(e)
})
}}
focusedBackgroundColor={theme.backgroundPanel}
cursorColor={theme.primary}
focusedTextColor={theme.textMuted}
ref={(r) => {
input = r
setTimeout(() => {
if (!input) return
if (input.isDestroyed) return
input.focus()
}, 1)
}}
placeholder={props.placeholder ?? "Search"}
placeholderColor={theme.textMuted}
/>
</box>
<Show when={!props.hideFilter}>
<box paddingTop={1}>
<input
onInput={(e) => {
batch(() => {
setStore("filter", e)
props.onFilter?.(e)
})
}}
focusedBackgroundColor={theme.backgroundPanel}
cursorColor={theme.primary}
focusedTextColor={theme.textMuted}
ref={(r) => {
input = r
setTimeout(() => {
if (!input) return
if (input.isDestroyed) return
input.focus()
}, 1)
}}
placeholder={props.placeholder ?? "Search"}
placeholderColor={theme.textMuted}
/>
</box>
</Show>
</box>
<Show
when={grouped().length > 0}
@ -293,6 +312,27 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
{(option) => {
const active = createMemo(() => isDeepEqual(option.value, selected()?.value))
const current = createMemo(() => isDeepEqual(option.value, props.current))
const flatIndex = createMemo(() => flat().findIndex((x) => isDeepEqual(x.value, option.value)))
const numberLabel = createMemo(() => {
if (!props.numbered) return undefined
const idx = flatIndex()
return idx >= 0 && idx < 9 ? `${idx + 1}` : undefined
})
const gutter = createMemo(() => {
if (numberLabel()) {
return (
<box flexDirection="row" flexShrink={0}>
<text
fg={active() ? selectedForeground(theme) : current() ? theme.primary : theme.textMuted}
flexShrink={0}
>
{numberLabel()}
</text>
</box>
)
}
return option.gutter
})
return (
<box
id={JSON.stringify(option.value)}
@ -316,7 +356,7 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
moveTo(index)
}}
backgroundColor={active() ? (option.bg ?? theme.primary) : RGBA.fromInts(0, 0, 0, 0)}
paddingLeft={current() || option.gutter ? 1 : 3}
paddingLeft={current() || gutter() ? 1 : 3}
paddingRight={3}
gap={1}
>
@ -326,7 +366,7 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
description={option.description !== category ? option.description : undefined}
active={active()}
current={current()}
gutter={option.gutter}
gutter={gutter()}
/>
</box>
)
@ -369,16 +409,16 @@ function Option(props: {
return (
<>
<Show when={props.current}>
<text flexShrink={0} fg={props.active ? fg : props.current ? theme.primary : theme.text} marginRight={0}>
</text>
</Show>
<Show when={!props.current && props.gutter}>
<Show when={props.gutter}>
<box flexShrink={0} marginRight={0}>
{props.gutter}
</box>
</Show>
<Show when={props.current && !props.gutter}>
<text flexShrink={0} fg={props.active ? fg : props.current ? theme.primary : theme.text} marginRight={0}>
</text>
</Show>
<text
flexGrow={1}
fg={props.active ? fg : props.current ? theme.primary : theme.text}