Files

176 lines
22 KiB
Markdown
Raw Permalink Normal View History

# ClassHelper 技术栈调研笔记(Part 02/12/13 支撑)
> 本文件是队友 `stack` 的静态分析记录,供 Lead 汇总审校时核对"我读了什么、为什么这样映射、还剩什么不确定"。
> 全程只使用 read/grep/glob 与纯文本统计命令;**未执行任何构建、安装、运行、截图、冒烟或 UI 命令**;未修改 `repos/` 下任何文件。
## 1. 已读文件清单
### 1.1 ClassHelper(`repos/ClassHelper-master`)
| 文件 | 用途 |
|---|---|
| `package.json` | 根版本单一来源、`pnpm@11.8.0`、Node `>=20.19.0`、全部脚本(build/dev/typecheck/lint/dist/version) |
| `pnpm-workspace.yaml` | workspace 定义、`allowBuilds` 白名单、`verifyDepsBeforeRun` |
| `tsconfig.base.json` | 目标 ES2023、NodeNext、strict、verbatimModuleSyntax、declaration |
| `packages/desktop-client/package.json` | Electron 44.3、Vue 3.5.42、Element Plus 2.14.5、Pinia 4.0.3、Vue Router 5.3.1、Vite 8.3、TS 5.9.3、electron-builder 26.15.3、esbuild 0.28.2 |
| `packages/desktop-client/electron-builder.yml` | appId/productName、`electronLanguages: [zh-CN]`、files/asar/npmRebuild、win nsis+portable、artifactName |
| `packages/desktop-client/vite.config.mts` | 双入口(index/island)、`base:'./'`、outDir `dist/renderer`、5174 strictPort |
| `packages/desktop-client/island.html` / `index.html` | 透明底、CSP 全指令、preload 挂载点 |
| `packages/desktop-client/scripts/build-main.mjs` | esbuild CJS 主进程/preload/岛 preload、产物路径、更新助手编译 |
| `packages/desktop-client/scripts/dev.mjs` | Vite 就绪探测 → Electron 启动、`VITE_DEV_SERVER_URL` |
| `packages/desktop-client/src/main/index.ts` | 单实例、启动时序、设置窗口参数、导航白名单、开机自启、托盘、退出清理 |
| `src/main/config.ts` | `config.json` 路径、`PersistedConfig` 全字段与默认值、归一化、safeStorage、120ms 合并写、`flushConfigSync`、`deviceKey` |
| `src/main/island.ts`(前 1140 行) | 岛窗口全参数、固定包围盒、命中/穿透、失焦宽限、触摸模式、`CLOSE_ANIMATION_MS=360`、各状态超时表、通知队列与动作 |
| `src/main/ipc.ts` | IPC handler 注册、配置白名单剔除、appInfo、自启、日志目录、DevTools、client status |
| `src/main/channels.ts` | 通道名叶子模块(零 import)与原因 |
| `src/main/tray.ts` | 托盘菜单与图标兜底 |
| `src/main/logger.ts` | `main.log`、1MB 轮转、懒解析、格式与堆栈写入 |
| `src/main/crash-guard.ts` | 崩溃自愈三条路径、10 分钟 3 次限流、空闲重启、冒烟豁免 |
| `src/main/auto-update.ts` | 更新策略、文件名校验、下载/校验/差分、空闲 5 分钟、安装助手、结果文件 |
| `src/main/island.ts`(1141+ 行片段经 grep) | `recoverWindow`、命中轮询、`alive` 心跳相关字段 |
| `src/preload/index.ts` | `window.desktop` 全量白名单、订阅可取消、纯数据约束 |
| `src/preload/island.ts` | `window.island` 全量白名单(状态/外观/尺寸/命中/触摸/心跳) |
| `src/types/desktop.d.ts` | 桥接契约、`DesktopStoredConfig` 全字段、`IslandState`、冒烟钩子 |
| `src/island/main.ts` | 岛入口零 UI 库、二次注入动效令牌 |
| `src/island/spring.ts` | WinIsland 弹簧参数、换向限速、零过冲钳制 |
| `src/island/squircle.ts` | 连续圆角(p=4.2、14 段/角)、展开半径 |
| `src/renderer/composables/motion.ts` | 令牌注入、reduced-motion、hover 能力、count-up、stagger、主题揭示 |
| `src/renderer/main.ts` | 应用窗口挂载顺序、冒烟动态 import |
| `src/renderer/router/index.ts` | hash 路由、守卫、路由表结构 |
| `src/renderer/cache/db.ts` | IndexedDB 缓存层(`classhelper-cache`、object store 列表、容错) |
| `packages/shared/package.json` / `src/index.ts` / `src/motion.ts` / `src/island-layout.ts` / `src/constants.ts`(255-364 行)/ `src/types.ts`(ScheduleDto、IslandNotification、IslandState 片段) | 共享类型、动效令牌、岛方案 schema 与 normalize、外观默认值/范围、尺寸增量、ClassIsland 度量常量 |
| `packages/ui/package.json` / `src/tokens.css` / `src/index.ts` 文件清单 | 共享 UI 消费方式、基础令牌与深色覆盖 |
| `docs/dev/architecture.md` | 目录职责、数据流、更新助手链路、工程边界 |
| `docs/dev/motion.md` | 动效令牌表、CSS linear() 弹簧、四条硬约束、主题两条纪律 |
| `docs/dev/winisland-design-tokens.md` | 经典岛几何、自定义行度量、禁用 `setBackgroundMaterial`、阴影常驻 |
| `docs/dev/release.md` | 版本单一来源、构建与验证顺序、产物命名 |
| `docs/get-started/requirements.md` | 客户端内存/磁盘/触摸屏/平台要求(250–350MB、仅 Windows) |
| `AGENTS.md`(自动注入) | preload 白名单、CSP、离线缓存隔离、岛方案字段、验证纪律、发布纪律 |
### 1.2 ClassIsland(`repos/ClassIsland`)
| 文件 | 用途 |
|---|---|
| `ClassIsland/Program.cs` | 命令行参数全集、`Global\ClassIsland.Lock` 单实例、URI 二次启动转发、Sentry、进程优先级 |
| `ClassIsland.Launcher/Program.cs` | 多版本目录枚举(`app*`、`.current`、`.destroy`、`.partial`)、Linux 执行权限 |
| `ClassIsland/Services/SettingsService.cs` | `Settings.json` 加载/保存、版本迁移门、设置叠层(Add/RemoveSettingsOverlay)、属性变更即保存 |
| `ClassIsland/Models/Settings.cs` | 211 个默认值字段(grep `private .* _\w+ = ` 全量提取) |
| `ClassIsland.Shared/Helpers/ConfigureFileHelper.cs` | `.bak` 备份/回退、原子写、`MergeDictionary`、`CopyObject`、反序列化选项 |
| `ClassIsland.Shared/Models/ConfigError.cs` | 配置错误记录模型(只读声明) |
| `ClassIsland/Services/ProfileService.cs` | 档案路径、管理端拉取、临时层清理、信任档案、切换保存 |
| `ClassIsland.Shared/Models/Profile/Profile.cs` | 档案根字段、默认群、编辑科目、临时层、预定课表 |
| `.../Profile/TimeLayout.cs` / `TimeLayoutItem.cs` / `ClassPlan.cs` / `ClassInfo.cs` / `TimeRule.cs` / `Subject.cs` / `ClassPlanGroup.cs` / `OrderedSchedule.cs` | 课表数据模型全字段、默认值、`[JsonIgnore]`、GUID 空回退、有效时间点算法 |
| `ClassIsland/Services/ComponentsService.cs` | 组件布局路径/默认布局 GUID/迁移/保存时机 |
| `ClassIsland.Core/Models/Components/{ComponentProfile,MainWindowLineSettings,ComponentSettings,ComponentGroup}.cs` | 组件布局全字段与默认值 |
| `ClassIsland/Models/ComponentSettings/*.cs`(9 个) | 组件专属设置模型清单 |
| `ClassIsland/Services/AutomationService.cs` | 自动化目录、`Workflow[]` 顶层、切换保存、信任档案门槛、时间点行动、触发/恢复语义 |
| `ClassIsland.Core/Models/Automation/{Workflow,TriggerSettings,ActionMenuTreeNode}.cs`、`ClassIsland.Shared/Models/Automation/{ActionSet,ActionItem}.cs` | 自动化数据结构与 `Actions` 序列化名 |
| `ClassIsland.Core/Models/Ruleset/{Ruleset,Rule,RuleGroup,StringMatchingSettings}.cs` | 规则集结构与默认值(Or/And、IsReversed、IsEnabled) |
| `ClassIsland/Services/Automation/{Triggers,Actions}/*.cs`(grep `TriggerInfo`/`ActionInfo`) | 12 个触发器 ID、7 个动作 ID |
| `ClassIsland/Services/PluginService.cs` | `.cipx` 安装/解压/`.disabled`/`.uninstall`、清单解析、API 版本门槛、依赖拓扑、插件配置目录、审计 |
| `ClassIsland.Core/Abstractions/PluginBase.cs` / `Services/IPluginService.cs` / `Models/Plugin/{PluginManifest,PluginInfo,PluginIndex,PluginIndexItem,PluginDependency,PluginRepoManifest}.cs` / `Enums/PluginLoadStatus.cs` | 插件模型、市场索引、加载状态 |
| `ClassIsland/Services/PluginMarketService.cs` | 回退镜像、索引刷新 7 天、默认索引 URL |
| `ClassIsland.PluginSdk/ClassIsland.PluginSdk.targets` / `generate-md5.ps1` | 插件打包成 `.cipx`、`checksums.md` 的 MD5 注释 |
| `ClassIsland/Services/IpcService.cs`、`ClassIsland.Shared.IPC/{IpcClient,IpcRoutedNotifyIds}.cs`、`Abstractions/Services/IPublic*.cs`、`README.md` | 命名管道 IPC、公开服务契约、事件通知 ID |
| `ClassIsland.Core/CommonDirectories.cs` / `Services/GlobalStorageService.cs` | 路径划分、机器级全局存储 |
| `ClassIsland/Services/FileFolderService.cs` | 需创建目录全集、备份内容与命名、自动备份策略 |
| `ClassIsland/Services/XamlThemeService.cs`(grep) | 主题路径、`EnabledThemes.json`、主题包缓存 |
| `ClassIsland/Services/AppUpdating/UpdateService.cs`(1-150、380-500、630-880 行) | 更新通道/状态枚举、部署锁、文件图签名、差分、`.partial/.current/.destroy`、压缩包校验 |
| `ClassIsland/Models/AppUpdating/DeploymentLock.cs`、`Models/{UpdateChannel,UpdateSource}.cs`、`ClassIsland.Shared/Enums/{UpdateStatus,UpdateWorkingStatus}.cs` | 更新模型与状态 |
| `ClassIsland.Shared/Models/Notification/{NotificationSettings,NotificationPriority,NotificationRequest}.cs`、`ClassIsland.Core/Models/Notification/NotificationRequest.cs` | 提醒设置/优先级/播放会话字段 |
| `ClassIsland.Core/Converters/ColorHexJsonConverter.cs`、`ClassIsland.Shared/JsonConverters/GuidEmptyFallbackConverter.cs` | 颜色与空 GUID 的 JSON 兼容规则 |
| `ClassIsland.Core/Models/XamlTheme/ThemeManifest.cs` | 主题清单字段与默认值 |
| `examples/example.json` | 真实档案 JSON 形态(PascalCase、StartSecond 旧字段、`AttachedObjects`、TimeType 注释) |
| `AGENTS.md`(自动注入) | 仓库形状、SDK 版本、构建与验证纪律、Avalonia 开发规则 |
### 1.3 团队上下文
- `analysis/00-brief.md`(总简报、纪律、术语、分工)
- `analysis/40-baseline.md`(Lead 核对的 canonical 数值)
- `parts/01-overview.md`(总纲:三进程模型、ClassHelper 栈、P0/P1/P2 模块)
- 共享任务 `task-1`(认领/完成状态)
## 2. ClassIsland → ClassHelper 映射决策(含理由)
### 2.1 已定决策(写入 parts)
| 决策 | 选择 | 理由 / 证据 |
|---|---|---|
| 工程形态 | pnpm monorepo:`shared` + `ui` + `desktop-client` | 复用 ClassHelper 已验证结构(`pnpm-workspace.yaml:1-2`、`desktop-client/package.json`) |
| 包管理/运行时 | pnpm 11.8.0、Node ≥20.19 | 根 `package.json:6-9`;`allowBuilds` 必须显式声明,否则 install 失败 |
| 桌面壳 | Electron 44.3 + electron-builder 26.15 | 唯一被团队指定且与 ClassHelper 对齐 |
| 进程模型 | 主进程 / 应用窗口渲染 / 岛渲染 / 插件 utilityProcess / 更新助手 | 保留 ClassHelper 三进程 + 新增隔离进程(ClassIsland 的插件 `PluginLoadContext` 在 Electron 下的等价物是进程隔离) |
| 数据形态 | 本地 JSON 文件(ClassIsland 兼容 PascalCase 档案 + camelCase 运行配置) | ClassIsland 是单机应用;ClassHelper 的 IndexedDB 缓存是"服务端离线缓存",本应用无服务端,故只保留 JSON 文件 + 可选内存缓存 |
| 数据根目录 | `app.getPath('userData')` | Electron 正确做法;ClassIsland 的 `AppRootFolderPath` 在便携/安装两种形态下语义不同,用 userData 统一 |
| 主界面窗口 | 岛窗口 = ClassIsland MainWindow;设置窗口 = 新增 | 团队总纲第 1.6 节要求三进程分离;岛必须透明置顶,设置窗口是普通窗口 |
| 岛形变 | `SpringValue` 帧积分 + squircle 连续圆角 | ClassHelper 已验证(`spring.ts`、`squircle.ts`),且 ClassIsland 的 `WrapPanel` 尺寸动画可平滑映射 |
| 主题 | CSS 变量 + `html.dark` + 主题包目录 | ClassIsland 是 XAML 资源字典;Vue 下唯一可行且可热切换的等价物 |
| 插件包 | `.cipx` = zip + `manifest.yml`(camelCase)+ ESM 入口 | 保留 ClassIsland 生态语义(清单字段/依赖/API 版本/`.disabled`/`.uninstall`),把 DLL 入口换成 `main.mjs` |
| 插件市场 | 多源索引 JSON + 镜像回退 + MD5(兼容)+ SHA-256(安全) | 对齐 `PluginIndex`/`PluginMarketService`,并补足 ClassIsland 只校验 MD5 的弱点 |
| 更新 | 自研:清单 + SHA-256 + 独立安装助手 + 三档安装时机 | ClassIsland 是 `FileMap` 分组件部署 + Launcher 多版本;Electron 下以 NSIS 安装目录 + 助手替换 + 回滚副本等价实现(标注【推断】) |
| 单实例 | `requestSingleInstanceLock` + `second-instance` | 对齐 ClassIsland `Mutex` 语义,且 Electron 原生支持 |
| 外部进程集成 | 默认关闭本地 HTTP;插件通过宿主 API | ClassIsland 有命名管道 IPC 公开服务;Electron 不暴露等价物,改为按需可选的 localhost HTTP |
| 崩溃自愈 | 崩溃重建 + 无响应 10s 重建 + 内存 1.5GB 看门狗 + 10 分钟 3 次限流 | 直接对齐 `crash-guard.ts` 与 `MemoryWatchDogService.cs` |
| 日志 | 自研 `logger.ts`(main.log + 1MB 轮转) | 与 ClassHelper 完全一致;不引入 electron-log 以减少不可控行为 |
| 自动备份 | `backups/Auto_*.zip`,7 天/16 份 | 对齐 `FileFolderService.cs:83-113` |
| 安全 | CSP + contextBridge 白名单 + sandbox + 导航白名单 + 凭据门禁 + 插件权限 | 与 ClassHelper AGENTS.md 完全一致;插件权限是在 ClassIsland(全信任 DLL)之上的主动增强【推断】 |
### 2.2 有意偏离 ClassIsland 的地方(必须在提示词中说明)
1. **插件权限模型**:ClassIsland 插件是进程内 DLL,天然全信任;本实现必须默认拒绝、按 `permissions` 放行。属安全增强,不是功能缺失。
2. **热更新/热重载**:ClassIsland 有 HotAvalonia(开发期);本实现只在开发期用 Vite HMR,生产不得热替换插件。
3. **管理端(集控)**:ClassIsland 有 `ManagementService`(管理服务器下发设置/档案);本任务明确不要求后端,P2 可选。提示词中应写"默认不实现,预留接口"。
4. **Sentry**:ClassIsland 默认开启匿名崩溃上报;本实现默认提示可关闭,且不得上报课表/个人信息。
5. **多平台**:ClassIsland 支持 Windows/Linux/macOS;本实现主目标 Windows(触摸屏/希沃),macOS/Linux 尽力支持但不得阻塞 Windows 验收。
### 2.3 与其他 part 的边界
- Part 03(设计 tokens):`tokens.css` 数值我引用了基础令牌,但**完整色板/字号阶梯归 Part 03**;我未在 02 中重定义。
- Part 04/05(主界面/组件):岛窗口参数、组件行度量、组件布局 schema 的"存储字段"归我(13.5),"视觉与交互"归 04/05,已在 13.5 注明"视觉见第 5 部分"。
- Part 06/07/08(课表/提醒/自动化):数据 schema 我给出字段级清单,业务语义(触发时机、队列行为)归 06/07/08;我在 13.4/13.6/13.7 只规定"存什么、默认值、兼容规则"。
- Part 09(主题):主题目录与 `EnabledThemes.json`、`ThemeManifest` 字段归我(13.8),CSS 变量清单与壁纸取色算法归 09。
- Part 10/11(设置/其他窗口):设置页字段默认值我给了 13.3 全表,页面分组与控件归 10。
- Part 14(动画总表):我只引用 spring/squircle/动效令牌的数值,未展开逐动画时间表。
- Part 16/18(验收/禁止):我给出的验收标准是"本部分自查",与总验收不冲突。
## 3. 未决问题 / 缺口(交给 Lead 决策或后续 part 补齐)
1. **更新安装目录策略**:ClassIsland 用 Launcher 枚举 `app-<版本>` 目录实现"多版本 + 回滚";Electron + NSIS 下我建议"固定安装目录 + 助手替换 + 回滚副本",但**是否允许 side-by-side 多版本目录**需要 Lead 确认(影响 02/12 的更新章节与 17 里程碑)。
2. **插件清单字段名**:ClassIsland 的 `manifest.yml` 用 `camelCase`(YamlDotNet `CamelCaseNamingConvention`)。我按 camelCase 写;若团队希望与"PascalCase 档案"统一,需要在 Part 12 全局替换(会影响导入 ClassIsland 插件包)。
3. **插件设置页渲染方式**:我规定"声明式 schema,不允许任意 HTML"(安全),但 ClassIsland 插件可提供任意 Avalonia 控件;若要求 1:1 还原插件 UI 能力,需要引入受限的 iframe/webview 方案(会带来安全与样式隔离成本)——建议按声明式实现并在 Part 12 注明"这是有意限制"。
4. **外部进程 IPC**:ClassIsland 公开 `IPublicLessonsService`/`IPublicProfileService` 给外部程序(命名管道)。我建议默认关闭 localhost HTTP;若产品需要"第三方读取当前课程",需要 Lead 决定是否实现并规定端口/鉴权。
5. **`AttachedObjects`(附加设置)兼容**:ClassIsland 1.7 档案在 `TimeLayoutItem` 下有 `AttachedObjects`(提醒/天气/附加信息附加设置)。我在 13.4.3 规定"导入保留、新代码不写",但 Part 06/07 如果要支持"每个时间点的课级提醒设置",需要把该字段升级为一等公民。
6. **`Settings.json` 双写问题**:我规定 `config.json`(camelCase)为主、`Settings.json`(PascalCase)仅导入导出时出现;若团队希望"用户可以直接编辑 ClassIsland 风格的 Settings.json",需要改成以 PascalCase 为主(牵动 02/10/13)。
7. **通知历史是否落盘**:ClassIsland 没有持久化通知历史;我按"通知中心"需求(Part 07/11)推断了 `notification-history.json` 与 500 条上限,需 Part 07 确认。
8. **多显示器与 DPI**:`windowDockingMonitorIndex`、`followCursorDisplay`、`scale` 的 DPI 换算规则我未展开(属 Part 04);Part 04 需要给出"逻辑像素 vs 物理像素"的统一口径,避免 02 的窗口参数与 04 的定位算法冲突。
9. **`isIslandSeperated` 拼写**:ClassIsland 源码确实是 `IsIslandSeperated`(少一个 a,`Settings.cs:1852`)。为导入兼容必须保留该拼写;Part 04/10 若写正确拼写会造成字段不匹配。
10. **触摸模式与窗口形状**:`setShape` 只在 Windows 生效;macOS/Linux 需要不同的命中策略(ClassHelper 也没覆盖)。提示词中应写明"非 Windows 平台降级为整窗接收鼠标"。
11. **更新签名**:ClassIsland 的更新清单有签名(`DetachedSignatureProcessor`);我要求"必须签名",但密钥管理(公钥内置、轮换策略)需要 Lead 在 Part 17/18 里给一句总纪律。
12. **插件/主题市场的默认源**:我引用了 ClassIsland 的官方索引 URL 作为示例;本实现是否内置该源(涉及外网与合规)需要 Lead 决定,建议默认"仅用户手动添加源"。
### 3.1 最终裁决(Lead 已确定,已并入 parts 并覆盖上述 1–12 条)
1. 更新安装:固定安装目录 + 独立更新助手替换 + `updates/rollback/<旧版本>/` 回滚副本,**不做 side-by-side 多版本**。
2. 插件清单:统一 camelCase 写出,**兼容读取** PascalCase 旧清单并归一化。
3. 插件设置页:只能声明式 schema,禁止任意 HTML/iframe/DOM 访问(安全红线)。
4. 外部读取课程:**可选**本地只读 HTTP API(仅 127.0.0.1、随机端口、默认关闭、一次性 token),不恢复命名管道。
5. `AttachedObjects` 升级为一等公民 `attachedObjects`(Profile/ClassPlan/ClassInfo/TimeLayout/TimeLayoutItem/Subject),接收 `AttachedObjects` 原拼写。
6. 数据文件:`config.json` 为主 + `Settings.json` 只读导入/导出兼容层(不双向双写),保留 `IsIslandSeperated`。
7. 通知历史:默认 500 条、可调 100–5000、滚动删除、写入节流(≥5 条或 ≥5s)。
8. 坐标:全部使用 DIP(CSS px),窗口 bounds 也按 DIP;仅截图验收用 `devicePixelRatio`。
9. 非 Windows 无 `setShape`:降级为整窗 `setIgnoreMouseEvents(true,{forward:true})` + 岛可见区内命中。
10. 更新清单:SHA-256 必校验 + 可选 Ed25519(`keyId`、公钥轮换);默认只接受 https,镜像需显式开启并提示风险。
11. 插件/主题市场:默认不内置在线官方索引,仅离线示例索引;用户手动添加并二次确认后才联网。
12. 其余按原写法。
13. `AttachedObjects` 不是 1.7 遗留:`AttachableSettingsObject.cs:15` 定义、2.x `AttachedSettingsControlPresenter.axaml.cs:241,361` 仍读写;已补 4 个已知 Guid 与设置对象类型(课程显示/上课提醒/放学提醒/天气提醒)。
14. 已补天气数据 schema 一节(`WeatherInfo`/`WeatherAlert`/`ForecastDaily`/`ForecastHourly`/`Minutely`/`AqiInfo`/`WeatherQueryResult` + 缓存/来源/刷新/预警清洗 + 5 个规则 ID),与 07 对齐。
15. Excel 导入导出定为 **P1 必须实现(xlsx 重建)**,ReoGrid/WPF 仅参考;已写导入/导出/8MB 上限/导入前备份/worker 执行要求。
新增研读文件(为裁决 13/14/15):`ClassIsland.Shared/AttachableSettingsObject.cs`、`ClassIsland.Core/Controls/AttachedSettingsControlPresenter.axaml.cs`(241-253)、`ClassIsland.Core/Models/AttachedSettings/LessonControlAttachedSettings.cs`、`ClassIsland/Models/AttachedSettings/{ClassNotification,WeatherNotification,AfterSchoolNotification,Test}AttachedSettings.cs`、`ClassIsland/Controls/AttachedSettingsControls/*.axaml.cs`(Guid 与 Usage 目标)、`ClassIsland.Core/Enums/AttachedSettingsTargets.cs`、`ClassIsland.Core/Models/Weather/*.cs`(`WeatherInfo`/`WeatherAlert`/`CurrentWeather`/`ForecastDaily`/`ForecastHourly`/`Minutely`/`MinutelyPrecipitation`/`AqiInfo`/`ValueUnitPair`/`WindInfo`/`RangedValue`/`StatusValueBase`/`WeatherQueryResult`)、`ClassIsland/Services/WeatherService.cs`(来源/刷新/过滤)、`ClassIsland/Views/ExcelImportWindow.xaml.cs`、`ExcelExportWindow.xaml.cs`(ReoGrid 参考)、`Settings.cs:113,2122-2130`(`ExpIsExcelImportEnabled` 默认 false)。
## 4. 静态分析纪律声明
- 全程只读:`read` 读文本、`grep`/`glob` 找文件、`Select-String`/`Get-Content` 做纯文本统计、`[regex]` 统计中文字符数。
- 未执行 `dotnet build/run`、`pnpm install/dev/build`、`electron`、`npm`、截图、冒烟或任何会弹窗/占用屏幕的命令。
- 未修改 `repos/ClassIsland` 与 `repos/ClassHelper-master` 下任何文件;只写入了 `parts/02-tech-stack.md`、`parts/12-plugins-ipc.md`、`parts/13-data-storage.md`、`analysis/classhelper-stack.md`。
- 证据标注口径:`repos/...:行号`;无法从源码直接确认的工程设计一律标【推断】。