No description
  • Go 40.7%
  • TypeScript 34.2%
  • JavaScript 11.8%
  • CSS 6%
  • Python 5%
  • Other 2.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-11-02 13:52:09 +00:00
backend Improve transfers, bucket statistics, worker recovery and frontend modules 2026-10-22 15:58:40 +00:00
deploy Release SQL Beautify compatible formatting as 20261025 2026-10-26 08:43:44 +00:00
docker Replace notebook attachments with independent multi-bucket S3 file manager 2026-10-22 10:48:49 +00:00
docs Center the lock screen and restore its rounded card 2026-10-11 14:58:38 +00:00
frontend Match SQL Beautify formatting exactly using pinned upstream source 2026-10-26 08:35:30 +00:00
scripts Match SQL Beautify formatting exactly using pinned upstream source 2026-10-26 08:35:30 +00:00
.dockerignore Simplify page URLs, center download fields and seed manual acceptance data 2026-10-10 05:49:08 +00:00
.env.example feat: harden login security and public deployment settings 2026-09-10 20:00:09 +08:00
.gitignore Optimize personal workflow, safe history restore, backups and streaming downloads 2026-10-10 02:30:45 +00:00
Dockerfile Optimize personal workflow, safe history restore, backups and streaming downloads 2026-10-10 02:30:45 +00:00
kownme.md Snapshot project documentation before independent knowledge module 2026-11-02 13:52:09 +00:00
Layla_Note-知识库与知识图谱技术设计方案.md Snapshot project documentation before independent knowledge module 2026-11-02 13:52:09 +00:00
PROJECT_HANDOFF_INTERNAL.md Optimize personal workflow, safe history restore, backups and streaming downloads 2026-10-10 02:30:45 +00:00
README.md Release SQL Beautify compatible formatting as 20261025 2026-10-26 08:43:44 +00:00

文档修改时间:2026-10-26(北京时间)。注意:本文档可能过时,具体逻辑以代码为准。

20261025 SQL 选区格式化

SQL 文档中选中需要排版的代码,按 Alt+Shift+F,或在编辑器右键选择“格式化选中的 SQL”。没有选区时仅提示,绝不回退为格式化全文。支持多个非空选区,所有选区合并为一次撤销操作;Ctrl+Z 可以恢复。选区之外的文字不改动,空白选区不处理。

格式化直接使用 sql-beautify 0.3.22 仓库内的定制 vkbeautify 源码,固定上游提交 76e411d2f78f971a929b6718465b02f577f70fa9。默认参数与插件一致:uppercase=true、comma_location=false、bracket_char=false、as_loc_cnt=150。关键词大写、逗号前置、子查询缩进及 AS 对齐均按原插件输出,不使用编辑器缩进设置覆盖排版;不再使用 sql-formatter。相同输入与原插件默认设置逐字符一致;选区内首尾空白也按上游处理。原插件支持的 SQL/HQL 规则和片段处理行为随之保留,未包含 Hive DDL 提取等附加命令。

格式化在本地独立 Worker 中处理,不调用 AI、数据库或外部格式化服务。原版源码、SHA-256、来源提交与许可证保存在 frontend/src/vendor/sql-beautify/;浏览器适配只声明上游隐式变量、关闭调试日志并转换模块导出,不改格式化函数。node frontend/scripts/build-sql-beautify.mjs --check 校验适配文件;20 组黄金样例同时与未修改的原源码输出及真实编辑器比对。仅非空选区执行,最大 20 万字符、50 个选区,超时五秒停止 Worker。锁定/只读文档不执行;处理期间文档、选区或语言变化则放弃旧结果。格式化后仍需自行保存和提交。适配逻辑独立在 selectionFormatting 模块,本次仅启用 SQL,Python/Java/Go 等后续接入。

测试空间“20261024 SQL选区格式化验收”包含选区边界、CTE/子查询、字符串/注释及操作说明。发布镜像:cloverdo/layla-note:20261025;Forgejo:git.cloverdo.cn/cloverdo/layla-note:20261025。应用功能沿用已验证的 SQL Beautify 排版修正版,20261024 镜像保留用于回退。无数据库结构迁移,沿用 20261022 数据卷和配置。

20261022 传输、存储统计与后台恢复优化

