Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions packages/junior-dashboard/e2e/settings.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,23 @@ test("follows the system theme until the user picks one", async ({
await page.emulateMedia({ colorScheme: "dark" });
await expect(root).toHaveAttribute("data-theme", "dark");
});

test("turns on composer autocorrect for this browser", async ({
page,
dashboard,
}) => {
await page.goto(dashboard.baseURL);
const composer = page.getByPlaceholder("Message Junior…");
await expect(composer).toHaveAttribute("autocorrect", "off");
await expect(composer).toHaveAttribute("autocapitalize", "off");
await expect(composer).toHaveAttribute("spellcheck", "false");

await page.getByRole("button", { name: /Open profile menu/ }).click();
await page.getByRole("link", { name: "Settings", exact: true }).click();
await page.getByText("Autocorrect", { exact: true }).click();

await page.goto(dashboard.baseURL);
await expect(composer).toHaveAttribute("autocorrect", "on");
await expect(composer).toHaveAttribute("autocapitalize", "sentences");
await expect(composer).toHaveAttribute("spellcheck", "true");
});
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
} from "../components/ui/tooltip";
import { useDashboardOnline } from "../connection";
import { cn, dashboardComposerSurfaceClass } from "../styles";
import { useComposerAutocorrect } from "./composerAutocorrect";

const DRAFT_STORAGE_PREFIX = "junior:dashboard:conversation-draft:";
// Keep keystrokes off the storage path. Mobile Safari pays for every write.
Expand Down Expand Up @@ -132,6 +133,7 @@ export const ConversationComposer = memo(function ConversationComposer(
const [dragging, setDragging] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
const online = useDashboardOnline();
const [autocorrect] = useComposerAutocorrect();
const id = useId();
const textareaRef = useRef<HTMLTextAreaElement>(null);
const draftRef = useRef(initialDraft);
Expand Down Expand Up @@ -490,10 +492,11 @@ export const ConversationComposer = memo(function ConversationComposer(
</label>
<textarea
// Chat is technical: ids, paths, code. Keep the keyboard chat-like
// (Send) and suppress browser IME assist that fights identifiers.
autoCapitalize="off"
// (Send) and suppress browser IME assist that fights identifiers,
// unless the user turns autocorrect on in settings.
autoCapitalize={autocorrect ? "sentences" : "off"}
autoComplete="off"
autoCorrect="off"
autoCorrect={autocorrect ? "on" : "off"}
className="min-h-12 max-h-28 w-full resize-none overflow-y-auto border-0 bg-transparent px-3 py-3 font-sans text-base leading-relaxed text-dashboard-text outline-none placeholder:text-dashboard-text-muted/65 md:min-h-14 md:max-h-[min(calc(12lh_+_1.5rem),40dvh)] md:px-4 md:py-3"
disabled={Boolean(props.restoreDraftOnError && createPending)}
enterKeyHint="send"
Expand All @@ -510,7 +513,7 @@ export const ConversationComposer = memo(function ConversationComposer(
placeholder="Message Junior…"
ref={textareaRef}
rows={1}
spellCheck={false}
spellCheck={autocorrect}
/>
<div
className={cn(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { useLocalStorage } from "@uidotdev/usehooks";

const AUTOCORRECT_STORAGE_KEY = "junior:composer:autocorrect";

/**
* Edit the browser-only choice to let the keyboard autocorrect, spell check,
* and capitalize the message composer. Off by default because these change
* ids, paths, and code.
*/
export function useComposerAutocorrect() {
return useLocalStorage<boolean>(AUTOCORRECT_STORAGE_KEY, false);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ import {
import { Button } from "../../components/ui/button";
import { Input } from "../../components/ui/input";
import { Textarea } from "../../components/ui/textarea";
import { Checkbox } from "../../components/ui/checkbox";
import { Field, FieldDescription, FieldLabel } from "../../components/ui/field";
import { RadioGroup, RadioGroupItem } from "../../components/ui/radio-group";
import {
useConversationStartPosition,
type ConversationStartPosition,
} from "../../conversations/transcriptReadPosition";
import { useComposerAutocorrect } from "../../conversations/composerAutocorrect";
import { InlineError } from "../../components/InlineError";
import { Card } from "../../components/layout/Card";
import { DashboardApiError, fetchDashboardJson, patch, put } from "../../http";
Expand Down Expand Up @@ -105,6 +107,7 @@ export function SettingsPage({ identity }: SettingsPageProps) {
</Card>
</form>
<ConversationStartSetting />
<ComposerAutocorrectSetting />
<CustomizationForm />
</SettingsPageLayout>
);
Expand Down Expand Up @@ -166,6 +169,31 @@ function ConversationStartSetting() {
);
}

/** Let the user turn on keyboard autocorrect in the composer on this browser. */
function ComposerAutocorrectSetting() {
const [autocorrect, setAutocorrect] = useComposerAutocorrect();
return (
<Card className="mt-6 mb-0" padding="md" variant="raised">
<h2 className="m-0 text-lg font-bold">Message box</h2>
<label className="mt-4 flex cursor-pointer items-start gap-3">
<Checkbox
checked={autocorrect}
className="mt-0.5"
onCheckedChange={setAutocorrect}
/>
<span className="grid gap-0.5">
<span className="text-sm font-medium">Autocorrect</span>
<span className="text-xs text-dashboard-text-muted">
Let your keyboard correct spelling and capitalize sentences as you
type. This can change ids, paths, and code. This choice applies to
this browser.
</span>
</span>
</label>
</Card>
);
}

/**
* Let the user save text that changes how the agent replies to them. The
* server returns 404 when the app does not enable this experiment.
Expand Down
Loading