Skip to content

Commit dc21417

Browse files
authored
Merge pull request #97 from jetyu/feature/snaptium
Feature/snaptium
2 parents 3309cfa + c22c878 commit dc21417

40 files changed

Lines changed: 3689 additions & 1070 deletions

‎AGENTS.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
docs\DevGuide.md
1+
docs\DevGuide.md
2+
CLAUDE.md

‎Claude.md‎

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# CLAUDE.md
2+
3+
Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.
4+
5+
**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.
6+
7+
## 1. Think Before Coding
8+
9+
**Don't assume. Don't hide confusion. Surface tradeoffs.**
10+
11+
Before implementing:
12+
- State your assumptions explicitly. If uncertain, ask.
13+
- If multiple interpretations exist, present them - don't pick silently.
14+
- If a simpler approach exists, say so. Push back when warranted.
15+
- If something is unclear, stop. Name what's confusing. Ask.
16+
17+
## 2. Simplicity First
18+
19+
**Minimum code that solves the problem. Nothing speculative.**
20+
21+
- No features beyond what was asked.
22+
- No abstractions for single-use code.
23+
- No "flexibility" or "configurability" that wasn't requested.
24+
- No error handling for impossible scenarios.
25+
- If you write 200 lines and it could be 50, rewrite it.
26+
27+
Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
28+
29+
## 3. Surgical Changes
30+
31+
**Touch only what you must. Clean up only your own mess.**
32+
33+
When editing existing code:
34+
- Don't "improve" adjacent code, comments, or formatting.
35+
- Don't refactor things that aren't broken.
36+
- Match existing style, even if you'd do it differently.
37+
- If you notice unrelated dead code, mention it - don't delete it.
38+
39+
When your changes create orphans:
40+
- Remove imports/variables/functions that YOUR changes made unused.
41+
- Don't remove pre-existing dead code unless asked.
42+
43+
The test: Every changed line should trace directly to the user's request.
44+
45+
## 4. Goal-Driven Execution
46+
47+
**Define success criteria. Loop until verified.**
48+
49+
Transform tasks into verifiable goals:
50+
- "Add validation" → "Write tests for invalid inputs, then make them pass"
51+
- "Fix the bug" → "Write a test that reproduces it, then make it pass"
52+
- "Refactor X" → "Ensure tests pass before and after"
53+
54+
For multi-step tasks, state a brief plan:
55+
```
56+
1. [Step] → verify: [check]
57+
2. [Step] → verify: [check]
58+
3. [Step] → verify: [check]
59+
```
60+
61+
Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
62+
63+
---
64+
65+
**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.
2.96 MB
Loading

‎docs/WorkbenchLayoutSpec.md‎

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Workbench Layout Spec
2+
3+
This document defines the layout contract for the workbench dashboard. The goal is to keep the dashboard visually stable, organized, and readable across desktop and narrow windows without treating the whole UI as a single scaled image.
4+
5+
## Principles
6+
7+
- Keep layout structure stable within each breakpoint.
8+
- Use shared layout tokens for spacing, card radius, card heights, and page width.
9+
- Change layout mode only at breakpoints; do not rewrite many component-level sizes inside a breakpoint.
10+
- Align cards only within the same visual row.
11+
- Prefer outer card stability with inner content scrolling on desktop.
12+
- Let narrow layouts use natural height instead of fixed card heights.
13+
- Cap the workbench content width on wide displays so the page does not stretch into sparse columns.
14+
15+
## Layout Modes
16+
17+
### Desktop: `min-width: 1121px`
18+
19+
- Use a two-column dashboard: main content plus side rail.
20+
- Main and side columns use a stable `3fr / 1fr` relationship.
21+
- The full workbench content is capped by `--workbench-layout-max-width`.
22+
- Desktop placement is controlled by parent grid areas, not by two independent natural-height columns.
23+
- `main` and `aside` remain semantic wrappers, while their direct cards participate in the desktop parent grid.
24+
- The hero, overview metrics, recent panels, and smart recommendations stay in the main column.
25+
- Today stats, growth, active tags, and topic heat stay in the side rail.
26+
- Fixed card heights are allowed only for desktop row alignment.
27+
28+
Desktop grid areas:
29+
30+
```text
31+
hero stats
32+
hero growth
33+
overview growth
34+
recent tags
35+
smart topic
36+
```
37+
38+
### Tablet: `max-width: 1120px`
39+
40+
- Collapse the workbench into one main column.
41+
- Move the side rail below the main content.
42+
- Side cards may use a two-column grid.
43+
- Topic heat and active tags can span the full width when their content benefits from room.
44+
- Fixed desktop row heights are disabled.
45+
46+
### Compact: `max-width: 900px`
47+
48+
- Use a single-column flow.
49+
- Recent panels stack vertically.
50+
- Smart recommendations stack internally.
51+
- Side cards stack vertically.
52+
- Cards use natural height.
53+
54+
### Small: `max-width: 620px`
55+
56+
- Metrics become single-column.
57+
- Action buttons can become full width.
58+
- Time labels can be hidden in dense feed rows.
59+
60+
## Row Alignment
61+
62+
Desktop row alignment is limited to these groups:
63+
64+
- Recent activity, recent questions, and active tags use `--workbench-feed-card-height`.
65+
- Smart recommendations and topic heat use `--workbench-recommendation-card-height`.
66+
67+
Other cards keep natural height. This avoids unnecessary internal scrollbars and prevents unrelated modules from forcing each other into awkward dimensions.
68+
69+
## Token Ownership
70+
71+
Workbench layout tokens live on `.workbench-dashboard`:
72+
73+
- `--workbench-layout-max-width`
74+
- `--workbench-page-padding`
75+
- `--workbench-gap`
76+
- `--workbench-card-radius`
77+
- `--workbench-sidebar-min`
78+
- `--workbench-hero-min-height`
79+
- `--workbench-feed-card-height`
80+
- `--workbench-recommendation-card-height`
81+
- `--workbench-panel-header-padding`
82+
- `--workbench-panel-body-padding`
83+
84+
Breakpoints should adjust these tokens first. Component-level overrides should be reserved for true layout-mode changes, such as switching from two columns to one column.

‎electron/assets/locales/en-US.json‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,14 +116,20 @@
116116
"contextMenu.unstar": "Remove Favorite",
117117
"dataTransfer.error.databaseNotFound": "Database directory not found in the current workspace.",
118118
"dataTransfer.error.databaseNotFoundInPackage": "No valid Database directory found in the imported package.",
119+
"dataTransfer.error.invalidNwpPackage": "Invalid NWP package, nodes.jsonl not found.",
119120
"dataTransfer.error.workspaceUnavailable": "Current workspace is unavailable.",
121+
"dataTransfer.enex.sectionDescription": "Import Evernote (.enex) files, convert them to Markdown and import (conversion may take some time)",
122+
"dataTransfer.enex.sectionTitle": "Evernote Format Notes",
123+
"dataTransfer.enexImport.dialogTitle": "Select Evernote (.enex) File",
120124
"dataTransfer.markdown.sectionDescription": "Import/Export Markdown format, suitable for data migration with other note-taking applications (incremental import)",
121125
"dataTransfer.markdown.sectionTitle": "Markdown Format Note Data",
122126
"dataTransfer.markdownExport.dialogTitle": "Select Markdown Export Directory",
123127
"dataTransfer.markdownImport.dialogTitle": "Select Markdown Source Directory",
128+
"dataTransfer.nwpImport.dialogTitle": "Select Legacy NWP Package",
124129
"dataTransfer.message.failed": "Operation failed",
125130
"dataTransfer.message.markdownExportSummary": "Markdown export completed.\nNotes: {exported}, Directories created: {dirs}, Images copied: {images}, Images skipped: {skippedImages}, Failed files: {failed}.",
126131
"dataTransfer.message.markdownImportSummary": "Markdown import completed.\nScanned: {scanned}, Imported: {imported}, Notebooks created: {notebooks}, Images copied: {images}, Files skipped: {skipped}, Failed files: {failed}.",
132+
"dataTransfer.message.nwpImportSummary": "Legacy NWP import completed.\nImported: {imported}, Skipped (Duplicate): {skipped}, Failed: {failed}.",
127133
"dataTransfer.message.sppxExportSuccess": "SPPX format file export completed:",
128134
"dataTransfer.message.sppxImportSuccess": "SPPX format file import completed, workspace reloaded.",
129135
"dataTransfer.sppx.sectionDescription": "Import/Export Snaptium format, suitable for full backup and data recovery (import will overwrite current data)",
@@ -336,6 +342,12 @@
336342
"menu.edit.undo": "Undo",
337343
"menu.file": "File",
338344
"menu.file.open": "Open",
345+
"menu.file.importMarkdown": "Import Markdown...",
346+
"menu.file.importEnex": "Import Evernote (.enex)...",
347+
"menu.file.importSppx": "Import SPPX Backup...",
348+
"menu.file.importNwp": "Import Legacy NWP Package...",
349+
"menu.file.exportMarkdown": "Export as Markdown...",
350+
"menu.file.exportSppx": "Export as SPPX Backup...",
339351
"menu.file.preferences": "Preferences",
340352
"menu.file.quit": "Quit",
341353
"menu.file.save": "Save",