发布镜像:cloverdo/layla-note:20261022,Forgejo:git.cloverdo.cn/cloverdo/layla-note:20261022。

  • 下载由浏览器原生下载管理器接收,不再在前端通过 Blob 等待整个 ZIP。页面显示打包、传输、完成或失败;“完成”代表服务器传输结束,最终保存位置以浏览器为准。下载票据绑定当前登录会话,五分钟内单次使用;加密 ZIP、UUID 密码与按最新内容重新下载保持原逻辑。临时票据不保存文件字节,容器重启后可重新发起下载。
  • S3 连接建立超时 10 秒、响应头等待 30 秒、读写停顿 90 秒;移除统一两分钟总时限,持续正常传输的大文件可超过两分钟。单文件上传限制仍为 500 MiB。
  • 应用级上传队列支持站内切换页面继续上传、单文件独立失败/重试/取消,同名文件明确选择覆盖或换名。队列显示目标 Bucket 和目录;刷新或关闭整个页面时提醒未完成任务。队列和原始 File 对象仅在当前页面内存,刷新后需要重新选择文件;退出/锁屏导致工作台卸载时取消未完成队列。已完成项目释放 File 引用。
  • “我的 → 数据库信息”区分数据库占用、旧附件兼容数据和独立 S3 统计。按 Bucket 手动扫描当前对象数量与大小,不扫描即不产生额外列表请求;后台逐页统计,持久化汇总进度和游标,重启后继续。保留上次成功结果与时间,失败可重试。文件夹占位不算文件;多连接指向相同 Bucket 的统计不可重复相加,扫描不是 S3 的事务快照。
  • 敏感扫描数据库连接失败后逐步退避重试,重连后恢复工作;全量扫描批次之间处理待复查文档,避免长期等待。页面显示最近成功时间、当前进度和失败重试入口。Git 与 Bucket 检查原有循环保持可重试。
  • 前端按文件树、编辑区、导航、回收站拆分模块;S3 预览与上传队列分离。移除图片管理中已无入口的旧附件分支,保留后端旧编码、历史下载兼容。

数据库迁移 000029 仅为 Bucket 连接增加统计 JSON 字段,不保存对象内容或对象索引。发布前保留数据库/配置备份,恢复旧版本时使用对应版本备份。

测试空间“20261022 传输与后台优化验收”包含验收说明、敏感词修改、SQL 和 HTML 共 4 篇文档,沿用“20261021 验收 A/B”测试 Bucket。只在测试服务器运行: S3_ACCEPTANCE=1 python3 scripts/seed-optimization-acceptance.py --base-url http://10.10.10.102:8080。 scripts/regression-optimization.cjs 验证原生下载、跨页面上传、失败重试、统计和页面拆分; scripts/regression-worker-recovery.py 只对明确隔离的 QA 数据库容器注入短暂断连。

20261021 独立 S3 文件管理

附件管理升级为独立的「文件管理」(/file),与项目空间、笔记本无关。右上角切换 Bucket,在「我的 → S3 管理」新增多个连接;不同连接可使用不同地址与凭据。凭据在后端加密保存,不返回浏览器。停用连接不删除 S3 文件,重新启用原连接保留编码;地址、Bucket 和寻址方式变化请新增连接,避免把已有编码指向别处。

  • 所有内容由后端实时读取 S3;上传每片 8 MiB,直接写入 S3 Multipart Upload,不暂存本地文件、不存数据库正文、不提供离线代存。单文件上限 500 MiB(524288000 字节,界面显示 500 MB),包含边界值;前后端校验总大小与分片实际大小。进度按已确认分片计,支持取消、分片重试与同名覆盖/换名确认。未完成任务两小时后后台清理,S3 不可用时持续重试。
  • 目录按 S3 Prefix/Delimiter 实时列举,支持分页、面包屑、空文件夹创建、文件删除与空文件夹删除,删除操作直接作用于 S3,不进入 Layla 回收站。已有外部对象无需归档即可显示,首次发现分配稳定 file_ 编码;同一路径覆盖仍使用原编码。外部改名视为新路径。
  • 支持当前目录/整个 Bucket 的文件、文件夹和路径名称包含搜索。整个 Bucket 搜索逐页访问 S3,显示已扫描/命中数,可停止、继续,不能把当前已加载页当成全部结果。此版本不建立全文索引,不搜索文件正文。
  • 后台约每 30 秒测试列举能力,页面每 5 秒更新状态;操作失败即时反映,上传前对过期状态重新检查。保存配置或手动测试还会使用随机临时对象检查写入/删除权限。显示可用、只读、不可用、停用及检查时间。连接测试无法保证传输中网络不会中断,只有 S3 完成并通过大小校验后才提示上传成功。
  • 图片、隔离 SVG、浏览器兼容音视频(Range)、PDF、文本/代码可预览;HTML 复用两层沙箱预览,默认不运行脚本、不加载外部资源。文本预览最大 5 MiB,PDF 预览最大 20 MiB。PDF 使用随页面打包的 PDF.js Worker 逐页绘制到 Canvas,不运行 PDF 脚本、表单或链接;不依赖浏览器插件或外部 CDN。压缩包、Office 等仅显示元数据并提供下载,不转码、不执行。
  • S3 文件下载实时生成 AES-256 加密 ZIP,直接流向浏览器,不在服务器保存包;下载管理保留 UUID、密码、文件数、大小与状态。重新下载从 S3 读取当前对象,复用原 UUID/密码。文件编码与下载 UUID 是不同标识。
  • 笔记本普通上传、拖拽及文件夹导入只接受 UTF-8 文本;后端同时检查扩展名和实际内容。图片管理及 Markdown 粘贴图片保留独立流程。非文本文件需在文件管理选择 Bucket 上传。
  • 「迁移旧附件」为显式操作:数据库二进制先上传到所选 Bucket,再下载校验 SHA-256/大小,事务成功后清除旧二进制并解除笔记本节点关联,保留原编码。原有 S3 附件仅在 Bucket 匹配且对象存在时解除关联。失败条目保留原数据,可重试;升级前必须备份。
  • 项目备份包含数据库配置、编码映射和下载记录;独立文件管理的 S3 对象不镜像进 Layla 备份,应由 S3 自身备份。尚未迁移的旧笔记本附件仍按旧流程备份。

