Skip to content

feat(serve): 演示时钟可冻结、可步进、可跳到下一处变化 - #33

Open
modusensus wants to merge 1 commit into
mainfrom
feat/demo-timeline-controls
Open

modusensus wants to merge 1 commit into
mainfrom
feat/demo-timeline-controls

Conversation

@modusensus

Copy link
Copy Markdown
Collaborator

--demo 的时间轴以前只能自己走:要看到故障态得干等 140 秒的健康段,而"先看一眼前端长什么样"
的人多半已经在等第一轮了。现在页脚有一行 演示时钟(说明现在看的是哪一刻)加五个按钮:
冻结 / −10s / +10s / 下一处变化 / 回到现在

1)锚点是,不是控制端点

时间轴本来就是"某个时刻的纯函数",所以让那个时刻从请求里来:?at=<秒>。这个服务刻意是
只读的(POST 一律 405),加一个会改时钟的控制端点会往里面塞进第一份可变状态和第一条非幂等
路由。走查询串就没这个问题:

  • 服务端不持有任何可变状态——没有要抢的锁,没有"谁在什么时候读到半个状态";
  • 一个链接就是某一刻:截图把地址栏一并截下来就能自证,转发给谁都是同一刻;
  • 四个端点仍然幂等;实时的 ponte serve 直接忽略它(真实状态只有"现在",?at=abc
    负数、无穷、超大值一律回退到实时,上限一天)。

为此 provider 契约改为接收这一次请求的查询串(ponte.serve.Provider),实时那条明确
忽略它——并且有一条测试把"实时路径不许认锚点"钉死(反向对照确认过它会红)。

2)"下一处变化"跳的是看得见的变化

不是下一个阶段边界。web 的"断线 → 重连退避"两段判定与两列端口完全一样,落在那里的按钮看
起来就是坏的——所以判据跟着看板走(_visible_state),跳过看不出来的边界。

测试不是镜像那句判据(那样两处一起错还互不告白),而是拿渲染给看板的 payload 重算一遍:
到目标之前的每半秒都必须与现在一模一样,到目标那一刻必须不同。

3)冻结冻的是数据,不是刷新

页面照旧每 5 秒"已更新",只是时刻不再推进——所以冻结的看板是"显然冻住了",而不是和挂掉的
标签页长得一样。冻结状态下步进保持冻结:逐个状态看过去正是这个功能的用法。页脚会写明
三种状态:实时 / 已固定 / 已冻结

顺带

  • payload 的演示标记从 "demo": true 长成一个对象(at / anchored / next_at /
    next_profile),仍是 payload 里唯一多出来的键,所以契约测试没变松。时刻与"下一处
    变化"必须与看板同时到达客户端,否则按钮会基于过期的时刻跳。
  • 页脚的 status.json / metrics / healthz 链接带上锚点,免得看板钉在某一刻而 JSON
    回答"现在"。

验证

  • 449 测试、ruff、mypy(本机与 --platform linux)、smoke 全绿;
  • 四处反向对照都确认新测试抓得住:next 退化成"下一个边界"、handler 不传查询串、锚点不
    校验、实时路径认锚点;
  • 浏览器里逐个按钮点过(真服务、回环套接字):冻结 7 秒后读数与 URL 都不动而"已更新"
    照常走;步进 +10s;两次"下一处变化"分别落在 db 的"端口没监听 → 断线"与 web 的断线
    上;回到现在?at= 从地址栏去掉。

一句如实说明

tests/test_serve.py / test_main.py / test_integration_serve.py 里原有的 provider 全部
改成了接收查询串(机械改动,lambda: …lambda _query: …)——这是契约变更的代价,值得
单独看一眼。另外提交正文里记了:这次 git push 走不通(本机代理对 github.com 的 TLS 隧道
被重置,api.github.com 正常),所以提交是用 Git Data API 重建的,blob / tree / commit
三层 SHA 与本地逐一比对相同
0c15487),与一次正常 push 的结果没有区别。

--demo 的时间轴以前只能自己走:要看到故障态得干等 140 秒的健康段,而"看一眼前端长什么
样"的人多半已经在等第一轮了。现在页脚有一行"演示时钟"(说明现在看的是哪一刻)加五个
按钮:冻结 / −10s / +10s / 下一处变化 / 回到现在。

三个决定值得记下来。

1) 锚点是**读**,不是控制端点。时间轴本来就是"某个时刻的纯函数",所以让那个时刻从请求里
来:?at=<秒>。服务端因此仍然不持有任何可变状态——没有要抢的锁、没有非幂等路由、没有"谁在
什么时候读到半个状态",而且一个链接就是某一刻(截图把地址栏一并截下来就能自证)。为此
provider 契约改为接收这一次请求的查询串(ponte.serve.Provider),实时那条明确忽略它:真实
状态只有"现在",?at=abc、负数、无穷、超大值一律回退到实时(上限一天)。

2) "下一处变化"跳的是**看得见**的变化,不是下一个阶段边界。web 的"断线 → 重连退避"两段
判定与两列端口完全一样,落在那里的按钮看起来就是坏的。所以判据跟着看板走
(_visible_state),跳过看不出来的边界。测试不是镜像那句判据,而是拿渲染给看板的 payload
重算一遍:到目标之前的每半秒都必须与现在一模一样,到目标那一刻必须不同。

3) 冻结冻的是**数据**,不是刷新。页面照旧每 5 秒"已更新",只是时刻不再推进——所以冻结的
看板是显然冻住了,而不是和挂掉的标签页长得一样。冻结状态下步进保持冻结:逐个状态看过去
正是这个功能的用法。页脚写明三种状态:实时 / 已固定 / 已冻结。

顺带:payload 的演示标记从 "demo": true 长成一个对象(at / anchored / next_at /
next_profile),仍是 payload 里唯一多出来的键,契约测试因此没变松;时刻与"下一处变化"必须
与看板同时到达客户端,否则按钮会基于过期的时刻跳。页脚的 status.json / metrics / healthz
链接带上锚点,免得看板钉在某一刻而 JSON 回答"现在"。

验证:449 测试、ruff、两个平台的 mypy、smoke 全过。四处反向对照都确认新测试抓得住:next
退化成"下一个边界"、handler 不传查询串、锚点不校验、实时路径认锚点。浏览器里逐个按钮点
过:冻结 7 秒后读数与 URL 都不动而"已更新"照常走、步进 +10s、两次"下一处变化"分别落在 db
的"端口没监听 → 断线"与 web 的断线上、回到现在把 ?at= 从地址栏去掉。

---------

Co-authored-by: Codebuff <noreply@codebuff.com>
Copilot AI lite review requested due to automatic review settings September 21, 2026 06:19

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants