fix(app): stabilize server routes and selects

This commit is contained in:
Brendan Allan 2026-07-29 06:55:12 +00:00 committed by opencode-agent[bot]
commit 8be2027a2a
13 changed files with 115 additions and 35 deletions

View file

@ -0,0 +1,12 @@
import { expect, test } from "bun:test"
import { deferSelect } from "./select-defer"
test("defers controlled select updates until the current selection closes", async () => {
const order: string[] = []
deferSelect(() => order.push("update"), "theme")
order.push("close")
expect(order).toEqual(["close"])
await Promise.resolve()
expect(order).toEqual(["close", "update"])
})

View file

@ -0,0 +1,5 @@
export function deferSelect<T>(callback: ((value: T) => void) | undefined, value: T) {
// Kobalte closes the popup after onChange returns. Controlled updates must wait
// so they cannot rebuild the value or options during portal cleanup.
queueMicrotask(() => callback?.(value))
}

View file

@ -3,6 +3,7 @@ import { createMemo, onCleanup, splitProps, type ComponentProps, type JSX } from
import { pipe, groupBy, entries, map } from "remeda"
import { Button, ButtonProps } from "./button"
import { Icon } from "./icon"
import { deferSelect } from "./select-defer"
export type SelectProps<T> = Omit<ComponentProps<typeof Kobalte<T>>, "value" | "onSelect" | "children"> & {
placeholder?: string
@ -124,7 +125,7 @@ export function Select<T>(props: SelectProps<T> & Omit<ButtonProps, "children">)
</Kobalte.Item>
)}
onChange={(v) => {
local.onSelect?.(v ?? undefined)
deferSelect(local.onSelect, v ?? undefined)
stop()
}}
onOpenChange={(open) => {

View file

@ -1,5 +1,6 @@
import { Select as Kobalte } from "@kobalte/core/select"
import { Show, createMemo, onCleanup, splitProps, type ComponentProps, type JSX } from "solid-js"
import { deferSelect } from "../../components/select-defer"
import "./select-v2.css"
function groupOptions<T>(options: T[], groupBy?: (x: T) => string): { category: string; options: T[] }[] {
@ -165,7 +166,7 @@ export function SelectV2<T>(props: SelectV2Props<T>) {
)}
onChange={(next) => {
const v = next == null ? null : Array.isArray(next) ? ((next[0] as T) ?? null) : (next as T)
local.onSelect?.(v)
deferSelect(local.onSelect, v)
stop()
}}
onOpenChange={(open) => {