2108d432 by 柴进

chore(memory): 同步 Claude auto-memory 到 .claude/memory/

便于在其他机器(公司电脑)拉取后恢复对项目的记忆。
.gitignore 改为 .claude/* + 排除 .claude/memory/,其余 .claude/ 子内容继续忽略。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent f48ebc4d
# Memory Index
- [User: 跨平台 UI 一致性诉求](user_cross_platform_ui.md) — 用户强烈关注 macOS 和 Windows 两端视觉/交互的一致性
- [User: 自主交付偏好](user_overnight_autonomy.md) — 大型重构允许"睡前授权 → 醒来 review 分支"模式
- [Project: 珠宝壹佰 / 品牌色](project_zb100_brand.md) — 公司"珠宝壹佰",品牌色契合 Apple Blue
- [Project: UI 主题系统架构](project_theme_system.md) — (旧 QWidget 路线) theme.py 设计令牌,业务不写 inline setStyleSheet
- [Project: UI 迁移到 QML](project_qml_migration.md) — 当前活跃路线,feat/ui-qml-poc 分支,task #11/#12/#20 完成,#13-#19 待办
- [Reference: QML PoC 结构](reference_qml_poc_structure.md) — qml_poc/ 文件结构 + Theme token + 组件 API
- [Reference: 业务桥层 bridges/](reference_bridges.md) — 5 个 Bridge QObject 的 Property/Signal/Slot 形状(task #13-#17 wiring 查)
- [Reference: core/ 业务模块](reference_core_modules.md) — image_generator.py 拆分后的业务核心目录
- [Feedback: 拒绝路径盲从 / 第一性原理](feedback_first_principles.md) — 路径非最优时直接建议替代方案,不要按用户当下方案盲做
- [Feedback: UI 迭代用 python 跑,别打包](feedback_dev_iter_via_python.md) — 调 UI 用 pythonw.exe 直接跑,PyInstaller 太慢
- [Feedback: 旧代码多留几天](feedback_keep_legacy_code.md) — 重构 / 替换实现时默认 .txt 改名保留,不 git rm,等用户主动说删
- [Reference: 项目入口和打包](reference_entry_and_build.md) — 主入口、打包脚本、PyInstaller spec 位置
- [Reference: 打包当前形状 + 易踩坑](reference_packaging.md) — task #19 ZB100ImageGenerator.spec 现状(QML datas 路径 / pathex / Pillow)
---
name: UI 迭代用 python 直接跑,别打包
description: 调试 UI 改动时直接 python image_generator.py,不要 pyinstaller 打包
type: feedback
originSessionId: 21abab40-0d6c-449a-ae72-9ca03205f077
---
用户原话:"你直接启动py让我看效果好了,不要打包了,打包反而节奏慢了"。
**Why**:PyInstaller 打包跑一次约 60-90 秒(包括 collect dynamic libs、PYZ、COLLECT),UI 调试每次改一个 padding / 字号都要重打,浪费时间。Python 直接跑只要 1-2 秒到登录页。
**How to apply**
- 当用户想"看效果"或快速验收 UI 改动时,用 `start "" .venv/Scripts/pythonw.exe image_generator.py`(Windows 后台启动,无 console 窗口)
- pythonw.exe 而不是 python.exe — 前者不弹黑色 console,跟打包后体验一致
- 只在以下场景才打包:
- 任务结尾要交付产物(明天上班看 dist)
- 验证打包后的资源完整性(如确认 PIL 原生库进了包)
- 用户明确要求"打个新包"
- 不要在每次改完代码后默认打包
**配套**:先 `tasklist | grep -i ZB100\|pythonw` 看有没有残留实例,有就 kill,避免端口/文件锁冲突。
---
name: 第一性原理 / 拒绝路径盲从
description: 用户期望在路径非最优或目标模糊时直接被建议替代方案,而不是被动执行
type: feedback
originSessionId: 21abab40-0d6c-449a-ae72-9ca03205f077
---
来自全局 CLAUDE.md(用户的私有规则):
> **第一性原理**:从原始需求出发,拒绝路径盲从。目标模糊时停下讨论,路径非最优时直接建议替代方案
实际验证过的应用场景(2026-05-09):
- 用户最初问"UI 有点丑丑的,你有啥建议"——这是个开放问题。我直接给三个方向 + 推荐方案 B(自己写 QSS),用户接受。
- 用户在选第三方库 vs 自写 QSS 时也接受了我"先做 B"的推荐。
- brainstorming 阶段每个决策都给"我的推荐 + 理由"作为第一项,用户选择速度很快。
**Why**:用户技术判断力强,希望对话型协作而非"你说一我做一"的脚本式执行。给推荐 + 理由比给清单更省时间。
**How to apply**
- 用户描述任务时如果包含 "你觉得"/"建议"/"看看"等词,**先给推荐**再给替代
- 给方案永远是 2-3 个选项 + 我的判断 + 一句话理由,不要列 10 个让他选
- 如果发现用户提的实施路径有更好的替代(比如他说"加个 retry"但实际应该改架构),**先建议替代**而不是默默照做
- 简单问题直接回答,不要套结构化模板(CLAUDE.md 明确说"仅在架构决策 / 方案评估 / 代码审查时使用结构化模板")
---
name: 重构时保留旧实现,多留几天
description: 用户偏好在大重构 / 替换实现时保留旧代码作为回退参考,不急着删
type: feedback
originSessionId: 21abab40-0d6c-449a-ae72-9ca03205f077
---
重构、替换实现、删除老入口时,**默认先保留旧代码几天到几周**,不要在 PR 里同时删。
**Why**:用户多次强调(2026-05-09 改 image_generator.py.txt 时、2026-05-11 重申"多留几天")。
理由是新实现刚跑通时未必稳定,"万一有问题还能翻翻老代码"。Linus 风格的"不破坏兼容"
在这个项目里被延伸成"不要急着断老路"——回退点的存在比代码整洁更重要。
**How to apply**
- 老入口 / 老实现替换为新版后,git mv 改 `.txt` 后缀(Python 不加载 / PyInstaller 不扫)
而不是 `git rm`。等用户主动说"删了吧"再删。
- 类似 image_generator.py.txt 的情况:要重构的大文件别"一次干净到底",
先把核心拆出去,老文件保留两版并存几天。
- 在 PR 描述里指明老代码留作 fallback,预计何时清理(让 reviewer 知道"这不是漏删")。
- 自己看见老代码躺着别"顺手 cleanup" — 没用户授权不删。
**判断边界**
- 真正过期、已破坏 API、有安全问题的老代码,可以删(这种情况会很明显)。
- 普通的"功能重写、行为不变"的老代码,按本规则保留。
---
name: UI 迁移到 PySide6 + QML(QtQuick)
description: 2026-05-09 用户决定从 QWidget+QSS 转 QtQuick/QML,分支 feat/ui-qml-poc
type: project
originSessionId: 21abab40-0d6c-449a-ae72-9ca03205f077
---
**Why**:QWidget+QSS 视觉天花板低(layout 压缩 / sizeHint 不更新 / 字号溢出系列坑)。
QML layout 引擎稳定,圆角 / 阴影 / 动效原生支持,跨 Mac+Windows 视觉一致。
**Python 业务后端完全不动**,只换 UI 层。
**分支**`feat/ui-qml-poc`,独立于 master。
**目录结构**
```
core/ 业务核心(task #20 从 image_generator.py 拆出)
paths.py / database.py / history.py / generation.py / jewelry.py
runtime.py 启动期工具(crash diag / init_logging / cleanup / log_system_info)
bridges/ QML 桥层
auth.py / imagegen.py / history.py / taskqueue.py / jewelry.py / _icons.py
qml_poc/main_qml.py 主入口(8-phase boot + setContextProperty 注入桥)
qml_poc/qml/ QML 文件
Theme.qml (Singleton tokens)
Main.qml / LoginScreen.qml / MainWindow.qml
ImageGenTab.qml / StyleDesignerTab.qml / HistoryTab.qml
components/ (PrimaryButton / SecondaryButton / ThemedTextField / ThemedComboBox / Card / CaptionLabel)
image_generator.py.txt 老 QWidget 代码(git mv 改 .txt 不参与执行,保留扒老逻辑用)
task_queue.py / audit_logger.py / preflight.py / config_util.py 保持 master 一致,未改架构
```
**已完成**(截至 2026-05-10):
- ✅ #11 PoC UI 视觉 / #20 image_generator.py 拆 core/
- ✅ #12 桥层(5 QObject contextProperty)/ #13 db 登录 + 记住凭据
- ✅ #14 图片生成 tab 完整(核心 / 参考图 / 收藏 / 下载 / Ctrl+V / 14 比例 + 自动切模式 /
多任务 / 拖拽重排 DragHandler Finder 风格 / 跟手 ghost / 10MB 校验 / 编号 badge)
- ✅ #16 历史 tab(ListView + 详情面板 + 右键菜单 / 清空 / 复制 prompt / 在文件管理器中显示选中
/ 🔁 重做按钮回填到生成 tab 不带生成图)
- ✅ #17 任务队列 sidebar(taskQueue.model 增量 / 状态彩色 / 进度条 / 左键回填 loadTask /
右键 Menu 取消任务)
- ✅ #15 款式设计 tab(8 字段 + 锁定 + 随机 + 字段右键恢复单类别默认 + 顶部全部恢复 +
inline ➕/🗑️ 词条 + 实时 Prompt 预览)
- ✅ 启动 8-phase boot:crash diag → RotatingFileHandler logs/app.log → 系统信息 →
cleanup_clipboard_tempfiles → preflight → 窗口图标 → 桥层 → QML
- ✅ 完整 logtrace:bridges/* + core/* 所有 except 用 logger.exception(...)
- ✅ 剪贴板三层 fallback + macOS osascript 兜底(避免 Qt clipboard native crash)
- ✅ 输入框右键菜单(剪切/复制/粘贴/全选,密码框禁复制剪切)
- ✅ #21 修右键粘贴(TextField MouseArea 替代 TapHandler)+ 8px ScrollBar +
参考图双击 TapHandler.onDoubleTapped 系统查看器
- ✅ #22 日志分级 fsync:TieredFsyncHandler(WARNING+ 同步 fsync / INFO buffered)
+ 1s 周期 daemon 线程兜底 + macOS DiagnosticReports 提示写入 crash_log.txt
- ✅ #19 Windows 打包:ZB100ImageGenerator.spec 切 qml_poc/main_qml.py 入口,
pathex=项目根,datas qml_poc/qml→_MEIPASS/qml(不带 qml_poc/ 前缀!
PyInstaller 把入口平铺到 _MEIPASS 根,Path(__file__).parent/"qml" 找 _MEIPASS/qml)
本地 dist/ZB100ImageGenerator/ZB100ImageGenerator.exe 验证 8 phases 全过
**剩余待办**
- #19 Mac build_mac_universal.sh 真机验证(含 .app + .dmg + Pillow .dylibs)
- #19 image_generator.py.txt 真正删除(用户要求暂留作扒老逻辑参考;确认稳定后再删)
- #18 系统暗色模式跟随(Theme.qml 加深色 token + Qt.styleHints.colorScheme 监听)
- 款式设计批量词库管理对话框(旧版有,新版只有 inline ➕/🗑️)
- 双击大图内嵌 viewer(当前 Qt.openUrlExternally 系统查看器替代,等价但 UX 不同)
**How to apply**
- 业务代码改在 core/* + task_queue.py + audit_logger.py
- UI 改在 qml_poc/qml/*;桥层 API 形状见 reference_bridges.md
- 老逻辑参考 image_generator.py.txt(已重命名不参与执行)
- 完整启动序列、logtrace 在 main_qml.py + core/runtime.py
- 用户偏好:先做 #19 打包能交付给同事;#18 暗色等留以后单独迭代
---
name: UI 主题系统架构
description: theme.py 是设计系统单一真相源,业务代码不写 inline setStyleSheet
type: project
originSessionId: 21abab40-0d6c-449a-ae72-9ca03205f077
---
2026-05-09 重构上线(分支 `feat/ui-redesign-apple-theme`):
**架构**
- `theme.py` 包含 TOKENS_LIGHT / TOKENS_DARK(24 个语义颜色)/ SIZES(圆角/间距/字号)/ FONT_STACK
- `build_qss(mode)` 拼出完整 QSS 字符串
- `ThemeManager(QObject)` 监听 `QGuiApplication.styleHints().colorSchemeChanged` 自动切换
- `apply_theme(app)``image_generator.py main()` 创建 QApplication 后立即调用
- `get_color(token, fallback)` 用于 QSS 命中不到的渲染层 API(如 `setForeground`
**业务代码规则**
- ❌ 不要写 `widget.setStyleSheet(...)` 装饰样式
- ✅ 用 `setObjectName("xxx")` 命中 `#xxx` 选择器
- ✅ 用 `setProperty("variant", "primary"|"danger"|"ghost"|"link"|"success-flash")` 命中 `[variant="..."]` 按钮变体
- ✅ 用 `setProperty("status", "success"|"warning"|"danger"|"info"|"muted")` + `style().polish()` 命中 status label
- ✅ 用 `setProperty("role", "title"|"secondary"|"caption"|"muted"|"thumb"|"thumb_index")` 命中通用 label 角色
- ✅ 用 `setProperty("drag_state", "idle"|"active")` 命中拖拽态
- ✅ 用 `setProperty("has_image", "true"|"false")` 命中预览图占位/已显示
**仅允许的 inline setStyleSheet**
- 平台 hack(macOS 占位符颜色 etc.)— `image_generator.py` 1292/1356
- 单点临时高亮反馈(< 3 秒自动恢复)
**Why**:之前 65 处零散 `setStyleSheet` 让深色模式无法实现,且改一个圆角要找十处。集中后一处改全局。
**How to apply**:未来加新 widget / 改外观时:
1. 优先看 `theme.py` 里有没有合适的现成 token / variant / role
2. 如果没有,加到 `theme.py` 里(保持单一真相源),别再开 inline setStyleSheet 的口子
3. 状态切换(图标变化、颜色反馈)用 `setProperty + style().polish()`,不要 inline setStyleSheet
---
name: 珠宝壹佰品牌信息
description: 公司名"珠宝壹佰",应用名"珠宝壹佰图像生成器",品牌色与 Apple Blue 契合
type: project
originSessionId: 21abab40-0d6c-449a-ae72-9ca03205f077
---
公司中文名"珠宝壹佰",英文/代码 namespace 用 ZB100(如 `ZB100ImageGenerator``zb100_windows.ico``zb100_mac.icns`、GitLab 域名 `gitlab.zb100.com`)。
**品牌色**:用户选择 Apple Blue `#0071E3`(Light)/ `#0A84FF`(Dark)作为应用主色,理由是"跟品牌元素的颜色契合"。
**Why**:避免后续给珠宝业品牌联想(金色 / 祖母绿)—— 这些在 brainstorming 时被讨论过但被用户主动否决,他想要的是工具感而不是行业感。
**How to apply**
- 如果未来需要"品牌色装饰"(市场物料、登录页背景图、欢迎页),用蓝色系而非金/绿
- 涉及多色 chart / status indicator 时,主色固定 Apple Blue,状态色(success/warning/danger)用 Apple system colors
- 如果有客户提"想加点珠宝业色彩"的需求,先确认是否要打破当前的"工具感专注"原则,不要默默加金色
---
name: QML 业务桥层 bridges/* API
description: 5 个 Bridge QObject 的 Property/Signal/Slot 形状(截至 2026-05-10)
type: reference
originSessionId: 21abab40-0d6c-449a-ae72-9ca03205f077
---
**目录**
```
bridges/
├── _icons.py build_placeholder_icon
├── auth.py AuthBridge (db_config, audit_logger, last_user, saved_password_hash, config_path)
├── imagegen.py ImageGenBridge (TaskQueueManager, HistoryManager, AuthBridge, api_key, saved_prompts, config_path)
├── history.py HistoryBridge (HistoryManager)
├── taskqueue.py TaskQueueBridge (TaskQueueManager)
└── jewelry.py JewelryBridge (JewelryLibraryManager)
```
**main_qml.py contextProperty 名字**`auth / imageGen / history / taskQueue / jewelry / appState`
---
**AuthBridge**
- Property: `loggedIn` / `currentUser` / `lastUser` (constant) / `hasSavedPassword` (constant)
- Slot:
- `login(user, pwd) → bool` 明文密码
- `loginWithSavedPassword(user) → bool` 用已存 hash("记住密码"路径,跳过 hash)
- `saveCredentials(pwd, rememberUser, rememberPwd)` 写回 config.json(pwd="" 时保留旧 hash)
- `logout()` / `deviceName() → str`
- Signal: `loggedInChanged / currentUserChanged / loginFailed(str)`
- 内部:登录成功同步拿 public IP(3 API 兜底 3s timeout)走 audit log_login
**ImageGenBridge**
- Property: `apiKey / busy / savedPrompts (list)`
- Slot:
- `submitTask(prompt, refs, aspect, size, mode) → task_id` (task_type=IMAGE_GENERATION)
- `submitStyleTask(prompt, refs, aspect, size, mode) → task_id` (task_type=STYLE_DESIGN)
- `cancelTask(task_id)`
- `pasteFromClipboard() → list[str]` 三层 fallback(hasUrls / mime raw bytes / imageData / clipboard.image)
+ macOS osascript 兜底(避免 Qt clipboard NSPasteboard→NSImage native crash)
- `normalizeFileUrls(urls) → list` (内部走 _validate_image_file 校验扩展名 + ≤10MB + QPixmap)
- `validateImageFile(path) → {ok, reason}` / `validateImageFiles(paths) → list`
- `saveFile(src, dest) → bool` 下载图片
- `addSavedPrompt(p) / removeSavedPrompt(p) / toggleSavedPrompt(p) → bool` (返回切换后的"已收藏"状态)
- `isSavedPrompt(p) → bool`
- Signal: `taskSubmitted(taskId)`, `taskCompleted(taskId, resultPath, prompt, model)`,
`taskFailed(taskId, error)`, `taskProgress(taskId, progress, statusText)`,
`apiKeyChanged / busyChanged / savedPromptsChanged`
- 内部:_on_completed 调 history.save_generation → 写 task.result_path → emit taskCompleted
**HistoryBridge**
- Property: `model` (HistoryListModel QAbstractListModel) / `count`
- Slot:
- `refresh()` 整体 reset / `addNew(ts)` 增量插顶
- `deleteItem(ts)` 增量删 / `clearAll() → bool` rmtree+重建
- `getItem(ts) → dict` 详情 / `thumbnailPath(ts) → str` 缩略图路径
- `revealInExplorer(path)` Windows explorer /select, / macOS open -R / Linux xdg-open
- `copyToClipboard(text) → bool`
- `redoToImageGen(ts)` → emit redoRequested 让 ImageGenTab 回填 prompt+参考图+设置
- Signal: `itemAdded(ts) / itemRemoved(ts) / countChanged`,
`redoRequested(payload)` payload: {timestamp, prompt, referenceImages, aspectRatio, imageSize, mode}
- HistoryListModel.roleNames: display / decoration / toolTip / timestamp(=UserRole)
**TaskQueueBridge**
- Property: `model` (_TaskListModel) / `pendingCount` / `runningCount`
- Slot: `cancelTask(task_id)`, `loadTask(task_id)` → emit taskLoadRequested 回填到对应 tab
- Signal: `pendingCountChanged / runningCountChanged`,
`taskLoadRequested(payload)` payload: {taskId, type ("image_gen"|"style_design"),
prompt, referenceImages, aspectRatio, imageSize, mode, resultPath (空字符串 if 未完成)}
- _TaskListModel.roleNames: `taskId / prompt / status / progress / statusText / elapsed`
- status 值: "pending" / "running" / "completed" / "failed"
**JewelryBridge**
- Property: `categories` (list, 8 个类别)
- Slot: `getOptions(cat) → list`, `addItem(cat, val)`, `removeItem(cat, val)`,
`resetCategory(cat)`, `resetAll()`, `previewPrompt(formData) → str`
- Signal: `libraryChanged(category)`
---
**主要 wiring 模式**(QML 端):
1. ImageGenTab 接 `taskQueue.taskLoadRequested` → type=image_gen 时回填 + 切 tab 0
2. ImageGenTab 接 `history.redoRequested` → 回填(不带生成图)+ 切 tab 0
3. StyleDesignerTab 接 `taskQueue.taskLoadRequested` → type=style_design 时回填 assembledPrompt + 切 tab 1
4. ImageGenTab 接 `imageGen.taskCompleted/Failed/Progress` → 自己 myTaskIds 数组管理多任务
**重要约束**
- bytes 永远不传 QML,写盘 → 传文件路径
- Path 对象转 `as_posix()`(防 Windows 反斜杠在 QML "file:///" + path 拼接异常)
- HistoryItem 等 dataclass 不直接暴露,用 getItem 转 dict
- 异常都用 `logger.exception(msg)` 写完整 stack 到 logs/app.log
---
name: core/ 业务核心模块结构
description: image_generator.py 5082 行拆解后的业务核心目录布局(task #20 完成)
type: reference
originSessionId: 21abab40-0d6c-449a-ae72-9ca03205f077
---
**背景**:image_generator.py 原本 5082 行混了 12 个类(数据库/历史/Worker/词库/UI),
QML 迁移期顺手拆出业务核心,UI 类留待 task #19 删除。
**目录布局**(截至 2026-05-10):
```
core/
├── __init__.py
├── paths.py get_app_data_path + save_png_with_validation +
_migrate_data_from_app_bundle
├── database.py DatabaseManager + hash_password
(加了 authenticate_with_hash 走"记住密码"路径,跳过 hash)
├── history.py HistoryItem + HistoryListModel + HistoryManager
HistoryListModel.roleNames 已扩展给 QML:display/decoration/toolTip/timestamp
├── generation.py MODEL_BY_MODE / MODEL_PRO / FLASH_ONLY_ASPECT_RATIOS + ImageGenerationWorker
└── jewelry.py DEFAULT_JEWELRY_LIBRARY (8 类) + JewelryLibraryManager + PromptAssembler
core/runtime.py (新加,2026-05-09 抽出,2026-05-10 加日志兜底)
enable_crash_diagnostics(含 macOS DiagnosticReports 提示写入 crash_log.txt)
init_logging(用 TieredFsyncHandler 替代 RotatingFileHandler)
TieredFsyncHandler — WARNING+ 同步 fsync / INFO buffered
start_periodic_fsync — 1s 周期 daemon 线程兜底(INFO 最坏丢 1s)
log_system_info / cleanup_clipboard_tempfiles
flush_logs(升级为对所有 handlers 走 fsync)
get_crash_log_path
```
**image_generator.py.txt**(2026-05-10 重命名):
- git mv .py → .py.txt 保留全部历史;Python 不加载,PyInstaller 不扫
- 内容是老 QWidget UI 类(LoginDialog / ImageGeneratorWindow / DraggableThumbnail /
DragDropScrollArea / StyleDesignerTab + main())和顶部已搬走的工具函数副本
- 用户要求暂不删,留作扒老逻辑参考;task #19 真正"删掉"动作要等用户确认 QML 版稳定后
**外部消费**
- task_queue.py / temp_clean.py: `from core.generation import ImageGenerationWorker`
- bridges/*: `from core.history import HistoryManager / HistoryListModel`,
`from core.database import DatabaseManager`, `from core.jewelry import PromptAssembler`
- image_generator.py 顶部 `from core.* import *` 是 re-export,让 LoginDialog /
ImageGeneratorWindow 内部代码无感知(仍按原名字调用,任务 #19 一起删)
---
name: 项目入口、打包、远端
description: 主入口、PyInstaller spec、打包脚本、Git 远端等关键定位
type: reference
originSessionId: 21abab40-0d6c-449a-ae72-9ca03205f077
---
**主入口**
- `image_generator.py` `main()` (Phase 1-6 启动序列)
- QApplication 创建在 Phase 4,主题应用在 Phase 4 同步
- preflight 在 Phase 4.5,登录页在 Phase 6
**打包**
- spec 文件:`ZB100ImageGenerator.spec`(跨平台单一真相源)
- Windows 脚本:`build_windows.bat`(自动建 venv + pip install + pyinstaller,最后 pause)
- macOS 脚本:`build_mac_universal.sh`
- 直接命令:`.venv/Scripts/pyinstaller.exe ZB100ImageGenerator.spec`
- 产物:`dist/ZB100ImageGenerator/ZB100ImageGenerator.exe` (~134MB)
**Git 远端**
- `origin: http://gitlab.zb100.com:10080/chaijin/GoogleNanoBananaApp.git`
- 默认主分支 `master`
- Git 用户 `chaijin`
**用户管理工具**(管理员):
- `python user_util.py [add|list|disable|enable|reset] <username> [<password>]`
- 操作 MySQL `nano_banana_users` 表(hash SHA256)
- 配置从 `config.json``db_config` 字段读取
---
name: PyInstaller 打包配置 (task #19)
description: ZB100ImageGenerator.spec 当前形状 + 易踩坑(QML 路径 / Pillow / pathex)
type: reference
originSessionId: 21abab40-0d6c-449a-ae72-9ca03205f077
---
**spec 当前形状(2026-05-10)**
```python
a = Analysis(
['qml_poc/main_qml.py'], # 入口(不是老 image_generator.py)
pathex=[str(Path('.').resolve())], # 项目根,让 PyInstaller 找 audit_logger 等顶层模块
binaries=pil_native_libs, # Pillow .dylibs/.libs collect_dynamic_libs
datas=[
('config.json', '.'),
('qml_poc/qml', 'qml'), # ← 目标路径不带 qml_poc/ 前缀!
('jewelry_library.json', '.'),
] + 平台特定:
IS_WIN: ('zb100_windows.ico', '.')
IS_MAC: ('zb100_mac.icns', '.')
hiddenimports=[
'PySide6.QtQml', 'PySide6.QtQuick', 'PySide6.QtQuickControls2',
],
)
```
**关键坑(踩过的)**
1. **QML datas 目标路径必须是 `qml/` 不是 `qml_poc/qml/`**
原因:PyInstaller 把入口脚本 `qml_poc/main_qml.py` 平铺到 `_MEIPASS/main_qml.py`
(不是 `_MEIPASS/qml_poc/main_qml.py`),所以 `Path(__file__).parent / "qml"`
= `_MEIPASS/qml`。datas 写 `('qml_poc/qml', 'qml')` 才对得上。
2. **pathex 必须显式给项目根**
main_qml.py 顶部 `sys.path.insert(0, ROOT)` PyInstaller 静态分析看不到,
不给 pathex → 打包后 `from audit_logger import ...` ModuleNotFoundError。
3. **Pillow .dylibs 在 macOS 上必须收集**
spec 里有完整逻辑(collect_dynamic_libs + 显式枚举),输出量在 console。
macOS 验证打包要看 `[spec] 最终 Pillow 原生库数量: > 0`
**构建命令**
- Windows: `build_windows.bat``.venv/Scripts/pyinstaller.exe ZB100ImageGenerator.spec --noconfirm`
- macOS: `bash build_mac_universal.sh` (顺便打 .dmg,含 drag-to-install 布局)
**产物路径**
- Windows: `dist/ZB100ImageGenerator/ZB100ImageGenerator.exe` + `_internal/`
- macOS: `dist/ZB100ImageGenerator.app` + `dist/ZB100ImageGenerator.dmg`
**Windows 验证状态(2026-05-10)**
打包通过 → 启动 8 phases 全过 → QML 装载成功 → 进入主循环零警告。
日志在 `dist/ZB100ImageGenerator/logs/app.log`
**macOS 验证状态**:未做(开发机是 Windows)。明早起来 `bash build_mac_universal.sh`
跑一遍验证,重点看 .dmg 在 NAS / 邮件传输后 PIL/.dylibs symlink 是否完整。
**仍未删的旧入口**`image_generator.py.txt`(2026-05-10 git mv 改 .txt,不参与打包扫描)
用户要求确认 QML 版稳定后再删。
---
name: QML PoC 文件结构与设计系统
description: feat/ui-qml-poc 分支的 QML 代码组织、Theme 令牌、组件 API
type: reference
originSessionId: 21abab40-0d6c-449a-ae72-9ca03205f077
---
**目录结构**
```
qml_poc/
├── main_qml.py 入口:装配 5 桥 + audit_logger + history.refresh()
│ env 调试:QML_AUTO_LOGIN=1 / QML_DEBUG_TAB=0|1|2
└── qml/
├── qmldir 模块声明(singleton Theme 1.0)
├── Theme.qml Singleton 设计令牌
├── Main.qml ApplicationWindow(登录态切换尺寸 + 子页)
├── LoginScreen.qml 登录页(接 auth.login,错误展示 + busy)
├── MainWindow.qml 主窗口(下划线 TabBar + 任务队列 sidebar 接 taskQueue.model)
├── ImageGenTab.qml 图片生成 tab(参考图录入/收藏/下载/预览全接 imageGen)
├── HistoryTab.qml 历史记录 tab(ListView + 详情面板,接 history.model)
├── StyleDesignerTab.qml 款式设计 tab(8 字段 + 实时 Prompt 预览 + 生成)
└── components/
├── Card.qml 圆角 + 极淡边框,default property 子元素
├── PrimaryButton.qml pill 圆角 + Apple Blue + 120ms ColorAnimation
├── SecondaryButton.qml 圆角描边,hover 出灰底
├── ThemedTextField.qml 焦点 2px 蓝边动效
├── ThemedComboBox.qml 自定义 chevron + popup 圆角
└── CaptionLabel.qml 12pt DemiBold,组上方标签
```
**Theme.qml 暴露的 token**(singleton,QML import 后 `Theme.accent` 直接用):
- 颜色:bgCanvas/bgSurface/bgElevated/bgSubtle/bgHover, textPrimary/Secondary/Tertiary/OnAccent,
borderDefault/Strong, divider, accent/Hover/Pressed/Subtle, success/warning/danger
- 尺寸:radiusSm/Md/Lg/Pill, space1-6, controlHSm/Md/Lg, fontXs/Sm/Base/Lg/Xl/Xxl
- 字体:fontFamily(按 Qt.platform.os 自动选 SF Pro / Segoe UI Variable)
**关键设计规则**
- 所有圆角用 `Theme.radiusXxx`,不要硬编码
- 所有颜色用 `Theme.xxx`,不要硬编码 hex
- caption + control 用 ColumnLayout spacing 4-6,组之间 spacing 12-16
- Card 内 anchors.margins 用 `Theme.space4 (16)` 保证一致
- 主按钮(生成 / 登录 / 提交)一律 PrimaryButton(pill 圆角)
- 次按钮(添加 / 下载 / 取消)一律 SecondaryButton(描边圆角)
- 状态色(成功 / 失败 / 等待)用 `color: Theme.success/danger/warning`
**AppState QObject**(main_qml.py,作为 contextProperty 暴露):
- Property: `loggedIn` (bool), `currentTab` (int)
- Slot: `login(username, password) → bool`, `logout()`, `setTab(idx)`
- Signal: `loggedInChanged`, `currentTabChanged`
- 当前是 PoC 占位(login 接受任意非空),下一步要换成真实 db 认证
**业务桥层 TODO**(task #12):除 AppState,还需要暴露:
- `imageGen`:参考图列表 / 提示词 / 生成方法 / 进度信号
- `styleDesigner`:字段列表 / 词库管理 / Prompt 预览
- `history`:QAbstractListModel(复用现有 HistoryListModel)
- `taskQueue`:QAbstractListModel for 任务列表 + 取消方法
- `theme`:跟随系统 colorScheme,触发 QML Theme 切换
---
name: 跨平台 UI 一致性诉求
description: 用户强烈关注 macOS 和 Windows 两端的视觉与交互一致性,会优先选择能跨平台统一表现的方案
type: user
originSessionId: 21abab40-0d6c-449a-ae72-9ca03205f077
---
用户在选 UI 设计方向时明确说:"因为这个也跟我们的品牌元素的颜色契合"以及"可以统一 MacOS 与 Windows 的设计语言"。
在 dark mode 决策上他强调:"macos 会让软件跟随时间自由转换深浅主题,你可以不做深色主题,但是它变的时候就会巨丑巨难看,这个你应该知道的" — 说明他对系统级 OS 行为有深刻观察,期望应用级响应。
**应用方式**:未来给这个项目提任何 UI / 视觉建议时:
- 优先选 platform-neutral 的设计语言(Apple HIG / Material Design)而不是单平台专属(Fluent UI / iOS-specific)
- 涉及 OS 行为(系统主题、字体、缩放、HiDPI)必须双平台都验证
- 推荐第三方库前确认 Mac + Windows 都被支持
- 字体栈用回退(SF Pro → 苹方 → Segoe UI Variable → 微软雅黑)
---
name: 自主交付偏好(overnight 模式)
description: 用户接受 "睡前授权 → 醒来在公司 review 分支" 的工作模式,前提是有清晰兜底
type: user
originSessionId: 21abab40-0d6c-449a-ae72-9ca03205f077
---
2026-05-09 凌晨用户 brainstorming 完 UI 重设计后说:"我去睡觉了,你搞好把 memory 啥的 save 一下,推送到一个新分支,我明天在公司看新的分支里的内容就行了"。
明确认可的工作流:
1. 新分支隔离(不动 master)
2. 分支根目录留 OVERNIGHT_SUMMARY.md 供醒来看进度 + 验收清单
3. 多次小颗粒 commit(不是一个大 commit),便于 cherry-pick 或单点 revert
4. memory 同步保存
5. 推到 origin(GitLab)
**应用方式**:当他再次给出"你直接搞" / "我去睡了" 类指令,且任务符合"破坏性可控、有明确成功标准"时,可以走 overnight 模式:
- 先用 1-2 行确认范围 + 兜底(不动 master / 不破坏功能 / 仅视觉层 etc.)
- 不在中途追问澄清;保留为已知问题写在 SUMMARY 里
- 全程小步 commit,每个 commit message 精确到"做了什么 / 为什么"
- 顺手提交的 untracked 文件要在 SUMMARY 里说明
- 任务完成后必须打包/启动验证,让用户醒来直接能跑
**不适用场景**:涉及数据库变更、生产部署、密钥/凭证、第三方 API 协议变更 — 这些必须等用户在场。
......@@ -16,7 +16,8 @@ dist/
# IDE
.idea/
.vscode/
.claude/
.claude/*
!.claude/memory/
# Runtime / user data
logs/
......