pagedex 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,7 +18,17 @@ By default, `pagedex` serves the current directory on `http://127.0.0.1:4321`:
18
18
 
19
19
  - `/` — React + Tiptap web UI (desktop & mobile with edge-swipe drawers + long-press tree actions)
20
20
  - `/api/v1/*` — REST API for reading, editing, searching, and Git diff/commit
21
- - `/mcp` — MCP endpoint supporting `2026-07-28` (`server/discover`), `2025-11-25` Streamable HTTP, and `2024-11-05` HTTP+SSE
21
+ - `/mcp` — MCP endpoint supporting `2026-07-28` (`server/discover`), `2025-11-25` Streamable HTTP, and `2024-11-05` HTTP+SSE (see [MCP Server Guide](./docs/MCP.md))
22
+
23
+ ## Screenshots
24
+
25
+ | Desktop Preview & Pinned Sidebar | WYSIWYG Editor (Tiptap) |
26
+ |---|---|
27
+ | ![Desktop Preview](./docs/images/desktop-preview.png) | ![Desktop Editor](./docs/images/desktop-edit.png) |
28
+
29
+ | Quick Switcher (`Cmd/Ctrl+K`) | Mobile View & Bottom Bar |
30
+ |---|---|
31
+ | ![Quick Switcher](./docs/images/desktop-switcher.png) | ![Mobile Preview](./docs/images/mobile-preview.png) |
22
32
 
23
33
  ### CLI Options
24
34
 
@@ -27,14 +37,18 @@ Usage:
27
37
  pagedex [dir] [options]
28
38
 
29
39
  Arguments:
30
- dir Directory to serve (defaults to current working directory)
40
+ dir Directory to serve (defaults to current working directory)
31
41
 
32
42
  Options:
33
- -p, --port <port> Port to listen on (default: 4321, or PORT env)
34
- -H, --host <host> Host to bind (default: 127.0.0.1, or HOST env; use 0.0.0.0 for LAN)
35
- -o, --open Open browser after starting
36
- -v, --version Show version
37
- -h, --help Show this help
43
+ -p, --port <port> Port to listen on (default: 4321, or PORT env)
44
+ -H, --host <host> Host to bind (default: 127.0.0.1, or HOST env; use 0.0.0.0 for LAN)
45
+ -o, --open Open browser after starting
46
+ --mcp <mode> MCP mode: read, write, full (default), or off (or PAGEDEX_MCP_MODE)
47
+ --mcp-git Enable MCP git_commit and git_discard tools (or PAGEDEX_MCP_GIT=1)
48
+ --mcp-token-file <path> File containing Bearer token for /mcp (or PAGEDEX_MCP_TOKEN_FILE)
49
+ --read-only Disable all REST & MCP write/delete mutations (or PAGEDEX_READ_ONLY=1)
50
+ -v, --version Show version
51
+ -h, --help Show this help
38
52
  ```
39
53
 
40
54
  ### Examples
@@ -43,8 +57,11 @@ Options:
43
57
  # Serve current folder on default port 4321
44
58
  npx pagedex
45
59
 
46
- # Serve a specific notes folder on LAN/Tailscale
47
- npx pagedex ~/vault --host 0.0.0.0 --port 4321
60
+ # Serve a specific notes folder on LAN/Tailscale with read-only MCP and Bearer token file
61
+ npx pagedex ~/vault --host 0.0.0.0 --mcp read --mcp-token-file ~/.config/pagedex/mcp-token
62
+
63
+ # Allow MCP create/edit/move + local git commit/discard without delete_note
64
+ npx pagedex ~/vault --mcp write --mcp-git
48
65
  ```
49
66
 
50
67
  ## Search Index Cache
@@ -58,5 +75,8 @@ pnpm install
58
75
  pnpm dev # Hono API/MCP on :4321 + Vite HMR on :5173
59
76
  pnpm build # Build production UI into dist/
60
77
  pnpm test # Run unit tests
78
+ pnpm test:playwright # Run Playwright E2E tests
61
79
  pnpm smoke # Run REST + multi-era MCP smoke checks
62
80
  ```
81
+
82
+ See [docs/README.md](./docs/README.md) for full documentation.
package/bin/pagedex.js CHANGED
@@ -16,11 +16,15 @@ Arguments:
16
16
  dir Directory to serve (defaults to current working directory)
17
17
 
18
18
  Options:
19
- -p, --port <port> Port to listen on (default: 4321, or PORT env)
20
- -H, --host <host> Host to bind (default: 127.0.0.1, or HOST env; use 0.0.0.0 for LAN)
21
- -o, --open Open browser after starting
22
- -v, --version Show version
23
- -h, --help Show this help
19
+ -p, --port <port> Port to listen on (default: 4321, or PORT env)
20
+ -H, --host <host> Host to bind (default: 127.0.0.1, or HOST env; use 0.0.0.0 for LAN)
21
+ -o, --open Open browser after starting
22
+ --mcp <mode> MCP mode: read, write, full (default), or off (or PAGEDEX_MCP_MODE)
23
+ --mcp-git Enable MCP git_commit and git_discard tools (or PAGEDEX_MCP_GIT=1)
24
+ --mcp-token-file <path> File containing Bearer token for /mcp (or PAGEDEX_MCP_TOKEN_FILE)
25
+ --read-only Disable all REST & MCP write/delete mutations (or PAGEDEX_READ_ONLY=1)
26
+ -v, --version Show version
27
+ -h, --help Show this help
24
28
  `);
25
29
  }
