# 快捷指令：足迹 Atlas 年度导出

v15 从 Atlas 网页读取用户选择的年份，按 12 个月分别查询照片。全部含 GPS 的图片只传输文件名、带毫秒和时区的完整拍摄时间及坐标；可选每月沿时间线均匀生成最多 10 张 96px JPEG 缩略图。所有数据通过当前 Safari 页面分批传回，不经过服务器。

v15 在 v14 的 40 张批次、无 File Size 和单次 Base64 基础上，把 Location 提到每张照片的第一项读取：无 GPS 时不会再读取 Name、Date Taken、Latitude 或 Longitude。有 GPS 时使用一个内联 Dictionary 直接绑定动作输出。Date Taken 与阶段时间统一使用 `yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX`。

## 用户流程

1. iPhone Safari 打开 `https://atlas-photo.vercel.app`。
2. 在网站“快捷指令”区域选择年份，点击“准备批量导入”。
3. 点击 Safari `Share`。
4. 在下方 Actions 列表选择 `足迹 Atlas 年度导出`（安装 v15 后名称含 v15）。
5. 首次运行时允许照片访问和 Atlas 网页访问。
6. Shortcut 按月处理；每 40 个候选项子列表回传后等待网页完成 PIP、持久化和地图更新 ACK，再继续下一批。
7. 若启用代表图，每月额外回传最多 10 张缩略图。

## 输出格式

协议版本仍为 2。metadata 每 40 个候选项发送一个批次：

```json
{
  "runId": "2d1f…",
  "seq": "4",
  "protocolVersion": 2,
  "type": "metadata_batch",
  "year": "2025",
  "month": "8",
  "processed": "40",
  "monthTotal": "312",
  "candidateStart": "1",
  "candidateEnd": "40",
  "payloadPhotoCount": "36",
  "extractStartedAt": "2025-08-01T10:00:00.125+08:00",
  "extractEndedAt": "2025-08-01T10:00:03.375+08:00",
  "photos": [
    {
      "filename": "IMG_0365.HEIC",
      "dateTaken": "2025-08-03T14:21:08.123+08:00",
      "latitude": 31.2304,
      "longitude": 121.4737
    }
  ]
}
```

无 GPS 的照片不会传输。缩略图使用 `thumbnail_batch`，记录结构相同并额外包含 `thumbnail: data:image/jpeg;base64,...`。网站收到后立即转换为 JPEG Blob 存入 IndexedDB，不长期保存 Base64。

进度事件包括 `run_started`、`month_started`、`month_completed` 和 `run_completed`。

## 工作原理

1. 第一个 `Run JavaScript on Web Page` 读取网页中的年度配置和 12 个月边界。
2. 外层 `Repeat with Each` 遍历月份。
3. `Find Photos` 使用 `Date Taken is between MonthStart and MonthEnd`，结果存入 `MonthPhotos`。
4. 按 `ceil(月照片数 / 40)` 次取出子列表（`Get Items in Range`），内层 `Repeat with Each` 最多遍历 40 个 `Photo`。
5. 内层先提取 Location；仅在有值的条件内读取 Name、Date Taken、Longitude 和 Latitude，并用一个内联 Dictionary 生成行；metadata 与缩略图链都不读取 File Size，也不累积整月 `GpsPhotos`。
6. 每批 ACK 后清空 `Rows` 与 `PhotoBatch`。
7. 若启用代表图，每个均匀采样点向后尝试最多 `min(step, 10)` 张，索引不越月末且各采样窗口不重叠；无 GPS、空候选或空缩略图输出会继续下一张，不建立整月 `GpsPhotos`。Shortcuts 没有已验证的 Continue on Error 参数，因此宿主直接抛出的图片动作错误仍会终止运行，不伪装为可重试。
8. 网站 Worker 执行国家/一级行政区 PIP；无合法 ISO 时使用 geoBoundaries `shapeID`，不伪造 ISO。
9. IndexedDB 持久保存足迹和缩略图，MapLibre 先合并相同坐标再建立 cluster。
10. 每条 protocol v2 消息携带 `runId + seq`；JavaScript 在 append 前监听 ACK 属性，匹配成功后进入 40ms settle，错误可立即抢占，并以低频轮询兜底。20 秒 deadline 覆盖整批网页处理且 settle 期间仍保持有效；超过 deadline 会 NACK 并停止本次 Shortcut，避免宿主无限等待。只有真正调用 completion 时才原子完成并清理 observer/计时器。网页忙或消息 JSON 无法解析时会利用节点中的安全 run/seq 上下文立即 NACK。

## 大图库

- 约 100 张：通常可以直接处理。
- 约 1000 张：按月查询、每 40 候选项子列表回传，避免整月大列表迭代变慢。
- 约 1 万张：metadata 可继续分批；缩略图仍最多 120 张。保持 Safari/Shortcuts 前台运行。
- 中断后网页记录已完成月份；再次准备同一年时从未完成月份继续。中断月份会重跑，但已保存指纹会去重。
- 新 v3 指纹使用 `filename + 完整 dateTaken + GPS`，不依赖 File Size。新 payload 会从记录字段生成 v14 日期级 v2 alias（不解析 `|` 分隔文本），命中后把 record 与 thumb 先复制到 v3 key、确认后再删除旧 key，并同步内存索引；旧 v1/File Size 迁移仍兼容。若旧键清理失败，恢复前会按 canonical/alias 确定性去重并保留可用缩略图，避免 records、国家和州省重复计数；待清理数量进入恢复摘要。迁移可重试，失败保留旧数据，普通本地文件指纹不变。

## 本机性能诊断

网页根据每条消息的到达时间和 ACK 时间，自动分离：

- Shortcut/iCloud/照片 metadata 的真实墙钟间隔
- Shortcut 阶段：照片属性提取、单次 JSON/Base64、桥接等待
- 网页 JSON 校验、缩略图存储、Worker/PIP、IndexedDB 和地图更新时间

诊断数据仅保存在本机。`shortcut-stage-v15` 日志从每个消息节点读取 `encodedAt` 和实际 Base64 字符数；v15 payload 明确标记 `optimizationProfile=v15-loop-optimized`，避免把旧 v13/v14 消息误标。日志记录 `batchSize`、`fileSizeIncluded=false`、推导的 `gpsSkipped`，以及 `thumbnailAttempts`、`thumbnailRetries`、`thumbnailFailures` 的逐月和总计；不含照片、坐标或文件名。

## 安装与权限

优先用 iCloud 分享链接安装。当前链接已写入 `public/shortcuts/icloud-url.txt`，生产环境另有 `SHORTCUT_ICLOUD_URL` 覆盖它，网站因此显示「一键添加到快捷指令」。iPhone 上会打开 `shortcuts://shortcuts/<id>`，再在快捷指令 App 里点添加。Apple 仍会显示一次确认，无法完全跳过。链接缺失时才回落到 `.shortcut` 文件。

快捷指令必须开启：

- `Show in Share Sheet`
- `Receive: Safari Web Pages`
- iPhone `Settings → Apps → Shortcuts → Advanced → Allow Running Scripts`
- iPhone `Settings → Apps → Shortcuts → Advanced → Allow Sharing Large Amounts of Data`

Apple 关于密码、通讯录和信用卡的提示是所有网页 JavaScript 的通用安全警告。本快捷指令只读取 Atlas 页面中约定的配置元素，并写入隐藏数据桥，不读取页面表单。

## 发布

`shortcuts/VERSION` 是唯一版本源。修改 Shortcut 生成输入或发布产物后，运行：

```bash
npm run release-shortcut
```

发布命令分别比较工作流、验证器与签名工具摘要。生成器、v11 基线或 `compile-shortcut.sh` 变化时自动递增整数版本；artifact 断言或 Shortcut 结构验证器变化时只重建并重新验证当前版本，不升版；仅 `sign-shortcut.sh` 变化时同样按当前版本重建。签名后先用现有 shortcut-cli 验签、反编译，再对真实 artifact 执行同一结构断言，成功后才复制公开产物。构建、签名或结构检查失败会从持久化快照恢复旧版本及全部产物。

成功后，签名文件会写入 `public/shortcuts/atlas-photo-export.shortcut`，并复制到 `~/Downloads/足迹 Atlas 年度导出-vN.shortcut`。重复运行且内容未变化时不会升版，但仍会确保 Downloads 中的当前版本文件存在。升版后要在 Shortcuts App 重新生成 iCloud 分享链接，写入 `public/shortcuts/icloud-url.txt` 并更新 `SHORTCUT_ICLOUD_URL`，否则一键添加仍指向旧版本。

## v15 真机 A/B（4 月对照）

基线：同一 iPhone、同一网络、同一未完成年份的 **4 月**，缩略图开关与 v11 基线一致。

1. 安装网站提供的 v15 快捷指令，停用旧版本以免选错。
2. 在网页准备同一年份（保留已完成月份，确保 4 月会重跑）。
3. 运行 Shortcut，导出 `atlas-performance-*.json`。
4. 比较 4 月 `metadata_batch`：
   - p50 / p95（`shortcutGapMs` 或阶段 `photoExtractMs`）
   - 首三分之一 vs 末三分之一耗时斜率
   - 总 Shortcut / 网页时间、`payloadPhotoCount`、实际 `payloadEncodedChars`、NACK/漏项/重复
5. 与同设备 v14 对照 GPS 跳过率、每张 metadata 动作成本、缩略图命中率及 ACK 超时；不要把网络或照片 iCloud 下载波动归因给单项优化。

## v15 发布说明

v15 继续以 `shortcuts/VERSION` 作为唯一版本源。审查阶段 VERSION 保持 14；批准后发布命令才自动升至 15，并依次完成生成、结构校验、签名、公开产物与 Downloads 文件更新。