测试空间「20261021 S3文件管理验收」包含 3 篇说明/SQL/HTML 文档;文件管理预设「20261021 验收 A/B」两个独立测试 Bucket,包含预览、特殊字符路径和 225 项分页样例。测试文件保存在独立 S3 测试容器的数据卷,不保存在笔记数据库。S3_ACCEPTANCE=1 python3 scripts/seed-storage-acceptance.py 仅允许测试服务器的 8080/8085 实例;--tests 额外执行 500 MiB 真实上传等验收,只对专用测试 Bucket 使用。

升级涉及 000027/000028 数据库迁移。回退旧版本应同时恢复升级前数据库备份,不能只切换旧镜像。

发布镜像:cloverdo/layla-note:20261021;运行环境复用已验证的 20261020 Chromium/PostgreSQL 客户端,前后端由本次源码重新编译,通过 docker/Dockerfile.compiled-release 组装。Dockerfile 仍可完整构建。

敏感保护界面:每条规则可直接启停;命中详情通过弹窗查看。未修改的弹窗可点击外部或按 Esc 关闭;表单有未保存修改或请求处理中时保留。当前文档仅保留背景阴影,不显示蓝色边线。

Layla Note

20261020 SQL 高亮与 HTML 单文件预览发布

镜像 cloverdo/layla-note:20261020,Forgejo 镜像 git.cloverdo.cn/cloverdo/layla-note:20261020。测试入口 http://10.10.10.102:8080/workspace。本次不调整数据库结构,复用现有配置与数据卷。发布镜像使用 docker/Dockerfile.frontend-release,在 npm run build 通过后复用已验证的 20261019 后端和 Chromium 运行环境,仅替换前端资源;发布前检查后端源码相对该基线没有变更。

测试空间为「20261020 SQL与HTML预览验收」,包括「SQL 高亮」「HTML 单文件预览」「说明与回归」三个笔记本,共 10 篇草稿文档。包含 PostgreSQL/MySQL/SQL Server 示例、HTML/CSS 布局、内嵌脚本交互、SVG/片段、安全边界、外部资源与跳转限制及 Markdown 回归说明。测试文档默认不运行脚本,需要时在预览中手动开启。

SEED_EDITOR_PREVIEW_TEST_DATA=1 python3 scripts/seed-editor-preview.py 仅允许 10.10.10.102 的 8080/8085 测试实例,从本地私有凭据文件读取账号,创建独立验收空间,不覆盖既有笔记,完成后退出测试会话。

SQL 高亮与 HTML 单文件预览

  • SQL 编辑器参考 VS Code 的分类配色,区分关键字、类型、函数、字符串、数字、常量、变量、标识符和注释。补充常见 PostgreSQL/MySQL/SQL Server 写法、引号标识符、美元引用字符串和嵌套块注释;支持现有亮色、暗色、自定义及高对比度主题。高亮不连接数据库,不提供数据库结构语义验证。
  • .html / .htm 文档可点击右侧“HTML 预览”。渲染当前编辑内容(包括未保存修改),停止输入约 500 毫秒后更新;支持完整 HTML 或片段、内嵌 CSS、SVG 和 Data URL 图片。提供刷新预览,刷新和更新会重置预览交互状态。
  • 默认不运行脚本。需要单文件交互时,可在预览面板手动勾选“运行脚本”。关闭/切换预览文档、锁屏或页面进入后台后关闭脚本,不跨文档保存开关。
  • HTML 通过两层独立不透明源沙箱渲染。外层受控内容策略约束内层导航,内层只在明确开启时允许脚本。禁止访问主页面 DOM、Cookie、浏览器存储、认证 API,阻止普通网络请求、外部脚本/样式/图片、表单提交、弹窗及跳出预览。单文件不解析相邻文件、pict:// 或 file:// 引用,不加载 CDN 依赖。
  • 预览保持 HTML 自身配色和布局,独立滚动;Markdown 继续使用原有渲染和滚动同步。SQL 等不可预览类型不显示或继续渲染旧预览。
  • 沙箱不等于操作系统虚拟机,不能保证终止恶意死循环或限制所有浏览器资源消耗;只对可信的自有内容手动启用脚本。脚本默认关闭,预览没有任何调用笔记 API 的桥接接口。

浏览器回归:NODE_PATH=<Playwright 安装目录> node scripts/regression-editor-preview.cjs。脚本启动临时 Vite(默认 3101),使用浏览器内测试数据,结束后删除临时测试入口,不登录、不修改数据库。检查 SQL 实际分词及主题、HTML/CSS/脚本交互、导航和网络隔离、锁屏/切换关闭脚本。

