Skip to content

Commit b1f9f4f

Browse files
GiniGini
authored andcommitted
fix: preserve editable keyboard navigation in rail
1 parent 298a2f4 commit b1f9f4f

4 files changed

Lines changed: 16 additions & 2 deletions

File tree

‎docs/IMPLEMENTATION-LOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
- Reworked the deck workspace into a review surface rather than an outline list: compact visual slide cards, position-aware previous/next controls, a focused deck canvas, and per-slide notes that open directly in the evidence-bound source editor. These are visual outline thumbnails, not an assertion that the PPTX itself has been rendered to images.
6767
- Corrected compact tool-card causality in the Computer rail: a visual checkpoint tied to a folded tool-result event now appears with its originating tool card, while retaining the separate immutable screenshot card in chronological order. This closes a mixed-stream review gap without collapsing or rewriting the underlying evidence.
6868
- Added bounded text snippets to generated source/deliverable cards in the Computer rail. The API refuses inputs and evidence paths, recognizes text artifacts only, caps eligible files at 64 KiB and returned excerpts at 12,000 characters; the client applies the rail redactor again before display. This makes the rail more useful for file review without converting it into a workspace mount.
69+
- Kept the artifact-rail keyboard model review-safe: Arrow/Home/End navigation works on the rail, but does not intercept caret or selection behavior in the evidence search field, run selectors, textareas, or content-editable controls.
6970
- Replaced opaque click-to-cycle runtime selection with a server-derived readiness picker. It identifies the task boundary and availability for Safe demo, native Claude Agent SDK, ONEComputer sandbox, and remote runtime without exposing server credentials; unavailable remote/sandbox choices cannot be submitted, while the API remains the authority for enforcement.
7071
- Added a read-only task Settings workspace tab. It gathers the runtime/boundary, gateway-attestation, approval, artifact-contract, and attached-context record in one reviewable place while deliberately excluding credentials, VTI Wallet keys, provider controls, and X11/VNC/CDP channels.
7172
- Modernized generated Website/App/Game projects with Tailwind 4’s Vite integration and a minimal typed component foundation (`Button` plus `cn`). Static validation now verifies the foundation is present; installing or running third-party dependencies remains reserved for a controlled sandbox build gate.

‎src/components/ComputerTimeline.test.ts‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, expect, it } from 'vitest'
2-
import { activityPreviewFor, artifactRailItems, causalVisualItemsFor, commandFor, compareRunArtifacts, defaultComputerItem, evidenceItemId, filterItemsByRun, formatDuration, formatInspectable, matchesRailQuery, presentationItems, railCardTypeFor, runIdsFor, runLabel, summarizeRunEvidence, terminalActivityFor, virtualRailRange, visualEvidenceStateFor, type ComputerItem } from './computer-timeline-activity'
2+
import { activityPreviewFor, artifactRailItems, causalVisualItemsFor, commandFor, compareRunArtifacts, defaultComputerItem, evidenceItemId, filterItemsByRun, formatDuration, formatInspectable, matchesRailQuery, presentationItems, railCardTypeFor, runIdsFor, runLabel, summarizeRunEvidence, terminalActivityFor, timelineNavigationAllowedFor, virtualRailRange, visualEvidenceStateFor, type ComputerItem } from './computer-timeline-activity'
33
import type { RuntimeEvent } from '../types'
44

