Skip to content

feat(version-chain): auto-link sessions into version chains#83

Open
Svtter wants to merge 2 commits into
mainfrom
feat/version-chain
Open

feat(version-chain): auto-link sessions into version chains#83
Svtter wants to merge 2 commits into
mainfrom
feat/version-chain

Conversation

@Svtter

@Svtter Svtter commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

What

每次 sth send 自动按 (project, entry_file basename, owner) 归入版本链。同产物的多次迭代自动串成 v1→v2→v3,浏览器预览页注入版本时间线侧栏,相邻版本展示元数据 diff(tags 增删、category/project 变更)。

agent / skill 零改动——现有 sth send 工作流逐字段兼容。

OpenSpec change: version-chain-sessionsopenspec validate 通过,合并后执行 openspec archive version-chain-sessions)。

Why

sth 的核心场景是 agent 反复迭代同一份 HTML 产物。但当前每次 send 都是孤立 session,首页平铺一堆同名条目,无法回答"上次这个 dashboard 改到哪了"、"这一版相比上一版改了什么"。agent 自己也不会管理版本关系,需要在零改动前提下把隐式的"同产物迭代"显式化。

详细动机与决策见 openspec/changes/version-chain-sessions/{proposal,design}.md

How it works

  • 数据层document_chains 表按 (project, entry_file, user_id) 唯一标识链;sessionschain_id / version_no 列(nullable,老数据向后兼容)。
  • 自动链化handleCreateUploadedSession 在写元数据后调用 LinkToChain,单事务内 SELECT MAX(version_no)+1 或新建链。失败仅 log 到 stderr,不阻塞上传。
  • APIGET /api/sessions/{id}/chain 返回 chain 元信息、版本列表(v1→vN 升序、当前高亮)、相邻版本元数据 diff。
  • 预览页:复用 internal/live.InjectMiddleware 注入版本时间线 drawer。徽标默认隐藏,probe chain 接口仅当链长 > 1 时显示版本号;点击懒加载、缓存复用。
  • CLIsth send stderr 打印 version: vN of chain <id>list / search JSON 自动带 chainId / versionNo

Backward compatibility

  • --auth 关闭行为不变;老 session chain_id/version_no 为 NULL,不迁移不阻塞
  • 链化失败降级为孤立 session,预览页对单版本 session 不显示徽标
  • 现有 send/list/search/preview 路径逐字段兼容
  • 不修改 users / api_keys / user_credentials / login_sessions schema
  • 不引入任何新依赖

Test

  • go test ./... 全绿(新增 14 个测试:internal/session/version_test.go 覆盖链化/续链/owner 隔离/软删跳过/diff;internal/server/server_test.go 增测 chain endpoint happy path / 单版本 / 404)
  • go vet ./... 无警告
  • 端到端冒烟:3 次 send 同产物自动归同一条链 v1/v2/v3,chain API 返回正确,预览页注入了 sth-version-btn / sth-version-panel

Non-Goals(留作后续)

  • HTML 内容 diff(unified diff 或并排渲染)
  • --parent <id> 显式覆盖自动链化
  • 首页按链聚合折叠
  • 跨链比较 / 跨链搜索

OpenSpec tasks progress

所有任务在 `openspec/changes/version-chain-sessions/tasks.md` 中跟踪,合并后 archive。

Summary by CodeRabbit

  • New Features
    • Added automatic version timelines for related uploaded sessions.
    • Preview pages now include a version-history drawer with current-version highlighting and adjacent metadata changes.
    • Session and document listings now include chain and version identifiers.
    • Added an API to fetch ordered versions for a session, including per-version metadata diffs.
    • CLI output now includes version-chain context while preserving clean URL piping.
  • Documentation
    • Added specifications and design docs covering version-chain sessions behavior.

每次 sth send 现在自动按 (project, entry_file basename, owner) 归入版本链。
agent / skill 零改动:同产物的多次迭代自动串成 v1→v2→v3,浏览器预览页
注入版本时间线侧栏,相邻版本展示元数据 diff (tags 增删、category/project
变更)。HTML 内容 diff 留作下一版独立 capability。

数据层
- document_chains 表:UNIQUE(project, entry_file, user_id) 标识链
- sessions 加 chain_id / version_no 列 (nullable, 向后兼容) + 复合索引
- LinkToChain / GetChainOfSession / DiffChainMetadata (单事务, 单写者安全)

服务端
- GET /api/sessions/{id}/chain 返回 chain + versions + metadataDiff
- handleCreateUploadedSession 在 setSessionMetadata 后自动链化, 失败仅 log
  到 stderr 不阻塞上传 (降级为孤立 session)

