- 重构版主提示词(14 parts,面向 ClassHelper 灵动岛重构) - ClassIsland 全量规格提示词(20 parts,含分卷与逐章版本) - 投喂指南、分卷、逐节文件 - 调研记录、一致性基线、生成与质检脚本
40 KiB
40 KiB
4. 目标架构与文件级改造清单
4.0 重构原则(必须逐条遵守)
- 只在现有仓库内重构:不得新建工程、不得换框架、不得引入服务端依赖;沿用 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。
- 渐进式替换,不一次性推倒:每次改动后应用必须仍可启动、旧
config.json仍可加载;用"门面(facade)+ 新模块"的方式把旧调用点隔离,避免一次改src/main/index.ts、tray.ts、smoke.ts的全部 import。 - 单一事实来源:任何"主进程与渲染进程各算一次"的数值(窗口包围盒、列表高度、命中矩形、细缝尺寸、字号推导)必须收口到
packages/shared/src/island/,两端只调用同一函数。现状三处漂移见 02 §2.3。 - 保留现有业务语义:消息去重、上课暂存、紧急展开、已读回执、作业免打扰指纹、课件覆盖优先级、离线补弹,全部保持(02 §2.5/§2.12);ClassIsland 只改变"视觉与布局表现"。
- 两套风格族(family)共存,且 family 只作用于 fixed:
appearance.family取值'classisland' | 'winisland';默认classisland;winisland保留原胶囊+详情卡外观;老用户升级默认winisland(迁移见 05)。custom布局始终按 ClassIsland 规格渲染,不受 family 影响(family 只决定 fixed 用哪套渲染器)。 fixed重新定义:在family='classisland'下,fixed不再是胶囊+详情卡,而是 ClassIsland 风格信息行(默认 1 行 = 日期 + 课程表,行高 40、圆角 8、背景不透明度 0.5、3px 进度条、消息以行内气泡呈现)。原"胶囊+详情卡"只在family='winisland'下保留。这是本轮最重要的语义变化,所有文件改造必须按此分叉。- 一个原生窗口:多种布局/风格共用一个
BrowserWindow;切换 family/layout 不得新开窗口(现状约束,见docs/app/island.md:8-10、src/main/island.ts:468-634)。 - 不打扰纪律:默认验证无头/离屏,不创建可见岛或托盘;只有显式
--visual才做桌面像素验收。依据:docs/dev/testing.md:9,29-32。 - 调色板与风格分离:
style: black|glass|tinted继续保留,写为 winisland 调色板字段(canonicalappearance.winislandStyle,旧style作为兼容镜像,不删除);family 与 style 独立,切换 family 时保留用户 style 原值。 - 外观新增定稿字段:
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,迁移保留原值)。 - 版本只保留一个:落盘版本只有
islandLayout.version=2;schemaVersion仅允许出现在 IPC payload 作运行时校验,不得落盘。 - fixed 消息/提醒口径:行内气泡同一时刻只显示 1 条 active;多条折叠为「还有 N 条」并可打开应用内通知中心;
prepare/on-class/break以行内容接管呈现、ending只用蓝色圆形秒数徽标;队列/去重/暂存规则不变。
4.1 目标模块边界与目录
必须把灵动岛拆成四个边界清晰的部分。目录与职责如下(标注「新增」的目录/文件由本轮创建;其余为移动或改造):
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' |
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' |
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。 - 目标:
- 保留并迁移全部 winisland 行为(胶囊/展开/紧急/叫人/多条列表/课程提醒/命中),视觉与交互不变(老用户回退路径)。
- 把"壳"(squircle 路径、弹簧、
--card-path、命中上报)抽到shell/IslandShell.vue,把消息展示抽到shell/MessagePanel.vue,把课程提醒抽到shell/LessonBar.vue;ClassicIsland.vue只负责"胶囊内容 + 展开内容"的编排。 - 文件顶部保留兼容导出:
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 组件规格要为每类组件补字段(字号覆盖、进度条、显示模式),单文件条件链会继续膨胀。
- 风险:
<style scoped>拆分后类名作用域变化;必须把跨组件公共样式放styles/components.css,避免样式丢失。
4.3.8 src/island/ClassicNotifications.vue → shell/MessagePanel.vue(Change)
- 现状:单条详情卡 + 多条列表卡 + 按钮行;只用于展开态。依据:
src/island/ClassicNotifications.vue:104-233。 - 目标:同一组件支持三种形态:
detail(winisland 详情卡)、list(winisland 多条列表)、inline(classisland fixed 行内气泡);按钮语义(打开应用/标为已读/知道了/收到)与批量语义不变;canMarkRead排除作业的规则保留。 - 接口:props 增加
variant: 'detail'|'list'|'inline';保持state/appearance/maxHeight。 - 为什么:fixed 默认不再有详情卡,但消息仍需可操作;共用一套面板避免三处重复实现按钮/已读逻辑。
- 风险:
inline形态的高度必须计入行高与窗口包围盒(由shared/island/messages.ts提供),不得在渲染层单独硬编码。
4.3.9 src/island/LessonReminderContent.vue → shell/LessonBar.vue(Change)
- 现状:5 区斜切遮罩 + 进度条 + 秒数徽标;遮罩区段 360ms/错峰 90-120ms、开
1-(1-p)^4、关p²;文字 260ms、scale 1.1→1.0、750ms。依据:src/island/LessonReminderContent.vue:59-123,133-137。 - 目标:按 ClassIsland 规格校准:遮罩斜切权重
[0,0.1,0.2,0.4,0.2,0.1](与 03/14 部分一致)、区段 360ms、错峰 120ms、开 QuarticEaseOut、关 QuadraticEaseIn、文字 delay 260ms;行高 40、圆角 8。叠加口径(定稿):fixed(classisland)下prepare/on-class/break以行内容接管呈现(临时替换课程表内容,动画由 08/10 定义);ending不接管行内容,只在课程名右侧显示蓝色圆形秒数徽标;custom 不接管(保持课程组件 + 徽标)。 - 接口:props 保持
{ reminder, now, appearance, motion };新增variant: 'winisland'|'classisland'。 - 为什么:ClassIsland 的提醒遮罩是"整行被斜切色块覆盖 + 文字弹入",与现有实现同源但参数不同;必须抽成共用组件。
- 风险:
ref宽度依赖ResizeObserver,在 fixed 行内宽度变化频繁;必须避免测量死循环(只在行宽变化时重算多边形)。
4.3.10 src/renderer/views/settings/IslandSettingsView.vue(Change)
- 现状:布局二选一、外观滑块、测试预览。依据:
src/renderer/views/settings/IslandSettingsView.vue:26-160,162-392。 - 目标:
- 顶部先选 风格族 family(classisland 默认 / winisland 兼容)——只影响 fixed,说明文案必须写清"自定义布局始终使用 ClassIsland 规格";
fixed的说明按 family 变(classisland=fixed 信息行;winisland=fixed 胶囊+详情卡);- 新增外观控件:
colorScheme(浅色/深色/跟随系统,默认跟随)、theme(fluent/classic,仅 classisland)、effectEnabled(强提醒特效总开关)、effectScale(1/0.75/0.5)、progressBar.show/height(默认 3px)、fontSize(classisland 显示为"正文字号(16 基准)"、winisland 显示为旧"基础字号(13 基准)"); - 调色板
winislandStyle(black/glass/tinted)仅在 winisland 下显示;切换 family 时保留用户原值,不重置; - "测试灵动岛"必须按当前 family + layout 推预览。
- 接口:
window.desktop.saveConfig({ island: {...} })、islandSetAppearance不变;新增字段随island下发(纯数据,禁止传 Proxy)。 - 为什么:family 是重构后最上层的开关,必须最先可见;新增外观字段需要用户可发现、可关闭。
- 风险:切换 family 会立即改变 fixed 外观;必须先弹确认"切换后仅 fixed 表现改变,自定义方案与 style 不变",且不得自动改 layout。
4.3.11 src/renderer/views/settings/IslandEditorView.vue(Change)
- 现状:组件标签 + 内嵌预览 + 方案菜单 + 组件库 + 检查器 + 指针拖拽 + 保存队列。依据:
IslandEditorView.vue:25-130,130-540,541+。 - 目标:数据切到 v2;增加"行"选择与行设置抽屉(
IslandRowSettingsDrawer.vue,字段清单由 09 定义);预览统一按 ClassIsland 规格渲染(custom 编辑也用CustomRows);保留上限(200 组件/20 方案)、保存队列、离开前保存、JSON 导入导出。 - 接口:仍通过
window.desktop.getConfig/saveConfig;normalizeIslandLayouts改为normalizeIslandLayoutV2;getIslandSchedules/getIslandTimePoints不动。 - 为什么:ClassIsland 编辑器的核心是"行/组件/行设置抽屉/组件库拖拽",现有编辑器已有 80% 骨架。
- 风险:编辑器是 855 行大文件;拆分节奏按 09 的文件计划执行,不强制本轮先抽 composables;若单文件继续膨胀,再按 4.1 的
renderer/island/editor/抽取,抽取时不得改变保存队列/修订号语义。
4.3.12 src/main/config.ts(Change,迁移接入点)
- 现状:
PersistedConfig含islandLayout/island,逐字段归一化,120ms 合并写。依据:src/main/config.ts:26-84,198-233,334-346。 - 目标:不新增文件级版本号;读取
islandLayout时若version缺失 → 调用migrateIslandLayoutV1ToV2(含 family/外观默认值,见 05),迁移结果写回并备份;saveConfig收到islandLayout时先normalizeIslandLayoutV2;island增加family/colorScheme/theme/effectEnabled/effectScale/progressBar/winislandStyle归一化。 - 接口:导出
getConfig/saveConfig/flushConfigSync签名不变;新增getIslandLayoutV2()供主进程使用。 - 为什么:迁移必须在任何消费者(岛窗口、编辑器、冒烟)之前完成,且只能发生一次;版本字段只保留
islandLayout.version可避免两套版本号分叉。 - 风险:迁移写盘与"用户正在编辑"竞争;必须保证迁移只在
app.whenReady()后、岛窗口创建前执行一次,失败时保留原文件并让应用以默认 v2(只读)启动。
4.3.13 作业指纹链路(本机刚录作业不上岛)
- 现状:渲染层
src/renderer/island/bridge.ts用"内容指纹 + id"避免本机录入的作业弹上岛——homeworkFingerprint()由classId|assignDate|title|content拼成,markHomeworkCreatedLocally()在发请求前登记指纹、回来后补记 id,isLocalHomework()在pushHomeworkToIsland()前判定;指纹 TTL 60 秒。依据:bridge.ts:191-267;调用点在src/renderer/views/HomeworkView.vue:419-428(录入前/后登记)。 - 目标(双层去重,职责明确):
- 渲染层(快速路径):保留现有
isLocalHomework()判定,命中则根本不发islandPush;markHomeworkCreatedLocally()调用点保留,不改交互时序。 - 主进程(兜底去重):
island:pushpayload 增加source: 'realtime'|'homework-local'|'replay'|'call'与dedupeKey(作业用同一指纹;普通通知/叫人用source+id);main/island/ipc.ts校验字段、main/island/messages.ts维护 LRU 去重表(key→过期时间,TTL 60s),命中直接丢弃并记 debug 日志。 - IPC 归属:渲染层
bridge.ts计算并传source/dedupeKey→ preloadislandPush原样透传 → 主进程main/island/ipc.ts校验 →messages.ts去重后入队。不得在 preload 里做业务判断,也不得让主进程反查渲染层 store。
- 渲染层(快速路径):保留现有
- 接口:
IslandPushPayload(packages/shared/src/island/ipc.ts)新增两个可选字段;旧调用点缺字段时主进程按id去重(兼容)。 - 为什么:现状只有渲染层一层判定,实时事件先于接口响应到达时仍可能漏判;主进程兜底可保证"同一台机器录的作业绝不上岛",且离线补弹/重连重复投递也被覆盖。
- 风险:指纹跨班/跨天误伤(必须带 classId+assignDate);主进程去重表不能无限增长(LRU+TTL);冒烟需要能注入/清理去重表(提供
testResetHomeworkDedupe()或在冒烟模式禁用去重,名称归 part 11 测试基线)。
4.4 IPC 契约 v2(增量、不改旧通道语义)
| 通道 | 方向 | 现状 | v2 变更 |
|---|---|---|---|
island:get-layout |
岛→主 | 返回 islandLayout |
返回 v2(version:2;含 fixed/schemes) |
island:save-layout |
岛→主 | 归一化后保存 | 先 normalizeIslandLayoutV2,返回 v2 |
island:state |
主→岛 | IslandState |
增加 family/pendingStaged/bubblePhase/bubbleDeadline(运行期,不落盘);旧字段不删 |
island:appearance |
主→岛 | IslandAppearance |
增加 family/colorScheme/theme/effectEnabled/effectScale/progressBar/winislandStyle;旧字段保留 |
island:push |
应用窗口→主 | {notification, context} |
增加 source/dedupeKey(作业指纹去重,见 4.3.13);缺省按 id 去重 |
island:action |
岛→主 | 8 种动作 | 保留;新增 open-message(classisland fixed 行内气泡打开详情)与 open-notifications(「还有 N 条」打开应用内通知中心) |
island:content-size |
岛→主 | 自定义尺寸上报 | 增加 `mode:'custom' |
classisland:island:get-metrics |
新 | — | 返回当前 layout/family 的 metrics(编辑/冒烟/调试用) |
classisland:island:editor-open |
主→应用窗口 | 已有 island:open-editor |
保留旧通道;新通道可带 {rowId,nodeId} 定位 |
island:set-hit-rect/island:set-interactive |
岛→主 | 已有 | 语义不变;fixed 信息行按真实行矩形上报 |
- payload 可携带
schemaVersion:2仅用于运行时校验/调试,不得落盘;落盘版本只有islandLayout.version。接收方遇到未知字段必须忽略而不是拒绝。 - 通道名继续集中在
src/main/channels.ts或shared/island/ipc.ts的单一常量表中;preload 不得直接写字符串字面量。 - 旧通道(
island:get-layout等)不得改名/删除;改名只能通过"新通道 + 旧通道转发"完成,至少保留一个版本周期。 - 所有冒烟注入/读取通道(
island:set-interactive、island:set-hit-rect、island:set-touch-mode、island:alive及主进程方法setTestInputPassthrough/setHitTestCursor/getWindowBounds/getHitRect/getInteractive/setBlurCollapseEnabled/setTouchMode/getTouchMode/isRecovering)名称与语义保持不变,改造归属 part 11 测试基线。
4.5 迁移与门面的落地顺序(必须按序提交)
- M0 建契约层:
shared/island/类型、默认值、normalize、migrate、metrics、geometry、messages、lessons、ipc;只加不删,旧island-layout.ts/constants.ts保持再导出。 - M1 拆主进程:
main/island/*拆分 + 门面;行为与现状 1:1(不改变任何可见行为),用无头verify:desktop回归。 - M2 迁移层接入:
config.ts读时迁移 + 版本号;用旧配置夹具验证(fixed/custom 各存一份)。 - M3 渲染层抽壳:
shell/IslandShell.vue、MessagePanel.vue、LessonBar.vue;winisland 外观不变。 - M4 新增 classisland fixed:
FixedInfoRow.vue+InlineMessageBubble.vue+IslandSettingsViewfamily 选择器;默认新装走 classisland,老配置走 winisland;family 只影响 fixed。 - M5 自定义行改造:
CustomIslandRuntime行级外观/隐藏规则/进度条/滚动 mode/行设置抽屉;custom 始终 ClassIsland 规格(老 custom 用户升级后有视觉变化,需在发布说明提示)。 - M6 编辑器改造:v2 数据、行设置、预览、保存队列。
- M7 收尾:CSS/文档/冒烟断言/迁移回滚演练。
每一步都必须"可独立回滚":新增文件先经 feature 开关(appearance.family/config.islandLayout.version)控制;不得在某一步同时删除旧文件与旧逻辑。
4.6 风险清单与强制缓解
| 风险 | 触发点 | 缓解(必须实现) |
|---|---|---|
拆分 island.ts 导致隐藏状态丢失 |
M1 | 先对齐私有字段到接口;拆分后逐项对比 getState()/getWindowBounds()/getHitRect() 冒烟输出 |
| DOM/CSS 选择器断裂 | M3/M4 | 保留 .island-card/.island-root/[data-anchor]/[data-style] 钩子;视觉回归由 --visual 专项做 |
| 两套 family 逻辑分叉 | M4 | 业务状态机只保留一份(controller);family 只选择 fixed 渲染器,custom 不分叉 |
| fixed 语义变化引起老用户困惑 | M4 | 老配置迁移默认 winisland;设置页显著位置显示 family 并解释两种 fixed;custom 升级有视觉变化的提示 |
| custom 用户升级后观感变化 | M5 | 统一 ClassIsland 规格是定稿;发布说明+设置页提示;数据不变、可回滚 config 备份 |
| 迁移把用户自定义方案写坏 | M2 | 迁移前备份 config.json;迁移失败保留原文件;提供"恢复旧版布局" |
| 编辑器 855 行继续膨胀 | M6 | 先抽 composables;单文件不超过 500 行;新逻辑进 renderer/island/editor/ |
| 非 Windows 触摸命中退化 | M1 | setShape 仅 win32 分支;其它平台走"整窗穿透 + 可见区命中"(02 §2.4 第 5 条) |
| 性能(行数/组件数增加) | M5 | metrics 计算缓存;测量只在依赖变化时执行;列表/行使用 will-change 谨慎 |
| 无头验证覆盖不到视觉 | M7 | 默认 quick 保持无头;视觉断言只在显式 --visual 下跑,并在文档写明 |
4.7 验收标准(第 4 部分)
- 仓库中存在
packages/shared/src/island/与packages/desktop-client/src/main/island/两个新目录,且src/main/island.ts只剩门面(≤50 行,内容为 import/export 与类型再导出)。 src/main/index.ts、tray.ts、courseware.ts、smoke.ts对./island.js的 import 与调用不需要改动(门面签名兼容)。LayoutIslandApp.vue分发正确:custom始终CustomIslandRuntime(与 family 无关);fixed在 classisland 下用FixedInfoRow、winisland 下用ClassicIsland;切换不新建第二个窗口、不残留旧命中矩形。fixed在family='classisland'下渲染为信息行(默认 1 行:日期 + 课程表;行高 40、圆角 8、背景不透明度 0.5、progressBar.height默认 3px、消息行内气泡);在family='winisland'下保留原胶囊+详情卡行为与既有冒烟断言。CustomIslandRuntime支持行级外观/隐藏规则/进度条,行设置抽屉可打开并保存;滚动组件支持mode:'continuous'|'pause';rows/expandedRows编辑语义与现状一致。- 外观新字段全部落地:
family(只影响 fixed)、colorScheme、theme、effectEnabled、effectScale、progressBar、winislandStyle;fontSize在 classisland 下为正文基准(新装 16)、winisland 下旧语义(13,迁移保留原值)。 - 编辑器 / 设置页 / 组件设置全部读写 v2 数据;旧 v1 配置在首次启动被迁移且不丢字段(迁移细节按 05 验收)。
- 所有新增/修改的 IPC 通道登记在单一常量表;旧通道全部保留;preload 未暴露
ipcRenderer;island:push带source/dedupeKey且主进程有兜底去重表。 - 默认
pnpm verify:desktop(无头 quick)通过;不得因为重构把任何断言改成"跳过";冒烟注入点名称/语义全部保留。 - 代码中不存在
setBackgroundMaterial;不存在绕过 contextBridge 直接访问 Node 的渲染层代码;custom 不受 family 影响、不引用 winisland 样式命名空间。 - 作业指纹链路双层生效:本机录入后 60 秒内,实时事件/接口响应先后到达都不产生岛消息;跨班/跨天不误伤;去重表有 TTL 上限且可被冒烟清理。
- 文件级改造清单(4.2/4.3)逐项落地;
docs/app/island.md、docs/dev/implementation.md的 family/fixed/custom 描述与实现一致。