‎electron/assets/locales/zh-CN.json‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,21 +118,27 @@
118118
"contextMenu.unstar": "取消收藏",
119119
"dataTransfer.error.databaseNotFound": "当前工作区未找到 Database 目录。",
120120
"dataTransfer.error.databaseNotFoundInPackage": "导入包中未找到有效的 Database 目录。",
121+
"dataTransfer.error.invalidNwpPackage": "无效的 NWP 导入包,未找到 nodes.jsonl 文件。",
121122
"dataTransfer.error.workspaceUnavailable": "当前工作区不可用。",
123+
"dataTransfer.enex.sectionDescription": "导入 Evernote (.enex) 格式文件,将其转换为 Markdown 并导入(转换过程可能耗时较长)",
124+
"dataTransfer.enex.sectionTitle": "Evernote 格式笔记数据",
125+
"dataTransfer.enexImport.dialogTitle": "选择 Evernote (.enex) 文件",
122126
"dataTransfer.markdown.sectionDescription": "导入/导出 Markdown 格式,适用于与其他笔记应用的数据迁移(增量导入)",
123127
"dataTransfer.markdown.sectionTitle": "Markdown 格式笔记数据",
124128
"dataTransfer.markdownExport.dialogTitle": "选择 Markdown 导出目录",
125129
"dataTransfer.markdownImport.dialogTitle": "选择 Markdown 源目录",
130+
"dataTransfer.nwpImport.dialogTitle": "选择旧版 NWP 导入包",
126131
"dataTransfer.message.failed": "操作失败",
127132
"dataTransfer.message.markdownExportSummary": "Markdown导出完成。\n笔记:{exported},创建目录:{dirs},复制图片:{images},跳过图片:{skippedImages},失败文件:{failed}。",
128133
"dataTransfer.message.markdownImportSummary": "Markdown导入完成。\n扫描:{scanned},导入:{imported},创建笔记本:{notebooks},复制图片:{images},跳过文件:{skipped},失败文件:{failed}。",
134+
"dataTransfer.message.nwpImportSummary": "旧版 NWP 导入完成。\n导入:{imported},跳过(重复):{skipped},失败:{failed}。",
129135
"dataTransfer.message.sppxExportSuccess": "SPPX 格式文件导出完成:",
130136
"dataTransfer.message.sppxImportSuccess": "SPPX 格式文件导入完成,工作区已重新加载。",
131137
"dataTransfer.sppx.sectionDescription": "导入/导出 Snaptium 格式,适用于完整备份与数据恢复(导入将覆盖当前数据)",
132138
"dataTransfer.sppx.sectionTitle": "SPPX 格式笔记数据",
133139
"dataTransfer.sppxImport.confirmDetail": "如需保留现有内容,请在导入前先手动备份当前工作区。\n是否继续导入?",
134140
"dataTransfer.sppxImport.confirmMessage": "导入 SPPX 格式文件将覆盖当前工作区的全部笔记数据。",
135-
"dataTransfer.sppxImport.confirmTitle": "导入SPPX格式文件",
141+
"dataTransfer.sppxImport.confirmTitle": "导入 SPPX 格式文件",
136142
"dataTransfer.sppxImport.continueButton": "继续导入",
137143
"dialog.changeNoteStoragePath": "更改笔记存储路径",
138144
"dialog.confirmResetSyncProvider": "确认要清除 {name} 的同步配置吗?",
@@ -397,6 +403,12 @@
397403
"menu.edit.undo": "撤销",
398404
"menu.file": "文件",
399405
"menu.file.open": "打开",
406+
"menu.file.importMarkdown": "导入 Markdown 文件...",
407+
"menu.file.importEnex": "导入 Evernote (.enex) 文件...",
408+
"menu.file.importSppx": "导入 Snaptium (.sppx) 格式文件...",
409+
"menu.file.importNwp": "导入 NoteWizard (.nwp) 格式文件...",
410+
"menu.file.exportMarkdown": "导出 Markdown 文件...",
411+
"menu.file.exportSppx": "导出 Snaptium (.sppx) 格式文件...",
400412
"menu.file.preferences": "首选项",
401413
"menu.file.quit": "退出",
402414
"menu.file.save": "保存",