20261019 登录设备管理

在「我的 → 登录设备」查看已登录、在线、离线设备数,以及设备名称、备注、IP、登录时间、最近在线和到期时间。支持修改或清空设备备注、退出单个设备、退出其他所有设备,以及展开最近 30 天退出记录(每页 20 条)。

  • 设备按浏览器 Cookie 会话统计;同一浏览器多个标签页只计一次,独立浏览器配置或独立无痕会话分别计数。IP 变化不新建设备记录。
  • 最近 2 分钟与服务器有通信视为在线;离线不会自动注销。在线时间由现有状态轮询和普通认证请求更新,数据库写入最多每 30 秒一次,不会重置自动锁屏所用的用户操作计时。
  • 登录会话保存在 PostgreSQL 的 auth_session 表中,登录凭证携带独立会话 ID。后续认证请求都会验证会话是否被注销或过期;正常联网页面通常约 2 秒检测到退出。设备离线时,在恢复联网后校验。
  • 默认有效期延续原有 24 小时逻辑;成功 TOTP/密码解锁时续期并复用同一会话,避免重复计数。重新登录同一浏览器替换旧会话,并保留该浏览器的最近备注。账号共享锁屏状态与设备会话相互独立;已退出设备必须重新登录,无法直接通过解锁恢复。
  • 退出会隐藏工作台、清理账号查询缓存并同步同一浏览器其他标签页。已打开编辑器尽量把未保存内容留在本地 IndexedDB,重新登录后沿用原有草稿恢复流程。远程退出不会自动上传未保存内容,也不会删除服务器笔记;清除浏览器本地数据将无法找回尚未上传的草稿。
  • 已退出、已替换和已过期的记录显示最近 30 天,后续登录时清理更早的记录。

首次升级到本版本,旧凭证缺少会话 ID,需要各设备重新登录一次。服务器重启保留本版本尚未失效的会话。上线前应保留数据库与配置备份:旧镜像的数据库预检不认识新增表,回退时需要配合升级前数据库备份,不能只换回旧镜像。

测试环境验收空间为「20261019 登录设备验收」,笔记本「多设备与草稿恢复」包含 5 篇测试文档。设备列表来自真实登录,测试脚本退出后不会留下伪造的在线设备。

本次镜像:cloverdo/layla-note:20261019;Forgejo 镜像:git.cloverdo.cn/cloverdo/layla-note:20261019。测试入口:http://10.10.10.102:8080/workspace。

Layla Note is a single-user web notebook built around Monaco Editor, explicit Save/Commit semantics, immutable full-text versions, and PostgreSQL-backed resources.

Development

Requirements: Go 1.24+, Node.js 22+, pnpm 11+, PostgreSQL 17+.

cp .env.example .env
cd frontend && pnpm install && pnpm dev
cd backend && go run ./cmd/noteserver

The Vite development server proxies /api to http://127.0.0.1:8080.

On a new data directory the server starts without database environment variables. Open the web page, enter the one-time setup token printed in the server log, test the PostgreSQL connection, and create the first account. The generated configuration (including the database connection and stable encryption/session secrets) is written to LAYLA_DATA_DIR/layla-note.json with mode 0600. Keep that file and its directory private. MIGRATE_ONLY is retained for legacy environment-based deployments; new installations should use the setup flow.

Docker first run

The image includes an empty /app/data/layla-note.json and runs as the unprivileged layla user. Use a Docker-managed named volume so the setup configuration survives image replacement:

docker volume create layla-note-data
docker run -d --name layla-note \
  -p 8080:8080 \
  -v layla-note-data:/app/data \
  layla-note:latest
docker logs layla-note

The same setup is available through deploy/docker-compose.yml:

docker compose -f deploy/docker-compose.yml up -d --build
docker compose -f deploy/docker-compose.yml logs layla-note

Visit http://127.0.0.1:8080, copy the one-time token from the log, and complete setup. Do not pass DATABASE_URL, APP_PASSWORD, SESSION_SECRET, or CONFIG_ENCRYPTION_KEY; they are no longer required. The named volume must be reused when replacing the container.

Data semantics

  • Save overwrites document_current and never creates a version.
  • Commit appends a full-text version only when content differs from the latest version; explicit commits still synchronize the selected formal version when Git is enabled.
  • Restore first commits the current editing content when it differs from the latest formal version, then copies historical text to the draft atomically without deleting history.
  • Browser edits are protected in IndexedDB before they are explicitly saved.
  • Picture and File resources are referenced as pict://pic_xxx and file://file_xxx.