预览页
- 复用 internal/live.InjectMiddleware 注入 versionDrawer (CSS/HTML/JS)
- 徽标默认隐藏, probe chain 接口仅当 versions.length > 1 时显示版本号
- 时间线纵向展示, 当前版本高亮, 相邻版本下方渲染元数据 diff
- 选择器 #sth-version-* 命名空间 + !important 抵抗用户样式冲突

CLI
- runSend 解析 chainId/versionNo, stdout 仍只打 URL (管道友好), stderr
  打印 version: vN of chain <id>
- list/search JSON 自动带 chainId/versionNo 字段

软删兼容
- SoftDelete 现有行为不变, 时间线跳过被软删版本, 版本号不重排

OpenSpec
- openspec/changes/version-chain-sessions/ 全套 (proposal/design/tasks/spec)
- 决策 D14-D18 续接 support-password-login 的 D13
- openspec validate version-chain-sessions 通过

测试: go test ./... 全绿 (新增 14 个), go vet 无警告
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d577b4c5-8d36-4e02-9ffc-a1bf2d20cd19

📥 Commits

Reviewing files that changed from the base of the PR and between c5811a3 and bc8127a.

📒 Files selected for processing (7)
  • internal/server/server.go
  • internal/server/server_test.go
  • internal/session/metadata.go
  • internal/session/version.go
  • internal/session/version_test.go
  • openspec/changes/version-chain-sessions/design.md
  • openspec/changes/version-chain-sessions/tasks.md
🚧 Files skipped from review as they are similar to previous changes (5)
  • internal/session/metadata.go
  • internal/server/server.go
  • internal/session/version.go
  • openspec/changes/version-chain-sessions/design.md
  • internal/session/version_test.go

📝 Walkthrough

Walkthrough

Adds automatic session version chains keyed by project, entry-file basename, and owner. It introduces chain storage and APIs, metadata diffs, preview timeline UI, CLI chain output, document-list fields, tests, and OpenSpec documentation.

Changes

Session version chains

