docs: document canvas inheritance and integrated session policy

This commit is contained in:
2026-11-06 13:02:42 +08:00
parent 373582906d
commit 56c6588ad6
6 changed files with 167 additions and 11 deletions
+43
View File
@@ -0,0 +1,43 @@
# Canvas 元素继承关系
业务类定义在 `src/lib/canvas-elements.ts`。存储和传输的是普通 JSON 节点,恢复时依据 kind 选择组件;不会把类实例或 React 组件写入数据库。
```text
CanvasElement(基础:位置、尺寸、旋转;默认无连线、无分支)
├── CardElement(增加连线、四向连接点、分支)
│ ├── TextCardElement 文本卡片
│ ├── ScriptCardElement 脚本卡片
│ └── ImageCardElement 图片卡片
├── PlainTextElement 纯文本
├── PlainImageElement 纯图片
├── IconElement Lucide 图标
└── FrameElement 分区框(关闭旋转,无连线、无分支)
```
| 业务类 | data.kind | React Flow type / 渲染组件 | 操作 |
| --- | --- | --- | --- |
| TextCardElement | text | material / MaterialNode | 自由宽高、旋转、连线、添加分支;双击编辑正文与名称 |
| ScriptCardElement | script | material / MaterialNode | 同文本卡片 |
| ImageCardElement | image | material / MaterialNode | 带卡片的图片,自由宽高、旋转、连线、分支、双击改名 |
| PlainTextElement | plain-text | plainText / PlainTextNode | 选中后整块拖动,双击编辑;自由宽高,字号单独控制;无卡片、连接点和分支 |
| PlainImageElement | plain-image | plainImage / PlainImageNode | 按原图比例插入与缩放,整图拖动、旋转;无卡片标题、连接点和分支 |
| IconElement | icon | icon / IconNode | 整块拖动、等比缩放、旋转、颜色设置;库内 2313 个去重图标,按需加载 |
| FrameElement | frame | frame / FrameNode | 调整大小、改名、三种标签模式;带动已有成员;不旋转、不嵌套、不连线 |
所有非分区内容均可通过 `data.frameId` 加入分区。成员关系只在内容拖放时更新,不根据框的移动或缩放自动重算。
## React 组件共享关系
React 视图通过组合复用,不使用组件类继承:
- `TransformableNode`:选中外框、常驻右下角缩放符号、旋转模式、共享操作上下文。
- `MaterialNode`:三种卡片共用的框、外部标题及连接点;`NodeTitle` 处理改名。
- `CanvasTextEditor`:文本卡片、脚本、纯文本共用的「单击选中 / 双击编辑 / 非编辑时拖动」交互。
- `NodeStyleToolbar`:文字与视觉样式;`FrameNode` 使用独立分区设置栏。
- 工具栏显式设在画布交互层上方,避免负层级分区的弹出栏看得见却点不到。
右下角缩放符号未选中时也显示,拖动会先选中元素;四边连接点仍仅选中时显示。抓手模式下缩放符号可见但不响应拖动。
## Core 配套
分区格式已随 Core 022086b 部署。纯图片 `plain-image / plainImage` 的配套 [Core PR #11](https://gitea.entroia.com/Zayden-Jung/foundry-core/pulls/11)(工作分支 `codex/plain-images`,提交 `460b05f`)已合并,并随主线 7847167 的部署 #889 成功上线。全部元素沿用现有快照 API,不需要为纯图片新建表或接口。
+4 -2
View File
@@ -1,6 +1,8 @@
# Canvas 数据持久化:交给 Core 的接口需求
# Canvas 数据持久化:原始接口需求(已交付)
## 已核对的现状
Core 57aab71 已实现并部署下述快照和 slug 接口,前端已接入。本文保留原始约定与历史核对记录。Core 022086b 已补齐分区框并部署,最终分区契约见 [CORE_FRAME_EXTENSION.md](CORE_FRAME_EXTENSION.md)。
## 历史核对(以下为接口实现前的需求记录)
核对 Core main `e8ed5e4f9ffc526f4a42b001850ffc240420a1e0`:
+28
View File
@@ -0,0 +1,28 @@
# Core Canvas:分区框字段契约(已部署)
Core 022086b 已通过 PR #9 合入主线,[部署 #877](https://gitea.entroia.com/Zayden-Jung/foundry-core/actions/runs/877) 成功。前端已接入以下契约。基础快照接口来自 57aab71,分区沿用同一 Canvas GET/PATCH、canvas_document JSONB 与独立 canvas_version,不需要新接口或表结构。
1. 节点 `type=frame` 对应 `data.kind=frame`。width/height、position、title/text 沿用现有校验;分区 rotation 只允许 0 或省略。
2. `data.labelMode` 仅分区支持,枚举 hidden/fixed/scaled,省略时默认 fixed。
3. 普通内容节点可带 `data.frameId`,引用同一快照中 kind=frame 的节点 ID;分区本身不允许 frameId(当前不支持分区嵌套)。引用不存在、引用普通内容或引用自己均拒绝。
4. 连线仍只允许 text/image/script,分区不支持连线。现有 500 节点、2000 连线、5 MiB、If-Match 和权限规则保持。
5. 前端用绝对坐标保存成员位置。移动分区时,前端同步修改已有成员坐标;后端只原子保存快照,不依据几何重算成员,不能把覆盖范围内的其他内容自动归入分区。
示例节点:
```json
[
{
"id": "frame-1", "type": "frame",
"position": { "x": 100, "y": 100 }, "width": 640, "height": 400,
"data": { "kind": "frame", "title": "视觉参考", "text": "", "rotation": 0, "labelMode": "fixed" }
},
{
"id": "text-1", "type": "material",
"position": { "x": 160, "y": 160 }, "width": 260, "height": 200,
"data": { "kind": "text", "title": "笔记", "text": "分区里的内容", "frameId": "frame-1", "fontSize": 13, "rotation": 0 }
}
]
```
验收:三种 labelMode、框尺寸/位置和成员 frameId PATCH→GET 完整还原;错误归属和嵌套返回 400;现有样式、图标、图片、版本冲突与权限用例保持通过。前端已使用该格式保存;失败时保留本地恢复记录并提供重试。
+20
View File
@@ -0,0 +1,20 @@
# 会话策略与 Cookie 续期
用户确认的策略:30 天无活动过期,正常鉴权时续期,最长签发后 90 天重新登录。继续使用随机、可撤销的服务端会话令牌,不改用 JWT。
## Core
工作分支 `codex/session-lifetime`,提交 `34c461a`,PR #10 已合并;与纯图片改动一同进入主线 7847167,[部署 #889](https://gitea.entroia.com/Zayden-Jung/foundry-core/actions/runs/889) 已成功。新会话执行 30 天闲置 / 90 天绝对期限;既有过期会话不复活,有效会话可在下次鉴权时续期。
- 新会话 expires_in=2592000。
- 004-session-lifetime.sql 增加 created_at / absolute_expires_at。旧会话保留原 expires_at,不复活已过期会话;旧记录的 90 天上限从迁移时间起算。
- 鉴权原子更新 expires_at=min(当前时间+30天, absolute_expires_at)。闲置过期、绝对过期、撤销和跨项目令牌均不可续期。
- 成功鉴权后返回 `X-Foundry-Session-Expires-In`,值为按绝对期限截断后的剩余秒数。平台接口与 auth/me 均支持该头。
## 前端
同源代理从 HttpOnly Cookie 取令牌转发给 Core;不把令牌返回客户端 JavaScript。登录时按照 expires_in 设置 Cookie,普通已认证响应按照续期头更新 Cookie,最多 30 天。临近 90 天时按 Core 返回的剩余秒数缩短 Cookie;401 或非法续期头不续期。
Cookie 保留 HttpOnly、SameSite=Lax、Path=/;生产 HTTPS 使用 Secure。旧 Core 未返回续期头时保持兼容,不自行假定令牌有效期已经延长。
验证覆盖:Cookie 上限及剩余时间、非法头、401 不续期、退出清除;Core CI 覆盖真实隔离数据库中的闲置/绝对过期、并发、撤销、项目隔离、OAuth 发放与旧会话迁移。