Workspace search and editor settings

  • The top search field searches saved document names and current content in the selected workspace. Press Enter to search; results are paginated (20/page). Matching is case-insensitive literal substring matching, including % and _; trash and historical versions are excluded.
  • English/Chinese fonts and sizes are presets under My → Editor. Save settings explicitly, then refresh to apply them. Fonts not installed on the client fall back to system fonts.
  • AI references default to the current document and can be removed or extended to up to eight currently opened documents using the paperclip control in the composer. Closing a tab removes its reference. Only Send reads references, using local editing text where available. Oversized context is rejected with a visible message, never silently truncated.
  • AI settings include editable prompt presets and a connectivity test. The test uses the unsaved form values, does not read documents or save settings, and may incur a small provider charge.
  • Document details show live Unicode character counts (with/without whitespace), Chinese characters, English words, line count and UTF-8 bytes, including unsaved edits. These per-document display statistics do not query the database. The same panel also shows the saved draft size and the total size of the draft plus every historical version (KB/MB, exact bytes on hover), and the version history list shows each version's size.
  • Settings are stored in PostgreSQL. AI and Git keys are encrypted with AES-256-GCM using CONFIG_ENCRYPTION_KEY and are never returned by the API. Leaving a key blank preserves it; the explicit clear option removes it. Database backups and the encryption key must be protected separately.
  • Notebook creation/import must specify a valid workspace ID. Deleting a space requires clearing notebooks including those in trash.

Regression checks

Run go test ./... and go vet ./... in backend; run the frontend build and tests in frontend. scripts/regression.cjs additionally exercises the deployed API and headless Edge UI using Playwright. It reads login credentials from the local .env, creates uniquely named test spaces, restores the editor settings and removes its own test notebooks/spaces in cleanup. Run only against the development/test environment. TEST_BASE_URL overrides the default development server; NODE_PATH may point to an existing Playwright runtime. AI responses are mocked to avoid provider calls or charges.

AI settings persistence has an opt-in database regression: LAYLA_AI_TEST_DATABASE_URL=<test DSN> go test ./internal/httpapi -run TestAISettingsPersistenceIntegration -v (run from backend). It uses and removes a unique schema, reproduces the pre-000019 constraint failure, then verifies encrypted key creation, preservation, replacement, clearing, and isolation from assistant/Git keys. scripts/regression-ai-settings.cjs checks the same browser save flow against a disposable database clone and mock AI service. Set DISPOSABLE_AI_SETTINGS_TEST=1, TEST_BASE_URL, and TEST_AI_BASE_URL; the mock must offer regression-model and accept regression-key-first and regression-key-replaced. This script replaces the clone's AI workspace configuration; never run it against a database whose settings must be retained.

Git synchronization

  • Git configuration is scoped per project workspace and stored in PostgreSQL. Disabled configurations never enqueue new Git work; pending work pauses until re-enabled.
  • A document submit snapshots its new database version and one Git file in the same database transaction. A notebook submit snapshots each selected document. Git is pushed asynchronously in submission order; a push/path failure never rolls back or duplicates the database version and can be retried or abandoned from My → GIT repository configuration.
  • Paths are optional-prefix/notebook/folder/document. Layla Note updates only submitted paths and does not delete repository files. It refuses to overwrite a same-path remote file it does not recognize or that changed outside Layla Note.
  • Only HTTPS repositories are accepted. Tokens are not returned by the API or included in Git command arguments; encrypted tokens are stored separately from ordinary settings.
  • TestGitIntegration is opt-in (LAYLA_INTEGRATION_DATABASE_URL) and uses a disposable HTTPS Git server plus uniquely named database rows to verify single-file and notebook behavior, queue pausing, idempotent retry, file preservation, and collision protection.

Hermes Studio

Hermes Studio supports ordinary OpenAI-compatible model conversations and separately configured Hermes Agents. Ordinary models can have multiple Layla conversations. Each Hermes Agent has one fixed sidebar entry and retains its Hermes-managed context until /new or /reset rotates the external session. Both modes stream through the Layla backend and render user and assistant messages as sanitized Markdown. See docs/tasks/20260913-hermes-studio.md for configuration and event details.

Notebook and folder operations

  • Hover over notebook, folder, document names and opened document tabs to see the full name.
  • New document/folder dialogs show the notebook and complete parent path, plus the prospective path using the entered name.
  • Notebook and folder context menus download a ZIP of the latest server-saved draft from document_current, including nested directories, empty folders, pictures and attachments. Unsaved browser edits should be saved before downloading. Historical committed versions are not used. Markdown resources are bundled with relative links. The combined uncompressed export is limited to 256 MiB.
  • Folder menus lock all descendant documents, submit all descendant documents through the existing batch commit dialog, or download the subtree as ZIP. Siblings and other notebooks are excluded. Batch submission saves locally edited selected documents first.
  • Document details show the filename and full notebook/folder/file path. Click either to copy; HTTP/IP deployments have a clipboard fallback.
  • Opening another context menu closes the existing menu. Clicking away, pressing Escape, scrolling or losing focus also closes it.
  • My → Database information reports exact row counts per application table, table/index space and total size for the currently connected project database, including archive tables. It does not inspect other databases. Space includes PostgreSQL allocation overhead and differs from document text size.

PDF rendering runs as the unprivileged application user. The image explicitly restores /tmp mode 1777; an unwritable temporary directory now produces an actionable PDF availability error instead of a generic internal error.

Isolated Docker test deployment

deploy/docker-compose.test.yml runs the application on port 8080 with a dedicated PostgreSQL 17 database on its private Docker network. Set POSTGRES_PASSWORD using a private environment file and start with docker compose --env-file <private-env-file> -f deploy/docker-compose.test.yml -p layla-note-test up -d. Complete the first-run setup with database host postgres, database layla_note_test, user layla_note, and the configured password. Reuse both named volumes when upgrading. The application configuration and administrator credentials are not part of the image.

On hosts whose Docker bridge cannot reach their configured DNS server, build using docker build --network=host. NODE_IMAGE and GO_IMAGE build arguments can select accessible base-image mirrors; the frontend builder uses a 3072 MiB Node heap limit.

DISPOSABLE_NOTEBOOK_TEST=1 python3 scripts/regression-notebook-optimizations.py exercises draft ZIPs, nested scope isolation, empty folders, document paths, folder locks, subset commits, exact database counts and real Chromium PDF output against a test deployment. Supply LAYLA_TEST_CREDENTIALS (a private JSON file with username/password), or TEST_USERNAME and TEST_PASSWORD. TEST_BASE_URL selects the test service. Successful runs remove their uniquely named fixtures; --keep retains them for browser checks. Failed runs retain fixtures for diagnosis.

For browser acceptance, first run the API regression with --keep, then run scripts/regression-notebook-ui.cjs using Playwright. Set DISPOSABLE_NOTEBOOK_TEST=1, TEST_BASE_URL, and the private credential file path. It checks full-name tooltips, nested creation paths, folder commit selection, real ZIP/PDF downloads, clipboard fallback, exclusive context menus and database information in both themes. Fixtures and screenshots are written under .git/codex-audit/.

Permanent deletion of a notebook or folder also clears its picture nodes before removing the directory tree. Picture blobs shared by another notebook are retained. The notebook regression includes picture resources so its ordinary cleanup path verifies this behavior.

Download management (2026-10-09)

The left rail now includes 下载管理 below Hermes Studio and above the trash. Document source, rendered PDF, notebook/folder exports, picture originals and attachment download actions all produce an AES-256 encrypted ZIP and a persisted download record. Image preview endpoints still return image content for rendering.

  • Each successful package has a UUID, first/most recent download times, ZIP filename and size, included file count, source type and download count. Counts include bundled Markdown resources but exclude directory entries. An empty notebook/folder keeps its directory structure and includes an encrypted explanatory text file; its business file count is zero.
  • The password is the complete UUID with hyphens removed and letters converted to uppercase. It is stored in plaintext as requested. The interface shows only the first eight UUID characters; clicking them copies the complete password (including on HTTP/IP deployments).
  • Records store a versioned recipe in download_record.metadata: source kind, source ID and export method. No completed ZIP or document content snapshot is retained. 重新下载 resolves current saved data again and reuses the existing UUID/password. Current descendants are included for folder/notebook recipes; new children, renamed sources and edited drafts are reflected in the new package. Size, file count, last download time and download count are updated atomically after successful packaging; the original creation time is retained. Missing/deleted sources return an error and leave the record available.
  • All document/PDF export routes use document_current, never committed versions or supplied request content. Save unsaved editor changes before downloading. The export panel prevents downloading while its content differs from the saved draft. PDF is packaged as a .pdf file within the encrypted ZIP. An uploaded ZIP attachment stays as one original ZIP file inside the encrypted download package.
  • GET /api/v1/downloads?limit=30&offset=0 lists the authenticated user's history. POST /api/v1/downloads/{uuid}/download regenerates the package. Existing download/export routes retain their paths but now return encrypted ZIP bytes, with X-Download-ID and attachment headers. Picture download actions use GET /api/v1/pictures/{pictureId}/download; /original remains an image preview endpoint.
  • Migration 000023_download_records.sql adds the history table and user/time index. Source IDs intentionally have no foreign key so source deletion cannot remove the download history. Only successful package generation is recorded; this is generation history, not a claim that the browser completed saving every byte.
  • AES ZIPs can be opened with archive tools supporting WinZip AES, such as 7-Zip. ZIP entry names are visible in the archive directory; entry contents require the password.

Test data and acceptance

DISPOSABLE_NOTEBOOK_TEST=1 python3 scripts/seed-download-demo.py creates 下载管理验收数据 with 3 notebooks, 12 folders, 24 documents, 3 pictures and 4 attachments. It includes uncommitted saved drafts, Markdown tables/Mermaid, Chinese/emoji/long filenames, duplicate names in separate folders, empty folders and a ZIP attachment. It never overwrites existing notes and keeps its manifest in .git/codex-audit/download-demo.json. This script targets the dedicated test compose PostgreSQL database: attachments use supported legacy database binary storage because test S3 is unconfigured.

