diff options
| author | Adam Malczewski <[email protected]> | 2026-06-26 22:21:55 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-26 22:23:39 +0900 |
| commit | c333fcec32b1f90bf0da6bb14d2609c20e38a74f (patch) | |
| tree | 0db3ec77a6838c4f800c362df0de3c6cd8544431 /src/features/settings/ui | |
| parent | 1285564f12238b22f6b39b9f3fbcecaca8456911 (diff) | |
| download | dispatch-web-c333fcec32b1f90bf0da6bb14d2609c20e38a74f.tar.gz dispatch-web-c333fcec32b1f90bf0da6bb14d2609c20e38a74f.zip | |
style: switch from tabs to 2-space indentation (incl. svelte)
Diffstat (limited to 'src/features/settings/ui')
| -rw-r--r-- | src/features/settings/ui/ChatLimitField.svelte | 186 | ||||
| -rw-r--r-- | src/features/settings/ui/ChatLimitField.test.ts | 128 |
2 files changed, 157 insertions, 157 deletions
diff --git a/src/features/settings/ui/ChatLimitField.svelte b/src/features/settings/ui/ChatLimitField.svelte index 9b9911a..502212f 100644 --- a/src/features/settings/ui/ChatLimitField.svelte +++ b/src/features/settings/ui/ChatLimitField.svelte @@ -1,104 +1,104 @@ <script lang="ts"> - import { untrack } from "svelte"; - import { MAX_CHAT_LIMIT, MIN_CHAT_LIMIT } from "../../../core/chunks"; - import { chatLimitChanged, parseChatLimit, type SaveChatLimit } from "../logic/view-model"; + import { untrack } from "svelte"; + import { MAX_CHAT_LIMIT, MIN_CHAT_LIMIT } from "../../../core/chunks"; + import { chatLimitChanged, parseChatLimit, type SaveChatLimit } from "../logic/view-model"; - let { - chatLimit, - save, - }: { - /** The persisted chat limit (max loaded chunks per conversation). */ - chatLimit: number; - save: SaveChatLimit; - } = $props(); + let { + chatLimit, + save, + }: { + /** The persisted chat limit (max loaded chunks per conversation). */ + chatLimit: number; + save: SaveChatLimit; + } = $props(); - // Seed from the prop; the $effect below re-seeds on external changes (a live - // apply from elsewhere) but only while the field is untouched, so an in-flight - // change can't clobber what the user typed. - let value = $state(""); - let lastSeed = $state(""); - let saving = $state(false); - let error = $state<string | null>(null); - let justSaved = $state(false); - let savedValue = $state<number | null>(null); + // Seed from the prop; the $effect below re-seeds on external changes (a live + // apply from elsewhere) but only while the field is untouched, so an in-flight + // change can't clobber what the user typed. + let value = $state(""); + let lastSeed = $state(""); + let saving = $state(false); + let error = $state<string | null>(null); + let justSaved = $state(false); + let savedValue = $state<number | null>(null); - $effect(() => { - const incoming = String(chatLimit); - untrack(() => { - if (value === lastSeed) value = incoming; - lastSeed = incoming; - }); - }); + $effect(() => { + const incoming = String(chatLimit); + untrack(() => { + if (value === lastSeed) value = incoming; + lastSeed = incoming; + }); + }); - const dirty = $derived(chatLimitChanged(value, chatLimit)); + const dirty = $derived(chatLimitChanged(value, chatLimit)); - async function handleSave() { - if (saving || !dirty) return; - const parsed = parseChatLimit(value); - if (!parsed.ok) { - error = parsed.error; - return; - } - saving = true; - error = null; - justSaved = false; - const result = await save(parsed.value); - saving = false; - if (result.ok) { - justSaved = true; - savedValue = result.chatLimit; - // Reflect the clamped / persisted value back into the input immediately - // (the prop will also re-assert it via the effect above). - value = String(result.chatLimit); - lastSeed = value; - } else { - error = result.error; - } - } + async function handleSave() { + if (saving || !dirty) return; + const parsed = parseChatLimit(value); + if (!parsed.ok) { + error = parsed.error; + return; + } + saving = true; + error = null; + justSaved = false; + const result = await save(parsed.value); + saving = false; + if (result.ok) { + justSaved = true; + savedValue = result.chatLimit; + // Reflect the clamped / persisted value back into the input immediately + // (the prop will also re-assert it via the effect above). + value = String(result.chatLimit); + lastSeed = value; + } else { + error = result.error; + } + } - function onInput() { - justSaved = false; - error = null; - } + function onInput() { + justSaved = false; + error = null; + } </script> <div class="flex flex-col gap-1"> - <span class="text-xs font-semibold uppercase opacity-60">Chat limit</span> - <div class="flex items-center gap-2"> - <input - type="text" - inputmode="numeric" - class="input input-bordered input-sm w-full font-mono text-xs" - placeholder={String(chatLimit)} - bind:value - disabled={saving} - oninput={onInput} - onkeydown={(e) => { - if (e.key === "Enter") handleSave(); - }} - aria-label="Chat limit" - /> - <button - type="button" - class="btn btn-primary btn-sm" - disabled={saving || !dirty} - onclick={handleSave} - > - {#if saving} - <span class="loading loading-spinner loading-xs"></span> - {:else} - Set - {/if} - </button> - </div> - {#if error} - <p class="text-xs text-error">{error}</p> - {:else if justSaved && !dirty} - <p class="text-xs text-success">Saved{savedValue !== null ? `: ${savedValue}.` : "."}</p> - {:else} - <p class="text-xs opacity-50"> - Max loaded chunks per conversation ({MIN_CHAT_LIMIT}–{MAX_CHAT_LIMIT}). Lowering it unloads - older history; raise it and use "Show earlier" to page back in. - </p> - {/if} + <span class="text-xs font-semibold uppercase opacity-60">Chat limit</span> + <div class="flex items-center gap-2"> + <input + type="text" + inputmode="numeric" + class="input input-bordered input-sm w-full font-mono text-xs" + placeholder={String(chatLimit)} + bind:value + disabled={saving} + oninput={onInput} + onkeydown={(e) => { + if (e.key === "Enter") handleSave(); + }} + aria-label="Chat limit" + /> + <button + type="button" + class="btn btn-primary btn-sm" + disabled={saving || !dirty} + onclick={handleSave} + > + {#if saving} + <span class="loading loading-spinner loading-xs"></span> + {:else} + Set + {/if} + </button> + </div> + {#if error} + <p class="text-xs text-error">{error}</p> + {:else if justSaved && !dirty} + <p class="text-xs text-success">Saved{savedValue !== null ? `: ${savedValue}.` : "."}</p> + {:else} + <p class="text-xs opacity-50"> + Max loaded chunks per conversation ({MIN_CHAT_LIMIT}–{MAX_CHAT_LIMIT}). Lowering it unloads + older history; raise it and use "Show earlier" to page back in. + </p> + {/if} </div> diff --git a/src/features/settings/ui/ChatLimitField.test.ts b/src/features/settings/ui/ChatLimitField.test.ts index 73bc449..8f8da25 100644 --- a/src/features/settings/ui/ChatLimitField.test.ts +++ b/src/features/settings/ui/ChatLimitField.test.ts @@ -6,84 +6,84 @@ import ChatLimitField from "./ChatLimitField.svelte"; // A fake save that resolves ok with the value it was given. function fakeSave(): { - saves: number[]; - last: number | null; - impl: (value: number) => Promise<ChatLimitSaveResult>; + saves: number[]; + last: number | null; + impl: (value: number) => Promise<ChatLimitSaveResult>; } { - const saves: number[] = []; - return { - saves, - last: null, - impl: async (value: number) => { - saves.push(value); - return { ok: true, chatLimit: value }; - }, - }; + const saves: number[] = []; + return { + saves, + last: null, + impl: async (value: number) => { + saves.push(value); + return { ok: true, chatLimit: value }; + }, + }; } describe("ChatLimitField", () => { - it("seeds the input from the persisted limit and disables Set while unchanged", () => { - render(ChatLimitField, { props: { chatLimit: 256, save: fakeSave().impl } }); - expect(screen.getByLabelText("Chat limit")).toHaveValue("256"); - expect(screen.getByRole("button", { name: "Set" })).toBeDisabled(); - }); + it("seeds the input from the persisted limit and disables Set while unchanged", () => { + render(ChatLimitField, { props: { chatLimit: 256, save: fakeSave().impl } }); + expect(screen.getByLabelText("Chat limit")).toHaveValue("256"); + expect(screen.getByRole("button", { name: "Set" })).toBeDisabled(); + }); - it("enables Set when the typed value differs (regression: number-binding coercion)", async () => { - const user = userEvent.setup(); - const save = fakeSave(); - render(ChatLimitField, { props: { chatLimit: 256, save: save.impl } }); + it("enables Set when the typed value differs (regression: number-binding coercion)", async () => { + const user = userEvent.setup(); + const save = fakeSave(); + render(ChatLimitField, { props: { chatLimit: 256, save: save.impl } }); - const input = screen.getByLabelText("Chat limit"); - await user.clear(input); - await user.type(input, "10"); + const input = screen.getByLabelText("Chat limit"); + await user.clear(input); + await user.type(input, "10"); - expect(screen.getByRole("button", { name: "Set" })).toBeEnabled(); - }); + expect(screen.getByRole("button", { name: "Set" })).toBeEnabled(); + }); - it("keeps Set disabled for non-numeric input", async () => { - const user = userEvent.setup(); - const save = fakeSave(); - render(ChatLimitField, { props: { chatLimit: 256, save: save.impl } }); + it("keeps Set disabled for non-numeric input", async () => { + const user = userEvent.setup(); + const save = fakeSave(); + render(ChatLimitField, { props: { chatLimit: 256, save: save.impl } }); - const input = screen.getByLabelText("Chat limit"); - await user.clear(input); - await user.type(input, "abc"); + const input = screen.getByLabelText("Chat limit"); + await user.clear(input); + await user.type(input, "abc"); - expect(screen.getByRole("button", { name: "Set" })).toBeDisabled(); - }); + expect(screen.getByRole("button", { name: "Set" })).toBeDisabled(); + }); - it("clicking Set calls save with the parsed value and shows the confirmation", async () => { - const user = userEvent.setup(); - const save = fakeSave(); - const { rerender } = render(ChatLimitField, { - props: { chatLimit: 256, save: save.impl }, - }); + it("clicking Set calls save with the parsed value and shows the confirmation", async () => { + const user = userEvent.setup(); + const save = fakeSave(); + const { rerender } = render(ChatLimitField, { + props: { chatLimit: 256, save: save.impl }, + }); - const input = screen.getByLabelText("Chat limit"); - await user.clear(input); - await user.type(input, "50"); - await user.click(screen.getByRole("button", { name: "Set" })); + const input = screen.getByLabelText("Chat limit"); + await user.clear(input); + await user.type(input, "50"); + await user.click(screen.getByRole("button", { name: "Set" })); - expect(save.saves).toEqual([50]); - // In the real app the reactive `chatLimit` prop updates to the saved value - // (the store sets it synchronously), which clears `dirty` and reveals the - // "Saved" badge. Rerender simulates that propagation. - rerender({ chatLimit: 50, save: save.impl }); - expect(screen.getByText(/Saved/i)).toBeInTheDocument(); - }); + expect(save.saves).toEqual([50]); + // In the real app the reactive `chatLimit` prop updates to the saved value + // (the store sets it synchronously), which clears `dirty` and reveals the + // "Saved" badge. Rerender simulates that propagation. + rerender({ chatLimit: 50, save: save.impl }); + expect(screen.getByText(/Saved/i)).toBeInTheDocument(); + }); - it("clamps a below-floor value when saving", async () => { - const user = userEvent.setup(); - const save = fakeSave(); - render(ChatLimitField, { props: { chatLimit: 256, save: save.impl } }); + it("clamps a below-floor value when saving", async () => { + const user = userEvent.setup(); + const save = fakeSave(); + render(ChatLimitField, { props: { chatLimit: 256, save: save.impl } }); - const input = screen.getByLabelText("Chat limit"); - await user.clear(input); - await user.type(input, "5"); // clamps to MIN (10) - await user.click(screen.getByRole("button", { name: "Set" })); + const input = screen.getByLabelText("Chat limit"); + await user.clear(input); + await user.type(input, "5"); // clamps to MIN (10) + await user.click(screen.getByRole("button", { name: "Set" })); - // The save port receives the clamped value (the view-model clamps before save). - expect(save.saves).toEqual([10]); - expect(input).toHaveValue("10"); - }); + // The save port receives the clamped value (the view-model clamps before save). + expect(save.saves).toEqual([10]); + expect(input).toHaveValue("10"); + }); }); |
