- 重构版主提示词(14 parts,面向 ClassHelper 灵动岛重构) - ClassIsland 全量规格提示词(20 parts,含分卷与逐章版本) - 投喂指南、分卷、逐节文件 - 调研记录、一致性基线、生成与质检脚本
329 lines
40 KiB
Markdown
329 lines
40 KiB
Markdown
## 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 组件规格要为每类组件补字段(字号覆盖、进度条、显示模式),单文件条件链会继续膨胀。
|
||
- **风险**:`<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`。
|
||
- **目标**:
|
||
1. 顶部先选 **风格族 family**(classisland 默认 / winisland 兼容)——**只影响 fixed**,说明文案必须写清"自定义布局始终使用 ClassIsland 规格";
|
||
2. `fixed` 的说明按 family 变(classisland=fixed 信息行;winisland=fixed 胶囊+详情卡);
|
||
3. 新增外观控件:`colorScheme`(浅色/深色/跟随系统,默认跟随)、`theme`(fluent/classic,仅 classisland)、`effectEnabled`(强提醒特效总开关)、`effectScale`(1/0.75/0.5)、`progressBar.show/height`(默认 3px)、`fontSize`(classisland 显示为"正文字号(16 基准)"、winisland 显示为旧"基础字号(13 基准)");
|
||
4. 调色板 `winislandStyle`(black/glass/tinted)仅在 winisland 下显示;切换 family 时**保留用户原值**,不重置;
|
||
5. "测试灵动岛"必须按当前 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`(录入前/后登记)。
|
||
- **目标(双层去重,职责明确)**:
|
||
1. **渲染层(快速路径)**:保留现有 `isLocalHomework()` 判定,命中则根本不发 `islandPush`;`markHomeworkCreatedLocally()` 调用点保留,不改交互时序。
|
||
2. **主进程(兜底去重)**:`island:push` payload 增加 `source: 'realtime'|'homework-local'|'replay'|'call'` 与 `dedupeKey`(作业用同一指纹;普通通知/叫人用 `source+id`);`main/island/ipc.ts` 校验字段、`main/island/messages.ts` 维护 LRU 去重表(key→过期时间,TTL 60s),命中直接丢弃并记 debug 日志。
|
||
3. **IPC 归属**:渲染层 `bridge.ts` 计算并传 `source/dedupeKey` → preload `islandPush` 原样透传 → 主进程 `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-fixed'` |
|
||
| `classisland:island:get-metrics` | 新 | — | 返回当前 layout/family 的 metrics(编辑/冒烟/调试用) |
|
||
| `classisland:island:editor-open` | 主→应用窗口 | 已有 `island:open-editor` | 保留旧通道;新通道可带 `{rowId,nodeId}` 定位 |
|
||
| `island:set-hit-rect`/`island:set-interactive` | 岛→主 | 已有 | 语义不变;fixed 信息行按真实行矩形上报 |
|
||
|
||
1. payload 可携带 `schemaVersion:2` **仅用于运行时校验/调试**,**不得落盘**;落盘版本只有 `islandLayout.version`。接收方遇到未知字段必须忽略而不是拒绝。
|
||
2. 通道名继续集中在 `src/main/channels.ts` 或 `shared/island/ipc.ts` 的**单一常量表**中;preload 不得直接写字符串字面量。
|
||
3. 旧通道(`island:get-layout` 等)不得改名/删除;改名只能通过"新通道 + 旧通道转发"完成,至少保留一个版本周期。
|
||
4. 所有冒烟注入/读取通道(`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 迁移与门面的落地顺序(必须按序提交)
|
||
|
||
1. **M0 建契约层**:`shared/island/` 类型、默认值、normalize、migrate、metrics、geometry、messages、lessons、ipc;只加不删,旧 `island-layout.ts`/`constants.ts` 保持再导出。
|
||
2. **M1 拆主进程**:`main/island/*` 拆分 + 门面;行为与现状 1:1(不改变任何可见行为),用无头 `verify:desktop` 回归。
|
||
3. **M2 迁移层接入**:`config.ts` 读时迁移 + 版本号;用旧配置夹具验证(fixed/custom 各存一份)。
|
||
4. **M3 渲染层抽壳**:`shell/IslandShell.vue`、`MessagePanel.vue`、`LessonBar.vue`;winisland 外观不变。
|
||
5. **M4 新增 classisland fixed**:`FixedInfoRow.vue` + `InlineMessageBubble.vue` + `IslandSettingsView` family 选择器;默认新装走 classisland,老配置走 winisland;family 只影响 fixed。
|
||
6. **M5 自定义行改造**:`CustomIslandRuntime` 行级外观/隐藏规则/进度条/滚动 mode/行设置抽屉;custom 始终 ClassIsland 规格(老 custom 用户升级后有视觉变化,需在发布说明提示)。
|
||
7. **M6 编辑器改造**:v2 数据、行设置、预览、保存队列。
|
||
8. **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 部分)
|
||
|
||
1. 仓库中存在 `packages/shared/src/island/` 与 `packages/desktop-client/src/main/island/` 两个新目录,且 `src/main/island.ts` 只剩门面(≤50 行,内容为 import/export 与类型再导出)。
|
||
2. `src/main/index.ts`、`tray.ts`、`courseware.ts`、`smoke.ts` 对 `./island.js` 的 import 与调用**不需要改动**(门面签名兼容)。
|
||
3. `LayoutIslandApp.vue` 分发正确:`custom` 始终 `CustomIslandRuntime`(与 family 无关);`fixed` 在 classisland 下用 `FixedInfoRow`、winisland 下用 `ClassicIsland`;切换不新建第二个窗口、不残留旧命中矩形。
|
||
4. `fixed` 在 `family='classisland'` 下渲染为信息行(默认 1 行:日期 + 课程表;行高 40、圆角 8、背景不透明度 0.5、`progressBar.height` 默认 3px、消息行内气泡);在 `family='winisland'` 下保留原胶囊+详情卡行为与既有冒烟断言。
|
||
5. `CustomIslandRuntime` 支持行级外观/隐藏规则/进度条,行设置抽屉可打开并保存;滚动组件支持 `mode:'continuous'|'pause'`;`rows`/`expandedRows` 编辑语义与现状一致。
|
||
6. 外观新字段全部落地:`family`(只影响 fixed)、`colorScheme`、`theme`、`effectEnabled`、`effectScale`、`progressBar`、`winislandStyle`;`fontSize` 在 classisland 下为正文基准(新装 16)、winisland 下旧语义(13,迁移保留原值)。
|
||
7. 编辑器 / 设置页 / 组件设置全部读写 v2 数据;旧 v1 配置在首次启动被迁移且不丢字段(迁移细节按 05 验收)。
|
||
8. 所有新增/修改的 IPC 通道登记在单一常量表;旧通道全部保留;preload 未暴露 `ipcRenderer`;`island:push` 带 `source/dedupeKey` 且主进程有兜底去重表。
|
||
9. 默认 `pnpm verify:desktop`(无头 quick)通过;不得因为重构把任何断言改成"跳过";冒烟注入点名称/语义全部保留。
|
||
10. 代码中不存在 `setBackgroundMaterial`;不存在绕过 contextBridge 直接访问 Node 的渲染层代码;custom 不受 family 影响、不引用 winisland 样式命名空间。
|
||
11. 作业指纹链路双层生效:本机录入后 60 秒内,实时事件/接口响应先后到达都不产生岛消息;跨班/跨天不误伤;去重表有 TTL 上限且可被冒烟清理。
|
||
12. 文件级改造清单(4.2/4.3)逐项落地;`docs/app/island.md`、`docs/dev/implementation.md` 的 family/fixed/custom 描述与实现一致。
|