# personal-track(个人习惯追踪上下文) 在 DSH Web GUI 里运行的个人习惯追踪插件:一个看板记录每日习惯,一个统计面板回顾历史。 ## Language ### 习惯形态(五形态) **打卡 Check**: 每日有固定次数上限的习惯,点击即记录时间,无其他数据。 _Avoid_: 签到、check-in 项 **计数器 Counter**: 跨日持久的整数,以 ±n 步进,不随日重置。 _Avoid_: 累加器 **洗涤 Washing**: 一种**会话**习惯:记录一次洗衣的件数;"洗完"动作同时扣减待洗存量并追加本条会话。 **槽位 Slot**: 每日固定数量的命名位置,每槽至多一条记录(时间 + 可选属性)。 _Avoid_: 定时项 **会话 Session**: 每日 0..n 条(或 ≤1)的记录,每条携带指标(时长、数量、心率等)。 _Avoid_: 活动项 **登记 Registry**: 跨日持久的条目集合,带状态流转,没有"每日"概念;由**媒体**与**任务**两个具体登记项实例化。 _Avoid_: 清单、列表 **媒体 MediaLog**: 日志形的**登记**项:记录看过的电影与读过的书籍,事后补记,带看完状态与评分。 _Avoid_: 收藏、资源库 **任务 Task**: 任务形的**登记**项:先建条目后完成,带可选课程分类、截止日与**任务进度**;作业是其初始用途。 _Avoid_: Todo、待办清单(与 DSH 会话级 Todo 冲突)、作业表 **逾期 Overdue**: **任务**的派生态(非存储状态):已过截止日且未完成。 **任务进度 Task Progress**: **任务**的完成量 `current/total`,跨日持久;`total` 可省略(退化为普通勾选项)。 _Avoid_: 进度(单独使用) **任务状态 Task State**: 由**任务进度**派生的三态:待办 / 进行中 / 完成;不存储状态本身,只存完成时间。 **配速 Pace**: 每公里用时(分′秒″/公里,越小越快),由**会话**的时长与里程派生,不落库。 **心率-配速曲线 HR–Pace Curve**: 跑步专属统计视图:每次跑步一个点(x = **配速**,y = 平均心率),范围内连成曲线,用于观察同配速下的心率变化。 _范围_: 仅跨次趋势;单次跑步内的秒级采样(设备导入)明确不在范围内。 ### 挂在形态之上的概念 **习惯日 Habit Day**: 以凌晨 4 点为界的个人一天,是所有每日数据(打卡/槽位/会话/目标)的归属单位。 _时区_: 界线按**本机时区**计算(Config 可留固定时区覆盖);界线时刻默认 4 点,Config 可调。 _Avoid_: 自然日、日历日 **夜段 Night Tail**: 自然日 00:00 至界线(4 点)之间的时段,归属前一个**习惯日**;看板在此期间以"周X夜"标注当前习惯日。 **目标 Target**: 某个**会话**习惯在单个**习惯日**内的期望值,由当日会话累计得出**目标进度**;分两类:**数量目标**(按指标累计,如背单词的新词/复习数)与**存在目标**(当日存在至少一条会话即达成,如多邻国 ≥1 节)。 _归属_: 每个**习惯日**持有一份当日快照;新建时从最近一个已有快照的习惯日**向前继承**,无任何历史快照时用 Config 默认值;快照一旦存在即不被继承逻辑改写(补卡不影响之后的日子)。 _权重_: **数量目标**的指标可以不等价。背单词的一**复习**词按 1/5 个**新**词计入**目标进度**——分子分母同权,所以"100%"仍然等于"目标达成";`超额`仍是原始词数,不是加权值。 **格 Cell**: 看板完成条(`n/N`)的原子单位,**取值为 0..1 的小数**而非布尔。8 个习惯格(洗漱 2、洗澡 1、三餐 3、背单词 1、多邻国 1)是固定底座;**当日到期的任务各再占一格**,所以分母是 `8 + 当日到期任务数`。 _小数_: 多数格只有 0 与 1(做了/没做),两类格天生是分数:**背单词**填的是**目标进度**比率,**任务**填的是它自己的完成比例(`current/total`)。计数保留一位小数,形如 `5.3/9`。 _归属_: 任务格挂在**截止日**那一天(任务没有习惯日,截止日是唯一的链接);格取的是任务**当前**状态——今天做完今天的作业就填满今天的格,与补改会话即时改当天进度同理。 **到期带 Due Band**: **任务**截止日的紧迫程度,按距当前**习惯日**的整天数分档:过去 = 逾期(红)、当天(红)、3 日内(橙)、7 日内(黄)、更远(无强调)。纯显示派生,不存储。 **超额 Surplus**: **目标**完成率封顶 100% 之后多出的部分,统计中单独展示。 _Avoid_: 溢出 **连续 Streak**: 单个习惯连续达标的**习惯日**天数,分习惯各自计算;派生统计,补卡后即时重算。 **完美日 Perfect Day**: 8 个**格**全部满的**习惯日**;只作计数统计,不参与**连续**。 **空白天 Empty Day**: 无任何记录的**习惯日**,统计中视为未达标(**连续**中断)。 **补卡 Backfill**: 对历史**习惯日**的打卡/槽位/会话记录做补录或修改。 **备份 Backup**: 整个域的一份可移植 JSON 文件,只含**事实**(习惯日文档、洗衣存量、**登记**条目),不含任何派生值——导入后所有**格**、比率、**连续**、**逾期**从记录重新算出,文件里不存在能与数据本身矛盾的字段。 _两种导入方式_: **合并**(文件中的记录按同键覆盖,本地多余的保留)与**覆盖**(文件里没有的本地记录一并删除);文件先整体校验,任何一条不合法即整份拒绝。 ## Relationships - 一个**习惯**恰好属于一种形态;存储形状、统计口径由形态直接推导 - **目标**依附于**会话**(只有会话型习惯设目标),作用于单个**习惯日** - **打卡**、**槽位**、**目标**参与完成率统计;**会话**求和统计;**登记**按条目与状态统计 - **计数器**与**登记**不随**习惯日**重置,其余四者每日重新开始 - **登记**恰好有两个具体实例:**媒体**(日志形)与**任务**(任务形) - **任务**在**截止日**当天计入看板**格**数:每有一条当日到期的任务,就多一格,按**任务进度**填分;**完成 ≠ 删除**(完成后记录与 `completedAt` 都保留,状态由进度派生),只有硬删除才让它连同那一格消失 - 任务的显示顺序:未完成在前、已完成后,各自按**截止**升序,无截止日的排在各自组末 - **目标**分两类:**数量目标**(背单词)与**存在目标**(多邻国 ≥1 节即达成) - **逾期**由截止日与状态实时推导;迟到的完成只记完成时间 - 不做循环**任务**(周常重复等明确出范围) - **登记**条目采用**硬删除**:删除即连同统计一起消失,不留"已删除"态;完成才进统计,删除不进 - 无"删除整日"操作:**习惯日**的条目清空即该日不存在(空 = 无键) - 编辑历史日与编辑今天走同一条幂等写路径,`date` 只是参数,不存在"锁定过去" - **连续**是派生统计:补录**空白天**后即时重算(忘了记 ≠ 忘了做) - 统计面板默认范围为**当前习惯日**所在自然月;快捷切换含本周(周一起算)/ 近 30 天 / 本季度 / 自定义 - **任务状态**完全由**任务进度**派生,不存储;取消完成即清空完成时间,保持"完成"是当前事实 - **任务进度**填的是**截止日**那一**格**的分数(`current/total`,因此常见 0.25 这样的值),但它本身不是**目标进度**;输入仍是整数,小数只出现在**格**的分数与计数上 - **目标进度**随当日**会话**变化而变,**任务进度**跨日持久——两者同名不同物,不得混用 - **时区**只影响新记录归属哪个**习惯日**键;历史键是纯日期,不受时区变化影响 - 跑步**会话**记录时长(默认 30 分钟,可改)、里程与平均心率;**配速**由此派生 - 统计面板中唯一的手写 SVG 图表是**心率-配速曲线**;其余用数字卡、CSS 热力网格与 CSS 条形 - 洗衣 = **计数器**(待洗存量)+ **洗涤会话**(历史):"洗完"扣存量并记会话;"攒衣服"只加存量;**修正**只调存量不留会话 - 原则:**会话**记录"发生的事",**计数器**只存"现在的值";校准不产生事件、不进统计 - **目标**的进度与总时长由当日**会话**累计派生,**会话**是唯一事实源;补改会话即改进度 - **目标**按**习惯日**快照存储:改目标只影响之后的新日子,不重写历史完成率;当日改快照即时重算 - **目标**完成率封顶 100%,超出部分记为**超额** - 看板默认显示**当前习惯日**(= 界线推导);**夜段**内即前一个自然日 - 不能导航到未来**习惯日**;"回到今天"跳转到**当前习惯日** - **备份**是**事实**的搬运,不是状态的搬运:导出后再导入得到的统计与原样一致,靠的是重算而非存下的数字 - **任务**的**截止**初值取**当前习惯日**所在周的周日(周一为一周之始,与统计"本周"同口径);它只是表单初值,可改可清空 - **会话**的**时间**初值取当前本机时刻——即宿主本会盖上的那个值;前端并不因此获得构造时刻的权力,送出的仍是 `HH:mm`,归属哪一天由宿主换算 ## Example dialogue > **Dev:** "跑步每天至多一次,要不要单独立一种'每日单次'形态?" > **Domain expert:** "不用——它是**会话**,只是 n 上限为 1,指标是里程和心率。" ## Flagged ambiguities - "Todo" 一词被提议用于**登记**中的任务形条目 —— 已裁定:因与 DSH 会话级 Todo 冲突,规范术语为**任务**;"Todo"列入 Avoid - **登记**曾被当作单一同质集合 —— 已裁定:内部分化为**媒体**(日志形)与**任务**(任务形)两个实例 - 多邻国先被裁定"无目标",后修正为持**存在目标**(≥1 节即计 1 **格**)—— 最终口径:目标有两类,多邻国属存在目标 - "进度"曾被单独使用、含义不定 —— 已裁定拆为**目标进度**(每日派生)与**任务进度**(跨日持久)两个独立概念,单用视为歧义 - **格**曾被裁定为布尔(做了/没做),且**任务**明确不计入分母 —— 均已推翻:**格**取 0..1 的分数(背单词填加权**目标进度**、任务填自身完成比例),当日到期任务在**截止日**各占一格,分母 = `8 + 当日到期任务数`;理由是"今天完成了多少"这个问题本身是连续的,布尔答不了 - **任务进度**的输入仍是整数(词、篇、题是离散的)—— 小数只属于**格**的分数与计数,不属于输入 - **任务**曾被裁定为两态(待办/完成)—— 已被否决:改为由**任务进度**派生的三态,且进度是可量化的 `current/total`