未来日记 Future Journal

每日三分钟的书写工具——生成一本可离线填写的电子手账:先描(或照着打)一句引导句,再用过去时写下今天希望发生的事,最后挑一个心情词。49 天一轮、每周一个主题,含 49 句原创引导句库、质量闸门与页面回归测试。默认全离线、不发任何请求;可选的跨设备同步只在使用者自备后端配置后才会联网,日记正文在本地加密完才上传,所用同步组件按钉死版本 + SRI 完整性校验…

Bonnie Geng

@bonniegeng-max

Install

$ openclaw skills install @bonniegeng-max/future-journal

未来日记 · Future Journal

这是什么

一个单页、离线、手机优先的电子手账。每天三分钟,三步:

  1. 描摹一句浅灰引导句
  2. 用过去时写下今天希望发生的一件事
  3. 句末挑一个心情词

49 天为一轮,每周换一个主题(小确幸 → 身体节奏 → 关系 → 作品 → 金钱 → 空间 → 未来已来的我)。

方法与节奏参考山田弘美、滨田真由美所著的日记实践书。本工具不是该书的官方产品, 49 句引导句全部为原创,书中正文内容未被使用。

目录

文件作用
assets/index.html完整的手账页面。单文件、双击即可打开;不配同步时全程离线、不发任何请求
references/DESIGN.md暖纸手账设计系统 + 无障碍基线。改页面前先读这个
scripts/today.js算今天是第几天、本周主题、今天要描哪一句
scripts/check-prompts.js引导句质量闸门。改句库后必跑

以上 5 个文件(含 SKILL.md 自身)是 skill 的完整能力面,发放给别人的就是这些。

维护者工具(只在本仓库,不进 ClawHub 发布包)

文件作用
scripts/test-journal.js页面回归测试(数据不丢 / 键盘可达 / aria / 降级路径)

它依赖 jsdom(开发期依赖),属于「改这个仓库的人用的」,不是「装这个 skill 的人用的」。 不要把它打进 ClawHub 发布包 —— 维护者工具混进发布包被判过 suspicious / DO_NOT_INSTALL(见 MAINTAINING.md 里记的那次教训)。

改页面前后必跑的闸门

改句库:

node scripts/check-prompts.js

改页面逻辑或样式(仓库内,需要 jsdom):

node scripts/check-prompts.js
NODE_PATH=~/.workbuddy/binaries/node/workspace/node_modules node scripts/test-journal.js

两个都要退出码 0。

test-journal.js 覆盖三组:A 数据不丢(打字自动落盘 / 刷新还原 / 防抖窗口内切 tab)、 B 无障碍与键盘可达、C 拿不到 2D 上下文时的降级。

一个顺手的巧合:jsdom 不实现 canvas 2D,getContext('2d') 恒返回 null, 所以 C 组那条降级分支在 jsdom 里是被真实执行的,不是 mock 出来的。

交付页面给用户

把这个文件复制到用户指定的位置就行,它没有任何依赖:

