Skip to content

ui:工作台界面优化及张力心电图bug修复#83

Merged
shenminglinyi merged 4 commits into
shenminglinyi:masterfrom
yanxiaofei395118:master
Apr 17, 2026
Merged

ui:工作台界面优化及张力心电图bug修复#83
shenminglinyi merged 4 commits into
shenminglinyi:masterfrom
yanxiaofei395118:master

Conversation

@yanxiaofei395118

@yanxiaofei395118 yanxiaofei395118 commented Apr 17, 2026

Copy link
Copy Markdown
Collaborator

变更类型

  • feat 新功能
  • fix Bug 修复
  • refactor 重构(不影响功能)

变更说明

工作台 UI 布局与交互优化:

  1. 左右面板支持拖拽调整宽度与折叠/展开(替代 n-split)
  2. 中间内容区滚动条默认隐藏,悬停时显示
  3. 故事结构树在面板过窄时隐藏节点后缀

面板标题区域布局统一:

  • 作品设定、侧栏资料、伏笔账本、全息编年史等面板的标题、标签、操作按钮改为同一行 flex 布局,避免按钮被挤出

监控网格高度一致性:

  • 张力图表与实时日志卡片高度统一为 200px
  • 网格单元格垂直拉伸对齐

图表修复:

  • 张力心电图刷新后重新初始化实例
  • 新增 ResizeObserver 监听容器尺寸变化自动 resize

工具栏按钮防换行:

  • 关系图工具栏按钮布局修复,提示文本设 flex:1 避免挤占按钮

Bug 修复:

  • 修复 ForeshadowLedgerPanel 中 Naive UI 按钮属性冲突(dashed 与 secondary 不能同时使用)
  • 清理 BiblePanel 重复样式定义

架构影响

  • 涉及层级:frontend
  • 是否新增数据库表/字段:否
  • 是否修改现有 API 契约:否

测试

  • 新增/修改的逻辑有对应单测
  • 本地后端启动正常
  • 本地前端启动正常

风险说明

  • 潜在风险:无(纯 UI 样式调整)
  • 回滚方式:git revert

Git 提交信息

Summary by CodeRabbit

Release Notes

  • New Features

    • Added resizable left and right panes in the workbench with collapse controls for flexible workspace management.
  • Improvements

    • Enhanced responsive layout that adapts component display for narrower widths.
    • Reorganized panel header layouts for improved visual hierarchy and consistency.
    • Refined spacing and typography throughout the interface.
    • Improved chart and dashboard responsiveness to container size changes.

yanxiaofei395118 and others added 4 commits April 17, 2026 16:56
- 工作台左右面板支持拖拽调整宽度与折叠/展开
- 中间内容区滚动条默认隐藏,悬停时显示
- 故事结构树窄屏时隐藏节点后缀
- 统一各面板标题行布局(标题+标签+按钮同行)
- 监控网格张力图表与实时日志高度一致
- 张力心电图刷新后重新初始化,新增 ResizeObserver 自适应
- 关系图工具栏按钮防换行布局
- 修复 Naive UI 按钮属性冲突(dashed/secondary)
@coderabbitai

coderabbitai Bot commented Apr 17, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Multiple frontend components received layout reorganization and responsive behavior enhancements. Changes include header restructuring across several panels, addition of resize observers for responsive width-based rendering, refactoring of the main Workbench layout from <n-split> to custom flexbox with drag-resizable panes, and grid/spacing optimizations in dashboard and chart components.

Changes

