1. 产品概述

产品名称:Inserable

目标平台:macOS

技术栈:Swift + SwiftUI

核心功能:管理 Hugo 博客的图片资源,自动重命名并生成 Markdown 格式的图片引用文本

解决的问题:Hugo 博客中图片管理混乱、命名不规范、手动编写图片引用繁琐

2. 核心概念

2.1 层级结构

Inserable

├── 根目录 1 (Root Folder 1)
│   ├── Session A (文件夹 A)
│   │   ├── formatA_001.png
│   │   ├── formatA_002.png
│   │   └── ...
│   ├── Session B (文件夹 B)
│   │   └── ...
│   └── ...
└── 根目录 2 (Root Folder 2)
├── Session X (文件夹 X)
│   └── ...
└── ...

2.2 核心关系

概念说明
根目录 (Root Folder)用户的 Hugo 图片存储路径,如 /Users/alex/myblog/static/images/。最多支持 2 个。
Session与根目录下的一个文件夹形成 1:1 绑定关系。Session 名 = 文件夹名。
图片Session 文件夹内的图片文件,按统一格式命名。

2.3 命名规则

图片命名格式<formating_1>_<###>.<ext>

  • <formating_1>:用户为每个 Session 指定的命名前缀
  • <###>:三位数补零序号(001, 002, 003…),按图片创建时间排序,Session 内独立计数
  • <ext>:统一转换为 PNG 格式

