devglide 0.1.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 (252) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +338 -0
  3. package/bin/claude-md-template.js +94 -0
  4. package/bin/devglide.js +387 -0
  5. package/package.json +85 -0
  6. package/pnpm-workspace.yaml +3 -0
  7. package/src/apps/coder/.turbo/turbo-lint.log +5 -0
  8. package/src/apps/coder/package.json +16 -0
  9. package/src/apps/coder/public/favicon.svg +7 -0
  10. package/src/apps/coder/public/page.css +275 -0
  11. package/src/apps/coder/public/page.js +528 -0
  12. package/src/apps/coder/server.js +3 -0
  13. package/src/apps/documentation/public/page.css +597 -0
  14. package/src/apps/documentation/public/page.js +609 -0
  15. package/src/apps/kanban/.turbo/turbo-lint.log +97 -0
  16. package/src/apps/kanban/.turbo/turbo-typecheck.log +5 -0
  17. package/src/apps/kanban/package.json +32 -0
  18. package/src/apps/kanban/public/favicon.svg +7 -0
  19. package/src/apps/kanban/public/page.css +1010 -0
  20. package/src/apps/kanban/public/page.js +1730 -0
  21. package/src/apps/kanban/public/vendor/marked.min.js +6 -0
  22. package/src/apps/kanban/public/vendor/sortable.min.js +2 -0
  23. package/src/apps/kanban/src/db.ts +319 -0
  24. package/src/apps/kanban/src/index.ts +14 -0
  25. package/src/apps/kanban/src/mcp-helpers.test.ts +88 -0
  26. package/src/apps/kanban/src/mcp-helpers.ts +60 -0
  27. package/src/apps/kanban/src/mcp.ts +59 -0
  28. package/src/apps/kanban/src/routes/attachments.ts +161 -0
  29. package/src/apps/kanban/src/routes/features.ts +233 -0
  30. package/src/apps/kanban/src/routes/issues.ts +373 -0
  31. package/src/apps/kanban/src/tools/feature-tools.ts +164 -0
  32. package/src/apps/kanban/src/tools/item-tools.ts +307 -0
  33. package/src/apps/kanban/src/tools/versioned-entry-tools.ts +72 -0
  34. package/src/apps/kanban/tsconfig.check.json +9 -0
  35. package/src/apps/kanban/tsconfig.json +9 -0
  36. package/src/apps/keymap/.turbo/turbo-lint.log +5 -0
  37. package/src/apps/keymap/package.json +16 -0
  38. package/src/apps/keymap/public/page.css +275 -0
  39. package/src/apps/keymap/public/page.js +294 -0
  40. package/src/apps/keymap/server.js +25 -0
  41. package/src/apps/log/.turbo/turbo-build.log +5 -0
  42. package/src/apps/log/.turbo/turbo-lint.log +45 -0
  43. package/src/apps/log/.turbo/turbo-typecheck.log +5 -0
  44. package/src/apps/log/node_modules/.bin/tsc +21 -0
  45. package/src/apps/log/node_modules/.bin/tsserver +21 -0
  46. package/src/apps/log/node_modules/.bin/tsx +21 -0
  47. package/src/apps/log/package.json +36 -0
  48. package/src/apps/log/public/console-sniffer.js +221 -0
  49. package/src/apps/log/public/favicon.svg +7 -0
  50. package/src/apps/log/public/page.css +322 -0
  51. package/src/apps/log/public/page.js +463 -0
  52. package/src/apps/log/src/index.ts +9 -0
  53. package/src/apps/log/src/mcp.ts +122 -0
  54. package/src/apps/log/src/routes/log.ts +333 -0
  55. package/src/apps/log/src/routes/status.ts +25 -0
  56. package/src/apps/log/src/server-sniffer.ts +118 -0
  57. package/src/apps/log/src/services/file-patterns.ts +39 -0
  58. package/src/apps/log/src/services/file-tailer.ts +228 -0
  59. package/src/apps/log/src/services/line-parser.ts +94 -0
  60. package/src/apps/log/src/services/log-writer.ts +39 -0
  61. package/src/apps/log/tsconfig.json +8 -0
  62. package/src/apps/prompts/.turbo/turbo-build.log +5 -0
  63. package/src/apps/prompts/.turbo/turbo-lint.log +24 -0
  64. package/src/apps/prompts/.turbo/turbo-typecheck.log +5 -0
  65. package/src/apps/prompts/mcp.ts +175 -0
  66. package/src/apps/prompts/node_modules/.bin/tsc +21 -0
  67. package/src/apps/prompts/node_modules/.bin/tsserver +21 -0
  68. package/src/apps/prompts/node_modules/.bin/tsx +21 -0
  69. package/src/apps/prompts/package.json +25 -0
  70. package/src/apps/prompts/public/page.css +315 -0
  71. package/src/apps/prompts/public/page.js +541 -0
  72. package/src/apps/prompts/services/prompt-store.ts +212 -0
  73. package/src/apps/prompts/src/index.ts +9 -0
  74. package/src/apps/prompts/tsconfig.json +8 -0
  75. package/src/apps/prompts/types.ts +27 -0
  76. package/src/apps/shell/.turbo/turbo-build.log +5 -0
  77. package/src/apps/shell/.turbo/turbo-lint.log +34 -0
  78. package/src/apps/shell/.turbo/turbo-typecheck.log +5 -0
  79. package/src/apps/shell/package.json +35 -0
  80. package/src/apps/shell/public/favicon.svg +7 -0
  81. package/src/apps/shell/public/page.css +407 -0
  82. package/src/apps/shell/public/page.js +1577 -0
  83. package/src/apps/shell/src/index.ts +150 -0
  84. package/src/apps/shell/src/mcp.ts +398 -0
  85. package/src/apps/shell/src/shell-types.ts +41 -0
  86. package/src/apps/shell/tsconfig.json +8 -0
  87. package/src/apps/test/.turbo/turbo-build.log +5 -0
  88. package/src/apps/test/.turbo/turbo-lint.log +27 -0
  89. package/src/apps/test/.turbo/turbo-typecheck.log +5 -0
  90. package/src/apps/test/node_modules/.bin/tsc +21 -0
  91. package/src/apps/test/node_modules/.bin/tsserver +21 -0
  92. package/src/apps/test/node_modules/.bin/tsx +21 -0
  93. package/src/apps/test/node_modules/.bin/uuid +21 -0
  94. package/src/apps/test/package.json +35 -0
  95. package/src/apps/test/public/favicon.svg +7 -0
  96. package/src/apps/test/public/page.css +499 -0
  97. package/src/apps/test/public/page.js +417 -0
  98. package/src/apps/test/public/scenario-runner.js +450 -0
  99. package/src/apps/test/src/index.ts +9 -0
  100. package/src/apps/test/src/mcp.ts +192 -0
  101. package/src/apps/test/src/routes/trigger.ts +285 -0
  102. package/src/apps/test/src/services/scenario-broadcaster.ts +60 -0
  103. package/src/apps/test/src/services/scenario-manager.ts +361 -0
  104. package/src/apps/test/src/services/scenario-store.ts +145 -0
  105. package/src/apps/test/tsconfig.json +8 -0
  106. package/src/apps/vocabulary/.turbo/turbo-build.log +5 -0
  107. package/src/apps/vocabulary/.turbo/turbo-lint.log +25 -0
  108. package/src/apps/vocabulary/.turbo/turbo-typecheck.log +5 -0
  109. package/src/apps/vocabulary/mcp.ts +173 -0
  110. package/src/apps/vocabulary/node_modules/.bin/tsc +21 -0
  111. package/src/apps/vocabulary/node_modules/.bin/tsserver +21 -0
  112. package/src/apps/vocabulary/node_modules/.bin/tsx +21 -0
  113. package/src/apps/vocabulary/package.json +25 -0
  114. package/src/apps/vocabulary/public/page.css +247 -0
  115. package/src/apps/vocabulary/public/page.js +444 -0
  116. package/src/apps/vocabulary/services/vocabulary-store.ts +179 -0
  117. package/src/apps/vocabulary/src/index.ts +10 -0
  118. package/src/apps/vocabulary/tsconfig.json +8 -0
  119. package/src/apps/vocabulary/types.ts +22 -0
  120. package/src/apps/voice/.turbo/turbo-build.log +5 -0
  121. package/src/apps/voice/.turbo/turbo-lint.log +43 -0
  122. package/src/apps/voice/.turbo/turbo-typecheck.log +5 -0
  123. package/src/apps/voice/node_modules/.bin/openai +21 -0
  124. package/src/apps/voice/node_modules/.bin/tsc +21 -0
  125. package/src/apps/voice/node_modules/.bin/tsserver +21 -0
  126. package/src/apps/voice/node_modules/.bin/tsx +21 -0
  127. package/src/apps/voice/package.json +35 -0
  128. package/src/apps/voice/public/favicon.svg +7 -0
  129. package/src/apps/voice/public/page.css +388 -0
  130. package/src/apps/voice/public/page.js +718 -0
  131. package/src/apps/voice/src/index.ts +10 -0
  132. package/src/apps/voice/src/mcp.ts +70 -0
  133. package/src/apps/voice/src/providers/index.ts +85 -0
  134. package/src/apps/voice/src/providers/openai-compatible.ts +94 -0
  135. package/src/apps/voice/src/providers/types.ts +27 -0
  136. package/src/apps/voice/src/routes/config.ts +118 -0
  137. package/src/apps/voice/src/routes/transcribe.ts +90 -0
  138. package/src/apps/voice/src/services/config-store.ts +129 -0
  139. package/src/apps/voice/src/services/stats.ts +108 -0
  140. package/src/apps/voice/src/transcribe.ts +11 -0
  141. package/src/apps/voice/src/utils/mime.ts +16 -0
  142. package/src/apps/voice/tsconfig.json +8 -0
  143. package/src/apps/workflow/.turbo/turbo-build.log +5 -0
  144. package/src/apps/workflow/.turbo/turbo-lint.log +96 -0
  145. package/src/apps/workflow/.turbo/turbo-typecheck.log +5 -0
  146. package/src/apps/workflow/engine/executors/decision-executor.ts +87 -0
  147. package/src/apps/workflow/engine/executors/file-executor.ts +90 -0
  148. package/src/apps/workflow/engine/executors/git-executor.ts +137 -0
  149. package/src/apps/workflow/engine/executors/http-executor.ts +65 -0
  150. package/src/apps/workflow/engine/executors/index.ts +28 -0
  151. package/src/apps/workflow/engine/executors/kanban-executor.ts +154 -0
  152. package/src/apps/workflow/engine/executors/llm-executor.ts +46 -0
  153. package/src/apps/workflow/engine/executors/log-executor.ts +62 -0
  154. package/src/apps/workflow/engine/executors/loop-executor.ts +14 -0
  155. package/src/apps/workflow/engine/executors/shell-executor.ts +107 -0
  156. package/src/apps/workflow/engine/executors/sub-workflow-executor.ts +61 -0
  157. package/src/apps/workflow/engine/executors/test-executor.ts +73 -0
  158. package/src/apps/workflow/engine/executors/trigger-executor.ts +39 -0
  159. package/src/apps/workflow/engine/expression-evaluator.ts +117 -0
  160. package/src/apps/workflow/engine/graph-runner.ts +438 -0
  161. package/src/apps/workflow/engine/node-executor.ts +104 -0
  162. package/src/apps/workflow/engine/node-registry.ts +15 -0
  163. package/src/apps/workflow/engine/variable-resolver.ts +109 -0
  164. package/src/apps/workflow/mcp.ts +223 -0
  165. package/src/apps/workflow/node_modules/.bin/tsc +21 -0
  166. package/src/apps/workflow/node_modules/.bin/tsserver +21 -0
  167. package/src/apps/workflow/node_modules/.bin/tsx +21 -0
  168. package/src/apps/workflow/package.json +25 -0
  169. package/src/apps/workflow/public/editor/canvas.js +366 -0
  170. package/src/apps/workflow/public/editor/drag-manager.js +326 -0
  171. package/src/apps/workflow/public/editor/edge-renderer.js +235 -0
  172. package/src/apps/workflow/public/editor/history-manager.js +147 -0
  173. package/src/apps/workflow/public/editor/layout-engine.js +159 -0
  174. package/src/apps/workflow/public/editor/node-renderer.js +199 -0
  175. package/src/apps/workflow/public/editor/selection-manager.js +193 -0
  176. package/src/apps/workflow/public/favicon.svg +7 -0
  177. package/src/apps/workflow/public/models/node-types.js +300 -0
  178. package/src/apps/workflow/public/models/workflow-model.js +257 -0
  179. package/src/apps/workflow/public/page.css +406 -0
  180. package/src/apps/workflow/public/page.js +658 -0
  181. package/src/apps/workflow/public/panels/inspector.js +360 -0
  182. package/src/apps/workflow/public/panels/palette.js +106 -0
  183. package/src/apps/workflow/public/panels/run-view.js +275 -0
  184. package/src/apps/workflow/public/panels/toolbar.js +232 -0
  185. package/src/apps/workflow/public/panels/workflow-list.js +237 -0
  186. package/src/apps/workflow/public/state/store.js +47 -0
  187. package/src/apps/workflow/services/custom-node-loader.ts +48 -0
  188. package/src/apps/workflow/services/legacy-converter.ts +72 -0
  189. package/src/apps/workflow/services/run-manager.ts +190 -0
  190. package/src/apps/workflow/services/workflow-store.ts +424 -0
  191. package/src/apps/workflow/services/workflow-validator.test.ts +103 -0
  192. package/src/apps/workflow/services/workflow-validator.ts +98 -0
  193. package/src/apps/workflow/src/index.ts +10 -0
  194. package/src/apps/workflow/templates/ci-pipeline.json +18 -0
  195. package/src/apps/workflow/templates/code-review.json +22 -0
  196. package/src/apps/workflow/templates/kanban-testing.json +24 -0
  197. package/src/apps/workflow/tsconfig.json +8 -0
  198. package/src/apps/workflow/types.ts +268 -0
  199. package/src/packages/auth-middleware.ts +14 -0
  200. package/src/packages/design-tokens/.turbo/turbo-build.log +10 -0
  201. package/src/packages/design-tokens/STYLEGUIDE.md +414 -0
  202. package/src/packages/design-tokens/build.js +413 -0
  203. package/src/packages/design-tokens/demo/index.html +1367 -0
  204. package/src/packages/design-tokens/demo/proposition-a.html +717 -0
  205. package/src/packages/design-tokens/demo/proposition-b.html +1239 -0
  206. package/src/packages/design-tokens/demo/proposition-c.html +1049 -0
  207. package/src/packages/design-tokens/dist/tailwind-preset.js +115 -0
  208. package/src/packages/design-tokens/dist/tokens.css +345 -0
  209. package/src/packages/design-tokens/dist/tokens.d.ts +229 -0
  210. package/src/packages/design-tokens/dist/tokens.js +386 -0
  211. package/src/packages/design-tokens/package.json +25 -0
  212. package/src/packages/design-tokens/tokens.json +228 -0
  213. package/src/packages/devtools-middleware.ts +22 -0
  214. package/src/packages/eslint-config/index.js +63 -0
  215. package/src/packages/eslint-config/node_modules/.bin/eslint +21 -0
  216. package/src/packages/eslint-config/package.json +18 -0
  217. package/src/packages/json-file-store.ts +232 -0
  218. package/src/packages/mcp-utils/.turbo/turbo-build.log +5 -0
  219. package/src/packages/mcp-utils/dist/index.d.ts +33 -0
  220. package/src/packages/mcp-utils/dist/index.d.ts.map +1 -0
  221. package/src/packages/mcp-utils/dist/index.js +126 -0
  222. package/src/packages/mcp-utils/dist/index.js.map +1 -0
  223. package/src/packages/mcp-utils/node_modules/.bin/tsc +21 -0
  224. package/src/packages/mcp-utils/node_modules/.bin/tsserver +21 -0
  225. package/src/packages/mcp-utils/package.json +32 -0
  226. package/src/packages/mcp-utils/src/index.ts +171 -0
  227. package/src/packages/mcp-utils/tsconfig.json +9 -0
  228. package/src/packages/paths.ts +18 -0
  229. package/src/packages/project-context/index.js +55 -0
  230. package/src/packages/project-context/package.json +13 -0
  231. package/src/packages/project-store.ts +127 -0
  232. package/src/packages/server-sniffer.ts +132 -0
  233. package/src/packages/shared-assets/favicon.svg +7 -0
  234. package/src/packages/shared-assets/keymap-registry.js +512 -0
  235. package/src/packages/shared-assets/logo.svg +6 -0
  236. package/src/packages/shared-assets/package.json +11 -0
  237. package/src/packages/shared-assets/ui-utils.js +48 -0
  238. package/src/packages/shared-assets/voice-widget.d.ts +37 -0
  239. package/src/packages/shared-assets/voice-widget.js +695 -0
  240. package/src/packages/shared-types/.turbo/turbo-build.log +5 -0
  241. package/src/packages/shared-types/dist/index.d.ts +39 -0
  242. package/src/packages/shared-types/dist/index.d.ts.map +1 -0
  243. package/src/packages/shared-types/node_modules/.bin/tsc +21 -0
  244. package/src/packages/shared-types/node_modules/.bin/tsserver +21 -0
  245. package/src/packages/shared-types/package.json +25 -0
  246. package/src/packages/shared-types/src/index.ts +41 -0
  247. package/src/packages/shared-types/tsconfig.json +11 -0
  248. package/src/packages/tsconfig/base.json +15 -0
  249. package/src/packages/tsconfig/next.json +14 -0
  250. package/src/packages/tsconfig/node.json +11 -0
  251. package/src/packages/tsconfig/package.json +10 -0
  252. package/turbo.json +25 -0
