diff --git a/PROGRESS.md b/PROGRESS.md index 61b6027..a093b75 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -2,7 +2,7 @@ > 配合 `DESIGN.md`。本文件只记 phase 状态、决策偏差、文件量、下一步。每条 1-2 句:做了啥 + 关键判断;细节查 `git log` / `git diff` / `DESIGN §7.9`。 -最后更新:2026-05-25(dev SPA 左侧任务列表滚动区收窄到 task 列表) +最后更新:2026-05-25(dev SPA 三栏支持右栏折叠 + 拖拽调宽) --- @@ -23,6 +23,7 @@ ### 2026-05-25 +- **dev SPA 三栏支持右文件栏折叠 + 左右分隔线拖拽调宽**:`web/static/dev.html` 主布局从 3 列 grid 改为 5 列 grid(任务栏 / 左 splitter / 对话栏 / 右 splitter / 文件栏),新增 `#split-left` / `#split-right` 两条 6px 拖拽分隔线,拖动时分别调整 `--left-pane-width` / `--right-pane-width` 并持久化到 localStorage(`zcbot.left-width` / `zcbot.right-width`)。右侧文件栏新增 `#pane-toggle-right`,折叠态复用左栏 rail 范式:列宽 40px,只保留展开按钮,状态持久化到 `zcbot.right-collapsed`;手机端继续走三 tab 单列,隐藏折叠按钮和 splitter,避免与移动端导航冲突。`DESIGN.md` 不动(纯 dev SPA 布局交互);`RUN.md` 不动(运行方式无变化)。 - **dev SPA 右侧文件列表长名称 hover 显示全路径**:`web/static/dev.html` 在右 pane 文件行 `.file-row .name` 和"选入…"源文件列表 `.sp-row .sp-name` 上补 `title`,内容取 `e.rel || e.name`,保留现有 ellipsis 截断视觉,鼠标悬停可看完整相对路径/名称。`DESIGN.md` 不动(无架构/心智模型变化);`RUN.md` 不动(运行方式无变化)。 - **dev SPA 左侧滚动条只覆盖 task 列表**:`web/static/dev.html` 左 pane 改成 flex column,顶部 4 行 pane-head(任务标题/新建/搜索筛选/排序)固定不参与滚动;`#task-list` 与 `#task-sentinel` 包进 `#task-scroll`,并把 IntersectionObserver root 从 `#pane-left` 改到 `#task-scroll`,保证无限滚动仍按列表区域触发。`DESIGN.md` 不动(无架构/心智模型变化);`RUN.md` 不动(运行方式无变化)。 diff --git a/web/static/dev.html b/web/static/dev.html index 4832428..3166953 100644 --- a/web/static/dev.html +++ b/web/static/dev.html @@ -176,9 +176,18 @@ /* ───── 3-pane layout ───── */ #app { display: none; height: 100vh; } - #app.ready { display: grid; grid-template-columns: 320px 1fr 320px; grid-template-rows: auto 1fr; grid-template-areas: "head head head" "left mid right"; } + #app.ready { + --left-grid-width: var(--left-pane-width, 320px); + --right-grid-width: var(--right-pane-width, 320px); + display: grid; + grid-template-columns: var(--left-grid-width) 6px minmax(0, 1fr) 6px var(--right-grid-width); + grid-template-rows: auto 1fr; + grid-template-areas: + "head head head head head" + "left split-left mid split-right right"; + } /* 折叠左 pane:rail 模式,列收成 40px,pane 内只留一个展开按钮(类 VS Code 范式) */ - body.left-collapsed #app.ready { grid-template-columns: 40px 1fr 320px; } + body.left-collapsed #app.ready { --left-grid-width: 40px; } body.left-collapsed #pane-left > * { display: none; } body.left-collapsed #pane-left > .pane-head:first-child { display: flex; justify-content: center; align-items: center; @@ -187,6 +196,16 @@ } body.left-collapsed #pane-left > .pane-head:first-child > * { display: none; } body.left-collapsed #pane-left > .pane-head:first-child > #pane-toggle-left { display: inline-block; } + /* 折叠右 pane:同左侧 rail,只留展开按钮 */ + body.right-collapsed #app.ready { --right-grid-width: 40px; } + body.right-collapsed #pane-right > * { display: none; } + body.right-collapsed #pane-right > .pane-head:first-child { + display: flex; justify-content: center; align-items: center; + padding: 6px 4px; border-bottom: none; background: transparent; + position: static; + } + body.right-collapsed #pane-right > .pane-head:first-child > * { display: none; } + body.right-collapsed #pane-right > .pane-head:first-child > #pane-toggle-right { display: inline-block; } header { grid-area: head; background: #fff; border-bottom: 1px solid var(--border); @@ -211,9 +230,23 @@ #pane-left > .pane-head { flex-shrink: 0; } #task-scroll { flex: 1; min-height: 0; overflow: auto; } /* min-height: 0 + overflow: hidden 让内部 flex 子项的 overflow: auto 真正生效(否则被默认 min-height: auto 顶出) */ - #pane-mid { grid-area: mid; display: flex; flex-direction: column; border-right: 1px solid var(--border); background: var(--panel); min-height: 0; overflow: hidden; } + #pane-mid { grid-area: mid; display: flex; flex-direction: column; border-right: 1px solid var(--border); background: var(--panel); min-height: 0; min-width: 0; overflow: hidden; } #pane-right { grid-area: right; border-right: none; overflow: auto; background: var(--panel); min-height: 0; } + .splitter { + min-width: 6px; background: var(--bg); cursor: col-resize; + position: relative; z-index: 5; + } + .splitter::before { + content: ""; position: absolute; top: 0; bottom: 0; left: 2px; width: 1px; + background: var(--border); + } + .splitter:hover, body.resizing-panes .splitter.active { background: var(--accent-soft); } + .splitter:hover::before, body.resizing-panes .splitter.active::before { background: var(--accent); } + #split-left { grid-area: split-left; } + #split-right { grid-area: split-right; } + body.resizing-panes { cursor: col-resize; user-select: none; } + .pane-head { padding: 8px 12px; border-bottom: 1px solid var(--border); display: flex; gap: 8px; align-items: center; background: #fafafa; @@ -558,7 +591,12 @@ /* ───── responsive: tablet (641-1024px) ───── 断点内强制 rail(纯 CSS,不写 localStorage;回桌面用户偏好仍生效) */ @media (min-width: 641px) and (max-width: 1024px) { - #app.ready { grid-template-columns: 40px 1fr 260px; } + #app.ready { + --left-grid-width: 40px; + --right-grid-width: min(var(--right-pane-width, 260px), 260px); + grid-template-columns: 40px 0 minmax(0, 1fr) 6px var(--right-grid-width); + } + #split-left { display: none; } #pane-left > * { display: none; } #pane-left > .pane-head:first-child { display: flex; justify-content: center; align-items: center; @@ -588,7 +626,7 @@ body.mv-mid #pane-mid { display: flex; } body.mv-right #pane-right { display: block; } /* 折叠按钮在手机不可见 */ - #pane-toggle-left { display: none !important; } + #pane-toggle-left, #pane-toggle-right, .splitter { display: none !important; } /* header 紧凑化 */ header { padding: 6px 10px; gap: 6px; flex-wrap: wrap; } @@ -796,6 +834,7 @@
+