Skip to content
Closed
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
2 changes: 1 addition & 1 deletion docs/UI_CONTRACT.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ This contract preserves the approved MVP concept and prevents feature ownership
- The Files viewer renders Markdown (`.md`, `.markdown`, `.mdown`, `.mkd`) as formatted GitHub-Flavored Markdown with fenced code blocks syntax-highlighted, and syntax-highlights recognized code files by the language detected from their extension while unknown languages stay plain monospaced text. All rendered output is sanitized: raw HTML and scripts never render and event-handler attributes and dangerous link schemes such as `javascript:` are neutralized. Activating an `http(s)` link opens it in the built-in browser while other schemes are inert, only embedded `data:` images render while remote or relative sources fall back to alt text or a placeholder without a network request, and text or code above the rich-rendering size threshold falls back to plain text with a notice.
- An authenticated connection or heartbeat alone never shows agent presence. A branded badge appears only after that agent actually issues a browser command, and its cursor appears only after a real pointer position exists. Claude uses `#D97757`, Codex `#10A37F`, Qwen `#6D44E8`, Kimi `#7C5CFC`, OpenCode `#5A5858`, Hermes `#D6A700`, and an unknown provider `#7A8291`. Browser Settings separately controls indicator visibility without revoking browser access; the agent-access kill switch revokes the connection itself.
- With window snapping enabled, drag and resize use a hidden `10px` grid and a `10px` magnetic threshold for neighboring edges, centers, and a consistent `20px` gap.
- One trusted context dispatcher owns right-clicks on the canvas. Truly empty canvas offers region creation, a sticky note, the Settings-configured launcher submenu, Browser, and Settings. Holding RMB on empty canvas also opens the configurable radial quick launcher: moving toward an action and releasing activates it, while a stationary right-click continues to open the standard empty-canvas menu. Region and note right-clicks remain context-specific. A region offers rename, color, a note at the pressed point, and region-only deletion. A note frame offers edit, bring-to-front, and deletion. Terminal, Browser, plugin, textarea/input, and contenteditable surfaces keep their native context menus; middle-button panning is unchanged. `Cmd/Ctrl+K` opens a searchable palette over live sessions and the same global creation/launch actions. Regions persist independently of terminal restore, render behind HOME and windows, resize from every edge/corner, and move every terminal, plugin, Browser, or note whose complete bounds were inside the region at drag start. Attached contents follow the region continuously during the gesture and persist once at pointer release; deleting a region never deletes its contents. Sticky notes persist bounded text and geometry, snap like other windows, and resize from every edge/corner. General Settings exposes independent **Save colored regions after exit** and **Save notes after exit** controls. Both default on to preserve existing profiles. Turning either off removes only that collection from disk persistence without deleting its live canvas objects; turning it back on saves the current collection, while leaving it off starts the next launch with that collection empty.
- One trusted context dispatcher owns right-clicks on the canvas. Truly empty canvas offers region creation, a sticky note, the Settings-configured launcher submenu, Browser, Files, a **Tools** submenu, and Settings. **Tools** holds a single **Auto-sort** action whose criteria (by task, i.e. subagents to the right of the session that spawned them; by status; by project folder; grid) are radio rows in that submenu; there is no separate layouts surface, toolbar, or saved preset. Auto-sort moves the windows of the current multi-selection when it holds at least two, or else every terminal, plugin, and Browser window that is not completely inside a region (companion mascots move only when selected); sticky notes, materials, and Files cards never move. The sorted block keeps the snapping grid and `20px` gap when snapping is on, and shifts right or down until it is clear of HOME, regions, notes, materials, Files cards, and windows it does not move. The moved windows show the existing selection outline for a few seconds or until the next press; it is presentation-only and does not select them. They glide into place unless reduced motion is requested, and **Undo auto-sort** stays in the same submenu while any of them is still where the sort put it. Undo restores only those untouched windows; there is no global undo shortcut, so terminal and text inputs keep their keys. The criterion is remembered only for the running session. Holding RMB on empty canvas also opens the configurable radial quick launcher: moving toward an action and releasing activates it, while a stationary right-click continues to open the standard empty-canvas menu. Region and note right-clicks remain context-specific. A region offers rename, color, a note at the pressed point, and region-only deletion. A note frame offers edit, bring-to-front, and deletion. Terminal, Browser, plugin, textarea/input, and contenteditable surfaces keep their native context menus; middle-button panning is unchanged. `Cmd/Ctrl+K` opens a searchable palette over live sessions and the same global creation/launch actions. Regions persist independently of terminal restore, render behind HOME and windows, resize from every edge/corner, and move every terminal, plugin, Browser, or note whose complete bounds were inside the region at drag start. Attached contents follow the region continuously during the gesture and persist once at pointer release; deleting a region never deletes its contents. Sticky notes persist bounded text and geometry, snap like other windows, and resize from every edge/corner. General Settings exposes independent **Save colored regions after exit** and **Save notes after exit** controls. Both default on to preserve existing profiles. Turning either off removes only that collection from disk persistence without deleting its live canvas objects; turning it back on saves the current collection, while leaving it off starts the next launch with that collection empty.
- Context menus, region editors, the radial launcher, and the command palette use official Lucide/provider assets and shared application color tokens. Their dimensions follow root `--ui-scale`. Appearance owns the `0.85–1.25` scale in `0.05` steps and the sage/lilac/night system palette; the scale changes UI chrome rather than canvas zoom or terminal font size. Agents owns the independent context-menu and radial-launcher subsets and their safe reset defaults.
- Settings uses the approved two-pane modal grammar: a persistent icon-labelled sidebar for all seven sections, a separate current-section header, and a scrollable content pane. Settings must not fall back to the legacy light slide-over with one horizontal segmented tab strip. Simple settings use compact copy/control rows; all Settings chrome reads the active system tokens, official Lucide assets, and root `--ui-scale`.
- General Settings places the macOS/Windows/Linux/Custom keyboard preset after language, followed by an expandable Keyboard shortcuts section for canvas, terminal, Codex editor and canvas navigation bindings. Recording a shortcut selects Custom; choosing a named preset replaces bindings. Optional commands can be disabled, while submit, newline and whole-draft selection remain bound. Canvas and terminal bindings update immediately; native Codex editor bindings apply to new or restarted cards. Focused terminal and text inputs retain ownership of canvas keyboard shortcuts on every platform.
Expand Down
1 change: 1 addition & 0 deletions docs/UI_CONTRACT.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
- Мини-карта центрирует камеру по нажатию без изменения масштаба. В Controls выбирается **Клик** (по умолчанию, движение указателя игнорируется) или **Перетаскивание** (мини-карта захватывает указатель и непрерывно двигает камеру); клавиши-стрелки работают в обоих режимах.
- Canvas двигается RTS-style, пока указатель находится в пределах `56px` от viewport edge над пустым canvas; скорость линейно возрастает до `900px/s` на самой границе. Edge panning выключен по умолчанию и включается в Settings. Motion останавливается над interactive surfaces и во время drag-pan.
- Dialog close actions остаются внутри своей header/control row с одинаковым inset и не перекрывают field, outline или panel boundary.
- Контекстное меню пустого канваса содержит подменю **Инструменты** с одним действием **Автосортировка**. Критерии (по задачам — субагенты справа от запустившей их сессии; по статусу; по папке проекта; сеткой) — пункты-переключатели в том же подменю, без отдельной панели, тулбара или сохранённых раскладок. Сортируются окна текущего множественного выделения, если их хотя бы два, а иначе — все окна терминалов, плагинов и браузера, которые не лежат целиком внутри региона (маскоты — только если выделены); заметки, материалы и карточки Files не двигаются. При включённой привязке блок держит сетку и зазор `20px`, и сдвигается вправо или вниз, пока не перестанет задевать HOME, регионы, заметки, материалы, карточки Files и неперемещаемые окна. Перемещённые окна на несколько секунд (или до следующего нажатия) получают обычную обводку выделения — только визуально, без выделения — и плавно переезжают, если не включено уменьшение движения. Пункт **Отменить автосортировку** остаётся в подменю, пока хотя бы одно окно стоит там, куда его поставила сортировка, и возвращает только нетронутые окна. Глобального сочетания отмены нет: клавиши остаются у терминалов и полей ввода. Выбранный критерий помнится только до перезапуска.

## Приёмочные проверки

Expand Down
1 change: 1 addition & 0 deletions docs/UI_CONTRACT.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
- 在空白 canvas 上 drag 始终执行 pan。新配置中,空白 canvas 上的普通 scroll 会沿两个轴移动,pinch 与 `Cmd/Ctrl + scroll` 会以指针为中心缩放。**Use scroll wheel to zoom** 恢复历史 wheel zoom,并在迁移现有配置时自动启用。只有 focused input widget 会中断 canvas wheel/pinch;在 unfocused 或不可聚焦 widget 上,事件仍归 canvas。**Wheel/pinch capture over widgets** 有 Off、On、Key 三种模式:Off 保留 focused-widget ownership,On 始终只交给 canvas,Key 仅在按住绑定键时交给 canvas。因此,focused live Browser page 在 Off 或 Key 未按下时会由普通 scroll 原生滚动;unfocused Browser、On、激活的 Key binding、完整 override、pinch 与 `Cmd/Ctrl + scroll` 都归 canvas。Browser wheel sequence 开始时选定的 owner 会保持到 250 ms 没有 wheel event。Browser summary 与 placeholder 始终归 canvas。新配置默认使用 Key,在 macOS 为 `Command`,其他平台为 `Ctrl`。独立的 **Canvas navigation override**(macOS 默认 `Option`,其他平台默认 `Alt`)接管 wheel/pinch 与 drag,也允许单独 Command/Ctrl。按住完整 override 时所有 canvas surface 显示 `grab`;已开始的 pan 会显示 `grabbing`,并由 canvas 持有到 pointer up/cancel。
- 指针停在空白 canvas 的 viewport edge `56px` 范围内时,canvas 以 RTS 风格 edge-pan;速度线性增加,到边缘达到 `900px/s`。Edge panning 默认关闭,在 Settings 启用。指针位于 interactive surface 或正在 drag-pan 时暂停。
- Dialog close action 留在自己的 header/control row 内,使用一致 inset,不覆盖 field、outline 或 panel boundary。
- 空白画布的右键菜单包含 **Tools** 子菜单,其中只有一个 **Auto-sort** 操作。排序条件(按任务:子代理放在启动它的会话右侧;按状态;按项目文件夹;网格)是同一子菜单中的单选行,不提供单独的面板、工具栏或保存的布局。当前多选中至少有两个窗口时,Auto-sort 只移动这些窗口;否则,移动所有未完全位于区域内的终端、插件与 Browser 窗口(吉祥物仅在被选中时移动);便签、素材与 Files 卡片从不移动。开启吸附时排列保持 10px 网格与 `20px` 间距,并向右或向下移动,直到不再覆盖 HOME、区域、便签、素材、Files 卡片和未参与排序的窗口。被移动的窗口在几秒内(或直到下一次按下)显示现有的选中轮廓,这只是视觉提示,不会选中它们;在未要求减少动态效果时平滑移动。只要仍有窗口停在排序放置的位置,**Undo auto-sort** 就保留在同一子菜单中,并且只恢复这些未被改动的窗口。没有全局撤销快捷键,终端与文本输入保留自己的按键。所选条件只在本次运行期间记住。

## 验收检查

Expand Down
3 changes: 2 additions & 1 deletion src/renderer/src/assets/icons/lucide/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
These SVG files are unmodified icons downloaded from the official
[`lucide-icons/lucide`](https://github.com/lucide-icons/lucide) repository on
2026-08-04, with menu and Settings navigation additions fetched from the same
source on 2026-08-30 and canvas material additions on 2026-09-28.
source on 2026-08-30 and canvas material additions on 2026-09-28, and the canvas
Tools menu additions (`wrench`, `undo-2`) on 2026-10-12.
They are vendored as local assets so CanvasTTY does not need to
execute or fetch an icon package from npm.

Expand Down
14 changes: 14 additions & 0 deletions src/renderer/src/assets/icons/lucide/undo-2.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions src/renderer/src/assets/icons/lucide/wrench.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 3 additions & 2 deletions src/renderer/src/components/CanvasMenuPrimitives.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,10 @@ export function CanvasMenuSub({ children }: { children: ReactNode }): React.JSX.
return <span className="canvas-menu__sub">{children}</span>;
}

export function CanvasMenuChevron(): React.JSX.Element {
/** `side`: the submenu opens to that side, and the chevron points there; without it, it points down. */
export function CanvasMenuChevron({ side }: { side?: "left" | "right" } = {}): React.JSX.Element {
return (
<span className="canvas-menu__chevron">
<span className={side ? `canvas-menu__chevron canvas-menu__chevron--${side}` : "canvas-menu__chevron"}>
<UiIcon name="chevron" size="1em" />
</span>
);
Expand Down
6 changes: 6 additions & 0 deletions src/renderer/src/components/UiIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ import slidersHorizontalIcon from "../assets/icons/lucide/sliders-horizontal.svg
import stickyNoteIcon from "../assets/icons/lucide/sticky-note.svg";
import terminalIcon from "../assets/icons/lucide/square-terminal.svg";
import trashIcon from "../assets/icons/lucide/trash-2.svg";
import undoIcon from "../assets/icons/lucide/undo-2.svg";
import workingIcon from "../assets/icons/lucide/loader-circle.svg";
import wrenchIcon from "../assets/icons/lucide/wrench.svg";
import zoomInIcon from "../assets/icons/lucide/zoom-in.svg";
import zoomOutIcon from "../assets/icons/lucide/zoom-out.svg";

Expand Down Expand Up @@ -84,6 +86,8 @@ export type UiIconName =
| "clipboard-paste"
| "ellipsis"
| "trash"
| "undo"
| "wrench"
| "working"
| "attention"
| "error"
Expand Down Expand Up @@ -137,6 +141,8 @@ const ICONS: Record<UiIconName, string> = {
"clipboard-paste": clipboardPasteIcon,
ellipsis: ellipsisIcon,
trash: trashIcon,
undo: undoIcon,
wrench: wrenchIcon,
working: workingIcon,
attention: attentionIcon,
error: errorIcon,
Expand Down
Loading
Loading