‎electron/main/constants/ipc.constants.ts‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ export const IPC_CHANNELS = {
1515
DATA_EXPORT_MARKDOWN: 'data-transfer:export-markdown',
1616
DATA_EXPORT_SPPX: 'data-transfer:export-sppx',
1717
DATA_IMPORT_MARKDOWN: 'data-transfer:import-markdown',
18+
DATA_IMPORT_ENEX: 'data-transfer:import-enex',
19+
DATA_IMPORT_NWP: 'data-transfer:import-nwp',
1820
DATA_IMPORT_SPPX: 'data-transfer:import-sppx',
1921
E2EE_CHANGE_PASSWORD: 'e2ee:change-password',
2022
E2EE_EXPORT_RECOVERY_KEY: 'e2ee:export-recovery-key',
@@ -44,6 +46,12 @@ export const IPC_CHANNELS = {
4446
LOGGER_LOG: 'logger:log',
4547
LOGGER_OPEN_DIR: 'logger:open-dir',
4648
MENU_CHECK_FOR_UPDATES: 'menu:check-for-updates',
49+
MENU_EXPORT_MARKDOWN: 'menu:export-markdown',
50+
MENU_EXPORT_SPPX: 'menu:export-sppx',
51+
MENU_IMPORT_MARKDOWN: 'menu:import-markdown',
52+
MENU_IMPORT_ENEX: 'menu:import-enex',
53+
MENU_IMPORT_NWP: 'menu:import-nwp',
54+
MENU_IMPORT_SPPX: 'menu:import-sppx',
4755
MENU_OPEN_ABOUT: 'menu:open-about',
4856
MENU_OPEN_LICENSE: 'menu:open-license',
4957
MENU_OPEN_PREFERENCES: 'menu:open-preferences',
@@ -109,7 +117,7 @@ export const IPC_CHANNELS = {
109117
VFS_CONFIRM_RECOVER_VERSION: 'vfs:confirm-recover-version',
110118
VFS_CREATE_FILE: 'vfs:create-file',
111119
VFS_CREATE_FOLDER: 'vfs:create-folder',
112-
VFS_DELETE_NODE: 'vfs:delete-node',
120+
VFS_DELETE_NODES: 'vfs:delete-nodes',
113121
VFS_EMPTY_TRASH: 'vfs:empty-trash',
114122
VFS_GET_HISTORY: 'vfs:get-history',
115123
VFS_GET_HISTORY_CONTENT: 'vfs:get-history-content',

‎electron/main/ipc/modules/import-export.ts‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ export function registerImportExportIpcHandlers() {
1010
ipcMain.removeHandler(IPC_CHANNELS.DATA_IMPORT_SPPX);
1111
ipcMain.removeHandler(IPC_CHANNELS.DATA_EXPORT_MARKDOWN);
1212
ipcMain.removeHandler(IPC_CHANNELS.DATA_IMPORT_MARKDOWN);
13+
ipcMain.removeHandler(IPC_CHANNELS.DATA_IMPORT_ENEX);
14+
ipcMain.removeHandler(IPC_CHANNELS.DATA_IMPORT_NWP);
1315

1416
ipcMain.handle(IPC_CHANNELS.DATA_EXPORT_SPPX, async () => {
1517
return await importExportService.exportSppxPackage();
@@ -27,5 +29,13 @@ export function registerImportExportIpcHandlers() {
2729
return await importExportService.importMarkdownBatch();
2830
});
2931

32+
ipcMain.handle(IPC_CHANNELS.DATA_IMPORT_ENEX, async () => {
33+
return await importExportService.importEnex();
34+
});
35+
36+
ipcMain.handle(IPC_CHANNELS.DATA_IMPORT_NWP, async () => {
37+
return await importExportService.importNwpPackage();
38+
});
39+
3040
logger.debug('Import/export IPC handlers registered');
3141
}

‎electron/main/ipc/modules/vfs.ts‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export function registerVfsIpcHandlers() {
2828
ipcMain.removeHandler(IPC_CHANNELS.VFS_WRITE_CONTENT);
2929
ipcMain.removeHandler(IPC_CHANNELS.VFS_SAVE_IMAGE);
3030
ipcMain.removeHandler(IPC_CHANNELS.VFS_SHOW_NOTE_IN_FOLDER);
31-
ipcMain.removeHandler(IPC_CHANNELS.VFS_DELETE_NODE);
31+
ipcMain.removeHandler(IPC_CHANNELS.VFS_DELETE_NODES);
3232
ipcMain.removeHandler(IPC_CHANNELS.VFS_TOGGLE_NODE_LOCK);
3333
ipcMain.removeHandler(IPC_CHANNELS.VFS_UPDATE_NOTEBOOK_ICON_COLOR);
3434
ipcMain.removeHandler(IPC_CHANNELS.VFS_UPDATE_NOTEBOOK_ICON_EMOJI);
@@ -113,8 +113,9 @@ export function registerVfsIpcHandlers() {
113113
return vfsService.showNoteInFolder(uuidSchema.parse(nodeId));
114114
});
115115

116-
ipcMain.handle(IPC_CHANNELS.VFS_DELETE_NODE, (_event, nodeId) => {
117-
return vfsService.deleteNode(uuidSchema.parse(nodeId));
116+
ipcMain.handle(IPC_CHANNELS.VFS_DELETE_NODES, (_event, nodeIds = []) => {
117+
const schema = z.array(uuidSchema).min(1);
118+
return vfsService.deleteNodes(schema.parse(nodeIds));
118119
});
119120

120121
ipcMain.handle(IPC_CHANNELS.VFS_TOGGLE_NODE_LOCK, (_event, payload = {}) => {

0 commit comments

Comments
 (0)