Install pyzipper for the Python API acceptance scripts (or put it in the project audit pydeps directory). scripts/regression-download-management.py verifies independent decryption, password failures, saved draft selection, regeneration after edits/new children, UUID/password reuse, empty directories, document/tree/picture/database attachment sources and real PDF contents. scripts/regression-download-ui.cjs checks the navigation location, eight-character UUID display, HTTP clipboard copying, repeat download, resource download, both themes and mobile layout. Existing notebook regression scripts now accept encrypted ZIP/PDF packages.

Context menu labels and release 20260922

Context menu actions now use concise names based on the selected object: 锁定文件夹 / 提交文件夹 / 下载文件夹, 锁定笔记本 / 提交笔记本 / 下载笔记本, and 下载文件 for the existing document download/export action. Folder and notebook operations continue to include their descendants; downloads continue to use saved drafts, encrypted ZIP packages and the download history. Document download opens the existing source/PDF export panel.

The test deployment image is cloverdo/layla-note:20260929; its Forgejo registry tag is git.cloverdo.cn/cloverdo/layla-note:20260929. Run docker compose -p layla-note-test --env-file .git/codex-audit/test.env -f deploy/docker-compose.test.yml up -d with the private test environment file. The service remains on port 8080 and retains the existing data volumes.

Download management uses compact rows with the UUID copy control and repeat-download button beside the filename. The page requests 20 records per page, showing the newest records first and providing pagination for older records. Repeated per-record password formula text has been removed; clicking the eight-character UUID still copies the full password. Download timestamps retain their full precision in hover text while the visible value uses minute precision.

The frontend build stage now uses FRONTEND_NODE_IMAGE=node:24-bookworm-slim after Node 22 Alpine segmentation faults were observed during builds. NODE_IMAGE continues to select the Alpine runtime dependency stage; these stages have separate image arguments.

20260925 个人使用优化

  • 保存携带编辑基准哈希,服务器原子校验,多个标签或设备的旧内容不能静默覆盖新草稿。冲突时展示左右对比,支持复制本地内容、保留稍后处理、采用服务器内容或确认以本地覆盖。锁定文档在服务端也禁止保存和历史恢复。
  • 单篇和批量提交跳过与最新提交内容一致的文档,减少重复历史;如已启用 Git,仍同步所选文档的已有正式版本。版本列表每次加载 20 条,通过版本号游标继续加载更早的版本。
  • 恢复历史时,将当前编辑内容(包括未保存修改)与最新正式版本比较;不同则先自动提交,一致则跳过。自动提交和恢复在一个事务里完成,失败原状保留。自动提交参与已有 Git 同步;例如恢复 v1 前自动产生 v4,之后仍可恢复 v4。
  • 主要页面按需加载,Markdown 预览延迟 250 毫秒合并输入更新,编辑状态通过精确订阅减少其他页面重绘。静态文本资源支持 gzip,带哈希的构建资源缓存一年,HTML 每次验证更新。
  • 页面使用 /workspace、/file、/pict、/download、/hermes、/trash、/profile,支持直接进入、刷新和浏览器前进后退。仍为同一个 React 应用,页面模块分别加载。
  • 下载直接向临时文件写一次加密 ZIP,避免生成明文中间 ZIP 和反复压缩。草稿与资源按文件处理,同一次任务重复引用资源通过临时磁盘缓存复用;任务结束即清理,重新下载仍按元数据查询数据库、复用原 UUID 密码。单实例同时打包 1 个任务,最多 2 个排队;解压内容总量上限 256 MiB。
  • 数据库统计缓存 60 秒,可立即强制刷新。展示草稿、历史、图片、附件、索引和其他表的分配空间,另列内容原始大小与笔记本排名。全库共享图片去重,S3 外部对象大小单列,不计入数据库物理占用。
  • 提供数据库、配置密钥和项目已登记 S3 资源的统一备份工具,以及独立临时库恢复验证;每日 03:00 备份、保留最近 7 份,详见 备份和恢复。

镜像:cloverdo/layla-note:20260925;Forgejo 镜像:git.cloverdo.cn/cloverdo/layla-note:20260925。

20260926 页面路径、下载排版与测试数据

  • 页面地址为 /workspace、/file、/pict、/download、/hermes、/trash、/profile。旧的四个地址仍可打开,并在进入应用时替换为对应的新地址;刷新和浏览器前进后退继续有效。
  • 下载管理宽屏下将名称、时间、大小、文件数、下载次数、UUID 与操作按钮放在同一行垂直居中。窄屏自动换行,各字段组保持居中;每页仍显示最近 20 条,长文件名悬浮可查看全名。
  • 专用测试库使用 PostgreSQL 17,Docker 内网主机 postgres、端口 5432、数据库 layla_note_test、用户 layla_note。密码保存在部署私有配置,不写入仓库。数据库没有映射宿主机端口;通过服务器 SSH 隧道或 docker exec -it layla-note-test-db psql -U layla_note -d layla_note_test 连接。
  • DISPOSABLE_NOTEBOOK_TEST=1 python3 scripts/seed-release-demo.py 创建独立的 20260926 页面与下载验收 工作区:3 个笔记本、12 个文件夹、24 篇文档、3 张图片、4 个附件和 29 条真实生成的下载记录,便于测试分页、长名称、中文路径、已保存未提交草稿、锁定、空目录和重复下载。已有笔记与旧验收数据保留;重复执行复用记录。
  • 镜像为 cloverdo/layla-note:20260926,Forgejo 地址为 git.cloverdo.cn/cloverdo/layla-note:20260926。部署继续使用已有数据库和配置卷,网页端口为 8080。