55
const event = (id: string, type: string, payload: Record<string, unknown>, content?: string): RuntimeEvent => ({
@@ -141,6 +141,14 @@ describe('Computer timeline terminal inspection', () => {
141141
expect(virtualRailRange(3, 0, 340)).toEqual({ start: 0, end: 3 })
142142
})
143143

144+
it('reserves arrow and home/end controls for the rail without stealing editable-field navigation', () => {
145+
expect(timelineNavigationAllowedFor('div')).toBe(true)
146+
expect(timelineNavigationAllowedFor('input')).toBe(false)
147+
expect(timelineNavigationAllowedFor('textarea')).toBe(false)
148+
expect(timelineNavigationAllowedFor('select')).toBe(false)
149+
expect(timelineNavigationAllowedFor('div', true)).toBe(false)
150+
})
151+
144152
it('searches only projected rail metadata', () => {
145153
const item: ComputerItem = { id: 'browser-event', kind: 'terminal', title: 'browser_navigate', detail: 'Opened the approved reference', activityPreview: 'https://example.com/docs', createdAt: '2026-07-16T00:00:00.000Z' }
146154
expect(matchesRailQuery(item, 'navigate')).toBe(true)

‎src/components/ComputerTimeline.tsx‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { ArrowLeft, ArrowRight, CheckCircle2, Eye, FileCode2, Pause, Play, Presentation, Radio, ShieldCheck, TerminalSquare, Wrench } from 'lucide-react'
22
import { useCallback, useEffect, useMemo, useRef, useState, type KeyboardEvent } from 'react'
33
import type { PresentationPanel, TaskSnapshot } from '../types'
4-
import { artifactRailItems, causalVisualItemsFor, compareRunArtifacts, defaultComputerItem, evidenceItemId, filterItemsByRun, formatDuration, formatInspectable, matchesRailQuery, presentationItems, railCardTypeFor, runIdsFor, runLabel, summarizeRunEvidence, terminalActivityFor, virtualRailRange, visualEvidenceStateFor, type ComputerItem } from './computer-timeline-activity'
4+
import { artifactRailItems, causalVisualItemsFor, compareRunArtifacts, defaultComputerItem, evidenceItemId, filterItemsByRun, formatDuration, formatInspectable, matchesRailQuery, presentationItems, railCardTypeFor, runIdsFor, runLabel, summarizeRunEvidence, terminalActivityFor, timelineNavigationAllowedFor, virtualRailRange, visualEvidenceStateFor, type ComputerItem } from './computer-timeline-activity'
55

66
const iconFor = (item: ComputerItem) => item.kind === 'terminal' ? <TerminalSquare size={13} /> : item.kind === 'screenshot' ? <Eye size={13} /> : item.kind === 'preview' ? <Radio size={13} /> : item.kind === 'slide' ? <Presentation size={13} /> : item.kind === 'approval' ? <ShieldCheck size={13} /> : <FileCode2 size={13} />
77
const RAIL_ROW_HEIGHT = 68
@@ -170,6 +170,8 @@ export const ComputerTimeline = ({ task }: { task: TaskSnapshot }) => {
170170
persistEvidenceReference(railItems[index], 'all')
171171
}
172172
const onTimelineKeyDown = (event: KeyboardEvent<HTMLElement>) => {
173+
const target = event.target instanceof HTMLElement ? event.target : undefined
174+
if (event.altKey || event.ctrlKey || event.metaKey || !timelineNavigationAllowedFor(target?.tagName, target?.isContentEditable)) return
173175
if (event.key === 'ArrowLeft') { event.preventDefault(); move(selected - 1) }
174176
if (event.key === 'ArrowRight') { event.preventDefault(); move(selected + 1) }
175177
if (event.key === 'Home') { event.preventDefault(); move(0) }

‎src/components/computer-timeline-activity.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ export const formatDuration = (milliseconds: number | undefined) => {
7171
return `${Math.floor(milliseconds / 60_000)}m ${Math.round((milliseconds % 60_000) / 1_000)}s`
7272
}
7373

74+
/** Do not steal caret, select-menu, or editable-content navigation from the reviewer. */
75+
export const timelineNavigationAllowedFor = (tagName: string | undefined, isContentEditable = false) => !isContentEditable && !['INPUT', 'TEXTAREA', 'SELECT'].includes(tagName?.toUpperCase() ?? '')
76+
7477
export const virtualRailRange = (count: number, scrollTop: number, viewportHeight: number, rowHeight = 68, overscan = 12) => {
7578
if (count <= 0) return { start: 0, end: 0 }
7679
const visibleStart = Math.floor(Math.max(0, scrollTop) / rowHeight)

0 commit comments

Comments
 (0)