Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
0dbddaa
feat: um notebook (.um) container with packed images and notebook ui
sumbad Sep 23, 2026
8ebc5ce
feat: notebook structure controls and blog-style layout
sumbad Sep 23, 2026
42c9503
style: notebook gutter and controls match the blog draft view
sumbad Sep 23, 2026
88d8732
style: copy the blog gutter and controls exactly
sumbad Sep 23, 2026
044e493
feat: plus/trash section controls with delete confirmation
sumbad Sep 23, 2026
875e136
feat: bring back the undo notice after confirmed note deletion
sumbad Sep 23, 2026
5318abd
fix: notebook delete controls review pass
sumbad Sep 23, 2026
141491c
fix: make the trash icon gradation perceptible
sumbad Sep 23, 2026
3be871c
feat: keep the notebook toolbar visible at all times
sumbad Sep 23, 2026
051980b
fix: notebook images pack into the container and get their bubble menu
sumbad Sep 23, 2026
1b4e769
feat: persist notebook expansion state in the container manifest
sumbad Sep 23, 2026
37acf43
feat: expand the starter page of a new notebook
sumbad Sep 23, 2026
2374015
style: biome format remaining sources
sumbad Sep 23, 2026
1916fea
style: smaller right-aligned notebook section controls
sumbad Sep 23, 2026
7366943
style: close the page frame around the section controls
sumbad Sep 23, 2026
49ea9c7
style: restore 32px inter-page gap, icon-only trash hover
sumbad Sep 23, 2026
847d5dd
fix: put the 32px page gap between the page frames
sumbad Sep 23, 2026
6ea98c4
style: drop the um editor's vertical padding inside notebook pages
sumbad Sep 23, 2026
35bf5cc
style: keep the section gutter in one fixed spot, animate the fold
sumbad Sep 23, 2026
234e582
feat: notebook-wide search, hotkey routing, per-build element tags
sumbad Sep 24, 2026
a6c7982
fix: notebook search keeps focus in the input, highlight-only jumps
sumbad Sep 24, 2026
2863152
feat: notebook pages management — duplicate, nav sidebar, d&d reorder
sumbad Sep 24, 2026
53e6591
style: nav drawer toggle and list match the blog exactly
sumbad Sep 24, 2026
82bbe2d
fix: update toc styles
sumbad Sep 24, 2026
5dddcaa
style: nav drawer polish — icon morph, tight toc alignment, no inner …
sumbad Sep 24, 2026
79410a3
fix: nav toggle hangs outside the closed drawer, inside when open
sumbad Sep 24, 2026
12cca9a
fix: update toc nav styles
sumbad Sep 25, 2026
b99fa6d
refactor: rename notebook to notepad everywhere
sumbad Sep 25, 2026
e39a506
docs(changelog): unreleased — .um notepad container and UI
sumbad Sep 25, 2026
2b24238
feat: notepad title page — fixed cover with title/summary header, pag…
sumbad Sep 25, 2026
061b4d7
fix: title-page summary — no block drag handle, blog-matched styles f…
sumbad Sep 25, 2026
255dde1
fix: remove unused toc nav shadow
sumbad Sep 25, 2026
15fc576
feat: Smooth programmatic scrolling
sumbad Sep 26, 2026
39569f2
fix: revert icon renaming
sumbad Sep 26, 2026
726a732
fix: Image layouts in `.um` notepads are now scoped to their page
sumbad Sep 26, 2026
0e7fb85
fix: notepad save integrity and page editor lifecycle
sumbad Sep 26, 2026
1ed7ad4
fix: harden .um container IO — size caps, write guard, asset GC
sumbad Sep 26, 2026
2c2c29f
feat: um notepad — page fold persistence, fixed width, add row
sumbad Sep 27, 2026
e0019e3
refactor: um — remove inline rename, nav titles follow saves
sumbad Sep 27, 2026
3f720d4
fix: resolve all 58 tsc errors, add typecheck script and CI gate
sumbad Sep 27, 2026
609d684
test: cteate e2e tests
sumbad Sep 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: CI

on:
push:
branches:
- '**'
pull_request:

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: Use Node.js
uses: actions/setup-node@v6
with:
node-version: 24
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Cache Playwright browsers
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ hashFiles('package-lock.json') }}