20260929 TOTP 与全设备账号锁屏

锁定页使用圆角矩形卡片,时间与卡片作为整体在页面水平、垂直居中。默认聚焦六格验证码第一格,填满后自动验证,也支持粘贴;底部“账号密码登录”可切换验证方式。小高度屏幕可滚动,避免内容超出后无法操作。

“我的 → TOTP 与锁屏”支持二维码或手动密钥绑定、取消绑定、重新绑定及解绑。二维码在浏览器本地生成;绑定后默认不开启,需输入当前账号密码保存“启用登录验证与账号锁屏”。重新绑定验证当前账号密码,新验证码确认成功前原绑定始终保留。解绑同时关闭验证与锁屏。

启用后,新登录先进入验证页,可以使用 TOTP,也可以使用账号密码确认。左侧“锁定网站”将锁定状态写入数据库:所有设备、标签页及随后登录的设备都受到限制,刷新与服务重启不能绕过;任一设备成功解锁后,全账号解除锁定,已经输入正确登录密码并等待验证的其他设备也同步解除。其他设备每 2 秒查询状态,回到前台时立即检查;后端对每个业务请求检查锁定状态。锁屏与退出登录不同,锁屏不会提交草稿,解锁后恢复原工作台与未保存内容。

自动锁屏默认关闭,可选 5、15、30、60 分钟。所有设备的实际操作共享活动时间;后台查询不算活动。TOTP 为 SHA-1、6 位、30 秒,允许前后一个周期的时钟偏差。连续 5 次验证失败后暂停验证 30 秒。TOTP 密钥使用已有配置加密密钥加密保存,普通状态接口不返回密钥。由于允许账号密码替代动态码,此功能是可选择验证方式的登录确认与锁屏,不是强制双因素认证。

新增迁移 000024_account_lock_totp.sql 和表 user_security,默认不启用,不改动已有笔记、账号密码和配置文件位置。发布镜像 cloverdo/layla-note:20260929,Forgejo 镜像 git.cloverdo.cn/cloverdo/layla-note:20260929。测试与恢复方法见 账号锁屏说明。旧镜像不认识新增表,回退需配合升级前数据库备份,不能只换回旧镜像。

20261018 敏感保护与资源预览

左侧“敏感保护”(/sensitive)按当前项目空间管理规则。普通词按完整连续字符串匹配,不拆字、不模糊匹配;正则使用 Go / RE2 语法。规则可作用于整个项目空间或指定笔记本,多个范围共同生效。扫描正常文档的最新已保存草稿,不扫描历史版本、垃圾站、图片 OCR 或附件二进制。

页面顶部展示指标,中部展示待处理文档和命中位置,底部展示修复历史,每页 20 条。打开修改可定位至命中位置。修改并保存后自动复查,全部敏感词清除才记录修复;部分清除仍待处理,再次出现会重新待处理。规则停用、删除、范围变化及文档移入垃圾站不计为修复。

保存、恢复、导入及目录状态变化通过数据库触发器加入检查队列。全量扫描和增量检查在后端执行,文档内容与规则读取受事务保护,旧检查结果不能覆盖新保存。队列与调度在数据库保存,重启恢复,关闭浏览器不影响执行。同一空间最多一个全量扫描任务。

“设置”支持五段 Cron(分 时 日 月 星期),时区固定 Asia/Shanghai,自动扫描默认关闭。可视化生成每天、每周、每月的 Cron;指定日期使用带年份的一次性时间,执行后自动关闭。保存前可预览后续执行时间。停机错过的调度补执行一次,随后根据当前时间计算下次执行,不重复回放所有错过的时间。

编辑器检查当前未保存内容,红色状态点优先表示敏感词,悬浮同时显示保存/提交状态;正文命中位置带红色标记。编辑器临时内容不会直接写入待处理/修复历史,保存后才更新服务器记录。文档树高亮当前编辑文档并展开父目录。

图片和附件删除使用项目确认弹窗,仍保留被引用资源的删除保护。Markdown 中 名称 支持鼠标悬浮图片预览,延迟 300 毫秒、最大 320×240,使用已有预览图接口;不改变正文、不创建下载记录。仅适用于桌面鼠标,手机继续使用现有预览方式。

迁移 000025 新增 sensitive_rule、sensitive_schedule、sensitive_scan、sensitive_issue、sensitive_history、sensitive_dirty_document。旧版本不认识新增业务表,回退需升级前数据库与配置备份。发布镜像 cloverdo/layla-note:20261018;Forgejo 镜像 git.cloverdo.cn/cloverdo/layla-note:20261018。