Cohort / File(s) Summary
Responsive Container Behavior
frontend/src/components/StoryStructureTree.vue, frontend/src/components/autopilot/RealtimeLogStream.vue
Added container width tracking via ResizeObserver and responsive rendering logic. StoryStructureTree rebuilds node display names based on width thresholds (e.g., chapter titles collapse to 第X章 when narrow). RealtimeLogStream adjusted flex-based height sizing to fill available space with deep style overrides on Naive UI card components.
Chart and Dashboard Lifecycle Updates
frontend/src/components/autopilot/TensionChart.vue, frontend/src/components/autopilot/AutopilotDashboard.vue, frontend/src/components/autopilot/AutopilotPanel.vue
TensionChart added ResizeObserver for dynamic chart resizing, new refreshChart() method, and improved initialization lifecycle. AutopilotDashboard removed inline hint block and updated grid layout (grid-auto-rows: minmax(200px, auto) with flex children). AutopilotPanel reduced padding.
Header Layout Reorganization
frontend/src/components/knowledge/KnowledgePanel.vue, frontend/src/components/panels/BiblePanel.vue, frontend/src/components/workbench/ForeshadowLedgerPanel.vue, frontend/src/components/workbench/HolographicChroniclesPanel.vue
Refactored header structures across multiple components: moved action buttons into title rows, repositioned descriptive text, removed intermediate wrapper elements, and adjusted CSS for improved alignment and spacing. All maintain functional button wiring while improving layout flow.
Toolbar Styling
frontend/src/components/graphs/CastGraphCompact.vue, frontend/src/components/graphs/LocationGraphCompact.vue
Updated toolbar alignment from flex-start to center, reduced vertical padding, and changed hint width constraint from fixed max-width to flex-based layout with min-width: 0 for better responsive text wrapping.
Main Layout Refactor
frontend/src/views/Workbench.vue, frontend/src/components/workbench/WorkArea.vue
Replaced <n-split>-based layout with custom flexbox implementation featuring fixed-width side panes, central flexible WorkArea, and mouse-driven drag resizing with collapse controls. WorkArea now includes scrollable managed-mode stack, new monitoring hint block, and adjusted spacing. Comprehensive CSS restructuring for borders, dividers, and collapse buttons.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • shenminglinyi

Poem

🐰 Hop, resize, and reorganize!
Headers bloom in measured lines,
Workbench flexes, panes align,
Charts observe the container's size—
Layout magic, now responsive and so fine!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: UI optimization for the workbench and a bug fix for the tension chart, which aligns well with the comprehensive frontend modifications across multiple component layout improvements and the TensionChart ResizeObserver fix.
Description check ✅ Passed The description includes all required sections from the template: change type (feat/fix/refactor), detailed explanation of changes, architecture impact (frontend only, no DB/API changes), testing checklist (some items checked), and risk assessment. While the testing section shows no new unit tests were added, the description properly indicates frontend startup was tested, which is appropriate for UI-focused changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (1)
frontend/src/components/panels/BiblePanel.vue (1)

537-547: bible-roles 的单列切换阈值建议上调。

max-width: 320px 对可拖拽侧栏偏低,320~500px 区间仍双列会明显拥挤。建议回调到更实用阈值(如 480/520)。