- name: Install Playwright browser
run: npx playwright install --with-deps chromium

- name: Typecheck
run: npm run typecheck

- name: Lint
run: npm run lint

- name: Unit tests
run: npm test

- name: E2E tests
run: npm run test:e2e

- name: Upload Playwright report
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: playwright-report/
retention-days: 7
36 changes: 36 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,43 @@ permissions:
attestations: write

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: Use Node.js
uses: actions/setup-node@v6
with:
node-version: 24
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Cache Playwright browsers
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ hashFiles('package-lock.json') }}

- name: Install Playwright browser
run: npx playwright install --with-deps chromium

- name: Typecheck
run: npm run typecheck

- name: Lint
run: npm run lint

- name: Unit tests
run: npm test

- name: E2E tests
run: npm run test:e2e

build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@ local/
# Browser-test harness build output (see scripts/build-harness.mjs)
dist-harness/
/.harness
# Playwright E2E output (see playwright.config.ts)
/test-results/
/playwright-report/
49 changes: 49 additions & 0 deletions .zcodeignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
node_modules/
dist/
local/
*.log
.playwright-mcp/
.npmrc
.obsidian
.DS_Store
.inscriptum-log.jsonl
/.zcode
/docs/superpowers/
/export
# Local registry
/kley.lock
/.kley
# Browser-test harness build output (see scripts/build-harness.mjs)
dist-harness/
/.harness

# ===== ↑ 以上同步自 .gitignore(「从 .gitignore 同步」只重写以上部分)=====
.git/
.hg/
.svn/
bower_components/
jspm_packages/
__pycache__/
site-packages/
venv/
coverage/
htmlcov/
lcov-report/
cmakefiles/
cmake-build-*/
bazel-*/
pods/
deriveddata/
storybook-static/
playwright-report/
test-results/
allure-results/
allure-report/
cdk.out/
*.egg-info/
*.dist-info/
eggs/
pip-wheel-metadata/
wheels/
# ----- ↑ 以上为 ZCode 默认排除规则(自定义规则请写在本行下方,不会被同步/恢复改动)-----
# 自定义规则写在下方(本行提示可删除)
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- New document type: notepad (`.um`) — a single file that holds an ordered list of pages, edited one under another like one document. Pages can be added, duplicated, deleted (with confirmation and undo), renamed inline and reordered by drag & drop; each page can be collapsed, and its expanded/collapsed state is remembered.
- The first page is the notepad's title page — a fixed cover (title + summary) that can't be deleted, moved or renamed. Other pages don't need a title: their display title comes from the first line.
- Notepad navigation drawer (☰): lists all pages and jumps to the clicked one (smooth scrolling).
- Notebook-wide search (`Cmd/Ctrl+F`): finds across all pages, including collapsed ones, and jumps to the match.
- Images added inside a notepad are packed into the `.um` file itself, so the notepad is self-contained; other attachments stay as regular vault files.
- Pages created in a newer version of the plugin open with a "created in a newer version" notice instead of an editor and are preserved untouched on save.
- "New notepad" command and a Type selector (Note/Notepad) in the "New inscriptum" modal.

### Fixed

- Saving a `.um` container can no longer clobber its own manifest or note documents: asset descriptors naming a protected archive path (e.g. a hand-edited registry) are skipped at write time instead of overwriting the required entries.
- Assets are no longer garbage-collected while a notepad has a page this editor cannot interpret (newer version, unknown family, failed migration): the page is preserved verbatim, so its media stays in the archive even if its references use a shape this editor does not know.
- Opening a `.um` file is size-guarded against zip bombs: archives over a compressed-size cap are rejected before being read into memory, and decompression is bounded by a cap on the total inflated size — a hostile `.um` fails cleanly with a "too large" error instead of exhausting memory.
- Notepad saves no longer lose edits made while a save is in flight.
- Image layouts in notepads are scoped to their page: "full width" no longer spans the whole window, and wrap-layout images no longer float into the following pages.
- Toggling the plugin off/on without an app restart no longer breaks images/attachments.

## [0.9.0] - 2026-09-18