Layer / File(s) Summary
Chain schema and document contracts
internal/session/metadata.go, internal/session/store.go, internal/session/version.go
Adds chain tables, session columns, indexes, document chain fields, and chain/version data models.
Chain linking and metadata history
internal/session/version.go, internal/session/version_test.go
Creates and appends chains, retrieves ordered live versions, marks current sessions, and computes tag/category/project diffs with unit coverage.
Upload linking and chain endpoint
internal/server/server.go, internal/server/server_test.go
Links uploaded sessions, adds chain fields to creation responses, exposes GET /api/sessions/{id}/chain, and tests linked, unlinked, missing, and soft-deleted responses.
Preview timeline and CLI output
internal/live/inject.go, internal/cli/cli.go
Injects a lazy-loaded version timeline drawer and reports chain context on stderr while retaining the session URL on stdout.
Version-chain specification
openspec/changes/version-chain-sessions/*
Adds the design, proposal, requirements, task checklist, and OpenSpec metadata for the feature.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Server
  participant Store
  participant Preview
  Client->>Server: Upload session
  Server->>Store: Link session to project/file/owner chain
  Store-->>Server: chainId and versionNo
  Server-->>Client: Session URL and chain metadata
  Preview->>Server: GET /api/sessions/{id}/chain
  Server->>Store: Load versions and metadata diffs
  Store-->>Server: Chain response data
  Server-->>Preview: Ordered version timeline
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: automatically linking sessions into version chains.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/version-chain

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

🧹 Nitpick comments (2)
openspec/changes/version-chain-sessions/tasks.md (1)

34-36: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update the checklist to reflect the completed tests and validations.

The PR objectives confirm that go test, go vet, and openspec validate are all passing successfully. Consider checking these boxes to keep the specification checklist fully up to date before it is archived.

📝 Proposed update
- - [ ] 6.2 `go test ./...` 全绿。
- - [ ] 6.3 `go vet ./...` 无警告。
- - [ ] 6.4 `openspec validate version-chain-sessions` 通过。
+ - [x] 6.2 `go test ./...` 全绿。
+ - [x] 6.3 `go vet ./...` 无警告。
+ - [x] 6.4 `openspec validate version-chain-sessions` 通过。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openspec/changes/version-chain-sessions/tasks.md` around lines 34 - 36,
Update checklist items 6.2, 6.3, and 6.4 in the validation checklist to checked
state, reflecting that go test ./..., go vet ./..., and openspec validate
version-chain-sessions have completed successfully.
internal/session/version.go (1)

198-211: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Redundant fallback loop.

Lines 198-202 already set Current = true on the matching session unconditionally, so the currentVersion == 0 fallback loop at Lines 203-211 can never mark anything the first loop missed. It can be removed.

♻️ Proposed cleanup
 	for i := range versions {
 		if versions[i].SessionID == sessionID {
 			versions[i].Current = true
 		}
 	}
-	if currentVersion == 0 && len(versions) > 0 {
-		// Fallback when the column hasn't been populated yet.
-		for i := range versions {
-			if versions[i].SessionID == sessionID {
-				versions[i].Current = true
-				break
-			}
-		}
-	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/session/version.go` around lines 198 - 211, Remove the redundant
currentVersion == 0 fallback loop after the initial versions iteration. Keep the
existing loop that marks the matching session’s Current field true, and leave
the surrounding version-selection logic unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/server/server.go`:
- Around line 1889-1909: The handleGetChain logic must not synthesize a current
version for a soft-deleted requested session. Before the fallback that assigns
versions[len(versions)-1], detect when the requested session is deleted and
absent from the live versions, and return the existing not-found response;
otherwise preserve the fallback only for valid non-deleted requests.

In `@internal/session/version.go`:
- Around line 87-119: Make LinkToChain’s NULL-owner chain creation atomic around
the SELECT-then-INSERT flow, using the transaction’s locking or upsert
mechanism, and re-check version assignment under concurrent anonymous uploads so
one chain is used per project and entry file; update
internal/session/version.go:87-119. In internal/session/metadata.go:61-70,
document that UNIQUE(project, entry_file, user_id) does not enforce NULL-owner
uniqueness and remove the redundant idx_document_chains_lookup index, unless the
revised atomic creation mechanism replaces that constraint’s role.

---

Nitpick comments:
In `@internal/session/version.go`:
- Around line 198-211: Remove the redundant currentVersion == 0 fallback loop
after the initial versions iteration. Keep the existing loop that marks the
matching session’s Current field true, and leave the surrounding
version-selection logic unchanged.

In `@openspec/changes/version-chain-sessions/tasks.md`:
- Around line 34-36: Update checklist items 6.2, 6.3, and 6.4 in the validation
checklist to checked state, reflecting that go test ./..., go vet ./..., and
openspec validate version-chain-sessions have completed successfully.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 288eb228-7800-40e6-b30a-caee121b6a1e

📥 Commits

Reviewing files that changed from the base of the PR and between 2d0c4a0 and c5811a3.

📒 Files selected for processing (13)
  • internal/cli/cli.go
  • internal/live/inject.go
  • internal/server/server.go
  • internal/server/server_test.go
  • internal/session/metadata.go
  • internal/session/store.go
  • internal/session/version.go
  • internal/session/version_test.go
  • openspec/changes/version-chain-sessions/.openspec.yaml
  • openspec/changes/version-chain-sessions/design.md
  • openspec/changes/version-chain-sessions/proposal.md
  • openspec/changes/version-chain-sessions/specs/version-chain/spec.md
  • openspec/changes/version-chain-sessions/tasks.md

Comment thread internal/server/server.go
Comment thread internal/session/version.go Outdated
#1 软删 session 的 chain 请求错误兜底 (Major)
- GetChainOfSession 读 deleted_at, 软删直接返 ErrSessionNotFound
- handleGetChain 去掉 "fabricate current" 兜底, current.SessionID=="" 即 404
- 新增 store 层 + HTTP 层回归测试

#2 LinkToChain anonymous 竞态 (Major)
- SQLite UNIQUE(project, entry_file, user_id) 对 NULL owner 不生效
  (SQL 标准 NULL ≠ NULL), 原 SELECT-then-INSERT 在并发 anonymous send 下
  会拆链
- 改用 BEGIN IMMEDIATE 立即拿 RESERVED 写锁, 串行化整个读-改-写
- 链行创建改 upsert (INSERT ON CONFLICT DO NOTHING) + 再 SELECT,
  认证 owner 路径额外有 UNIQUE 兜底
- design.md D17 + Risks 同步更新事务安全说明

#3 冗余 fallback 循环 (Trivial)
- 删除 version.go 里 currentVersion==0 的 dead-code 分支, 上一循环已无条件
  标记 Current

#4 冗余索引 (Trivial)
- 删除 idx_document_chains_lookup, UNIQUE 约束已自带 (project, entry_file,
  user_id) 左前缀索引
- metadata.go 文档化 UNIQUE 对 NULL owner 不生效的限制

#5 tasks 勾选
- 6.2/6.3/6.4 打勾, 新增第 7 节记录本轮 review 修复

测试: go test ./... 全绿 (新增 2 个回归测试), go vet 无警告,
openspec validate 通过
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.

1 participant