Markdown 输出格式![<formating_1>_<###>.png](/images/<session_folder>/<formating_1>_<###>.png)

3. 数据完整性机制

3.1 镜像备份

项目说明
触发时机每 5 分钟自动执行一次
存储位置Application Support/Inserable/Mirror/
保留版本仅保留最新一份
镜像内容根目录结构(Session 文件夹名 + 文件夹内图片文件名列表)

3.2 完整性验证

验证内容

  • 根目录级别:Session 文件夹名称 + 数量
  • Session 级别:图片文件名称 + 数量

验证时机

  • 程序启动时
  • 每次镜像时(每 5 分钟)
  • 用户手动点击 “IN SYNC” 按钮时

不一致处理 (Integrity Error): 检测到任何外部修改触发 ERROR,锁定受影响区域。用户需在弹窗选择以下操作之一:

选项行为
Restore from Mirror (恢复镜像)将磁盘文件恢复到镜像记录的状态 (撤销外部修改)。
Delete Session (删除 Session)移除该 Session,文件夹移动到回收站。
Re-sync (Accept Changes) (重新同步)以当前磁盘状态为准,更新元数据和镜像。

Re-sync (Accept Changes) 详细逻辑

  1. 扫描:扫描当前 Session 文件夹。
  2. 分类处理
  • 合规文件 (符合 <formating_1>_<###>.png):保留,更新元数据。
  • 非合规图片强制标准化。视为"新添加图片",分配新序号(当前最大序号+1),转 PNG,重命名,originalPath 标记为 “unknown (re-synced)"。
  • 非图片文件:弹窗提示用户手动移除,或选择自动移动到回收站。
  1. 更新状态:更新 nextSequenceNumber 并立即写入磁盘。
  2. 快照:生成新镜像快照并解除锁定。

3.3 允许的例外

  • .DS_Store 文件:自动忽略
  • 同名文件内容替换:只验证文件名,不验证文件内容(允许用户在 Preview 中编辑图片后保存)

4. 根目录管理

4.1 根目录要求

  • 最多支持 2 个根目录
  • 根目录内只允许存在:文件夹 或 空
  • 如检测到非文件夹文件(如散落的 .png),提示用户清理后才能继续

4.2 根目录初始化流程


用户选择文件夹

↓

扫描文件夹内容

↓

[存在非法文件?] → 是 → 提示用户清理 → 阻止继续

↓ 否

为每个子文件夹创建对应 Session(名称 = 文件夹名)

↓

按字母顺序排列 Sessions

↓

根目录初始化完成(Sessions 处于"待标准化"状态)

4.3 根目录更换

  • 更换根目录 = 将所有 Session 文件夹迁移到新位置
  • 旧根目录的所有数据(Sessions、镜像)清空后重建

5. Session 管理

5.1 Session 创建方式

方式 A:点击 “New Session” 按钮

  1. 在当前根目录下创建空文件夹,默认名 newsession<#>
  2. Session 列表中出现新条目
  3. 用户可重命名 Session(同步重命名文件夹)
  4. Session 处于空状态,等待用户拖入图片

方式 B:拖入文件夹到 “New Session” 按钮

  1. 暂存路径:程序读取并暂存该文件夹内所有图片的原始绝对路径。
  2. 用户输入:弹窗要求输入 <formating_1>
  • 用户取消 → 操作中止,清除暂存数据。
  1. 复制文件:复制文件夹到当前根目录。
  2. 原子性检查 (Atomic Safety Check)
  • 验证目标文件是否存在且大小 > 0。
  • 验证失败 → 回滚(删除已复制的不完整文件),中止流程,保留原文件夹。
  1. 标准化:执行标准化流程(使用暂存的原始路径写入元数据)。
  2. 源文件处理:仅在步骤 4 和 5 全部成功后,根据设置决定是否将原文件夹移动到回收站。

5.2 Session 标准化流程

当用户首次向空 Session 拖入图片,或从文件夹创建 Session 时: 弹窗要求输入 <formating_1> ↓ [用户取消?] → 是 → 操作中止,Session 保持原状 ↓ 否 按创建时间排序所有图片 ↓ 转换所有图片为 PNG 格式 ↓ 重命名:<formating_1>_001.png, <formating_1>_002.png, … ↓ 生成变更日志(首次标准化时) ↓ 写入磁盘:立即写入 nextSequenceNumber 和元数据 ↓ 创建镜像快照

5.3 Session 删除

  • 用户删除 Session → 文件夹重命名为 <原名>_Inserable_conflict 后移动到回收站
  • Session 数据从 Inserable 中移除

6. 图片操作

6.1 添加图片

操作方式:拖入图片到已选中的 Session 区域 处理流程

  1. 暂存路径:程序读取并暂存图片的原始绝对路径。
  2. 前置检查:如果 Session 未标准化 → 先触发标准化流程。
  3. 复制文件:复制图片到 Session 文件夹。
  4. 原子性检查 (Atomic Safety Check)
  • 验证目标文件是否存在且大小 > 0。
  • 验证失败 → 回滚,中止流程,保留原图。
  1. 标准化处理
  • 转换为 PNG 格式。
  • 分配下一个可用序号。
  • 重命名为 <formating_1>_<###>.png
  • 将暂存的原始路径写入元数据。
  1. 立即持久化立即将更新后的 nextSequenceNumber 写入磁盘,不等待程序关闭。
  2. 源文件处理:仅在上述所有步骤成功后,根据设置决定是否将原图移动到回收站。
  3. 镜像:更新镜像。

6.2 查看图片

  • 点击图片行(COPY 按钮以外区域)→ 调用系统 Preview.app 打开图片

6.3 图片格式转换

优先级:PNG > JPG > WEBP 规则:所有图片统一转换为 PNG 格式

7. 输出功能

7.1 单条复制

点击图片行的 COPY 按钮 → 复制该图片的 Markdown 文本到剪贴板。

7.2 全部复制 (Copy All)

首次标准化后的输出(包含变更日志): Markdown 引用 + Original Path 日志。 后续复制的输出(简洁版): 仅 Markdown 引用列表。

7.3 变更日志备份

存储位置Application Support/Inserable/past_change_log/ 文件命名<session_folder>_<dd-mm-yyyy>_change_log_backup.txt 内容:首次标准化时的完整输出(包含 Original Path)

8. 全局设置

设置项类型说明
根目录 1 路径路径选择第一个 Hugo 图片根目录
根目录 2 路径路径选择第二个 Hugo 图片根目录
删除原图Checkbox拖入图片后是否将原图移动到回收站
删除原文件夹Checkbox拖入文件夹后是否将原文件夹移动到回收站
深色/浅色模式ToggleUI 主题切换

9. UI 布局

9.1 主界面结构


┌─────────────────────────────────────────────────────────────┐
│ ● ● ●                                                       │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────┐   ┌─────────────────────────────────────┐  │
│  │ ROOT_FOLDER │   │         SESSION NAME                │  │
│  │ (可点击切换) │   │  ┌─────────────────────────────────┐│  │
│  ├─────────────┤   │  │ COPY │      pic_001.png        │││  │
│  │             │   │  ├─────────────────────────────────┤│  │
│  │ SESSION 1   │   │  │ COPY │      pic_002.png        │││  │
│  │ SESSION 2   │   │  ├─────────────────────────────────┤│  │
│  │ SESSION 3   │   │  │ COPY │      pic_003.png        │││  │
│  │ ...         │   │  │ ...                             ││  │
│  │             │   │  │                          [滚动条]│  │
│  │             │   │  └─────────────────────────────────┘│  │
│  │             │   └─────────────────────────────────────┘  │
│  │ ┌─────────┐ │                                            │
│  │ │NEW SESS.│ │                                            │
│  │ └─────────┘ │                                            │
│  └─────────────┘                                            │
├─────────────────────────────────────────────────────────────┤
│ [IN SYNC] [SETTINGS]                        [COPY ALL]      │
└─────────────────────────────────────────────────────────────┘

inverable_deve_pic_002.png

inverable_deve_pic_003.png

inverable_deve_pic_004.png

9.2 状态指示

状态显示说明
正常IN SYNC(绿色)数据完整性正常
同步中旋转图标正在执行镜像操作
错误ERROR(红色)检测到完整性问题

9.3 Integrity Error 界面

  • 主界面模糊化,显示 “INTEGRITY ERROR”。
  • 弹窗选项:
  1. Restore from Mirror: 恢复到上次同步状态。
  2. Re-sync (Accept Changes): 接受当前磁盘状态(强制标准化非合规文件)。
  3. Delete Session: 删除此 Session。

10. 首次启动流程

启动 Inserable → 检测配置 → (未配置)显示空界面 → 用户点击 Root Folder → 选择文件夹 → 初始化根目录 → 完成。

11. 错误处理

11.1 根目录错误

错误类型触发条件处理方式
非法文件根目录下存在非文件夹文件阻止选择,提示用户清理
Session 名称冲突创建新 Session 时名称已存在弹窗询问是否替换(旧文件夹移动到回收站)
完整性错误Session 文件夹名称或数量变化锁定整个根目录,要求修复

11.2 Session 错误

错误类型触发条件处理方式
非法文件Session 文件夹内存在非图片文件提示用户清理或移动到回收站
完整性错误图片文件名或数量变化锁定该 Session,要求修复

12. 数据存储

12.1 存储位置

~/Library/Application Support/Inserable/ ├── config.json (全局设置) ├── sessions/ (Session 元数据) ├── mirror/ (镜像数据) └── past_change_log/ (变更日志备份)

12.2 Session 元数据结构

{
  "sessionName": "Banff_Travel",
  "formattingPrefix": "banff_picture",
  "nextSequenceNumber": 5,
  "lastUpdated": "2026-01-14T15:30:00Z",
  "isStandardized": true,
  "files": [
    {
      "currentName": "banff_picture_001.png",
      "originalName": "IMG_1190.jpeg",
      "originalPath": "/Users/alex/Downloads/IMG_1190.jpeg",
      "sequenceNumber": 1,
      "addedAt": "2026-01-14T15:25:00Z"
    }
  ]
}

13. MVP 范围外(未来扩展)

  • 支持超过 2 个根目录
  • Session 昵称
  • 自定义图片格式/Alt text
  • 批量重命名已有图片
  • 多版本镜像历史

推荐项目文件结构 (Project Structure)

为了确保开发过程清晰且符合上述设计逻辑,建议采用以下 Swift 项目结构:

Inserable/
├── App/
│   ├── InserableApp.swift       # 入口
│   └── AppDelegate.swift        # 生命周期处理
├── Model/
│   ├── AppConfig.swift          # 全局设置模型
│   ├── Session.swift            # Session 元数据模型 (Codable)
│   ├── ImageFile.swift          # 图片文件模型 (Codable)
│   └── IntegrityStatus.swift    # 枚举:Synced, Syncing, Error
├── ViewModel/
│   ├── RootViewModel.swift      # 根目录管理,Session 列表逻辑
│   ├── SessionViewModel.swift   # 具体 Session 的图片操作逻辑
│   └── SettingsViewModel.swift  # 设置页面逻辑
├── View/
│   ├── MainLayout/
│   │   ├── SidebarView.swift    # 左侧 Session 列表
│   │   └── ImageListView.swift  # 右侧图片列表
│   ├── Components/
│   │   ├── ImageRowView.swift   # 单行图片组件
│   │   └── StatusBadge.swift    # In Sync/Error 指示器
│   ├── Overlays/
│   │   ├── IntegrityErrorView.swift # 错误处理弹窗 (含 Re-sync)
│   │   └── NewSessionPopup.swift    # 创建/标准化弹窗
│   └── SettingsView.swift
├── Service/
│   ├── FileSystemManager.swift  # 核心:处理复制、删除、原子性检查
│   ├── ImageProcessor.swift     # 处理格式转换 (PNG)、重命名
│   ├── PersistenceManager.swift # 处理 JSON 读写 (立即写入逻辑)
│   ├── MirrorManager.swift      # 处理 5分钟定时镜像
│   └── IntegrityChecker.swift   # 处理比对逻辑、Re-sync 扫描
├── Utils/
│   ├── Extensions.swift         # String, URL, Date 扩展
│   ├── Logger.swift             # 简单的调试日志
│   └── PathHelper.swift         # 处理 App Support 路径
└── Resources/
    ├── Assets.xcassets
    └── Info.plist

LOG

项目开发开始

Jan14, 1:06am

项目雏形完成

Jan 14, 1:28am

存在bug:

  1. 镜像还原过程存在问题,没有还原到目标位置。这整个功能完全是废掉的。镜像还原后,会出现幽灵文件夹。具体看bug4。

  2. 初始化文档只有最初对第一个session进行标准化完成后的第一次复制,才会复制成功且输出格式为:


![apple_vision_pro__001.png](/images/Apple_Vision_Pro_Article/apple_vision_pro__001.png)

Original Path:

(/Users/kircerta/Desktop/Inversable_TESTING/MYBLOG_static/images/Apple_Vision_Pro_Article/Body_1.png)

============================================================

![apple_vision_pro__002.png](/images/Apple_Vision_Pro_Article/apple_vision_pro__002.png)

Original Path:

(/Users/kircerta/Desktop/Inversable_TESTING/MYBLOG_static/images/Apple_Vision_Pro_Article/Body_2.png)

============================================================

![apple_vision_pro__003.png](/images/Apple_Vision_Pro_Article/apple_vision_pro__003.png)

Original Path:

(/Users/kircerta/Desktop/Inversable_TESTING/MYBLOG_static/images/Apple_Vision_Pro_Article/Body_3.png)

其余时间失败,在其它sesssion初始化时,输出均为:

![appleAppArticle_001.png](/images/appleAppArticle/appleAppArticle_001.png)
![appleAppArticle_002.png](/images/appleAppArticle/appleAppArticle_002.png)
![appleAppArticle_003.png](/images/appleAppArticle/appleAppArticle_003.png)
![appleAppArticle_004.png](/images/appleAppArticle/appleAppArticle_004.png)
  1. New Session 创建问题严重,具体表现为:
  • 创建后会莫名其妙出现重命名。重命名应该只有右键时才会出现。
  • 拖入文件夹后,session的创建要么是有延迟,要么逻辑有问题
  • 其它问题待发掘
  1. 存在幽灵文件夹,不存在,但确实在session中。这个状态点击列表里的文件名不会出现预览,因为文件根本不存在。但也没有出现报错。手动sync也没有出现integrity报错,这点非常奇怪。

  2. 改动root文件及/删除root文件夹路径时,完全没有出现警告提醒。这是会直接把整个文件夹里session记录全部改掉的操作,完全需要用户二次确认。

inverable_deve_pic_005.png

  1. delete original image after import 选项toggle后完全无效,原因未知。
  • folder toggle选项暂未测试,但推测也有问题
  1. Root Folder 切换按钮是坏的,点击会跳出选择root folder的选项。如果只有一个root folder没有第二个,按钮应该不允许点击。
  • 并且,root folder现在1和2可以选择完全相同的folder,这是不应该出现的现象。
  • root folder 2 根本就没读取进去。

inverable_deve_pic_005.png

  1. 我可以直接打开为Standardized的folder。关闭窗口后右侧内容并未被高斯模糊屏蔽并显示类似"finish set-up in pop-up window, reactivate pop up window here(此按钮后续加入)。

inverable_deve_pic_005.png

  1. session文件夹重命名貌似有延迟,但重命名后引导正确。

待改进内容

  1. UI风格还是要参考Texmorph去做。目前看上去不太像SwiftUI的风格,反而像是Flutter.

  2. sync 按钮动画有问题,会导致整个画面glitch,不符合期望,但不影响使用。

  • 问题调查完发现是因为sync的速度太快,
  1. 弹出窗口内容显示不全,虽然目前不影响信息传递,但依然需要改进。

inverable_deve_pic_006.png

  1. session设置完成后,初始设置无法被修改。期望里应该是可以被修改,然后一键应用。应用后留下一个修改log(格式参考开发文档1.0 alpha)的。现在没有这个功能。

  2. standardize按钮显示有问题

  3. 如果session少了东西,整个session就必须被delete,这个目前来看逻辑没问题。但也许有更好的方案也说不定?

Jan14 开发总结

  1. UI布局没问题,细节带打磨。在布局不改变的基础上,应用Texmorph的设计语言也许是个不错的思路。

  2. 依然有很多隐藏bug没测试,上述只是问题的一小部分。不过,程序能够运行这一点已经足够令人欣慰。


[to be continued…]


Jan16, 1:56am 于 Robart Commons

Inserable Alpha 0.0.2 开发日志与路线图总结:One-Tap Mapping 模块

日期:2026-01-16 模块:One-Tap Mapping Utility 状态:功能竣工 (Feature Complete) / 核心 Bug 已修复


1. 今日工作日志 (Dev Log)

今日主要完成了 One-Tap Mapping 功能的从无到有,并解决了 macOS 沙盒机制 (Sandbox) 与文件系统交互的一系列棘手问题。

修复与改进清单

A. 核心逻辑构建

  • 功能实现:实现了从“散乱 Root Folder”到“Session Folder”的自动化整理逻辑。
  • 文章级去重 (Deduplication):对于同一篇文章多次引用同一张图的情况,实现了智能识别,不再创建 img_1.png, img_2.png 副本,而是复用文件。
  • 智能格式保留:确立了“白名单”机制。jpg/gif 保持原样(防止动图变静帧或体积膨胀),仅将 heic/webp 等格式标准化为 PNG。

B. 权限与沙盒攻坚 (The Sandbox Battles)

这是今日最关键的 Debug 环节。

  • 问题:App 只有读取权限,无法删除源文件,导致整理后出现“旧文件残留 + 新文件冗余”的双重占用。
  • 修复 1 (显式授权):在 UtilityView 中移除了文件夹路径自动预填。强制用户手动点击 NSOpenPanel 选择文件夹。这是获取 macOS 安全作用域写入权限 (Security-Scoped Write Permission) 的唯一正解。
  • 修复 2 (权限锁):在 Manager 代码中引入 startAccessingSecurityScopedResource(),在操作期间显式持有文件锁,防止系统拦截写操作。
  • 修复 3 (输出重定向):将日志、备份和未使用的图片统一移动到 Downloads 文件夹,规避了 Desktop 文件夹的高级权限限制。

C. 算法健壮性提升

  • URL 解码修复:修复了 Markdown 中 image%202.png 无法匹配本地 image 2.png 的 Bug。现在算法会先进行 URL Decode 再匹配文件。
  • Gatekeeper 兼容:将物理删除 (removeItem) 改为移入废纸篓 (trashItem)。这不仅符合 macOS 安全规范,也为用户提供了最后的“后悔药”。

2. 未来改进建议 (Future Improvements)

虽然功能已跑通,但为了追求极致体验,以下方向值得在后续迭代中考虑:

Technical Debt & Optimization

  • 性能优化:目前采用单线程遍历。若 Root Folder 图片数量破万,可能会阻塞 UI。建议未来引入 Swift TaskGroup 进行并行文件处理。
  • 事务回滚 (Atomic Operations):目前依靠 .bak 文件备份。未来可引入文件操作的事务机制,一旦中间出错,自动还原所有移动过的文件(类似数据库 Rollback)。

UX/UI Enhancements

  • 结果导向:操作完成后,可以在 UI 上增加一个 “Show Log Folder” 按钮,直接打开 Finder 定位到下载目录的日志位置。
  • 忽略列表:允许用户配置 .inignore 文件,指定某些散落在根目录的文件不被 One-Tap Mapping 处理(例如固定的 Logo 或背景图)。

3. 用户说明书执行方案 (Documentation Plan)

根据讨论,我们放弃在 App 内硬编码说明书,转为**“静态网站 + 客户端入口”**的轻量化方案。

执行步骤

  1. 内容生产 (Content)

    • 来源:直接复用你现有的 content_zh/posts/Developer/Inverable开发文档.md
    • 完善:将今天 One-Tap Mapping 的操作逻辑(特别是“必须手动选择文件夹以授权”这一点)补充进去。
  2. 部署 (Deployment)

    • 使用 Hugo 将其编译为静态网站。
    • 部署到你的 GitHub Pages 或个人服务器。
  3. 客户端集成 (App Integration)

    • 入口:在 App 顶部菜单栏 Help -> Inserable Documentation
    • 代码实现CommandGroup(replacing: .help) { Button("文档") { NSWorkspace.shared.open(URL(string: "你的网址")!) } }
  4. 兜底提示 (Fallback)

    • UtilityView 的文件夹选择器下方,保留一行小字提示:“注意:请务必手动点击选择按钮,以授予 App 清理旧文件的权限。”

4. 结语

Inserable 的 One-Tap Mapping 模块现已具备了**“零残留、零误判、有迹可循”**的工程标准。今天的 Debug 有力地证明了在 macOS 开发中,顺应 Sandbox 规则比对抗它更有效。

Mission Accomplished.


以上Gemini总结。