## 4. 目标架构与文件级改造清单 ### 4.0 重构原则(必须逐条遵守) 1. **只在现有仓库内重构**:不得新建工程、不得换框架、不得引入服务端依赖;沿用 pnpm@11.8.0 monorepo 与 Electron ^44.3.0 + Vue ^3.5.42 + Element Plus ^2.14.5 + Pinia ^4.0.3 + Vite ^8.3.0 + TS ~5.9.3。 2. **渐进式替换,不一次性推倒**:每次改动后应用必须仍可启动、旧 `config.json` 仍可加载;用"门面(facade)+ 新模块"的方式把旧调用点隔离,避免一次改 `src/main/index.ts`、`tray.ts`、`smoke.ts` 的全部 import。 3. **单一事实来源**:任何"主进程与渲染进程各算一次"的数值(窗口包围盒、列表高度、命中矩形、细缝尺寸、字号推导)必须收口到 `packages/shared/src/island/`,两端只调用同一函数。现状三处漂移见 02 §2.3。 4. **保留现有业务语义**:消息去重、上课暂存、紧急展开、已读回执、作业免打扰指纹、课件覆盖优先级、离线补弹,全部保持(02 §2.5/§2.12);ClassIsland 只改变"视觉与布局表现"。 5. **两套风格族(family)共存,且 family 只作用于 fixed**:`appearance.family` 取值 `'classisland' | 'winisland'`;**默认 `classisland`**;`winisland` 保留原胶囊+详情卡外观;老用户升级默认 `winisland`(迁移见 05)。**`custom` 布局始终按 ClassIsland 规格渲染,不受 family 影响**(family 只决定 fixed 用哪套渲染器)。 6. **`fixed` 重新定义**:在 `family='classisland'` 下,`fixed` 不再是胶囊+详情卡,而是 **ClassIsland 风格信息行**(默认 1 行 = 日期 + 课程表,行高 40、圆角 8、背景不透明度 0.5、3px 进度条、消息以行内气泡呈现)。原"胶囊+详情卡"只在 `family='winisland'` 下保留。这是本轮最重要的语义变化,所有文件改造必须按此分叉。 7. **一个原生窗口**:多种布局/风格共用一个 `BrowserWindow`;切换 family/layout 不得新开窗口(现状约束,见 `docs/app/island.md:8-10`、`src/main/island.ts:468-634`)。 8. **不打扰纪律**:默认验证无头/离屏,不创建可见岛或托盘;只有显式 `--visual` 才做桌面像素验收。依据:`docs/dev/testing.md:9,29-32`。 9. **调色板与风格分离**:`style: black|glass|tinted` 继续保留,写为 winisland 调色板字段(canonical `appearance.winislandStyle`,旧 `style` 作为兼容镜像,不删除);family 与 style 独立,切换 family 时保留用户 style 原值。 10. **外观新增定稿字段**:`colorScheme: 'light'|'dark'|'system'`(默认 system)、`theme: 'fluent'|'classic'`(默认 fluent)、`effectEnabled`(默认 true)、`effectScale: 1|0.75|0.5`(默认 1)、`progressBar:{height:3,show:true}`;`fontSize` 语义随 family:classisland=正文基准(新装默认 16,派生 14/18/20),winisland=旧语义(默认 13,迁移保留原值)。 11. **版本只保留一个**:落盘版本只有 `islandLayout.version=2`;`schemaVersion` 仅允许出现在 IPC payload 作运行时校验,不得落盘。 12. **fixed 消息/提醒口径**:行内气泡同一时刻只显示 1 条 active;多条折叠为「还有 N 条」并可打开应用内通知中心;`prepare/on-class/break` 以行内容接管呈现、`ending` 只用蓝色圆形秒数徽标;队列/去重/暂存规则不变。 ### 4.1 目标模块边界与目录 必须把灵动岛拆成四个边界清晰的部分。目录与职责如下(标注「新增」的目录/文件由本轮创建;其余为移动或改造): ```text packages/shared/src/island/ # 新增:岛的唯一契约层(纯 TS,无 DOM/Electron) ├─ index.ts # 统一导出(供 desktop-client 与 web-admin 可复用) ├─ schema.ts # IslandLayoutV2 / IslandAppearanceV2 / IslandRowV2 / IslandNodeV2 类型 ├─ defaults.ts # ClassIsland 规格默认值(family/行/组件/外观) ├─ normalize.ts # normalizeIslandLayoutV2 / normalizeAppearanceV2(校验+夹取) ├─ migrate.ts # migrateIslandLayoutV1ToV2(旧 config → v2) ├─ metrics.ts # CLASSISLAND_ISLAND_METRICS + WINISLAND_ISLAND_METRICS + 排版系数 ├─ geometry.ts # 锚点/包围盒/细缝/触摸贴合尺寸的纯函数(主/渲染共用) ├─ messages.ts # 去重、排序、列表布局、行内气泡分组(纯函数) ├─ lessons.ts # 课程提醒计算(从 main/lesson-reminder.ts 上移) ├─ dto.ts # IslandState/IslandNotification/ScheduleDto 等对外 DTO 与映射 └─ ipc.ts # IPC 通道名与 payload 类型(主进程与 preload 共用) packages/desktop-client/src/main/island/ # 新增:主进程岛引擎(从 island.ts 拆出) ├─ index.ts # 门面:export { island, registerIslandIpc, ... }(旧 import 不变) ├─ controller.ts # 状态机编排:消息、上课状态、预览、动作分发 ├─ window.ts # 建窗、固定包围盒、锚点布局、可见性/焦点/透明度 ├─ hit-test.ts # 命中轮询、岛体矩形、interactive 决策(含 setShape 降级) ├─ touch.ts # 触摸模式、贴合、失焦宽限、常驻控件 ├─ messages.ts # 队列存储 + 合并 + 超时计时器(无 UI) ├─ lessons.ts # 每秒推进课程提醒 ├─ weather.ts # 从 island-weather.ts 移入 ├─ courseware.ts # 课件覆盖状态桥 ├─ preview.ts # 设置页"测试灵动岛"的预览注入 └─ ipc.ts # island:* 全部 handler(从 island.ts 尾部搬来) packages/desktop-client/src/island/ # 渲染进程(现有目录内重组) ├─ main.ts # Keep:tokens + motion + mount ├─ LayoutIslandApp.vue # Change:分发(custom→CustomIslandRuntime;fixed 按 family 二选一) ├─ family/ │ ├─ classisland/ │ │ ├─ FixedInfoRow.vue # 新增:ClassIsland 信息行(fixed 新默认) │ │ ├─ InlineMessageBubble.vue # 新增:行内消息气泡(fixed 下替代详情卡) │ │ └─ ClassislandTheme.css # 新增:Fluent/Classic 两套行样式与动画 │ └─ winisland/ │ ├─ ClassicIsland.vue # Change:由 IslandApp.vue 迁移并改名(原胶囊+详情卡) │ └─ winisland.css # Change:由 classic-island.css 迁移(数值不变,仅 fixed 使用) ├─ custom/ │ ├─ CustomIslandRuntime.vue # Change:始终按 ClassIsland 行/组件规格重写测量与适配(不看 family) │ ├─ CustomRows.vue # 新增:常态行 / 展开行渲染(行级外观) │ ├─ CustomRow.vue # 新增:单行(行设置、隐藏规则、进度条) │ └─ CustomRowSettingsDrawer.vue# 新增:行设置抽屉(背景/字号/圆角/隐藏;字段由 09 定义) ├─ shell/ │ ├─ IslandShell.vue # 新增:squircle 形状 + 弹簧形变 + 命中上报(fixed 两族共用) │ ├─ MessagePanel.vue # Change:由 ClassicNotifications.vue 改造(单条/列表/行内气泡) │ └─ LessonBar.vue # Change:由 LessonReminderContent.vue 改造(ClassIsland 遮罩) ├─ components/ # 新增:按 kind 拆分的组件渲染器 │ ├─ index.ts # node.kind → 渲染器映射 │ ├─ DateNode.vue / ClockNode.vue / TextNode.vue / CountdownNode.vue │ ├─ ScheduleNode.vue / WeatherNode.vue / SeparatorNode.vue │ └─ ContainerNode.vue # group/stack/slide/rolling(rolling 支持 mode continuous|pause) ├─ motion/ # Keep:spring.ts、squircle.ts(可加适配器) ├─ styles/ # 新增:shared.css / classisland.css / custom.css(custom 始终 classisland 命名空间) └─ Legacy/ # 可选:保留旧文件名再导出门面(ComponentNode.vue 等) packages/desktop-client/src/renderer/island/ # 主窗口侧的桥(现有) ├─ bridge.ts # Keep:数据桥(课表/上课状态/通知/作业/叫人/补弹/已读) └─ editor/ # 新增:编辑器逻辑 composables(从 IslandEditorView 抽出) ├─ useIslandDraft.ts # 草稿、脏标记、保存队列、修订号 ├─ useIslandDrag.ts # 指针拖拽/落点/自动滚动 └─ useIslandPreview.ts# 内嵌预览缩放与测量 packages/desktop-client/src/renderer/ # 设置/编辑器页面(现有) ├─ views/settings/IslandSettingsView.vue # Change:family 选择器 + 条件外观 ├─ views/settings/IslandEditorView.vue # Change:调用新版架构 + 行设置 ├─ components/IslandComponentSettings.vue # Change:ClassIsland 组件字段 ├─ components/IslandEditorNode.vue # Keep(可加行级拖拽) ├─ components/IslandRowSettingsDrawer.vue # 新增:行设置抽屉 └─ styles/island-editor.css # Change:ClassIsland 编辑器视觉 ``` ### 4.2 文件级 Keep / Change / Add / Remove 总表 | 文件/目录 | 类型 | 一句话目标 | |---|---|---| | `packages/shared/src/island-layout.ts` | Change | 保留导出名兼容;实现迁移到 `island/` 新目录,本文件仅再导出 | | `packages/shared/src/constants.ts` | Change | 保留旧 WinIsland 常量;新增 ClassIsland 度量与 family 常量(迁移见 05) | | `packages/shared/src/types.ts` | Change | `IslandAppearance` 增加 `family`/`colorScheme`/`effectEnabled`/`effectScale`/`progressBar`/`theme`/`winislandStyle`;新增 `IslandState.family/pendingStaged/bubblePhase/bubbleDeadline`、`IslandNotification.level`、`IslandLessonReminder.progress/leftSeconds`(运行期,不落盘) | | `packages/shared/src/island/ipc.ts` | Add | IPC 通道名与 payload 类型;`island:push` 增加 `source`/`dedupeKey` | | `packages/shared/src/motion.ts` | Keep | 不动令牌数值(03/10 部分引用) | | `packages/shared/src/class-plan.ts` | Keep | `IslandTimePointDto`/`TimePointType` 不动 | | `src/main/island.ts` | Change | 2445 行 → 门面(约 30 行),实际逻辑移入 `main/island/*` | | `src/main/island/*.ts` | Add | 见 4.1(controller/window/hit-test/touch/messages/lessons/weather/courseware/preview/ipc) | | `src/main/config.ts` | Change | 启动时执行 v2 迁移钩子(唯一版本=`islandLayout.version`);写盘前校验 `family/colorScheme/fontSize/effect*`;保留合并写/退出 flush | | `src/main/index.ts` | Change | `island.init()` 改调新门面;注册编辑器/预览所需 IPC | | `src/main/ipc.ts` | Change | 岛相关 handler 全部委托 `main/island/ipc.ts`;主窗口配置剔除规则保留 | | `src/main/courseware.ts` | Change | 改为调用 `island.setCoursewareTransfer`(门面保留同名方法) | | `src/main/tray.ts` | Change | 菜单文案与入口不变;`island.editLayout()` 改由新控制器实现 | | `src/main/island/messages.ts` | Add | 消息队列 + 合并 + 超时 + **作业指纹/去重表**(见 4.3.13) | | `src/main/island/ipc.ts` | Add | 校验 `island:push` 的 `source/dedupeKey`,投递前查重 | | `src/main/smoke.ts` | Change | 保留全部既有读取器与注入点(名称/语义不变,见 4.7 第 11 条);新增 v2 断言(行高/行数/气泡/迁移);改造归属 part 11 测试基线 | | `src/preload/index.ts` | Change | 新增 `family/colorScheme` 感知的配置读写;`islandPush` 透传 `source/dedupeKey`;保留全部旧方法名 | | `src/preload/island.ts` | Change | 新增行/编辑器预览相关事件;旧事件名与语义不变 | | `src/types/desktop.d.ts` | Change | 配置形状加外观新字段与 `islandLayout.version`;新增 v2 DTO 类型;`islandPush` payload 扩展 | | `src/island/LayoutIslandApp.vue` | Change | 分发:`custom` → CustomIslandRuntime;`fixed` 按 family 二选一(见 4.3.1) | | `src/island/IslandApp.vue` | Change | 迁出为 `family/winisland/ClassicIsland.vue`;旧文件保留再导出(防断裂) | | `src/island/CustomIslandRuntime.vue` | Change | **始终按 ClassIsland 行/组件规格**重写测量、行渲染、行设置抽屉;不受 family 影响 | | `src/island/ComponentNode.vue` | Change | 拆为 `components/*Node.vue`;本文件保留兼容导出;滚动组件新增 `settings.mode('continuous'|'pause')` | | `src/island/ClassicNotifications.vue` | Change | 改为 `shell/MessagePanel.vue`;新增行内气泡模式(同一时刻 1 条 active) | | `src/island/LessonReminderContent.vue` | Change | 改为 `shell/LessonBar.vue`;遮罩参数按 ClassIsland 重算;fixed 下 prepare/on-class/break 行内容接管、ending 仅徽标 | | `src/island/CoursewareTransfer.vue` | Keep | 课件覆盖卡保持现状(424×132 等,见 02 §2.10) | | `src/island/WeatherBrief.vue` | Keep | 天气简报保持现状(Open-Meteo/15 分钟/缓存标记);**本轮不引入 ClassIsland 天气服务** | | `src/island/spring.ts`、`squircle.ts` | Keep | 弹簧与连续圆角数值不动(10 部分仍按此写) | | `src/island/classic-island.css` | Change | 改名 `family/winisland/winisland.css`(fixed 专用);数值不变,选择器按新 DOM 调整 | | `src/island/custom-runtime.css`、`custom-island.css` | Change | 移到 `styles/custom.css` 与 `custom/*`;**始终按 ClassIsland 行规格重写**(custom 不受 family 影响) | | `src/island/lesson-ending.css` | Keep | 秒数徽标样式不动 | | `src/island/main.ts` | Keep | 入口与令牌注入顺序不动 | | `src/renderer/views/settings/IslandSettingsView.vue` | Change | 顶部加 `family` 选择器(只影响 fixed);新增 colorScheme/theme/effectEnabled/effectScale/progressBar;fontSize 语义随 family 展示 | | `src/renderer/views/settings/IslandEditorView.vue` | Change | 数据改为 v2;行设置抽屉;预览按 ClassIsland 规格;保存队列/上限保留;拆分节奏按 09 文件计划(见 4.3.11) | | `src/renderer/components/IslandComponentSettings.vue` | Change | 组件字段与 ClassIsland 规格对齐(含滚动 mode);旧字段保留只读兼容 | | `src/renderer/components/IslandEditorNode.vue` | Keep | 标签节点可复用;若加行级拖拽则小幅 Change | | `src/renderer/components/IslandRowSettingsDrawer.vue` | Add | 行级设置的唯一入口(字段清单由 09 定义) | | `src/renderer/island/bridge.ts` | Change | 作业投递增加 `source:'homework-local'|'realtime'|'replay'` 与 `dedupeKey`;保留本机指纹快速路径(见 4.3.13) | | `src/renderer/views/HomeworkView.vue` | Keep | `markHomeworkCreatedLocally` 调用点保留(录入前/后登记指纹,见 4.3.13) | | `src/renderer/styles/island-editor.css` | Change | 编辑器视觉按 ClassIsland(间距/分隔/虚线框/预览画布) | | `packages/desktop-client/scripts/smoke.mjs` | Change | 视觉/几何断言改为按 family 分套;默认 quick 仍无头 | | `docs/app/island.md`、`docs/dev/implementation.md` | Change | 口径更新为 family 与 fixed 新语义(文档不是代码,但必须与实现同步) | ### 4.3 关键文件改造详情(逐个文件:现状 → 目标 → 接口 → 风险) #### 4.3.1 `src/island/LayoutIslandApp.vue`(Change,按 layout + family 分发) - **现状**:只按 `layout==='custom'` 二选一(`CustomIslandRuntime` / `FixedIsland`),课件优先覆盖。依据:`packages/desktop-client/src/island/LayoutIslandApp.vue:56-75`。 - **目标**(定稿分发规则): - `layout==='custom'` → **始终** `custom/CustomIslandRuntime.vue`(ClassIsland 行/组件规格),**不看 family**; - `layout==='fixed'` 且 `appearance.family==='classisland'` → 新增 `family/classisland/FixedInfoRow.vue`(信息行 + 行内气泡); - `layout==='fixed'` 且 `appearance.family==='winisland'` → `family/winisland/ClassicIsland.vue`(原胶囊+详情卡,行为不变); - 课件传输仍最高优先级,覆盖以上所有分支。 - **接口**:props 保持 `settings/state/schedules/timePoints/appearance/nativeVisible`;新增计算 `family = appearance.family ?? 'classisland'` 仅用于 fixed 分支;不新增 IPC 通道。 - **为什么**:family 只影响 fixed,custom 不需要两套渲染;分发规则收窄后,custom 的数据与视觉可以独立演进。 - **风险**:切换 family 只改变 fixed 渲染器,custom 不受影响——必须保证切换时若当前 layout=custom 不重建 custom 运行时(避免方案展开态/测量状态丢失);切换命中矩形时 `reportHit(null)` 并 `syncTargets(true)`。 #### 4.3.2 `src/island/IslandApp.vue` → `family/winisland/ClassicIsland.vue`(Change,大改) - **现状**:683 行,胶囊层 + 展开层 + 课程提醒 + 命中上报 + 弹簧形变全在一个文件;窗口固定包围盒内的岛由 `morph` 驱动。依据:`src/island/IslandApp.vue:44-570`。 - **目标**: 1. 保留并迁移全部 winisland 行为(胶囊/展开/紧急/叫人/多条列表/课程提醒/命中),**视觉与交互不变**(老用户回退路径)。 2. 把"壳"(squircle 路径、弹簧、`--card-path`、命中上报)抽到 `shell/IslandShell.vue`,把消息展示抽到 `shell/MessagePanel.vue`,把课程提醒抽到 `shell/LessonBar.vue`;`ClassicIsland.vue` 只负责"胶囊内容 + 展开内容"的编排。 3. 文件顶部保留兼容导出:`export { default } from './family/winisland/ClassicIsland.vue'`,避免旧 import 断裂。 - **接口**:props 必须兼容现有 `{ embedded?, suspended? }`;继续调用 `window.island.sendAction/setInteractive/setHitRect`;继续使用 `ISLAND_SIZE_DELTA`/`ISLAND_LIST_METRICS`。 - **为什么**:ClassIsland fixed 取代胶囊后,旧实现仍要作为 `winisland` 保留;抽壳后两族共用同一套命中/形变,减少重复实现。 - **风险**:抽壳会改变 DOM 层级,现有 CSS 选择器(`.island-card .content`、`.pill-inner`)与冒烟断言依赖 DOM;必须保留 `.island-card`、`.island-root`、`data-anchor`、`data-style` 等关键钩子。 #### 4.3.3 `family/classisland/FixedInfoRow.vue`(Add,fixed 的新默认) - **目标**:ClassIsland 信息行的固定布局:默认 1 行,行高 40、行内水平内边距 12、行间距 5、圆角 8、背景不透明度 0.5(背景色取主题 token + `appearance.winislandStyle`/surface 材质)、1px 描边、字号由 `appearance.fontSize`(classisland 下=正文基准,默认 16)派生 14/18/20、进度条高度取 `appearance.progressBar.height`(默认 3)、`progressBar.show=false` 时不显示;默认行 = 日期组件 + 课程表组件;消息以**行内气泡**呈现(普通/重要/紧急/叫人/作业),不再弹"详情卡";强提醒特效仅在 `effectEnabled=true` 时叠加,尺寸按 `effectScale`。 - **接口**:props `{ state, appearance, schedules, timePoints, now, editing? }`;内部复用 `components/*Node.vue` 与 `shell/MessagePanel.vue`(行内模式);只读 `appearance.colorScheme` 决定行底色/文字对比。 - **为什么**:ClassIsland 的主界面就是"一行行信息",fixed 只有变成信息行才符合重构目标。 - **风险**:fixed 原有"点胶囊展开卡片"的点击目标消失,必须保证消息可读(行内气泡可点开详情面板);命中矩形上报必须按真实行矩形,不能用旧胶囊尺寸(窗口包围盒由 geometry.ts 计算)。 #### 4.3.4 `family/classisland/InlineMessageBubble.vue`(Add,行内气泡) - **目标**:**同一时刻只显示 1 条 active 气泡**;点击展开详情/操作;有多条未处理消息时折叠为「还有 N 条」,点击打开应用内通知中心(沿用 ClassHelper 现有通知中心路由),不并列展示多条;两阶段表现为先横向展开成条、再展开详情面板(动画由 08/10 定义);普通/重要/紧急/叫人/作业五类样式;紧急消息仍按现有业务规则"立即展开、无视上课"。 - **接口**:props `{ active, pendingCount, mode, listExpanded }`;动作只通过 `window.island.sendAction`,不得直接改状态;数据来源是 `IslandState.active/queued` 与 `IslandNotification.level`。 - **为什么**:ClassIsland 的提醒是"行内/气泡"而非独立卡片;不改变业务队列,只换表现。 - **风险**:行内气泡只改变行内内容测量与截断(正文最多 2 行、超出省略),**不改变窗口包围盒**(fixed 窗口尺寸固定为 min(工作区宽,960) × (40+2×pad));「还有 N 条」跳转必须带未读上下文,不得丢失已读回执。 #### 4.3.5 `custom/CustomIslandRuntime.vue`(Change,大改;始终 ClassIsland 规格) - **现状**:三页(compact/details/lesson)交叉淡入 + 自然内容测量 + 铃铛 + 命中上报;行/组件来自 `scheme.rows`。依据:`src/island/CustomIslandRuntime.vue:52-336,338-527`。 - **目标**:保留"测量 + 三页"骨架,**统一按 ClassIsland 行规格**重写(**不区分 family**):单行基准高 40、行间距 5、行内水平留白 12、行背景/圆角/不透明度/字号覆盖、行级隐藏规则、行级进度条(3px,含义见 05 §5.3);行设置抽屉入口(字段由 09 定义);`rows`/`expandedRows` 的编辑语义不变;滚动组件支持 `settings.mode: 'continuous'|'pause'`(pause 时到边界停顿 `settings.interval`)。 - **接口**:props 不变;新增读取 `row.appearance/row.hideRules/row.progress`;`window.island.reportResidentControl` 继续上报铃铛矩形;`window.island.reportSize` 继续上报适配后尺寸;CSS 使用 `classisland` 命名空间(custom 不受 family 影响)。 - **为什么**:ClassIsland 组件库/行模型与本应用 schemes 高度相似,且 family 最终只作用于 fixed;custom 统一到 ClassIsland 规格可避免"两套自定义外观"。 - **风险**:老 custom 用户升级后视觉会变化(自定义布局统一到 ClassIsland 规格),必须在发布说明与设置页提示;`ResizeObserver` + `requestAnimationFrame` 测量链对行数敏感,改写后的几何只由 metrics 计算,不用动画中的壳反推(现状注释见 `:73-79`)。 #### 4.3.6 `src/main/island.ts` → `main/island/*`(Change + Add,大重构) - **现状**:2445 行单文件(分区见 02 §2.2)。 - **目标**: - `index.ts`:门面导出 `island`、`registerIslandIpc`、`recomputeSizes`、`normalizeAppearance`、`getSliverSize`、`ISLAND_SIZES` 等旧导出名,**保证 `main/index.ts`、`tray.ts`、`courseware.ts`、`smoke.ts` 的 import 不改**。 - `controller.ts`:状态与编排;持有 `IslandState`;消息/上课/预览/动作分发。 - `window.ts`:建窗、固定包围盒/触摸贴合、锚点、可见性/焦点/透明度、`syncWindow` 拆分后的"可见性决策"。 - `hit-test.ts`:60ms 轮询、`setInteractive`、`setHitRect`、`expectedHitRect`、`setShape` 降级(非 Windows 见 4.6)。 - `messages.ts`:`pushNotification` 分流、合并、排序、超时计时器、`dismissActive`、`clearPendingForClose`、`handleAction`。 - `lessons.ts`:`LessonReminderTracker` 每秒推进与 `lessonShowing` 判定。 - `ipc.ts`:`registerIslandIpc()` 全部 handler(含发送方校验与外观合并节流 16ms)。 - **接口**:必须保留全部旧公开方法与冒烟读取器(02 §2.5 第 9 条清单);新增 `getMetrics()`、`getLayoutVersion()` 供编辑器/冒烟断言。 - **为什么**:不拆文件无法安全替换 fixed 渲染、无法让 engine 复用 shared 纯函数,也无法让 ChatGPT 在单文件里做可靠改动。 - **风险**:拆分过程中最容易出现"私有字段跨模块互相引用";必须先把 `IslandState`、`WindowBox`、`HitRect`、`Metrics` 定义到 `types`,模块间只经接口通信;拆完立即用现有 `verify:desktop`(无头)回归。 #### 4.3.7 `src/island/ComponentNode.vue` → `components/*Node.vue`(Change) - **现状**:456 行,14 类 kind 的条件渲染 + 课程表折叠算法 + 编辑手柄 + 行内样式。依据:`src/island/ComponentNode.vue:29-210,212-293`。 - **目标**:按 kind 拆文件,`components/index.ts` 提供 `resolveNodeRenderer(kind)`;每个组件只负责自己的内容与样式;课程表的"折叠/展开/隐藏/进度/徽标"逻辑放进 `ScheduleNode.vue` 并复用 02 §2.8/§2.9 规则。 - **接口**:保持 `IslandComponentNode` 数据结构不变(字段名/默认值见 `packages/shared/src/island-layout.ts:77-108`);编辑态仍用 `node-handle` 拖拽句柄。 - **为什么**:ClassIsland 组件规格要为每类组件补字段(字号覆盖、进度条、显示模式),单文件条件链会继续膨胀。 - **风险**:`