@@ -0,0 +1,247 @@
1
+ @keyframes vc-slide-up {
2
+ from { opacity: 0; transform: translateY(8px); }
3
+ to { opacity: 1; transform: translateY(0); }
4
+ }
5
+
6
+ .page-vocabulary {
7
+ font-family: var(--df-font-mono);
8
+ background: var(--df-color-bg-base);
9
+ color: var(--df-color-text-primary);
10
+ -webkit-font-smoothing: antialiased;
11
+ display: flex;
12
+ flex-direction: column;
13
+ height: 100%;
14
+ overflow: hidden;
15
+ }
16
+
17
+ /* ── Header ─────────────────────────────────────────────────────── */
18
+ .page-vocabulary header {
19
+ flex-shrink: 0;
20
+ }
21
+
22
+ .page-vocabulary .toolbar-actions {
23
+ display: flex;
24
+ align-items: center;
25
+ gap: var(--df-space-2);
26
+ }
27
+
28
+ .page-vocabulary .vc-filter-select {
29
+ background: var(--df-color-bg-raised);
30
+ border: 1px solid var(--df-color-border-default);
31
+ color: var(--df-color-text-primary);
32
+ font-family: var(--df-font-mono);
33
+ font-size: var(--df-font-size-xs);
34
+ padding: var(--df-space-1) var(--df-space-3);
35
+ padding-right: var(--df-space-5);
36
+ cursor: pointer;
37
+ appearance: none;
38
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23636e7b'/%3E%3C/svg%3E");
39
+ background-repeat: no-repeat;
40
+ background-position: right 8px center;
41
+ transition: border-color var(--df-duration-fast);
42
+ }
43
+
44
+ .page-vocabulary .vc-filter-select:hover,
45
+ .page-vocabulary .vc-filter-select:focus-visible {
46
+ border-color: var(--df-color-accent-default);
47
+ }
48
+
49
+ /* ── Main layout ────────────────────────────────────────────────── */
50
+ .page-vocabulary main {
51
+ flex: 1;
52
+ min-height: 0;
53
+ overflow-y: auto;
54
+ padding: var(--df-space-6) var(--df-space-4);
55
+ scrollbar-width: thin;
56
+ scrollbar-color: var(--df-color-border-default) transparent;
57
+ }
58
+
59
+ .page-vocabulary .vc-container {
60
+ max-width: var(--df-max-width-md);
61
+ margin: 0 auto;
62
+ }
63
+
64
+ /* ── Search bar ─────────────────────────────────────────────────── */
65
+ .page-vocabulary .vc-search-bar {
66
+ margin-bottom: var(--df-space-4);
67
+ }
68
+
69
+ .page-vocabulary .vc-search-input {
70
+ width: 100%;
71
+ background: var(--df-color-bg-raised);
72
+ border: 1px solid var(--df-color-border-default);
73
+ color: var(--df-color-text-primary);
74
+ font-family: var(--df-font-mono);
75
+ font-size: var(--df-font-size-sm);
76
+ padding: var(--df-space-2) var(--df-space-3);
77
+ outline: none;
78
+ transition: border-color var(--df-duration-fast);
79
+ }
80
+
81
+ .page-vocabulary .vc-search-input::placeholder {
82
+ color: var(--df-color-text-muted);
83
+ }
84
+
85
+ .page-vocabulary .vc-search-input:focus-visible {
86
+ border-color: var(--df-color-accent-default);
87
+ box-shadow: inset 0 0 0 1px var(--df-color-accent-dim);
88
+ }
89
+
90
+ /* ── Groups ─────────────────────────────────────────────────────── */
91
+ .page-vocabulary .vc-group {
92
+ margin-bottom: var(--df-space-4);
93
+ background: var(--df-color-bg-surface);
94
+ border: 1px solid var(--df-color-border-subtle);
95
+ clip-path: var(--df-clip-md);
96
+ overflow: hidden;
97
+ animation: vc-slide-up var(--df-duration-base) var(--df-easing-default) both;
98
+ }
99
+
100
+ .page-vocabulary .vc-group-title {
101
+ display: flex;
102
+ align-items: center;
103
+ gap: var(--df-space-2);
104
+ padding: var(--df-space-2) var(--df-space-4);
105
+ font-size: var(--df-font-size-xs);
106
+ font-weight: normal;
107
+ color: var(--df-color-text-muted);
108
+ letter-spacing: var(--df-letter-spacing-wider);
109
+ text-transform: uppercase;
110
+ background: var(--df-color-bg-raised);
111
+ border-bottom: 1px solid var(--df-color-border-subtle);
112
+ user-select: none;
113
+ }
114
+
115
+ /* ── Entry rows ─────────────────────────────────────────────────── */
116
+ .page-vocabulary .vc-entry-row {
117
+ display: flex;
118
+ align-items: flex-start;
119
+ justify-content: space-between;
120
+ gap: var(--df-space-3);
121
+ padding: var(--df-space-3) var(--df-space-4);
122
+ border-bottom: 1px solid var(--df-color-border-subtle);
123
+ transition: background var(--df-duration-fast) ease;
124
+ }
125
+
126
+ .page-vocabulary .vc-entry-row:last-child {
127
+ border-bottom: none;
128
+ }
129
+
130
+ .page-vocabulary .vc-entry-row:hover {
131
+ background: color-mix(in srgb, var(--df-color-accent-default) 4%, transparent);
132
+ }
133
+
134
+ .page-vocabulary .vc-entry-left {
135
+ flex: 1;
136
+ min-width: 0;
137
+ }
138
+
139
+ .page-vocabulary .vc-entry-term {
140
+ font-size: var(--df-font-size-sm);
141
+ font-weight: 500;
142
+ color: var(--df-color-accent-default);
143
+ letter-spacing: var(--df-letter-spacing-wide);
144
+ }
145
+
146
+ .page-vocabulary .vc-entry-def {
147
+ font-size: var(--df-font-size-sm);
148
+ color: var(--df-color-text-primary);
149
+ margin-top: 2px;
150
+ line-height: var(--df-line-height-normal);
151
+ }
152
+
153
+ .page-vocabulary .vc-entry-aliases {
154
+ font-size: var(--df-font-size-xs);
155
+ color: var(--df-color-text-secondary);
156
+ margin-top: var(--df-space-1);
157
+ font-style: italic;
158
+ }
159
+
160
+ .page-vocabulary .vc-entry-tags {
161
+ display: flex;
162
+ flex-wrap: wrap;
163
+ gap: 4px;
164
+ margin-top: var(--df-space-2);
165
+ }
166
+
167
+ .page-vocabulary .vc-entry-actions {
168
+ display: flex;
169
+ align-items: center;
170
+ gap: var(--df-space-2);
171
+ flex-shrink: 0;
172
+ opacity: 0;
173
+ transition: opacity var(--df-duration-fast);
174
+ }
175
+
176
+ .page-vocabulary .vc-entry-row:hover .vc-entry-actions {
177
+ opacity: 1;
178
+ }
179
+
180
+ .page-vocabulary .vc-delete-msg {
181
+ font-size: var(--df-font-size-xs);
182
+ color: var(--df-color-state-error);
183
+ white-space: nowrap;
184
+ }
185
+
186
+ /* ── Modal error ────────────────────────────────────────────────── */
187
+ .page-vocabulary .vc-modal-error {
188
+ font-size: var(--df-font-size-xs);
189
+ color: var(--df-color-state-error);
190
+ min-height: 1.2em;
191
+ margin-top: var(--df-space-1);
192
+ }
193
+
194
+ /* ── Stagger animation ──────────────────────────────────────────── */
195
+ .page-vocabulary .vc-group:nth-child(1) { animation-delay: 0ms; }
196
+ .page-vocabulary .vc-group:nth-child(2) { animation-delay: 60ms; }
197
+ .page-vocabulary .vc-group:nth-child(3) { animation-delay: 120ms; }
198
+ .page-vocabulary .vc-group:nth-child(4) { animation-delay: 180ms; }
199
+ .page-vocabulary .vc-group:nth-child(5) { animation-delay: 240ms; }
200
+
201
+ /* ── Scrollbar ──────────────────────────────────────────────────── */
202
+ .page-vocabulary ::-webkit-scrollbar { width: 8px; }
203
+ .page-vocabulary ::-webkit-scrollbar-track { background: transparent; }
204
+ .page-vocabulary ::-webkit-scrollbar-thumb { background: var(--df-color-border-default); border-radius: var(--df-radius-full); }
205
+ .page-vocabulary ::-webkit-scrollbar-thumb:hover { background: var(--df-color-border-strong); }
206
+
207
+ /* ── Reduced motion ─────────────────────────────────────────────── */
208
+ @media (prefers-reduced-motion: reduce) {
209
+ .page-vocabulary .vc-group { animation: none; }
210
+ }
211
+
212
+ /* ── Mobile ─────────────────────────────────────────────────────── */
213
+ @media (max-width: 639px) {
214
+ .page-vocabulary main {
215
+ padding: var(--df-space-3);
216
+ }
217
+
218
+ .page-vocabulary .toolbar-actions {
219
+ gap: var(--df-space-1);
220
+ }
221
+
222
+ .page-vocabulary .vc-entry-row {
223
+ flex-direction: column;
224
+ gap: var(--df-space-2);
225
+ }
226
+
227
+ .page-vocabulary .vc-entry-actions {
228
+ opacity: 1;
229
+ width: 100%;
230
+ justify-content: flex-end;
231
+ }
232
+
233
+ .page-vocabulary .vc-filter-select {
234
+ display: none;
235
+ }
236
+ }
237
+
238
+ /* ── Large screens ──────────────────────────────────────────────── */
239
+ @media (min-width: 1440px) {
240
+ .page-vocabulary .vc-container {
241
+ max-width: var(--df-max-width-lg);
242
+ }
243
+
244
+ .page-vocabulary .vc-entry-row {
245
+ padding: var(--df-space-3) var(--df-space-5);
246
+ }
247
+ }
@@ -0,0 +1,444 @@
1
+ // ── Vocabulary App — Page Module ─────────────────────────────────────
2
+ // ES module that exports mount(container, ctx), unmount(container),
3
+ // and onProjectChange(project).
4
+
5
+ import { escapeHtml } from '/shared-assets/ui-utils.js';
6
+
7
+ let _container = null;
8
+ let _entries = [];
9
+ let _categories = [];
10
+ let _activeFilter = { category: null, tag: null };
11
+ let _deleteTarget = null;
12
+
13
+ // ── API helpers ─────────────────────────────────────────────────────
14
+
15
+ async function api(path, opts) {
16
+ const res = await fetch('/api/vocabulary' + path, {
17
+ headers: { 'Content-Type': 'application/json' },
18
+ ...opts,
19
+ });
20
+ return res;
21
+ }
22
+
23
+ // ── HTML ────────────────────────────────────────────────────────────
24
+
25
+ const BODY_HTML = `
26
+ <header>
27
+ <div class="brand">Vocabulary</div>
28
+ <div class="header-meta">
29
+ <span id="vc-count"></span>
30
+ </div>
31
+ <div class="toolbar-actions">
32
+ <select id="vc-filter-category" class="vc-filter-select" title="Filter by category">
33
+ <option value="">All categories</option>
34
+ </select>
35
+ <button class="btn btn-primary" id="vc-btn-add">+ Add Term</button>
36
+ </div>
37
+ </header>
38
+
39
+ <main>
40
+ <div class="vc-container" id="vc-container">
41
+ <div class="vc-search-bar">
42
+ <input type="text" id="vc-search" class="vc-search-input" placeholder="Search terms..." autocomplete="off" />
43
+ </div>
44
+ <div class="vc-entries" id="vc-entries"></div>
45
+ </div>
46
+ </main>
47
+
48
+ <!-- Delete Confirmation Dialog -->
49
+ <div class="modal-overlay hidden" id="vc-delete-overlay" role="dialog" aria-modal="true">
50
+ <div class="modal">
51
+ <div class="modal-header">
52
+ <h2>Delete Term</h2>
53
+ <p class="modal-desc" id="vc-delete-msg"></p>
54
+ </div>
55
+ <div class="modal-actions">
56
+ <button class="btn btn-secondary" id="vc-delete-cancel">Cancel</button>
57
+ <button class="btn btn-danger" id="vc-delete-confirm">Delete</button>
58
+ </div>
59
+ </div>
60
+ </div>
61
+ `;
62
+
63
+ // ── Data loading ────────────────────────────────────────────────────
64
+
65
+ async function loadEntries() {
66
+ if (!_container) return;
67
+ try {
68
+ const params = new URLSearchParams();
69
+ if (_activeFilter.category) params.set('category', _activeFilter.category);
70
+ if (_activeFilter.tag) params.set('tag', _activeFilter.tag);
71
+ const qs = params.toString();
72
+ const res = await api('/entries' + (qs ? '?' + qs : ''));
73
+ _entries = await res.json();
74
+ _categories = [...new Set(_entries.map(e => e.category).filter(Boolean))].sort();
75
+ renderEntries();
76
+ updateCategoryFilter();
77
+ updateCount();
78
+ } catch (err) {
79
+ console.error('[vocabulary] Failed to load entries:', err);
80
+ }
81
+ }
82
+
83
+ // ── Rendering ───────────────────────────────────────────────────────
84
+
85
+ function updateCount() {
86
+ const el = _container?.querySelector('#vc-count');
87
+ if (el) el.textContent = `${_entries.length} term${_entries.length !== 1 ? 's' : ''}`;
88
+ }
89
+
90
+ function updateCategoryFilter() {
91
+ const select = _container?.querySelector('#vc-filter-category');
92
+ if (!select) return;
93
+ const current = select.value;
94
+ select.innerHTML = '<option value="">All categories</option>';
95
+ for (const cat of _categories) {
96
+ const opt = document.createElement('option');
97
+ opt.value = cat;
98
+ opt.textContent = cat;
99
+ if (cat === current) opt.selected = true;
100
+ select.appendChild(opt);
101
+ }
102
+ }
103
+
104
+ function getFilteredEntries() {
105
+ const search = _container?.querySelector('#vc-search')?.value?.toLowerCase() ?? '';
106
+ if (!search) return _entries;
107
+ return _entries.filter(e =>
108
+ e.term.toLowerCase().includes(search) ||
109
+ e.definition.toLowerCase().includes(search) ||
110
+ (e.aliases || []).some(a => a.toLowerCase().includes(search)) ||
111
+ (e.category || '').toLowerCase().includes(search)
112
+ );
113
+ }
114
+
115
+ function renderEntries() {
116
+ const listEl = _container?.querySelector('#vc-entries');
117
+ if (!listEl) return;
118
+
119
+ const filtered = getFilteredEntries();
120
+
121
+ if (filtered.length === 0) {
122
+ listEl.innerHTML = `
123
+ <div class="empty-state">
124
+ <div style="font-size:48px;opacity:0.15">\u2261</div>
125
+ <div>${_entries.length === 0 ? 'No vocabulary entries yet' : 'No matching entries'}</div>
126
+ ${_entries.length === 0 ? '<div style="font-size:var(--df-font-size-xs);color:var(--df-color-text-secondary);text-transform:none;letter-spacing:normal">Add domain-specific terms so the LLM understands your shorthand</div>' : ''}
127
+ </div>
128
+ `;
129
+ return;
130
+ }
131
+
132
+ // Group by category
133
+ const groups = new Map();
134
+ for (const entry of filtered) {
135
+ const cat = entry.category || 'Uncategorized';
136
+ if (!groups.has(cat)) groups.set(cat, []);
137
+ groups.get(cat).push(entry);
138
+ }
139
+
140
+ listEl.innerHTML = '';
141
+
142
+ for (const [category, entries] of groups) {
143
+ const section = document.createElement('section');
144
+ section.className = 'vc-group';
145
+
146
+ const title = document.createElement('h2');
147
+ title.className = 'vc-group-title';
148
+ title.textContent = category;
149
+ const countBadge = document.createElement('span');
150
+ countBadge.className = 'badge';
151
+ countBadge.textContent = entries.length;
152
+ title.appendChild(countBadge);
153
+ section.appendChild(title);
154
+
155
+ for (const entry of entries) {
156
+ section.appendChild(buildEntryRow(entry));
157
+ }
158
+
159
+ listEl.appendChild(section);
160
+ }
161
+ }
162
+
163
+ function buildEntryRow(entry) {
164
+ const row = document.createElement('div');
165
+ row.className = 'vc-entry-row';
166
+ row.dataset.id = entry.id;
167
+
168
+ const left = document.createElement('div');
169
+ left.className = 'vc-entry-left';
170
+
171
+ const termEl = document.createElement('div');
172
+ termEl.className = 'vc-entry-term';
173
+ termEl.textContent = entry.term;
174
+
175
+ const defEl = document.createElement('div');
176
+ defEl.className = 'vc-entry-def';
177
+ defEl.textContent = entry.definition;
178
+
179
+ left.appendChild(termEl);
180
+ left.appendChild(defEl);
181
+
182
+ if (entry.aliases?.length) {
183
+ const aliasEl = document.createElement('div');
184
+ aliasEl.className = 'vc-entry-aliases';
185
+ aliasEl.textContent = 'aka: ' + entry.aliases.join(', ');
186
+ left.appendChild(aliasEl);
187
+ }
188
+
189
+ if (entry.tags?.length) {
190
+ const tagsEl = document.createElement('div');
191
+ tagsEl.className = 'vc-entry-tags';
192
+ for (const tag of entry.tags) {
193
+ const badge = document.createElement('span');
194
+ badge.className = 'badge';
195
+ badge.textContent = tag;
196
+ tagsEl.appendChild(badge);
197
+ }
198
+ left.appendChild(tagsEl);
199
+ }
200
+
201
+ const actions = document.createElement('div');
202
+ actions.className = 'vc-entry-actions';
203
+
204
+ const editBtn = document.createElement('button');
205
+ editBtn.className = 'btn btn-sm btn-secondary';
206
+ editBtn.textContent = 'Edit';
207
+ editBtn.addEventListener('click', (e) => {
208
+ e.stopPropagation();
209
+ openModal('edit', entry);
210
+ });
211
+
212
+ const deleteBtn = document.createElement('button');
213
+ deleteBtn.className = 'btn btn-sm btn-danger';
214
+ deleteBtn.textContent = 'Delete';
215
+ deleteBtn.addEventListener('click', (e) => {
216
+ e.stopPropagation();
217
+ openDeleteDialog(entry);
218
+ });
219
+
220
+ actions.appendChild(editBtn);
221
+ actions.appendChild(deleteBtn);
222
+
223
+ row.appendChild(left);
224
+ row.appendChild(actions);
225
+
226
+ return row;
227
+ }
228
+
229
+ // ── Delete confirmation dialog ──────────────────────────────────────
230
+
231
+ function openDeleteDialog(entry) {
232
+ _deleteTarget = entry;
233
+ const overlay = _container?.querySelector('#vc-delete-overlay');
234
+ if (!overlay) return;
235
+ const msg = overlay.querySelector('#vc-delete-msg');
236
+ if (msg) msg.innerHTML = `Are you sure you want to delete <strong>${escapeHtml(entry.term)}</strong>? This action cannot be undone.`;
237
+ overlay.classList.remove('hidden');
238
+ }
239
+
240
+ function closeDeleteDialog() {
241
+ _deleteTarget = null;
242
+ _container?.querySelector('#vc-delete-overlay')?.classList.add('hidden');
243
+ }
244
+
245
+ function bindDeleteDialog() {
246
+ const overlay = _container?.querySelector('#vc-delete-overlay');
247
+ if (!overlay) return;
248
+
249
+ overlay.querySelector('#vc-delete-cancel').addEventListener('click', closeDeleteDialog);
250
+
251
+ overlay.querySelector('#vc-delete-confirm').addEventListener('click', async () => {
252
+ if (!_deleteTarget) return;
253
+ await api('/entries/' + _deleteTarget.id, { method: 'DELETE' });
254
+ closeDeleteDialog();
255
+ loadEntries();
256
+ });
257
+ }
258
+
259
+ // ── Modal ───────────────────────────────────────────────────────────
260
+
261
+ function openModal(mode, entry) {
262
+ closeModal();
263
+
264
+ const overlay = document.createElement('div');
265
+ overlay.className = 'modal-overlay';
266
+ overlay.id = 'vc-modal-overlay';
267
+
268
+ const modal = document.createElement('div');
269
+ modal.className = 'modal';
270
+
271
+ const isEdit = mode === 'edit';
272
+ const title = isEdit ? 'Edit Term' : 'Add Term';
273
+ const submitLabel = isEdit ? 'Save' : 'Add';
274
+
275
+ modal.innerHTML = `
276
+ <div class="modal-header">
277
+ <h2>${title}</h2>
278
+ <p class="modal-desc">${isEdit ? 'Update term details.' : 'Add a domain-specific term for LLM context.'}</p>
279
+ </div>
280
+ <div class="modal-body">
281
+ <div class="form-group">
282
+ <label for="vc-m-term">Term</label>
283
+ <input type="text" id="vc-m-term" value="${isEdit ? escapeHtml(entry.term) : ''}" placeholder="e.g. PR, LGTM, K8s" autocomplete="off" />
284
+ </div>
285
+
286
+ <div class="form-group">
287
+ <label for="vc-m-definition">Definition</label>
288
+ <textarea id="vc-m-definition" rows="3" placeholder="What this term means in your domain...">${isEdit ? escapeHtml(entry.definition) : ''}</textarea>
289
+ </div>
290
+
291
+ <div class="form-row">
292
+ <div class="form-group">
293
+ <label for="vc-m-category">Category</label>
294
+ <input type="text" id="vc-m-category" value="${isEdit && entry.category ? escapeHtml(entry.category) : ''}" placeholder="e.g. API, Database" list="vc-m-category-list" autocomplete="off" />
295
+ <datalist id="vc-m-category-list">
296
+ ${_categories.map(c => `<option value="${escapeHtml(c)}">`).join('')}
297
+ </datalist>
298
+ </div>
299
+
300
+ <div class="form-group">
301
+ <label for="vc-m-aliases">Aliases <span style="text-transform:none;letter-spacing:normal;color:var(--df-color-text-muted)">(comma-separated)</span></label>
302
+ <input type="text" id="vc-m-aliases" value="${isEdit && entry.aliases ? escapeHtml(entry.aliases.join(', ')) : ''}" placeholder="e.g. pull request, merge request" autocomplete="off" />
303
+ </div>
304
+ </div>
305
+
306
+ <div class="form-group">
307
+ <label for="vc-m-tags">Tags <span style="text-transform:none;letter-spacing:normal;color:var(--df-color-text-muted)">(comma-separated)</span></label>
308
+ <input type="text" id="vc-m-tags" value="${isEdit && entry.tags ? escapeHtml(entry.tags.join(', ')) : ''}" placeholder="e.g. git, review, workflow" autocomplete="off" />
309
+ </div>
310
+
311
+ <div class="vc-modal-error" id="vc-m-error"></div>
312
+
313
+ <div class="modal-actions">
314
+ <button class="btn btn-secondary" id="vc-m-cancel">Cancel</button>
315
+ <button class="btn btn-primary" id="vc-m-submit">${submitLabel}</button>
316
+ </div>
317
+ </div>
318
+ `;
319
+
320
+ overlay.appendChild(modal);
321
+ document.body.appendChild(overlay);
322
+
323
+ const termInput = document.getElementById('vc-m-term');
324
+ requestAnimationFrame(() => termInput.focus());
325
+
326
+ async function handleSubmit() {
327
+ const term = document.getElementById('vc-m-term').value.trim();
328
+ const definition = document.getElementById('vc-m-definition').value.trim();
329
+ const category = document.getElementById('vc-m-category').value.trim() || undefined;
330
+ const aliasesRaw = document.getElementById('vc-m-aliases').value.trim();
331
+ const tagsRaw = document.getElementById('vc-m-tags').value.trim();
332
+
333
+ const aliases = aliasesRaw ? aliasesRaw.split(',').map(s => s.trim()).filter(Boolean) : undefined;
334
+ const tags = tagsRaw ? tagsRaw.split(',').map(s => s.trim()).filter(Boolean) : [];
335
+
336
+ const errorEl = document.getElementById('vc-m-error');
337
+
338
+ if (!term) { errorEl.textContent = 'Term is required'; termInput.focus(); return; }
339
+ if (!definition) { errorEl.textContent = 'Definition is required'; document.getElementById('vc-m-definition').focus(); return; }
340
+
341
+ try {
342
+ if (isEdit) {
343
+ const res = await api('/entries/' + entry.id, {
344
+ method: 'PUT',
345
+ body: JSON.stringify({ term, definition, category, aliases, tags }),
346
+ });
347
+ if (!res.ok) {
348
+ const data = await res.json();
349
+ errorEl.textContent = data.error || 'Failed to update';
350
+ return;
351
+ }
352
+ } else {
353
+ const res = await api('/entries', {
354
+ method: 'POST',
355
+ body: JSON.stringify({ term, definition, category, aliases, tags }),
356
+ });
357
+ if (!res.ok) {
358
+ const data = await res.json();
359
+ errorEl.textContent = data.error || 'Failed to add';
360
+ return;
361
+ }
362
+ }
363
+ closeModal();
364
+ loadEntries();
365
+ } catch (err) {
366
+ errorEl.textContent = err.message;
367
+ }
368
+ }
369
+
370
+ document.getElementById('vc-m-submit').addEventListener('click', handleSubmit);
371
+ document.getElementById('vc-m-cancel').addEventListener('click', closeModal);
372
+
373
+ overlay.addEventListener('click', (e) => {
374
+ if (e.target === overlay) closeModal();
375
+ });
376
+
377
+ function onKeyDown(e) {
378
+ if (e.key === 'Escape') closeModal();
379
+ else if (e.key === 'Enter' && e.ctrlKey) handleSubmit();
380
+ }
381
+ document.addEventListener('keydown', onKeyDown);
382
+ overlay._keydownHandler = onKeyDown;
383
+ }
384
+
385
+ function closeModal() {
386
+ const existing = document.getElementById('vc-modal-overlay');
387
+ if (existing) {
388
+ if (existing._keydownHandler) {
389
+ document.removeEventListener('keydown', existing._keydownHandler);
390
+ }
391
+ existing.remove();
392
+ }
393
+ }
394
+
395
+ // ── Event binding ───────────────────────────────────────────────────
396
+
397
+ function bindEvents() {
398
+ if (!_container) return;
399
+
400
+ _container.querySelector('#vc-btn-add').addEventListener('click', () => openModal('add'));
401
+
402
+ _container.querySelector('#vc-filter-category').addEventListener('change', (e) => {
403
+ _activeFilter.category = e.target.value || null;
404
+ loadEntries();
405
+ });
406
+
407
+ let searchTimer;
408
+ _container.querySelector('#vc-search').addEventListener('input', () => {
409
+ clearTimeout(searchTimer);
410
+ searchTimer = setTimeout(renderEntries, 150);
411
+ });
412
+
413
+ bindDeleteDialog();
414
+ }
415
+
416
+ // ── Exports ─────────────────────────────────────────────────────────
417
+
418
+ export function mount(container, ctx) {
419
+ _container = container;
420
+ _entries = [];
421
+ _categories = [];
422
+ _activeFilter = { category: null, tag: null };
423
+
424
+ container.classList.add('page-vocabulary');
425
+ container.innerHTML = BODY_HTML;
426
+
427
+ bindEvents();
428
+ loadEntries();
429
+ }
430
+
431
+ export function unmount(container) {
432
+ closeModal();
433
+ container.classList.remove('page-vocabulary');
434
+ container.innerHTML = '';
435
+ _container = null;
436
+ _entries = [];
437
+ _categories = [];
438
+ _deleteTarget = null;
439
+ }
440
+
441
+ export function onProjectChange(project) {
442
+ _activeFilter = { category: null, tag: null };
443
+ loadEntries();
444
+ }