@senguoyun/dsh-arkme 0.1.64 → 0.1.65

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -25,7 +25,7 @@ Flutter `ChatPhasedRequestClient.fetchChatSessionStatistics` 使用:
25
25
 
26
26
  选择日期后直接调用已有的 `/api/v1/chat/timeline/around`,保留完整 owner ID,获取目标消息前后内容。没有逐页扫描全部聊天记录,也不借用个人日历推算群聊历史。
27
27
 
28
- 发给自己和主题继续使用现有日历适配器,保留 DSH 输入过滤、隐私锁过滤及子主题去重。Flutter 主题的 `/api/v1/topics/display/statistics` 是另一种统计范围,未用它直接替换上述过滤后的统计。
28
+ 发给自己、未分类和主题使用 Record 新视图日历接口,过滤、可见性和子主题去重由后端统一完成,不再逐日拉取正文重算数量。月份只请求 `buckets/query`,点击日期请求 `records/query` 的 `oldest_first=true, limit=1` 第一页再定位。完整对接说明见 [发给自己日历接口升级](self-calendar-view-integration.md)。Flutter 主题的 `/api/v1/topics/display/statistics` 是另一种统计范围,未直接替换。
29
29
 
30
30
  ## 缓存与边界
31
31
 
@@ -0,0 +1,49 @@
1
+ # 发给自己日历:后端视图接口对接
2
+
3
+ ## 范围与依据
4
+
5
+ 2026-09-18 对接同事提供的《发给自己日历:最终实现方案与对接说明》,并核对 Record 后端 master `e130682`(实现 `e2c4d9b`)的 `gin/api/calendar_bucket.go` 和 `gin/compound/calendar_view.go`。
6
+
7
+ 仅升级发给自己全部、未分类、主题及可见后代;不改独立全局日历,不改私聊/群聊统计,不将仍在讨论的互动日历方案当作已发布接口。
8
+
9
+ ## 请求和展示
10
+
11
+ | 当前范围 | view_scope_kind | bucket_scope_kind | 额外参数 |
12
+ |---|---:|---:|---|
13
+ | 发给自己全部 | 1 | 1 | 无 |
14
+ | 未分类 | 2 | 1 | 无 |
15
+ | 主题及后代 | 3 | 2 | 签名 sourceRef 对应的根主题 UID;include_descendants=true |
16
+
17
+ - host 验证账号和签名 sourceRef 后生成参数,不转发调用者任意指定的 owner、视图枚举或主题 UID。
18
+ - 每个加载月份一次 `/api/v1/calendar/buckets/query`。多月可见窗口与相邻月份预取各自按需查询;“一次”不是整个日历生命周期只有一次网络请求。
19
+ - 不逐日读取内容、不在客户端枚举子主题,不重算或加减服务端已过滤的 count。后端负责首页准入、归属、隐私和去重。不能以旧来源分类过滤已经进入首页的聊天来源记录。
20
+ - 明确保持自然日策略 `{kind:1}` 与当前 IANA 时区,两条接口相同。校验响应时区和策略;本次不增加 Smart/固定切日设置,也不将响应额外字段原样传回请求。
21
+ - 点击日期请求 `oldest_first=true, limit=1`,第一页无游标,找到当天最早可见记录后沿用原定位流程。月份 `first_*` 表示最新记录,不当作当天最早定位锚点或第一页游标。
22
+ - 当天分页只使用响应游标对,排序方向、范围、时区、策略不变。验证重复条目、排序、游标推进与可见内容,不静默截掉异常条目后假装成功。
23
+ - 复用记录内容/媒体/历史头像投影、定位加载提示、取消重试与请求竞态保护;界面布局和已保存日历高度不变。
24
+ - SDK 的 calendarBuckets/calendarRecords 支持同一签名 sourceRef;不另建一套统计逻辑或新增 DSH 工具。
25
+
26
+ ## 失败、缓存和回滚
27
+
28
+ - 新视图仅接受业务 `code=0`,HTTP 200 不代表业务成功。缺少数组、日期/计数/has_records/策略不一致不作为空月份缓存。
29
+ - 40001/50001 与视图响应校验失败展示错误,清除当前过期摘要与锚点,允许显式重试;不自动改用旧总日历、不无限重试业务错误。
30
+ - 服务层保留原 60 秒、账号/范围/时区/月份/修订隔离的摘要缓存与并发合并;新视图不叠加传输缓存。浏览器缓存升级为 v2,隔离旧客户端重算结果,键包含自然日语义;仅存摘要,不存正文或媒体地址。
31
+ - 数据、归属及隐私变化继续复用现有失效通知;账号切换和失效后的晚返回不会覆盖当前界面。跨页不是数据库快照,并发修改后应重新加载。
32
+ - `selfCalendarViewsEnabled` 默认 true;部署方可明确设置 false,恢复原客户端精确过滤统计路径(更慢),不是失败降级。生产发布前仍需后端确认所有实例兼容,单次联调不能证明全实例部署完成。
33
+ - 回滚日历缓存语义时应重启预览/客户端并刷新统计。旧路径按天分页精确过滤,日期起点必要时扫描当天,不返回未过滤全局数字。
34
+
35
+ ## 验证要求
36
+
37
+ 自动化覆盖三种范围、单请求统计、空月、缓存隔离/失效、稳定正反序分页、最早记录定位、保留合法聊天来源记录、沿用历史内容投影、错误码、异常响应、完整游标、旧契约与私聊/群聊不回归。
38
+
39
+ 真实联调另外检查月份加载、早期日期定位、未分类/主题切换。构建和 mock 测试不等同于生产性能或全部后端实例验收。
40
+
41
+ ## 本次验证结果(2026-09-18)
42
+
43
+ - 全量测试:628 个测试文件通过、8 个跳过;7,464 项测试通过、11 项跳过,无失败。类型检查及最终构建通过。
44
+ - 本地 Web 预览已更新,使用已有账号验证全部、未分类、主题及可见后代,三种范围均返回业务 code=0、自然日策略 kind=1。
45
+ - 本次账号的月份请求样本耗时 105—698 ms,覆盖有数据月份及空月。打开和滚动日历只产生按月统计请求,没有逐日拉取正文来计算数量;这些耗时是后端请求样本,不是端到端响应承诺。
46
+ - 全部范围点击 8 月 12 日,最早记录请求约 249 ms;主题范围点击 9 月 3 日约 199 ms。两次均以 oldest_first=true、limit=1 查询,界面显示定位中间态后完成定位。
47
+ - 主题重新打开日历时仍聚焦 9 月 3 日;切回全部后恢复其原阅读日期 8 月 12 日,已检查选中日期状态。
48
+ - 本次验证只更新本地 Web 预览插件,保留更新前备份;未修改原生客户端安装包,不代表原生安装包或生产发布验收。仍需在实际发布环境验收后端全实例兼容性。
49
+ - 推送前再次刷新远端,确认基于最新 master `886ecbdd`;复跑 9 个相关测试文件、252 项测试及类型检查,全部通过。
package/lib/client.js CHANGED
@@ -1306,12 +1306,15 @@ window.__ModuleLoader__.load({
1306
1306
  return await this.call("calendar.buckets", {
1307
1307
  startDate: options.startDate,
1308
1308
  endDate: options.endDate,
1309
+ ...options.sourceRef === void 0 ? {} : { sourceRef: options.sourceRef },
1309
1310
  ...options.timezone === void 0 ? {} : { timezone: options.timezone }
1310
1311
  }, options.signal);
1311
1312
  }
1312
1313
  async calendarRecords(options) {
1313
1314
  return await this.call("calendar.records", {
1314
1315
  bucketDate: options.bucketDate,
1316
+ ...options.sourceRef === void 0 ? {} : { sourceRef: options.sourceRef },
1317
+ ...options.oldestFirst === void 0 ? {} : { oldestFirst: options.oldestFirst },
1315
1318
  ...options.timezone === void 0 ? {} : { timezone: options.timezone },
1316
1319
  ...options.limit === void 0 ? {} : { limit: options.limit },
1317
1320
  ...options.cursor === void 0 ? {} : { cursor: options.cursor }
@@ -5951,7 +5954,7 @@ window.__ModuleLoader__.load({
5951
5954
  qrcode.stringToBytes;
5952
5955
  //#endregion
5953
5956
  //#region package.json
5954
- var version$3 = "0.1.64";
5957
+ var version$3 = "0.1.65";
5955
5958
  //#endregion
5956
5959
  //#region src/client/arkme-billing.css?inline
5957
5960
  var arkme_billing_default = ".arkme-billing-backdrop {\n z-index: 120;\n backdrop-filter: blur(3px);\n background: #181b223d;\n place-items: center;\n padding: 28px;\n display: grid;\n position: fixed;\n inset: 0;\n}\n\n.arkme-billing-recharge-dialog, .arkme-billing-payment-dialog {\n color: #20232c;\n background: #fff;\n border: 1px solid #dedfe3;\n border-radius: 20px;\n width: min(620px, 100%);\n max-height: min(720px, 100vh - 56px);\n overflow-y: auto;\n box-shadow: 0 24px 72px #191c232e;\n}\n\n.arkme-billing-payment-dialog {\n width: min(420px, 100%);\n}\n\n.arkme-billing-dialog-header {\n border-bottom: 1px solid #ececef;\n justify-content: space-between;\n align-items: flex-start;\n gap: 18px;\n min-height: 78px;\n padding: 20px 22px 16px;\n display: flex;\n}\n\n.arkme-billing-dialog-header h2 {\n margin: 0;\n font-size: 18px;\n font-weight: 600;\n line-height: 25px;\n}\n\n.arkme-billing-dialog-header p {\n overflow-wrap: anywhere;\n color: #8a8e96;\n margin: 4px 0 0;\n font-size: 12px;\n line-height: 18px;\n}\n\n.arkme-billing-dialog-header > button {\n color: #777c85;\n width: 32px;\n height: 32px;\n font: inherit;\n cursor: pointer;\n background: none;\n border: 0;\n border-radius: 9px;\n flex: none;\n place-items: center;\n font-size: 22px;\n display: grid;\n}\n\n.arkme-billing-dialog-header > button:hover {\n color: #20232c;\n background: #f3f3f5;\n}\n\n.arkme-billing-dialog-body {\n padding: 18px 22px 22px;\n}\n\n.arkme-billing-dialog-balance {\n border-bottom: 1px solid #ececef;\n grid-template-columns: minmax(0, 1fr) auto;\n align-items: center;\n column-gap: 12px;\n min-height: 48px;\n padding: 0 2px 16px;\n display: grid;\n}\n\n.arkme-billing-dialog-balance > span {\n color: #8a8e96;\n grid-column: 1;\n font-size: 11px;\n line-height: 16px;\n}\n\n.arkme-billing-dialog-balance > strong {\n grid-column: 1;\n font-size: 22px;\n font-weight: 600;\n line-height: 28px;\n}\n\n.arkme-billing-dialog-balance > button, .arkme-billing-error > button {\n color: #626772;\n height: 30px;\n font: inherit;\n cursor: pointer;\n background: #fff;\n border: 1px solid #dfe1e5;\n border-radius: 9px;\n grid-area: 1 / 2 / span 2;\n padding: 0 12px;\n font-size: 11px;\n}\n\n.arkme-billing-products-section, .arkme-billing-payment-section {\n padding-top: 17px;\n}\n\n.arkme-billing-products-section h3, .arkme-billing-payment-section h3 {\n color: #747984;\n margin: 0 0 11px;\n font-size: 12px;\n font-weight: 500;\n line-height: 18px;\n}\n\n.arkme-billing-product-grid {\n width: 100%;\n}\n\n.arkme-billing-product-grid > button {\n color: #20232c;\n min-width: 0;\n min-height: 62px;\n font: inherit;\n text-align: left;\n cursor: pointer;\n background: #fff;\n border: 1px solid #dfe1e5;\n border-radius: 11px;\n align-content: center;\n gap: 4px;\n padding: 9px 11px;\n display: grid;\n}\n\n.arkme-billing-product-grid > button:hover {\n background: #f8f8f9;\n border-color: #cfd2d8;\n}\n\n.arkme-billing-product-grid > button.is-selected {\n border-color: #75839e;\n box-shadow: 0 0 0 1px #75839e;\n}\n\n.arkme-billing-product-grid > button > span {\n white-space: nowrap;\n text-overflow: ellipsis;\n font-size: 12px;\n font-weight: 500;\n line-height: 17px;\n overflow: hidden;\n}\n\n.arkme-billing-product-grid > button > strong {\n font-size: 14px;\n font-weight: 600;\n line-height: 19px;\n}\n\n.arkme-billing-payment-actions {\n flex-wrap: wrap;\n gap: 10px;\n display: flex;\n}\n\n.arkme-billing-payment-actions > button {\n color: #333842;\n min-width: 154px;\n height: 38px;\n font: inherit;\n cursor: pointer;\n background: #fff;\n border: 1px solid #dfe1e5;\n border-radius: 10px;\n justify-content: center;\n align-items: center;\n gap: 8px;\n padding: 0 14px;\n font-size: 12px;\n display: inline-flex;\n}\n\n.arkme-billing-payment-actions > button:hover {\n background: #f7f7f8;\n border-color: #cfd3da;\n}\n\n.arkme-billing-payment-actions > button:disabled, .arkme-billing-product-grid > button:disabled {\n opacity: .48;\n cursor: default;\n}\n\n.arkme-billing-platform-icon {\n flex: none;\n width: 18px;\n height: 18px;\n}\n\n.arkme-billing-platform-icon.is-alipay {\n color: #1677ff;\n}\n\n.arkme-billing-platform-icon.is-wechat {\n color: #07c160;\n}\n\n.arkme-billing-state {\n color: #858992;\n margin: 12px 0 2px;\n font-size: 12px;\n line-height: 18px;\n}\n\n.arkme-billing-error {\n color: #b54841;\n justify-content: space-between;\n align-items: center;\n gap: 12px;\n margin-top: 10px;\n font-size: 12px;\n line-height: 18px;\n display: flex;\n}\n\n.arkme-billing-error.is-purchase {\n justify-content: flex-start;\n}\n\n.arkme-billing-payment-content {\n text-align: center;\n justify-items: center;\n gap: 10px;\n padding: 28px 24px 18px;\n display: grid;\n}\n\n.arkme-billing-payment-content > .arkme-billing-platform-icon {\n width: 38px;\n height: 38px;\n margin-bottom: 4px;\n}\n\n.arkme-billing-payment-content > img {\n border-radius: 10px;\n width: 210px;\n height: 210px;\n}\n\n.arkme-billing-payment-content > strong {\n font-size: 14px;\n font-weight: 600;\n line-height: 21px;\n}\n\n.arkme-billing-payment-content > p {\n color: #858992;\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n}\n\n.arkme-billing-payment-content.is-paid > strong {\n color: #16794f;\n font-size: 18px;\n line-height: 25px;\n}\n\n.arkme-billing-payment-content.is-paid > .arkme-billing-error {\n justify-content: center;\n}\n\n.arkme-billing-payment-content.is-crediting > strong {\n color: #335f9e;\n}\n\n.arkme-billing-dialog-actions {\n flex-wrap: wrap;\n justify-content: center;\n gap: 9px;\n padding: 0 22px 20px;\n display: flex;\n}\n\n.arkme-billing-dialog-actions > button, .arkme-billing-primary-action {\n color: #3c424d;\n height: 34px;\n font: inherit;\n cursor: pointer;\n background: #fff;\n border: 1px solid #dfe1e5;\n border-radius: 9px;\n padding: 0 14px;\n font-size: 12px;\n}\n\n.arkme-billing-dialog-actions > .arkme-billing-primary-action, .arkme-billing-payment-content > .arkme-billing-primary-action {\n color: #fff;\n background: #20232d;\n border-color: #20232d;\n}\n";
@@ -126989,7 +126992,7 @@ body[data-ds-dark-theme] .arkme-mention-all-avatar [data-arkme-theme-image="dark
126989
126992
  loading: false,
126990
126993
  error: ""
126991
126994
  };
126992
- const PREFIX = "dsh-arkme:calendar-months:v1:";
126995
+ const PREFIX = "dsh-arkme:calendar-months:v2:";
126993
126996
  const LIMIT = 48;
126994
126997
  const RETAIN_MS = 7 * 864e5;
126995
126998
  const FRESH_MS = 6e4;
@@ -126998,6 +127001,7 @@ body[data-ds-dark-theme] .arkme-mention-all-avatar [data-arkme-theme-image="dark
126998
127001
  query.timezone,
126999
127002
  query.startDate,
127000
127003
  query.endDate,
127004
+ "natural-day:1",
127001
127005
  ...query.timezoneOffsetMillis === void 0 ? [] : [query.timezoneOffsetMillis]
127002
127006
  ]);
127003
127007
  const storage = () => {
@@ -127127,8 +127131,9 @@ body[data-ds-dark-theme] .arkme-mention-all-avatar [data-arkme-theme-image="dark
127127
127131
  }
127128
127132
  }).catch((error) => {
127129
127133
  if (!current()) return;
127130
- const code = error?.body?.code;
127131
- const accessDenied = code && /privacy|login-required|account-changed|source-invalid|ref-invalid|not-found/.test(code);
127134
+ const failure = error;
127135
+ const code = failure?.body?.code ?? failure?.code;
127136
+ const accessDenied = code && /privacy|login-required|account-changed|source-invalid|ref-invalid|not-found|calendar-view|40001|50001/.test(code);
127132
127137
  if (accessDenied) {
127133
127138
  this.saved.delete(identity$1(query));
127134
127139
  this.persist();
@@ -128429,6 +128434,7 @@ body[data-ds-dark-theme] .arkme-mention-all-avatar [data-arkme-theme-image="dark
128429
128434
  try {
128430
128435
  const page = await callArkme("calendar.records", {
128431
128436
  ...sourceRef === void 0 ? {} : { sourceRef },
128437
+ ...sourceRef === void 0 ? {} : { oldestFirst: true },
128432
128438
  bucketDate: key,
128433
128439
  timezone,
128434
128440
  limit: 1
@@ -130684,7 +130690,8 @@ body[data-ds-dark-theme] .arkme-mention-all-avatar [data-arkme-theme-image="dark
130684
130690
  (selection.momentAnchor !== void 0 ? Promise.resolve(selection.momentAnchor) : selection.anchor !== void 0 ? Promise.resolve(selection.anchor) : withArkmeReadDeadline((signal) => callArkme("calendar.records", {
130685
130691
  sourceRef,
130686
130692
  ...selection,
130687
- limit: 1
130693
+ limit: 1,
130694
+ oldestFirst: true
130688
130695
  }, signal), current.controller.signal).then((page) => page.items[0])).then((item) => {
130689
130696
  if (job.current !== current || current.controller.signal.aborted) return;
130690
130697
  if (item === void 0) throw new Error("这一天没有可查看的记录,请重新选择日期");