From 77d1b3ec8684f25a58aa876713071d9ddd3a4c6e Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Fri, 6 Nov 2026 13:05:35 +0800 Subject: [PATCH] chore: keep project docs local and untracked --- .gitignore | 1 + docs/CANVAS_ELEMENTS.md | 43 ------------- docs/CORE_CANVAS_API.md | 118 ----------------------------------- docs/CORE_FRAME_EXTENSION.md | 28 --------- docs/SESSION_LIFETIME.md | 20 ------ 5 files changed, 1 insertion(+), 209 deletions(-) delete mode 100644 docs/CANVAS_ELEMENTS.md delete mode 100644 docs/CORE_CANVAS_API.md delete mode 100644 docs/CORE_FRAME_EXTENSION.md delete mode 100644 docs/SESSION_LIFETIME.md diff --git a/.gitignore b/.gitignore index a5f328f..91240fc 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ node_modules/ !.env.example *.tsbuildinfo .DS_Store +/docs/ diff --git a/docs/CANVAS_ELEMENTS.md b/docs/CANVAS_ELEMENTS.md deleted file mode 100644 index d85cb76..0000000 --- a/docs/CANVAS_ELEMENTS.md +++ /dev/null @@ -1,43 +0,0 @@ -# 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,不需要为纯图片新建表或接口。 diff --git a/docs/CORE_CANVAS_API.md b/docs/CORE_CANVAS_API.md deleted file mode 100644 index 98a96c0..0000000 --- a/docs/CORE_CANVAS_API.md +++ /dev/null @@ -1,118 +0,0 @@ -# Canvas 数据持久化:原始接口需求(已交付) - -Core 57aab71 已实现并部署下述快照和 slug 接口,前端已接入。本文保留原始约定与历史核对记录。Core 022086b 已补齐分区框并部署,最终分区契约见 [CORE_FRAME_EXTENSION.md](CORE_FRAME_EXTENSION.md)。 - -## 历史核对(以下为接口实现前的需求记录) - -核对 Core main `e8ed5e4f9ffc526f4a42b001850ffc240420a1e0`: - -- `internal/platform/materials.go` 的 material GET / PATCH 保存名称、描述、content,并以带引号的 `If-Match` 控制版本。 -- `internal/platform/content.go` 的 `Content` 只有 schema_version/text/article/image/parts,解码器使用 `DisallowUnknownFields`;board 只允许空内容 `{ "schema_version": 1 }`。直接提交 nodes、edges 或样式会被拒绝,不能用现有接口冒充已存库。 -- 未发现 Canvas 快照读写接口。当前前端节点、文本样式、图标、角度、尺寸和连线仍保存在本地草稿及导出的 JSON,尚未存入数据库。 - -## 必需接口 - -建议为 board 增加独立 Canvas 快照,使用独立的版本号,避免改名与编辑画布互相覆盖。 - -后端路径:`/projects/{project}/workspaces/{workspace}/materials/{board}/canvas` - -前端同源代理路径:`/api/foundry/workspaces/{workspace}/materials/{board}/canvas`。Core 完成后前端需扩展当前代理白名单,并接入下述读写接口;本次没有向不存在的接口发送请求。 - -### GET - -响应 `200`,`ETag: "7"`: - -```json -{ - "version": 7, - "document": { - "schema_version": 1, - "nodes": [ - { - "id": "text-node-1", - "type": "material", - "position": { "x": 100, "y": 200 }, - "width": 320, - "height": 220, - "data": { - "kind": "text", - "title": "灵感笔记", - "text": "正文", - "rotation": 30, - "fontSize": 24, - "textStyle": { - "color": "#37352f", - "background": "transparent", - "bold": true, - "italic": false, - "underline": false, - "align": "left", - "fontFamily": "sans", - "lineHeight": 1.5 - } - } - }, - { - "id": "icon-node-1", - "type": "icon", - "position": { "x": 500, "y": 200 }, - "width": 80, - "height": 80, - "data": { - "kind": "icon", - "title": "火箭", - "text": "", - "icon": "Rocket", - "rotation": 0, - "textStyle": { "color": "#78523f" } - } - } - ], - "edges": [], - "viewport": { "x": 0, "y": 0, "zoom": 1 } - } -} -``` - -没有快照的旧 board 应返回 version=1 的空图谱(nodes/edges 为空、viewport 为 0/0/1),可自动初始化。非 board 或不可访问的材料按已有资源错误约定返回。 - -### PATCH - -请求 `If-Match: "7"`,body 为 `{ "document": { ...完整快照... } }`,原子替换整张图谱,成功响应 `200`,body `{ "version": 8 }`,`ETag: "8"`。 - -- 必须校验 project/workspace/board 归属及现有成员/材料授权;GET 允许 viewer,PATCH 只允许 admin/editor。 -- 缺版本返回 428,版本冲突返回 412 / `version_conflict`,冲突不得覆盖数据库内容。 -- 无效快照返回 400 / `invalid_canvas`,超限返回 413;拒绝时不能部分写入。 -- 返回持久化版本,刷新、重新登录或另一台设备 GET 时必须能还原全部内容。 -- Canvas 与材料名称/描述分别更新,不能在保存快照时覆盖名称,也不能在改名时清空快照。 - -## 数据字段与校验 - -以本仓库 `src/lib/canvas.ts` 的 `decodeCanvas` 和 `src/lib/canvas-elements.ts` 为当前前端契约: - -| 数据 | 必须保留或校验的内容 | -| --- | --- | -| 节点类型 | text/image/script 使用 material;plain-text 使用 plainText;icon 使用 icon | -| 几何 | position.x/y 为有限数、绝对值不超过 1,000,000;width/height 成对、20–3000;rotation 为 [0,360) | -| 文本 | title 最长 300 字符,text 最长 100,000 字符;fontSize 8–160 | -| 样式 | color 为六位 hex;background 为六位 hex 或 transparent;bold/italic/underline 为布尔;align=left/center/right;fontFamily=sans/serif/mono;lineHeight=1–3 | -| 图标 | icon 为 Lucide PascalCase 标识,当前例如 Star/Rocket/Heart;颜色及尺寸由相同样式/几何字段确定 | -| 图片 | src 为 HTTPS 图片 URL 或当前前端支持的 PNG/JPEG/WebP/GIF data URL;单次本地选图最大 2 MB | -| 连线 | id/source/target/sourceHandle/targetHandle;四侧 handle=top/right/bottom/left;保留旧无名连接点兼容;引用必须存在,不允许连接纯文本或图标 | -| 视图 | viewport.x/y 为有限数,zoom=0.1–4 | - -最多 500 个节点、2000 条连线;节点与连线 ID 各自唯一。selected/dragging/measured 等 UI 临时状态不入库。节点 style.width/height 是前端尺寸镜像,可不持久化,恢复时用 width/height 生成。连线按贝塞尔曲线渲染。 - -快照请求体建议支持至少 5 MiB(目前 Core 和前端代理都是 1 MiB,需要同步调整该路由的限制),否则一张 2 MB 图片转为 data URL 后就无法保存。限制应按 UTF-8 字节计算。若 Core 选择使用独立图片上传接口代替 data URL,请提供上传和持久 URL 返回契约,前端再替换图片保存方式。 - -## 图标是否需要专门的后端服务 - -本次图标由已有 npm 包 `lucide-react@1.69.0` 本地渲染,前端提供 45 个图标和搜索。添加、搜索与显示不需要图标下载或目录 API;数据库只需通过 Canvas 快照保存 icon 标识、颜色、位置、尺寸和旋转角度,不保存 SVG 源码。后续如支持多个图标库,再为节点增加图标库及版本标识。 - -## Core 交付验收 - -1. 写入含中文文本、全部 textStyle 字段、旋转尺寸、图标和四向连线的快照,重新 GET 完全还原。 -2. 两个客户端持相同版本写入,只允许一个成功,另一个收到 412。 -3. viewer、其他 workspace 成员及未登录用户不能越权写入;非法引用、无效样式、超限请求不能留下部分数据。 -4. 改名不清空快照,保存快照不改变名称;旧 board 可读取空快照;包含一张 2 MB 图片的合法快照能够保存和还原。 -5. 接口上线后前端接入:优先读取服务端快照、提供本地旧草稿迁移、串行自动保存与冲突处理,并验证跨设备恢复;在此之前不能将前端本地持久化称为数据库保存。 diff --git a/docs/CORE_FRAME_EXTENSION.md b/docs/CORE_FRAME_EXTENSION.md deleted file mode 100644 index ebef8f9..0000000 --- a/docs/CORE_FRAME_EXTENSION.md +++ /dev/null @@ -1,28 +0,0 @@ -# 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;现有样式、图标、图片、版本冲突与权限用例保持通过。前端已使用该格式保存;失败时保留本地恢复记录并提供重试。 diff --git a/docs/SESSION_LIFETIME.md b/docs/SESSION_LIFETIME.md deleted file mode 100644 index 4559e2f..0000000 --- a/docs/SESSION_LIFETIME.md +++ /dev/null @@ -1,20 +0,0 @@ -# 会话策略与 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 发放与旧会话迁移。