cp assets/index.html ~/Desktop/未来日记.html
  • 双击用浏览器打开即可使用(file:// 协议下完整可用)
  • 手机端:把文件传到手机后用 Safari 打开
  • 数据存在浏览器 localStorage,键名 future-journal-v1

不要为它起本地服务器。 页面不依赖服务器就能完整使用(唯一的例外是可选同步), 起服务器只会带来额外故障点。

例外:想用「跨设备同步」就必须走线上网址 —— 见下面的「同步」一节。

回答「今天写哪一句」

node scripts/today.js 2026-09-17           # 以该日期为第 1 天,算到今天
node scripts/today.js 2026-09-17 --day 8   # 直接指定第几天

输出第几天、本周主题、今天要描的句子。用户想写在实体本子上时用这个。

改引导句库

49 句内嵌在 assets/index.html 的 <script id="prompt-data"> JSON 块里。 编辑后必须跑校验,退出码 0 才算通过:

node scripts/check-prompts.js

检查项:7 周 × 7 句的结构、重复句、将来时标志词、句末情绪词、单句长度 ≤ 30 字。

新句子的公式

时间锚点 + 一件具体的好事 + 完成态 + 句末情绪词
  • 对:今天的活一件件推进了,我很安心。
  • 错:我希望今天工作顺利。 ← 将来时,方法明确要求写过去时

中文没有时态标记,肉眼判断「是不是过去时」极不可靠,所以校验脚本用 禁止将来时标志词这条硬规则兜底。不要跳过。

数据在哪(重要,别承诺错)

默认情况下,页面数据只存在用户浏览器的 localStorage 里,skill 读不到。因此:

  • 用户问「我写了什么」→ 让他在页面的「回顾」页看,或从「设置」导出 JSON
  • 用户要备份 / 换设备(不登录)→ 页面「设置」里有导出与导入按钮
  • 清缓存 / 换浏览器,本机数据就没了 —— localStorage 的固有特性,务必如实告知
  • 「进度」「3 个想要的」都在同一个 state 里,导出 / 导入是整包,不拆分

跨设备同步是可选功能,且必须先发布到线上网址,见下一节。

同步(可选,需先发布到线上)

页面内置了「账号登录 + 端到端加密」的跨设备同步,但有三条硬约束:

  1. 只在线上网址可用。 判断依据是 location.protocol:file: 开头时页面会直接提示 「联网同步要在发布后的网址上用」,且不加载 SDK。双击本地文件打开 = 纯本地模式, 这和以前完全一样,不要因此认为页面坏了。

  2. 需要先激活云服务并建表。 表是 journal_sync,一人一行,只存密文:

    id BIGINT identity PK / owner_id TEXT DEFAULT auth.uid() UNIQUE
    cipher TEXT NOT NULL / updated_at TIMESTAMPTZ
    

    owner_id 由数据库默认值填充(TEXT,不是 uuid),客户端永远不要发这个字段。 RLS 要同时给 GRANT 和 POLICY 两道,缺一不可。

  3. 加密在浏览器里做,服务器只有密文。 PBKDF2 20 万次派生 AES-GCM-256 密钥, 密钥存本机 localStorage(fj-dk),不上传。所以同步密码丢了,云端那份就解不开, 本机记录不受影响 —— 这句话必须原样告诉用户,不能含糊。

其他约定:

  • 首次登录后要用户自己设「同步密码」,不自动生成 —— 他要能在别的设备上手输同一个
  • 推送是写完后延迟合并触发(schedulePush),不是每次按键
  • 冲突用 mergeStates 合并:按天取 at 较新的一条,「3 个想要的」按文本取并集
  • 云端写入走「先查后定 insert/update」,不用 upsert —— 因为 owner_id 是数据库默认值, 客户端不知道值,onConflict 无从下手

配置串(endpoint + publishableKey)存在本机 fj-cfg,用户换设备时粘贴一次即可。

⚠️ 不要把配置串写死进页面

看起来最顺手的「优化」是把 endpoint / publishableKey 直接内嵌进 assets/index.html, 这样用户就不用粘贴了。绝对不要这么做。

这个页面是要分发给别人的 skill 资产。一旦写死,任何装了这个 skill 的人打开页面都会连上 同一个后端——虽然 RLS 按用户隔离、彼此看不到内容,但陌生人会在号主的环境里注册账号、 消耗号主的配额。作者自己用着舒服的代价,是所有人的数据都涌向同一个人的账上。

配置串必须由使用者自己提供(fj-cfg 或设置页粘贴)。号主自己的那份也走同一条路—— 把配置串放在 skill 目录外面的文件里,这样它永远不会随 skill 分发出去。

这个 skill 会碰网络吗(如实清单)

发布审核最容易被卡的点不是「有联网」,而是声称与实际不符。所以这里列全, 一个不漏 —— 如实罗列比写得含糊安全。

行为何时发生去向传了什么
下载云同步 SDK仅当使用者自己粘贴了配置串、且页面从 http(s):// 打开cdn.jsdelivr.net 上的 @tencent-ai/workbuddy-cloud-sdk(版本号钉死 + SRI 完整性校验)只下载代码,不上传
发验证码 / 维持登录态使用者主动点登录使用者自己配置的后端邮箱、会话令牌(会离开设备)
上传日记使用者设了同步密码之后写入内容使用者自己配置的后端只有密文(在本机加密完才发)

这个表以外,没有别的:

  • 无统计、无埋点、无第三方字体 / 图标 CDN
  • 无硬编码的服务器地址或密钥:endpoint / publishableKey 一律由使用者自备
  • 不配同步时:页面从 file:// 打开,一个网络请求都不发(SDK 根本不加载)
  • scripts/today.js、scripts/check-prompts.js、references/DESIGN.md 全程纯本地

远端 SDK 必须钉版本 + 校验完整性

assets/index.html 里的 SDK_URL / SDK_SRI 是一对必须同进同退的常量:

var SDK_URL = '…/@tencent-ai/workbuddy-cloud-sdk@<具体版本>/lib/index.global.js';
var SDK_SRI = 'sha384-<该版本文件的 sha384>';

为什么这条是硬约束:这段远端代码和页面同源执行,能读到 localStorage 里的日记正文、 fj-cfg 里的后端地址与 key、以及导出的加密密钥 fj-dk。所以「加载哪一份」不能由 CDN 当下决定。用 @dev 这类漂移标签,等于审核时的那份代码和用户实际执行的可以是两份。

  • 改版本 → 必须用 hashlib.sha384 对新文件重算哈希并写回 SDK_SRI
  • 忘了改 → 同步会静默失效(onerror 会给出提示,不会留半截界面)
  • scripts/test-journal.js 的 D 组会卡住这个约定;@dev / @latest 一出现就失败
  • <script> 上的三个属性缺一不可:integrity(不放行改动过的字节)、 crossorigin="anonymous"(SRI 需要 CORS 取文件)、referrerpolicy="no-referrer"
  • 用 setAttribute 写而不是 s.integrity = …:后者的 IDL 反射不是所有引擎都有

那句话「加载远程脚本」确实是扫描器会多看两眼的模式——所以页面把它做成 必须先有人粘贴配置串才会发生,不是打开就跑。这是刻意的,改的时候别把它提前。

登录用的「账号」到底是什么(别答错)

不是 WorkBuddy 账号,也不是微信 / Google / Apple 之类 —— 是使用者自己那个云服务环境里的 终端用户账号,用邮箱当唯一标识,登录靠邮箱收到的 6 位验证码(也可以另外设密码)。

要说准的四点:

  • 邮箱只存在使用者自己配的那个后端里。分发 skill 的人拿不到任何东西。
  • 平台目前不提供匿名登录,所以只要想跨设备,邮箱就是唯一的身份选项,没有更轻的替代。
  • 让人填邮箱确实是审核会留意的一类信号(收集个人信息)。三条缓解都已到位: 完全可选(不登录 = 纯本地,功能一个不缺)、不是作者的服务器、日记内容端到端加密。
  • 反过来:配置串被别人拿到,他的邮箱就会进那个人的后端 —— 这是「配置串必须使用者自备」 的又一个理由。

页面的几个设计决定(改动前先理解)

  • 回看是延迟解锁的:满 7 天才打开上一周。这是刻意设计,不是 bug —— 方法本身建议不要天天回头翻。不要把解锁提前。
  • 「过一遍」有两种方式,不要拿掉任何一种:有触控笔走「用笔描」(canvas,只记录笔迹、 不做对错判定,描歪也算过);鼠标或触控板走「照着打一遍」(照着灰字逐字输入,打对变深、 打错标红但不拦)。两种都记 traced: true,traceMode 存方式。 触控板描摹是手眼分离,实测无法控制 —— 打字模式不是可选项,是必需项。
  • 笔迹按归一化坐标存储(0–1 区间),所以换设备、换屏幕尺寸后不会错位。
  • 输入框字号锁 16px:低于此值会触发 iOS Safari 自动缩放。不要改。
  • 写作框必须自动落盘,不许退回「点按钮才存」。 曾经只有「收好了」按钮会写 S.entries[day].text,而 renderToday() 又会用已存的值覆盖输入框 —— 于是「打了一半切个 tab」或「直接刷新」就把正在写的内容丢了。现在有 5 个落盘点:输入防抖 500ms、 切 tab、pagehide、beforeunload、页面隐藏。切 tab 前必须先 flushWrite() 再渲染。 描摹和心情词本来即时存,写作框过去是唯一的例外,别再制造这个例外。
  • 同步卡片的 4 个状态都在 renderSyncCard() 一处渲染:未填配置串 / 已填未登录 / 已登录待设同步密码 / 已连接。改同步相关文案只改这一个函数,别散到各处。
  • 可读性优先于纸感。 正文一律 ≥ 4.5:1、UI ≥ 3:1。唯一豁免是描摹引导字 (--trace,#ABA492,2.20:1)—— 它的身份是"盖着描的底稿"不是正文,真按 4.5:1 加深, 描上去的笔迹就和底稿分不清了。等价路径已备齐:打字模式用弱墨(4.55:1)、打印版加深到 6.71:1。 别在别处复用 --color-trace。
  • 状态不能只靠颜色。 已写 / 今天 / 未来的差异落到 disabled + aria-label 上; 心情词的选中态落到 aria-pressed;当前 tab 落到 aria-current。 读屏读不出"底色深浅",只写 CSS 等于没做。
  • 能力探测要在动 DOM 之前做。 典型例子:canvas.getContext('2d') 会返回 null (浏览器禁用画布、或 iOS Safari 画布上下文超限)。原来 openTrace() 直接 .font = … 就抛 TypeError,表现为点「用笔描」毫无反应。现在先探、拿不到就原地退回打字模式 并给一句人话。"点了没反应"比报错还差。
  • 焦点环必须是一层额外描边,不能只靠改边框色(触屏上看不见)。 全站 :focus-visible 用 2px 赤陶 + 2px offset;赤陶底上的按钮换成主墨描边。 不要写 outline: none。

不做什么

  • 不默认开同步。 不登录 = 纯本地,这条不能被改动破坏。同步必须由用户主动粘贴配置串开启
  • 不做推送提醒(页面关闭即止,做不到)。想每天被提醒,用 WorkBuddy 的定时任务推当天引导句
  • 不做暗色模式(纸质手账没有暗色版)
  • 不引入任何第三方图标库、字体 CDN、统计脚本 —— 离线可用是硬要求 (云同步 SDK 是唯一例外,且只在线上模式按需加载,版本钉死 + SRI 校验)

语言

本 skill 刻意面向中文使用者:界面文案、引导句、设计文档全部中文。这不是疏漏 —— 这套方法的语感(「过去时」在中文里靠词汇而非词形、「心情词」的调子)建立在中文上, 做双语反而两头都不到位。英文摘要放在 skill-card.md,供英文语境的人判断要不要装。

版本历史

  • 1.0.1 — 修掉 ClawHub 安全扫描的 suspicious:云同步 SDK 从漂移的 @dev 标签改为 钉死具体版本 + SRI 完整性校验(扫描报告 SDI-2);补齐「开启同步后哪些数据会离开设备」的 前端告知与文档说明(SDI-1);声明中文优先是有意为之(SQP-3)。回归测试新增 D 组,44 条全过。
  • 1.0.0 — 首次发布。

Top skills in this category