### Added
Expand Down
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,34 @@ Run tests:
npm test
```

### E2E tests

Browser tests run the real editor (ProseMirror + extensions) in Chromium via
Playwright, against a harness page with a shimmed `obsidian` module
(`src/browser-harness/`). Install the browser once, then run:

```bash
npx playwright install chromium
npm run test:e2e
```

Useful variants: `npm run test:e2e:headed` (visible browser) and
`npm run test:e2e:ui` (Playwright UI mode). CI runs the same suite on every
push (see `.github/workflows/ci.yml`).

Type-check the codebase (`vite build` does not check types; CI runs this on
every push, so keep it clean):

```bash
npm run typecheck
```

For a continuous check while refactoring:

```bash
npm run typecheck:watch
```

## Release

Releases are built automatically by GitHub Actions.
Expand Down
11 changes: 10 additions & 1 deletion eslint.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export default defineConfig(
},
parserOptions: {
projectService: {
allowDefaultProject: ["eslint.config.mts"],
allowDefaultProject: ["eslint.config.mts", "playwright.config.ts"],
},
tsconfigRootDir: import.meta.dirname,
},
Expand Down Expand Up @@ -115,6 +115,15 @@ export default defineConfig(
"obsidianmd/hardcoded-config-path": "off",
},
},
{
// The Playwright config is a Node script (process.env.CI), not plugin code.
files: ["playwright.config.ts"],
languageOptions: {
globals: {
...globals.node,
},
},
},
{
// The review does not report unused disable directives (only missing
// descriptions via `eslint-comments/require-description`), so disable
Expand Down
40 changes: 40 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,18 @@
"scripts": {
"dev": "vite build --watch --mode development",
"build": "vite build",
"typecheck": "tsc --noEmit",
"typecheck:watch": "tsc --noEmit --watch --preserveWatchOutput",
"android:debug": "node scripts/android-debug.mjs",
"ios:debug": "node scripts/ios-debug.mjs",
"deploy": "cp dist/main.js manifest.json dist/styles.css .obsidian/plugins/inscriptum/",
"release": "node scripts/release.mjs",
"test": "vitest run",
"test:watch": "vitest",
"build:harness": "node scripts/build-harness.mjs",
"test:e2e": "npm run build:harness && playwright test",
"test:e2e:headed": "npm run build:harness && playwright test --headed",
"test:e2e:ui": "npm run build:harness && playwright test --ui",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "biome format --write .",
Expand All @@ -22,6 +28,7 @@
"@web-companions/gfc": "2.5.0",
"@web-companions/h": "0.3.4",
"@web-companions/lit": "0.2.1",
"fflate": "0.8.3",
"highlight.js": "11.11.1",
"linkifyjs": "4.3.3",
"lit-html": "2.8.0",
Expand Down Expand Up @@ -52,6 +59,7 @@
"@biomejs/biome": "2.5.13",
"@eslint/js": "9.39.5",
"@eslint/json": "0.14.0",
"@playwright/test": "1.63.0",
"@types/node": "26.1.2",
"babel-plugin-transform-jsx-to-tt": "0.5.0",
"eslint": "9.39.5",
Expand Down
31 changes: 31 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { defineConfig, devices } from "@playwright/test";

const PORT = 4599;
const baseURL = `http://127.0.0.1:${PORT}`;

export default defineConfig({
testDir: "tests/e2e",
timeout: 30_000,
expect: { timeout: 5_000 },
fullyParallel: true,
forbidOnly: Boolean(process.env.CI),
retries: process.env.CI ? 1 : 0,
reporter: process.env.CI
? [["github"], ["html", { open: "never" }]]
: [["list"]],
use: {
baseURL,
screenshot: "only-on-failure",
trace: "retain-on-failure",
},
projects: [
// The editor uses standard DOM APIs; Chromium covers the e2e surface.
{ name: "chromium", use: { ...devices["Desktop Chrome"] } },
],
webServer: {
command: "node scripts/e2e-serve.mjs",
url: `${baseURL}/tests/e2e/harness/index.html`,
reuseExistingServer: !process.env.CI,
timeout: 15_000,
},
});
Loading
Loading