mirror of
https://github.com/Darkatse/TauriTavern.git
synced 2026-10-19 15:45:12 +00:00
feat(agent): add mobile swipe navigation to run timeline
This commit is contained in:
parent
dcad6d14bf
commit
c2d23fc2f7
6 changed files with 296 additions and 3 deletions
|
|
@ -173,6 +173,7 @@ future APIs approveToolCall/readDiff/rollback throw explicitly
|
|||
run timeline/detail view switching has a single explicit state source and does not derive detailsOpen from scrollLeft
|
||||
run timeline main panel does not use horizontal scroll-snap or smooth scroll as a view state machine
|
||||
run timeline closes detail by resetting detail state and does not measure or auto-stick hidden timeline scrollers
|
||||
run timeline mobile view gesture uses Pointer Events only as an input shortcut and commits through openDetails/showTimeline
|
||||
```
|
||||
|
||||
Provider normalizer tests 必须覆盖可见 reasoning 提取:Claude `thinking`、Gemini `thought` 文本、OpenAI Responses reasoning summary 进入 `reasoning_content`;signature / encrypted continuation 仍作为 native/provider state 保留,不能作为可展示文本。
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
- Handoff MVP 已落地:具备 `delegation.canHandoff` 且显式暴露 `agent.handoff` 的 foreground owner 可把同一 `AgentRun` 的下一阶段交给 `allowAsHandoffTarget` 的 target Profile。handoff 创建 `TransferControl` task 与 `Handoff` invocation,不进入后台 scheduler;当前 invocation 记为 `Transferred` 后,executor 在同一 run 内继续目标 Agent 的工具循环。已经 `workspace.commit` 过的 Agent 仍允许 handoff,最终 handoff owner 可继续修订、再次 commit 并 `workspace.finish`。实现框架、结构体与事件序列见 `docs/Agent/Handoff.md`。
|
||||
- 当前工具循环是非 streaming;provider stream 仍不是 Agent timeline event。
|
||||
- Agent System 扩展开关开启时,当前前端会把普通发送、regenerate 与 overswipe 新候选生成接入 Agent;实际 root run 使用扩展设置中的 `activeProfileId`。Profile 面板的 `editingProfileId` 只表示当前正在编辑的配置档案,不影响生成。Agent Mode off 时上游 SillyTavern 生成、事件和保存语义不变。
|
||||
- Agent System 前端已提供 run timeline / detail panel;timeline 以 `readEvents(beforeSeq, limit)` 读取最新页并按需补拉更早 journal 页,默认视图只投影用户可见操作,DOM 使用窗口化渲染避免长 run 在低端移动设备上堆积节点;Timeline / Detail 使用 `detailsOpen` 单一事实源驱动互斥视图,不再依赖横向 scroll-snap、smooth scroll 或 scroll 事件反写状态,避免低端 WebView 在切换中进入半页卡住状态;关闭 Detail 会 reset detail state 并取消 stale detail load,Detail 打开期间不测量或贴底隐藏的 timeline scroller;详情面板顶部可拖动调整高度,高度仅作为扩展 UI 偏好保存,不进入 Agent Host ABI、journal 或 Rust runtime。前端实现将 timeline 数据会话、projection 规范化、详情读取、显示格式与 Vue 外壳拆分:运行中贴片、历史回看弹窗与 SubAgent 局部 timeline 共享同一 session/detail primitives,但各自仍通过 Host ABI 表达运行态、只读历史态与 `invocationId` 局部事件页。
|
||||
- Agent System 前端已提供 run timeline / detail panel;timeline 以 `readEvents(beforeSeq, limit)` 读取最新页并按需补拉更早 journal 页,默认视图只投影用户可见操作,DOM 使用窗口化渲染避免长 run 在低端移动设备上堆积节点;Timeline / Detail 使用 `detailsOpen` 单一事实源驱动互斥视图,不再依赖横向 scroll-snap、smooth scroll 或 scroll 事件反写状态,避免低端 WebView 在切换中进入半页卡住状态;移动端横向滑动只作为 Pointer Events 输入快捷方式,最终仍调用 `openDetails()` / `showTimeline()`,并通过 `touch-action: pan-y pinch-zoom` 保留原生纵向滚动与缩放;关闭 Detail 会 reset detail state 并取消 stale detail load,Detail 打开期间不测量或贴底隐藏的 timeline scroller;详情面板顶部可拖动调整高度,高度仅作为扩展 UI 偏好保存,不进入 Agent Host ABI、journal 或 Rust runtime。前端实现将 timeline 数据会话、projection 规范化、详情读取、显示格式与 Vue 外壳拆分:运行中贴片、历史回看弹窗与 SubAgent 局部 timeline 共享同一 session/detail primitives,但各自仍通过 Host ABI 表达运行态、只读历史态与 `invocationId` 局部事件页。
|
||||
- Agent run history 已提供只读 `listRuns()` / `list_agent_runs(dto)` 入口;首版从 `_tauritavern/agent-workspaces/index/runs/*.json` 读取 `AgentRun` 摘要,支持 `chatRef`、`stableChatId`、`statuses`、时间游标与 limit。列表会维护 `_tauritavern/agent-workspaces/index/run-summaries/<run-id>.json` projection,从 `chat_commit_completed.messageId` 派生提交当时的 `messageIndex`;该索引是提交时快照,不扫描聊天消息、不反查当前楼层。只有已经写入 terminal event 的终态 run 会复用/落盘 summary projection,运行中或终态事件尚未出现的窗口只即时投影。
|
||||
- Agent run retention policy 已进入 `tauritavern-settings.agent.retention`:`auto_prune_enabled` 默认 `false`,控制 Rust 后端是否在 TauriTavern 进程运行期间周期性执行自动清理;`keep_recent_terminal_runs` 默认 100,表示保留最近 terminal run 的核心历史;`keep_full_recent_runs` 默认 20,表示其中保留完整 workspace/debug artifacts 的窗口。两个数量允许为 0,最大 10000,且 full 窗口必须是 history 窗口子集。前端 Host ABI 已提供 `api.agent.retention.readSettings()` / `updateSettings()` / `planPrune()` / `applyPrune()` facade,Agent System Runs 面板可保存策略、显示 dry-run 清理预览,并在用户确认后执行手动清理。后端已提供 `plan_agent_run_prune(dto)` 与 `apply_agent_run_prune(dto)` command,用当前设置或一次性 override 生成并执行 `slim_heavy_artifacts` / `delete_run` 候选;`apply` 使用显式 execution plan 模式,在后端重新规划全量候选,不信任前端预览明细,且同一服务实例内串行执行。自动清理由 `AgentRunRetentionAutomationService` 拥有生命周期,不依赖 Agent System 面板打开;保存 retention 设置只唤醒调度器重读配置,不同步执行清理。`slim_heavy_artifacts` 基于 Agent run storage class,路径归属词汇与 TT-Sync Agent run datasets 对齐,但 retention 不依赖同步 profile。明细受 `detailLimit` 限制但 totals 不截断,active run、缺失 terminal event、journal/storage 异常会进入 `blockedRuns`;单个执行失败会进入 `failedRuns`。
|
||||
- 输入框 Agent 快捷开关 `#ttas_agent_send_toggle` 可以在 Agent System 扩展抽屉中隐藏;该状态只是扩展 UI 偏好,不改变 `agentModeEnabled`、生成路由、Agent Host ABI、journal 或 Rust runtime。
|
||||
|
|
|
|||
|
|
@ -11,6 +11,14 @@ import {
|
|||
RUN_TIMELINE_PAGE_STEP_PX,
|
||||
runTimelineHeightBounds,
|
||||
} from './run-timeline-resize.js';
|
||||
import {
|
||||
canStartRunTimelineViewGesture,
|
||||
createRunTimelineViewGesture,
|
||||
resolveRunTimelineViewGesture,
|
||||
RUN_TIMELINE_VIEW_GESTURE_ACTION_DETAILS,
|
||||
RUN_TIMELINE_VIEW_GESTURE_ACTION_TIMELINE,
|
||||
shouldCancelRunTimelineViewGesture,
|
||||
} from './run-timeline-view-gesture.js';
|
||||
import {
|
||||
projectAgentInvocations,
|
||||
} from './run-invocation-projector.js';
|
||||
|
|
@ -82,6 +90,7 @@ function createAgentRunTimelineApp(options = {}) {
|
|||
resizeStartY: 0,
|
||||
resizeStartHeightPx: 0,
|
||||
resizeBounds: null,
|
||||
viewGesture: null,
|
||||
timelineScrollTop: 0,
|
||||
timelineViewportHeight: 1,
|
||||
unsubscribeSettings: null,
|
||||
|
|
@ -288,6 +297,7 @@ function createAgentRunTimelineApp(options = {}) {
|
|||
},
|
||||
unmounted() {
|
||||
this.stopResize(false);
|
||||
this.cancelViewGesture();
|
||||
this.$refs.subAgentDialog?.reset?.();
|
||||
if (this.timelineProjectionRefreshTimer) {
|
||||
clearTimeout(this.timelineProjectionRefreshTimer);
|
||||
|
|
@ -328,6 +338,7 @@ function createAgentRunTimelineApp(options = {}) {
|
|||
this.detailsOpen = false;
|
||||
this.timelineScrollTop = 0;
|
||||
this.subAgentTrayExpanded = false;
|
||||
this.cancelViewGesture();
|
||||
this.detail.reset();
|
||||
this.$refs.subAgentDialog?.reset?.();
|
||||
await this.loadRunHistory();
|
||||
|
|
@ -514,6 +525,47 @@ function createAgentRunTimelineApp(options = {}) {
|
|||
this.stickToBottomIfNeeded();
|
||||
});
|
||||
},
|
||||
startViewGesture(event) {
|
||||
if (this.viewGesture || !canStartRunTimelineViewGesture({
|
||||
event,
|
||||
target: event.target,
|
||||
collapsed: this.collapsed,
|
||||
resizing: this.resizing,
|
||||
detailsOpen: this.detailsOpen,
|
||||
selectedHasDetails: this.selectedHasDetails,
|
||||
})) {
|
||||
return;
|
||||
}
|
||||
this.viewGesture = createRunTimelineViewGesture(event, this.detailsOpen);
|
||||
},
|
||||
trackViewGesture(event) {
|
||||
if (shouldCancelRunTimelineViewGesture(this.viewGesture, event)) {
|
||||
this.cancelViewGesture(event);
|
||||
}
|
||||
},
|
||||
finishViewGesture(event) {
|
||||
const gesture = this.viewGesture;
|
||||
if (!gesture || event.pointerId !== gesture.pointerId) {
|
||||
return;
|
||||
}
|
||||
this.viewGesture = null;
|
||||
|
||||
const action = resolveRunTimelineViewGesture(gesture, event, {
|
||||
detailsOpen: this.detailsOpen,
|
||||
selectedHasDetails: this.selectedHasDetails,
|
||||
});
|
||||
if (action === RUN_TIMELINE_VIEW_GESTURE_ACTION_DETAILS) {
|
||||
this.openDetails();
|
||||
} else if (action === RUN_TIMELINE_VIEW_GESTURE_ACTION_TIMELINE) {
|
||||
this.showTimeline();
|
||||
}
|
||||
},
|
||||
cancelViewGesture(event = null) {
|
||||
if (event && this.viewGesture && event.pointerId !== this.viewGesture.pointerId) {
|
||||
return;
|
||||
}
|
||||
this.viewGesture = null;
|
||||
},
|
||||
onTimelineViewport(viewport) {
|
||||
this.timelineScrollTop = viewport.scrollTop;
|
||||
this.timelineViewportHeight = viewport.viewportHeight;
|
||||
|
|
@ -759,7 +811,15 @@ function createAgentRunTimelineApp(options = {}) {
|
|||
</div>
|
||||
</header>
|
||||
|
||||
<div v-if="!collapsed" ref="panelBody" class="ttas-run-body">
|
||||
<div
|
||||
v-if="!collapsed"
|
||||
ref="panelBody"
|
||||
class="ttas-run-body"
|
||||
@pointerdown.passive="startViewGesture"
|
||||
@pointermove.passive="trackViewGesture"
|
||||
@pointerup.passive="finishViewGesture"
|
||||
@pointercancel.passive="cancelViewGesture"
|
||||
>
|
||||
<section
|
||||
v-show="!detailsOpen"
|
||||
class="ttas-run-view ttas-run-view-events"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,98 @@
|
|||
export const RUN_TIMELINE_VIEW_GESTURE_ACTION_DETAILS = 'details';
|
||||
export const RUN_TIMELINE_VIEW_GESTURE_ACTION_TIMELINE = 'timeline';
|
||||
export const RUN_TIMELINE_VIEW_GESTURE_MIN_DISTANCE_PX = 64;
|
||||
export const RUN_TIMELINE_VIEW_GESTURE_AXIS_RATIO = 1.5;
|
||||
|
||||
const TOUCH_POINTER_TYPE = 'touch';
|
||||
const VERTICAL_CANCEL_DISTANCE_PX = 24;
|
||||
const VERTICAL_CANCEL_AXIS_RATIO = 1.2;
|
||||
const EXCLUDED_TARGET_SELECTOR = [
|
||||
'.ttas-run-header',
|
||||
'.ttas-run-resize-handle',
|
||||
'.ttas-run-detail-head',
|
||||
'.ttas-run-detail-nav',
|
||||
'.ttas-run-detail-actions',
|
||||
'.ttas-run-detail-block-head',
|
||||
'.ttas-run-detail-block pre',
|
||||
'.ttas-run-diff',
|
||||
'.ttas-subagent-tray',
|
||||
'a[href]',
|
||||
'input',
|
||||
'textarea',
|
||||
'select',
|
||||
'option',
|
||||
'[contenteditable="true"]',
|
||||
].join(', ');
|
||||
|
||||
function isPrimaryTouchPointer(event) {
|
||||
return event?.isPrimary === true && event.pointerType === TOUCH_POINTER_TYPE;
|
||||
}
|
||||
|
||||
function isGestureStartTarget(target) {
|
||||
const element = typeof target?.closest === 'function' ? target : target?.parentElement;
|
||||
return Boolean(element && typeof element.closest === 'function' && !element.closest(EXCLUDED_TARGET_SELECTOR));
|
||||
}
|
||||
|
||||
function gestureDelta(gesture, event) {
|
||||
return {
|
||||
dx: event.clientX - gesture.startX,
|
||||
dy: event.clientY - gesture.startY,
|
||||
};
|
||||
}
|
||||
|
||||
function hasHorizontalIntent(dx, dy) {
|
||||
return Math.abs(dx) >= RUN_TIMELINE_VIEW_GESTURE_MIN_DISTANCE_PX
|
||||
&& Math.abs(dx) >= Math.abs(dy) * RUN_TIMELINE_VIEW_GESTURE_AXIS_RATIO;
|
||||
}
|
||||
|
||||
export function canStartRunTimelineViewGesture({
|
||||
event,
|
||||
target,
|
||||
collapsed,
|
||||
resizing,
|
||||
detailsOpen,
|
||||
selectedHasDetails,
|
||||
} = {}) {
|
||||
if (!isPrimaryTouchPointer(event) || collapsed || resizing || !isGestureStartTarget(target)) {
|
||||
return false;
|
||||
}
|
||||
return Boolean(detailsOpen || selectedHasDetails);
|
||||
}
|
||||
|
||||
export function createRunTimelineViewGesture(event, detailsOpen) {
|
||||
return {
|
||||
pointerId: event.pointerId,
|
||||
startX: event.clientX,
|
||||
startY: event.clientY,
|
||||
detailsOpen: Boolean(detailsOpen),
|
||||
};
|
||||
}
|
||||
|
||||
export function shouldCancelRunTimelineViewGesture(gesture, event) {
|
||||
if (!gesture || event?.pointerId !== gesture.pointerId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { dx, dy } = gestureDelta(gesture, event);
|
||||
const absX = Math.abs(dx);
|
||||
const absY = Math.abs(dy);
|
||||
return absY >= VERTICAL_CANCEL_DISTANCE_PX && absY > absX * VERTICAL_CANCEL_AXIS_RATIO;
|
||||
}
|
||||
|
||||
export function resolveRunTimelineViewGesture(gesture, event, {
|
||||
detailsOpen,
|
||||
selectedHasDetails,
|
||||
} = {}) {
|
||||
if (!gesture || event?.pointerId !== gesture.pointerId || Boolean(detailsOpen) !== gesture.detailsOpen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { dx, dy } = gestureDelta(gesture, event);
|
||||
if (!hasHorizontalIntent(dx, dy)) {
|
||||
return null;
|
||||
}
|
||||
if (gesture.detailsOpen) {
|
||||
return dx > 0 ? RUN_TIMELINE_VIEW_GESTURE_ACTION_TIMELINE : null;
|
||||
}
|
||||
return dx < 0 && selectedHasDetails ? RUN_TIMELINE_VIEW_GESTURE_ACTION_DETAILS : null;
|
||||
}
|
||||
|
|
@ -635,6 +635,14 @@ body:is([data-generating="true"], [data-swiping="true"]) .ttas-agent-send-toggle
|
|||
overscroll-behavior: contain;
|
||||
}
|
||||
|
||||
@media (pointer: coarse) {
|
||||
.ttas-run-body,
|
||||
.ttas-run-event-scroll,
|
||||
.ttas-run-detail-scroll {
|
||||
touch-action: pan-y pinch-zoom;
|
||||
}
|
||||
}
|
||||
|
||||
.ttas-run-detail-scroll {
|
||||
flex: 1;
|
||||
height: auto;
|
||||
|
|
|
|||
|
|
@ -448,11 +448,31 @@ test('Agent run timeline view switching is not driven by horizontal scroll state
|
|||
assert.doesNotMatch(panelSource, /@scroll\.passive="onPagesScroll"/);
|
||||
assert.match(panelSource, /v-show="!detailsOpen"/);
|
||||
assert.match(panelSource, /v-if="detailsOpen"/);
|
||||
assert.match(panelSource, /run-timeline-view-gesture\.js/);
|
||||
assert.match(panelSource, /@pointerdown\.passive="startViewGesture"/);
|
||||
assert.match(panelSource, /@pointermove\.passive="trackViewGesture"/);
|
||||
assert.match(panelSource, /@pointerup\.passive="finishViewGesture"/);
|
||||
assert.match(panelSource, /@pointercancel\.passive="cancelViewGesture"/);
|
||||
|
||||
const showTimelineSource = sourceBetween(
|
||||
panelSource,
|
||||
'showTimeline() {',
|
||||
'onTimelineViewport(viewport) {',
|
||||
'startViewGesture(event) {',
|
||||
);
|
||||
const startGestureSource = sourceBetween(
|
||||
panelSource,
|
||||
'startViewGesture(event) {',
|
||||
'trackViewGesture(event) {',
|
||||
);
|
||||
const trackGestureSource = sourceBetween(
|
||||
panelSource,
|
||||
'trackViewGesture(event) {',
|
||||
'finishViewGesture(event) {',
|
||||
);
|
||||
const finishGestureSource = sourceBetween(
|
||||
panelSource,
|
||||
'finishViewGesture(event) {',
|
||||
'cancelViewGesture(event = null) {',
|
||||
);
|
||||
const measureTimelineSource = sourceBetween(
|
||||
panelSource,
|
||||
|
|
@ -465,15 +485,121 @@ test('Agent run timeline view switching is not driven by horizontal scroll state
|
|||
'async loadDetails() {',
|
||||
);
|
||||
assert.match(showTimelineSource, /this\.detailsOpen = false;\s*this\.detail\.reset\(\);/);
|
||||
assert.doesNotMatch(startGestureSource, /preventDefault/);
|
||||
assert.doesNotMatch(trackGestureSource, /preventDefault/);
|
||||
assert.doesNotMatch(finishGestureSource, /preventDefault/);
|
||||
assert.match(finishGestureSource, /this\.openDetails\(\);/);
|
||||
assert.match(finishGestureSource, /this\.showTimeline\(\);/);
|
||||
assert.match(measureTimelineSource, /if \(this\.collapsed \|\| this\.detailsOpen\) \{\s*return;\s*\}/);
|
||||
assert.match(stickToBottomSource, /if \(!this\.autoStick \|\| this\.collapsed \|\| this\.detailsOpen\) \{\s*return;\s*\}/);
|
||||
|
||||
assert.doesNotMatch(styleSource, /\.ttas-run-pages/);
|
||||
assert.doesNotMatch(styleSource, /scroll-snap-type/);
|
||||
assert.doesNotMatch(styleSource, /scroll-snap-align/);
|
||||
assert.match(styleSource, /touch-action:\s*pan-y pinch-zoom;/);
|
||||
assert.match(styleSource, /\.ttas-run-view/);
|
||||
});
|
||||
|
||||
test('Agent run timeline mobile view gesture commits only conservative touch intent', async () => {
|
||||
const gesture = await importFresh(
|
||||
'src/scripts/extensions/agent-system/src/run-timeline-view-gesture.js',
|
||||
);
|
||||
const target = { closest: () => null };
|
||||
const textTarget = { parentElement: target };
|
||||
const detailActionTarget = {
|
||||
closest(selector) {
|
||||
return selector.includes('.ttas-run-detail-actions') ? {} : null;
|
||||
},
|
||||
};
|
||||
const touch = (clientX, clientY, overrides = {}) => ({
|
||||
pointerId: 7,
|
||||
pointerType: 'touch',
|
||||
isPrimary: true,
|
||||
clientX,
|
||||
clientY,
|
||||
...overrides,
|
||||
});
|
||||
|
||||
assert.equal(gesture.canStartRunTimelineViewGesture({
|
||||
event: touch(200, 20),
|
||||
target,
|
||||
collapsed: false,
|
||||
resizing: false,
|
||||
detailsOpen: false,
|
||||
selectedHasDetails: true,
|
||||
}), true);
|
||||
assert.equal(gesture.canStartRunTimelineViewGesture({
|
||||
event: touch(200, 20),
|
||||
target: textTarget,
|
||||
collapsed: false,
|
||||
resizing: false,
|
||||
detailsOpen: false,
|
||||
selectedHasDetails: true,
|
||||
}), true);
|
||||
assert.equal(gesture.canStartRunTimelineViewGesture({
|
||||
event: touch(200, 20, { pointerType: 'mouse' }),
|
||||
target,
|
||||
collapsed: false,
|
||||
resizing: false,
|
||||
detailsOpen: false,
|
||||
selectedHasDetails: true,
|
||||
}), false);
|
||||
assert.equal(gesture.canStartRunTimelineViewGesture({
|
||||
event: touch(200, 20, { isPrimary: false }),
|
||||
target,
|
||||
collapsed: false,
|
||||
resizing: false,
|
||||
detailsOpen: false,
|
||||
selectedHasDetails: true,
|
||||
}), false);
|
||||
assert.equal(gesture.canStartRunTimelineViewGesture({
|
||||
event: touch(200, 20),
|
||||
target,
|
||||
collapsed: false,
|
||||
resizing: false,
|
||||
detailsOpen: false,
|
||||
selectedHasDetails: false,
|
||||
}), false);
|
||||
assert.equal(gesture.canStartRunTimelineViewGesture({
|
||||
event: touch(200, 20),
|
||||
target: detailActionTarget,
|
||||
collapsed: false,
|
||||
resizing: false,
|
||||
detailsOpen: true,
|
||||
selectedHasDetails: false,
|
||||
}), false);
|
||||
|
||||
const timelineSwipe = gesture.createRunTimelineViewGesture(touch(220, 30), false);
|
||||
assert.equal(gesture.resolveRunTimelineViewGesture(timelineSwipe, touch(150, 34), {
|
||||
detailsOpen: false,
|
||||
selectedHasDetails: true,
|
||||
}), gesture.RUN_TIMELINE_VIEW_GESTURE_ACTION_DETAILS);
|
||||
assert.equal(gesture.resolveRunTimelineViewGesture(timelineSwipe, touch(150, 34), {
|
||||
detailsOpen: false,
|
||||
selectedHasDetails: false,
|
||||
}), null);
|
||||
assert.equal(gesture.resolveRunTimelineViewGesture(timelineSwipe, touch(170, 88), {
|
||||
detailsOpen: false,
|
||||
selectedHasDetails: true,
|
||||
}), null);
|
||||
assert.equal(gesture.resolveRunTimelineViewGesture(timelineSwipe, touch(150, 34), {
|
||||
detailsOpen: true,
|
||||
selectedHasDetails: true,
|
||||
}), null);
|
||||
|
||||
const detailSwipe = gesture.createRunTimelineViewGesture(touch(80, 30), true);
|
||||
assert.equal(gesture.resolveRunTimelineViewGesture(detailSwipe, touch(154, 33), {
|
||||
detailsOpen: true,
|
||||
selectedHasDetails: false,
|
||||
}), gesture.RUN_TIMELINE_VIEW_GESTURE_ACTION_TIMELINE);
|
||||
assert.equal(gesture.resolveRunTimelineViewGesture(detailSwipe, touch(8, 33), {
|
||||
detailsOpen: true,
|
||||
selectedHasDetails: false,
|
||||
}), null);
|
||||
assert.equal(gesture.shouldCancelRunTimelineViewGesture(detailSwipe, touch(84, 72)), true);
|
||||
assert.equal(gesture.shouldCancelRunTimelineViewGesture(detailSwipe, touch(150, 60)), false);
|
||||
});
|
||||
|
||||
test('Agent run timeline refresh predicates use narrated model turns explicitly', async () => {
|
||||
const panelSource = await readFile(path.join(
|
||||
REPO_ROOT,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue