From c4e934c9dc9f03b5b22c90d438abdc89573ff60a Mon Sep 17 00:00:00 2001 From: amosblomqvist Date: Wed, 28 Oct 2026 18:54:57 +0200 Subject: [PATCH] feat: attach desktop file paths as editable learner chips --- components/answered-card.tsx | 5 +- components/chat.tsx | 13 +- components/composer.tsx | 9 +- components/dictation.tsx | 18 ++ components/file-path-chip.tsx | 62 ++++ components/file-path-drop-zone.tsx | 106 +++++++ components/home.tsx | 13 +- components/new-session.tsx | 4 +- components/path-input-dom.ts | 245 ++++++++++++++ components/path-input.tsx | 291 +++++++++++++++++ components/practice-dock.tsx | 10 +- components/question-dock.tsx | 32 +- components/reading-thinking.tsx | 6 +- components/session-find-dom.ts | 2 +- components/sidebar.tsx | 5 +- components/thinking-block.tsx | 3 +- electron/preload.cjs | 5 +- scripts/smoke-file-paths.mjs | 424 +++++++++++++++++++++++++ scripts/smoke-opening-prompt.mjs | 17 +- src/core/file-paths.ts | 86 +++++ src/core/index.ts | 2 + tests/desktop-platform.test.ts | 2 +- tests/file-path-chip.test.tsx | 89 ++++++ tests/file-path-drop-zone.test.tsx | 223 +++++++++++++ tests/file-path-preload.test.ts | 93 ++++++ tests/file-paths.test.ts | 127 ++++++++ tests/path-input.test.tsx | 491 +++++++++++++++++++++++++++++ tests/render-cards.test.tsx | 2 +- tests/render-new-session.test.tsx | 2 +- tests/session-find.test.tsx | 3 +- 30 files changed, 2331 insertions(+), 59 deletions(-) create mode 100644 components/file-path-chip.tsx create mode 100644 components/file-path-drop-zone.tsx create mode 100644 components/path-input-dom.ts create mode 100644 components/path-input.tsx create mode 100644 scripts/smoke-file-paths.mjs create mode 100644 src/core/file-paths.ts create mode 100644 tests/file-path-chip.test.tsx create mode 100644 tests/file-path-drop-zone.test.tsx create mode 100644 tests/file-path-preload.test.ts create mode 100644 tests/file-paths.test.ts create mode 100644 tests/path-input.test.tsx diff --git a/components/answered-card.tsx b/components/answered-card.tsx index ce76b80..1b7c95c 100644 --- a/components/answered-card.tsx +++ b/components/answered-card.tsx @@ -4,6 +4,7 @@ import { useState } from "react"; import { ConfidenceRail } from "@/components/confidence-rail"; import { GlyphMark } from "@/components/ui/glyph-mark"; import { ThinkingBlock } from "@/components/thinking-block"; +import { PathText } from "@/components/file-path-chip"; import { cn } from "@/lib/utils"; import { ToolContentResponse } from "@/components/ai-elements/message"; import type { QuestionAnswerData } from "@/src/core"; @@ -153,13 +154,13 @@ export function AnsweredCard({ answer: savedAnswer, input }: { answer: QuestionA data-testid="answered-other" className="flex items-center gap-2 rounded-lg border border-primary bg-accent px-3 py-1.5" > - “{answer.other}” + “” )} )} - {answer.text &&
“{answer.text}”
} + {answer.text &&
“”
} {/* The confidence record (owner round 6): the slider's rail, disc removed — the missing handle itself says "this is a record, not a diff --git a/components/chat.tsx b/components/chat.tsx index 2d851d1..8c70164 100644 --- a/components/chat.tsx +++ b/components/chat.tsx @@ -14,6 +14,7 @@ import { ConversationScrollButton, } from "@/components/ai-elements/conversation"; import { Message, MessageContent, MessageResponse } from "@/components/ai-elements/message"; +import { PathText } from "@/components/file-path-chip"; import { Reasoning, ReasoningContent, ReasoningTrigger } from "@/components/ai-elements/reasoning"; import { Shimmer } from "@/components/ai-elements/shimmer"; import { BrailleSpinner } from "@/components/braille-spinner"; @@ -687,10 +688,10 @@ export function Chat({ const cycleChatFields = useCallback((): readonly HTMLElement[] => { const pick = (sel: string) => Array.from(document.querySelectorAll(sel)); return [ - ...pick('[data-testid="reading-thinking-field"] textarea'), - ...pick('[data-testid="question-dock"] textarea'), - ...pick('[data-testid="practice-dock"] textarea'), - ...pick('[data-testid="composer"] textarea'), + ...pick('[data-testid="reading-thinking-field"] [data-file-path-input]'), + ...pick('[data-testid="question-dock"] [data-file-path-input]'), + ...pick('[data-testid="practice-dock"] [data-file-path-input]'), + ...pick('[data-testid="composer"] [data-file-path-input]'), ]; }, []); useFieldCycle(cycleChatFields); @@ -705,7 +706,7 @@ export function Chat({ * composer: with a question pending the Composer is unmounted, the ref * is null, and the thinking field is left alone by design. */ const dictationWasActiveRef = useRef(false); - const composerInputRef = useRef(null); + const composerInputRef = useRef(null); useEffect(() => { if (dictation.active) { dictationWasActiveRef.current = true; @@ -1507,7 +1508,7 @@ export function Chat({ rail as the thinking rows — one quote geometry. */}
You
- {m.parts.filter((p) => p.type === "text").map((p) => p.text).join("\n")} + p.type === "text").map((p) => p.text).join("\n")} />
) : ( diff --git a/components/composer.tsx b/components/composer.tsx index c5c7db4..649862d 100644 --- a/components/composer.tsx +++ b/components/composer.tsx @@ -3,6 +3,7 @@ import { ArrowRightIcon, CornerDownLeftIcon, SquareIcon } from "lucide-react"; import { cn } from "@/lib/utils"; import { Hint } from "@/components/ui/hint"; +import { PathInput } from "@/components/path-input"; /** * The chat composer — content row ONLY (the persistent rounded shell lives in @@ -59,7 +60,7 @@ export function Composer({ autoFocus?: boolean; /** The page's handle to the textarea — the dictation-end focus (owner, * 2026-10-01): a run ending hands the cursor to the bare composer. */ - inputRef?: React.RefObject; + inputRef?: React.RefObject; /** Escape while a dictation runs stops it (STT wave, 2026-09-17). No-op * when absent — Esc otherwise means nothing in the composer. */ onEscape?: () => void; @@ -90,10 +91,11 @@ export function Composer({ act(); }} > - +
needle path draft notes.pdf
`); const ranges = sessionFindRanges(document.body as unknown as HTMLElement, "needle"); assert.equal(ranges.length, 1);