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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Breathing online machine indicator

Status: implemented
Translation: current

[中文](2026-10-20-machine-online-motion.zh.md)

PR: [#1742](https://github.com/LodyAI/Lody/pull/1742)

## Abstract

The user requested motion for the static green online mark on machine cards.
The outer halo now expands and contracts at a constant speed over 2.8 seconds
while the solid centre stays green. Offline machines remain static, and reduced
motion retains the original stationary halo. This changes presentation only; the
existing presence value still determines whether a machine is online.

## Decision

`WorkspaceMachineAccordionRow` owns the same mark in collapsed and expanded
cards. Its separate decorative halo animates only transform and opacity, keeping
the dot's layout box, accessible status label, and card interactions unchanged.
Linear timing gives uniform scaling in each direction, with 1.4 seconds to
expand and 1.4 seconds to contract. The halo unmounts when `isOnline` becomes
false. CSS handles the loop and the reduced-motion preference without a timer,
React updates per frame, or network requests. The centre stays fully visible so
motion never makes an online machine look disconnected.

The existing MachinesDesktopLayout stories gain a controlled presence preview
using the real collapsed card. It supports inspection of online/offline transitions
without requiring a real machine or transport.

## Verification

The existing accordion behavior test passes. Components type checking, targeted
typed lint, and formatting of the changed TypeScript files pass. A real browser
run of the OnlineStatusMotion story samples the halo at evenly spaced
deterministic animation times: equal time steps give equal scale changes in
each direction, while the centre colour, centre opacity and dot box remain
unchanged. It also
verifies online/offline/online transitions and that reduced motion removes the
animation and preserves a static halo. A two-cycle browser recording provides
the visual preview.

The full `pnpm check` passes, including workspace type checking, typed and
Effect lint, all configured workspace tests, i18n validation, and boundary
guards. `pnpm format` also passes. The documentation check retains six existing
missing Kimi/Pi submodule links in the bilingual subagent-events note.

A cropped recording of the real card with synthetic machine data:

![Linear online halo animation](../../assets/machine-online-motion/linear.gif)
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# 机器在线标记的呼吸动画

Status: implemented
Translation: current

[English](2026-10-20-machine-online-motion.md)

PR: [#1742](https://github.com/LodyAI/Lody/pull/1742)

## 摘要

用户希望机器卡片原本静态的绿色在线标记具有动态效果。
现在外圈以 2.8 秒为周期匀速放大、匀速缩小并改变透明度,中心绿点保持常亮。
离线机器保持静态;启用减少动态效果时,在线外圈也恢复静态。
本次只改变展示,机器是否在线仍由现有的 presence 状态决定。

## 决定

`WorkspaceMachineAccordionRow` 统一渲染折叠和展开卡片中的标记。
独立的装饰外圈只动画化 transform 和 opacity,圆点的布局尺寸、可访问状态标签及
卡片交互保持不变。使用线性时序,放大与缩小各用 1.4 秒,每个方向的缩放速度保持
恒定。`isOnline` 变为 false 时卸载外圈。循环和减少动态效果偏好
交给 CSS 处理,不增加定时器、逐帧 React 更新或网络请求。
中心始终保持可见,避免动画让在线机器看起来断开了连接。

现有 MachinesDesktopLayout stories 增加受控在线状态预览,使用真实的折叠卡片,
无需真实机器或传输连接即可检查在线和离线切换。

## 验证

现有折叠卡片行为测试通过。组件类型检查、针对修改文件的类型感知 lint,以及修改的
TypeScript 文件格式化通过。浏览器运行真实的 OnlineStatusMotion story,在等间隔的
确定动画时间点取样:每个方向的等间隔时间产生等量缩放变化,中心颜色、中心透明度
及圆点布局尺寸保持不变。另验证了在线/离线/在线切换,以及减少动态效果会取消动画
并保留静态外圈。浏览器录制的两个循环用于动态预览。

完整的 `pnpm check` 通过,包括工作区类型检查、类型感知与 Effect lint、全部配置的
工作区测试、国际化验证和边界检查;`pnpm format` 也通过。文档检查仍有
subagent-events 双语笔记中原有的六个本地 Kimi/Pi 子模块缺失链接。

以下为使用合成机器数据的真实卡片局部录制:

![Linear online halo animation](../../assets/machine-online-motion/linear.gif)
15 changes: 15 additions & 0 deletions packages/components/src/components/settings/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Settings surfaces

Binding guidelines live in [AGENTS.md](AGENTS.md).

## Machine connection marks

`workspace-machine-accordion.tsx` renders the connection mark for collapsed and
expanded machine cards. An online machine keeps its solid green centre while
its halo expands and contracts at a constant speed over 2.8 seconds, using linear
CSS transform and opacity animation. Each direction takes 1.4 seconds.
Offline machines keep a static gray dot. Reduced motion keeps the online halo
static. Motion follows the existing `isOnline` value and adds no presence polling.

The [OnlineStatusMotion story](../../stories/DevicesDesktopLayout.stories.tsx)
uses the real machine card with a controlled online/offline value for inspection.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ export type WorkspaceMachineAccordionMeta = {
agentCount: number;
};

const onlineHaloBreathe = stylex.keyframes({
'0%, 100%': { transform: 'scale(0.875)', opacity: 0.12 },
'50%': { transform: 'scale(1.2)', opacity: 0.28 },
});

const styles = stylex.create({
summary: {
display: 'flex',
Expand Down Expand Up @@ -102,6 +107,7 @@ const styles = stylex.create({
},
},
dot: {
position: 'relative',
flexShrink: 0,
width: '10px',
height: '10px',
Expand All @@ -111,7 +117,23 @@ const styles = stylex.create({
},
dotOnline: {
backgroundColor: colors.success,
boxShadow: `0 0 0 3px color-mix(in oklab, ${colors.success} 20%, transparent)`,
},
/** Only the halo breathes; the solid centre continues to say "online". */
onlineHalo: {
position: 'absolute',
inset: '-3px',
borderRadius: radius.full,
cornerShape: corner.round,
backgroundColor: colors.success,
opacity: 0.2,
pointerEvents: 'none',
animationName: {
default: onlineHaloBreathe,
'@media (prefers-reduced-motion: reduce)': 'none',
},
animationDuration: '2.8s',
animationTimingFunction: 'linear',
animationIterationCount: 'infinite',
},
name: {
flexGrow: 1,
Expand Down Expand Up @@ -270,7 +292,9 @@ export function WorkspaceMachineAccordionRow({
role="img"
aria-label={statusLabel}
{...stylex.props(styles.dot, meta.isOnline && styles.dotOnline)}
/>
>
{meta.isOnline ? <span aria-hidden {...stylex.props(styles.onlineHalo)} /> : null}
</span>
<span {...stylex.props(styles.name)}>{machineName}</span>
<Summary meta={meta} showOwner={false} inRow />
<ChevronDown
Expand Down
34 changes: 34 additions & 0 deletions packages/components/src/stories/DevicesDesktopLayout.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Meta, StoryObj } from '@storybook/react';
import { useMemo, useState } from 'react';
import { fn } from 'storybook/test';
import { Button } from '@lody/ui/button';
import type {
AgentConfigId,
AgentConfigMeta,
Expand Down Expand Up @@ -451,3 +452,36 @@ export const OfflinePrivateMachine: Story = {
export const WaitingForResourceSample: Story = {
args: { resourcePending: true },
};

/** A real card with controlled presence, for motion and reduced-motion inspection. */
function MachineOnlineStatusPreview() {
const [isOnline, setIsOnline] = useState(true);
const previewMeta: WorkspaceMachineAccordionMeta = {
machine: {
id: 'machine-status-preview' as MachineId,
name: 'Docker Agent Workspace',
os: 'Linux',
cliVersion: '0.57.1',
sessions: [],
raceLimits: {},
},
isOnline,
isLocal: true,
isPrivate: false,
owner: null,
directoryCount: 4,
agentCount: 3,
};
return (
<div className="space-y-4">
<WorkspaceMachineCollapsedRow meta={previewMeta} onExpand={fn()} />
<Button variant="secondary" size="small" onClick={() => setIsOnline((online) => !online)}>
Toggle online status
</Button>
</div>
);
}

export const OnlineStatusMotion: Story = {
render: () => <MachineOnlineStatusPreview />,
};
Loading