💡 Suggested tweak
-@media (max-width: 320px) {
+@media (max-width: 520px) {
   .bible-roles {
     grid-template-columns: 1fr;
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/panels/BiblePanel.vue` around lines 537 - 547, The
responsive breakpoint for the .bible-roles grid is too small (max-width: 320px)
causing two columns to be cramped on narrow screens; update the media query
controlling .bible-roles to use a larger threshold (e.g., max-width: 480px or
520px) so it switches to a single column earlier. Locate the .bible-roles CSS
and replace the existing `@media` (max-width: 320px) rule with the chosen larger
max-width value to force grid-template-columns: 1fr at that breakpoint.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/components/autopilot/RealtimeLogStream.vue`:
- Around line 522-524: The CSS rule in RealtimeLogStream.vue sets a hard
min-width on the .realtime-log-stream class which causes overflow on narrow
layouts; remove the min-width: 368px (or replace it with a responsive rule such
as max-width, width: 100%, or a media-query that only applies a min-width at
larger breakpoints) so the RealtimeLogStream component can shrink with the
center pane (update the .realtime-log-stream declaration in
RealtimeLogStream.vue accordingly).

In `@frontend/src/components/autopilot/TensionChart.vue`:
- Around line 154-163: The chart instance reuse bug occurs because we only check
chartInstance.isDisposed() before reusing it; update the initialization logic
(around chartInstance, chartRef, resizeObserver) to also detect a stale DOM by
comparing chartInstance.getDom() !== chartRef.value and force a
re-initialization (dispose and init) when they differ, and in refreshChart()
ensure you call resizeObserver.disconnect() (and set resizeObserver = null)
before clearing/letting the DOM unmount so the observer is not left attached to
a destroyed element; finally, when creating a new chart instance
(init(chartRef.value)), recreate and reattach a new ResizeObserver that observes
chartRef.value.

In `@frontend/src/components/knowledge/KnowledgePanel.vue`:
- Around line 4-6: The header action buttons in KnowledgePanel.vue can overflow
when the split-pane is very narrow because .kp-header-row is a single-line flex
container and the action area (the n-space shown when sideTab === 'narrative')
doesn't wrap or shrink; update the header layout to allow wrapping and
shrinking: make the .kp-header-row flex container enable flex-wrap and ensure
the title (kp-title) and the action container (the n-space for sideTab
'narrative') have appropriate flex properties (e.g., title flex: 0 1 auto and
action area flex: 0 1 auto or 1 1 auto) or set the action area to wrap its items
so buttons move to a new line at narrow widths; apply the same fix to the other
header instance noted (lines 829-836).

In `@frontend/src/components/panels/BiblePanel.vue`:
- Around line 5-13: The .bible-title-row layout can cause the action buttons to
overflow when the panel is narrow; update the CSS for .bible-title-row and
.bible-hero-actions (used around the n-button elements and props generating/save
methods) to allow wrapping: make .bible-title-row display:flex with
align-items:center and flex-wrap:wrap, give the title (.bible-title) flex:1 1
auto and .bible-hero-actions flex:0 0 auto with a max-width or min-width clamp
on its child buttons to allow them to wrap to the next line when space is
constrained; ensure the Vue methods generateBible and save and reactive flags
generating/saving remain unchanged.

In `@frontend/src/components/workbench/ForeshadowLedgerPanel.vue`:
- Around line 8-14: The title row currently uses a fixed single-line layout
which causes action buttons (n-button) to overflow when the workbench is dragged
narrower; update the CSS for the .title-row/.panel-title/.header-actions block
to support a narrow-width fallback by enabling flex-wrap on .title-row and
allowing .panel-title to shrink/grow (e.g., flex: 1 1 auto) while making
.header-actions wrap to a new line at small widths (e.g., flex-basis: 100% or
width: 100% with margin-top) so the + 添加伏笔 button (openCreateModal) and 刷新
button (load, bound to loading) move under the title instead of overflowing;
apply the same style changes to the other similar blocks referenced (around the
other title rows).

In `@frontend/src/views/Workbench.vue`:
- Around line 128-137: Initial left/right pane widths can exceed the viewport,
leaving the center pane unusable; on mount and on window resize clamp them so
they never reserve more space than available. Implement an onMounted() and
resize listener that reads window.innerWidth, computes the max allowed side
width (e.g. availableWidth = window.innerWidth; maxSides = Math.max(0,
availableWidth - MIN_CENTER_WIDTH)), then if leftWidth.value + rightWidth.value
> maxSides reduce them (either clamp each with LEFT_MIN/LEFT_MAX and
RIGHT_MIN/RIGHT_MAX or scale them proportionally) and write back to
leftWidth.value and rightWidth.value; use the existing constants LEFT_MIN,
LEFT_MAX, RIGHT_MIN, RIGHT_MAX and refs leftWidth/rightWidth, and update/cleanup
the resize listener on unmount.

---

Nitpick comments:
In `@frontend/src/components/panels/BiblePanel.vue`:
- Around line 537-547: The responsive breakpoint for the .bible-roles grid is
too small (max-width: 320px) causing two columns to be cramped on narrow
screens; update the media query controlling .bible-roles to use a larger
threshold (e.g., max-width: 480px or 520px) so it switches to a single column
earlier. Locate the .bible-roles CSS and replace the existing `@media` (max-width:
320px) rule with the chosen larger max-width value to force
grid-template-columns: 1fr at that breakpoint.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e274b4ee-66e8-4f45-8f13-1c0edf9bd617

📥 Commits

Reviewing files that changed from the base of the PR and between 12ea3a2 and dcd686e.

📒 Files selected for processing (13)
  • frontend/src/components/StoryStructureTree.vue
  • frontend/src/components/autopilot/AutopilotDashboard.vue
  • frontend/src/components/autopilot/AutopilotPanel.vue
  • frontend/src/components/autopilot/RealtimeLogStream.vue
  • frontend/src/components/autopilot/TensionChart.vue
  • frontend/src/components/graphs/CastGraphCompact.vue
  • frontend/src/components/graphs/LocationGraphCompact.vue
  • frontend/src/components/knowledge/KnowledgePanel.vue
  • frontend/src/components/panels/BiblePanel.vue
  • frontend/src/components/workbench/ForeshadowLedgerPanel.vue
  • frontend/src/components/workbench/HolographicChroniclesPanel.vue
  • frontend/src/components/workbench/WorkArea.vue
  • frontend/src/views/Workbench.vue

Comment on lines 522 to +524
.realtime-log-stream {
height: 100%;
min-width: 368px;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Drop the hard min-width here.

Below 900px, frontend/src/components/autopilot/AutopilotDashboard.vue switches to a single-column grid, and frontend/src/views/Workbench.vue now reserves ~900px for the two side panes by default. This min-width: 368px forces the log card to overflow/clamp as soon as the center pane is narrower than 368px, which is easy to hit on smaller windows.

Suggested fix
 .realtime-log-stream {
   height: 100%;
-  min-width: 368px;
+  min-width: 0;
+  width: 100%;
   position: relative;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.realtime-log-stream {
height: 100%;
min-width: 368px;
.realtime-log-stream {
height: 100%;
min-width: 0;
width: 100%;
position: relative;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/autopilot/RealtimeLogStream.vue` around lines 522 -
524, The CSS rule in RealtimeLogStream.vue sets a hard min-width on the
.realtime-log-stream class which causes overflow on narrow layouts; remove the
min-width: 368px (or replace it with a responsive rule such as max-width, width:
100%, or a media-query that only applies a min-width at larger breakpoints) so
the RealtimeLogStream component can shrink with the center pane (update the
.realtime-log-stream declaration in RealtimeLogStream.vue accordingly).

Comment on lines +154 to +163
// 如果实例已被销毁或不存在,重新初始化
if (!chartInstance || chartInstance.isDisposed()) {
chartInstance = init(chartRef.value)
// 在 chartRef 确保存在后才初始化 ResizeObserver(解决 onMounted 时 chartRef 为 null 的问题)
if (!resizeObserver && chartRef.value) {
resizeObserver = new ResizeObserver(() => {
chartInstance?.resize()
})
resizeObserver.observe(chartRef.value)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

fd -t f TensionChart.vue

Repository: shenminglinyi/PlotPilot

Length of output: 117


🏁 Script executed:

cat -n frontend/src/components/autopilot/TensionChart.vue | head -100

Repository: shenminglinyi/PlotPilot

Length of output: 3923


🏁 Script executed:

cat -n frontend/src/components/autopilot/TensionChart.vue | sed -n '150,170p'

Repository: shenminglinyi/PlotPilot

Length of output: 853


🏁 Script executed:

cat -n frontend/src/components/autopilot/TensionChart.vue | sed -n '305,325p'

Repository: shenminglinyi/PlotPilot

Length of output: 656


🏁 Script executed:

cat -n frontend/src/components/autopilot/TensionChart.vue | sed -n '1,40p'

Repository: shenminglinyi/PlotPilot

Length of output: 1844


🏁 Script executed:

rg -t ts "getDom|dispose|isDisposed" frontend/src/components/autopilot/TensionChart.vue

Repository: shenminglinyi/PlotPilot

Length of output: 215


🌐 Web query:

ECharts TypeScript getDom method API

💡 Result:

The ECharts instance method getDom returns the DOM element (HTMLElement) that serves as the container for the ECharts chart. It is available on the EChartsType interface, which extends the ECharts class and is the return type of echarts.init. Signature (from ECharts 4+ documentation, consistent across versions including latest ECharts 6): getDom: HTMLCanvasElement | HTMLDivElement Usage example in TypeScript: typescript import * as echarts from 'echarts'; const chart = echarts.init(document.getElementById('main') as HTMLElement); const domContainer = chart.getDom; // Returns the original DOM element passed to init console.log(domContainer); // HTMLElement (typically div or canvas) This method retrieves the root DOM container where the chart was initialized, useful for accessing the container element after initialization (e.g., for resizing or DOM manipulation). It is listed under Dimensions methods alongside getWidth and getHeight in instance API documentation. Note: Official API docs at echarts.apache.org/en/api.html redirect to the main site; detailed method docs are preserved in source repositories and third-party mirrors like Mintlify. Source code confirms getDom is public on EChartsType.

Citations:


🏁 Script executed:

rg -t ts "from 'echarts" frontend/src/components/autopilot/TensionChart.vue | head -10

Repository: shenminglinyi/PlotPilot

Length of output: 347


🏁 Script executed:

cat -n frontend/src/components/autopilot/TensionChart.vue | sed -n '100,145p'

Repository: shenminglinyi/PlotPilot

Length of output: 1702


🏁 Script executed:

cat -n frontend/src/components/autopilot/TensionChart.vue | sed -n '240,280p'

Repository: shenminglinyi/PlotPilot

Length of output: 1884


🏁 Script executed:

cat -n frontend/src/components/autopilot/TensionChart.vue | sed -n '322,350p'

Repository: shenminglinyi/PlotPilot

Length of output: 940


🏁 Script executed:

cat -n frontend/src/components/autopilot/TensionChart.vue | sed -n '112,145p'

Repository: shenminglinyi/PlotPilot

Length of output: 1269


Detect when the ECharts instance is bound to a stale DOM node, and disconnect the ResizeObserver during refresh.

The template conditionally renders the chart container (line 27). When refreshChart() clears tensionData (line 312), the template switches to the empty state (line 17), unmounting the old DOM element. When loadTensionData() completes with new data, line 27 renders a new DOM element. However, the check at line 155 only tests isDisposed(), so the old instance—still alive and bound to the destroyed element—is reused instead of re-initialized on the new DOM.

Additionally, resizeObserver is never disconnected in refreshChart() (lines 310–320), leaving it attached to the destroyed element.

Suggested fix
 let chartInstance: ECharts | null = null
 let resizeObserver: ResizeObserver | null = null

+function bindResizeObserver(el: HTMLElement) {
+  resizeObserver?.disconnect()
+  resizeObserver = new ResizeObserver(() => {
+    chartInstance?.resize()
+  })
+  resizeObserver.observe(el)
+}
+
 function renderChart() {
   if (!chartRef.value || tensionData.value.length === 0) return

   const rect = chartRef.value.getBoundingClientRect()
   if (rect.width < 10 || rect.height < 10) {
     setTimeout(() => renderChart(), 200)
     return
   }

-  if (!chartInstance || chartInstance.isDisposed()) {
-    chartInstance = init(chartRef.value)
-    if (!resizeObserver && chartRef.value) {
-      resizeObserver = new ResizeObserver(() => {
-        chartInstance?.resize()
-      })
-      resizeObserver.observe(chartRef.value)
-    }
+  if (
+    !chartInstance ||
+    chartInstance.isDisposed() ||
+    chartInstance.getDom() !== chartRef.value
+  ) {
+    chartInstance?.dispose()
+    chartInstance = init(chartRef.value)
+    bindResizeObserver(chartRef.value)
   }
   // ...
 }

 async function refreshChart() {
   tensionData.value = []
+  resizeObserver?.disconnect()
+  resizeObserver = null
   if (chartInstance) {
     chartInstance.dispose()
+    chartInstance = null
   }
   await nextTick()
   await loadTensionData()
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/autopilot/TensionChart.vue` around lines 154 - 163,
The chart instance reuse bug occurs because we only check
chartInstance.isDisposed() before reusing it; update the initialization logic
(around chartInstance, chartRef, resizeObserver) to also detect a stale DOM by
comparing chartInstance.getDom() !== chartRef.value and force a
re-initialization (dispose and init) when they differ, and in refreshChart()
ensure you call resizeObserver.disconnect() (and set resizeObserver = null)
before clearing/letting the DOM unmount so the observer is not left attached to
a destroyed element; finally, when creating a new chart instance
(init(chartRef.value)), recreate and reattach a new ResizeObserver that observes
chartRef.value.

Comment on lines +4 to 6
<div class="kp-header-row">
<h3 class="kp-title">侧栏资料</h3>
<n-space v-show="sideTab === 'narrative'" :size="8" align="center" style="flex-shrink:0">

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Header actions may be clipped in narrow split-pane widths.

当前标题行固定单行且操作区不收缩,面板被拖窄时按钮可能溢出并影响点击。建议给窄宽度加换行兜底。

💡 Suggested fix
 .kp-header-row {
   display: flex;
   justify-content: space-between;
   align-items: center;
   gap: 12px;
   padding: 10px 12px 6px;
   flex-shrink: 0;
 }
+
+@media (max-width: 560px) {
+  .kp-header-row {
+    flex-wrap: wrap;
+    align-items: flex-start;
+  }
+}

Also applies to: 829-836

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/knowledge/KnowledgePanel.vue` around lines 4 - 6, The
header action buttons in KnowledgePanel.vue can overflow when the split-pane is
very narrow because .kp-header-row is a single-line flex container and the
action area (the n-space shown when sideTab === 'narrative') doesn't wrap or
shrink; update the header layout to allow wrapping and shrinking: make the
.kp-header-row flex container enable flex-wrap and ensure the title (kp-title)
and the action container (the n-space for sideTab 'narrative') have appropriate
flex properties (e.g., title flex: 0 1 auto and action area flex: 0 1 auto or 1
1 auto) or set the action area to wrap its items so buttons move to a new line
at narrow widths; apply the same fix to the other header instance noted (lines
829-836).

Comment on lines +5 to 13
<div class="bible-title-row">
<h3 class="bible-title">作品设定</h3>
<n-tag size="small" round :bordered="false" class="bible-badge">Story Bible</n-tag>
<div class="bible-hero-actions">
<n-button size="small" secondary :loading="generating" @click="generateBible" title="用 AI 根据小说标题重新生成设定">
✦ AI 生成
</n-button>
<n-button size="small" type="primary" :loading="saving" @click="save">保存设定</n-button>
</div>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

标题行在窄面板下可能挤压操作按钮。

当前一行布局 + 操作区不收缩,在分栏拖窄时可能出现按钮溢出。建议加窄宽度换行兜底。

💡 Suggested fix
 .bible-title-row {
   display: flex;
   align-items: center;
   gap: 8px;
   margin-bottom: 8px;
 }
@@
 .bible-hero-actions {
   margin-left: auto;
   display: flex;
   align-items: center;
   gap: 8px;
   flex-shrink: 0;
 }
+
+@media (max-width: 560px) {
+  .bible-title-row {
+    flex-wrap: wrap;
+  }
+}

Also applies to: 493-506

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/panels/BiblePanel.vue` around lines 5 - 13, The
.bible-title-row layout can cause the action buttons to overflow when the panel
is narrow; update the CSS for .bible-title-row and .bible-hero-actions (used
around the n-button elements and props generating/save methods) to allow
wrapping: make .bible-title-row display:flex with align-items:center and
flex-wrap:wrap, give the title (.bible-title) flex:1 1 auto and
.bible-hero-actions flex:0 0 auto with a max-width or min-width clamp on its
child buttons to allow them to wrap to the next line when space is constrained;
ensure the Vue methods generateBible and save and reactive flags
generating/saving remain unchanged.

Comment on lines +8 to 14
<div class="title-row">
<h3 class="panel-title">伏笔账本</h3>
<n-tag size="small" round :bordered="false">Foreshadow Ledger</n-tag>
<div class="header-actions">
<n-button size="small" secondary @click="openCreateModal">+ 添加伏笔</n-button>
<n-button size="small" type="primary" :loading="loading" @click="load">刷新</n-button>
</div>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Title row needs a narrow-width fallback.

在可拖拽缩窄的工作台中,这里固定单行 + 右侧操作区不收缩,容易出现按钮溢出。建议补一个小屏换行规则。

💡 Suggested fix
 .title-row {
   display: flex;
   align-items: center;
   gap: 10px;
   margin-bottom: 8px;
 }
@@
 .header-actions {
   margin-left: auto;
   display: flex;
   align-items: center;
   gap: 8px;
   flex-shrink: 0;
 }
+
+@media (max-width: 560px) {
+  .title-row {
+    flex-wrap: wrap;
+  }
+}

Also applies to: 400-405, 424-430

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/workbench/ForeshadowLedgerPanel.vue` around lines 8 -
14, The title row currently uses a fixed single-line layout which causes action
buttons (n-button) to overflow when the workbench is dragged narrower; update
the CSS for the .title-row/.panel-title/.header-actions block to support a
narrow-width fallback by enabling flex-wrap on .title-row and allowing
.panel-title to shrink/grow (e.g., flex: 1 1 auto) while making .header-actions
wrap to a new line at small widths (e.g., flex-basis: 100% or width: 100% with
margin-top) so the + 添加伏笔 button (openCreateModal) and 刷新 button (load, bound to
loading) move under the title instead of overflowing; apply the same style
changes to the other similar blocks referenced (around the other title rows).

Comment on lines +128 to +137
// ━━━ 侧栏宽度 & 折叠 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
const LEFT_MIN = 238
const LEFT_MAX = 600
const RIGHT_MIN = 260
const RIGHT_MAX = 520

const leftWidth = ref(388)
const rightWidth = ref(512)
const leftCollapsed = ref(false)
const rightCollapsed = ref(false)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Clamp the initial pane widths to the available viewport.

These defaults reserve ~900px for the side panes before the center pane gets any room. Since .main-pane is allowed to shrink to 0, the editor can render nearly unusable on smaller windows until the user manually collapses or drags a pane.

A responsive fallback on mount/resize would avoid that first-render regression.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/views/Workbench.vue` around lines 128 - 137, Initial left/right
pane widths can exceed the viewport, leaving the center pane unusable; on mount
and on window resize clamp them so they never reserve more space than available.
Implement an onMounted() and resize listener that reads window.innerWidth,
computes the max allowed side width (e.g. availableWidth = window.innerWidth;
maxSides = Math.max(0, availableWidth - MIN_CENTER_WIDTH)), then if
leftWidth.value + rightWidth.value > maxSides reduce them (either clamp each
with LEFT_MIN/LEFT_MAX and RIGHT_MIN/RIGHT_MAX or scale them proportionally) and
write back to leftWidth.value and rightWidth.value; use the existing constants
LEFT_MIN, LEFT_MAX, RIGHT_MIN, RIGHT_MAX and refs leftWidth/rightWidth, and
update/cleanup the resize listener on unmount.

@shenminglinyi
shenminglinyi merged commit 5e59e40 into shenminglinyi:master Apr 17, 2026
2 checks passed
@shenminglinyi

Copy link
Copy Markdown
Owner

你好,

非常感谢 @yanxiaofei395118 这次对工作台 UI 的用心打磨:可拖拽侧栏、布局统一、张力图与监控区高度对齐等,都是实实在在提升体验的细节。合并时与上游 PR 在张力图上有少量冲突,已在本地合并处理并推上 master,当前仓库已包含你的改动。

若你本地还有旧分支,拉一下最新 master 即可。若之后有任何不顺手的地方,随时欢迎继续反馈或 PR。

再次感谢你的时间与贡献,祝顺利 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants