@openparachute/vault 0.2.4 → 0.3.0

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 (102) hide show
  1. package/.claude/settings.local.json +2 -25
  2. package/CHANGELOG.md +64 -0
  3. package/CLAUDE.md +17 -7
  4. package/README.md +169 -136
  5. package/core/src/core.test.ts +591 -19
  6. package/core/src/hooks.ts +111 -3
  7. package/core/src/indexed-fields.test.ts +285 -0
  8. package/core/src/indexed-fields.ts +238 -0
  9. package/core/src/mcp.ts +127 -6
  10. package/core/src/notes.ts +153 -11
  11. package/core/src/query-operators.ts +174 -0
  12. package/core/src/schema.ts +69 -2
  13. package/core/src/store.ts +95 -1
  14. package/core/src/tag-schemas.ts +5 -0
  15. package/core/src/types.ts +28 -1
  16. package/docs/HTTP_API.md +105 -1
  17. package/docs/auth-model.md +340 -0
  18. package/package/package.json +32 -0
  19. package/package.json +2 -2
  20. package/src/auth.test.ts +83 -114
  21. package/src/auth.ts +68 -6
  22. package/src/backup-launchd.ts +1 -1
  23. package/src/backup.test.ts +1 -1
  24. package/src/backup.ts +18 -17
  25. package/src/bind.test.ts +28 -0
  26. package/src/bind.ts +19 -0
  27. package/src/cli.ts +228 -133
  28. package/src/config-triggers.test.ts +49 -0
  29. package/src/config.test.ts +317 -2
  30. package/src/config.ts +420 -40
  31. package/src/context.test.ts +136 -0
  32. package/src/context.ts +115 -0
  33. package/src/daemon.ts +17 -16
  34. package/src/doctor.test.ts +9 -7
  35. package/src/launchd.test.ts +1 -1
  36. package/src/launchd.ts +6 -6
  37. package/src/mcp-http.ts +75 -21
  38. package/src/mcp-install.test.ts +125 -0
  39. package/src/mcp-install.ts +60 -0
  40. package/src/mcp-tools.ts +34 -96
  41. package/src/module-config.ts +109 -0
  42. package/src/oauth.test.ts +345 -57
  43. package/src/oauth.ts +155 -35
  44. package/src/published.test.ts +2 -2
  45. package/src/routes.ts +209 -33
  46. package/src/routing.test.ts +817 -300
  47. package/src/routing.ts +204 -202
  48. package/src/scopes.test.ts +294 -0
  49. package/src/scopes.ts +253 -0
  50. package/src/scribe-env.test.ts +49 -0
  51. package/src/scribe-env.ts +33 -0
  52. package/src/server.ts +73 -9
  53. package/src/services-manifest.test.ts +140 -0
  54. package/src/services-manifest.ts +99 -0
  55. package/src/systemd.ts +3 -3
  56. package/src/token-store.ts +42 -9
  57. package/src/transcription-worker.test.ts +864 -0
  58. package/src/transcription-worker.ts +501 -0
  59. package/src/triggers.test.ts +191 -1
  60. package/src/triggers.ts +17 -2
  61. package/src/vault.test.ts +693 -77
  62. package/src/version.test.ts +1 -1
  63. package/.playwright-mcp/console-2026-04-14T04-17-25-395Z.log +0 -2
  64. package/.playwright-mcp/console-2026-04-14T04-18-11-767Z.log +0 -1
  65. package/.playwright-mcp/console-2026-04-14T04-19-07-733Z.log +0 -2
  66. package/.playwright-mcp/console-2026-04-14T04-20-45-440Z.log +0 -2
  67. package/.playwright-mcp/page-2026-04-14T04-17-25-536Z.yml +0 -1
  68. package/.playwright-mcp/page-2026-04-14T04-18-11-816Z.yml +0 -1
  69. package/.playwright-mcp/page-2026-04-14T04-18-31-674Z.yml +0 -211
  70. package/.playwright-mcp/page-2026-04-14T04-19-07-795Z.yml +0 -59
  71. package/.playwright-mcp/page-2026-04-14T04-19-36-239Z.yml +0 -232
  72. package/.playwright-mcp/page-2026-04-14T04-19-58-327Z.yml +0 -182
  73. package/.playwright-mcp/page-2026-04-14T04-20-10-517Z.yml +0 -91
  74. package/.playwright-mcp/page-2026-04-14T04-20-14-796Z.yml +0 -70
  75. package/.playwright-mcp/page-2026-04-14T04-20-45-509Z.yml +0 -59
  76. package/religions-abrahamic-filter.png +0 -0
  77. package/religions-buddhism-v2.png +0 -0
  78. package/religions-buddhism.png +0 -0
  79. package/religions-final.png +0 -0
  80. package/religions-v1.png +0 -0
  81. package/religions-v2.png +0 -0
  82. package/religions-zen.png +0 -0
  83. package/web/README.md +0 -73
  84. package/web/bun.lock +0 -827
  85. package/web/eslint.config.js +0 -23
  86. package/web/index.html +0 -15
  87. package/web/package.json +0 -36
  88. package/web/public/favicon.svg +0 -1
  89. package/web/public/icons.svg +0 -24
  90. package/web/src/App.tsx +0 -149
  91. package/web/src/Graph.tsx +0 -200
  92. package/web/src/NoteView.tsx +0 -155
  93. package/web/src/Sidebar.tsx +0 -186
  94. package/web/src/api.ts +0 -21
  95. package/web/src/index.css +0 -50
  96. package/web/src/main.tsx +0 -10
  97. package/web/src/types.ts +0 -37
  98. package/web/src/utils.ts +0 -107
  99. package/web/tsconfig.app.json +0 -25
  100. package/web/tsconfig.json +0 -7
  101. package/web/tsconfig.node.json +0 -24
  102. package/web/vite.config.ts +0 -16
