351891714af8eb9e327f2fa5fa06fc91a4378a98
契约 §1 行 6a/6b 的两个端点与 §3.8 的两帧,整条链接完(不含「重发」——契约
明确没有重发端点,客户端用编辑后的内容再 POST /chat 即可,本模块不做)。
数据层
- SessionsApi 增 PATCH / DELETE api/v1/messages/{id}(与既有消息端点同源,因此
MessageRepository 能直接复用、不必动 AppContainer),MessageRepository 增
editMessage / deleteMessage,走既有 apiCall / Result 风格。
- 响应形状照契约不照旧文:PATCH 的 data 直接就是消息 DTO(没有 message 包一层),
DELETE 回 {deleted: id};编辑内容按「非空、≤4000 rune(码点,不是 length)」
预校验,不合格一个请求都不发。
帧解析
- WsEvent 增 message_edited / message_deleted,形态照 message_persisted 那一套:
会话归属先取帧壳、其次取消息自身的 sessionId,未知键安全、未知帧与坏帧安静忽略、
永不抛。
- message_edited 的 data 就是消息本身(与 message_persisted 的 data.message 不同形),
照抄那一套会解出 null——这一点由单测钉住。
VM
- 按 id 原位改 content / 原位移除。两条路各自只有一个落地出口:本端 PATCH/DELETE 的
成功回执与随后到达的广播都调同一对纯函数,而它们幂等(同一内容做两次返回同一实例)
⇒ 「响应 + 广播」双到达收敛成一次,不需要去重表、也不会出现两套状态源。
- 编辑只对 role == user 开放、删除对 user 与 assistant 都开放,判据是同一份纯函数,
气泡(隐藏入口)与 VM(再挡一次)共用。
- 失败一律如实:弹层还开着就留在弹层里(输入不丢、可原地重试),弹层已被关掉就挂到
页面顶部那行横幅,绝不静默。
UI
- 长按气泡打开动作菜单;由于正文包在 SelectionContainer 里、长按正文同时也是选字手势
(两者抢同一个长按,谁先消费由运行时决定),另给一枚看得见的「⋯」
(content-desc 消息操作)作为同一条入口,避免只有知道的人才会用。
- 编辑弹层:原始 content 作初值 + 码点计数 + 取消/保存;删除必须二次确认。
- ChatScreen 只加 24 行接线(两个回调 + 两个弹层的渲染)。
测试 +49(766 → 815,全绿):帧解析 14、纯逻辑 15、Retrofit 层 7、失败文案与落点 13。
Releases
5
Alpha_v0.1.0_4
Latest