fix(welcome,sidebar): 侧栏布局优化 + 面板收起修复#191
Open
SZhenY wants to merge 246 commits into
Open
Conversation
SZhenY
commented
Jun 30, 2026
- 欢迎页侧栏模式拆分布局:标题行(快速连接+收起按钮) / 操作行(新建按钮+导入提示)
- 标签页模式保持单行布局,按钮在右上角
- 欢迎侧边栏最小宽度 160→200px,防止按钮溢出
- 资源面板收起时添加 visible 控制,修复壁纸高透明度下透出
- PrimaryButton 文字水平居中
fix: 修复ssh密钥认证连接失败
…nux 桌面集成 Security fixes / 安全修复: - jeff141#12 Windows 命令注入: open_with_os 改用 ShellExecuteW + sanitize_filename - jeff141#10 编辑会话密码回显: 不再回填真实密码, 留空保留原密码 - jeff141#8 密码内存清零: 新增 Secret 类型(zeroize), Drop 清零 + Debug 脱敏 Features / 新增: - jeff141#9 完整 i18n: 中/英双语, 运行时实时切换(Slint @tr + bundled .po + Rust t()) - 私钥文件选择器 + .pub 容错 + 路径统一 / - Linux: meatshell.desktop + install-linux.sh + xdg_app_id(Wayland 图标) - README 截图(docs/screenshots, 敏感信息已打码) 详见 CHANGELOG.md / see CHANGELOG.md for details. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The fullwidth plus (U+FF0B +) had no glyph in the Latin font Slint picks for English text, so it rendered as a tofu box. Use an ASCII "+" instead (renders in any font; Chinese button now shows "+ 新建会话"). 全角加号在英文(拉丁字体)语境下没有字形 → 豆腐块。改用 ASCII "+",任何字体都能 渲染;中文按钮也相应改为 "+ 新建会话"。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add an "Import ~/.ssh/config" button on the welcome page. Parses the standard SSH config (Host / HostName / User / Port / IdentityFile; wildcard `Host *` blocks skipped) and adds each host as a session, skipping duplicates by alias or host+user. Hosts with an IdentityFile default to key auth. Includes a unit test. 欢迎页新增「导入 ~/.ssh/config」按钮:解析标准 SSH 配置(Host/HostName/User/ Port/IdentityFile,跳过 `Host *` 通配块),将主机加为会话并按别名或 host+user 去 重;带 IdentityFile 的默认密钥认证。含单元测试。 Also: bump to v0.2.3, update CHANGELOG. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the SSH-config import action out of the welcome header (cleaner) into the gear/settings menu as a third item next to language toggle and About. The import result hint still shows in the Quick-connect header. 把导入操作从欢迎页头部(挤在新建会话旁不够美观)移到齿轮设置菜单,作为第三个菜单 项,与语言切换、关于并列;导入结果提示仍显示在快速连接栏。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add .github/workflows/release.yml: builds native binaries for Windows (x86_64), Linux (x86_64), and macOS (arm64 + x86_64) via a matrix. Installs the Slint/rfd/GTK system deps on the Linux runner. On a `v*` tag it attaches the archives to a GitHub Release; on manual dispatch it uploads workflow artifacts. 新增三平台(Windows/Linux/macOS arm64+x86)构建发布工作流;Linux runner 自动安装 Slint/rfd/GTK 依赖;打 v* tag 自动建 Release 并上传产物。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This reverts commit 3688f1c.
install-linux.sh now picks up the `meatshell` binary sitting next to it (the release-package layout) when no argument is given, falling back to the source-tree ./target/release path only when there's no sibling binary. Also auto-chmod +x the binary. README (zh/en) gains a Download & install section covering the GitHub Actions builds for Windows / Linux / macOS. install-linux.sh 默认用同目录的 meatshell(发布包布局),无参数即可装;源码树下 才回退到 ./target/release。并自动给二进制加可执行权限。README 中英补充三平台下载 与 Linux 傻瓜式安装说明。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GitHub Actions only runs workflows under .github/workflows/. The release workflow had ended up at .github/release.yml (which GitHub treats as a release- notes config, not a workflow), so it never triggered. Move it back. GitHub Actions 只运行 .github/workflows/ 下的工作流;文件被放到了 .github/ release.yml(被当成 release notes 配置,不是 workflow),不会触发。移回正确位置。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
text-muted (#6b6f7a) was only ~3.4:1 on the dark background — below WCAG AA — so placeholders, secondary labels and disk details were hard to read. Brighten text-muted to #9196a3 and text-secondary slightly to #b4b9c4. text-muted 在深色背景上对比度仅 ~3.4:1(低于 WCAG AA),占位符/次要标签/磁盘明细 偏暗看不清。提亮 text-muted 与 text-secondary。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Connections can now tunnel through a SOCKS5 (socks5://) or HTTP CONNECT (http://) proxy with optional user:pass@ credentials, for network-restricted environments. Resolved from the per-session "Proxy" field, falling back to the $ALL_PROXY environment variable; empty = direct. src/proxy.rs establishes the tunneled TcpStream (tokio-socks for SOCKS5, a hand-rolled HTTP CONNECT) and the existing connection code hands it to russh's connect_stream. Verified end-to-end: meatshell -> local SOCKS5 proxy -> server, both shell and SFTP. 新增 SSH/SFTP 经 SOCKS5 / HTTP CONNECT 代理(支持 user:pass@ 认证),用于网络受限 环境。会话「代理」字段优先,留空则用 $ALL_PROXY,再空直连。已端到端验证: meatshell -> 本地 SOCKS5 代理 -> 服务器,shell 与 SFTP 均走代理。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
jeff141#16 (perf): pipelined SFTP upload — keep ~32 WRITE requests in flight on a dedicated raw SFTP channel instead of write-and-wait, hiding RTT (was ~15x slower than scp). Verified: 3 MB upload, remote md5 matches local exactly. jeff141#18 (bug): dragging the SFTP panel up cleared recent terminal output, because vt100's set_size truncates the grid from the bottom. Before shrinking, save the top rows to scrollback and scroll so the bottom (recent) rows stay visible. jeff141#15 (security): debug logs recorded raw SSH keystroke bytes (could include passwords); log only the byte length now (ssh.rs + app.rs). jeff141#16 上传流水线(并发 32 个 WRITE,藏住 RTT,原比 scp 慢约 15 倍);已验证 3MB 上传 远端 md5 与本地完全一致。jeff141#18 上拉面板清空输出(vt100 缩小从底部截断),改为缩小前 存顶部行入回滚并滚动保留底部。jeff141#15 debug 日志不再记录原始按键(可能含密码)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- 新增 format_mem_mib():总内存 >=1GiB 时自动用 GiB 单位显示 - StatRow detail 文本固定宽度改为 min-width + stretch,不再被进度条挤压 - 大数值(>=100G)或整数G值省略小数点,进一步缩短字符串长度 - detail min-width 从 56px 提升至 64px
The earlier jeff141#15 fix only covered the byte-length log site. Two more leaks remained in wire_key_input: send_key logged the raw key string at debug level, and the [KEY_DIAG] IME diagnostic logged Shift-typed key code points at info level (no RUST_LOG required). Both could expose password characters. Add redact_key(): reveals only C0/C1 control code points (the IME markers the diagnostics rely on) and masks every printable character. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add two non-SSH transports that reuse the full terminal pipeline: - Serial (src/serial.rs): opens COM3 / /dev/ttyUSB0 via the serialport crate (blocking reader thread + spawn_blocking writes), configurable baud / data bits / stop bits / parity / flow control. Closes jeff141#14. - Telnet (src/telnet.rs): TCP with RFC 854 IAC option negotiation (suppress-go-ahead, echo, NAWS window size), strips IAC from the data stream, escapes 0xFF in input, re-sends NAWS on resize, and tunnels through the existing SOCKS5/HTTP proxy. Closes jeff141#17. Session gains a kind field (ssh/serial/telnet) plus serial settings; old configs default to ssh. The session dialog gets a connection-type selector and per-kind fields. Serial/Telnet have no SFTP or resource monitor, so those panels are hidden/labelled accordingly. CI installs libudev-dev for the serialport crate on Linux. Verified: telnet end-to-end against a local echo server (IAC stripped, bidirectional input/output), serial worker error path (open COM99 -> localized error in terminal), and all three dialog modes render correctly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…n shrink The previous shrink fix scrolled the screen up by the full row delta and saved that many top rows to scrollback. When the grid was not full (a fresh shell with a few prompt lines and blank space below), this pushed the real content off the top into history, leaving a blank screen with the cursor stranded at the top; rapid up/down dragging repeated it until the prompt was gone. Now scroll up only by max(0, cursor_row+1 - new_rows): rows below the cursor are unused blanks and truncate for free, so content and the prompt stay put and rapid resizing is stable. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Selection endpoints were stored as visible-window rows, so when the view auto-scrolled during a drag (dragging past the bottom edge of a short terminal to select more than one screen of scrollback) the anchor could not stay pinned to content that scrolled off-window. Releasing then copied only the final visible window, losing everything above it — so a top-to-bottom selection of scrolled-back output never captured all of it. Store the anchor and focus as absolute (combined_row, col) indices over the virtual history+live buffer. vis_to_abs maps a visible row to its absolute index for the current view; selection_rects_visible clips highlights to the window; extract_selection_text reads the whole selection from the combined buffer regardless of the current scroll position. Auto-scroll now just moves the view and re-points the focus at the edge row, leaving the absolute anchor fixed. Removes the old visible-coordinate helpers. Unit tests cover the visible<->absolute mapping, history+live extraction, view-independence of a copy, and highlight clipping. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
SFTP delete is irreversible (no trash), so a misclick on the context-menu Delete item could silently destroy a file. Gate it behind an in-app ConfirmDialog (ui/confirm_dialog.slint) styled to match the rest of meatshell — a dark card over a dimmed backdrop, like SessionDialog, with a red destructive button and the full path shown. The confirmation lives in the UI layer: the context-menu delete now opens the modal and stashes the target; the existing Rust delete only fires on confirm. Backdrop click or Cancel dismisses without deleting. Verified end-to-end: Delete opens the themed dialog; the red button removes the file (status "已删除…", row disappears); Cancel/backdrop leaves it intact. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The download path was built directly from the server-supplied name via base_name(), so a malicious SFTP server could craft a name with path separators, shell-special characters, or a Windows reserved device name (CON, NUL, COM1, ...) to write outside the chosen directory or target a device. The open/edit flow already ran names through sanitize_filename(); the download path did not. Run downloads through sanitize_filename, and harden that helper to also neutralise Windows reserved device names (reserved case-insensitively and even with an extension) and strip leading whitespace, while preserving Unix dotfiles. Unit tests cover separators/traversal, special chars, reserved names, whitespace trimming, and the empty fallback. Closes jeff141#26. (jeff141#24, the resize-churn cursor bug, was already fixed by the cursor-based shrink change.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The sidebar monitor runs a small loop over an SSH exec channel parsing /proc and df output. Three hardenings against a malicious/compromised server (which controls that output and the shell environment): 1. PATH hijacking: prepend "PATH=/usr/bin:/bin:/usr/sbin:/sbin; export PATH" to the monitor command so a server with a tampered PATH or BASH_ENV cannot shadow awk/cat/df/sleep. A fixed PATH is more portable than hardcoding one absolute path per tool; monitoring is best-effort either way. 2. Unbounded buffer: the reassembly buffer grew until the __MSTICK__ marker arrived. A server that streams data and never emits the marker could grow it without limit. Cap the leftover tail at 1 MiB and drop it past that. 3. Parser robustness: a server can put arbitrary values in /proc/stat, /proc/net/dev and df output. Sum CPU jiffies and the KiB->bytes multiply with saturating arithmetic (no debug overflow panic), and cap interfaces and filesystems at 64 per sample so a flood of fabricated rows cannot swamp the parser or sidebar. Unit tests cover df saturation, CPU overflow values, and the interface cap. Verified live that the PATH-reset monitor still reports CPU/mem/swap/net/disk. Closes jeff141#27. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Uses linuxdeploy + linuxdeploy-plugin-appimage (APPIMAGE_EXTRACT_AND_RUN=1 so no FUSE needed in CI). Produces a single-file portable executable alongside the existing .tar.gz; both are uploaded as workflow artifacts and attached to GitHub Releases. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…on-error) `--plugin appimage` conflicts with `--output appimage`; the output plugin is invoked automatically by --output. Also added `continue-on-error: true` so a future AppImage failure can't take the regular .tar.gz down with it. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds an image crate dependency (Linux-only, png feature only) and a set_window_icon helper that decodes the bundled 512x512 PNG at startup and hands it to slint::Window::set_window_icon. This makes the Wayland/ X11 dock show the correct icon when running the AppImage without AppImageLauncher, or the bare binary without install-linux.sh. Windows keeps its icon from the linked .ico; macOS from the app bundle -- neither is affected. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
slint::Window has no set_window_icon method; replace with the winit equivalent accessed via with_winit_window. Works on X11 sessions (sets _NET_WM_ICON); is a no-op on Wayland by design. 修复 Linux 窗口图标:改用 winit API,slint::Window 并无 set_window_icon 方法,改为通过 with_winit_window 调用 winit 的同名 方法。X11 会话下生效(写入 _NET_WM_ICON);Wayland 下为空操作,符合预期。
Passwords in sessions.json are no longer stored in plaintext. On first launch a random 256-bit key is written to secret.key in the same config directory (mode 0600 on Unix). Each non-empty password is encrypted with ChaCha20-Poly1305 (random 96-bit nonce per value) and stored as "enc:v1:<base64url(nonce||ciphertext)>". Legacy plaintext passwords from older installs are kept as-is in memory and silently re-encrypted on the next save — no user action required. Copying sessions.json alone is no longer sufficient to read credentials. 新增:密码静态加密,改用 ChaCha20-Poly1305 存储 sessions.json 中的密码不再明文保存。首次启动时在配置目录生成随机 256 位密钥文件 secret.key(Unix 下权限 0600)。每个非空密码使用 ChaCha20-Poly1305(每次随机 96 位 nonce)加密后以 "enc:v1:<base64url(nonce||密文)>" 格式写入 JSON。 旧版本的明文密码加载后保持原样,下次保存配置时自动加密,无需用户操作。 单独拿走 sessions.json 已无法还原密码。
- Add reactive light/dark theme system with sun/moon toggle button; defaults to system preference (dark-light crate), persisted in config - Redesign ANSI 16-color palette: separate fg (ANSI16_LIGHT) and bg (ANSI16_LIGHT_BG) tables for light mode so slot-7 white stays readable as text while TUI background fills render as light pastels - Add HSL-based transform for true-color (24-bit) backgrounds in light mode: dark RGB backgrounds (L < 0.45) are remapped to light pastels, making btop/htop/vim feel native to the light theme - Add symmetric darken_light_fg() transform: light RGB foregrounds (L >= 0.55) are darkened so btop text stays readable on white background - Store raw vt100::Color in HistSpan; convert at render time so theme switching retroactively recolors the entire scrollback history - Bump version to 0.2.5 feat: 新增浅色/深色主题,TUI 程序自适应配色 - 添加响应式浅色/深色主题切换,顶栏太阳/月亮按钮一键切换; 启动时跟随系统设置(dark-light crate),偏好持久化到配置文件 - 重设计 ANSI 16 色调色板:浅色模式下前景(ANSI16_LIGHT)与背景 (ANSI16_LIGHT_BG)使用独立映射表,确保 slot-7 白色作为文字仍 可读,TUI 填充块渲染为浅色粉彩风格 - 对真彩色(24-bit)背景添加 HSL 亮度重映射:浅色模式下深色 RGB 背景(亮度 L < 0.45)自动映射为浅色,btop/htop/vim 在浅色主题下 视觉风格统一 - 添加对称的 darken_light_fg():浅色 RGB 前景(L ≥ 0.55)压暗, 确保 btop 文字在白色背景上可读 - HistSpan 改为存储原始 vt100::Color,渲染时再转换颜色,切换主题 可即时重绘整个历史滚动缓冲区 - 版本升至 0.2.5
Shift+Insert 是 X11/xterm 通用的粘贴快捷键,之前 meatshell 终端只认 Ctrl+V / Ctrl+Shift+V,按 Shift+Insert 没反应。现在在终端按键处理里加一条:Shift 且按下 Insert 键(Slint 的 Key.Insert)时走 paste-from-clipboard,和 Ctrl+V 同一套粘贴路径。 feat(term): paste with Shift+Insert in the terminal (jeff141#144) Shift+Insert is the common X11/xterm paste shortcut, but the meatshell terminal only recognised Ctrl+V / Ctrl+Shift+V, so Shift+Insert did nothing. The terminal key handler now pastes (same paste-from-clipboard path as Ctrl+V) when Shift is held and the Insert key (Slint's Key.Insert) is pressed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
设置下拉菜单原来写死 height: 220px,加了「批量导入(文本)」这第 8 项后内容超出卡片、 末项溢出圆角背景。改为 height: menu-vl.preferred-height,让卡片跟随内容高度, 以后增删菜单项、或导入 ~/.ssh/config 的结果提示展开,都不会再溢出。 fix(ui): make the settings menu height fit its content, fixing overflow after batch import (jeff141#150) The settings dropdown had a hardcoded height: 220px; adding the "Batch import (text)" entry (8th item) pushed the content past the card so the last item spilled outside the rounded background. It now uses height: menu-vl.preferred-height so the card tracks its content — adding/removing items or the ~/.ssh/config import hint expanding no longer overflows. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
汇总 0.4.19 之后的 12 项改动:终端缩放 reflow (jeff141#169)、alt-screen 滚轮转发 (jeff141#170)、 文本批量导入 (jeff141#150)、分组下拉框 (jeff141#179)、Shift+Insert 粘贴 (jeff141#144);修复主机密钥被拒 缓存中毒 (jeff141#152)、兼容旧服务器算法 (jeff141#172)、SFTP 修改时间本地时区 (jeff141#168)、Linux 缩放 指针抓取卡死 (jeff141#159)、回显抑制 1.2s→2s (jeff141#176)、设置菜单溢出 (jeff141#150);合并输出事件防 tail -f 假死 (jeff141#171)。版本号 + Cargo.lock + CHANGELOG 已同步。 chore(release): release 0.4.20 Rolls up the 12 changes since 0.4.19: terminal reflow on resize (jeff141#169), alt-screen mouse-wheel forwarding (jeff141#170), text batch import (jeff141#150), group dropdown (jeff141#179), Shift+Insert paste (jeff141#144); fixes for the rejected-host-key cache poison (jeff141#152), legacy SSH algorithms (jeff141#172), SFTP local-time mtime (jeff141#168), the stuck Linux resize grab (jeff141#159), echo-suppression 1.2s→2s (jeff141#176) and the settings-menu overflow (jeff141#150); and output-event coalescing for the tail -f freeze (jeff141#171). Version, Cargo.lock and CHANGELOG synced. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
部分服务器(FNOS 等 NAS)的 df 里有几十条 Docker overlay 挂载,全是同一个底层卷被按 容器层重复计数,把资源面板的分区列表刷爆。现在把读到的每个分区按(总量, 可用)丢进一个 Set 去重——已经出现过的同尺寸文件系统就跳过;df 先列出真实挂载,所以保留的是真实那条, 一波几十条 overlay 收敛成一条。 fix(ui): dedupe the resource-panel filesystems, fixing duplicate NAS paths (jeff141#38) Some servers (FNOS and other NAS boxes) list dozens of Docker overlay mounts in df, all the same underlying volume re-counted per container layer, flooding the resource panel. Each filesystem is now dropped into a set keyed on (total, available) and skipped if already seen; df lists the real mount first, so that is the row kept, and dozens of identical overlays collapse to one. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
旧的关于窗口是 380×440 竖屏,塞了一长串依赖库列表,又长又丑。改为 520×188 横屏: 左边 logo,右边「meatshell + 版本号 + 简介(复用 welcome 那句)+ 可点击的开源地址 github.com/jeff141/meatshell + 许可证」。点开源地址用浏览器打开仓库(复用现成的跨平台 open)。 feat(ui): redesign the About window as a compact landscape card with the repo link The old About window was a 380×440 portrait card stuffed with a long dependency list — tall and ugly. It is now a 520×188 landscape card: logo on the left; on the right the name + version, a one-line intro (reusing the welcome tagline), a clickable open-source link (github.com/jeff141/meatshell, opens the repo in the browser) and the license. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
每个标签(包括首个"新标签页")现在都能按住左右拖动重排:拖过相邻标签一半宽度就跟它 换位,松开即定。只改标签栏顺序——终端内容按 active id 显示,不受影响;普通单击仍是 选中(拖动时不触发选中)。 实现:SingleTab 的 TouchArea 记录按下点,moved 时按(标签宽 + 2px 间距)判定是否跨过半个 槽位,跨过就回调 reorder(±1),Rust 在 tabs VecModel 里把该标签挪一格,并重锚定 grab-offset 避免抖动。每次拖动手势移动一格(Slint 的 for 在模型重排后会重建实例、丢失指针抓取),要 移多格连续拖几下即可。 feat(ui): drag tabs to reorder, including the "New tab" (v0.5) Every tab (including the leading "New tab") can now be dragged left/right to reorder: drag past half of the neighbouring tab's width to swap with it, release to settle. Only the tab-bar order changes — terminal content shows by active id, so it's unaffected — and a plain click still selects (a drag doesn't). Implemented with the tab's TouchArea tracking the grab point and emitting reorder(±1) when the drift crosses half a slot; grab-offset re-anchors each hop to avoid oscillation. One slot per gesture (Slint's `for` rebuilds instances and drops the pointer grab once the model reorders); drag again to move further. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
IDEA 式可嵌套分屏的第一步。Slint 不能递归渲染组件,所以把可任意嵌套的分屏布局放到 Rust 里做成一棵树(src/panes.rs):节点要么是二分(横/竖 + 比例 + 两个子节点),要么是 叶子 pane(一个标签组 + 当前标签)。遍历这棵树就能给每个叶子 pane 和每条分隔条算出绝对 矩形,拍平成列表供 UI 用 for 直接渲染。 含完整操作:split(把某 pane 一分为二并把一个标签移进新 pane)、move_tab(标签在 pane 间 移动)、remove_tab、add_tab、set_ratio(拖分隔条)、prune(空 pane 自动塌缩)。5 个单元测试 全过(单 pane 铺满、横分对半 + 出分隔条、关最后一个标签塌缩、跨 pane 移标签、嵌套不重叠)。 尚未接入 UI(暂 allow(dead_code)),不影响现有行为;下一步把 panes 模型接到 Slint 渲染。 feat(panes): Rust foundation for the split-pane layout tree (v0.5 M1) First step of IDEA-style nestable split panes. Slint can't render recursive components, so the nestable split layout lives in Rust as a tree (src/panes.rs): a node is either a binary split (dir + ratio + two children) or a leaf pane (a tab group + active tab). Walking the tree assigns every leaf pane and splitter an absolute rect, flattened into lists the UI renders with `for`. Includes the full set of ops: split (halve a pane, moving one tab into the new pane), move_tab (between panes), remove_tab, add_tab, set_ratio (splitter drag) and prune (collapse emptied panes). Five unit tests pass (single pane fills area, horizontal split halves + emits a splitter, closing the last tab collapses the split, moving a tab between panes, nested splits partition without overlap). Not wired into the UI yet (allow(dead_code) for now), so behaviour is unchanged; next step hooks the panes model into Slint rendering. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
给 AppWindow 加上分屏模型的声明层:PaneInfo(含每 pane 自己的 tabs: [TabInfo] 标签组 + 几何 + active + focused + reserve-right)、SplitterInfo,以及 panes / splitters 两个属性和 per-pane 标签回调(pane-tab-selected/closed、pane-new-tab、pane-tab-reorder、pane-focus、 splitter-drag)。编译通过——确认了 Slint 的 struct 支持嵌套数组字段(整个「每 pane 独立 标签组」架构成立)。暂未驱动/渲染,行为不变;下一步是 Rust 把分屏树拍平喂进 panes、并把 内容区改成按 pane 渲染。 chore(panes): declare the Slint split-pane model; confirm nested arrays work (v0.5 M1) Adds the declaration layer for split panes on AppWindow: PaneInfo (with each pane's own tabs: [TabInfo] group + geometry + active + focused + reserve-right), SplitterInfo, the panes / splitters properties, and per-pane tab callbacks (pane-tab-selected/closed, pane-new-tab, pane-tab-reorder, pane-focus, splitter-drag). It compiles — confirming Slint structs support nested array fields, so the "per-pane tab group" architecture holds. Not driven or rendered yet (behaviour unchanged); next the Rust side flattens the split tree into `panes` and the content area renders per pane. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
把中央内容区从「单条全局标签栏 + 全屏终端」重构成 `for pane in panes` 平铺:每个 pane 是一个 矩形,内含自己的标签栏(VerticalLayout: 标签栏 + 内容),内容按 pane.active-id 显示 welcome 或对应终端;另加一个 `for sp in splitters` 的可拖动分隔条层(单 pane 时模型为空,不渲染)。 本步保持纯 Slint:panes 暂时在 Slint 里绑成「一个铺满的 pane」(tabs=root.tabs、 active=root.active-tab-id),标签回调仍走原来的全局 tab-selected/closed/new/reorder。所以单 pane 行为与现在完全一致,没动 Rust。下一步(M2)再把 panes 改成 Rust 分屏树驱动 + 接分隔条拖动。 左上 30px 展开按钮留位现在只给第一个 pane(pi==0);右上工具栏留位走 pane.reserve-right。 feat(panes): render the content area per pane; single pane == today (v0.5 M1) Reworks the central content area from "one global tab strip + fullscreen terminal" into a tiled `for pane in panes`: each pane is a rectangle with its own strip (VerticalLayout: strip + content), content showing welcome or the matching terminal by pane.active-id, plus a `for sp in splitters` draggable-splitter layer (empty model on a single pane, so nothing renders). This step stays pure Slint: `panes` is bound in Slint to a single full-area pane (tabs=root.tabs, active=root.active-tab-id) and the tab callbacks still go through the existing global tab-selected/closed/new/reorder. So single-pane behaviour is identical to before and no Rust changed. M2 will switch `panes` to be driven by the Rust split tree and wire splitter dragging. The top-left 30px expand-button gap now applies only to the first pane (pi==0); the top-right toolbar gap uses pane.reserve-right. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
让 Rust 的分屏树(panes.rs Layout)成为内容区的真正数据源:Slint 上报 pane-area 尺寸 (content-resized),Rust flatten() 拍平成 panes/splitters 模型;每个 pane 的标签操作改走 per-pane 回调(pane-tab-selected/closed/new/reorder/focus),由对应 leaf 维护自己的 active。 全局 active-tab-id 仍保留,等于「聚焦 pane 的 active」,侧栏/按键路由照旧读它。 - 新建 refresh_panes():把 leaf 的 tab-id 映射回 TabInfo,touch 右上角的 pane 才留 140px 给工具栏(jeff141#122)。 - 连接会话 → layout.add_tab 到聚焦 pane → refresh。 - 关标签 → 清模型/句柄 + layout.remove_tab(自动回退 active、空 pane 折叠)。 - 选标签/重排/「+」/聚焦 都先改树再 refresh。 - layout + content_size 串进 wire_session_callbacks / wire_tab_callbacks。 单 pane 行为与之前一致,已真机验证:welcome 渲染、连 jeff@192.168.100.41、切标签(终端态 保留)、关标签回退,侧栏全部正常。分隔条拖动和「拖标签触发分屏」留到 M2b/M2c。 feat(panes): drive the content area from the split tree; per-pane tabs (v0.5 M2a) Makes the Rust split tree (panes.rs Layout) the real source of truth for the content area: Slint reports the pane-area size (content-resized) and Rust flatten()s it into the panes/splitters models. Each pane's tab actions now go through per-pane callbacks (pane-tab-selected/closed/new/reorder/focus) and each leaf owns its own active tab. The global active-tab-id stays, equal to the focused pane's active, so the sidebar / key routing keep reading it. - New refresh_panes(): maps a leaf's tab ids back to TabInfo and only reserves 140px for the toolbar on the pane touching the top-right corner (jeff141#122). - Connect a session -> layout.add_tab into the focused pane -> refresh. - Close a tab -> tear down models/handles + layout.remove_tab (re-homes active, collapses an emptied pane). - Select / reorder / "+" / focus all mutate the tree then refresh. - Threads layout + content_size through wire_session_callbacks / wire_tab_callbacks. Single-pane behaviour is identical and verified live: welcome renders, connect to jeff@192.168.100.41, tab switch (terminal state preserved), close-falls-back, sidebar all correct. Splitter dragging and drag-tab-to-split come in M2b/M2c. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
把"能分屏"接通:标签右键菜单(向右拆分/向下拆分/关闭标签页)+ 分隔条拖动改比例。 - 标签右键 → 上下文菜单(tabs.slint 新增 TabMenuItem + PopupWindow,沿用 sftp 面板那套 右键定位 + 点外面关闭);拆分项仅在该 pane 有 >1 个标签时可用。 - pane-split(pane-id, tab-id, dir) → Rust 把该标签从原 pane 剥到新 pane: right→Horizontal、down→Vertical(left/up 预留),focus 跟到新 pane。 - splitter-drag:SplitterRect 补 axis-start/axis-len(轴向窗口),拖动时按 pos 反算 ratio 调 set_ratio;分隔条 TouchArea 比 6px 视觉条左右各宽 5px,好抓,hover/按下高亮 accent。 - 关掉某 pane 最后一个标签 → remove_tab 清空该 pane → prune 自动折叠回去。 - 菜单文案走 @tr,补 zh/en .po:向右拆分/向下拆分/关闭标签页。 真机验证(jeff@192.168.100.41):向右/向下拆分成两个 pane、拖分隔条重新分配、关标签折叠回单 pane,全部正常。剩 M2c:拖标签到边缘触发分屏。 feat(panes): right-click split + draggable splitter — real split panes now (v0.5 M2b) Wires up actually splitting: a tab right-click menu (split right / split down / close tab) plus draggable splitters that change the ratio. - Right-click a tab -> context menu (new TabMenuItem + PopupWindow in tabs.slint, reusing the sftp panel's right-click positioning + click-away-to-close). Split items are enabled only when the pane has >1 tab. - pane-split(pane-id, tab-id, dir) -> Rust peels the tab out of its pane into a new one: right->Horizontal, down->Vertical (left/up reserved); focus follows the new pane. - splitter-drag: SplitterRect now carries axis-start/axis-len (the axis window) so the drag handler recovers the ratio from `pos` and calls set_ratio. The splitter's TouchArea reaches 5px past the 6px visual bar so it's easy to grab; it highlights on hover/press. - Closing a pane's last tab -> remove_tab empties it -> prune collapses the split back. - Menu labels via @tr; added zh/en .po strings (split right / split down / close tab). Verified live (jeff@192.168.100.41): split right/down into two panes, drag the splitter to rebalance, close-tab collapses back to one pane — all good. M2c next: drag a tab to a pane edge to split. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
标签右键菜单顶部加「复制打开」:对终端标签,按它连接的 saved session 再开一个新标签(独立的第 二条连接),落在源标签所在的 pane。welcome 标签下禁用。 - tabs.slint:菜单加一项 + tab-duplicate(tab-id) 回调;app.slint 透传到 tab-duplicate。 - Rust:on_tab_duplicate 从 tab_statuses 查到该标签的 session_id,把 layout.focused 设到源 pane,再 invoke_connect_session 复用整套连接流程。 - 文案用独立 key @tr("Duplicate connection")(避免和会话「复制副本」那条 @tr("Duplicate") 撞 msgid);补 zh「复制打开」/ en。 修了一个一开始就崩的坑:`if let Some(p) = layout.borrow().leaf_of_tab(..) { layout.borrow_mut() }` 里 borrow 在 if-let 体内还活着,borrow_mut 会 RefCell 双借用 panic —— 先把 pane id 取进局部 变量再 borrow_mut。真机验证:连 local_server 后复制打开,出现第二条独立连接,不崩。 feat(tabs): add "Duplicate connection" to the tab right-click menu (v0.5) Adds "Duplicate connection" at the top of the tab context menu: for a terminal tab it opens a fresh tab to the same saved session (an independent second connection), landing in the source tab's pane. Disabled on the welcome tab. - tabs.slint: one more menu item + tab-duplicate(tab-id) callback; app.slint forwards it to tab-duplicate. - Rust: on_tab_duplicate looks the tab's session_id up from tab_statuses, sets layout.focused to the source pane, then invoke_connect_session to reuse the whole flow. - Uses a distinct key @tr("Duplicate connection") so it doesn't collide with the session "Duplicate" msgid; added zh/en .po entries. Fixes an immediate crash: `if let Some(p) = layout.borrow().leaf_of_tab(..) { layout.borrow_mut() }` keeps the borrow alive inside the if-let body, so borrow_mut panics (RefCell double borrow) — read the pane id into a local first. Verified live: duplicating a local_server tab opens a second independent connection without crashing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
把标签拖进 pane 的边缘区就分屏:拖动时目标 pane 的上/下/左/右 30% 边缘带高亮一块(松手新 pane 占的区域),落在该区就朝那个方向 split;落在中间且是别的 pane 就并入它的标签组。 - SingleTab:拖动时上报绝对光标 drag-moved,松手 drag-dropped;横向漂移或纵向离开标签都算 进入拖动;条内重排只在光标还在标签行时触发(拖进 pane 体就交给分屏,避免边拖边重排)。 - TabBar/app.slint 透传成 pane-area 坐标的 tab-drag-move/drop;Rust drag_target() 拍平命中 pane + 算 zone(strip 行不高亮,留给重排);拖动设 drag-active + 高亮矩形,Slint 画半透明 accent 框(纯视觉,不挡拖拽)。 - 落点:左/右→Horizontal、上/下→Vertical 的 split 把标签剥过去;中间→move_tab 并入(同 pane 则无操作)。 真机验证(jeff@192.168.100.41):拖到右边缘→左右分;拖到下边缘→上下分;高亮跟手、zone 正确 (中间 center、底部 down);条内重排不再闪高亮。M2(分屏)至此完成。 feat(panes): drag a tab to a pane edge to split (IDEA-style) (v0.5 M2c) Dragging a tab into a pane's edge band splits it: while dragging, the target pane's top/bottom/left/right 30% edge highlights (the region the new pane will take) and dropping there splits that way; dropping in the middle of a *different* pane merges into its tab group. - SingleTab reports the absolute cursor on drag-moved and the drop point on drag-dropped; a drag now starts on horizontal drift OR leaving the tab vertically; in-strip reorder only fires while the cursor stays on the strip row (dragging into the body hands off to split, so it no longer reorders mid-drag). - TabBar/app.slint forward these as pane-area coords (tab-drag-move/drop); Rust drag_target() flattens, hit-tests the pane + zone (the strip row gets no highlight, left for reorder), sets drag-active + a highlight rect, and Slint draws a translucent accent box (visual only, never eats the drag). - Drop: left/right -> Horizontal, up/down -> Vertical split peels the tab across; middle -> move_tab into that pane (no-op for the same pane). Verified live (jeff@192.168.100.41): drag to the right edge splits left/right, to the bottom edge splits top/bottom; the highlight tracks the cursor and zones are correct (middle=center, bottom=down); in-strip reorder no longer flashes the highlight. M2 (split panes) is complete. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
修复:分屏后拖分隔条只能拖动一点点就停。根因不是"监听单击"——是 refresh_panes 每次都用 set_panes/set_splitters **整个替换**模型,导致 Slint 把 `for sp` 分隔条元素销毁重建,拖动中的 指针 grab 一断,后续 move 收不到,所以只动了第一帧。 改法:panes / splitters 改成在 run() 里建一次的持久 VecModel,refresh_panes **原地更新** (行数不变就 set_row_data,变了才 set_vec)。元素被复用 → 分隔条 grab 在整个拖动期间保持; 顺带每个 pane 也复用,终端不再每次 refresh 重建(切标签/连接/缩放都更顺、不闪)。pane 行只在 标签集变化时才换 tabs 子模型(tabs_eq 比对 id),几何-only 刷新不动标签栏。 把 panes_model / splitters_model 串进 wire_session_callbacks / wire_tab_callbacks 及全部 refresh_panes 调用点。真机验证:右键拆分后拖分隔条,从一端拖到另一端全程跟手。 fix(panes): the splitter drags the whole way now — update models in place, no element rebuild (v0.5) Fix: after splitting, the splitter only moved a tiny bit then stopped. The cause wasn't "listening to a click" — refresh_panes replaced the whole model every call via set_panes/set_splitters, so Slint destroyed and recreated the `for sp` splitter element, breaking the in-flight pointer grab; only the first move landed. Now panes / splitters are persistent VecModels created once in run(), and refresh_panes updates them IN PLACE (set_row_data when the row count is unchanged, set_vec otherwise). Elements are reused, so the splitter keeps its grab for the whole drag; panes are reused too, so terminals are no longer recreated on every refresh (smoother, flicker-free tab switch / connect / resize). A pane row only swaps its tabs sub-model when the tab set actually changes (tabs_eq compares ids), so a geometry-only refresh doesn't churn the strip. Threads panes_model / splitters_model through wire_session_callbacks / wire_tab_callbacks and every refresh_panes call. Verified live: after a right-split, the splitter tracks the cursor end to end. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
修复:复制打开(以及任意连接)后,标签上的连接小圆点一直是黄的,要切一下标签才变绿。 原因:per-pane 改造后每个 pane 的标签栏渲染的是 `pane.tabs`——一份从 tabs_model 拷出来的 快照。SessionEvent::Connected 里 `update_tab` 只把 connected=true 写回 tabs_model,没同步到 pane 的拷贝,所以圆点(info.connected ? 绿 : 黄)一直黄,直到下次 refresh_panes 重建拷贝 (切标签/拆分时)才更新。 改法:`update_tab` 在写 tabs_model 之后,顺带遍历 panes_model 每个 pane 的 tabs 子模型,把 同 id 的那行也跑一遍 mutator。连接/断开状态(connected true/false)即时反映到分屏标签栏。 另:之前反馈的「复制打开后终端显示字符错乱、要切标签才正常」已被上一提交(abbec3c 模型原地 更新)顺带修掉——不再重建 TerminalView,就没有瞬时 0 宽触发的 10 列地板;实测复制 pve 后新 标签终端直接 168 列正常渲染。 fix(panes): sync a tab's connection state into the split-pane strips (yellow dot -> green) (v0.5) Fix: after "Duplicate connection" (and any connect), the tab's connection dot stayed yellow until you switched tabs. Cause: with the per-pane rework each pane strip renders `pane.tabs`, a snapshot copied from tabs_model. update_tab on SessionEvent::Connected only wrote connected=true back to tabs_model, not the pane copies, so the dot (info.connected ? green : yellow) stayed yellow until the next refresh_panes rebuilt the copy (on a tab switch / split). Now update_tab, after updating tabs_model, also walks each pane's tabs sub-model in panes_model and runs the same mutator on the matching row — so connected/disconnected state shows on the split strips immediately. Also: the earlier-reported "garbled terminal after duplicate, fixed by switching tabs" was incidentally fixed by the previous commit (abbec3c, in-place model updates) — TerminalView is no longer recreated, so there's no transient 0-width triggering the 10-col floor; verified the duplicated pve tab renders at 168 cols right away. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
复现并真正修掉"复制打开后终端只显示按 ~10 列折行的碎片"。 根因:新连接的初始尺寸取自全局 `last_term_size`(最后一次终端尺寸)。一个标签变成非激活/被分屏 挡住时,它的 TerminalView 宽度绑定成 0px,会触发一次 `terminal-resize(0)`;回调里 `cols.max(10)` 把它**地板成 10 列**,既缩了那个标签的 PTY,又把 `last_term_size` 污染成 10。于是来回切几次标签后 再"复制打开",新连接初始就是 10 列,首屏输出按 ~10 字符折行。(上个版本没切标签时取到的是正常 尺寸,所以只在多标签切换后复现。) 改法:`on_terminal_resize` 开头直接忽略 `cols_f < 1 || rows_f < 1` 的事件——隐藏终端根本不该 驱动 resize。只有真实可见的尺寸才会落到 PTY/vt100 和 `last_term_size`。 真机验证:连 local_server + pve,来回切标签数次后复制打开 local_server,新标签终端直接整屏 MOTD 正常渲染,不再碎片折行。 fix(term): ignore 0-width resizes from hidden terminals so duplicated tabs don't wrap to ~10 cols (v0.5) Reproduced and properly fixed "Duplicate connection shows only a ~10-col wrapped fragment". Cause: a new connection's initial size comes from the global `last_term_size` (the last terminal size). When a tab goes inactive / hidden behind a split, its TerminalView width binds to 0px and fires a `terminal-resize(0)`; the callback's `cols.max(10)` floors that to 10 cols, shrinking that tab's PTY *and* poisoning `last_term_size` to 10. So after switching tabs a few times, "Duplicate connection" starts at 10 cols and its first output wraps to ~10 chars. (The previous build only avoided it when no switch happened, so it kept recurring.) Fix: `on_terminal_resize` now ignores events with `cols_f < 1 || rows_f < 1` up front — a hidden terminal should never drive a resize. Only genuine, visible sizes reach the PTY/vt100 and `last_term_size`. Verified live: connect local_server + pve, switch tabs several times, then duplicate local_server — the new tab renders the full MOTD at full width, no fragment wrapping. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 1a:先把开关和设置 UI 接通(渲染逻辑下一步做)。 - config:ConfigFile 加 welcome_as_sidebar: bool + getter/setter,随配置持久化。 - 界面设置:新增「欢迎页」分页,内含开关「欢迎页设为侧栏」(中英文案 + 说明)。 - app.slint / interface_panel.slint:welcome-as-sidebar 属性 + set-welcome-as-sidebar 回调 双向绑定 + 透传。 - app.rs:启动时加载该设置写入属性;on_set_welcome_as_sidebar 落盘。 本步仅设置层,勾选暂不改变界面;下一步(M3b)再实现:勾选后欢迎标签消失、会话列表停靠左侧、 中央区显示终端。 feat(welcome): add "Welcome page as sidebar" setting (Interface › Welcome page), persisted (v0.5 M3a) Phase 1a: wire up the toggle and settings UI (rendering comes next). - config: ConfigFile gains welcome_as_sidebar: bool + getter/setter, persisted. - Interface settings: new "Welcome page" sub-page with a "Show the welcome page as a sidebar" switch (bilingual label + description). - app.slint / interface_panel.slint: welcome-as-sidebar property + set-welcome-as-sidebar callback, two-way bound and forwarded. - app.rs: load the setting into the property at startup; on_set_welcome_as_sidebar saves. This step is settings-only; toggling doesn't change the UI yet. Next (M3b) implements the rendering: hide the welcome tab, dock the session list left, terminals fill the center. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 1b:让「欢迎页设为侧栏」真正生效。开启后: - 欢迎/会话列表停靠在左侧固定面板(240px),顶部不再有「新标签页」标签; - 打开会话 → 终端在中央区域渲染,会话列表常驻左侧; - 没有打开会话时,中央显示居中提示「从左侧选择一个会话开始」; - 「+」在侧栏模式下无操作(没有欢迎标签可开)。 实现: - 布局:welcome-as-sidebar 时分屏树初始为空(无 welcome 标签);否则照旧含 welcome。 - 运行时切换:on-set-welcome-as-sidebar 落盘后,把 welcome 标签移出/移回分屏树并 refresh ——开/关都即时生效,无需重启。 - 渲染:dock-area 左侧切出 welcome-w 宽的会话面板(渲染 Welcome 组件),其余(资源面板 + 分隔条 + 中央 tab/pane 内容)包进 `rest` 子矩形右移,各 dock 边缘几何不变。 - 空 pane(tabs.length==0)显示居中提示。 - pane-new-tab 在侧栏模式下直接返回,避免误加 welcome 标签。 真机验证(jeff@192.168.100.41):开启→会话列表停靠左、欢迎标签消失、空态提示;点会话→终端 占中央、绿点正常;关闭→欢迎标签复原、终端标签保留。设置项持久化。 feat(welcome): welcome-as-sidebar — dock the session list left, terminals fill the center (v0.5 M3b) Phase 1b: make "Welcome page as sidebar" actually take effect. When enabled: - the welcome/session list docks in a fixed left panel (240px) and there is no "New tab" tab; - opening a session renders the terminal in the center while the session list stays docked; - with no session open the center shows a centered hint "Pick a session on the left to begin"; - "+" is a no-op in sidebar mode (there is no welcome tab to open). Implementation: - Layout: in welcome-as-sidebar mode the split tree starts empty (no welcome tab); otherwise it owns welcome as before. - Runtime toggle: on-set-welcome-as-sidebar persists, then moves the welcome tab out of / back into the split tree and re-flattens — both directions apply live, no restart. - Rendering: dock-area carves a welcome-w-wide session panel off the left (rendering the Welcome component); everything else (resource panel + splitter + central tab/pane content) is wrapped in a `rest` sub-rectangle shifted right, so per-edge dock geometry is unchanged. - Empty pane (tabs.length==0) shows the centered hint. - pane-new-tab returns early in sidebar mode so it can't re-add a welcome tab. Verified live (jeff@192.168.100.41): enable -> list docks left, welcome tab gone, empty hint; click a session -> terminal fills center, green dot; disable -> welcome tab restored, terminal tab kept. Setting persists. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
来自实测反馈的三处修复: 1) SFTP 收起状态改成每会话独立(之前是全局,分屏里收一个全部跟着收)。把 sftp-collapsed 从 AppWindow 全局属性挪进每个 TerminalState(terminal_view 改成 in 属性 + set-sftp-collapsed 回调写回模型);新会话默认读「默认收起 SFTP」那个公共配置。实测:local_server 展开、pve 收起 互不影响。 2) 欢迎页设为侧栏时隐藏大标题容器:Welcome 加 compact 属性,侧栏实例 compact:true → 去掉 「meatshell」标题/副标题、收紧 padding。 3) 欢迎侧栏宽度可拖拽调节:加 welcome_sidebar_width 配置 + welcome-sidebar-width 属性 + 右缘拖动条(clamp 160–480px),松手持久化。 fix(panes/welcome): per-session SFTP collapse + hide sidebar title / resizable width (v0.5) Three fixes from live feedback: 1) SFTP collapse is now per-session (was global — collapsing one split pane collapsed them all). Moved sftp-collapsed off the AppWindow global into each TerminalState (terminal_view takes it as an `in` property + a set-sftp-collapsed callback that writes back to the model); new sessions seed it from the "collapse SFTP by default" preference. Verified: local_server expanded while pve collapsed, independent. 2) Hide the big title block in welcome-as-sidebar mode: Welcome gains a `compact` property; the sidebar instance sets compact:true to drop the "meatshell" title/tagline and tighten padding. 3) Resizable welcome sidebar: welcome_sidebar_width config + welcome-sidebar-width property + a right-edge drag handle (clamped 160–480px), persisted on release. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
接着上一个「收起状态每会话独立」,把 SFTP 面板的高度/宽度/保存高度也从全局 AppWindow 属性挪进 每个 TerminalState。之前 sftp-panel-height 还是全局 `<=>` 绑定,所以分屏里拖 A 的 SFTP 高度 B 跟着变;收起 A(把全局高度设成 30px)B 也被压扁。 - TerminalState 加 sftp-panel-height / -width / -saved-height(length);新会话从全局默认 (持久化配置)初始化。 - terminal_view:这几个改成 in 属性 + set-sftp-panel-height/-width/-saved-height 回调,拖动/ 收起/展开都通过回调写回模型(元素原地复用,拖动 grab 不断)。 - Rust:update_terminal_row 辅助函数 + 三个 on-set 回调更新对应会话行;高/宽再镜像到全局属性 (用于持久化 + 给新会话当默认 + SFTP 拖拽落区定位),但不影响其它已开会话(各用自己的字段)。 真机验证:分屏 local_server + pve,收起 local_server 的 SFTP → pve 的 SFTP 仍完整展开,互不 影响。 fix(sftp): make SFTP panel height/width per-session too, no more global coupling (v0.5) Following the per-session collapse fix, move the SFTP panel's height/width/saved-height off the global AppWindow properties into each TerminalState. sftp-panel-height was still bound `<=>` global, so in a split dragging A's SFTP height moved B's, and collapsing A (which sets the global height to 30px) squashed B too. - TerminalState gains sftp-panel-height / -width / -saved-height (length); new sessions seed them from the global defaults (persisted config). - terminal_view: these become `in` properties + set-sftp-panel-height/-width/-saved-height callbacks; resize/collapse/expand all write back through the callbacks (the element is reused in place, so the resize grab survives). - Rust: an update_terminal_row helper + three on-set handlers update the matching session's row; height/width are also mirrored to the global property (for persistence, seeding new sessions, and the SFTP drop-zone locator) without touching other open tabs (each uses its own field). Verified live: split local_server + pve, collapse local_server's SFTP -> pve's stays fully expanded, independent. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
侧栏模式下,最左边常驻一条 36px 的图标条(IDEA 工具窗那种),欢迎/会话侧栏可收起: - 图标条始终在(初始就有);上面一个会话图标,展开时高亮(accent 底+图标)。 - 点图标 = 切换:展开→收起(侧栏隐藏、终端区变宽),收起→展开。中间终端区永远在。 - 收起/展开状态持久化(welcome_collapsed),重启保持。 布局:dock-area 左侧切出 strip-w(图标条)+ welcome-w(侧栏,收起时为 0),其余(资源面板 + 中央)在 rest 里右移 strip-w+welcome-w;宽度拖动条的换算也加上 strip-w 偏移。收起纯靠 Slint 绑定响应式重排,终端随 rest 变宽经 content-resized 自动重算。 目前图标条只放了「会话列表」一个工具图标(资源面板仍用原箭头收起);以后要把更多面板纳入这条 图标栏很容易扩展。 feat(welcome): IDEA-style collapse drawer — welcome sidebar collapses to an edge icon strip (v0.5 M4) In sidebar mode there is now an always-on 36px edge icon strip (IDEA tool-window style); the welcome/session sidebar collapses into it: - The strip is always present (there from launch); it holds a session icon, highlighted (accent bg + glyph) while the drawer is open. - Click the icon to toggle: open -> collapsed (sidebar hidden, terminal area widens), collapsed -> open. The central terminal never collapses. - Collapsed/open state is persisted (welcome_collapsed) across restarts. Layout: dock-area carves strip-w (the icon strip) + welcome-w (the sidebar, 0 when collapsed) off the left; everything else (resource panel + central) shifts right by strip-w+welcome-w in `rest`. The width drag handle adds the strip-w offset. Collapse is pure reactive Slint reflow; the terminal widens via the content-resized → refresh_panes path. The strip holds one tool icon (the session list) for now; the resource panel still uses its own arrow collapse. Adding more panels to the strip later is straightforward. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
把 IDEA 式收起做成可复用组件 ToolStrip(widgets.slint):收起时沿停靠边渲染一条细图标条, 显示该面板的图标,点击展开;朝向随 dock-edge(左/右竖条、上/下横条)。 欢迎侧栏从「常驻图标条」改成「仅收起时出现」(按你的选择): - 展开:只有面板,标题行加一个「‹」收起按钮(compact 模式); - 收起:左边一条 ToolStrip 图标条(会话图标),点击展开; - strip-w / welcome-w 互斥(展开给面板、收起给图标条),rest 跟着右移。 下一步把资源面板和 SFTP 也换成 ToolStrip(去掉它们的箭头展开按钮)。 refactor(panes): extract a reusable ToolStrip collapse bar; welcome sidebar becomes collapse-only (v0.5 M5a) Extracts the IDEA-style collapse into a reusable ToolStrip component (widgets.slint): when a panel is collapsed the host renders a thin icon bar along its docked edge showing the panel's icon; clicking re-expands. Orientation follows dock-edge (vertical bar for left/right, horizontal for top/bottom). The welcome sidebar moves from an always-on strip to collapse-only (per your choice): - expanded: just the panel, with a "‹" collapse button in the header (compact mode); - collapsed: a left ToolStrip icon bar (session glyph), click to re-open; - strip-w / welcome-w are mutually exclusive (panel when open, icon bar when collapsed) and `rest` shifts accordingly. Next: switch the resource panel and SFTP to ToolStrip too (dropping their arrow expand buttons). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
把资源面板和 SFTP 的收起也换成 M5 的公共 ToolStrip 图标条(去掉原来的 DockExpandButton 箭头): - 资源面板:收起时在 rest 里、其停靠边渲染一条 36px ToolStrip(dashboard 图标),点击展开; sb-w/sb-h 收起值从 0 改成 36(给图标条留位),中央区相应右移/上移;顺手去掉旧的标签栏 30px 占位 + 右上工具栏的 30px→36px 偏移。 - SFTP(每会话):收起时在各自 pane 内、SFTP 停靠边渲染 36px ToolStrip(folder 图标); sf-w/sf-h 收起值 0→36。收起不再把高度戳成 30px(那会污染全局默认、让新会话 SFTP 只有 30px), 高度收起期间原样保留,展开直接复原。 现在欢迎/资源/SFTP 三者收起都是同一套 IDEA 图标条;左/下/右取决于各面板停靠在哪条边。 feat(panes): resource panel + SFTP collapse via ToolStrip too, drop the arrow buttons (v0.5 M5b) Switch the resource panel and SFTP collapse to the shared M5 ToolStrip icon bar (removing the old DockExpandButton arrows): - Resource panel: when collapsed, a 36px ToolStrip (dashboard glyph) renders inside `rest` at its docked edge; click to expand. sb-w/sb-h collapsed value goes 0 -> 36 (reserve the bar) and the central shifts accordingly; also dropped the old 30px tab-strip spacer and bumped the top-right toolbar offset 30 -> 36. - SFTP (per session): when collapsed, a 36px ToolStrip (folder glyph) renders inside each pane at the SFTP's docked edge; sf-w/sf-h collapsed 0 -> 36. Collapsing no longer pokes the height to 30px (which would pollute the global default seeded into new sessions) — the height is preserved while collapsed and restored as-is on expand. Welcome / resource / SFTP now all collapse with the same IDEA icon bar; left/bottom/right follows wherever each panel is docked. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
浅色 + 壁纸时白色磨砂太浓,把三处背景填充的不透明度在浅色模式下调低(深色不动、无壁纸的纯色模式 不受影响,因为 frost 无壁纸时返回原色): - 面板 panel-alpha:0.86 → 0.72(浅色) - 根背景 bg-root:0.42 → 0.30(浅色) - 终端 term-bg:0.72 → 0.55(浅色) 只动背景填充的 alpha,文字/边框/组件颜色都没变。数值好调,觉得还浓/太淡再说一声。 style(theme): thin out the light-mode wallpaper white wash so the wallpaper reads more (text unchanged) (v0.5) The frosted white wash was too heavy in light mode over a wallpaper. Lowered the opacity of three background fills in light mode only (dark mode unchanged; the opaque no-wallpaper mode is unaffected since `frost` returns the base colour when no wallpaper is active): - panels panel-alpha: 0.86 -> 0.72 (light) - root bg-root: 0.42 -> 0.30 (light) - terminal term-bg: 0.72 -> 0.55 (light) Only the background-fill alphas change; text / border / component colours are untouched. Easy to tune — say the word if it's still too heavy or now too faint. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
把上一版写死的浅色透明度还原(panel-alpha 回到 0.86),改成用户可调: - 设置 → 界面 → 壁纸,新增「壁纸遮罩透明度」横向拖动条(越往右越透,0–60%);实时预览、 松手持久化(wallpaper_overlay 配置)。 - 一条拖动条统一控制整体磨砂:panel-alpha 由它驱动,bg-root / term-bg 从 panel-alpha 派生 (-0.44 / -0.14,保持原比例,0.86 时正好等于原来的 0.42 / 0.72)。深浅模式共用;没设壁纸时 不生效(frost 无壁纸返回原色)。 feat(theme): make the wallpaper-overlay opacity an adjustable slider (Interface › Wallpaper), restore default (v0.5) Reverts the previous hard-coded light-mode tweak (panel-alpha back to 0.86) and makes it user-adjustable instead: - Interface › Wallpaper gains a "Wallpaper transparency" horizontal slider (further right = more transparent, 0–60%); live preview, persisted on release (wallpaper_overlay config). - One slider scales the whole frost: panel-alpha is driven by it and bg-root / term-bg derive from panel-alpha (-0.44 / -0.14, keeping the original ratios — at 0.86 they equal the old 0.42 / 0.72). Shared by light/dark; no effect without a wallpaper (frost returns the base). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
设置面板的字偏小/糊:卡片本来就小且字号写死(故意不跟随界面缩放)。两处改: - 卡片加大:660×430 → 720×480,并随新设置缩放。 - 设置 → 界面 → 字体 新增「界面字体大小」步进器(80–160%),驱动 Theme.panel-font;设置面板 的字号(原来写死的 11/12/13/14/15/22px)和卡片尺寸都乘上它,于是本面板能整体放大。持久化。 不动全局「界面缩放」(它本就不影响本面板),两者独立。 feat(settings): bigger settings panel + a "Settings font size" control (Interface › Font) to enlarge it (v0.5) The settings panel text was small/soft: the card was small and its font sizes were hard-coded (deliberately exempt from UI scale). Two changes: - Bigger card: 660×430 -> 720×480, and it scales with the new setting. - Interface › Font gains a "Settings font size" stepper (80–160%) driving Theme.panel-font; the panel's font sizes (the hard-coded 11/12/13/14/15/22px) and the card dimensions both multiply by it, so the whole panel enlarges. Persisted. Leaves the global "UI scale" untouched (it never affected this panel); the two are independent. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
设置面板的字"糊"不是字体问题(它和资源面板用的是同一个默认 UI 字体)。原因是设置卡片居中算出来 是半像素位置(root.width - 卡宽)/2,奇数宽度时落在 .5px,卡片内所有文字就渲染在亚像素偏移上 → 发虚;而资源面板停靠在整数像素 → 清晰。 把卡片的居中 / 拖动位置都 round 到整数逻辑像素(并改用新的 720×480 尺寸)。 fix(settings): snap the settings card to whole pixels to kill the blurry text (half-pixel centering) (v0.5) The settings panel text wasn't blurry because of the font (it uses the same default UI font as the resource panel). The cause: the card is centred at (root.width - cardW)/2, which lands on a half-pixel for odd widths, so every Text inside renders on a subpixel offset and looks soft — while the docked resource panel sits on integer pixels and stays crisp. Round the card's centred / dragged position to whole logical pixels (and use the new 720×480 size in the clamp). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
版本号 0.4.20 → 0.5.0,补 CHANGELOG 0.5.0 条目:IDEA 式拖动分屏、复制打开、欢迎页设为侧栏 + IDEA 式收起、壁纸遮罩透明度可调、界面字体大小、每会话独立 SFTP,以及设置面板字体发虚的修复。 chore(release): release v0.5.0 (split panes + welcome sidebar + wallpaper/font settings) Bump 0.4.20 → 0.5.0 and add the 0.5.0 CHANGELOG entry: IDEA-style drag-to-split panes, duplicate connection, welcome-page-as-sidebar + IDEA-style collapse, adjustable wallpaper overlay opacity, settings font size, per-session SFTP state, plus the blurry-settings-text fix. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- 欢迎页侧栏模式拆分布局:标题行(快速连接+收起按钮) / 操作行(新建按钮+导入提示) - 标签页模式保持单行布局,按钮在右上角 - 欢迎侧边栏最小宽度 160→200px,防止按钮溢出 - 资源面板收起时添加 visible 控制,修复壁纸高透明度下透出 - PrimaryButton 文字水平居中
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.