dev: initial implementation of notebook-snapshot extension

Manual-commit notebook snapshot extension (JupyterLab 4):

Backend
- Storage adapter layer (Local + S3 via boto3, pip extra)
  - Dumb put/get/list/delete contract; key validation
  - S3Storage: lazy boto3 import, S3ConnectionError with friendly
    messages + connectivity check at startup
- SnapshotStore
  - Strip code outputs/execution_count, MD5 on cleaned JSON
  - Gzipped envelope (id/timestamp/name/description/hash/size/notebook)
  - Append-only manifest.json (rebuildable from version files)
  - SnapshotUnchangedError when new hash matches most recent version
- REST API (commit / list / content) under /snapshot/ namespace
- traitlets config (storage_type, local_root, s3_* + S3_* env fallback)

Frontend
- snapshot:commit command (toolbar button + command palette)
- Dialog for name + description
- SnapshotPanel (left sidebar timeline) + DiffWidget (main area)
- Cell diff: id-match first, LCS fallback; jsdiff for line diff
- Restore via model.fromJSON() + context.save() (no refresh)
- All AGENTS.md hard conventions enforced

Tests: 33 backend pytest passing (storage + store + routes)

Docs: AGENTS.md, design.md, README.md synced with implementation.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
tao.chen
2026-07-21 17:33:55 +08:00
co-authored by Claude
commit cd1aba6698
67 changed files with 17926 additions and 0 deletions
+147
View File
@@ -0,0 +1,147 @@
# notebook-snapshot
JupyterLab 扩展:**手动保存 .ipynb 快照**,无 Git 依赖。
左侧栏查看版本时间轴、Cell 级 diff、一键无刷新恢复。
## 功能
- 工具栏「保存快照」按钮 / 命令面板 `snapshot:commit` 触发
- 对话框填写 `name`(必填)+ `description`(可选)后保存
- 内容与最近版本相同时自动跳过(弹 warning),避免重复快照
- 左侧栏时间轴,支持对比(Cell 级 diff,行级红绿高亮)和恢复
- 恢复走 `model.fromJSON()` + `context.save()`,无页面刷新
- 历史 append-only,永不删除 / 改写
## 存储后端
- **Local**(默认):`<root_dir>/.notebook_versions` 或自定义路径
- **S3**pip extra):通过 boto3 对接 AWS S3 / MinIO / 任何 S3-API 兼容服务
## 环境要求
- JupyterLab >= 4.0.0
- Python >= 3.10
- Node.js(构建前端用)
## 安装
```bash
# 1. 后端(基础包零依赖)
pip install snapshot
# 2. 前端构建
jlpm install
jlpm build
# 3. 启用 server extension
jupyter server extension enable snapshot
jupyter labextension develop . --overwrite # dev 安装;正式版 wheel 跳过此步
```
可选 S3 后端:
```bash
pip install "snapshot[s3]" # 装 boto3
```
## 配置
写在 `~/.jupyter/jupyter_server_config.py`(用户级)或 `<项目>/jupyter_server_config.py`(项目级):
| 字段 | 默认 | 说明 |
|------|------|------|
| `storage_type` | `"local"` | `"local"``"s3"` |
| `local_root` | `""``<root_dir>/.notebook_versions` | Local 存储根目录;**建议显式设置**避免污染 notebook 目录 |
| `s3_endpoint` | `""`env: `S3_ENDPOINT` | `localhost:9000`MinIO/ `s3.us-east-1.amazonaws.com`AWS |
| `s3_access_key` | `""`env: `S3_ACCESS_KEY` | |
| `s3_secret_key` | `""`env: `S3_SECRET_KEY` | |
| `s3_bucket` | `"notebook-snapshot"` | 不存在自动建 |
| `s3_secure` | `False` | HTTPS |
| `s3_prefix` | `""` | 桶内 key 前缀 |
### 示例
```python
# 切到自定义 local 目录
c.SnapshotConfig.local_root = "/var/lib/jupyter/snapshots"
# 切到 MinIO
c.SnapshotConfig.storage_type = "s3"
c.SnapshotConfig.s3_endpoint = "minio.example.com:9000"
c.SnapshotConfig.s3_bucket = "team-snapshots"
# 凭证走 envexport S3_ACCESS_KEY=... ; export S3_SECRET_KEY=...
# 切到 AWS S3
c.SnapshotConfig.storage_type = "s3"
c.SnapshotConfig.s3_endpoint = "s3.us-east-1.amazonaws.com"
c.SnapshotConfig.s3_secure = True
# 用 AWS 标准凭证:export AWS_ACCESS_KEY_ID=... ; export AWS_SECRET_ACCESS_KEY=...
# (注:当前版本仅读取 S3_* 环境变量;如需 AWS_* 自动识别,参考 boto3 文档配置)
```
环境变量 / 命令行 / 配置文件三种方式优先级遵循 traitlets 规则;改完配置**必须重启 `jupyter lab`**。
## 开发
```bash
source .venv/bin/activate # 本仓库用 uv/venv
pip install -e ".[dev,test,s3]" # 后端装 dev/test/s3 依赖
.venv/bin/jlpm install # 前端依赖
.venv/bin/jlpm build # 一次性构建(tsc + rspack + labextension
.venv/bin/jlpm watch # 终端 1:监听 src/ 自动重编
jupyter lab # 终端 2:启动 JupyterLab
```
**改了 Python 后端**:重启 `jupyter lab`(不需要 rebuild 前端)。
**改了前端 TypeScript**`jlpm watch` 自动重编,浏览器刷新即可。
## 验证
```bash
# 后端
.venv/bin/pytest snapshot/tests/ -vv
# 前端类型检查 + 打包
.venv/bin/jlpm build
# Lint
.venv/bin/jlpm lint:check
```
## 架构
参见 [design.md](design.md)。
简要:
- **后端**`/snapshot/` 命名空间下三个端点(commit / list / content);`SnapshotStore` 包装 `StorageAdapter`Local + S3),gzip + envelope 存盘,manifest 是可重建的索引
- **前端**`SnapshotModel` + `SnapshotPanel`(左侧栏) + `DiffWidget`(主区) + `snapshot:commit` 命令(工具栏 + 命令面板)
硬约定(实现时不得违背)见 [AGENTS.md](AGENTS.md)。
## 排错
```bash
jupyter server extension list # 后端 extension 状态
jupyter labextension list # 前端 extension 状态
```
- **工具栏没有「保存快照」按钮**:刷新浏览器;确认 `jupyter labextension list` 显示 enabled + OK
- **保存时 400**:检查 `path` / `content` / `name` 是否齐全
- **保存后弹"内容未变更"**:正常行为——内容与最近版本 hash 相同会自动跳过,改动 cell 后再保存即可
- **S3 连不上**:确认 `endpoint`(不带 scheme)、`secure`http/https)、bucket 存在性;凭证走 env 别写进配置
## 卸载
```bash
jupyter server extension disable snapshot
pip uninstall snapshot
```
## AI 工具支持
本项目遵循 [AGENTS.md](https://agents.md) 标准,配套符号链接:
- `CLAUDE.md``AGENTS.md`Claude Code
- 其他工具:Cursor / Copilot / Windsurf / Aider 等直接读取 `AGENTS.md`
详细开发规范见 [AGENTS.md](AGENTS.md),设计文档见 [design.md](design.md)。