From 2a04925aecdc3075194cc18113cea5ced09bf9f6 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Sun, 8 Nov 2026 05:37:11 +0800 Subject: [PATCH] Restore focus after reading a research group Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- .../personal-workspace/goal-research-history.tsx | 10 ++++++---- .../personal-workspace/goal-research-timeline.tsx | 8 ++++---- .../research-route-reading.mjs | 12 +++++++++++- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/apps/presentation/dashboard/src/features/personal-workspace/goal-research-history.tsx b/apps/presentation/dashboard/src/features/personal-workspace/goal-research-history.tsx index 7d15617961..5960bf4e6c 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/goal-research-history.tsx +++ b/apps/presentation/dashboard/src/features/personal-workspace/goal-research-history.tsx @@ -338,9 +338,9 @@ export function GoalResearchHistory({goalId, originalGoalId = goalId, preferHist const clusterY = layout.length ? Math.max(...layout.map(item => item.y + 124)) + 24 : 24; const width = Math.max(600, grouped.groups.length ? 944 : 0, ...layout.map(item => item.x + 230)); const height = Math.max(230, ...layout.map(item => item.y + 124), grouped.groups.length ? clusterY + Math.ceil(grouped.groups.length / 4) * 124 : 0); - const select = (next: Selection, focus = true) => { + const select = (next: Selection, focus = true, openingControl?: HTMLButtonElement) => { if (focus && (next.nodes.length || next.day !== null)) { - opener.current = document.activeElement instanceof HTMLElement ? document.activeElement : null; + opener.current = openingControl ?? (document.activeElement instanceof HTMLElement ? document.activeElement : null); focusPending.current = true; } next = {...next, question: next.question ?? !next.finding, @@ -426,7 +426,9 @@ export function GoalResearchHistory({goalId, originalGoalId = goalId, preferHist : null; const historyView = !value ? null : lens === "timeline" && timeline ?
- select({nodes: [], day})} phase={phase} onPhase={next => chooseView(lens, next)} onSelect={(id, finding) => choose(id, true, finding)} onSelectMany={nodes => select({nodes, day: null})} timeLabel={fullTime} dayLabel={dayTime}/> + select({nodes: [], day}, true, button)} phase={phase} onPhase={next => chooseView(lens, next)} + onSelect={(id, finding) => choose(id, true, finding)} onSelectMany={(nodes, button) => select({nodes, day: null}, true, button)} timeLabel={fullTime} dayLabel={dayTime}/>
:
{node.updates.length > 1 ? ` · ${node.updates.length} ${copy.changes}` : ""}{node.findings.length ? ` · ${findingCopy.count(node.findings.length)}` : ""}} })} {grouped.groups.map((group, index) => )} diff --git a/apps/presentation/dashboard/src/features/personal-workspace/goal-research-timeline.tsx b/apps/presentation/dashboard/src/features/personal-workspace/goal-research-timeline.tsx index 96bac7196a..122704f068 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/goal-research-timeline.tsx +++ b/apps/presentation/dashboard/src/features/personal-workspace/goal-research-timeline.tsx @@ -12,8 +12,8 @@ import {ContextHelp} from "./context-help"; type Copy = Record; type Props = { timeline: ExploreTimeline; zh: boolean; selectedIds: readonly string[]; selectedEventId?: string; phase: number | null; - selectedDay: string | null; dayGroups: ReturnType["groups"]; onGroupSelect: (day: string) => void; - onSelect: (id: string, finding?: string) => void; onSelectMany: (ids: string[]) => void; onPhase: (index: number | null) => void; + selectedDay: string | null; dayGroups: ReturnType["groups"]; onGroupSelect: (day: string, button: HTMLButtonElement) => void; + onSelect: (id: string, finding?: string) => void; onSelectMany: (ids: string[], button: HTMLButtonElement) => void; onPhase: (index: number | null) => void; timeLabel: (value: number) => string; dayLabel: (value: number) => string; }; @@ -84,7 +84,7 @@ export function GoalResearchTimeline({timeline, zh, selectedIds, selectedEventId : `${ids.length} nearby research items · ${timeLabel(group.items[0]!.first!)} — ${timeLabel(group.items.at(-1)!.first!)} · Read every record and source`; return ; + style={{left: pct(group.at)}} onClick={event => onSelectMany(ids, event.currentTarget)}>{ids.length}; }); const trail = groupExploreTimelineDecisions(timeline, phase); const decisionOutcomes = [...new Set(timeline.decisions.map(item => item.sections!.decision!.outcome))]; @@ -189,7 +189,7 @@ export function GoalResearchTimeline({timeline, zh, selectedIds, selectedEventId
{copy.singles} · {groupedIds.size}
{dayGroups.map(group => )}
+ onClick={event => onGroupSelect(group.day, event.currentTarget)}>{group.nodes.length} {group.nodes.length === 1 ? copy.question : copy.questions})}
: null} {swarm.length ? <>
{copy.singles} · {swarm.length}
diff --git a/examples/personal-workspace-browser/research-route-reading.mjs b/examples/personal-workspace-browser/research-route-reading.mjs index 69af7facb6..f7db2ed722 100644 --- a/examples/personal-workspace-browser/research-route-reading.mjs +++ b/examples/personal-workspace-browser/research-route-reading.mjs @@ -251,7 +251,17 @@ export const researchRouteReadingScenario = { await page.emulateMedia({reducedMotion: "reduce"}); await page.evaluate(value => localStorage.setItem("loopx-pw-locale", value), locale); await page.goto(url); - await history.locator('[data-research-day="2026-01-04"]').click(); + const day = history.locator('[data-research-day="2026-01-04"]'); + await day.waitFor(); + await history.locator(".research-history-canvas").focus(); + // WebKit can activate the button while leaving focus on the canvas. + await day.evaluate(button => button.click()); + await reader.locator(".research-question-index button").nth(3).click(); + await page.keyboard.press("Escape"); + await reader.locator(".research-question-index").waitFor(); + await page.keyboard.press("Escape"); + await page.waitForFunction(() => document.activeElement?.getAttribute("data-research-day") === "2026-01-04"); + await day.click(); assert.equal(await reader.locator(".research-question-index li").count(), 24); await reader.locator(".research-question-index button").nth(3).click(); const questions = reader.getByRole("combobox", {name: zh ? "选择同组研究问题" : "Choose a question in this group"});