@@ -1,23 +0,0 @@
1
- import js from '@eslint/js'
2
- import globals from 'globals'
3
- import reactHooks from 'eslint-plugin-react-hooks'
4
- import reactRefresh from 'eslint-plugin-react-refresh'
5
- import tseslint from 'typescript-eslint'
6
- import { defineConfig, globalIgnores } from 'eslint/config'
7
-
8
- export default defineConfig([
9
- globalIgnores(['dist']),
10
- {
11
- files: ['**/*.{ts,tsx}'],
12
- extends: [
13
- js.configs.recommended,
14
- tseslint.configs.recommended,
15
- reactHooks.configs.flat.recommended,
16
- reactRefresh.configs.vite,
17
- ],
18
- languageOptions: {
19
- ecmaVersion: 2020,
20
- globals: globals.browser,
21
- },
22
- },
23
- ])
package/web/index.html DELETED
@@ -1,15 +0,0 @@
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.0" />
6
- <title>Religions of the World</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=Newsreader:ital,wght@0,400;0,600;1,400&display=swap" rel="stylesheet" />
10
- </head>
11
- <body>
12
- <div id="root"></div>
13
- <script type="module" src="/src/main.tsx"></script>
14
- </body>
15
- </html>
package/web/package.json DELETED
@@ -1,36 +0,0 @@
1
- {
2
- "name": "web",
3
- "private": true,
4
- "version": "0.0.0",
5
- "type": "module",
6
- "scripts": {
7
- "dev": "vite",
8
- "build": "tsc -b && vite build",
9
- "lint": "eslint .",
10
- "preview": "vite preview"
11
- },
12
- "dependencies": {
13
- "@tailwindcss/vite": "^4.2.2",
14
- "@types/d3": "^7.4.3",
15
- "d3": "^7.9.0",
16
- "react": "^19.2.4",
17
- "react-dom": "^19.2.4",
18
- "react-markdown": "^10.1.0",
19
- "remark-gfm": "^4.0.1",
20
- "tailwindcss": "^4.2.2"
21
- },
22
- "devDependencies": {
23
- "@eslint/js": "^9.39.4",
24
- "@types/node": "^24.12.2",
25
- "@types/react": "^19.2.14",
26
- "@types/react-dom": "^19.2.3",
27
- "@vitejs/plugin-react": "^6.0.1",
28
- "eslint": "^9.39.4",
29
- "eslint-plugin-react-hooks": "^7.0.1",
30
- "eslint-plugin-react-refresh": "^0.5.2",
31
- "globals": "^17.4.0",
32
- "typescript": "~6.0.2",
33
- "typescript-eslint": "^8.58.0",
34
- "vite": "^8.0.4"
35
- }
36
- }
@@ -1 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="48" height="46" fill="none" viewBox="0 0 48 46"><path fill="#863bff" d="M25.946 44.938c-.664.845-2.021.375-2.021-.698V33.937a2.26 2.26 0 0 0-2.262-2.262H10.287c-.92 0-1.456-1.04-.92-1.788l7.48-10.471c1.07-1.497 0-3.578-1.842-3.578H1.237c-.92 0-1.456-1.04-.92-1.788L10.013.474c.214-.297.556-.474.92-.474h28.894c.92 0 1.456 1.04.92 1.788l-7.48 10.471c-1.07 1.498 0 3.579 1.842 3.579h11.377c.943 0 1.473 1.088.89 1.83L25.947 44.94z" style="fill:#863bff;fill:color(display-p3 .5252 .23 1);fill-opacity:1"/><mask id="a" width="48" height="46" x="0" y="0" maskUnits="userSpaceOnUse" style="mask-type:alpha"><path fill="#000" d="M25.842 44.938c-.664.844-2.021.375-2.021-.698V33.937a2.26 2.26 0 0 0-2.262-2.262H10.183c-.92 0-1.456-1.04-.92-1.788l7.48-10.471c1.07-1.498 0-3.579-1.842-3.579H1.133c-.92 0-1.456-1.04-.92-1.787L9.91.473c.214-.297.556-.474.92-.474h28.894c.92 0 1.456 1.04.92 1.788l-7.48 10.471c-1.07 1.498 0 3.578 1.842 3.578h11.377c.943 0 1.473 1.088.89 1.832L25.843 44.94z" style="fill:#000;fill-opacity:1"/></mask><g mask="url(#a)"><g filter="url(#b)"><ellipse cx="5.508" cy="14.704" fill="#ede6ff" rx="5.508" ry="14.704" style="fill:#ede6ff;fill:color(display-p3 .9275 .9033 1);fill-opacity:1" transform="matrix(.00324 1 1 -.00324 -4.47 31.516)"/></g><g filter="url(#c)"><ellipse cx="10.399" cy="29.851" fill="#ede6ff" rx="10.399" ry="29.851" style="fill:#ede6ff;fill:color(display-p3 .9275 .9033 1);fill-opacity:1" transform="matrix(.00324 1 1 -.00324 -39.328 7.883)"/></g><g filter="url(#d)"><ellipse cx="5.508" cy="30.487" fill="#7e14ff" rx="5.508" ry="30.487" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.814 -25.913 -14.639)scale(1 -1)"/></g><g filter="url(#e)"><ellipse cx="5.508" cy="30.599" fill="#7e14ff" rx="5.508" ry="30.599" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.814 -32.644 -3.334)scale(1 -1)"/></g><g filter="url(#f)"><ellipse cx="5.508" cy="30.599" fill="#7e14ff" rx="5.508" ry="30.599" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="matrix(.00324 1 1 -.00324 -34.34 30.47)"/></g><g filter="url(#g)"><ellipse cx="14.072" cy="22.078" fill="#ede6ff" rx="14.072" ry="22.078" style="fill:#ede6ff;fill:color(display-p3 .9275 .9033 1);fill-opacity:1" transform="rotate(93.35 24.506 48.493)scale(-1 1)"/></g><g filter="url(#h)"><ellipse cx="3.47" cy="21.501" fill="#7e14ff" rx="3.47" ry="21.501" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.009 28.708 47.59)scale(-1 1)"/></g><g filter="url(#i)"><ellipse cx="3.47" cy="21.501" fill="#7e14ff" rx="3.47" ry="21.501" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.009 28.708 47.59)scale(-1 1)"/></g><g filter="url(#j)"><ellipse cx=".387" cy="8.972" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(39.51 .387 8.972)"/></g><g filter="url(#k)"><ellipse cx="47.523" cy="-6.092" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 47.523 -6.092)"/></g><g filter="url(#l)"><ellipse cx="41.412" cy="6.333" fill="#47bfff" rx="5.971" ry="9.665" style="fill:#47bfff;fill:color(display-p3 .2799 .748 1);fill-opacity:1" transform="rotate(37.892 41.412 6.333)"/></g><g filter="url(#m)"><ellipse cx="-1.879" cy="38.332" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 -1.88 38.332)"/></g><g filter="url(#n)"><ellipse cx="-1.879" cy="38.332" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 -1.88 38.332)"/></g><g filter="url(#o)"><ellipse cx="35.651" cy="29.907" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 35.651 29.907)"/></g><g filter="url(#p)"><ellipse cx="38.418" cy="32.4" fill="#47bfff" rx="5.971" ry="15.297" style="fill:#47bfff;fill:color(display-p3 .2799 .748 1);fill-opacity:1" transform="rotate(37.892 38.418 32.4)"/></g></g><defs><filter id="b" width="60.045" height="41.654" x="-19.77" y="16.149" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="7.659"/></filter><filter id="c" width="90.34" height="51.437" x="-54.613" y="-7.533" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="7.659"/></filter><filter id="d" width="79.355" height="29.4" x="-49.64" y="2.03" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="e" width="79.579" height="29.4" x="-45.045" y="20.029" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="f" width="79.579" height="29.4" x="-43.513" y="21.178" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="g" width="74.749" height="58.852" x="15.756" y="-17.901" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="7.659"/></filter><filter id="h" width="61.377" height="25.362" x="23.548" y="2.284" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="i" width="61.377" height="25.362" x="23.548" y="2.284" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="j" width="56.045" height="63.649" x="-27.636" y="-22.853" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="k" width="54.814" height="64.646" x="20.116" y="-38.415" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="l" width="33.541" height="35.313" x="24.641" y="-11.323" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="m" width="54.814" height="64.646" x="-29.286" y="6.009" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="n" width="54.814" height="64.646" x="-29.286" y="6.009" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="o" width="54.814" height="64.646" x="8.244" y="-2.416" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="p" width="39.409" height="43.623" x="18.713" y="10.588" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter></defs></svg>
@@ -1,24 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg">
2
- <symbol id="bluesky-icon" viewBox="0 0 16 17">
3
- <g clip-path="url(#bluesky-clip)"><path fill="#08060d" d="M7.75 7.735c-.693-1.348-2.58-3.86-4.334-5.097-1.68-1.187-2.32-.981-2.74-.79C.188 2.065.1 2.812.1 3.251s.241 3.602.398 4.13c.52 1.744 2.367 2.333 4.07 2.145-2.495.37-4.71 1.278-1.805 4.512 3.196 3.309 4.38-.71 4.987-2.746.608 2.036 1.307 5.91 4.93 2.746 2.72-2.746.747-4.143-1.747-4.512 1.702.189 3.55-.4 4.07-2.145.156-.528.397-3.691.397-4.13s-.088-1.186-.575-1.406c-.42-.19-1.06-.395-2.741.79-1.755 1.24-3.64 3.752-4.334 5.099"/></g>
4
- <defs><clipPath id="bluesky-clip"><path fill="#fff" d="M.1.85h15.3v15.3H.1z"/></clipPath></defs>
5
- </symbol>
6
- <symbol id="discord-icon" viewBox="0 0 20 19">
7
- <path fill="#08060d" d="M16.224 3.768a14.5 14.5 0 0 0-3.67-1.153c-.158.286-.343.67-.47.976a13.5 13.5 0 0 0-4.067 0c-.128-.306-.317-.69-.476-.976A14.4 14.4 0 0 0 3.868 3.77C1.546 7.28.916 10.703 1.231 14.077a14.7 14.7 0 0 0 4.5 2.306q.545-.748.965-1.587a9.5 9.5 0 0 1-1.518-.74q.191-.14.372-.293c2.927 1.369 6.107 1.369 8.999 0q.183.152.372.294-.723.437-1.52.74.418.838.963 1.588a14.6 14.6 0 0 0 4.504-2.308c.37-3.911-.63-7.302-2.644-10.309m-9.13 8.234c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.894 0 1.614.82 1.599 1.82.001 1-.705 1.82-1.6 1.82m5.91 0c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.893 0 1.614.82 1.599 1.82 0 1-.706 1.82-1.6 1.82"/>
8
- </symbol>
9
- <symbol id="documentation-icon" viewBox="0 0 21 20">
10
- <path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="m15.5 13.333 1.533 1.322c.645.555.967.833.967 1.178s-.322.623-.967 1.179L15.5 18.333m-3.333-5-1.534 1.322c-.644.555-.966.833-.966 1.178s.322.623.966 1.179l1.534 1.321"/>
11
- <path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M17.167 10.836v-4.32c0-1.41 0-2.117-.224-2.68-.359-.906-1.118-1.621-2.08-1.96-.599-.21-1.349-.21-2.848-.21-2.623 0-3.935 0-4.983.369-1.684.591-3.013 1.842-3.641 3.428C3 6.449 3 7.684 3 10.154v2.122c0 2.558 0 3.838.706 4.726q.306.383.713.671c.76.536 1.79.64 3.581.66"/>
12
- <path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M3 10a2.78 2.78 0 0 1 2.778-2.778c.555 0 1.209.097 1.748-.047.48-.129.854-.503.982-.982.145-.54.048-1.194.048-1.749a2.78 2.78 0 0 1 2.777-2.777"/>
13
- </symbol>
14
- <symbol id="github-icon" viewBox="0 0 19 19">
15
- <path fill="#08060d" fill-rule="evenodd" d="M9.356 1.85C5.05 1.85 1.57 5.356 1.57 9.694a7.84 7.84 0 0 0 5.324 7.44c.387.079.528-.168.528-.376 0-.182-.013-.805-.013-1.454-2.165.467-2.616-.935-2.616-.935-.349-.91-.864-1.143-.864-1.143-.71-.48.051-.48.051-.48.787.051 1.2.805 1.2.805.695 1.194 1.817.857 2.268.649.064-.507.27-.857.49-1.052-1.728-.182-3.545-.857-3.545-3.87 0-.857.31-1.558.8-2.104-.078-.195-.349-1 .077-2.078 0 0 .657-.208 2.14.805a7.5 7.5 0 0 1 1.946-.26c.657 0 1.328.092 1.946.26 1.483-1.013 2.14-.805 2.14-.805.426 1.078.155 1.883.078 2.078.502.546.799 1.247.799 2.104 0 3.013-1.818 3.675-3.558 3.87.284.247.528.714.528 1.454 0 1.052-.012 1.896-.012 2.156 0 .208.142.455.528.377a7.84 7.84 0 0 0 5.324-7.441c.013-4.338-3.48-7.844-7.773-7.844" clip-rule="evenodd"/>
16
- </symbol>
17
- <symbol id="social-icon" viewBox="0 0 20 20">
18
- <path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M12.5 6.667a4.167 4.167 0 1 0-8.334 0 4.167 4.167 0 0 0 8.334 0"/>
19
- <path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M2.5 16.667a5.833 5.833 0 0 1 8.75-5.053m3.837.474.513 1.035c.07.144.257.282.414.309l.93.155c.596.1.736.536.307.965l-.723.73a.64.64 0 0 0-.152.531l.207.903c.164.715-.213.991-.84.618l-.872-.52a.63.63 0 0 0-.577 0l-.872.52c-.624.373-1.003.094-.84-.618l.207-.903a.64.64 0 0 0-.152-.532l-.723-.729c-.426-.43-.289-.864.306-.964l.93-.156a.64.64 0 0 0 .412-.31l.513-1.034c.28-.562.735-.562 1.012 0"/>
20
- </symbol>
21
- <symbol id="x-icon" viewBox="0 0 19 19">
22
- <path fill="#08060d" fill-rule="evenodd" d="M1.893 1.98c.052.072 1.245 1.769 2.653 3.77l2.892 4.114c.183.261.333.48.333.486s-.068.089-.152.183l-.522.593-.765.867-3.597 4.087c-.375.426-.734.834-.798.905a1 1 0 0 0-.118.148c0 .01.236.017.664.017h.663l.729-.83c.4-.457.796-.906.879-.999a692 692 0 0 0 1.794-2.038c.034-.037.301-.34.594-.675l.551-.624.345-.392a7 7 0 0 1 .34-.374c.006 0 .93 1.306 2.052 2.903l2.084 2.965.045.063h2.275c1.87 0 2.273-.003 2.266-.021-.008-.02-1.098-1.572-3.894-5.547-2.013-2.862-2.28-3.246-2.273-3.266.008-.019.282-.332 2.085-2.38l2-2.274 1.567-1.782c.022-.028-.016-.03-.65-.03h-.674l-.3.342a871 871 0 0 1-1.782 2.025c-.067.075-.405.458-.75.852a100 100 0 0 1-.803.91c-.148.172-.299.344-.99 1.127-.304.343-.32.358-.345.327-.015-.019-.904-1.282-1.976-2.808L6.365 1.85H1.8zm1.782.91 8.078 11.294c.772 1.08 1.413 1.973 1.425 1.984.016.017.241.02 1.05.017l1.03-.004-2.694-3.766L7.796 5.75 5.722 2.852l-1.039-.004-1.039-.004z" clip-rule="evenodd"/>
23
- </symbol>
24
- </svg>
package/web/src/App.tsx DELETED
@@ -1,149 +0,0 @@
1
- import { useState, useEffect, useCallback, useMemo } from "react";
2
- import { fetchAllNotes } from "./api";
3
- import { buildGraph, extractWikilinks } from "./utils";
4
- import type { Note } from "./types";
5
- import Graph from "./Graph";
6
- import NoteView from "./NoteView";
7
- import Sidebar from "./Sidebar";
8
-
9
- export default function App() {
10
- const [notes, setNotes] = useState<Note[]>([]);
11
- const [loading, setLoading] = useState(true);
12
- const [selectedId, setSelectedId] = useState<string | null>(null);
13
- const [activeFilter, setActiveFilter] = useState<string | null>(null);
14
- const [searchQuery, setSearchQuery] = useState("");
15
-
16
- useEffect(() => {
17
- fetchAllNotes().then((data) => {
18
- setNotes(data);
19
- setLoading(false);
20
- });
21
- }, []);
22
-
23
- const { nodes, edges } = useMemo(() => buildGraph(notes), [notes]);
24
-
25
- const noteById = useMemo(() => {
26
- const map = new Map<string, Note>();
27
- for (const n of notes) map.set(n.id, n);
28
- return map;
29
- }, [notes]);
30
-
31
- const pathToId = useMemo(() => {
32
- const map = new Map<string, string>();
33
- for (const n of notes) {
34
- if (n.path) map.set(n.path, n.id);
35
- }
36
- return map;
37
- }, [notes]);
38
-
39
- const filteredNodes = useMemo(() => {
40
- if (!activeFilter) return nodes;
41
- return nodes.filter((n) => n.tags.includes(activeFilter));
42
- }, [nodes, activeFilter]);
43
-
44
- const filteredEdges = useMemo(() => {
45
- const ids = new Set(filteredNodes.map((n) => n.id));
46
- return edges.filter((e) => {
47
- const s = typeof e.source === "string" ? e.source : e.source.id;
48
- const t = typeof e.target === "string" ? e.target : e.target.id;
49
- return ids.has(s) && ids.has(t);
50
- });
51
- }, [filteredNodes, edges]);
52
-
53
- const searchResults = useMemo(() => {
54
- if (!searchQuery.trim()) return [];
55
- const q = searchQuery.toLowerCase();
56
- return nodes.filter(
57
- (n) =>
58
- n.label.toLowerCase().includes(q) ||
59
- n.path.toLowerCase().includes(q)
60
- ).slice(0, 20);
61
- }, [nodes, searchQuery]);
62
-
63
- const handleNodeClick = useCallback((nodeId: string) => {
64
- setSelectedId(nodeId);
65
- setSearchQuery("");
66
- }, []);
67
-
68
- const handleWikilinkClick = useCallback(
69
- (path: string) => {
70
- const id = pathToId.get(path);
71
- if (id) setSelectedId(id);
72
- },
73
- [pathToId],
74
- );
75
-
76
- const selectedNote = selectedId ? noteById.get(selectedId) : null;
77
-
78
- // Find connected notes for the sidebar "related" section
79
- const connectedIds = useMemo(() => {
80
- if (!selectedNote) return [];
81
- const links = extractWikilinks(selectedNote.content);
82
- const seen = new Set<string>();
83
- return links
84
- .map((p) => pathToId.get(p))
85
- .filter((id): id is string => {
86
- if (!id || id === selectedId || seen.has(id)) return false;
87
- seen.add(id);
88
- return true;
89
- });
90
- }, [selectedNote, pathToId, selectedId]);
91
-
92
- if (loading) {
93
- return (
94
- <div className="h-screen flex items-center justify-center bg-bg">
95
- <div className="text-text-muted text-lg animate-pulse">
96
- Loading the world's religions...
97
- </div>
98
- </div>
99
- );
100
- }
101
-
102
- return (
103
- <div className="h-screen flex overflow-hidden bg-bg">
104
- {/* Left sidebar */}
105
- <Sidebar
106
- nodes={nodes}
107
- activeFilter={activeFilter}
108
- onFilterChange={setActiveFilter}
109
- searchQuery={searchQuery}
110
- onSearchChange={setSearchQuery}
111
- searchResults={searchResults}
112
- onSelect={handleNodeClick}
113
- selectedId={selectedId}
114
- />
115
-
116
- {/* Graph canvas */}
117
- <div className="flex-1 relative">
118
- <Graph
119
- nodes={activeFilter ? filteredNodes : nodes}
120
- edges={activeFilter ? filteredEdges : edges}
121
- selectedId={selectedId}
122
- onNodeClick={handleNodeClick}
123
- />
124
-
125
- {/* Stats overlay */}
126
- <div className="absolute bottom-4 left-4 text-xs text-text-muted space-x-4 pointer-events-none select-none">
127
- <span>{notes.length} notes</span>
128
- <span>{edges.length} connections</span>
129
- {activeFilter && (
130
- <span className="text-accent">
131
- filtered: {activeFilter}
132
- </span>
133
- )}
134
- </div>
135
- </div>
136
-
137
- {/* Note reading pane */}
138
- {selectedNote && (
139
- <NoteView
140
- note={selectedNote}
141
- connectedIds={connectedIds}
142
- noteById={noteById}
143
- onClose={() => setSelectedId(null)}
144
- onNavigate={handleWikilinkClick}
145
- />
146
- )}
147
- </div>
148
- );
149
- }
package/web/src/Graph.tsx DELETED
@@ -1,200 +0,0 @@
1
- import { useEffect, useRef } from "react";
2
- import * as d3 from "d3";
3
- import type { GraphNode, GraphEdge } from "./types";
4
- import { nodeColor, nodeRadius } from "./utils";
5
-
6
- interface Props {
7
- nodes: GraphNode[];
8
- edges: GraphEdge[];
9
- selectedId: string | null;
10
- onNodeClick: (id: string) => void;
11
- }
12
-
13
- export default function Graph({ nodes, edges, selectedId, onNodeClick }: Props) {
14
- const svgRef = useRef<SVGSVGElement>(null);
15
- const simRef = useRef<d3.Simulation<GraphNode, GraphEdge> | null>(null);
16
-
17
- const onNodeClickRef = useRef(onNodeClick);
18
- onNodeClickRef.current = onNodeClick;
19
-
20
- useEffect(() => {
21
- const svg = d3.select(svgRef.current!);
22
- const width = svgRef.current!.clientWidth;
23
- const height = svgRef.current!.clientHeight;
24
-
25
- svg.selectAll("*").remove();
26
-
27
- // Background
28
- svg
29
- .append("rect")
30
- .attr("width", width)
31
- .attr("height", height)
32
- .attr("fill", "#0a0a0f");
33
-
34
- const g = svg.append("g");
35
-
36
- // Zoom
37
- const zoom = d3
38
- .zoom<SVGSVGElement, unknown>()
39
- .scaleExtent([0.15, 5])
40
- .on("zoom", (event) => {
41
- g.attr("transform", event.transform);
42
- });
43
- svg.call(zoom);
44
-
45
- // Center initial view
46
- svg.call(
47
- zoom.transform,
48
- d3.zoomIdentity.translate(width / 2, height / 2).scale(0.8),
49
- );
50
-
51
- // Copy nodes/edges so D3 can mutate them
52
- const simNodes: GraphNode[] = nodes.map((n) => ({ ...n }));
53
- const simEdges: GraphEdge[] = edges.map((e) => ({
54
- source: typeof e.source === "string" ? e.source : e.source.id,
55
- target: typeof e.target === "string" ? e.target : e.target.id,
56
- }));
57
-
58
- // Simulation
59
- const sim = d3
60
- .forceSimulation<GraphNode>(simNodes)
61
- .force(
62
- "link",
63
- d3
64
- .forceLink<GraphNode, GraphEdge>(simEdges)
65
- .id((d) => d.id)
66
- .distance(40)
67
- .strength(0.3),
68
- )
69
- .force("charge", d3.forceManyBody().strength(-60).distanceMax(300))
70
- .force("center", d3.forceCenter(0, 0).strength(0.05))
71
- .force("collision", d3.forceCollide().radius((d: any) => nodeRadius(d) + 2))
72
- .alphaDecay(0.02);
73
-
74
- simRef.current = sim;
75
-
76
- // Edges
77
- const link = g
78
- .append("g")
79
- .selectAll("line")
80
- .data(simEdges)
81
- .join("line")
82
- .attr("stroke", "#1e1e30")
83
- .attr("stroke-width", 0.5)
84
- .attr("stroke-opacity", 0.6);
85
-
86
- // Nodes
87
- const node = g
88
- .append("g")
89
- .selectAll("circle")
90
- .data(simNodes)
91
- .join("circle")
92
- .attr("r", (d) => nodeRadius(d))
93
- .attr("fill", (d) => nodeColor(d))
94
- .attr("stroke", "none")
95
- .attr("opacity", 0.9)
96
- .style("cursor", "pointer")
97
- .on("click", (_event, d) => {
98
- onNodeClickRef.current(d.id);
99
- })
100
- .on("mouseenter", function (event, d) {
101
- d3.select(this).attr("r", nodeRadius(d) * 1.6).attr("opacity", 1);
102
- tooltip
103
- .style("display", "block")
104
- .style("left", event.pageX + 12 + "px")
105
- .style("top", event.pageY - 8 + "px")
106
- .text(d.label);
107
- })
108
- .on("mouseleave", function (_event, d) {
109
- d3.select(this).attr("r", nodeRadius(d)).attr("opacity", 0.9);
110
- tooltip.style("display", "none");
111
- });
112
-
113
- // Drag
114
- const drag = d3
115
- .drag<SVGCircleElement, GraphNode>()
116
- .on("start", (event, d) => {
117
- if (!event.active) sim.alphaTarget(0.3).restart();
118
- d.fx = d.x;
119
- d.fy = d.y;
120
- })
121
- .on("drag", (event, d) => {
122
- d.fx = event.x;
123
- d.fy = event.y;
124
- })
125
- .on("end", (event, d) => {
126
- if (!event.active) sim.alphaTarget(0);
127
- d.fx = null;
128
- d.fy = null;
129
- });
130
- (node as any).call(drag);
131
-
132
- // Labels for large nodes
133
- const labels = g
134
- .append("g")
135
- .selectAll("text")
136
- .data(simNodes.filter((n) => n.type === "tradition" || n.type === "moc"))
137
- .join("text")
138
- .text((d) => d.label)
139
- .attr("fill", (d) => nodeColor(d))
140
- .attr("font-size", (d) => (d.type === "moc" ? "8px" : "7px"))
141
- .attr("font-weight", 500)
142
- .attr("font-family", "Inter, sans-serif")
143
- .attr("text-anchor", "middle")
144
- .attr("dy", (d) => nodeRadius(d) + 11)
145
- .attr("opacity", 0.7)
146
- .attr("pointer-events", "none");
147
-
148
- // Tooltip
149
- const tooltip = d3
150
- .select("body")
151
- .append("div")
152
- .style("position", "absolute")
153
- .style("display", "none")
154
- .style("background", "#1a1a26")
155
- .style("color", "#d4d4e0")
156
- .style("padding", "4px 10px")
157
- .style("border-radius", "4px")
158
- .style("font-size", "12px")
159
- .style("font-family", "Inter, sans-serif")
160
- .style("pointer-events", "none")
161
- .style("z-index", "100")
162
- .style("border", "1px solid #2a2a3a");
163
-
164
- sim.on("tick", () => {
165
- link
166
- .attr("x1", (d: any) => d.source.x)
167
- .attr("y1", (d: any) => d.source.y)
168
- .attr("x2", (d: any) => d.target.x)
169
- .attr("y2", (d: any) => d.target.y);
170
-
171
- node.attr("cx", (d) => d.x!).attr("cy", (d) => d.y!);
172
-
173
- labels.attr("x", (d) => d.x!).attr("y", (d) => d.y!);
174
- });
175
-
176
- return () => {
177
- sim.stop();
178
- tooltip.remove();
179
- };
180
- }, [nodes, edges]);
181
-
182
- // Highlight selected node
183
- useEffect(() => {
184
- if (!svgRef.current) return;
185
- const svg = d3.select(svgRef.current);
186
- svg.selectAll("circle").attr("stroke", (d: any) =>
187
- d.id === selectedId ? "#ffffff" : "none",
188
- ).attr("stroke-width", (d: any) =>
189
- d.id === selectedId ? 2 : 0,
190
- );
191
- }, [selectedId]);
192
-
193
- return (
194
- <svg
195
- ref={svgRef}
196
- className="w-full h-full"
197
- style={{ background: "#0a0a0f" }}
198
- />
199
- );
200
- }
@@ -1,155 +0,0 @@
1
- import { useMemo } from "react";
2
- import Markdown from "react-markdown";
3
- import remarkGfm from "remark-gfm";
4
- import type { Note } from "./types";
5
- import { nodeColor } from "./utils";
6
-
7
- interface Props {
8
- note: Note;
9
- connectedIds: string[];
10
- noteById: Map<string, Note>;
11
- onClose: () => void;
12
- onNavigate: (path: string) => void;
13
- }
14
-
15
- function getType(tags: string[]): string {
16
- for (const t of tags) if (t.startsWith("type/")) return t.slice(5);
17
- return "other";
18
- }
19
-
20
- function getFamily(tags: string[]): string {
21
- for (const t of tags) if (t.startsWith("family/")) return t.slice(7);
22
- return "";
23
- }
24
-
25
- export default function NoteView({ note, connectedIds, noteById, onClose, onNavigate }: Props) {
26
- const type = getType(note.tags);
27
- const family = getFamily(note.tags);
28
-
29
- // Convert wikilinks [[Path]] and [[Path|Label]] to markdown links
30
- const processedContent = useMemo(() => {
31
- return note.content.replace(
32
- /\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g,
33
- (_match, path, label) => `[${label || path.split("/").pop()}](wikilink:${encodeURIComponent(path)})`,
34
- );
35
- }, [note.content]);
36
-
37
- const connected = connectedIds
38
- .map((id) => noteById.get(id))
39
- .filter((n): n is Note => !!n)
40
- .slice(0, 15);
41
-
42
- const familyLabel = family ? family.charAt(0).toUpperCase() + family.slice(1) : null;
43
- const typeLabel = type.charAt(0).toUpperCase() + type.slice(1);
44
-
45
- const color = nodeColor({
46
- id: note.id,
47
- path: note.path ?? "",
48
- label: "",
49
- type,
50
- family,
51
- tags: note.tags,
52
- });
53
-
54
- return (
55
- <div className="w-[480px] min-w-[380px] h-screen bg-surface border-l border-border flex flex-col overflow-hidden">
56
- {/* Header */}
57
- <div className="flex items-start justify-between p-5 pb-3 border-b border-border">
58
- <div className="flex-1 min-w-0">
59
- <div className="flex items-center gap-2 mb-1.5 text-xs">
60
- <span
61
- className="inline-block w-2 h-2 rounded-full"
62
- style={{ background: color }}
63
- />
64
- <span className="text-text-muted">{typeLabel}</span>
65
- {familyLabel && (
66
- <>
67
- <span className="text-text-muted opacity-40">/</span>
68
- <span className="text-text-muted">{familyLabel}</span>
69
- </>
70
- )}
71
- </div>
72
- {note.path && (
73
- <div className="text-text-muted text-xs font-mono truncate opacity-50">
74
- {note.path}
75
- </div>
76
- )}
77
- </div>
78
- <button
79
- onClick={onClose}
80
- className="text-text-muted hover:text-text ml-3 mt-0.5 text-lg leading-none cursor-pointer"
81
- >
82
- &times;
83
- </button>
84
- </div>
85
-
86
- {/* Content */}
87
- <div className="flex-1 overflow-y-auto p-5">
88
- <div className="note-content">
89
- <Markdown
90
- remarkPlugins={[remarkGfm]}
91
- components={{
92
- a({ href, children }) {
93
- if (href?.startsWith("wikilink:")) {
94
- const path = decodeURIComponent(href.slice(9));
95
- return (
96
- <a
97
- href="#"
98
- onClick={(e) => {
99
- e.preventDefault();
100
- onNavigate(path);
101
- }}
102
- className="text-accent hover:underline cursor-pointer"
103
- >
104
- {children}
105
- </a>
106
- );
107
- }
108
- return (
109
- <a href={href} target="_blank" rel="noopener noreferrer">
110
- {children}
111
- </a>
112
- );
113
- },
114
- }}
115
- >
116
- {processedContent}
117
- </Markdown>
118
- </div>
119
- </div>
120
-
121
- {/* Connected notes */}
122
- {connected.length > 0 && (
123
- <div className="border-t border-border p-4 max-h-[200px] overflow-y-auto">
124
- <div className="text-xs text-text-muted mb-2 font-medium uppercase tracking-wider">
125
- Connected ({connected.length})
126
- </div>
127
- <div className="space-y-1">
128
- {connected.map((n) => (
129
- <button
130
- key={n.id}
131
- onClick={() => onNavigate(n.path ?? "")}
132
- className="w-full text-left text-sm text-text hover:text-accent transition-colors truncate py-0.5 cursor-pointer"
133
- >
134
- <span
135
- className="inline-block w-1.5 h-1.5 rounded-full mr-2 opacity-70"
136
- style={{
137
- background: nodeColor({
138
- id: n.id,
139
- path: n.path ?? "",
140
- label: "",
141
- type: getType(n.tags),
142
- family: getFamily(n.tags),
143
- tags: n.tags,
144
- }),
145
- }}
146
- />
147
- {(n.path ?? n.id).split("/").pop()}
148
- </button>
149
- ))}
150
- </div>
151
- </div>
152
- )}
153
- </div>
154
- );
155
- }