387 lines
41 KiB
Markdown
387 lines
41 KiB
Markdown
## 5. 数据契约、schema v2 与配置迁移
|
||||
|
|
|
|||
|
|
### 5.0 总则(必须遵守)
|
|||
|
|
|
|||
|
|
1. 本章定义重构后的**唯一数据契约**。所有读写岛配置的代码(主进程 `config.ts`、岛渲染进程、编辑器、设置页、冒烟)必须只经过 `packages/shared/src/island/schema.ts` 的类型与 `normalize.ts`/`migrate.ts` 的函数,不得各自解析 JSON。
|
|||
|
|
2. 旧配置必须**无损升级**:`config.json` 现有的 `islandLayout`(fixed/custom、schemes、nodes、expandedRows、residentEnabled、pointerFade、lessonReminders)与 `island`(appearance 全字段)一个都不许丢;升级失败必须可回滚。
|
|||
|
|
3. 新增 `appearance.family`:取值 `'classisland' | 'winisland'`。**新装默认 `classisland`;已存在配置的老用户升级默认 `winisland`**。`fixed` 的语义随 family 分叉:`classisland` 下 fixed = ClassIsland 信息行(默认 1 行 = 日期 + 课程表,行高 40、圆角 8、不透明度 0.5、3px 进度条、消息行内气泡);`winisland` 下 fixed = 原胶囊 + 详情卡。
|
|||
|
|
4. `islandLayout.version` 必须写入;当前版本 `2`。版本号是**唯一迁移判据**,不得用"字段是否存在"猜版本(字段判断只作为迁移内部的兜底)。
|
|||
|
|
5. 写入必须沿用现有 `config.json` 的"内存缓存 + 120ms 合并写 + 退出前 flush"语义(`packages/desktop-client/src/main/config.ts:235-346`),**不得**为迁移新增同步写盘路径;迁移本身例外(必须一次写成功并立即 flush)。
|
|||
|
|
6. 客户端不得把 v2 数据写回给旧版本可见的字段后又不写版本号;写 v2 必须同时写 `version:2`,保证降级/回滚可判断。
|
|||
|
|
|
|||
|
|
### 5.1 落点与版本治理
|
|||
|
|
|
|||
|
|
| 数据 | 位置 | 版本字段 | 说明 |
|
|||
|
|
|---|---|---|---|
|
|||
|
|
| 运行期配置 | `<userData>/config.json` | 无独立文件版本 | 版本只在 `islandLayout.version` 上表达(唯一版本字段) |
|
|||
|
|
| 岛布局 | `config.json.islandLayout` | `version`(新增,当前 2) | 方案/行/节点/外观的结构版本;唯一迁移判据 |
|
|||
|
|
| 岛外观 | `config.json.island` | 无独立版本 | 增加 `family`、`theme`、`effectEnabled`、`effectScale`、`progressBar`、`fontSizes`;`style` 保留为 surface 层 |
|
|||
|
|
| 迁移前备份 | `<userData>/config.json.v1.bak` | — | 迁移前完整拷贝,只保留一份(覆盖式) |
|
|||
|
|
| 迁移标记 | `<userData>/config.island-migration.json` | `{from,to,at,appVersion}` | **仅诊断用**,不参与版本判断、不是第二套版本字段 |
|
|||
|
|
|
|||
|
|
1. **只保留 `islandLayout.version` 一个落盘版本字段**:不得新增 `config.schemaVersion` 或其它文件级版本号;迁移判定只看 `islandLayout.version`(缺失=v1)。
|
|||
|
|
2. 迁移必须在 `app.whenReady()` 后、`island.init()` 之前完成一次;不得由渲染进程触发,也不得在岛窗口创建后再迁移(否则窗口会先按旧数据渲染一帧)。
|
|||
|
|
3. 迁移成功后必须立即 `flushConfigSync()`;失败时不得写回半成品:保留原 `config.json`,删除 `.tmp`,应用以内存默认 v2 运行并在设置页显示"迁移失败,已保留原配置"。
|
|||
|
|
4. 读到 `islandLayout.version > 2` 时应用必须**只读**(不写回),并在设置页提示"配置来自更高版本"。
|
|||
|
|
|
|||
|
|
### 5.2 `appearance` v2(`config.json.island`)
|
|||
|
|
|
|||
|
|
在现有 `IslandAppearance` 基础上**只增不改**,旧字段全部保留(`packages/shared/src/types.ts:1145-1195`)。新增/语义变化如下:
|
|||
|
|
|
|||
|
|
| 字段 | 类型 | 默认值(新装) | 说明与依据 |
|
|||
|
|
|---|---|---|---|
|
|||
|
|
| `family` | `'classisland' \| 'winisland'` | `'classisland'`(老用户迁移为 `'winisland'`) | **风格族字段的唯一落点**;**作用域只限 fixed**(classisland=信息行;winisland=旧胶囊+详情);`custom` 始终 ClassIsland 规格、不受影响 |
|
|||
|
|
| `colorScheme` | `'light' \| 'dark' \| 'system'` | `'system'` | 岛配色模式:跟随应用主题(system,默认)/强制浅色/强制深色;只影响岛自身渲染,不改主窗口主题 |
|
|||
|
|
| `theme` | `'fluent' \| 'classic'` | `'fluent'` | 仅 `family='classisland'` 的 fixed 使用;对应 ClassIsland Fluent/Classic 两套主题;具体 token 由 06 定义 |
|
|||
|
|
| `effectEnabled` | boolean | `true` | **强提醒特效总开关**(对应 ClassIsland 的提醒特效 Topmost/Effect 是否启用);关闭后只保留行内气泡/普通提醒 |
|
|||
|
|
| `effectScale` | `1 \| 0.75 \| 0.5` | `1` | 强提醒特效渲染缩放(对应 ClassIsland `NotificationEffectRenderingScale`,默认 1.0);只允许三档 |
|
|||
|
|
| `progressBar` | `{height:number, show:boolean}` | `{height:3, show:true}` | 岛内进度条统一配置:高度默认 3px,总开关默认显示;语义见 5.3 行级进度条说明 |
|
|||
|
|
| `fontSize` | number | classisland=`16`(正文基准,派生 14/18/20 阶梯);winisland=`13`(旧语义) | `family='classisland'` 时它是正文基准字号,`fontSizes` 由它派生;`family='winisland'` 保持旧语义;**迁移时旧配置保留原值,新安装默认 16** |
|
|||
|
|
| `fontSizes` | `{secondary:number, body:number, emphasized:number, large:number}` | 由 `fontSize` 派生:classisland `14/16/18/20`;winisland `≈14/16/18/20`(按 `fontSize/13` 比例) | ClassIsland 四档字号;显式配置优先于派生(见 5.4) |
|
|||
|
|
| `height` / `width` | number | `44` / `268` | 仅 `family='winisland'` 的胶囊尺寸;classisland 下不作为信息行尺寸 |
|
|||
|
|
| `radius` | number | `20`(winisland)/`8`(classisland 行为默认) | classisland 下作为行默认圆角;解析时取 `family` 对应默认 |
|
|||
|
|
| `opacity` | number | `1` | 岛整体不透明度;classisland 下行背景不透明度另见行设置(默认 0.5) |
|
|||
|
|
| `winislandStyle` | `'black' \| 'glass' \| 'tinted'` | `'black'` | **winisland 调色板字段的 canonical 名**:纯黑/半透明/主题色渐变;仅 `family='winisland'` 的 fixed 使用 |
|
|||
|
|
| `style` | `'black' \| 'glass' \| 'tinted'` | 镜像 `winislandStyle` | **兼容镜像字段(不删除)**:旧配置读入时若没有 `winislandStyle` 则用它;写出时同时写 `winislandStyle` 与 `style`,保持旧版本可读;建议 v3 再评估废弃 |
|
|||
|
|
| `position`/`alwaysOnTop`/`animations`/`speed`/`idleSliver`/`marginX`/`marginY`/`followCursorDisplay`/`accent` | 不变 | 不变 | fixed(两种 family)与 custom 共用;classisland fixed 也用停靠与边距 |
|
|||
|
|
|
|||
|
|
1. **字段职责必须严格区分**:`family` 只决定 **fixed** 用哪套渲染器(ClassIsland 信息行 / WinIsland 胶囊);`winislandStyle`(兼容镜像 `style`)只决定 winisland 的调色板;`theme` 只决定 classisland fixed 的 Fluent/Classic;`colorScheme` 只决定浅色/深色/跟随。四者不得互相覆盖、不得合并。`custom` 不使用 `family`/`theme`/`winislandStyle`(始终 ClassIsland 规格),但仍读取 `colorScheme` 决定明暗。
|
|||
|
|
2. `family` 归一化:只接受两个值,非法值回落 `'classisland'`(新装默认);迁移函数负责把老用户写成 `'winisland'`(见 5.4)。
|
|||
|
|
3. `colorScheme` 归一化:只接受 `light/dark/system`,非法回落 `system`;缺失补 `system`。
|
|||
|
|
4. `fontSize` 归一化:8–96;classisland 下作为正文基准,`fontSizes` 缺失时由它派生(body=`fontSize`,secondary=`round(fontSize*14/16)`,emphasized=`round(fontSize*18/16)`,large=`round(fontSize*20/16)`);winisland 下保持旧语义(`fontSizes` 缺失时按 `fontSize/13 × 14/16/18/20` 推导)。迁移时旧 `fontSize` 原值保留,不强行改成 16。
|
|||
|
|
5. `fontSizes` 归一化:每项 8–96;显式值优先于派生;缺失项按第 4 条补齐。
|
|||
|
|
6. `theme` 仅 classisland fixed 生效;非法值回落 `'fluent'`;`winisland` 下忽略但不删除该字段。
|
|||
|
|
7. `effectEnabled` 必须为 boolean(缺省 true);`effectScale` 必须夹取到 `[1, 0.75, 0.5]`(缺省 1,非法值取 1);`progressBar.height` 夹取 1–8、`progressBar.show` 缺省 true;`progressBar` 缺失时整对象补默认。
|
|||
|
|
8. `winislandStyle` 归一化:仅 `black/glass/tinted` 合法,非法回落 `black`;`style` 作为镜像字段同步写;读入优先 `winislandStyle`,回退 `style`。**切换 family 时保留用户 `winislandStyle` 原值,不重置**(UI 默认值提示归 09)。
|
|||
|
|
|
|||
|
|
### 5.3 `islandLayout` v2(`config.json.islandLayout`)
|
|||
|
|
|
|||
|
|
v2 顶层(`IslandLayoutV2`):
|
|||
|
|
|
|||
|
|
| 字段 | 类型 | 默认值 | 说明 |
|
|||
|
|
|---|---|---|---|
|
|||
|
|
| `version` | number | `2` | 必须存在 |
|
|||
|
|
| `layout` | `'fixed' \| 'custom'` | `'fixed'` | 布局模式(沿用旧字段) |
|
|||
|
|
| `fixed` | `FixedSchemeV2` | 见下 | classisland fixed 信息行配置;winisland 下忽略 |
|
|||
|
|
| `schemes` | `IslandLayoutSchemeV2[]` | 1 个默认方案 | 自定义方案(沿用旧结构并扩展) |
|
|||
|
|
| `activeSchemeId` | string | `'default'` | 当前方案 |
|
|||
|
|
| `residentEnabled` | boolean | `layout==='custom'` | 兼容字段;不得再作为独立开关 |
|
|||
|
|
| `pointerFadeEnabled` / `pointerFadeReversed` | boolean | `true` / `false` | 鼠标避让淡化(沿用) |
|
|||
|
|
| `lessonReminders` | `LessonReminderSettings` | 沿用旧默认 | 准备/上课/课间/结束/课中进度开关与秒数 |
|
|||
|
|
| `updatedAt` | number | `0` | 最近保存时间(仅诊断) |
|
|||
|
|
|
|||
|
|
`FixedSchemeV2`(classisland fixed 的信息行):
|
|||
|
|
|
|||
|
|
| 字段 | 类型 | 默认值 | 说明 |
|
|||
|
|
|---|---|---|---|
|
|||
|
|
| `rows` | `IslandRowV2[]` | 1 行 = 日期 + 课程表 | 信息行;默认只有一行 |
|
|||
|
|
| `mainRowId` | string | 首行 id | 主行(承载消息气泡/课程提醒) |
|
|||
|
|
| `rowHeight` | number | `40` | ClassIsland 行高(32–64 夹取) |
|
|||
|
|
| `rowSpacing` | number | `5` | 行间距(0–150) |
|
|||
|
|
| `backgroundOpacity` | number | `0.5` | 行背景不透明度 |
|
|||
|
|
| `cornerRadius` | number | `8` | 行圆角(0–20) |
|
|||
|
|
| `progressBarHeight` | number | `3` | 行内进度条高度(3px) |
|
|||
|
|
| `messageBubble` | `'inline' \| 'card'` | `'inline'` | fixed 下消息表现:inline=行内气泡,card=老式详情卡(仅回退用) |
|
|||
|
|
| `maxRows` | number | `3` | 信息行最多行数(自动换行/溢出策略由 07 定义) |
|
|||
|
|
|
|||
|
|
`IslandLayoutSchemeV2`(在 v1 方案上扩展):
|
|||
|
|
|
|||
|
|
| 字段 | 类型 | 默认值 | 变化 |
|
|||
|
|
|---|---|---|---|
|
|||
|
|
| `id`/`name`/`rows`/`expandedRows` | 沿用 | 沿用 | 结构不变;行内容升级为 `IslandRowV2` |
|
|||
|
|
| `scale` | number | `1` | 0.75–2.5(沿用于 `CLASSISLAND_ISLAND_METRICS.scaleMin/Max`) |
|
|||
|
|
| `color`/`foreground`/`opacity`/`radius`/`separated`/`spacing` | 沿用 | 沿用 | `radius` 0–20;`spacing` 0–150 |
|
|||
|
|
| `rowHeight` | number | `40` | 新增:行高 32–64 |
|
|||
|
|
| `rowSpacing` | number | `5` | 新增:与 `spacing` 同步;`spacing` 保留兼容 |
|
|||
|
|
| `theme` | `'fluent' \| 'classic'` | 继承 `appearance.theme` | 新增:方案级主题覆盖 |
|
|||
|
|
| `fontSizes` | `{secondary,body,emphasized,large}` | 继承 `appearance.fontSizes` | 新增:方案级字号覆盖 |
|
|||
|
|
| `progressBarHeight` | number | `3` | 新增:行内进度条高度 |
|
|||
|
|
| `unrecognizedNodes` | `object[]` | `[]` | 新增:未知 kind 的原始节点(**不得丢弃**,见 5.5 第 6 条) |
|
|||
|
|
|
|||
|
|
`IslandRowV2`(行):
|
|||
|
|
|
|||
|
|
| 字段 | 类型 | 默认值 | 说明 |
|
|||
|
|
|---|---|---|---|
|
|||
|
|
| `id`/`name`/`main`/`allowNotifications`/`gap`/`nodes` | 沿用 | 沿用 | `main` 行唯一;`gap` 0–64 |
|
|||
|
|
| `appearance` | `RowAppearance` | 见下 | 行级外观(新增) |
|
|||
|
|
| `hideRules` | `RowHideRules` | 全 false | 行级隐藏规则(新增) |
|
|||
|
|
| `progress` | `RowProgress` | 见下 | 行级进度条(新增) |
|
|||
|
|
|
|||
|
|
`RowAppearance`:`backgroundEnabled=false`、`backgroundColor='#000000'`、`backgroundOpacity=0.5`、`foregroundColor='#ffffff'`、`cornerRadius=8`、`opacity=1`、`fontSizes`(继承方案/外观)。
|
|||
|
|
|
|||
|
|
`RowHideRules`:`inClass=false`、`noMessage=false`、`noClass=false`、`fullscreen=false`;全部为 false 表示不隐藏。语义对应 ClassIsland 的行隐藏规则(`repos/ClassIsland/ClassIsland.Core/Models/Components/MainWindowLineSettings.cs` 的 `HideOnRule`/`HidingRules`)。
|
|||
|
|
|
|||
|
|
`RowProgress`:`enabled=true`、`style='line'`、`height=3`、`color='accent'`、`showRemaining=true`。
|
|||
|
|
|
|||
|
|
**行内 3px 进度条的语义(数据来源不在本层实现)**:进度条表示"当前显示内容的时间进度"——消息气泡显示时 = 该消息剩余显示时间;否则 = 当前课程进度。具体数据来源与取值规则由 07/08 定义,本 schema 只保证 `progress`(行级)与 `appearance.progressBar`(全局)提供 `height/show` 两个开关;行设置抽屉的完整字段由 09 定义,本章只登记字段名(`appearance/hideRules/progress`)并要求与 09 对齐,不得在 05 里发明抽屉字段。
|
|||
|
|
|
|||
|
|
`IslandNodeV2`(节点,在 v1 `IslandComponentNode` 上扩展,字段名不变):
|
|||
|
|
|
|||
|
|
| 字段 | 变化 |
|
|||
|
|
|---|---|
|
|||
|
|
| `id`/`kind`/`appearance`/`advanced`/`children` | 沿用(`appearance` 四档字号默认改为继承行/方案) |
|
|||
|
|
| `settings.schedule.display` | 沿用 `'current'|'next'|'today'`;新增 `today` 折叠规则由此驱动 |
|
|||
|
|
| `settings.schedule.progressStyle` | 新增:`'bar'|'line'`,默认 `'line'`(3px) |
|
|||
|
|
| `settings.schedule.showProgress` | 沿用;默认 `true` |
|
|||
|
|
| `settings.schedule.showPeriod`/`count` | 沿用 |
|
|||
|
|
| `settings.mode`(仅 `kind='rolling'`) | 新增:`'continuous' | 'pause'`,默认 `'continuous'`;`pause` 表示滚动到边界后停顿,停顿时长由同组件的 `settings.interval`(秒,1–300)控制 |
|
|||
|
|
| `advanced.hideRules` | 沿用 `in-class/no-message/no-class`;新增 `fullscreen`(与行级一致) |
|
|||
|
|
| `advanced.rowSpan` | 新增:仅容器组件使用,默认 `1` |
|
|||
|
|
|
|||
|
|
**默认方案(新装/重置)**:`layout:'fixed'`、`family:'classisland'`、`fixed.rows=[{main:true, allowNotifications:true, gap:12, nodes:[date, schedule]}]`,`schemes=[默认方案:main 行含 date/clock/schedule/notification]`。数值依据:`packages/shared/src/island-layout.ts:217-255`(现有默认方案)+ `CLASSISLAND_ISLAND_METRICS`(`:19-36`)。注意:`schemes` 是 custom 布局数据,**描述 ClassIsland 规格**且不随 family 改变;`fixed` 配置只在 `family='classisland'` 时由 fixed 渲染器读取。
|
|||
|
|
|
|||
|
|
### 5.4 v1 → v2 迁移算法(必须逐步实现,可重入)
|
|||
|
|
|
|||
|
|
**输入判定**:
|
|||
|
|
|
|||
|
|
1. 读取 `config.json`(不存在 → 全新安装:直接构造 v2 默认,`family='classisland'`,**不写迁移标记**)。
|
|||
|
|
2. 文件存在但 `islandLayout.version` 缺失:视为 v1(唯一版本判据;**不得**再引入 `schemaVersion` 作为第二套版本号)。
|
|||
|
|
3. 老用户判定:`islandLayout.version` 缺失 **或** 文件里存在 `islandLayout`/`island` 任一字段 → 迁移时 `family='winisland'`(保持原外观,不惊吓用户)。
|
|||
|
|
4. 全新安装判定:文件不存在、或既无 `islandLayout` 又无 `island` → `family='classisland'`。
|
|||
|
|
|
|||
|
|
**步骤(必须按序、每步失败可中止)**:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
step 0 备份:把 config.json 原样复制到 config.json.v1.bak(存在则覆盖);写 config.island-migration.json {from,to,at}(仅诊断,不是版本字段)
|
|||
|
|
step 1 外观迁移(appearance):
|
|||
|
|
- family := 'winisland'(老用户)| 'classisland'(新装)
|
|||
|
|
- colorScheme := 'system'(缺失补默认;已有值原样保留)
|
|||
|
|
- winislandStyle := 旧 style(缺省 'black');style := winislandStyle(兼容镜像)
|
|||
|
|
- theme := 'fluent'(新装默认;老用户也写 'fluent',仅 classisland fixed 生效)
|
|||
|
|
- effectEnabled := true(缺失补默认)
|
|||
|
|
- effectScale := 1(缺失补默认;只允许 1/0.75/0.5)
|
|||
|
|
- progressBar := { height:3, show:true }(缺失补默认)
|
|||
|
|
- fontSize:迁移保留旧值(classisland 新装写 16;老用户写原值)
|
|||
|
|
- fontSizes := 由 fontSize 派生:
|
|||
|
|
老用户(winisland):secondary=round(fontSize*14/13), body=round(fontSize*16/13),
|
|||
|
|
emphasized=round(fontSize*18/13), large=round(fontSize*20/13)
|
|||
|
|
新装(classisland):14/16/18/20(fontSize=16 为正文基准)
|
|||
|
|
- 所有旧字段原样保留(height/width/radius/opacity/accent/fontSize/style/...)
|
|||
|
|
step 2 布局迁移(islandLayout):
|
|||
|
|
- version := 2(唯一落盘版本字段)
|
|||
|
|
- layout := 旧 layout(缺省 'fixed')
|
|||
|
|
- residentEnabled := (layout==='custom')(覆盖旧的独立值,保证一致)
|
|||
|
|
- pointerFadeEnabled/pointerFadeReversed/lessonReminders 原样保留
|
|||
|
|
- fixed := 若旧文件无 fixed 字段 → 生成 FixedSchemeV2 默认(1 行 date+schedule;progressBar 继承 appearance.progressBar)
|
|||
|
|
- schemes := 旧 schemes 逐个升级:
|
|||
|
|
rowHeight := 40;rowSpacing := 旧 spacing;theme := 'fluent';
|
|||
|
|
fontSizes := 继承 appearance.fontSizes;progressBarHeight := appearance.progressBar.height;unrecognizedNodes := []
|
|||
|
|
每行补 appearance/hideRules/progress 默认;
|
|||
|
|
每节点补 settings.schedule.progressStyle='line'、advanced.rowSpan=1;
|
|||
|
|
若节点 kind='rolling' 补 settings.mode='continuous'(pause 保留旧 interval 语义)
|
|||
|
|
- 未知 kind 的原节点移入该方案的 unrecognizedNodes(保留原始 JSON,不参与渲染)
|
|||
|
|
step 3 校验与夹取:调用 normalizeIslandLayoutV2 / normalizeAppearanceV2(见 5.5);
|
|||
|
|
校验失败项收集成 warnings,不得中止;结构性错误(schemes 为空等)用默认方案兜底
|
|||
|
|
step 4 写回:saveConfig({ islandLayout, island }) 后立即 flushConfigSync()(**不写 schemaVersion**)
|
|||
|
|
step 5 读回校验:重新读取并 deep-equal 比较关键字段(islandLayout.version/family/layout/方案数/节点数);
|
|||
|
|
不一致 → 从 config.json.v1.bak 恢复、删除迁移标记、报错并降级为"只读默认 v2"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
1. **可重入/幂等**:若 `islandLayout.version===2`,迁移函数必须直接返回原对象(不写盘、不改字段);重复执行不得产生二次修改。
|
|||
|
|
2. **老用户 fixed 不变**:迁移后 `family='winisland'`,fixed 仍渲染原胶囊+详情卡(`ClassicIsland.vue`),因此老用户升级后视觉 0 变化;只有用户主动在设置页切到 `classisland` 或新装用户才看到信息行。
|
|||
|
|
3. **custom 老用户**:`custom` **始终按 ClassIsland 规格渲染、不受 family 影响**;迁移只升级数据(补行级默认、滚动 mode 等),不改 schema 结构。老 custom 用户升级后视觉会变为 ClassIsland 行规格(这是定稿行为),发布说明与设置页必须提示;fixed 用户(默认 winisland)不受影响。
|
|||
|
|
4. 编辑器打开时必须先读迁移后的 v2;若 `islandLayout.version` 不匹配(例如用户用旧版应用改过文件),必须触发一次"再迁移"而不是把 v1 当 v2 解析。
|
|||
|
|
5. 迁移必须记录 warning 列表到内存并在设置页"存储/诊断"可查看(哪几项被夹取、哪几个未知组件被保留)。
|
|||
|
|
|
|||
|
|
### 5.5 校验与夹取(`normalizeIslandLayoutV2` / `normalizeAppearanceV2`)
|
|||
|
|
|
|||
|
|
必须在 shared 中实现,主进程保存前、编辑器加载后、IPC 存入前**三处共用同一个函数**。规则:
|
|||
|
|
|
|||
|
|
1. `version`:仅接受 `2`;缺失/非法按迁移处理,不得静默改成 2 后继续用旧结构。
|
|||
|
|
2. `layout`:`'fixed'|'custom'`,非法回落 `'fixed'`。
|
|||
|
|
3. `family`:`'classisland'|'winisland'`,非法回落 `'classisland'`。
|
|||
|
|
4. 数量上限:行 ≤12/方案、节点 ≤200/全部方案、嵌套深度 ≤6、方案 ≤20;`main` 行唯一(多余取消)。
|
|||
|
|
5. 数值夹取:`rowHeight 32–64`、`rowSpacing 0–150`、`gap 0–64`、`radius 0–20`、`opacity 0–1`、`backgroundOpacity 0–1`、`fontSizes 8–96`、`scale 0.75–2.5`、`rowSpan 1–6`、`progressBarHeight 1–8`;非法/NaN 回落默认值(禁止 `Math.max(min, NaN)` 这种写法,参考 `src/main/island.ts:2225-2241` 的 fallback 说明)。
|
|||
|
|
6. 未知 kind:**不得丢弃**。把原始节点 JSON 放入 `scheme.unrecognizedNodes`(最多 200 条),渲染时忽略并显示"未知组件(已保留)";这样用旧版应用打开再保存也不会丢数据。
|
|||
|
|
7. 颜色:统一 `#rrggbb` 小写;非法回落默认;透明度字段独立,不把 `#rrggbbaa` 塞进颜色字段。
|
|||
|
|
8. id:空/非字符串/重复 → 重新生成(`node-<n>`/`row-<n>`/`scheme-<n>`);id 最长 100 字符。
|
|||
|
|
9. 文本:`settings.text` ≤2000 字符、`locationName/countdownTitle` ≤64;超长截断并记 warning。
|
|||
|
|
10. 经纬度:`latitude -90..90`、`longitude -180..180`,可空;空表示未配置(组件显示"请设置天气地点")。
|
|||
|
|
11. 未知字段:v2 对象里出现但类型未定义的字段必须**原样保留**在对象上(不删除),以便前向兼容。
|
|||
|
|
12. `lessonReminders`:`prepareSeconds 5–1800`、`endingSeconds 5–600`(依据现有 `packages/shared/src/island-layout.ts:390-398`)。
|
|||
|
|
13. `winislandStyle` / `style`:两者都只接受 `black/glass/tinted`,非法回落 `black`;读入优先 `winislandStyle`、回退 `style`,写出两者同步(兼容镜像);**不得**因 family 迁移而删除 `style`;family 与调色板相互独立(family 决定 fixed 用哪套渲染,winislandStyle 决定 winisland 的调色板)。
|
|||
|
|
14. `theme`:只接受 `fluent/classic`,非法回落 `fluent`;具体色板/圆角/阴影 token 由 06 定义,05 只登记字段与默认值。
|
|||
|
|
15. `colorScheme`:只接受 `light/dark/system`,非法回落 `system`;`system` 时跟随应用 `html.dark` 状态决定岛明暗(映射逻辑由渲染层实现,数据层只存枚举)。
|
|||
|
|
16. `fontSize`:8–96,缺省按 family 取(classisland=16 正文基准、winisland=13 旧基线);`fontSizes` 缺失时按 5.2 第 4 条派生;显式 `fontSizes` 优先;迁移不得覆盖旧 `fontSize`。
|
|||
|
|
17. `effectEnabled`:boolean,缺省 `true`;`effectScale`:只接受 `1/0.75/0.5`,缺省 `1`,非法取 `1`;`progressBar`:`{height:1–8, show:boolean}`,缺省 `{3,true}`。
|
|||
|
|
18. 进度条高度优先级:`行 progress.height` > `方案 progressBarHeight` > `appearance.progressBar.height`;任一缺失沿上一级回落,最终默认 3。`progressBar.show=false` 时所有行都不显示(行级 `enabled=false` 只关该行)。
|
|||
|
|
19. 滚动组件:`settings.mode` 只接受 `continuous/pause`,缺省 `continuous`;`pause` 时停顿秒数复用 `settings.interval`(夹取 1–300);`continuous` 时 `speed` 夹取 1–200(沿用现有范围)。
|
|||
|
|
20. 渲染/样式命名空间**只由 layout(fixed 的 family)决定,不写入数据**:`fixed + family='winisland'` → winisland 命名空间;`fixed + family='classisland'` → classisland 命名空间;**`custom` 始终 classisland 命名空间、不受 family 影响**;normalize 不得改写样式类名/选择器相关字段。
|
|||
|
|
21. `IslandNotification.level`、`IslandLessonReminder.progress/leftSeconds`、`IslandState.family/pendingStaged/bubblePhase/bubbleDeadline` 均为**运行期派生字段**,normalize 不处理、不落盘。
|
|||
|
|
|
|||
|
|
### 5.6 DTO 映射(ClassHelper 数据 → ClassIsland 语义)
|
|||
|
|
|
|||
|
|
**fixed(classisland)行内气泡规则**:同一时刻只显示 1 条 active 气泡;点击展开详情/操作;有多条未处理消息时不并列显示,而是折叠为「还有 N 条」,点击打开应用内通知中心(沿用 ClassHelper 现有通知中心路由);消息队列、去重、上课暂存、已读回执规则完全不变(02 §2.5/§2.12)。
|
|||
|
|
|
|||
|
|
**`IslandState` → ClassIsland 主界面状态**:
|
|||
|
|
|
|||
|
|
| ClassHelper 字段 | ClassIsland 语义 | 渲染映射(classisland fixed) |
|
|||
|
|
|---|---|---|
|
|||
|
|
| `mode:'hidden'` | 岛不可见(仅提醒特效时临时显示) | 信息行隐藏/仅保留细缝 |
|
|||
|
|
| `mode:'pill'` | 紧凑态 | 行内气泡/铃铛提示 |
|
|||
|
|
| `mode:'expanded'` | 展开态 | 行展开 + 消息面板 |
|
|||
|
|
| `active`/`queued` | 提醒队列(当前播放/待播) | 行内气泡列表,按 `PRIORITY_RANK` 排序 |
|
|||
|
|
| `inClass` | `TimeState=OnClass` | 行隐藏规则 `inClass` 生效、提醒遮罩可用 |
|
|||
|
|
| `currentPeriodEnd` | 当前时间点结束 | 进度条/倒计时取值 |
|
|||
|
|
| `reason` | 提醒来源(新消息/课后补发/紧急/叫人/收回中) | 气泡入场动画与角标 |
|
|||
|
|
| `listExpanded` | 列表是否展开更多 | 行内"展开更多" |
|
|||
|
|
| `lessonReminder` | 课级提醒(本地) | 行覆盖层(遮罩/秒数徽标) |
|
|||
|
|
| `coursewareTransfer` | 课件提示(覆盖态) | 最高优先级覆盖全部行 |
|
|||
|
|
|
|||
|
|
**`IslandNotification` → ClassIsland 提醒语义**:
|
|||
|
|
|
|||
|
|
| ClassHelper 字段 | ClassIsland 对应 | 规则 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| `priority: LOW/NORMAL/HIGH/URGENT` | 提醒队列优先级(`NotificationPriority`) | URGENT=插队立即播放;HIGH/NORMAL/LOW 按排序入队 |
|
|||
|
|
| `kind:'notification'` | 普通提醒(Mask+Overlay 两阶段) | 两阶段时长取 5s/5s(`repos/ClassIsland/...NotificationRequest.cs:17-20`) |
|
|||
|
|
| `kind:'homework'` | 作业提醒(本地合成 id `homework-<id>`) | 不进入已读回执;不可"标为已读" |
|
|||
|
|
| `kind:'call'` | 叫人(Call) | 上课时保持可点、不自动展开;紧急叫人按 URGENT |
|
|||
|
|
| `title/content/teacherName/courseName/createdAt` | 提醒标题/正文/来源/时间 | 气泡第一行标题、第二行来源+时间 |
|
|||
|
|
| `id` | 去重键 | 双链路(通知/叫人)合并时 id 相同即合并(见 02 §2.5 第 3 条) |
|
|||
|
|
|
|||
|
|
**`ScheduleDto` → ClassIsland 课表语义(仅映射,不落 Profile 文件)**:
|
|||
|
|
|
|||
|
|
| ClassHelper 字段 | ClassIsland 概念 | 映射规则 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| `id` | `ClassInfo`/条目标识 | 仅内部使用 |
|
|||
|
|
| `course.courseName` | `Subject.Name` | 直接展示;null 显示"—" |
|
|||
|
|
| `dayOfWeek`(1–7) | `TimeRule.WeekDay` | 1=周一…7=周日 |
|
|||
|
|
| `startTime`/`endTime`(HH:mm) | `TimeLayoutItem.StartTime/EndTime` | 转 `HH:mm:00`;只用于显示与进度计算 |
|
|||
|
|
| `weekStart`/`weekEnd` | 周次范围 | 用于"本周是否有课" |
|
|||
|
|
| `weekParity: ALL/ODD/EVEN` | `TimeRule.WeekCountDiv`(0/1/2)+ `WeekCountDivTotal=2` | ALL→0;ODD→1;EVEN→2 |
|
|||
|
|
| `location` | 附加信息 | 仅在气泡/详情展示,不参与时间点 |
|
|||
|
|
|
|||
|
|
**`IslandTimePointDto` → `TimeLayoutItem`**:
|
|||
|
|
|
|||
|
|
| ClassHelper | ClassIsland | 规则 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| `timeType: LESSON/BREAK/DIVIDER/ACTION` | `TimeType: 0/1/2/3` | 一一对应(`repos/ClassIsland/...TimeLayoutItem.cs:119-138`) |
|
|||
|
|
| `name`(课间名/时间点名) | `BreakName`/名称 | 课间空时显示"课间休息" |
|
|||
|
|
| `courseName` | 默认课程 | null 等价 `Guid.Empty`(无课) |
|
|||
|
|
| `startTime`/`endTime` | 开始/结束 | 用于行内课程表折叠与进度 |
|
|||
|
|
| `isHideDefault` | `IsHideDefault` | 折叠时隐藏、轮到时展开 |
|
|||
|
|
|
|||
|
|
1. 上述映射只用于**渲染语义对齐**,不得把 `ScheduleDto` 落盘成 ClassIsland 档案;数据仍以服务端解析结果为准(02 §2.12)。
|
|||
|
|
2. `CoursewareTransfer` 映射为"最高优先级覆盖态",不进入提醒队列、不参与已读;映射字段 `name/category/teacherName/receivedBytes/totalBytes/progress/stage/error` 直接使用(`packages/shared/src/courseware.ts`、`src/island/CoursewareTransfer.vue:12-65`)。
|
|||
|
|
|
|||
|
|
**运行期状态扩展(`IslandState`,不落盘,必须随状态推送)**:
|
|||
|
|
|
|||
|
|
| 字段 | 类型 | 默认值 | 语义 |
|
|||
|
|
|---|---|---|---|
|
|||
|
|
| `family` | `'classisland' \| 'winisland'` | 当前 `appearance.family` | 当前生效风格;渲染进程据此切换 CSS 命名空间 |
|
|||
|
|
| `pendingStaged` | number | `0` | 上课期间被暂存、下课后待补弹的消息数(对应现有 `queued` 中因上课被压住的数量;`queued.length` 不变) |
|
|||
|
|
| `bubblePhase` | `'idle' \| 'mask-in' \| 'content-in' \| 'out'` | `'idle'` | 行内气泡/提醒的两阶段播放阶段(固定 5s/5s 时长的可视化映射) |
|
|||
|
|
| `bubbleDeadline` | number | `0` | 当前气泡自动收起的时间戳(毫秒);`0` 表示无 |
|
|||
|
|
|
|||
|
|
1. `IslandState` 的新字段只存在于**运行期推送**(`island:state`),不得写进 `config.json`、不得被编辑器保存;旧渲染进程缺字段时必须按默认值渲染(前向兼容)。
|
|||
|
|
2. `pendingStaged` 必须由主进程根据现有上课暂存逻辑计算(02 §2.5 第 2 条),不得让渲染进程自行推断。
|
|||
|
|
|
|||
|
|
**`IslandNotification.level`(展示层级,派生字段)**:
|
|||
|
|
|
|||
|
|
| `priority` | `level` | 用途 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| `URGENT` | `'emergency'` | 紧急气泡/强提醒特效可触发 |
|
|||
|
|
| `HIGH` | `'high'` | 高亮气泡/角标 |
|
|||
|
|
| `NORMAL` | `'normal'` | 普通气泡 |
|
|||
|
|
| `LOW` | `'low'` | 弱化展示 |
|
|||
|
|
|
|||
|
|
`level` 由 priority 派生,**不落库、不持久化**;通知进入岛队列时在 `main/island/messages.ts` 统一计算,渲染层只读取。
|
|||
|
|
|
|||
|
|
**`IslandLessonReminder` 扩展字段(运行期,不落盘)**:
|
|||
|
|
|
|||
|
|
| 字段 | 类型 | 语义 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| `progress` | number (0–1) | 当前提醒/课程进度:`ending/in-class` = `(now-lessonStartsAt)/(lessonEndsAt-lessonStartsAt)`;其余 = `(endsAt-now)/(endsAt-startedAt)`(与现有 `LessonReminderContent.vue:36-50` 计算一致) |
|
|||
|
|
| `leftSeconds` | number | 下课前剩余秒(`ceil((lessonEndsAt-now)/1000)`,clamp ≥0),供蓝色圆形秒数徽标与遮罩标题使用;现有 `LessonReminderContent.vue:24-35` 已有同义计算 |
|
|||
|
|
| `kind` | 不变 | 仍为 `prepare/on-class/break/ending/in-class` |
|
|||
|
|
|
|||
|
|
**fixed(classisland)下的提醒叠加口径**:`prepare`/`on-class`/`break` 以**行内容接管**呈现(临时替换课程表内容,动画由 08/10 定义);`ending` 不接管行内容,只在课程名右侧显示蓝色圆形秒数徽标(`leftSeconds` 驱动);`custom` 模式不接管,保持课程组件 + 秒数徽标。该口径只影响渲染,不改变 02 §2.8 的五类提醒计算与开关语义。
|
|||
|
|
|
|||
|
|
### 5.7 IPC payload 版本与兼容
|
|||
|
|
|
|||
|
|
1. IPC payload **可以**携带 `schemaVersion:2` 仅用于运行时校验/调试,**不得落盘、不得作为配置版本判据**;配置版本只认 `islandLayout.version`。`island:state`/`island:appearance`/`island:get-layout` 三个高频 payload 建议带上该字段(旧字段保留,新增字段可选)。
|
|||
|
|
2. 接收方规则:**未知字段忽略、缺字段回落默认**;不得因为缺 `family` 就拒绝渲染(老渲染进程遇到新主进程时应按 `winisland` 降级,反之按 `classisland`)。
|
|||
|
|
3. `island:save-layout` 必须返回归一化后的 v2 结果(编辑器以返回值为准,避免"本地看着对、主进程夹取后不一致")。
|
|||
|
|
4. 主进程 `getConfig()` 返回给渲染进程的 `islandLayout` 必须是 v2;`island` 必须含 `family` 与新增外观字段(`theme/effectEnabled/effectScale/progressBar`)。
|
|||
|
|
5. 旧通道名全部保留(见 04 §4.4);新增通道必须同时在 `shared/island/ipc.ts` 与 preload 中登记,不得字符串散落。
|
|||
|
|
6. 作业指纹相关 payload(见 04 §4.3.13)必须带 `source`(`realtime/homework-local/replay/call`)与 `dedupeKey`;主进程按 key 去重,渲染进程保留现有"本机刚录作业免上岛"快速路径。
|
|||
|
|
|
|||
|
|
### 5.8 编辑器预览与临时状态(不持久化)
|
|||
|
|
|
|||
|
|
1. 编辑器的内嵌预览、缩放、选中项、拖拽态属于**临时状态**,不得写入 `config.json`;只有"应用/保存"才写 v2。
|
|||
|
|
2. "无消息展开内容(expandedRows)"与常态 `rows` 分开编辑、分开保存;`activeSchemeId` 只在切换方案时写。
|
|||
|
|
3. 设置页"测试灵动岛"仍走 `islandPush` + `context.preview=true`(30 秒预览、失焦不收起,见 02 §2.15),预览消息 id 必须带前缀(如 `appearance-test-`)以便清理;预览不写历史、不改已读。
|
|||
|
|
4. 编辑器打开时若检测到迁移 warning,必须在顶部显示"部分旧设置已按新格式调整(可查看详情)",不得静默。
|
|||
|
|
|
|||
|
|
### 5.9 回滚与降级
|
|||
|
|
|
|||
|
|
1. **回滚入口**:设置页"灵动岛 → 恢复升级前布局"必须存在;回滚步骤:先备份当前 `config.json` → 把 `config.json.v1.bak` 覆盖回去(该文件天然没有 `islandLayout.version`,即 v1)→ 提示重启 → 重启后旧版本行为恢复(family=winisland/fixed=胶囊)。
|
|||
|
|
2. **迁移失败降级**:应用不得崩溃;以内存默认 v2 启动(不写盘),岛显示默认信息行,设置页显示错误与"恢复备份"按钮。
|
|||
|
|
3. **前向兼容**:读到 `islandLayout.version>2` 时,应用只读该配置(不覆盖写),在设置页提示"配置由更高版本创建";不存在其他版本字段可用于判断。
|
|||
|
|
4. **文件损坏**:`config.json` 非法 JSON 时沿用现有 `.bak` 回退逻辑(`config.ts` 现状:读取失败回落默认),回滚后仍必须按 `islandLayout.version` 缺失执行一次迁移判定;不得用损坏文件覆盖 `.bak`。
|
|||
|
|
5. **旧版应用打开新文件**:不可避免(旧版不认识 v2 字段),因此 v2 必须保持"未知字段保留"原则(5.5 第 11 条)并把关键新数据放在旧版会**忽略而不是报错**的位置;旧版保存后新字段可能丢失,需在文档提示用户不要跨版本反复切换。
|
|||
|
|
|
|||
|
|
### 5.10 版本变更记录与兼容矩阵
|
|||
|
|
|
|||
|
|
必须维护一张可追溯的变更表(代码里以常量数组或文档形式存在),用于判断"某个字段从哪版开始有、哪版废弃":
|
|||
|
|
|
|||
|
|
| 变更 | 版本 | 兼容策略 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| `islandLayout.version` 新增(唯一落盘版本字段) | v2 | 缺失=1,按 5.4 迁移 |
|
|||
|
|
| `appearance.family` 新增 | v2 | 缺失时:老配置迁移=winisland;新装=classisland |
|
|||
|
|
| `appearance.theme`(fluent/classic)新增 | v2 | 缺失=fluent;仅 classisland 生效;具体 token 由 06 定义 |
|
|||
|
|
| `appearance.effectEnabled` / `effectScale` 新增 | v2 | 缺失 `true`/`1`;effectScale 只允许 1/0.75/0.5 |
|
|||
|
|
| `appearance.progressBar {height,show}` 新增 | v2 | 缺失 `{3,true}`;高度优先级 行 > 方案 > 外观 |
|
|||
|
|
| `appearance.fontSizes` 新增 | v2 | 缺失时按旧 `fontSize` 比例推导(5.4 step1) |
|
|||
|
|
| `fixed`(信息行)新增 | v2 | 缺失时生成默认 1 行(date+schedule);仅 classisland fixed 使用 |
|
|||
|
|
| 行级 `appearance/hideRules/progress` 新增 | v2 | 缺失时补默认;字段名与 09 对齐;winisland fixed 下保留数据但忽略(custom 始终使用) |
|
|||
|
|
| 方案级 `rowHeight/rowSpacing/theme/fontSizes/progressBarHeight` 新增 | v2 | 缺失时继承 appearance 默认 |
|
|||
|
|
| `appearance.colorScheme` 新增 | v2 | 缺失=`system`;非法回落 `system` |
|
|||
|
|
| `appearance.fontSize` 语义按 family 分叉 | v2 | classisland=正文基准(新装 16,派生 14/18/20);winisland=旧语义(13);迁移保留旧值 |
|
|||
|
|
| `appearance.winislandStyle` 新增,`style` 转兼容镜像 | v2 | 读优先 `winislandStyle`、回退 `style`;写两者同步;`style` 不删除;切换 family 保留原值 |
|
|||
|
|
| 滚动组件 `settings.mode` 新增 | v2 | 缺失=`continuous`;非法回落 `continuous`;`pause` 复用 `settings.interval` |
|
|||
|
|
| `residentEnabled` 转纯兼容 | v2 | 读出后与 `layout==='custom'` 对齐;不再作为独立开关 |
|
|||
|
|
| `family` 作用域 | v2 | **只影响 fixed**;custom 始终 ClassIsland 规格、不受 family 影响 |
|
|||
|
|
| 旧 `style` 字段 | 保留镜像 | 本轮不删除;与 family/winislandStyle 独立;建议 v3 再评估废弃 |
|
|||
|
|
| 迁移标记文件 `config.island-migration.json` | 2 | 仅诊断用途;不是版本字段、不参与运行逻辑 |
|
|||
|
|
| 回滚文件 `config.json.v1.bak` | 2 | 迁移前覆盖写;回滚后仍保留,供二次回滚 |
|
|||
|
|
|
|||
|
|
**兼容矩阵(读写方向必须明确)**:
|
|||
|
|
|
|||
|
|
1. **新读旧**(v2 代码读 v1 文件):允许,走 5.4 迁移;迁移后内存中只存在 v2。
|
|||
|
|
2. **新读新**:允许,走 normalize;`islandLayout.version` 更高时只读。
|
|||
|
|
3. **旧读新**(旧代码读 v2 文件):旧代码会忽略未知字段继续运行(因为 v2 把新字段放在其不认识的键上),但**旧代码保存后会丢新字段**;因此必须在文档与发布说明中写明"不要用旧版应用反复编辑新配置"。
|
|||
|
|
4. **新写旧**:禁止。v2 代码写盘必须带 `islandLayout.version:2`,不得输出 v1 结构,也不得写任何文件级版本号。
|
|||
|
|
5. **迁移与编辑器并发**:编辑器打开时若检测到 `config.json` 被迁移,必须重新加载(比较 `updatedAt`/`mtime` 或读取返回值),不得用旧草稿覆盖迁移结果;保存前再次 `getConfig` 核对 `islandLayout.version`。
|
|||
|
|
|
|||
|
|
**兼容性红线(不得违反)**:
|
|||
|
|
|
|||
|
|
- 不得删除任何仍被旧配置使用的字段名(如 `style`、`residentEnabled`、`spacing`、`expandedRows`、`pointerFadeEnabled`)。
|
|||
|
|
- 不得把对象结构从"方案数组"改成"对象字典"或反之(旧编辑器/旧冒烟会解析失败)。
|
|||
|
|
- 不得改变 `ISLAND_LIST_METRICS` 与 `ISLAND_SIZE_DELTA` 的**语义**(可以新增 classisland 专用 metrics,但旧常量必须继续导出且数值不变),因为大量冒烟断言按旧值计算(`packages/shared/src/constants.ts:344-351,379-402`)。
|
|||
|
|
- 不得让 v2 的 `rows` 与 v1 的 `rows` 在同一键上结构不兼容:v2 行只**增加**字段,不重命名已有字段。
|
|||
|
|
|
|||
|
|
### 5.11 验收标准(第 5 部分)
|
|||
|
|
|
|||
|
|
1. 新装启动后 `config.json` 不含任何 `schemaVersion`;含 `island.family:'classisland'`、`island.colorScheme:'system'`、`island.fontSize:16`、`island.theme:'fluent'`、`island.effectEnabled:true`、`island.effectScale:1`、`island.progressBar:{height:3,show:true}`、`islandLayout.version:2`、`fixed.rows` 默认 1 行(日期+课程表);`layout:'fixed'`,界面呈现 ClassIsland 信息行。
|
|||
|
|
2. 用一份旧 v1 配置(fixed、custom 各一份,含 schemes/nodes/appearance/residentEnabled)启动:迁移成功、无字段丢失、`family:'winisland'`、旧胶囊外观不变、`fontSize` 保留原值、`style`/`winislandStyle` 一致;`config.json.v1.bak` 存在。
|
|||
|
|
3. 迁移后再次启动:不重复迁移、不重复写盘(可用文件 mtime/内容哈希断言幂等)。
|
|||
|
|
4. 迁移失败模拟(把 `config.json` 改坏/只读)时:原文件不被破坏、应用以默认 v2 启动、设置页显示错误与恢复入口。
|
|||
|
|
5. "恢复升级前布局"可把 v1 备份恢复回来并重启生效;恢复前自动再备份当前配置。
|
|||
|
|
6. `normalizeIslandLayoutV2` 对行数>12、节点>200、深度>6、方案>20、非法颜色/数值/kind 全部夹取或保留到 `unrecognizedNodes`;不崩溃、不丢未知组件。
|
|||
|
|
7. 老用户切到 `classisland` 后 fixed 变信息行、切回 `winisland` 恢复胶囊;**family 切换时 `style`/`winislandStyle` 与 `fontSize` 保持用户原值不变**;custom 始终 ClassIsland 规格、切换 family 不改变 custom 渲染。
|
|||
|
|
8. 编辑器中常态行/展开行分开保存;应用后 `islandLayout` 为 v2 且 `island:save-layout` 返回值与本地一致。
|
|||
|
|
9. `IslandState`/`IslandNotification`/`ScheduleDto`/`IslandTimePointDto` 的映射表逐项实现:优先级、上课态、时间点类型、单双周、作业/叫人特殊语义均可按 5.6 追踪。
|
|||
|
|
10. IPC 兼容:旧通道全部可用;payload 可选带 `schemaVersion` 仅作校验(不落盘);缺字段时按 family 降级渲染而不是报错。
|
|||
|
|
11. 编辑器/设置页不把预览、缩放、选中项写入配置;预览消息不写历史、不改已读。
|
|||
|
|
12. 读到 `islandLayout.version>2` 时应用只读并提示,不得覆盖用户文件。
|
|||
|
|
13. 外观新字段:关闭 `effectEnabled` 后强提醒特效不再渲染(行内气泡/普通提醒仍工作);`effectScale` 只接受 1/0.75/0.5 并实际改变特效尺寸;`progressBar.show=false` 时所有行进度条隐藏,行级高度覆盖方案/外观。
|
|||
|
|
14. 运行期状态:`IslandState` 含 `family/pendingStaged/bubblePhase/bubbleDeadline` 且**不写入 config.json**;上课暂存时 `pendingStaged` 与队列数一致;气泡阶段按 `idle→mask-in→content-in→out` 推进。
|
|||
|
|
15. 派生字段:`IslandNotification.level` 按 `URGENT→emergency/HIGH→high/NORMAL→normal/LOW→low` 生成且不落盘;`IslandLessonReminder.progress` 在 0–1、`leftSeconds` 与蓝色圆形徽标一致(剩余 1 秒显示 `-1`)。
|
|||
|
|
16. fixed(classisland)下 `prepare/on-class/break` 以行内容接管呈现、`ending` 只显示秒数徽标;custom 下不接管(课程组件 + 徽标);两种 family 的提醒开关语义一致。
|
|||
|
|
17. fixed 行内气泡同一时刻只显示 1 条 active;多条折叠为「还有 N 条」并可打开应用内通知中心;队列/去重/暂存规则不变。
|
|||
|
|
18. `family='winisland'` 只影响 fixed(保持现有胶囊观感);custom 始终按 ClassIsland 规格渲染、不受 family 影响、不引用 winisland 样式命名空间;`family='classisland'` 的 fixed 使用 classisland 命名空间。
|
|||
|
|
19. 经 `pnpm verify:desktop`(默认无头 quick)验证时,所有既有冒烟注入点仍可用:`setTestInputPassthrough`/`setHitTestCursor`/`getWindowBounds`/`getHitRect`/`getInteractive`/`setBlurCollapseEnabled`/`setTouchMode`/`getTouchMode`/`isRecovering` 等名称与语义不变(改造归属 part 11 测试基线)。
|
|||
|
|
20. `colorScheme` 三种取值均生效:`system` 跟随应用深浅色(`html.dark`)、`light`/`dark` 强制;非法值回落 `system`;切换后岛即时重渲染且不改主窗口主题。
|
|||
|
|
21. `fontSize` 语义按 family:classisland 下改 `fontSize` 会派生 14/18/20 阶梯并整体缩放行文字;winisland 下保持旧"基准字号"行为;迁移用户旧值不被覆盖。
|
|||
|
|
22. 滚动组件 `settings.mode`:`continuous` 持续滚动、`pause` 到边界停顿 `settings.interval` 秒;normalize 后取值合法,旧配置缺失补 `continuous`。
|