pi-para 0.6.0 → 0.6.1

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.
Files changed (208) hide show
  1. package/ARCHITECTURE.md +0 -9
  2. package/CHANGELOG.md +13 -0
  3. package/CONFIGURATION.md +0 -23
  4. package/README.md +1 -29
  5. package/SETUP.md +1 -1
  6. package/STRUCTURE.md +0 -9
  7. package/dist/atomic-write.d.ts +2 -0
  8. package/dist/atomic-write.d.ts.map +1 -0
  9. package/dist/atomic-write.js +18 -0
  10. package/dist/atomic-write.js.map +1 -0
  11. package/dist/capture.d.ts +34 -0
  12. package/dist/capture.d.ts.map +1 -0
  13. package/dist/capture.js +461 -0
  14. package/dist/capture.js.map +1 -0
  15. package/dist/cli-legacy.d.ts +4 -0
  16. package/dist/cli-legacy.d.ts.map +1 -0
  17. package/dist/cli-legacy.js +41 -0
  18. package/dist/cli-legacy.js.map +1 -0
  19. package/dist/cli.d.ts +8 -0
  20. package/dist/cli.d.ts.map +1 -0
  21. package/dist/cli.js +427 -0
  22. package/dist/cli.js.map +1 -0
  23. package/dist/commands.d.ts +15 -0
  24. package/dist/commands.d.ts.map +1 -0
  25. package/dist/commands.js +719 -0
  26. package/dist/commands.js.map +1 -0
  27. package/dist/config.d.ts +94 -0
  28. package/dist/config.d.ts.map +1 -0
  29. package/dist/config.js +228 -0
  30. package/dist/config.js.map +1 -0
  31. package/dist/context.d.ts +63 -0
  32. package/dist/context.d.ts.map +1 -0
  33. package/dist/context.js +311 -0
  34. package/dist/context.js.map +1 -0
  35. package/dist/credentials.d.ts +27 -0
  36. package/dist/credentials.d.ts.map +1 -0
  37. package/dist/credentials.js +85 -0
  38. package/dist/credentials.js.map +1 -0
  39. package/dist/daemon.d.ts +49 -0
  40. package/dist/daemon.d.ts.map +1 -0
  41. package/dist/daemon.js +226 -0
  42. package/dist/daemon.js.map +1 -0
  43. package/dist/doctor.d.ts +20 -0
  44. package/dist/doctor.d.ts.map +1 -0
  45. package/dist/doctor.js +176 -0
  46. package/dist/doctor.js.map +1 -0
  47. package/dist/frontmatter.d.ts +59 -0
  48. package/dist/frontmatter.d.ts.map +1 -0
  49. package/dist/frontmatter.js +170 -0
  50. package/dist/frontmatter.js.map +1 -0
  51. package/dist/index.d.ts +21 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +342 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/ingest.d.ts +88 -0
  56. package/dist/ingest.d.ts.map +1 -0
  57. package/dist/ingest.js +105 -0
  58. package/dist/ingest.js.map +1 -0
  59. package/dist/link-discovery.d.ts +40 -0
  60. package/dist/link-discovery.d.ts.map +1 -0
  61. package/dist/link-discovery.js +122 -0
  62. package/dist/link-discovery.js.map +1 -0
  63. package/dist/link-utils.d.ts +30 -0
  64. package/dist/link-utils.d.ts.map +1 -0
  65. package/dist/link-utils.js +145 -0
  66. package/dist/link-utils.js.map +1 -0
  67. package/dist/lint.d.ts +39 -0
  68. package/dist/lint.d.ts.map +1 -0
  69. package/dist/lint.js +827 -0
  70. package/dist/lint.js.map +1 -0
  71. package/dist/maintainer.d.ts +24 -0
  72. package/dist/maintainer.d.ts.map +1 -0
  73. package/dist/maintainer.js +358 -0
  74. package/dist/maintainer.js.map +1 -0
  75. package/dist/model-resolver.d.ts +30 -0
  76. package/dist/model-resolver.d.ts.map +1 -0
  77. package/dist/model-resolver.js +71 -0
  78. package/dist/model-resolver.js.map +1 -0
  79. package/dist/paths.d.ts +20 -0
  80. package/dist/paths.d.ts.map +1 -0
  81. package/dist/paths.js +33 -0
  82. package/dist/paths.js.map +1 -0
  83. package/dist/processor.d.ts +14 -0
  84. package/dist/processor.d.ts.map +1 -0
  85. package/dist/processor.js +361 -0
  86. package/dist/processor.js.map +1 -0
  87. package/dist/qmd-providers.d.ts +12 -0
  88. package/dist/qmd-providers.d.ts.map +1 -0
  89. package/dist/qmd-providers.js +45 -0
  90. package/dist/qmd-providers.js.map +1 -0
  91. package/dist/query.d.ts +69 -0
  92. package/dist/query.d.ts.map +1 -0
  93. package/dist/query.js +120 -0
  94. package/dist/query.js.map +1 -0
  95. package/dist/raw.d.ts +57 -0
  96. package/dist/raw.d.ts.map +1 -0
  97. package/dist/raw.js +219 -0
  98. package/dist/raw.js.map +1 -0
  99. package/dist/redact.d.ts +21 -0
  100. package/dist/redact.d.ts.map +1 -0
  101. package/dist/redact.js +143 -0
  102. package/dist/redact.js.map +1 -0
  103. package/dist/repair.d.ts +11 -0
  104. package/dist/repair.d.ts.map +1 -0
  105. package/dist/repair.js +41 -0
  106. package/dist/repair.js.map +1 -0
  107. package/dist/scheduler/controls.d.ts +35 -0
  108. package/dist/scheduler/controls.d.ts.map +1 -0
  109. package/dist/scheduler/controls.js +98 -0
  110. package/dist/scheduler/controls.js.map +1 -0
  111. package/dist/scheduler/index.d.ts +48 -0
  112. package/dist/scheduler/index.d.ts.map +1 -0
  113. package/dist/scheduler/index.js +193 -0
  114. package/dist/scheduler/index.js.map +1 -0
  115. package/dist/scheduler/leases.d.ts +22 -0
  116. package/dist/scheduler/leases.d.ts.map +1 -0
  117. package/dist/scheduler/leases.js +74 -0
  118. package/dist/scheduler/leases.js.map +1 -0
  119. package/dist/scheduler/session-capture.d.ts +33 -0
  120. package/dist/scheduler/session-capture.d.ts.map +1 -0
  121. package/dist/scheduler/session-capture.js +108 -0
  122. package/dist/scheduler/session-capture.js.map +1 -0
  123. package/dist/scheduler/state.d.ts +56 -0
  124. package/dist/scheduler/state.d.ts.map +1 -0
  125. package/dist/scheduler/state.js +210 -0
  126. package/dist/scheduler/state.js.map +1 -0
  127. package/dist/scope.d.ts +54 -0
  128. package/dist/scope.d.ts.map +1 -0
  129. package/dist/scope.js +161 -0
  130. package/dist/scope.js.map +1 -0
  131. package/dist/scripts/bulk-link-fix.d.ts +9 -0
  132. package/dist/scripts/bulk-link-fix.d.ts.map +1 -0
  133. package/dist/scripts/bulk-link-fix.js +106 -0
  134. package/dist/scripts/bulk-link-fix.js.map +1 -0
  135. package/dist/session-tools.d.ts +33 -0
  136. package/dist/session-tools.d.ts.map +1 -0
  137. package/dist/session-tools.js +211 -0
  138. package/dist/session-tools.js.map +1 -0
  139. package/dist/settings.d.ts +19 -0
  140. package/dist/settings.d.ts.map +1 -0
  141. package/dist/settings.js +106 -0
  142. package/dist/settings.js.map +1 -0
  143. package/dist/setup.d.ts +29 -0
  144. package/dist/setup.d.ts.map +1 -0
  145. package/dist/setup.js +90 -0
  146. package/dist/setup.js.map +1 -0
  147. package/dist/state.d.ts +54 -0
  148. package/dist/state.d.ts.map +1 -0
  149. package/dist/state.js +176 -0
  150. package/dist/state.js.map +1 -0
  151. package/dist/status.d.ts +22 -0
  152. package/dist/status.d.ts.map +1 -0
  153. package/dist/status.js +93 -0
  154. package/dist/status.js.map +1 -0
  155. package/dist/store.d.ts +71 -0
  156. package/dist/store.d.ts.map +1 -0
  157. package/dist/store.js +282 -0
  158. package/dist/store.js.map +1 -0
  159. package/dist/summarize.d.ts +60 -0
  160. package/dist/summarize.d.ts.map +1 -0
  161. package/dist/summarize.js +266 -0
  162. package/dist/summarize.js.map +1 -0
  163. package/dist/tag-registry.d.ts +56 -0
  164. package/dist/tag-registry.d.ts.map +1 -0
  165. package/dist/tag-registry.js +146 -0
  166. package/dist/tag-registry.js.map +1 -0
  167. package/dist/templates/prompts.d.ts +51 -0
  168. package/dist/templates/prompts.d.ts.map +1 -0
  169. package/dist/templates/prompts.js +203 -0
  170. package/dist/templates/prompts.js.map +1 -0
  171. package/dist/tools.d.ts +26 -0
  172. package/dist/tools.d.ts.map +1 -0
  173. package/dist/tools.js +1198 -0
  174. package/dist/tools.js.map +1 -0
  175. package/dist/watcher.d.ts +25 -0
  176. package/dist/watcher.d.ts.map +1 -0
  177. package/dist/watcher.js +84 -0
  178. package/dist/watcher.js.map +1 -0
  179. package/dist/webui/client/dist/index.html +1885 -0
  180. package/dist/webui/server.d.ts +31 -0
  181. package/dist/webui/server.d.ts.map +1 -0
  182. package/dist/webui/server.js +566 -0
  183. package/dist/webui/server.js.map +1 -0
  184. package/dist/wiki-tool-guidance.d.ts +6 -0
  185. package/dist/wiki-tool-guidance.d.ts.map +1 -0
  186. package/dist/wiki-tool-guidance.js +70 -0
  187. package/dist/wiki-tool-guidance.js.map +1 -0
  188. package/dist/wiki.d.ts +108 -0
  189. package/dist/wiki.d.ts.map +1 -0
  190. package/dist/wiki.js +497 -0
  191. package/dist/wiki.js.map +1 -0
  192. package/package.json +2 -1
  193. package/scripts/build-assets.mjs +1 -13
  194. package/skills/setup/SKILL.md +1 -1
  195. package/src/cli.ts +0 -70
  196. package/src/config.ts +0 -25
  197. package/src/index.ts +1 -38
  198. package/src/templates/prompts.ts +0 -56
  199. package/scripts/gepa/dataset.py +0 -192
  200. package/scripts/gepa/lm_providers.py +0 -472
  201. package/scripts/gepa/metric.py +0 -279
  202. package/scripts/gepa/optimize.py +0 -367
  203. package/scripts/gepa/program.py +0 -99
  204. package/scripts/gepa/pyproject.toml +0 -19
  205. package/scripts/gepa/run-all.sh +0 -53
  206. package/scripts/gepa/wiki_reader.py +0 -113
  207. package/src/gepa/index.ts +0 -287
  208. package/src/gepa/optimized/capture-prompt.txt +0 -65
@@ -0,0 +1,1885 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>pi-para Wiki</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
10
+ <script src="https://unpkg.com/react@18/umd/react.production.min.js" crossorigin></script>
11
+ <script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js" crossorigin></script>
12
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
13
+ <script src="https://d3js.org/d3.v7.min.js"></script>
14
+ <style>
15
+ /* ============================================================
16
+ DESIGN TOKENS
17
+ Semantic color tokens, type scale, spacing scale, shadows.
18
+ Dark-first with light-mode override via [data-theme="light"].
19
+ ============================================================ */
20
+ :root {
21
+ /* --- Color tokens (dark, default) --- */
22
+ --bg: #0f172a;
23
+ --bg-alt: #1e293b;
24
+ --surface: #334155;
25
+ --surface-hover: #3e4d66;
26
+ --text: #f1f5f9;
27
+ --text-secondary: #94a3b8;
28
+ --text-muted: #64748b;
29
+ --accent: #60a5fa;
30
+ --accent-hover: #93c5fd;
31
+ --accent-subtle: rgba(96,165,250,0.12);
32
+ --green: #34d399;
33
+ --green-subtle: rgba(52,211,153,0.12);
34
+ --red: #f87171;
35
+ --red-subtle: rgba(248,113,113,0.12);
36
+ --yellow: #fbbf24;
37
+ --yellow-subtle: rgba(251,191,36,0.12);
38
+ --border: #334155;
39
+ --border-subtle: rgba(255,255,255,0.06);
40
+ --ring: rgba(96,165,250,0.5);
41
+ --shadow-sm: 0 1px 2px rgba(0,0,0,0.3);
42
+ --shadow-md: 0 4px 12px rgba(0,0,0,0.4);
43
+ --shadow-lg: 0 8px 24px rgba(0,0,0,0.5);
44
+ --overlay: rgba(0,0,0,0.6);
45
+
46
+ /* --- Typography --- */
47
+ --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
48
+ --font-mono: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace;
49
+
50
+ /* Type scale (major-third ~1.25) */
51
+ --text-xs: 0.75rem; /* 12px */
52
+ --text-sm: 0.875rem; /* 14px */
53
+ --text-base: 1rem; /* 16px */
54
+ --text-lg: 1.125rem; /* 18px */
55
+ --text-xl: 1.25rem; /* 20px */
56
+ --text-2xl: 1.5rem; /* 24px */
57
+ --text-3xl: 1.875rem; /* 30px */
58
+
59
+ --leading-tight: 1.25;
60
+ --leading-normal: 1.6;
61
+ --leading-relaxed: 1.75;
62
+
63
+ /* --- Spacing (4px base) --- */
64
+ --sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px;
65
+ --sp-5: 20px; --sp-6: 24px; --sp-8: 32px; --sp-10: 40px;
66
+ --sp-12: 48px; --sp-16: 64px;
67
+
68
+ /* --- Radii --- */
69
+ --radius-sm: 4px;
70
+ --radius-md: 8px;
71
+ --radius-lg: 12px;
72
+ --radius-full: 9999px;
73
+
74
+ /* --- Transitions --- */
75
+ --duration-fast: 150ms;
76
+ --duration-normal: 200ms;
77
+ --duration-slow: 300ms;
78
+ --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
79
+ --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
80
+ }
81
+
82
+ /* --- Light mode override --- */
83
+ [data-theme="light"] {
84
+ --bg: #f8fafc;
85
+ --bg-alt: #f1f5f9;
86
+ --surface: #e2e8f0;
87
+ --surface-hover: #cbd5e1;
88
+ --text: #0f172a;
89
+ --text-secondary: #475569;
90
+ --text-muted: #94a3b8;
91
+ --accent: #2563eb;
92
+ --accent-hover: #1d4ed8;
93
+ --accent-subtle: rgba(37,99,235,0.08);
94
+ --green: #16a34a;
95
+ --green-subtle: rgba(22,163,74,0.08);
96
+ --red: #dc2626;
97
+ --red-subtle: rgba(220,38,38,0.08);
98
+ --yellow: #d97706;
99
+ --yellow-subtle: rgba(217,119,6,0.08);
100
+ --border: #e2e8f0;
101
+ --border-subtle: rgba(0,0,0,0.04);
102
+ --ring: rgba(37,99,235,0.4);
103
+ --shadow-sm: 0 1px 2px rgba(0,0,0,0.06);
104
+ --shadow-md: 0 4px 12px rgba(0,0,0,0.08);
105
+ --shadow-lg: 0 8px 24px rgba(0,0,0,0.12);
106
+ --overlay: rgba(0,0,0,0.4);
107
+ }
108
+
109
+ /* ============================================================
110
+ RESET & BASE
111
+ ============================================================ */
112
+ *, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
113
+
114
+ body {
115
+ font-family: var(--font-sans);
116
+ font-size: var(--text-base);
117
+ line-height: var(--leading-normal);
118
+ background: var(--bg);
119
+ color: var(--text);
120
+ -webkit-font-smoothing: antialiased;
121
+ -moz-osx-font-smoothing: grayscale;
122
+ }
123
+
124
+ a { color: var(--accent); text-decoration: none; }
125
+ a:hover { text-decoration: underline; }
126
+
127
+ /* Global focus ring */
128
+ :focus-visible {
129
+ outline: 2px solid var(--ring);
130
+ outline-offset: 2px;
131
+ border-radius: var(--radius-sm);
132
+ }
133
+
134
+ /* Skip link */
135
+ .skip-link {
136
+ position: absolute;
137
+ top: -100%;
138
+ left: var(--sp-4);
139
+ padding: var(--sp-2) var(--sp-4);
140
+ background: var(--accent);
141
+ color: var(--bg);
142
+ border-radius: var(--radius-md);
143
+ font-weight: 600;
144
+ z-index: 1000;
145
+ transition: top var(--duration-fast) var(--ease-out);
146
+ }
147
+ .skip-link:focus { top: var(--sp-2); }
148
+
149
+ /* Reduced motion */
150
+ @media (prefers-reduced-motion: reduce) {
151
+ *, *::before, *::after {
152
+ animation-duration: 0.01ms !important;
153
+ transition-duration: 0.01ms !important;
154
+ }
155
+ }
156
+
157
+ /* ============================================================
158
+ LAYOUT SHELL
159
+ ============================================================ */
160
+ .app { display: flex; height: 100vh; overflow: hidden; }
161
+
162
+ /* --- Sidebar --- */
163
+ .sidebar {
164
+ width: 280px;
165
+ background: var(--bg-alt);
166
+ border-right: 1px solid var(--border);
167
+ overflow-y: auto;
168
+ flex-shrink: 0;
169
+ display: flex;
170
+ flex-direction: column;
171
+ transition: transform var(--duration-normal) var(--ease-out);
172
+ }
173
+ .sidebar-header {
174
+ padding: var(--sp-4) var(--sp-4) var(--sp-3);
175
+ display: flex;
176
+ align-items: center;
177
+ gap: var(--sp-3);
178
+ border-bottom: 1px solid var(--border);
179
+ flex-shrink: 0;
180
+ }
181
+ .sidebar-header .logo {
182
+ display: flex;
183
+ align-items: center;
184
+ gap: var(--sp-2);
185
+ }
186
+ .sidebar-header .logo svg { color: var(--accent); flex-shrink: 0; }
187
+ .sidebar-header .logo-text {
188
+ font-size: var(--text-sm);
189
+ font-weight: 700;
190
+ letter-spacing: -0.01em;
191
+ color: var(--text);
192
+ }
193
+ .sidebar-header .logo-sub {
194
+ font-size: var(--text-xs);
195
+ color: var(--text-muted);
196
+ font-weight: 400;
197
+ }
198
+ .sidebar-nav { flex: 1; overflow-y: auto; padding: var(--sp-2) 0; }
199
+
200
+ /* Sidebar sections */
201
+ .sidebar-section { margin-bottom: var(--sp-1); }
202
+ .sidebar-section-header {
203
+ display: flex;
204
+ align-items: center;
205
+ gap: var(--sp-2);
206
+ padding: var(--sp-2) var(--sp-4);
207
+ font-size: var(--text-xs);
208
+ font-weight: 600;
209
+ color: var(--text-muted);
210
+ text-transform: uppercase;
211
+ letter-spacing: 0.05em;
212
+ cursor: pointer;
213
+ user-select: none;
214
+ border: none;
215
+ background: none;
216
+ width: 100%;
217
+ text-align: left;
218
+ border-radius: 0;
219
+ min-height: 36px;
220
+ }
221
+ .sidebar-section-header:hover { color: var(--text-secondary); }
222
+ .sidebar-section-header svg {
223
+ transition: transform var(--duration-fast) var(--ease-out);
224
+ flex-shrink: 0;
225
+ }
226
+ .sidebar-section.open .sidebar-section-header svg {
227
+ transform: rotate(90deg);
228
+ }
229
+ .sidebar-section-header .count {
230
+ margin-left: auto;
231
+ background: var(--surface);
232
+ padding: 0 var(--sp-2);
233
+ border-radius: var(--radius-full);
234
+ font-size: 11px;
235
+ line-height: 20px;
236
+ min-width: 20px;
237
+ text-align: center;
238
+ }
239
+
240
+ .sidebar-list { list-style: none; display: none; padding-bottom: var(--sp-2); }
241
+ .sidebar-section.open .sidebar-list { display: block; }
242
+
243
+ .sidebar-item {
244
+ display: flex;
245
+ align-items: center;
246
+ gap: var(--sp-2);
247
+ padding: var(--sp-2) var(--sp-4) var(--sp-2) var(--sp-8);
248
+ cursor: pointer;
249
+ font-size: var(--text-sm);
250
+ color: var(--text-secondary);
251
+ white-space: nowrap;
252
+ overflow: hidden;
253
+ text-overflow: ellipsis;
254
+ border-radius: var(--radius-md);
255
+ margin: 0 var(--sp-2);
256
+ min-height: 36px;
257
+ transition: background var(--duration-fast) var(--ease-out),
258
+ color var(--duration-fast) var(--ease-out);
259
+ }
260
+ .sidebar-item:hover {
261
+ background: var(--surface);
262
+ color: var(--text);
263
+ }
264
+ .sidebar-item.active {
265
+ background: var(--accent-subtle);
266
+ color: var(--accent);
267
+ font-weight: 500;
268
+ }
269
+
270
+ /* --- Top bar / Nav --- */
271
+ .topbar {
272
+ display: flex;
273
+ align-items: center;
274
+ gap: var(--sp-2);
275
+ padding: var(--sp-2) var(--sp-4);
276
+ background: var(--bg-alt);
277
+ border-bottom: 1px solid var(--border);
278
+ flex-shrink: 0;
279
+ min-height: 52px;
280
+ flex-wrap: wrap;
281
+ }
282
+
283
+ .nav-btn {
284
+ display: inline-flex;
285
+ align-items: center;
286
+ gap: var(--sp-2);
287
+ padding: var(--sp-2) var(--sp-3);
288
+ background: transparent;
289
+ border: 1px solid transparent;
290
+ color: var(--text-secondary);
291
+ border-radius: var(--radius-md);
292
+ cursor: pointer;
293
+ font-size: var(--text-sm);
294
+ font-family: var(--font-sans);
295
+ font-weight: 500;
296
+ min-height: 36px;
297
+ min-width: 36px;
298
+ justify-content: center;
299
+ transition: background var(--duration-fast) var(--ease-out),
300
+ color var(--duration-fast) var(--ease-out),
301
+ border-color var(--duration-fast) var(--ease-out);
302
+ }
303
+ .nav-btn:hover {
304
+ background: var(--surface);
305
+ color: var(--text);
306
+ }
307
+ .nav-btn.active {
308
+ background: var(--accent-subtle);
309
+ color: var(--accent);
310
+ border-color: var(--accent);
311
+ }
312
+ .nav-btn svg { flex-shrink: 0; }
313
+
314
+ /* Hamburger - hidden on desktop */
315
+ .hamburger { display: none; }
316
+
317
+ /* Search input in topbar */
318
+ .search-wrapper {
319
+ flex: 1;
320
+ min-width: 180px;
321
+ max-width: 400px;
322
+ margin-left: auto;
323
+ position: relative;
324
+ }
325
+ .search-wrapper svg {
326
+ position: absolute;
327
+ left: var(--sp-3);
328
+ top: 50%;
329
+ transform: translateY(-50%);
330
+ color: var(--text-muted);
331
+ pointer-events: none;
332
+ }
333
+ .search-input {
334
+ width: 100%;
335
+ padding: var(--sp-2) var(--sp-3) var(--sp-2) var(--sp-10);
336
+ background: var(--bg);
337
+ border: 1px solid var(--border);
338
+ color: var(--text);
339
+ border-radius: var(--radius-md);
340
+ font-size: var(--text-sm);
341
+ font-family: var(--font-sans);
342
+ min-height: 36px;
343
+ transition: border-color var(--duration-fast) var(--ease-out),
344
+ box-shadow var(--duration-fast) var(--ease-out);
345
+ }
346
+ .search-input::placeholder { color: var(--text-muted); }
347
+ .search-input:focus {
348
+ outline: none;
349
+ border-color: var(--accent);
350
+ box-shadow: 0 0 0 3px var(--ring);
351
+ }
352
+
353
+ /* Scope filter */
354
+ .scope-filter {
355
+ display: inline-flex;
356
+ align-items: center;
357
+ gap: var(--sp-1);
358
+ }
359
+ .scope-filter select {
360
+ padding: var(--sp-1) var(--sp-3) var(--sp-1) var(--sp-2);
361
+ background: var(--bg);
362
+ border: 1px solid var(--border);
363
+ color: var(--text-secondary);
364
+ border-radius: var(--radius-md);
365
+ font-size: var(--text-xs);
366
+ font-family: var(--font-sans);
367
+ font-weight: 500;
368
+ min-height: 32px;
369
+ cursor: pointer;
370
+ transition: border-color var(--duration-fast) var(--ease-out);
371
+ appearance: none;
372
+ padding-right: var(--sp-6);
373
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
374
+ background-repeat: no-repeat;
375
+ background-position: right 8px center;
376
+ }
377
+ .scope-filter select:focus {
378
+ outline: none;
379
+ border-color: var(--accent);
380
+ box-shadow: 0 0 0 3px var(--ring);
381
+ }
382
+ .scope-filter-label {
383
+ font-size: var(--text-xs);
384
+ color: var(--text-muted);
385
+ font-weight: 500;
386
+ }
387
+ .scope-badge {
388
+ display: inline-flex;
389
+ align-items: center;
390
+ gap: var(--sp-1);
391
+ padding: 1px var(--sp-2);
392
+ background: var(--accent-subtle);
393
+ color: var(--accent);
394
+ border-radius: var(--radius-sm);
395
+ font-size: 11px;
396
+ font-weight: 600;
397
+ margin-left: var(--sp-2);
398
+ }
399
+
400
+ /* Theme toggle */
401
+ .theme-toggle {
402
+ display: inline-flex;
403
+ align-items: center;
404
+ justify-content: center;
405
+ width: 36px;
406
+ height: 36px;
407
+ background: transparent;
408
+ border: 1px solid transparent;
409
+ color: var(--text-secondary);
410
+ border-radius: var(--radius-md);
411
+ cursor: pointer;
412
+ transition: background var(--duration-fast) var(--ease-out),
413
+ color var(--duration-fast) var(--ease-out);
414
+ }
415
+ .theme-toggle:hover { background: var(--surface); color: var(--text); }
416
+
417
+ /* --- Main content area --- */
418
+ .main-wrapper {
419
+ flex: 1;
420
+ display: flex;
421
+ flex-direction: column;
422
+ overflow: hidden;
423
+ min-width: 0;
424
+ }
425
+ .main-scroll {
426
+ flex: 1;
427
+ overflow-y: auto;
428
+ scrollbar-width: thin;
429
+ scrollbar-color: var(--surface) transparent;
430
+ }
431
+ .main-scroll::-webkit-scrollbar { width: 8px; }
432
+ .main-scroll::-webkit-scrollbar-track { background: transparent; }
433
+ .main-scroll::-webkit-scrollbar-thumb {
434
+ background: var(--surface);
435
+ border-radius: var(--radius-full);
436
+ border: 2px solid transparent;
437
+ background-clip: padding-box;
438
+ }
439
+ .main-scroll::-webkit-scrollbar-thumb:hover { background: var(--surface-hover); }
440
+ .main {
441
+ max-width: 860px;
442
+ width: 100%;
443
+ margin: 0 auto;
444
+ padding: var(--sp-6) var(--sp-8);
445
+ }
446
+ .main.main-wide {
447
+ max-width: none;
448
+ }
449
+
450
+ /* ============================================================
451
+ RESPONSIVE
452
+ ============================================================ */
453
+ @media (max-width: 768px) {
454
+ .sidebar {
455
+ position: fixed;
456
+ top: 0; left: 0; bottom: 0;
457
+ z-index: 200;
458
+ transform: translateX(-100%);
459
+ box-shadow: var(--shadow-lg);
460
+ }
461
+ .sidebar.open { transform: translateX(0); }
462
+ .hamburger { display: inline-flex; }
463
+ .main { padding: var(--sp-4); }
464
+ .topbar { padding: var(--sp-2) var(--sp-3); gap: var(--sp-1); }
465
+ .topbar .scope-filter { display: none; }
466
+ .search-wrapper { max-width: none; min-width: 120px; }
467
+ }
468
+ @media (max-width: 480px) {
469
+ .nav-btn span.nav-label { display: none; }
470
+ .main { padding: var(--sp-3); }
471
+ }
472
+
473
+ /* Overlay */
474
+ .overlay {
475
+ display: none;
476
+ position: fixed; top: 0; left: 0; right: 0; bottom: 0;
477
+ background: var(--overlay);
478
+ z-index: 199;
479
+ opacity: 0;
480
+ transition: opacity var(--duration-normal) var(--ease-out);
481
+ }
482
+ .overlay.show { display: block; opacity: 1; }
483
+
484
+ /* ============================================================
485
+ CONTENT TYPOGRAPHY
486
+ ============================================================ */
487
+ .content { padding: var(--sp-4) 0; }
488
+ .content h1 {
489
+ font-size: var(--text-2xl);
490
+ font-weight: 700;
491
+ line-height: var(--leading-tight);
492
+ margin-bottom: var(--sp-2);
493
+ letter-spacing: -0.02em;
494
+ }
495
+ .content h2 {
496
+ font-size: var(--text-xl);
497
+ font-weight: 600;
498
+ margin: var(--sp-6) 0 var(--sp-2);
499
+ color: var(--accent);
500
+ line-height: var(--leading-tight);
501
+ }
502
+ .content h3 {
503
+ font-size: var(--text-lg);
504
+ font-weight: 600;
505
+ margin: var(--sp-4) 0 var(--sp-1);
506
+ line-height: var(--leading-tight);
507
+ }
508
+ .content h4 {
509
+ font-size: var(--text-base);
510
+ font-weight: 600;
511
+ margin: var(--sp-3) 0 var(--sp-1);
512
+ }
513
+ .content p { margin: var(--sp-2) 0; line-height: var(--leading-relaxed); }
514
+ .content ul, .content ol { margin: var(--sp-2) 0 var(--sp-2) var(--sp-6); }
515
+ .content li { margin: var(--sp-1) 0; line-height: var(--leading-relaxed); }
516
+ .content code {
517
+ background: var(--surface);
518
+ padding: 2px 6px;
519
+ border-radius: var(--radius-sm);
520
+ font-family: var(--font-mono);
521
+ font-size: 0.9em;
522
+ }
523
+ .content pre {
524
+ background: var(--bg-alt);
525
+ border: 1px solid var(--border);
526
+ padding: var(--sp-4);
527
+ border-radius: var(--radius-md);
528
+ overflow-x: auto;
529
+ margin: var(--sp-3) 0;
530
+ }
531
+ .content pre code { background: none; padding: 0; font-size: var(--text-sm); }
532
+ .content blockquote {
533
+ border-left: 3px solid var(--accent);
534
+ padding-left: var(--sp-4);
535
+ color: var(--text-secondary);
536
+ margin: var(--sp-3) 0;
537
+ font-style: italic;
538
+ }
539
+ .content a.wikilink {
540
+ color: var(--accent);
541
+ background: var(--accent-subtle);
542
+ padding: 1px 4px;
543
+ border-radius: var(--radius-sm);
544
+ font-weight: 500;
545
+ }
546
+ .content a.wikilink:hover { background: var(--accent); color: var(--bg); text-decoration: none; }
547
+
548
+ /* ============================================================
549
+ COMPONENTS
550
+ ============================================================ */
551
+
552
+ /* --- Tags / Chips --- */
553
+ .tags { display: flex; gap: var(--sp-2); flex-wrap: wrap; margin: var(--sp-3) 0; }
554
+ .tag {
555
+ display: inline-flex;
556
+ align-items: center;
557
+ gap: var(--sp-1);
558
+ background: var(--surface);
559
+ padding: var(--sp-1) var(--sp-3);
560
+ border-radius: var(--radius-full);
561
+ font-size: var(--text-xs);
562
+ font-weight: 500;
563
+ color: var(--text-secondary);
564
+ line-height: 1.5;
565
+ }
566
+ .tag.para {
567
+ background: var(--accent-subtle);
568
+ color: var(--accent);
569
+ font-weight: 600;
570
+ }
571
+ .tag.scope { background: var(--green-subtle); color: var(--green); }
572
+
573
+ /* --- Buttons --- */
574
+ .btn {
575
+ display: inline-flex;
576
+ align-items: center;
577
+ gap: var(--sp-2);
578
+ padding: var(--sp-2) var(--sp-4);
579
+ border: none;
580
+ border-radius: var(--radius-md);
581
+ cursor: pointer;
582
+ font-size: var(--text-sm);
583
+ font-weight: 500;
584
+ font-family: var(--font-sans);
585
+ min-height: 36px;
586
+ transition: background var(--duration-fast) var(--ease-out),
587
+ opacity var(--duration-fast) var(--ease-out),
588
+ box-shadow var(--duration-fast) var(--ease-out);
589
+ }
590
+ .btn:disabled { opacity: 0.5; cursor: not-allowed; }
591
+ .btn svg { flex-shrink: 0; }
592
+
593
+ .btn-primary { background: var(--accent); color: #fff; }
594
+ .btn-primary:hover:not(:disabled) { background: var(--accent-hover); box-shadow: var(--shadow-sm); }
595
+
596
+ .btn-secondary {
597
+ background: var(--surface);
598
+ color: var(--text);
599
+ border: 1px solid var(--border);
600
+ }
601
+ .btn-secondary:hover:not(:disabled) { background: var(--surface-hover); }
602
+
603
+ .btn-danger { background: var(--red); color: #fff; }
604
+ .btn-danger:hover:not(:disabled) { opacity: 0.9; }
605
+
606
+ .btn-ghost {
607
+ background: transparent;
608
+ color: var(--text-secondary);
609
+ }
610
+ .btn-ghost:hover:not(:disabled) { background: var(--surface); color: var(--text); }
611
+
612
+ /* Spinner inside buttons */
613
+ .spinner {
614
+ display: inline-block;
615
+ width: 16px; height: 16px;
616
+ border: 2px solid currentColor;
617
+ border-right-color: transparent;
618
+ border-radius: var(--radius-full);
619
+ animation: spin 0.6s linear infinite;
620
+ }
621
+ @keyframes spin { to { transform: rotate(360deg); } }
622
+
623
+ /* --- Cards --- */
624
+ .card {
625
+ background: var(--bg-alt);
626
+ border: 1px solid var(--border);
627
+ border-radius: var(--radius-lg);
628
+ padding: var(--sp-4);
629
+ transition: box-shadow var(--duration-fast) var(--ease-out),
630
+ border-color var(--duration-fast) var(--ease-out);
631
+ }
632
+ .card:hover { border-color: var(--accent); box-shadow: var(--shadow-sm); }
633
+
634
+ /* --- Page header --- */
635
+ .page-header {
636
+ display: flex;
637
+ justify-content: space-between;
638
+ align-items: flex-start;
639
+ gap: var(--sp-4);
640
+ flex-wrap: wrap;
641
+ margin-bottom: var(--sp-2);
642
+ }
643
+ .page-header h1 { margin: 0; }
644
+ .page-actions { display: flex; gap: var(--sp-2); flex-shrink: 0; }
645
+
646
+ /* --- Editor --- */
647
+ .editor textarea {
648
+ width: 100%;
649
+ min-height: 400px;
650
+ background: var(--bg);
651
+ color: var(--text);
652
+ border: 1px solid var(--border);
653
+ border-radius: var(--radius-md);
654
+ padding: var(--sp-4);
655
+ font-family: var(--font-mono);
656
+ font-size: var(--text-sm);
657
+ line-height: var(--leading-relaxed);
658
+ resize: vertical;
659
+ transition: border-color var(--duration-fast) var(--ease-out);
660
+ }
661
+ .editor textarea:focus {
662
+ outline: none;
663
+ border-color: var(--accent);
664
+ box-shadow: 0 0 0 3px var(--ring);
665
+ }
666
+ .editor-actions {
667
+ margin-top: var(--sp-3);
668
+ display: flex;
669
+ gap: var(--sp-2);
670
+ align-items: center;
671
+ }
672
+
673
+ /* --- Search results --- */
674
+ .search-results { list-style: none; }
675
+ .search-result {
676
+ padding: var(--sp-4);
677
+ border: 1px solid var(--border);
678
+ border-radius: var(--radius-md);
679
+ margin-bottom: var(--sp-2);
680
+ cursor: pointer;
681
+ transition: border-color var(--duration-fast) var(--ease-out),
682
+ background var(--duration-fast) var(--ease-out);
683
+ }
684
+ .search-result:hover {
685
+ border-color: var(--accent);
686
+ background: var(--accent-subtle);
687
+ }
688
+ .search-result-title {
689
+ font-weight: 600;
690
+ font-size: var(--text-base);
691
+ color: var(--text);
692
+ }
693
+ .search-result-meta {
694
+ display: flex;
695
+ align-items: center;
696
+ gap: var(--sp-2);
697
+ margin-top: var(--sp-1);
698
+ font-size: var(--text-xs);
699
+ color: var(--text-muted);
700
+ }
701
+ .search-result-meta .score-bar {
702
+ width: 40px; height: 4px;
703
+ background: var(--surface);
704
+ border-radius: var(--radius-full);
705
+ overflow: hidden;
706
+ }
707
+ .search-result-meta .score-bar-fill {
708
+ height: 100%;
709
+ background: var(--accent);
710
+ border-radius: var(--radius-full);
711
+ }
712
+ .search-result-snippet {
713
+ font-size: var(--text-sm);
714
+ color: var(--text-secondary);
715
+ margin-top: var(--sp-2);
716
+ line-height: var(--leading-relaxed);
717
+ }
718
+
719
+ /* --- Log entries --- */
720
+ .log-entry {
721
+ padding: var(--sp-3) 0;
722
+ border-bottom: 1px solid var(--border-subtle);
723
+ display: flex;
724
+ align-items: baseline;
725
+ gap: var(--sp-3);
726
+ flex-wrap: wrap;
727
+ }
728
+ .log-date {
729
+ font-size: var(--text-xs);
730
+ font-weight: 600;
731
+ color: var(--accent);
732
+ font-family: var(--font-mono);
733
+ flex-shrink: 0;
734
+ }
735
+ .log-op {
736
+ font-size: var(--text-xs);
737
+ font-weight: 600;
738
+ padding: 1px var(--sp-2);
739
+ border-radius: var(--radius-sm);
740
+ background: var(--surface);
741
+ color: var(--text-secondary);
742
+ text-transform: uppercase;
743
+ }
744
+ .log-summary { font-size: var(--text-sm); color: var(--text); flex: 1; }
745
+ .log-pages {
746
+ font-size: var(--text-xs);
747
+ color: var(--text-muted);
748
+ width: 100%;
749
+ padding-left: 0;
750
+ }
751
+
752
+ /* --- Session entries --- */
753
+ .session-entry {
754
+ padding: var(--sp-4);
755
+ border: 1px solid var(--border);
756
+ border-radius: var(--radius-md);
757
+ margin-bottom: var(--sp-2);
758
+ }
759
+ .session-header {
760
+ display: flex;
761
+ justify-content: space-between;
762
+ align-items: center;
763
+ gap: var(--sp-3);
764
+ }
765
+ .session-project {
766
+ font-weight: 600;
767
+ font-size: var(--text-sm);
768
+ color: var(--text);
769
+ }
770
+ .session-date {
771
+ font-size: var(--text-xs);
772
+ font-family: var(--font-mono);
773
+ color: var(--text-muted);
774
+ }
775
+ .session-summary {
776
+ font-size: var(--text-sm);
777
+ color: var(--text-secondary);
778
+ margin-top: var(--sp-2);
779
+ line-height: var(--leading-relaxed);
780
+ }
781
+
782
+ /* --- Graph --- */
783
+ .graph-container {
784
+ width: 100%;
785
+ height: calc(100vh - 180px);
786
+ background: var(--bg-alt);
787
+ border: 1px solid var(--border);
788
+ border-radius: var(--radius-lg);
789
+ overflow: hidden;
790
+ position: relative;
791
+ }
792
+ .graph-container svg { width: 100%; height: 100%; }
793
+ .graph-legend {
794
+ position: absolute;
795
+ bottom: var(--sp-4);
796
+ left: var(--sp-4);
797
+ display: flex;
798
+ gap: var(--sp-4);
799
+ padding: var(--sp-2) var(--sp-3);
800
+ background: var(--bg);
801
+ border: 1px solid var(--border);
802
+ border-radius: var(--radius-md);
803
+ font-size: var(--text-xs);
804
+ color: var(--text-secondary);
805
+ box-shadow: var(--shadow-md);
806
+ }
807
+ .graph-legend-item { display: flex; align-items: center; gap: var(--sp-1); }
808
+ .graph-legend-dot {
809
+ width: 10px; height: 10px;
810
+ border-radius: var(--radius-full);
811
+ flex-shrink: 0;
812
+ }
813
+
814
+ /* --- Move modal --- */
815
+ .modal-backdrop {
816
+ position: fixed; top: 0; left: 0; right: 0; bottom: 0;
817
+ background: var(--overlay);
818
+ z-index: 300;
819
+ display: flex;
820
+ align-items: center;
821
+ justify-content: center;
822
+ padding: var(--sp-4);
823
+ }
824
+ .modal {
825
+ background: var(--bg-alt);
826
+ border: 1px solid var(--border);
827
+ padding: var(--sp-6);
828
+ border-radius: var(--radius-lg);
829
+ box-shadow: var(--shadow-lg);
830
+ min-width: 320px;
831
+ max-width: 440px;
832
+ width: 100%;
833
+ }
834
+ .modal h3 {
835
+ font-size: var(--text-lg);
836
+ font-weight: 600;
837
+ margin-bottom: var(--sp-4);
838
+ }
839
+ .modal select {
840
+ width: 100%;
841
+ padding: var(--sp-2) var(--sp-3);
842
+ background: var(--bg);
843
+ color: var(--text);
844
+ border: 1px solid var(--border);
845
+ border-radius: var(--radius-md);
846
+ font-size: var(--text-sm);
847
+ font-family: var(--font-sans);
848
+ min-height: 36px;
849
+ margin-bottom: var(--sp-4);
850
+ }
851
+ .modal select:focus {
852
+ outline: none;
853
+ border-color: var(--accent);
854
+ box-shadow: 0 0 0 3px var(--ring);
855
+ }
856
+ .modal-actions {
857
+ display: flex;
858
+ gap: var(--sp-2);
859
+ justify-content: flex-end;
860
+ }
861
+
862
+ /* --- Toast --- */
863
+ .toast {
864
+ position: fixed;
865
+ bottom: var(--sp-6);
866
+ right: var(--sp-6);
867
+ padding: var(--sp-3) var(--sp-4);
868
+ border-radius: var(--radius-md);
869
+ font-size: var(--text-sm);
870
+ font-weight: 500;
871
+ box-shadow: var(--shadow-lg);
872
+ z-index: 400;
873
+ display: flex;
874
+ align-items: center;
875
+ gap: var(--sp-2);
876
+ animation: toast-in var(--duration-normal) var(--ease-out);
877
+ color: #fff;
878
+ }
879
+ .toast.success { background: var(--green); }
880
+ .toast.error { background: var(--red); }
881
+ @keyframes toast-in {
882
+ from { opacity: 0; transform: translateY(8px); }
883
+ to { opacity: 1; transform: translateY(0); }
884
+ }
885
+
886
+ /* --- Empty states --- */
887
+ .empty-state {
888
+ text-align: center;
889
+ padding: var(--sp-12) var(--sp-6);
890
+ color: var(--text-muted);
891
+ }
892
+ .empty-state svg { color: var(--text-muted); margin-bottom: var(--sp-3); opacity: 0.5; }
893
+ .empty-state p { font-size: var(--text-sm); margin-top: var(--sp-2); }
894
+
895
+ /* --- Home page grid --- */
896
+ .stats-row {
897
+ display: flex;
898
+ gap: var(--sp-3);
899
+ margin-bottom: var(--sp-6);
900
+ flex-wrap: wrap;
901
+ }
902
+ .stat-card {
903
+ flex: 1;
904
+ min-width: 100px;
905
+ background: var(--bg-alt);
906
+ border: 1px solid var(--border);
907
+ border-radius: var(--radius-md);
908
+ padding: var(--sp-3) var(--sp-4);
909
+ text-align: center;
910
+ }
911
+ .stat-number {
912
+ font-size: var(--text-2xl);
913
+ font-weight: 700;
914
+ color: var(--accent);
915
+ }
916
+ .stat-label {
917
+ font-size: var(--text-xs);
918
+ color: var(--text-muted);
919
+ text-transform: uppercase;
920
+ letter-spacing: 0.05em;
921
+ margin-top: var(--sp-1);
922
+ }
923
+
924
+ .home-section { margin-bottom: var(--sp-6); }
925
+ .home-section h2 {
926
+ font-size: var(--text-base);
927
+ font-weight: 600;
928
+ color: var(--text-secondary);
929
+ text-transform: uppercase;
930
+ letter-spacing: 0.03em;
931
+ margin-bottom: var(--sp-3);
932
+ padding-bottom: var(--sp-2);
933
+ border-bottom: 1px solid var(--border-subtle);
934
+ }
935
+ .home-page-list { list-style: none; }
936
+ .home-page-item {
937
+ display: flex;
938
+ align-items: center;
939
+ gap: var(--sp-2);
940
+ padding: var(--sp-2) var(--sp-3);
941
+ border-radius: var(--radius-md);
942
+ cursor: pointer;
943
+ font-size: var(--text-sm);
944
+ color: var(--text);
945
+ transition: background var(--duration-fast) var(--ease-out);
946
+ }
947
+ .home-page-item:hover { background: var(--surface); }
948
+ .home-page-item svg { color: var(--text-muted); flex-shrink: 0; }
949
+
950
+ /* --- Breadcrumb --- */
951
+ .breadcrumb {
952
+ display: flex;
953
+ align-items: center;
954
+ gap: var(--sp-1);
955
+ font-size: var(--text-xs);
956
+ color: var(--text-muted);
957
+ margin-bottom: var(--sp-3);
958
+ }
959
+ .breadcrumb a {
960
+ color: var(--text-muted);
961
+ font-weight: 500;
962
+ }
963
+ .breadcrumb a:hover { color: var(--accent); text-decoration: none; }
964
+ .breadcrumb svg { flex-shrink: 0; }
965
+
966
+ /* Page metadata bar */
967
+ .page-meta {
968
+ display: flex;
969
+ align-items: center;
970
+ gap: var(--sp-3);
971
+ font-size: var(--text-xs);
972
+ color: var(--text-muted);
973
+ margin-top: var(--sp-2);
974
+ padding-top: var(--sp-2);
975
+ border-top: 1px solid var(--border-subtle);
976
+ }
977
+ .page-meta svg { flex-shrink: 0; }
978
+ </style>
979
+ </head>
980
+ <body>
981
+ <a href="#main-content" class="skip-link">Skip to main content</a>
982
+ <div id="root"></div>
983
+ <script>
984
+ "use strict";
985
+ const h = React.createElement;
986
+ const { useState, useEffect, useCallback, useRef, useMemo } = React;
987
+ const API = "/api";
988
+
989
+ /* ============================================================
990
+ SVG ICONS (Lucide-style, 24x24 stroke-based)
991
+ ============================================================ */
992
+ const Icon = ({ name, size = 18, className = "" }) => {
993
+ const icons = {
994
+ home: "M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z M9 22V12h6v10",
995
+ network: "M12 2a2 2 0 0 1 2 2c0 .74-.4 1.39-1 1.73V7h3a2 2 0 0 1 2 2v1.27A2 2 0 0 1 19 12a2 2 0 0 1-1 1.73V16a2 2 0 0 1-2 2h-3v1.27A2 2 0 0 1 12 22a2 2 0 0 1-1-3.73V18H8a2 2 0 0 1-2-2v-2.27A2 2 0 0 1 5 12a2 2 0 0 1 1-3.73V7h3V5.73A2 2 0 0 1 10 4a2 2 0 0 1 2-2z",
996
+ fileText: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z M14 2v6h6 M16 13H8 M16 17H8 M10 9H8",
997
+ messageSquare: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z",
998
+ edit: "M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7 M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z",
999
+ move: "M5 12h14 M12 5l7 7-7 7",
1000
+ menu: "M3 12h18 M3 6h18 M3 18h18",
1001
+ search: "M11 3a8 8 0 1 0 0 16 8 8 0 0 0 0-16z M21 21l-4.35-4.35",
1002
+ chevronRight: "M9 18l6-6-6-6",
1003
+ chevronDown: "M6 9l6 6 6-6",
1004
+ x: "M18 6L6 18 M6 6l12 12",
1005
+ sun: "M12 1v2 M12 21v2 M4.22 4.22l1.42 1.42 M18.36 18.36l1.42 1.42 M1 12h2 M21 12h2 M4.22 19.78l1.42-1.42 M18.36 5.64l1.42-1.42 M12 7a5 5 0 1 0 0 10 5 5 0 0 0 0-10z",
1006
+ moon: "M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z",
1007
+ save: "M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z M17 21v-8H7v8 M7 3v5h8",
1008
+ check: "M20 6L9 17l-5-5",
1009
+ folder: "M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z",
1010
+ archive: "M21 8v13H3V8 M1 3h22v5H1z M10 12h4",
1011
+ book: "M4 19.5A2.5 2.5 0 0 1 6.5 17H20 M4 4.5A2.5 2.5 0 0 1 6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15z",
1012
+ layers: "M12 2L2 7l10 5 10-5-10-5z M2 17l10 5 10-5 M2 12l10 5 10-5",
1013
+ clock: "M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20z M12 6v6l4 2",
1014
+ doc: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z M14 2v6h6",
1015
+ inbox: "M22 12h-6l-2 3H10l-2-3H2",
1016
+ };
1017
+
1018
+ const d = icons[name];
1019
+ if (!d) return null;
1020
+
1021
+ const paths = d.split(" M").map((p, i) => i === 0 ? p : "M" + p);
1022
+
1023
+ return h("svg", {
1024
+ width: size, height: size, viewBox: "0 0 24 24",
1025
+ fill: "none", stroke: "currentColor", strokeWidth: 2,
1026
+ strokeLinecap: "round", strokeLinejoin: "round",
1027
+ className, "aria-hidden": "true"
1028
+ }, ...paths.map((p, i) => h("path", { key: i, d: p })));
1029
+ };
1030
+
1031
+ /* ============================================================
1032
+ THEME MANAGEMENT
1033
+ ============================================================ */
1034
+ function getInitialTheme() {
1035
+ const stored = localStorage.getItem("pi-para-theme");
1036
+ if (stored === "light" || stored === "dark") return stored;
1037
+ return window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark";
1038
+ }
1039
+
1040
+ function useTheme() {
1041
+ const [theme, setTheme] = useState(getInitialTheme);
1042
+ useEffect(() => {
1043
+ document.documentElement.setAttribute("data-theme", theme);
1044
+ localStorage.setItem("pi-para-theme", theme);
1045
+ }, [theme]);
1046
+ const toggle = useCallback(() => setTheme(t => t === "dark" ? "light" : "dark"), []);
1047
+ return [theme, toggle];
1048
+ }
1049
+
1050
+ /* ============================================================
1051
+ TOAST HOOK
1052
+ ============================================================ */
1053
+ function useToast() {
1054
+ const [toast, setToast] = useState(null);
1055
+ const timerRef = useRef(null);
1056
+
1057
+ const show = useCallback((message, type = "success") => {
1058
+ if (timerRef.current) clearTimeout(timerRef.current);
1059
+ setToast({ message, type });
1060
+ timerRef.current = setTimeout(() => setToast(null), 3500);
1061
+ }, []);
1062
+
1063
+ return [toast, show];
1064
+ }
1065
+
1066
+ /* ============================================================
1067
+ APP
1068
+ ============================================================ */
1069
+ function App() {
1070
+ const [pages, setPages] = useState({});
1071
+ const [currentPage, setCurrentPage] = useState(null);
1072
+ const [view, setView] = useState("home");
1073
+ const [sidebarOpen, setSidebarOpen] = useState(false);
1074
+ const [searchQuery, setSearchQuery] = useState("");
1075
+ const [searchResults, setSearchResults] = useState([]);
1076
+ const [logEntries, setLogEntries] = useState([]);
1077
+ const [sessions, setSessions] = useState([]);
1078
+ const [graphData, setGraphData] = useState({ nodes: [], edges: [] });
1079
+ const [maxNodes, setMaxNodes] = useState(100);
1080
+ const [editing, setEditing] = useState(false);
1081
+ const [editBody, setEditBody] = useState("");
1082
+ const [moveTarget, setMoveTarget] = useState(null);
1083
+ const [loading, setLoading] = useState({});
1084
+ const [scopeFilter, setScopeFilter] = useState("all");
1085
+ const [theme, toggleTheme] = useTheme();
1086
+ const [toast, showToast] = useToast();
1087
+ const searchTimerRef = useRef(null);
1088
+
1089
+ const setLoadingKey = (key, val) => setLoading(prev => ({ ...prev, [key]: val }));
1090
+
1091
+ const loadPages = useCallback(async () => {
1092
+ try {
1093
+ const r = await fetch(API + "/pages");
1094
+ if (r.ok) setPages(await r.json());
1095
+ } catch (e) { console.error("Failed to load pages", e); }
1096
+ }, []);
1097
+
1098
+ useEffect(() => { loadPages(); }, [loadPages]);
1099
+
1100
+ // Reload graph when scope filter changes while graph view is active
1101
+ useEffect(() => { if (view === "graph") loadGraph(maxNodes); }, [scopeFilter]);
1102
+
1103
+ const openPage = async (cat, slug) => {
1104
+ setLoadingKey("page", true);
1105
+ try {
1106
+ const r = await fetch(API + "/pages/" + cat + "/" + slug);
1107
+ if (r.ok) {
1108
+ setCurrentPage(await r.json());
1109
+ setView("page");
1110
+ setEditing(false);
1111
+ setSidebarOpen(false);
1112
+ }
1113
+ } catch (e) { showToast("Failed to load page", "error"); }
1114
+ setLoadingKey("page", false);
1115
+ };
1116
+
1117
+ const savePage = async () => {
1118
+ if (!currentPage) return;
1119
+ setLoadingKey("save", true);
1120
+ try {
1121
+ const r = await fetch(API + "/pages/" + currentPage.category + "/" + currentPage.slug, {
1122
+ method: "PUT",
1123
+ headers: { "Content-Type": "application/json" },
1124
+ body: JSON.stringify({ body: editBody })
1125
+ });
1126
+ if (r.ok) {
1127
+ const p = await r.json();
1128
+ setCurrentPage(p);
1129
+ setEditing(false);
1130
+ loadPages();
1131
+ showToast("Page saved successfully");
1132
+ } else {
1133
+ showToast("Failed to save page", "error");
1134
+ }
1135
+ } catch (e) { showToast("Failed to save page", "error"); }
1136
+ setLoadingKey("save", false);
1137
+ };
1138
+
1139
+ const doSearch = async (q) => {
1140
+ if (!q.trim()) return setSearchResults([]);
1141
+ setLoadingKey("search", true);
1142
+ try {
1143
+ const r = await fetch(API + "/search?q=" + encodeURIComponent(q));
1144
+ if (r.ok) setSearchResults(await r.json());
1145
+ } catch (e) { console.error("Search failed", e); }
1146
+ setLoadingKey("search", false);
1147
+ };
1148
+
1149
+ // Debounced search
1150
+ const handleSearchInput = (e) => {
1151
+ const q = e.target.value;
1152
+ setSearchQuery(q);
1153
+ if (searchTimerRef.current) clearTimeout(searchTimerRef.current);
1154
+ if (q.length > 1) {
1155
+ setView("search");
1156
+ searchTimerRef.current = setTimeout(() => doSearch(q), 250);
1157
+ }
1158
+ };
1159
+
1160
+ const loadLog = async () => {
1161
+ setLoadingKey("log", true);
1162
+ const r = await fetch(API + "/log");
1163
+ if (r.ok) setLogEntries(await r.json());
1164
+ setLoadingKey("log", false);
1165
+ };
1166
+
1167
+ const loadSessions = async () => {
1168
+ setLoadingKey("sessions", true);
1169
+ const r = await fetch(API + "/sessions");
1170
+ if (r.ok) setSessions(await r.json());
1171
+ setLoadingKey("sessions", false);
1172
+ };
1173
+
1174
+ const loadGraph = async (nodes) => {
1175
+ setLoadingKey("graph", true);
1176
+ const params = [];
1177
+ if (scopeFilter !== "all") params.push("scope=" + encodeURIComponent(scopeFilter));
1178
+ const mn = nodes ?? maxNodes;
1179
+ if (mn !== 100) params.push("maxNodes=" + mn);
1180
+ const qs = params.length ? "?" + params.join("&") : "";
1181
+ const r = await fetch(API + "/graph" + qs);
1182
+ if (r.ok) setGraphData(await r.json());
1183
+ setLoadingKey("graph", false);
1184
+ };
1185
+
1186
+ const doMovePage = async (newCat) => {
1187
+ if (!moveTarget) return;
1188
+ setLoadingKey("move", true);
1189
+ try {
1190
+ const r = await fetch(API + "/pages/" + moveTarget.category + "/" + moveTarget.slug + "/move", {
1191
+ method: "POST",
1192
+ headers: { "Content-Type": "application/json" },
1193
+ body: JSON.stringify({ to: newCat })
1194
+ });
1195
+ if (r.ok) {
1196
+ setMoveTarget(null);
1197
+ loadPages();
1198
+ if (currentPage?.slug === moveTarget.slug) openPage(newCat, moveTarget.slug);
1199
+ showToast("Page moved to " + newCat);
1200
+ } else {
1201
+ showToast("Failed to move page", "error");
1202
+ }
1203
+ } catch (e) { showToast("Failed to move page", "error"); }
1204
+ setLoadingKey("move", false);
1205
+ };
1206
+
1207
+ const renderMd = (md) => {
1208
+ let html = marked.parse(md || "");
1209
+ html = html.replace(/\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g, (m, slug, label) =>
1210
+ '<a href="#" class="wikilink" data-slug="' + slug + '">' + (label || slug) + '</a>');
1211
+ return html;
1212
+ };
1213
+
1214
+ // Extract unique scopes from all pages
1215
+ const allScopes = useMemo(() => {
1216
+ const scopes = new Set();
1217
+ for (const cat of ["projects", "areas", "resources", "archives"]) {
1218
+ for (const p of (pages[cat] || [])) {
1219
+ if (p.scope) p.scope.forEach(s => { if (s !== "global") scopes.add(s); });
1220
+ }
1221
+ }
1222
+ return [...scopes].sort();
1223
+ }, [pages]);
1224
+
1225
+ // Filter pages by scope
1226
+ const filteredPages = useMemo(() => {
1227
+ if (scopeFilter === "all") return pages;
1228
+ const result = {};
1229
+ for (const cat of ["projects", "areas", "resources", "archives"]) {
1230
+ result[cat] = (pages[cat] || []).filter(p =>
1231
+ !p.scope || p.scope.includes(scopeFilter) || p.scope.includes("global")
1232
+ );
1233
+ }
1234
+ return result;
1235
+ }, [pages, scopeFilter]);
1236
+
1237
+ const totalPages = useMemo(() =>
1238
+ ["projects", "areas", "resources", "archives"].reduce((s, c) => (filteredPages[c] || []).length + s, 0),
1239
+ [filteredPages]
1240
+ );
1241
+
1242
+ const categories = ["projects", "areas", "resources", "archives"];
1243
+
1244
+ return h("div", { className: "app" },
1245
+ // Overlay for mobile sidebar
1246
+ h("div", {
1247
+ className: "overlay" + (sidebarOpen ? " show" : ""),
1248
+ onClick: () => setSidebarOpen(false),
1249
+ "aria-hidden": "true"
1250
+ }),
1251
+
1252
+ // Sidebar
1253
+ h("nav", {
1254
+ className: "sidebar" + (sidebarOpen ? " open" : ""),
1255
+ "aria-label": "Wiki navigation"
1256
+ },
1257
+ h("div", { className: "sidebar-header" },
1258
+ h("div", { className: "logo" },
1259
+ h(Icon, { name: "layers", size: 20 }),
1260
+ h("div", null,
1261
+ h("div", { className: "logo-text" }, "pi-para"),
1262
+ h("div", { className: "logo-sub" }, "Knowledge Wiki")
1263
+ )
1264
+ )
1265
+ ),
1266
+ h("div", { className: "sidebar-nav" },
1267
+ categories.map(cat =>
1268
+ h(SidebarSection, {
1269
+ key: cat, cat,
1270
+ pages: filteredPages[cat] || [],
1271
+ currentPage, openPage
1272
+ })
1273
+ )
1274
+ )
1275
+ ),
1276
+
1277
+ // Main wrapper
1278
+ h("div", { className: "main-wrapper" },
1279
+ // Top bar
1280
+ h("header", { className: "topbar", role: "banner" },
1281
+ h("button", {
1282
+ className: "nav-btn hamburger",
1283
+ onClick: () => setSidebarOpen(!sidebarOpen),
1284
+ "aria-label": "Toggle navigation menu",
1285
+ "aria-expanded": sidebarOpen
1286
+ }, h(Icon, { name: "menu" })),
1287
+
1288
+ h("button", {
1289
+ className: "nav-btn" + (view === "home" ? " active" : ""),
1290
+ onClick: () => setView("home"),
1291
+ "aria-label": "Home"
1292
+ }, h(Icon, { name: "home" }), h("span", { className: "nav-label" }, "Home")),
1293
+
1294
+ h("button", {
1295
+ className: "nav-btn" + (view === "graph" ? " active" : ""),
1296
+ onClick: () => { setView("graph"); loadGraph(); },
1297
+
1298
+ "aria-label": "Graph view"
1299
+ }, h(Icon, { name: "network" }), h("span", { className: "nav-label" }, "Graph")),
1300
+
1301
+ h("button", {
1302
+ className: "nav-btn" + (view === "log" ? " active" : ""),
1303
+ onClick: () => { setView("log"); loadLog(); },
1304
+ "aria-label": "Activity log"
1305
+ }, h(Icon, { name: "fileText" }), h("span", { className: "nav-label" }, "Log")),
1306
+
1307
+ h("button", {
1308
+ className: "nav-btn" + (view === "sessions" ? " active" : ""),
1309
+ onClick: () => { setView("sessions"); loadSessions(); },
1310
+ "aria-label": "Session digests"
1311
+ }, h(Icon, { name: "messageSquare" }), h("span", { className: "nav-label" }, "Sessions")),
1312
+
1313
+ // Scope filter
1314
+ allScopes.length > 0 && h("div", { className: "scope-filter" },
1315
+ h("select", {
1316
+ value: scopeFilter,
1317
+ onChange: e => setScopeFilter(e.target.value),
1318
+ "aria-label": "Filter by project scope"
1319
+ },
1320
+ h("option", { value: "all" }, "All scopes"),
1321
+ allScopes.map(s => h("option", { key: s, value: s }, s))
1322
+ )
1323
+ ),
1324
+
1325
+ h("div", { className: "search-wrapper" },
1326
+ h(Icon, { name: "search", size: 16 }),
1327
+ h("input", {
1328
+ className: "search-input",
1329
+ type: "search",
1330
+ placeholder: "Search wiki\u2026",
1331
+ value: searchQuery,
1332
+ onChange: handleSearchInput,
1333
+ onKeyDown: e => { if (e.key === "Enter") doSearch(searchQuery); },
1334
+ "aria-label": "Search wiki pages"
1335
+ })
1336
+ ),
1337
+
1338
+ h("button", {
1339
+ className: "theme-toggle",
1340
+ onClick: toggleTheme,
1341
+ "aria-label": "Toggle " + (theme === "dark" ? "light" : "dark") + " mode",
1342
+ title: "Toggle theme"
1343
+ }, h(Icon, { name: theme === "dark" ? "sun" : "moon", size: 16 }))
1344
+ ),
1345
+
1346
+ // Main content — scroll wrapper + centered content
1347
+ h("div", { className: "main-scroll" },
1348
+ h("main", { id: "main-content", className: "main" + (view === "graph" ? " main-wide" : ""), role: "main" },
1349
+ view === "home" && h(HomePage, { pages: filteredPages, openPage, totalPages, scopeFilter }),
1350
+ view === "page" && currentPage && h(PageView, {
1351
+ page: currentPage, editing, editBody,
1352
+ setEditing, setEditBody, savePage, renderMd,
1353
+ setMoveTarget, openPage, loading, setView
1354
+ }),
1355
+ view === "search" && h(SearchView, { results: searchResults, openPage, query: searchQuery, loading: loading.search }),
1356
+ view === "graph" && h(GraphView, { data: graphData, openPage, loading: loading.graph, maxNodes, onMaxNodesChange: (n) => { setMaxNodes(n); loadGraph(n); } }),
1357
+ view === "log" && h(LogView, { entries: logEntries, loading: loading.log }),
1358
+ view === "sessions" && h(SessionsView, { sessions, loading: loading.sessions })
1359
+ )
1360
+ )
1361
+ ),
1362
+
1363
+ // Move modal
1364
+ moveTarget && h(MoveModal, {
1365
+ page: moveTarget,
1366
+ onMove: doMovePage,
1367
+ onClose: () => setMoveTarget(null),
1368
+ loading: loading.move
1369
+ }),
1370
+
1371
+ // Toast
1372
+ toast && h("div", {
1373
+ className: "toast " + toast.type,
1374
+ role: "status",
1375
+ "aria-live": "polite"
1376
+ },
1377
+ h(Icon, { name: toast.type === "success" ? "check" : "x", size: 16 }),
1378
+ toast.message
1379
+ )
1380
+ );
1381
+ }
1382
+
1383
+ /* ============================================================
1384
+ SIDEBAR SECTION
1385
+ ============================================================ */
1386
+ function SidebarSection({ cat, pages, currentPage, openPage }) {
1387
+ const [open, setOpen] = useState(cat === "projects" || cat === "areas");
1388
+ const iconName = { projects: "layers", areas: "folder", resources: "book", archives: "archive" }[cat] || "folder";
1389
+
1390
+ return h("div", { className: "sidebar-section" + (open ? " open" : "") },
1391
+ h("button", {
1392
+ className: "sidebar-section-header",
1393
+ onClick: () => setOpen(!open),
1394
+ "aria-expanded": open,
1395
+ "aria-label": cat + " (" + pages.length + " pages)"
1396
+ },
1397
+ h(Icon, { name: "chevronRight", size: 14 }),
1398
+ h(Icon, { name: iconName, size: 14 }),
1399
+ h("span", null, cat),
1400
+ h("span", { className: "count" }, pages.length)
1401
+ ),
1402
+ h("ul", { className: "sidebar-list", role: "list" },
1403
+ pages.map(p =>
1404
+ h("li", {
1405
+ key: p.slug,
1406
+ className: "sidebar-item" + (currentPage?.slug === p.slug ? " active" : ""),
1407
+ onClick: () => openPage(cat, p.slug),
1408
+ role: "listitem",
1409
+ tabIndex: 0,
1410
+ onKeyDown: e => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); openPage(cat, p.slug); } },
1411
+ "aria-current": currentPage?.slug === p.slug ? "page" : undefined
1412
+ }, p.title || p.slug)
1413
+ )
1414
+ )
1415
+ );
1416
+ }
1417
+
1418
+ /* ============================================================
1419
+ HOME PAGE
1420
+ ============================================================ */
1421
+ function HomePage({ pages, openPage, totalPages, scopeFilter }) {
1422
+ const cats = ["projects", "areas", "resources", "archives"];
1423
+
1424
+ return h("div", { className: "content" },
1425
+ h("h1", null, "Wiki Overview",
1426
+ scopeFilter && scopeFilter !== "all" && h("span", { className: "scope-badge" }, scopeFilter)
1427
+ ),
1428
+
1429
+ // Stats row
1430
+ h("div", { className: "stats-row" },
1431
+ h("div", { className: "stat-card" },
1432
+ h("div", { className: "stat-number" }, totalPages),
1433
+ h("div", { className: "stat-label" }, "Total Pages")
1434
+ ),
1435
+ cats.map(cat =>
1436
+ h("div", { key: cat, className: "stat-card" },
1437
+ h("div", { className: "stat-number" }, (pages[cat] || []).length),
1438
+ h("div", { className: "stat-label" }, cat)
1439
+ )
1440
+ )
1441
+ ),
1442
+
1443
+ // Category lists
1444
+ cats.map(cat => {
1445
+ const items = pages[cat] || [];
1446
+ if (!items.length) return null;
1447
+ return h("div", { key: cat, className: "home-section" },
1448
+ h("h2", null, cat),
1449
+ h("ul", { className: "home-page-list", role: "list" },
1450
+ items.map(p =>
1451
+ h("li", {
1452
+ key: p.slug,
1453
+ className: "home-page-item",
1454
+ onClick: () => openPage(cat, p.slug),
1455
+ tabIndex: 0,
1456
+ role: "listitem",
1457
+ onKeyDown: e => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); openPage(cat, p.slug); } }
1458
+ },
1459
+ h(Icon, { name: "doc", size: 14 }),
1460
+ h("span", null, p.title || p.slug)
1461
+ )
1462
+ )
1463
+ )
1464
+ );
1465
+ }),
1466
+
1467
+ totalPages === 0 && h("div", { className: "empty-state" },
1468
+ h(Icon, { name: "inbox", size: 48 }),
1469
+ h("h3", null, "No pages yet"),
1470
+ h("p", null, "Start capturing knowledge and pages will appear here.")
1471
+ )
1472
+ );
1473
+ }
1474
+
1475
+ /* ============================================================
1476
+ PAGE VIEW
1477
+ ============================================================ */
1478
+ function PageView({ page, editing, editBody, setEditing, setEditBody, savePage, renderMd, setMoveTarget, openPage, loading, setView }) {
1479
+ const contentRef = useRef(null);
1480
+
1481
+ useEffect(() => {
1482
+ if (!contentRef.current) return;
1483
+ const handleClick = (e) => {
1484
+ const el = e.target.closest(".wikilink");
1485
+ if (!el) return;
1486
+ e.preventDefault();
1487
+ const slug = el.getAttribute("data-slug");
1488
+ if (!slug) return;
1489
+ const cats = ["projects", "areas", "resources", "archives"];
1490
+ for (const c of cats) {
1491
+ fetch(API + "/pages/" + c + "/" + slug)
1492
+ .then(r => { if (r.ok) return r.json(); })
1493
+ .then(p => { if (p) openPage(c, slug); });
1494
+ }
1495
+ };
1496
+ contentRef.current.addEventListener("click", handleClick);
1497
+ return () => { if (contentRef.current) contentRef.current.removeEventListener("click", handleClick); };
1498
+ }, [page, openPage]);
1499
+
1500
+ if (editing) return h("div", { className: "content" },
1501
+ h("div", { className: "breadcrumb" },
1502
+ h("a", { href: "#", onClick: e => { e.preventDefault(); setView("home"); } }, "Home"),
1503
+ h(Icon, { name: "chevronRight", size: 12 }),
1504
+ h("span", null, page.frontmatter.para),
1505
+ h(Icon, { name: "chevronRight", size: 12 }),
1506
+ h("span", null, "Editing")
1507
+ ),
1508
+ h("h1", null, "Editing: " + page.frontmatter.title),
1509
+ h("div", { className: "editor" },
1510
+ h("label", { htmlFor: "page-editor", style: { display: "block", fontSize: "var(--text-sm)", color: "var(--text-secondary)", marginBottom: "var(--sp-2)" } },
1511
+ "Page content (Markdown)"
1512
+ ),
1513
+ h("textarea", {
1514
+ id: "page-editor",
1515
+ value: editBody,
1516
+ onChange: e => setEditBody(e.target.value),
1517
+ "aria-label": "Page content editor"
1518
+ }),
1519
+ h("div", { className: "editor-actions" },
1520
+ h("button", {
1521
+ className: "btn btn-primary",
1522
+ onClick: savePage,
1523
+ disabled: loading.save,
1524
+ "aria-label": "Save page"
1525
+ },
1526
+ loading.save ? h("span", { className: "spinner" }) : h(Icon, { name: "save", size: 16 }),
1527
+ loading.save ? "Saving\u2026" : "Save"
1528
+ ),
1529
+ h("button", {
1530
+ className: "btn btn-secondary",
1531
+ onClick: () => setEditing(false)
1532
+ }, "Cancel")
1533
+ )
1534
+ )
1535
+ );
1536
+
1537
+ return h("div", { className: "content" },
1538
+ // Breadcrumb
1539
+ h("div", { className: "breadcrumb" },
1540
+ h("a", { href: "#", onClick: e => { e.preventDefault(); setView("home"); } }, "Home"),
1541
+ h(Icon, { name: "chevronRight", size: 12 }),
1542
+ h("span", null, page.frontmatter.para)
1543
+ ),
1544
+
1545
+ // Header with actions
1546
+ h("div", { className: "page-header" },
1547
+ h("h1", null, page.frontmatter.title),
1548
+ h("div", { className: "page-actions" },
1549
+ h("button", {
1550
+ className: "btn btn-secondary",
1551
+ onClick: () => { setEditBody(page.body); setEditing(true); },
1552
+ "aria-label": "Edit page"
1553
+ }, h(Icon, { name: "edit", size: 16 }), "Edit"),
1554
+ h("button", {
1555
+ className: "btn btn-secondary",
1556
+ onClick: () => setMoveTarget(page),
1557
+ "aria-label": "Move page to different category"
1558
+ }, h(Icon, { name: "move", size: 16 }), "Move")
1559
+ )
1560
+ ),
1561
+
1562
+ // Tags
1563
+ h("div", { className: "tags", role: "list", "aria-label": "Page tags" },
1564
+ h("span", { className: "tag para", role: "listitem" }, page.frontmatter.para),
1565
+ ...(page.frontmatter.scope || []).map(s =>
1566
+ h("span", { key: s, className: "tag scope", role: "listitem" }, s)
1567
+ ),
1568
+ ...(page.frontmatter.tags || []).map(t =>
1569
+ h("span", { key: t, className: "tag", role: "listitem" }, t)
1570
+ )
1571
+ ),
1572
+
1573
+ // Rendered body
1574
+ h("div", {
1575
+ ref: contentRef,
1576
+ className: "content",
1577
+ dangerouslySetInnerHTML: { __html: renderMd(page.body) }
1578
+ }),
1579
+
1580
+ // Metadata footer
1581
+ page.frontmatter.updated && h("div", { className: "page-meta" },
1582
+ h(Icon, { name: "clock", size: 14 }),
1583
+ "Updated: " + new Date(page.frontmatter.updated).toLocaleDateString("en-US", {
1584
+ year: "numeric", month: "short", day: "numeric"
1585
+ })
1586
+ )
1587
+ );
1588
+ }
1589
+
1590
+ /* ============================================================
1591
+ SEARCH VIEW
1592
+ ============================================================ */
1593
+ function SearchView({ results, openPage, query, loading }) {
1594
+ return h("div", { className: "content" },
1595
+ h("h1", null, "Search Results"),
1596
+ h("p", { style: { color: "var(--text-secondary)", marginBottom: "var(--sp-4)" } },
1597
+ loading ? "Searching\u2026" :
1598
+ results.length + " result" + (results.length !== 1 ? "s" : "") + (query ? ' for "' + query + '"' : "")
1599
+ ),
1600
+
1601
+ loading && h("div", { style: { textAlign: "center", padding: "var(--sp-8)" } },
1602
+ h("span", { className: "spinner", style: { width: 24, height: 24 } })
1603
+ ),
1604
+
1605
+ !loading && results.length === 0 && query && h("div", { className: "empty-state" },
1606
+ h(Icon, { name: "search", size: 48 }),
1607
+ h("h3", null, "No results found"),
1608
+ h("p", null, "Try different keywords or check your spelling.")
1609
+ ),
1610
+
1611
+ !loading && h("ul", { className: "search-results", role: "list" },
1612
+ results.map((r, i) =>
1613
+ h("li", {
1614
+ key: i,
1615
+ className: "search-result",
1616
+ onClick: () => openPage(r.category, r.slug),
1617
+ tabIndex: 0,
1618
+ role: "listitem",
1619
+ onKeyDown: e => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); openPage(r.category, r.slug); } }
1620
+ },
1621
+ h("div", { className: "search-result-title" }, r.title || r.slug),
1622
+ h("div", { className: "search-result-meta" },
1623
+ h("span", null, r.category + "/" + r.slug),
1624
+ h("div", { className: "score-bar" },
1625
+ h("div", { className: "score-bar-fill", style: { width: ((r.score || 0) * 100) + "%" } })
1626
+ ),
1627
+ h("span", null, ((r.score || 0) * 100).toFixed(0) + "%")
1628
+ ),
1629
+ r.snippet && h("div", { className: "search-result-snippet" }, r.snippet.slice(0, 250))
1630
+ )
1631
+ )
1632
+ )
1633
+ );
1634
+ }
1635
+
1636
+ /* ============================================================
1637
+ GRAPH VIEW
1638
+ ============================================================ */
1639
+ function GraphView({ data, openPage, loading, onMaxNodesChange, maxNodes }) {
1640
+ const ref = useRef(null);
1641
+
1642
+ useEffect(() => {
1643
+ if (!ref.current || !data.nodes.length) return;
1644
+
1645
+ const container = ref.current;
1646
+ const w = container.clientWidth;
1647
+ const ht = container.clientHeight;
1648
+
1649
+ d3.select(container).select("svg").remove();
1650
+
1651
+ const svg = d3.select(container).append("svg")
1652
+ .attr("viewBox", [0, 0, w, ht])
1653
+ .attr("role", "img")
1654
+ .attr("aria-label", "Wiki page relationship graph with " + data.nodes.length + " nodes and " + data.edges.length + " connections");
1655
+
1656
+ const g = svg.append("g");
1657
+
1658
+ svg.call(d3.zoom()
1659
+ .scaleExtent([0.3, 4])
1660
+ .on("zoom", e => g.attr("transform", e.transform))
1661
+ );
1662
+
1663
+ const colors = {
1664
+ projects: "#60a5fa",
1665
+ areas: "#34d399",
1666
+ resources: "#fbbf24",
1667
+ archives: "#94a3b8"
1668
+ };
1669
+
1670
+ const sim = d3.forceSimulation(data.nodes)
1671
+ .force("link", d3.forceLink(data.edges).id(d => d.id).distance(100))
1672
+ .force("charge", d3.forceManyBody().strength(-300))
1673
+ .force("center", d3.forceCenter(w / 2, ht / 2))
1674
+ .force("collision", d3.forceCollide().radius(30));
1675
+
1676
+ const link = g.selectAll("line").data(data.edges).join("line")
1677
+ .attr("stroke", "var(--border)")
1678
+ .attr("stroke-width", 1.5)
1679
+ .attr("stroke-opacity", 0.5);
1680
+
1681
+ const nodeGroup = g.selectAll("g.node").data(data.nodes).join("g")
1682
+ .attr("class", "node")
1683
+ .attr("cursor", "pointer")
1684
+ .call(d3.drag()
1685
+ .on("start", (e, d) => { if (!e.active) sim.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; })
1686
+ .on("drag", (e, d) => { d.fx = e.x; d.fy = e.y; })
1687
+ .on("end", (e, d) => { if (!e.active) sim.alphaTarget(0); d.fx = null; d.fy = null; })
1688
+ )
1689
+ .on("click", (e, d) => openPage(d.category, d.slug));
1690
+
1691
+ nodeGroup.append("circle")
1692
+ .attr("r", 8)
1693
+ .attr("fill", d => colors[d.category] || "#60a5fa")
1694
+ .attr("stroke", "var(--bg)")
1695
+ .attr("stroke-width", 2);
1696
+
1697
+ nodeGroup.append("text")
1698
+ .text(d => d.title || d.id)
1699
+ .attr("font-size", 11)
1700
+ .attr("font-family", "var(--font-sans)")
1701
+ .attr("fill", "var(--text)")
1702
+ .attr("dx", 14)
1703
+ .attr("dy", 4)
1704
+ .attr("pointer-events", "none");
1705
+
1706
+ sim.on("tick", () => {
1707
+ link
1708
+ .attr("x1", d => d.source.x).attr("y1", d => d.source.y)
1709
+ .attr("x2", d => d.target.x).attr("y2", d => d.target.y);
1710
+ nodeGroup.attr("transform", d => "translate(" + d.x + "," + d.y + ")");
1711
+ });
1712
+
1713
+ return () => sim.stop();
1714
+ }, [data]);
1715
+
1716
+ return h("div", { className: "content" },
1717
+ h("h1", null, "Knowledge Graph"),
1718
+ h("div", { style: { display: "flex", alignItems: "center", gap: "var(--sp-4)", marginBottom: "var(--sp-3)", flexWrap: "wrap" } },
1719
+ h("p", { style: { color: "var(--text-secondary)", margin: 0 } },
1720
+ data.nodes.length + " pages \u00b7 " + data.edges.length + " connections"
1721
+ ),
1722
+ h("label", { style: { display: "flex", alignItems: "center", gap: "var(--sp-2)", fontSize: "0.85rem", color: "var(--text-secondary)" } },
1723
+ "Max nodes",
1724
+ h("input", {
1725
+ type: "range", min: 10, max: 200, step: 10, value: maxNodes,
1726
+ onChange: (e) => onMaxNodesChange(parseInt(e.target.value)),
1727
+ style: { width: 120 },
1728
+ "aria-label": "Maximum nodes to display"
1729
+ }),
1730
+ h("span", { style: { minWidth: 30, textAlign: "right" } }, maxNodes)
1731
+ )
1732
+ ),
1733
+
1734
+ loading && h("div", { style: { textAlign: "center", padding: "var(--sp-8)" } },
1735
+ h("span", { className: "spinner", style: { width: 24, height: 24 } })
1736
+ ),
1737
+
1738
+ !loading && data.nodes.length === 0
1739
+ ? h("div", { className: "empty-state" },
1740
+ h(Icon, { name: "network", size: 48 }),
1741
+ h("h3", null, "No connections yet"),
1742
+ h("p", null, "Add [[wikilinks]] between pages to see the graph.")
1743
+ )
1744
+ : h("div", { style: { position: "relative" } },
1745
+ h("div", { ref, className: "graph-container" }),
1746
+ h("div", { className: "graph-legend", "aria-label": "Graph legend" },
1747
+ ["projects", "areas", "resources", "archives"].map(cat =>
1748
+ h("div", { key: cat, className: "graph-legend-item" },
1749
+ h("div", {
1750
+ className: "graph-legend-dot",
1751
+ style: { background: { projects: "#60a5fa", areas: "#34d399", resources: "#fbbf24", archives: "#94a3b8" }[cat] }
1752
+ }),
1753
+ h("span", null, cat)
1754
+ )
1755
+ )
1756
+ )
1757
+ )
1758
+ );
1759
+ }
1760
+
1761
+ /* ============================================================
1762
+ LOG VIEW
1763
+ ============================================================ */
1764
+ function LogView({ entries, loading }) {
1765
+ return h("div", { className: "content" },
1766
+ h("h1", null, "Activity Log"),
1767
+ h("p", { style: { color: "var(--text-secondary)", marginBottom: "var(--sp-4)" } },
1768
+ entries.length + " entr" + (entries.length !== 1 ? "ies" : "y")
1769
+ ),
1770
+
1771
+ loading && h("div", { style: { textAlign: "center", padding: "var(--sp-8)" } },
1772
+ h("span", { className: "spinner", style: { width: 24, height: 24 } })
1773
+ ),
1774
+
1775
+ !loading && entries.length === 0 && h("div", { className: "empty-state" },
1776
+ h(Icon, { name: "fileText", size: 48 }),
1777
+ h("h3", null, "No activity yet"),
1778
+ h("p", null, "Activity will appear here as pages are created and updated.")
1779
+ ),
1780
+
1781
+ !loading && h("div", { role: "list", "aria-label": "Activity log entries" },
1782
+ entries.map((e, i) =>
1783
+ h("div", { key: i, className: "log-entry", role: "listitem" },
1784
+ h("span", { className: "log-date" }, e.date || ""),
1785
+ h("span", { className: "log-op" }, e.operation || ""),
1786
+ h("span", { className: "log-summary" }, e.summary || ""),
1787
+ e.pages?.length
1788
+ ? h("span", { className: "log-pages" }, e.pages.join(", "))
1789
+ : null
1790
+ )
1791
+ )
1792
+ )
1793
+ );
1794
+ }
1795
+
1796
+ /* ============================================================
1797
+ SESSIONS VIEW
1798
+ ============================================================ */
1799
+ function SessionsView({ sessions, loading }) {
1800
+ return h("div", { className: "content" },
1801
+ h("h1", null, "Session Digests"),
1802
+ h("p", { style: { color: "var(--text-secondary)", marginBottom: "var(--sp-4)" } },
1803
+ sessions.length + " session" + (sessions.length !== 1 ? "s" : "")
1804
+ ),
1805
+
1806
+ loading && h("div", { style: { textAlign: "center", padding: "var(--sp-8)" } },
1807
+ h("span", { className: "spinner", style: { width: 24, height: 24 } })
1808
+ ),
1809
+
1810
+ !loading && sessions.length === 0 && h("div", { className: "empty-state" },
1811
+ h(Icon, { name: "messageSquare", size: 48 }),
1812
+ h("h3", null, "No session digests"),
1813
+ h("p", null, "Session digests will appear as the daemon captures knowledge.")
1814
+ ),
1815
+
1816
+ !loading && sessions.map((s, i) =>
1817
+ h("div", { key: i, className: "session-entry" },
1818
+ h("div", { className: "session-header" },
1819
+ h("span", { className: "session-project" }, s.project || "unknown"),
1820
+ h("span", { className: "session-date" }, s.date || "")
1821
+ ),
1822
+ h("p", { className: "session-summary" }, s.summary || ""),
1823
+ s.capturedPages?.length
1824
+ ? h("div", { className: "tags", style: { marginTop: "var(--sp-2)" } },
1825
+ s.capturedPages.map(p =>
1826
+ h("span", { key: p, className: "tag" }, "[[" + p + "]]")
1827
+ )
1828
+ )
1829
+ : null
1830
+ )
1831
+ )
1832
+ );
1833
+ }
1834
+
1835
+ /* ============================================================
1836
+ MOVE MODAL
1837
+ ============================================================ */
1838
+ function MoveModal({ page, onMove, onClose, loading }) {
1839
+ const cats = ["projects", "areas", "resources", "archives"].filter(c => c !== page.category);
1840
+ const [cat, setCat] = useState(cats[0]);
1841
+
1842
+ return h("div", {
1843
+ className: "modal-backdrop",
1844
+ onClick: e => { if (e.target === e.currentTarget) onClose(); },
1845
+ role: "dialog",
1846
+ "aria-modal": "true",
1847
+ "aria-labelledby": "move-modal-title"
1848
+ },
1849
+ h("div", { className: "modal" },
1850
+ h("h3", { id: "move-modal-title" }, "Move Page"),
1851
+ h("p", { style: { fontSize: "var(--text-sm)", color: "var(--text-secondary)", marginBottom: "var(--sp-4)" } },
1852
+ "Move \"" + page.frontmatter.title + "\" from " + page.category + " to:"
1853
+ ),
1854
+ h("label", { htmlFor: "move-category", style: { display: "block", fontSize: "var(--text-sm)", fontWeight: 500, marginBottom: "var(--sp-1)" } },
1855
+ "Target category"
1856
+ ),
1857
+ h("select", {
1858
+ id: "move-category",
1859
+ value: cat,
1860
+ onChange: e => setCat(e.target.value)
1861
+ },
1862
+ cats.map(c => h("option", { key: c, value: c }, c))
1863
+ ),
1864
+ h("div", { className: "modal-actions" },
1865
+ h("button", { className: "btn btn-secondary", onClick: onClose }, "Cancel"),
1866
+ h("button", {
1867
+ className: "btn btn-primary",
1868
+ onClick: () => onMove(cat),
1869
+ disabled: loading
1870
+ },
1871
+ loading ? h("span", { className: "spinner" }) : null,
1872
+ loading ? "Moving\u2026" : "Move"
1873
+ )
1874
+ )
1875
+ )
1876
+ );
1877
+ }
1878
+
1879
+ /* ============================================================
1880
+ MOUNT
1881
+ ============================================================ */
1882
+ ReactDOM.createRoot(document.getElementById("root")).render(h(App));
1883
+ </script>
1884
+ </body>
1885
+ </html>