26
30
 
@@ -40,6 +44,12 @@ let targetDir = null;
40
44
  let port = process.env.PORT || '4321';
41
45
  let host = process.env.HOST || '127.0.0.1';
42
46
  let shouldOpen = false;
47
+ let mcpMode = process.env.PAGEDEX_MCP_MODE || 'full';
48
+ let mcpGit = process.env.PAGEDEX_MCP_GIT === '1' || process.env.PAGEDEX_MCP_GIT === 'true';
49
+ let mcpTokenFile = process.env.PAGEDEX_MCP_TOKEN_FILE || '';
50
+ let readOnly = process.env.PAGEDEX_READ_ONLY === '1' || process.env.PAGEDEX_READ_ONLY === 'true';
51
+
52
+ const VALID_MCP_MODES = new Set(['read', 'write', 'full', 'off']);
43
53
 
44
54
  for (let i = 0; i < args.length; i++) {
45
55
  const arg = args[i];
@@ -55,6 +65,46 @@ for (let i = 0; i < args.length; i++) {
55
65
  shouldOpen = true;
56
66
  continue;
57
67
  }
68
+ if (arg === '--read-only') {
69
+ readOnly = true;
70
+ continue;
71
+ }
72
+ if (arg === '--mcp-git') {
73
+ mcpGit = true;
74
+ continue;
75
+ }
76
+ if (arg === '--mcp') {
77
+ mcpMode = (args[++i] || '').toLowerCase();
78
+ if (!VALID_MCP_MODES.has(mcpMode)) {
79
+ console.error(`Invalid --mcp mode: ${mcpMode || '(missing)'}. Expected: read, write, full, or off`);
80
+ process.exit(1);
81
+ }
82
+ continue;
83
+ }
84
+ if (arg.startsWith('--mcp=')) {
85
+ mcpMode = arg.slice('--mcp='.length).toLowerCase();
86
+ if (!VALID_MCP_MODES.has(mcpMode)) {
87
+ console.error(`Invalid --mcp mode: ${mcpMode || '(missing)'}. Expected: read, write, full, or off`);
88
+ process.exit(1);
89
+ }
90
+ continue;
91
+ }
92
+ if (arg === '--mcp-token-file') {
93
+ mcpTokenFile = args[++i];
94
+ if (!mcpTokenFile) {
95
+ console.error('Missing value for --mcp-token-file');
96
+ process.exit(1);
97
+ }
98
+ continue;
99
+ }
100
+ if (arg.startsWith('--mcp-token-file=')) {
101
+ mcpTokenFile = arg.slice('--mcp-token-file='.length);
102
+ if (!mcpTokenFile) {
103
+ console.error('Missing value for --mcp-token-file');
104
+ process.exit(1);
105
+ }
106
+ continue;
107
+ }
58
108
  if (arg === '-p' || arg === '--port') {
59
109
  port = args[++i];
60
110
  if (!port) {
@@ -95,10 +145,28 @@ if (!fs.existsSync(resolvedRoot) || !fs.statSync(resolvedRoot).isDirectory()) {
95
145
  process.exit(1);
96
146
  }
97
147
 
148
+ if (!VALID_MCP_MODES.has(mcpMode.toLowerCase())) {
149
+ console.error(`Invalid PAGEDEX_MCP_MODE: ${mcpMode}. Expected: read, write, full, or off`);
150
+ process.exit(1);
151
+ }
152
+
153
+ if (mcpTokenFile) {
154
+ const resolvedTokenFile = path.resolve(mcpTokenFile);
155
+ if (!fs.existsSync(resolvedTokenFile) || !fs.statSync(resolvedTokenFile).isFile()) {
156
+ console.error(`MCP token file not found: ${resolvedTokenFile}`);
157
+ process.exit(1);
158
+ }
159
+ mcpTokenFile = resolvedTokenFile;
160
+ }
161
+
98
162
  process.env.PAGEDEX_ROOT = resolvedRoot;
99
163
  process.env.VAULT_ROOT = resolvedRoot;
100
164
  process.env.PORT = String(port);
101
165
  process.env.HOST = String(host);
166
+ process.env.PAGEDEX_MCP_MODE = mcpMode.toLowerCase();
167
+ if (mcpGit) process.env.PAGEDEX_MCP_GIT = '1';
168
+ if (mcpTokenFile) process.env.PAGEDEX_MCP_TOKEN_FILE = mcpTokenFile;
169
+ if (readOnly) process.env.PAGEDEX_READ_ONLY = '1';
102
170
 
103
171
  await import('../src/server/index.js');
104
172
 
@@ -0,0 +1 @@
1
+ :root{color-scheme:dark;--bg: #111216;--bg-alt: #181a20;--bg-hover: rgba(255, 255, 255, .06);--border: rgba(255, 255, 255, .09);--text: #eceef2;--muted: #9499a6;--accent: #8b5cf6;--accent-hover: #7c3aed;--accent-soft: rgba(139, 92, 246, .16);--green: #4ade80;--amber: #fbbf24;--code-bg: #16181f;--glass: rgba(24, 26, 32, .88);--font-sans: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", Roboto, sans-serif;--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace}[data-theme=light]{color-scheme:light;--bg: #ffffff;--bg-alt: #f7f7f9;--bg-hover: rgba(0, 0, 0, .05);--border: rgba(0, 0, 0, .09);--text: #18181b;--muted: #6b7280;--accent: #6d28d9;--accent-hover: #5b21b6;--accent-soft: rgba(109, 40, 217, .1);--green: #16a34a;--amber: #d97706;--code-bg: #f3f4f6;--glass: rgba(247, 247, 249, .9)}*{box-sizing:border-box}html,body,#root{margin:0;height:100%;background:var(--bg);color:var(--text);font-family:var(--font-sans);-webkit-font-smoothing:antialiased;overscroll-behavior-x:none;overflow-x:hidden}.app{display:grid;grid-template-columns:270px 1fr;height:100vh}.sidebar,.rightbar{background:var(--bg-alt);border-right:1px solid var(--border);display:flex;flex-direction:column;min-height:0}.workspace-body{flex:1;display:flex;min-height:0;min-width:0}.workspace-body.outline-right,.workspace-body.outline-hidden{flex-direction:row}.workspace-body.outline-bottom{flex-direction:column}.note-pane{flex:1;display:flex;flex-direction:column;min-width:0;min-height:0}.workspace-body.outline-bottom .note-pane{flex:7 1 0%}.rightbar{border-right:0;border-left:1px solid var(--border);padding:0;width:240px;flex-shrink:0;overflow:hidden}.workspace-body.outline-bottom .rightbar{flex:3 1 0%;width:100%;max-height:none;min-height:0;border-left:0;border-top:1px solid var(--border)}.rightbar-head{height:44px;padding:0 12px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;gap:8px;flex-shrink:0}.rightbar-body{flex:1;overflow-y:auto;padding:12px;display:flex;flex-direction:column;gap:18px}.workspace-body.outline-bottom .rightbar-body{display:grid;grid-template-columns:1fr 1fr;gap:16px;padding:10px 20px 14px}.rightbar-sec{min-width:0}.panel-subtitle{display:none}.workspace-body.outline-bottom .panel-subtitle{display:block;font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);margin:2px 0 4px}.backlinks-title{margin-top:0}.workspace-body.outline-bottom .backlinks-title{font-size:10.5px;margin:2px 0 4px}.brand{height:44px;padding:0 12px 0 14px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;gap:8px;flex-shrink:0}.brand-title{font-weight:700;font-size:14px;display:flex;align-items:center;gap:8px}.brand-logo{display:block;flex-shrink:0;border-radius:5px}.pill{font-size:11px;padding:2px 7px;border-radius:999px;background:var(--accent-soft);color:var(--green);font-weight:600}.pill.off{color:var(--amber)}.sidebar-tabs{display:grid;grid-template-columns:1fr 1fr;gap:2px;margin:10px 12px 0;padding:3px;border-radius:8px;background:var(--bg);border:1px solid var(--border)}.sidebar-tab{background:transparent;border:0;color:var(--muted);font-size:12px;font-weight:600;padding:5px 8px;border-radius:6px;cursor:pointer;font-family:inherit;display:flex;align-items:center;justify-content:center;gap:6px}.sidebar-tab:hover{color:var(--text)}.sidebar-tab.active{background:var(--bg-alt);color:var(--text);box-shadow:0 1px 2px #00000040}.search-wrap{padding:8px 12px 10px;border-bottom:1px solid var(--border)}.search{width:100%;padding:7px 10px;border-radius:6px;border:1px solid var(--border);background:var(--bg);color:var(--text);font-size:13px;outline:none}.search:focus{border-color:var(--accent)}.git-badge{font-size:10.5px;font-weight:700;min-width:18px;text-align:center;padding:1px 5px;border-radius:4px;font-family:var(--font-mono);flex-shrink:0}.git-M{color:var(--amber);background:#fbbf2424}.git-U,.git-A{color:var(--green);background:#4ade8024}.git-D{color:#f87171;background:#f8717124}.changes-bar{display:flex;align-items:center;justify-content:space-between;gap:6px;padding:6px 8px 8px;border-bottom:1px solid var(--border);margin-bottom:6px}.changes-select{background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:6px;padding:4px 8px;font-size:12px;outline:none}.commit-files{max-height:220px;overflow-y:auto;border:1px solid var(--border);border-radius:8px;background:var(--bg);padding:6px;margin-bottom:12px}.commit-file-row{display:flex;align-items:center;gap:8px;padding:5px 6px;font-size:12.5px;cursor:pointer;border-radius:6px}.commit-file-row:hover{background:var(--bg-hover)}.git-output{background:var(--code-bg);border:1px solid var(--border);border-radius:8px;padding:10px;font-family:var(--font-mono);font-size:11.5px;white-space:pre-wrap;max-height:140px;overflow-y:auto;margin-top:8px}.diff-wrap{margin:0 auto}.diff-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:12px;flex-wrap:wrap}.diff-hunk{border:1px solid var(--border);border-radius:10px;overflow:hidden;margin-bottom:14px;background:var(--code-bg)}.diff-hunk-bar{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:6px 10px;background:var(--bg-alt);border-bottom:1px solid var(--border);font-family:var(--font-mono);font-size:11.5px;color:var(--muted)}.diff-line{display:grid;grid-template-columns:36px 20px 1fr 28px;align-items:start;font-family:var(--font-mono);font-size:12.5px;line-height:1.5;padding:1px 6px}.diff-line.add{background:#4ade801f}.diff-line.del{background:#f8717121}.diff-ln{color:var(--muted);font-size:11px;-webkit-user-select:none;user-select:none;text-align:right;padding-right:8px}.diff-sig{-webkit-user-select:none;user-select:none;font-weight:700}.diff-line.add .diff-sig{color:var(--green)}.diff-line.del .diff-sig{color:#f87171}.diff-txt{white-space:pre-wrap;word-break:break-word}.diff-discard-btn{background:none;border:0;color:var(--muted);cursor:pointer;font-size:13px;padding:0 4px;border-radius:4px;line-height:1.4}.diff-discard-btn:hover{color:#f87171;background:#f8717129}.tree{flex:1;overflow-y:auto;padding:8px;font-size:13px}.tree-dir-btn,.tree-file{width:100%;text-align:left;background:none;border:0;color:var(--text);padding:5px 8px;border-radius:5px;cursor:pointer;display:flex;align-items:center;gap:6px;font-size:13px;font-family:inherit;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;-webkit-touch-callout:none;-webkit-user-select:none;user-select:none}.tree-dir-btn{color:var(--muted);font-weight:600}.tree-dir-btn:hover,.tree-file-row:hover{background:var(--bg-hover)}.tree-file-row{display:flex;align-items:center;border-radius:5px;gap:2px}.tree-file-row.active{background:var(--accent-soft)}.tree-file-row .tree-file,.tree-file-row .tree-dir-btn{flex:1;min-width:0}.tree-file-row .tree-file:hover,.tree-file-row .tree-dir-btn:hover{background:none}.tree-file.active{background:none;color:var(--accent);font-weight:600}.tree-row-more{background:none;border:0;color:var(--muted);cursor:pointer;padding:4px 6px;border-radius:4px;font-size:13px;line-height:1;opacity:0;flex-shrink:0}.tree-file-row:hover .tree-row-more,.tree-file-row.active .tree-row-more{opacity:1}.tree-row-more:hover{color:var(--text);background:var(--bg-hover)}@media(hover:none),(max-width:768px){.tree-row-more{opacity:.75}}.file-ctx-menu{position:fixed;z-index:60;background:var(--glass);-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);border:1px solid var(--border);border-radius:10px;padding:5px;min-width:170px;display:flex;flex-direction:column;gap:2px;max-height:calc(100vh - 16px);max-height:calc(100dvh - 16px);overflow-y:auto;overscroll-behavior:contain;box-shadow:0 12px 32px #00000080}.file-ctx-menu .more-item{flex-shrink:0;justify-content:flex-start;gap:8px}.file-ctx-menu-icon{width:14px;height:14px;flex-shrink:0;opacity:.85}.tree-children{padding-left:14px}.main{display:flex;flex-direction:column;min-width:0;min-height:0}.topbar{height:44px;padding:0 14px 0 18px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;gap:12px;flex-shrink:0}.topbar-left{display:flex;align-items:center;gap:8px;min-width:0}.crumbs{font-family:var(--font-mono);font-size:12px;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.actions{display:flex;gap:6px;align-items:center;position:relative}.icon-btn{width:30px;height:30px;padding:0;border-radius:7px;border:0;background:transparent;color:var(--muted);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;transition:background .12s,color .12s}.icon-btn:hover,.icon-btn:active{background:var(--bg-hover);color:var(--text)}.icon-btn.active{color:var(--accent);background:var(--accent-soft)}@media(max-width:960px){.outline-toggle-btn.active{color:var(--muted);background:transparent}}.more-menu{position:absolute;top:calc(100% + 6px);right:0;z-index:35;background:var(--glass);-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);border:1px solid var(--border);border-radius:12px;padding:5px;min-width:155px;display:flex;flex-direction:column;gap:2px;box-shadow:0 12px 32px #00000073}.more-item{width:100%;text-align:left;background:none;border:0;color:var(--text);padding:8px 10px;border-radius:8px;font-size:13px;cursor:pointer;font-family:inherit;display:flex;align-items:center;justify-content:space-between}.more-item:hover{background:var(--bg-hover)}.more-item.active{color:var(--accent);font-weight:600}.outline-menu-trigger{display:none}.outline-menu-trigger.show-desktop{display:flex}.tiptap-wrap{margin:0 auto}.tiptap-toolbar{display:flex;gap:4px;align-items:center;overflow-x:auto;padding:6px 8px;margin-bottom:16px;background:var(--glass);-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);border:1px solid var(--border);border-radius:12px;position:sticky;top:0;z-index:5}.tiptap-toolbar .btn{border-color:transparent;background:transparent;padding:5px 9px;border-radius:7px}.tiptap-toolbar .btn:hover{background:var(--bg-hover)}.tiptap-toolbar .btn.active{background:var(--accent-soft);color:var(--accent)}.tiptap-toolbar .btn.primary{background:var(--accent);color:#fff;padding:5px 12px;border-radius:8px}.tiptap-body .ProseMirror{outline:none;min-height:55vh;overflow-wrap:anywhere;word-break:break-word}.tiptap-body .ProseMirror>:first-child{margin-top:0}.btn{background:var(--bg-alt);border:1px solid var(--border);color:var(--text);border-radius:8px;padding:5px 11px;font-size:12.5px;font-weight:500;cursor:pointer;white-space:nowrap;flex-shrink:0;transition:background .12s,border-color .12s}.btn:hover{background:var(--bg-hover)}.btn.active{border-color:var(--accent);color:var(--accent);background:var(--accent-soft)}.btn.primary{background:var(--accent);border-color:var(--accent);color:#fff;font-weight:600}.btn.primary:hover{background:var(--accent-hover);border-color:var(--accent-hover)}.menu-btn{display:none;gap:5px}.backdrop{display:none}.content-scroll{flex:1;overflow-y:auto;overflow-x:hidden;min-width:0;padding:28px 36px 80px}.doc{max-width:780px;width:100%;margin:0 auto;line-height:1.65;font-size:15px;overflow-wrap:anywhere;word-break:break-word}.doc h1,.doc h2,.doc h3,.doc h4{margin-top:1.5em;margin-bottom:.5em;line-height:1.25}.doc h1{font-size:1.85em;border-bottom:1px solid var(--border);padding-bottom:.3em;margin-top:0}.doc h2{font-size:1.35em}.doc h3{font-size:1.12em}.doc p{margin:.8em 0}.doc a,.wikilink{color:var(--accent);text-decoration:none;cursor:pointer}.doc a:hover,.wikilink:hover{text-decoration:underline}.wikilink.broken{color:var(--amber);border-bottom:1px dashed var(--amber)}.doc code{font-family:var(--font-mono);font-size:.88em;background:var(--code-bg);padding:2px 5px;border-radius:4px;overflow-wrap:anywhere}.doc pre{background:var(--code-bg);border:1px solid var(--border);padding:14px 16px;border-radius:8px;max-width:100%;overflow-x:auto;font-size:13px;line-height:1.5}.doc pre code{background:none;padding:0}.doc blockquote{margin:1em 0;padding:8px 14px;border-left:3px solid var(--accent);background:var(--accent-soft);border-radius:0 6px 6px 0}.doc blockquote p{margin:.3em 0}.doc ul,.doc ol{padding-left:1.5em}.doc li{margin:.3em 0}.doc hr{border:0;border-top:1px solid var(--border);margin:1.8em 0}.doc table{display:block;max-width:100%;overflow-x:auto;border-collapse:collapse;margin:1em 0;font-size:14px}.doc th,.doc td{border:1px solid var(--border);padding:7px 10px;text-align:left}.doc th{background:var(--bg-alt)}.doc img{max-width:100%;height:auto;border-radius:8px;border:1px solid var(--border)}.raw-view{max-width:860px;width:100%;margin:0 auto;white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word;font-family:var(--font-mono);font-size:13px;line-height:1.55;background:var(--code-bg);border:1px solid var(--border);padding:18px;border-radius:8px}.hljs{color:var(--text);background:transparent}.hljs-comment,.hljs-quote{color:#7f849c;font-style:italic}.hljs-keyword,.hljs-selector-tag,.hljs-subst,.hljs-section{color:#c084fc}.hljs-number,.hljs-literal,.hljs-variable,.hljs-template-variable,.hljs-tag .hljs-attr{color:#f59e0b}.hljs-string,.hljs-doctag,.hljs-regexp{color:#4ade80}.hljs-title,.hljs-title.function_,.hljs-title.class_{color:#60a5fa}.hljs-type,.hljs-built_in,.hljs-class .hljs-title{color:#38bdf8}.hljs-attr,.hljs-attribute,.hljs-symbol,.hljs-bullet,.hljs-link{color:#f472b6}.hljs-addition{color:#4ade80;background:#4ade801f}.hljs-deletion{color:#f87171;background:#f871711f}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}[data-theme=light] .hljs-comment,[data-theme=light] .hljs-quote{color:#6b7280}[data-theme=light] .hljs-keyword,[data-theme=light] .hljs-selector-tag,[data-theme=light] .hljs-subst,[data-theme=light] .hljs-section{color:#7c3aed}[data-theme=light] .hljs-number,[data-theme=light] .hljs-literal,[data-theme=light] .hljs-variable,[data-theme=light] .hljs-template-variable,[data-theme=light] .hljs-tag .hljs-attr{color:#b45309}[data-theme=light] .hljs-string,[data-theme=light] .hljs-doctag,[data-theme=light] .hljs-regexp{color:#15803d}[data-theme=light] .hljs-title,[data-theme=light] .hljs-title.function_,[data-theme=light] .hljs-title.class_{color:#2563eb}[data-theme=light] .hljs-type,[data-theme=light] .hljs-built_in,[data-theme=light] .hljs-class .hljs-title{color:#0369a1}[data-theme=light] .hljs-attr,[data-theme=light] .hljs-attribute,[data-theme=light] .hljs-symbol,[data-theme=light] .hljs-bullet,[data-theme=light] .hljs-link{color:#be185d}.img-wrap{text-align:center}.img-wrap img{max-width:100%;max-height:80vh}.binary-card{max-width:460px;margin:40px auto;padding:28px 24px;border:1px solid var(--border);border-radius:12px;background:var(--bg-alt);text-align:center}.binary-card-name{font-size:16px;font-weight:700;margin-bottom:6px}.binary-card-path{font-size:12.5px;color:var(--muted);font-family:var(--font-mono);margin-bottom:18px}.panel-title{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);margin:8px 0}.toc-item,.backlink-item{display:block;padding:4px 6px;border-radius:4px;font-size:12.5px;color:var(--text);text-decoration:none;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.toc-item:hover,.backlink-item:hover{background:var(--bg-hover);color:var(--accent)}.toc-h2{padding-left:14px;color:var(--muted)}.toc-h3{padding-left:24px;color:var(--muted);font-size:12px}.empty-note{font-size:12px;color:var(--muted);padding:4px 6px}.fab-wrap{position:fixed;right:18px;bottom:18px;z-index:25;display:flex;flex-direction:column;align-items:flex-end;gap:8px}.fab-btn{width:48px;height:48px;border-radius:999px;border:0;background:var(--accent);color:#fff;font-size:20px;font-weight:700;cursor:pointer;display:flex;align-items:center;justify-content:center;box-shadow:0 6px 20px #00000059}.fab-menu{background:var(--bg-alt);border:1px solid var(--border);border-radius:10px;padding:6px;min-width:170px;display:flex;flex-direction:column;gap:2px;box-shadow:0 10px 28px #0006}.fab-item{width:100%;text-align:left;background:none;border:0;color:var(--text);padding:8px 10px;border-radius:6px;font-size:13px;cursor:pointer;display:flex;align-items:center;gap:8px;font-family:inherit}.fab-item:hover{background:var(--bg-hover);color:var(--accent)}.mobile-bar,.sheet-backdrop,.bottom-sheet{display:none}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;background:#000000a6;z-index:50;display:flex;align-items:center;justify-content:center;padding:12px}.modal{width:min(96vw,760px);height:min(90vh,660px);background:var(--bg-alt);border:1px solid var(--border);border-radius:14px;box-shadow:0 18px 48px #0000008c;display:flex;flex-direction:column;overflow:hidden}.modal-head{display:flex;align-items:center;justify-content:space-between;padding:14px 18px;border-bottom:1px solid var(--border);flex-shrink:0}.modal-head h2{margin:0;font-size:16px}.settings-tabs{display:flex;gap:6px;padding:10px 16px;border-bottom:1px solid var(--border);overflow-x:auto;flex-shrink:0;background:var(--bg)}.settings-tab{background:none;border:1px solid transparent;color:var(--muted);padding:6px 12px;border-radius:999px;font-size:13px;font-weight:600;cursor:pointer;white-space:nowrap;font-family:inherit}.settings-tab:hover{color:var(--text);background:var(--bg-hover)}.settings-tab.active{color:var(--accent);background:var(--accent-soft);border-color:var(--accent)}.modal-body{flex:1;overflow-y:auto;padding:18px}.field{margin-bottom:16px}.field label{display:block;font-size:12px;color:var(--muted);margin-bottom:6px;font-weight:600}.field-hint{font-size:11.5px;color:var(--muted);margin-top:4px}.field input[type=text],.field select{width:100%;padding:9px 11px;border-radius:8px;border:1px solid var(--border);background:var(--bg);color:var(--text);font-size:13px;outline:none}.field input[type=range]{width:100%}.toggle-row{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 12px;border-radius:8px;background:var(--bg);border:1px solid var(--border);margin-bottom:12px;font-size:13px;cursor:pointer}.endpoint-box{background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:10px 12px;font-family:var(--font-mono);font-size:12px;margin-bottom:8px;word-break:break-all}.modal-foot{display:flex;justify-content:space-between;gap:8px;padding:12px 18px;border-top:1px solid var(--border);flex-shrink:0}.switcher-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;background:#0000008c;z-index:45;display:flex;align-items:flex-end;justify-content:center;padding:14px}@media(min-width:769px){.switcher-backdrop{align-items:center}}.switcher{width:100%;max-width:520px;max-height:70vh;background:var(--bg-alt);border:1px solid var(--border);border-radius:16px;display:flex;flex-direction:column;overflow:hidden;box-shadow:0 18px 48px #0000008c}.switcher-head{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:8px 10px;border-bottom:1px solid var(--border)}.switcher-tabs{display:flex;gap:4px}.switcher-tab{background:none;border:0;color:var(--muted);font-size:12px;font-weight:600;padding:5px 10px;border-radius:7px;cursor:pointer;font-family:inherit}.switcher-tab:hover{color:var(--text);background:var(--bg-hover)}.switcher-tab.active{background:var(--accent-soft);color:var(--accent)}.switcher-cap{font-size:11px;color:var(--muted);font-family:var(--font-mono)}.switcher-group-title{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);padding:8px 10px 4px}.switcher-row-top{display:flex;align-items:center;justify-content:space-between;gap:8px;width:100%}.switcher-time{font-size:11px;color:var(--muted);font-family:var(--font-mono);flex-shrink:0}.switcher-list{flex:1;overflow-y:auto;padding:10px;display:flex;flex-direction:column;gap:2px}.switcher-item{width:100%;text-align:left;background:none;border:0;color:var(--text);padding:10px 12px;border-radius:10px;cursor:pointer;font-family:inherit;display:flex;flex-direction:column;gap:2px}.switcher-item:hover,.switcher-item.selected{background:var(--accent-soft);color:var(--accent)}.switcher-path{font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.switcher-snippet{font-size:12px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.switcher-input-wrap{padding:10px 12px;border-top:1px solid var(--border);display:flex;align-items:center;gap:8px;background:var(--bg-alt)}.switcher-input{flex:1;padding:10px 14px;border-radius:999px;border:1px solid var(--border);background:var(--bg);color:var(--text);font-size:14px;outline:none}.switcher-input:focus{border-color:var(--accent)}.switcher-clear{width:30px;height:30px;border-radius:999px;border:0;background:var(--bg-hover);color:var(--text);font-size:15px;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0}.folder-picker{margin-top:6px;max-height:170px;overflow-y:auto;border:1px solid var(--border);border-radius:8px;background:var(--bg);padding:4px;display:flex;flex-direction:column;gap:2px}.folder-pick-item{width:100%;text-align:left;background:none;border:0;color:var(--text);padding:6px 8px;border-radius:6px;cursor:pointer;font-size:12.5px;font-family:var(--font-mono)}.folder-pick-item:hover{background:var(--bg-hover)}.folder-pick-item.active{background:var(--accent-soft);color:var(--accent);font-weight:600}.desktop-tabs{display:flex;align-items:center;gap:4px;padding:6px 14px 0;background:var(--bg-alt);border-bottom:1px solid var(--border);overflow-x:auto}.desktop-tab{display:flex;align-items:center;gap:6px;padding:6px 10px;border-radius:8px 8px 0 0;border:1px solid transparent;border-bottom:0;background:none;color:var(--muted);font-size:12px;cursor:pointer;font-family:inherit;white-space:nowrap;max-width:180px}.desktop-tab:hover{background:var(--bg-hover);color:var(--text)}.desktop-tab.active{background:var(--bg);color:var(--text);border-color:var(--border);font-weight:600}.desktop-tab-title{overflow:hidden;text-overflow:ellipsis}.desktop-tab-close{border:0;background:none;color:var(--muted);cursor:pointer;padding:0 2px;font-size:13px;line-height:1}.desktop-tab-close:hover{color:var(--text)}.desktop-tab-add{border:0;background:none;color:var(--muted);cursor:pointer;padding:4px 8px;font-size:16px;border-radius:6px}.desktop-tab-add:hover{background:var(--bg-hover);color:var(--text)}.new-tab-view{min-height:60vh;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px}.new-tab-btn{width:210px;padding:12px 18px;border-radius:999px;border:1px solid var(--border);background:var(--bg-alt);color:var(--text);font-size:14px;font-family:inherit;cursor:pointer;display:flex;align-items:center;gap:12px}.new-tab-btn:hover,.new-tab-btn:active{background:var(--bg-hover);border-color:var(--accent);color:var(--accent)}.new-tab-btn svg{width:17px;height:17px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;flex-shrink:0}.tabs-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background:#0a0c12f0;z-index:42;display:flex;flex-direction:column;justify-content:flex-end}.tabs-grid{overflow-y:auto;padding:20px 14px 12px;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;max-width:680px;width:100%;margin:0 auto}.tab-card-wrap{display:flex;flex-direction:column;align-items:center;gap:6px}.tab-card{width:100%;height:190px;background:var(--bg-alt);border:1.5px solid var(--border);border-radius:16px;padding:12px;position:relative;cursor:pointer;overflow:hidden;text-align:left;color:var(--text);font-family:inherit;display:flex;flex-direction:column;gap:6px}.tab-card.active{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent)}.tab-card-close{position:absolute;top:8px;right:8px;width:24px;height:24px;border-radius:999px;border:0;background:var(--bg-hover);color:var(--text);font-size:14px;cursor:pointer;display:flex;align-items:center;justify-content:center}.tab-card-title{font-size:13px;font-weight:700;color:var(--accent);padding-right:26px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tab-card-preview{font-size:11px;line-height:1.45;color:var(--muted);overflow:hidden;white-space:pre-wrap}.tab-card-label{font-size:12px;font-weight:600;color:var(--text);max-width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tabs-footer{display:flex;align-items:center;justify-content:space-between;padding:12px 18px calc(14px + env(safe-area-inset-bottom,0px));border-top:1px solid var(--border);background:var(--bg-alt)}.tabs-footer-btn{background:none;border:0;color:var(--text);font-size:15px;font-weight:600;cursor:pointer;padding:4px 8px;font-family:inherit}.tabs-count-badge{width:21px;height:21px;border:1.7px solid currentColor;border-radius:5px;font-size:11px;font-weight:700;display:flex;align-items:center;justify-content:center}@media(max-width:960px){.app{grid-template-columns:230px 1fr}.outline-menu-trigger{display:flex}.backdrop.open{display:block;position:fixed;top:0;right:0;bottom:0;left:0;background:#00000080;z-index:20}.workspace-body:not(.outline-bottom) .rightbar{position:fixed;inset:0 0 0 auto;width:min(84vw,300px);z-index:30;transform:translate(100%);transition:transform .18s ease}.workspace-body:not(.outline-bottom) .rightbar.mobile-open{transform:translate(0)}}@media(max-width:768px){.app{grid-template-columns:1fr}.workspace-body.outline-bottom .rightbar-body{grid-template-columns:1fr;gap:10px;padding:10px 14px calc(74px + env(safe-area-inset-bottom,0px))}.menu-btn{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;padding:0}.menu-btn svg{width:19px;height:19px;stroke:currentColor;fill:none;stroke-width:1.9;stroke-linecap:round;stroke-linejoin:round}.icon-btn{width:34px;height:34px}.desktop-tabs,.fab-wrap{display:none}.sheet-backdrop,.bottom-sheet{display:block}.sidebar{position:fixed;inset:0 auto 0 0;width:min(84vw,310px);z-index:30;transform:translate(-100%);transition:transform .18s ease}.sidebar.open{transform:translate(0)}.backdrop.open{display:block;position:fixed;top:0;right:0;bottom:0;left:0;background:#00000080;z-index:20}.brand{height:48px;padding:0 12px 0 14px}.brand-title{font-size:15px;gap:8px}.sidebar-tabs{margin:10px 12px 0;padding:3px;gap:2px}.sidebar-tab{font-size:13px;padding:6px 8px;border-radius:6px}.search-wrap{padding:8px 12px 10px}.search{font-size:16px;padding:8px 11px;border-radius:7px}.tree{padding:8px 10px;font-size:14px}.tree-dir-btn,.tree-file{padding:8px 9px;font-size:14px;border-radius:6px}.tree-row-more{min-width:32px;min-height:32px;padding:4px 6px;font-size:15px;display:inline-flex;align-items:center;justify-content:center}.changes-bar{padding:6px 8px 8px}.changes-select{font-size:13.5px;padding:6px 9px}.topbar{height:48px;padding:0 12px}.rightbar-head{height:44px;padding:0 12px}.crumbs{font-size:13px}.btn{padding:8px 13px;font-size:13.5px;border-radius:8px}.more-menu,.file-ctx-menu{min-width:200px;padding:5px;border-radius:12px}.more-item{padding:10px 12px;font-size:14px;border-radius:8px}.new-tab-view{gap:12px}.new-tab-btn{width:min(84vw,270px);padding:14px 20px;font-size:15.5px}.new-tab-btn svg{width:19px;height:19px}.switcher-tab{font-size:13.5px;padding:7px 12px}.switcher-item{padding:12px 14px}.switcher-path{font-size:15px}.switcher-snippet{font-size:13px}.switcher-input{font-size:16px;padding:11px 16px}.switcher-clear{width:34px;height:34px;font-size:17px}.field label{font-size:13px}.field input[type=text],.field select{font-size:16px;padding:11px 12px;border-radius:9px}.folder-picker{max-height:200px;padding:6px}.folder-pick-item{padding:10px 12px;font-size:14px;border-radius:7px}.toggle-row{padding:12px 14px;font-size:14px}.commit-file-row{padding:8px 10px;font-size:14px}.content-scroll{padding:18px 16px calc(86px + env(safe-area-inset-bottom,0px))}.mobile-bar{position:fixed;left:50%;transform:translate(-50%);bottom:calc(14px + env(safe-area-inset-bottom,0px));z-index:25;display:flex;align-items:center;gap:6px;background:var(--glass);-webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);border:1px solid var(--border);border-radius:999px;padding:7px 12px;box-shadow:0 10px 30px #00000073}.mobile-bar-btn{background:none;border:0;color:var(--muted);cursor:pointer;display:flex;align-items:center;justify-content:center;width:42px;height:40px;border-radius:999px}.mobile-bar-btn svg{width:21px;height:21px;stroke:currentColor;fill:none;stroke-width:1.9;stroke-linecap:round;stroke-linejoin:round}.mobile-bar-btn.active,.mobile-bar-btn:active{color:var(--accent);background:var(--accent-soft)}.tabs-count-badge{width:23px;height:23px;font-size:12px}.sheet-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;background:#00000080;z-index:35}.bottom-sheet{position:fixed;left:0;right:0;bottom:0;z-index:36;background:var(--bg-alt);border-top:1px solid var(--border);border-radius:18px 18px 0 0;padding:10px 14px calc(18px + env(safe-area-inset-bottom,0px));box-shadow:0 -12px 36px #00000080}.sheet-handle{width:38px;height:4px;border-radius:999px;background:var(--border);margin:0 auto 12px}.sheet-list{background:var(--bg);border:1px solid var(--border);border-radius:12px;overflow:hidden}.sheet-item{width:100%;background:none;border:0;border-bottom:1px solid var(--border);color:var(--text);padding:14px 16px;font-size:15px;font-family:inherit;display:flex;align-items:center;gap:12px;cursor:pointer;text-align:left}.sheet-item:last-child{border-bottom:0}.sheet-item:active,.sheet-item:hover{background:var(--bg-hover);color:var(--accent)}.sheet-item svg{width:20px;height:20px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;flex-shrink:0;color:var(--muted)}}.media-viewer{display:flex;flex-direction:column;height:100%;min-height:480px}.media-bar{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 16px;border-bottom:1px solid var(--border);background:var(--bg-alt);font-size:13px;color:var(--muted)}.pdf-frame,.html-frame{flex:1;width:100%;height:100%;min-height:600px;border:0;background:#fff}.video-player{width:100%;max-width:960px;max-height:calc(100vh - 180px);margin:24px auto;border-radius:10px;background:#000}.csv-scroll{overflow-x:auto}.folder-picker-wrap{display:flex;flex-direction:column;gap:8px}.folder-picker-toggle{width:100%;display:flex;align-items:center;justify-content:space-between;gap:8px;padding:8px 10px;border-radius:8px;border:1px solid var(--border);background:var(--bg);color:var(--text);font-family:var(--font-mono);font-size:13px;cursor:pointer;text-align:left}.folder-picker-toggle:hover{border-color:var(--accent)}.folder-picker-value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.folder-picker-caret{color:var(--muted);flex-shrink:0}.folder-picker-panel{display:flex;flex-direction:column;gap:8px}.pinned-section{margin-bottom:10px;padding:6px;border:1px solid var(--border);border-radius:10px;background:var(--bg-elev)}.pinned-toggle{width:100%;display:flex;align-items:center;gap:6px;padding:4px 6px;border:none;border-radius:6px;background:transparent;color:var(--muted);font-size:11px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;cursor:pointer;text-align:left}.pinned-toggle:hover{background:var(--bg-hover);color:var(--text)}.pinned-icon{color:var(--accent);flex-shrink:0}.pinned-count{margin-left:auto;padding:1px 6px;border-radius:999px;border:1px solid var(--border);background:var(--bg);font-size:10px;font-weight:600;color:var(--muted)}.pinned-list{margin-top:4px}.tree-section-divider{display:flex;align-items:center;gap:8px;margin:12px 4px 6px;font-size:10px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}.tree-section-divider:before,.tree-section-divider:after{content:"";flex:1;height:1px;background:var(--border)}