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,541 @@
1
+ // ── Prompts App — Page Module ────────────────────────────────────────
2
+ // ES module: mount(container, ctx), unmount(container), onProjectChange(project)
3
+
4
+ import { escapeHtml } from '/shared-assets/ui-utils.js';
5
+
6
+ let _container = null;
7
+ let _entries = [];
8
+ let _categories = [];
9
+ let _activeFilter = { category: null };
10
+ let _deleteTarget = null;
11
+ let _searchTimer = null;
12
+
13
+ // ── API helpers ──────────────────────────────────────────────────────
14
+
15
+ async function api(path, opts) {
16
+ const res = await fetch('/api/prompts' + 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">Prompts</div>
28
+ <div class="header-meta">
29
+ <span id="pr-count"></span>
30
+ </div>
31
+ <div class="toolbar-actions">
32
+ <select id="pr-filter-category" class="pr-filter-select" title="Filter by category">
33
+ <option value="">All categories</option>
34
+ </select>
35
+ <button class="btn btn-primary" id="pr-btn-add">+ New Prompt</button>
36
+ </div>
37
+ </header>
38
+
39
+ <main>
40
+ <div class="pr-container" id="pr-container">
41
+ <div class="pr-search-bar">
42
+ <input type="text" id="pr-search" class="pr-search-input" placeholder="Search prompts..." autocomplete="off" />
43
+ </div>
44
+ <div class="pr-entries" id="pr-entries"></div>
45
+ </div>
46
+ </main>
47
+
48
+ <!-- Delete confirmation -->
49
+ <div class="modal-overlay hidden" id="pr-delete-overlay" role="dialog" aria-modal="true">
50
+ <div class="modal">
51
+ <div class="modal-header">
52
+ <h2>Delete Prompt</h2>
53
+ <p class="modal-desc" id="pr-delete-msg"></p>
54
+ </div>
55
+ <div class="modal-actions">
56
+ <button class="btn btn-secondary" id="pr-delete-cancel">Cancel</button>
57
+ <button class="btn btn-danger" id="pr-delete-confirm">Delete</button>
58
+ </div>
59
+ </div>
60
+ </div>
61
+ `;
62
+
63
+ // ── Stars ────────────────────────────────────────────────────────────
64
+
65
+ function renderStars(rating) {
66
+ if (!rating) return '';
67
+ return Array.from({ length: 5 }, (_, i) =>
68
+ `<span class="pr-star${i < rating ? ' filled' : ''}">\u2605</span>`
69
+ ).join('');
70
+ }
71
+
72
+ // ── Data loading ─────────────────────────────────────────────────────
73
+
74
+ async function loadEntries() {
75
+ if (!_container) return;
76
+ try {
77
+ const params = new URLSearchParams();
78
+ if (_activeFilter.category) params.set('category', _activeFilter.category);
79
+ const qs = params.toString();
80
+ const res = await api('/entries' + (qs ? '?' + qs : ''));
81
+ if (!res.ok) throw new Error(`Failed to load prompts (${res.status})`);
82
+ _entries = await res.json();
83
+ _categories = [...new Set(_entries.map(e => e.category).filter(Boolean))].sort();
84
+ renderEntries();
85
+ updateCategoryFilter();
86
+ updateCount();
87
+ } catch (err) {
88
+ console.error('[prompts] Failed to load entries:', err);
89
+ }
90
+ }
91
+
92
+ // ── Rendering ────────────────────────────────────────────────────────
93
+
94
+ function updateCount() {
95
+ const el = _container?.querySelector('#pr-count');
96
+ if (el) el.textContent = `${_entries.length} prompt${_entries.length !== 1 ? 's' : ''}`;
97
+ }
98
+
99
+ function updateCategoryFilter() {
100
+ const select = _container?.querySelector('#pr-filter-category');
101
+ if (!select) return;
102
+ const current = select.value;
103
+ select.innerHTML = '<option value="">All categories</option>';
104
+ for (const cat of _categories) {
105
+ const opt = document.createElement('option');
106
+ opt.value = cat;
107
+ opt.textContent = cat;
108
+ if (cat === current) opt.selected = true;
109
+ select.appendChild(opt);
110
+ }
111
+ }
112
+
113
+ function getFilteredEntries() {
114
+ const search = _container?.querySelector('#pr-search')?.value?.toLowerCase() ?? '';
115
+ if (!search) return _entries;
116
+ return _entries.filter(e =>
117
+ e.title.toLowerCase().includes(search) ||
118
+ (e.description || '').toLowerCase().includes(search) ||
119
+ (e.category || '').toLowerCase().includes(search) ||
120
+ (e.tags || []).some(t => t.toLowerCase().includes(search))
121
+ );
122
+ }
123
+
124
+ function renderEntries() {
125
+ const listEl = _container?.querySelector('#pr-entries');
126
+ if (!listEl) return;
127
+
128
+ const filtered = getFilteredEntries();
129
+
130
+ if (filtered.length === 0) {
131
+ listEl.innerHTML = `
132
+ <div class="empty-state">
133
+ <div style="font-size:48px;opacity:0.15">\u270E</div>
134
+ <div>${_entries.length === 0 ? 'No prompts yet' : 'No matching prompts'}</div>
135
+ ${_entries.length === 0 ? '<div style="font-size:var(--df-font-size-xs);color:var(--df-color-text-secondary);text-transform:none;letter-spacing:normal">Save your best prompt templates for reuse and evaluation</div>' : ''}
136
+ </div>
137
+ `;
138
+ return;
139
+ }
140
+
141
+ // Group by category
142
+ const groups = new Map();
143
+ for (const entry of filtered) {
144
+ const cat = entry.category || 'Uncategorized';
145
+ if (!groups.has(cat)) groups.set(cat, []);
146
+ groups.get(cat).push(entry);
147
+ }
148
+
149
+ listEl.innerHTML = '';
150
+
151
+ for (const [category, entries] of groups) {
152
+ const section = document.createElement('section');
153
+ section.className = 'pr-group';
154
+
155
+ const title = document.createElement('h2');
156
+ title.className = 'pr-group-title';
157
+ title.textContent = category;
158
+ const countBadge = document.createElement('span');
159
+ countBadge.className = 'badge';
160
+ countBadge.textContent = entries.length;
161
+ title.appendChild(countBadge);
162
+ section.appendChild(title);
163
+
164
+ for (const entry of entries) {
165
+ section.appendChild(buildEntryCard(entry));
166
+ }
167
+
168
+ listEl.appendChild(section);
169
+ }
170
+ }
171
+
172
+ function buildEntryCard(entry) {
173
+ const card = document.createElement('div');
174
+ card.className = 'pr-entry-card';
175
+ card.dataset.id = entry.id;
176
+
177
+ const meta = document.createElement('div');
178
+ meta.className = 'pr-entry-meta';
179
+
180
+ const titleEl = document.createElement('div');
181
+ titleEl.className = 'pr-entry-title';
182
+ titleEl.textContent = entry.title;
183
+
184
+ meta.appendChild(titleEl);
185
+
186
+ if (entry.rating) {
187
+ const starsEl = document.createElement('div');
188
+ starsEl.className = 'pr-entry-stars';
189
+ starsEl.innerHTML = renderStars(entry.rating);
190
+ meta.appendChild(starsEl);
191
+ }
192
+
193
+ card.appendChild(meta);
194
+
195
+ if (entry.description) {
196
+ const descEl = document.createElement('div');
197
+ descEl.className = 'pr-entry-desc';
198
+ descEl.textContent = entry.description;
199
+ card.appendChild(descEl);
200
+ }
201
+
202
+ const footer = document.createElement('div');
203
+ footer.className = 'pr-entry-footer';
204
+
205
+ const tagsEl = document.createElement('div');
206
+ tagsEl.className = 'pr-entry-tags';
207
+ for (const tag of (entry.tags || [])) {
208
+ const badge = document.createElement('span');
209
+ badge.className = 'badge';
210
+ badge.textContent = tag;
211
+ tagsEl.appendChild(badge);
212
+ }
213
+ footer.appendChild(tagsEl);
214
+
215
+ const updatedEl = document.createElement('div');
216
+ updatedEl.className = 'pr-entry-updated';
217
+ updatedEl.textContent = new Date(entry.updatedAt).toLocaleDateString();
218
+ footer.appendChild(updatedEl);
219
+
220
+ card.appendChild(footer);
221
+
222
+ const actions = document.createElement('div');
223
+ actions.className = 'pr-entry-actions';
224
+
225
+ const copyBtn = document.createElement('button');
226
+ copyBtn.className = 'btn btn-sm btn-secondary';
227
+ copyBtn.textContent = 'Copy';
228
+ copyBtn.addEventListener('click', async (e) => {
229
+ e.stopPropagation();
230
+ try {
231
+ const res = await api('/entries/' + entry.id);
232
+ if (!res.ok) { copyBtn.textContent = 'Error'; return; }
233
+ const full = await res.json();
234
+ await navigator.clipboard.writeText(full.content);
235
+ copyBtn.textContent = 'Copied!';
236
+ setTimeout(() => { copyBtn.textContent = 'Copy'; }, 1500);
237
+ } catch {
238
+ copyBtn.textContent = 'Failed';
239
+ setTimeout(() => { copyBtn.textContent = 'Copy'; }, 1500);
240
+ }
241
+ });
242
+
243
+ const editBtn = document.createElement('button');
244
+ editBtn.className = 'btn btn-sm btn-secondary';
245
+ editBtn.textContent = 'Edit';
246
+ editBtn.addEventListener('click', (e) => {
247
+ e.stopPropagation();
248
+ openModal('edit', entry.id);
249
+ });
250
+
251
+ const deleteBtn = document.createElement('button');
252
+ deleteBtn.className = 'btn btn-sm btn-danger';
253
+ deleteBtn.textContent = 'Delete';
254
+ deleteBtn.addEventListener('click', (e) => {
255
+ e.stopPropagation();
256
+ openDeleteDialog(entry);
257
+ });
258
+
259
+ actions.appendChild(copyBtn);
260
+ actions.appendChild(editBtn);
261
+ actions.appendChild(deleteBtn);
262
+ card.appendChild(actions);
263
+
264
+ return card;
265
+ }
266
+
267
+ // ── Delete dialog ────────────────────────────────────────────────────
268
+
269
+ function openDeleteDialog(entry) {
270
+ _deleteTarget = entry;
271
+ const overlay = _container?.querySelector('#pr-delete-overlay');
272
+ if (!overlay) return;
273
+ const msg = overlay.querySelector('#pr-delete-msg');
274
+ if (msg) msg.innerHTML = `Are you sure you want to delete <strong>${escapeHtml(entry.title)}</strong>? This cannot be undone.`;
275
+ overlay.classList.remove('hidden');
276
+ }
277
+
278
+ function closeDeleteDialog() {
279
+ _deleteTarget = null;
280
+ _container?.querySelector('#pr-delete-overlay')?.classList.add('hidden');
281
+ }
282
+
283
+ function bindDeleteDialog() {
284
+ const overlay = _container?.querySelector('#pr-delete-overlay');
285
+ if (!overlay) return;
286
+
287
+ overlay.querySelector('#pr-delete-cancel').addEventListener('click', closeDeleteDialog);
288
+
289
+ overlay.querySelector('#pr-delete-confirm').addEventListener('click', async () => {
290
+ if (!_deleteTarget) return;
291
+ const res = await api('/entries/' + _deleteTarget.id, { method: 'DELETE' });
292
+ if (!res.ok) console.error('[prompts] Failed to delete:', res.status);
293
+ closeDeleteDialog();
294
+ loadEntries();
295
+ });
296
+ }
297
+
298
+ // ── Modal ────────────────────────────────────────────────────────────
299
+
300
+ async function openModal(mode, entryId) {
301
+ closeModal();
302
+
303
+ let entry = null;
304
+ if (mode === 'edit' && entryId) {
305
+ const res = await api('/entries/' + entryId);
306
+ if (res.ok) entry = await res.json();
307
+ }
308
+
309
+ const isEdit = mode === 'edit' && entry;
310
+
311
+ const overlay = document.createElement('div');
312
+ overlay.className = 'modal-overlay';
313
+ overlay.id = 'pr-modal-overlay';
314
+
315
+ const modal = document.createElement('div');
316
+ modal.className = 'modal modal-lg';
317
+
318
+ modal.innerHTML = `
319
+ <div class="modal-header">
320
+ <h2>${isEdit ? 'Edit Prompt' : 'New Prompt'}</h2>
321
+ </div>
322
+ <div class="modal-body">
323
+ <div class="form-row">
324
+ <div class="form-group" style="flex:2">
325
+ <label for="pr-m-title">Title</label>
326
+ <input type="text" id="pr-m-title" value="${isEdit ? escapeHtml(entry.title) : ''}" placeholder="e.g. Code Review Checklist" autocomplete="off" />
327
+ </div>
328
+ <div class="form-group">
329
+ <label for="pr-m-category">Category</label>
330
+ <input type="text" id="pr-m-category" value="${isEdit && entry.category ? escapeHtml(entry.category) : ''}" placeholder="e.g. code-review" list="pr-m-category-list" autocomplete="off" />
331
+ <datalist id="pr-m-category-list">
332
+ ${_categories.map(c => `<option value="${escapeHtml(c)}">`).join('')}
333
+ </datalist>
334
+ </div>
335
+ </div>
336
+
337
+ <div class="form-group">
338
+ <label for="pr-m-description">Description</label>
339
+ <input type="text" id="pr-m-description" value="${isEdit && entry.description ? escapeHtml(entry.description) : ''}" placeholder="What does this prompt do?" autocomplete="off" />
340
+ </div>
341
+
342
+ <div class="form-group">
343
+ <label for="pr-m-content">
344
+ Prompt Content
345
+ <span class="pr-vars-hint" id="pr-vars-hint"></span>
346
+ </label>
347
+ <textarea id="pr-m-content" rows="8" placeholder="Enter your prompt... use {{varName}} for variables">${isEdit ? escapeHtml(entry.content) : ''}</textarea>
348
+ <div class="pr-vars-detected" id="pr-vars-detected"></div>
349
+ </div>
350
+
351
+ <div class="form-row">
352
+ <div class="form-group">
353
+ <label for="pr-m-tags">Tags <span style="text-transform:none;letter-spacing:normal;color:var(--df-color-text-muted)">(comma-separated)</span></label>
354
+ <input type="text" id="pr-m-tags" value="${isEdit && entry.tags ? escapeHtml(entry.tags.join(', ')) : ''}" placeholder="e.g. review, refactor" autocomplete="off" />
355
+ </div>
356
+ <div class="form-group">
357
+ <label for="pr-m-model">Model hint</label>
358
+ <input type="text" id="pr-m-model" value="${isEdit && entry.model ? escapeHtml(entry.model) : ''}" placeholder="e.g. claude-opus-4-6" autocomplete="off" />
359
+ </div>
360
+ <div class="form-group" style="flex:0 0 100px">
361
+ <label for="pr-m-temperature">Temperature</label>
362
+ <input type="number" id="pr-m-temperature" value="${isEdit && entry.temperature != null ? entry.temperature : ''}" placeholder="0–2" min="0" max="2" step="0.1" />
363
+ </div>
364
+ </div>
365
+
366
+ <div class="form-row">
367
+ <div class="form-group">
368
+ <label>Rating</label>
369
+ <div class="pr-rating-input" id="pr-rating-input">
370
+ ${[1,2,3,4,5].map(n => `<button type="button" class="pr-rating-star${isEdit && entry.rating >= n ? ' active' : ''}" data-val="${n}">\u2605</button>`).join('')}
371
+ </div>
372
+ </div>
373
+ <div class="form-group" style="flex:2">
374
+ <label for="pr-m-notes">Notes</label>
375
+ <input type="text" id="pr-m-notes" value="${isEdit && entry.notes ? escapeHtml(entry.notes) : ''}" placeholder="Evaluation notes..." autocomplete="off" />
376
+ </div>
377
+ </div>
378
+
379
+ <div class="pr-modal-error" id="pr-m-error"></div>
380
+
381
+ <div class="modal-actions">
382
+ <button class="btn btn-secondary" id="pr-m-cancel">Cancel</button>
383
+ <button class="btn btn-primary" id="pr-m-submit">${isEdit ? 'Save' : 'Add'}</button>
384
+ </div>
385
+ </div>
386
+ `;
387
+
388
+ overlay.appendChild(modal);
389
+ _container.appendChild(overlay);
390
+
391
+ // Live variable detection
392
+ const contentArea = document.getElementById('pr-m-content');
393
+ const varsDetected = document.getElementById('pr-vars-detected');
394
+ function updateVars() {
395
+ const content = contentArea.value;
396
+ const matches = [...content.matchAll(/\{\{([\w.-]+)\}\}/g)].map(m => m[1]);
397
+ const unique = [...new Set(matches)];
398
+ if (unique.length > 0) {
399
+ varsDetected.innerHTML = 'Variables: ' + unique.map(v => `<span class="badge">${escapeHtml(v)}</span>`).join(' ');
400
+ } else {
401
+ varsDetected.innerHTML = '';
402
+ }
403
+ }
404
+ contentArea.addEventListener('input', updateVars);
405
+ updateVars();
406
+
407
+ // Rating input
408
+ let selectedRating = isEdit ? (entry.rating || 0) : 0;
409
+ const ratingBtns = modal.querySelectorAll('.pr-rating-star');
410
+ function updateRatingUI() {
411
+ ratingBtns.forEach(btn => {
412
+ btn.classList.toggle('active', parseInt(btn.dataset.val) <= selectedRating);
413
+ });
414
+ }
415
+ ratingBtns.forEach(btn => {
416
+ btn.addEventListener('click', () => {
417
+ const val = parseInt(btn.dataset.val);
418
+ selectedRating = selectedRating === val ? 0 : val;
419
+ updateRatingUI();
420
+ });
421
+ btn.addEventListener('mouseenter', () => {
422
+ ratingBtns.forEach(b => b.classList.toggle('hover', parseInt(b.dataset.val) <= parseInt(btn.dataset.val)));
423
+ });
424
+ btn.addEventListener('mouseleave', () => {
425
+ ratingBtns.forEach(b => b.classList.remove('hover'));
426
+ });
427
+ });
428
+
429
+ const titleInput = document.getElementById('pr-m-title');
430
+ requestAnimationFrame(() => titleInput.focus());
431
+
432
+ async function handleSubmit() {
433
+ const title = document.getElementById('pr-m-title').value.trim();
434
+ const content = document.getElementById('pr-m-content').value.trim();
435
+ const description = document.getElementById('pr-m-description').value.trim() || undefined;
436
+ const category = document.getElementById('pr-m-category').value.trim() || undefined;
437
+ const tagsRaw = document.getElementById('pr-m-tags').value.trim();
438
+ const model = document.getElementById('pr-m-model').value.trim() || undefined;
439
+ const tempRaw = document.getElementById('pr-m-temperature').value.trim();
440
+ const temperature = tempRaw ? parseFloat(tempRaw) : undefined;
441
+ const notes = document.getElementById('pr-m-notes').value.trim() || undefined;
442
+ const tags = tagsRaw ? tagsRaw.split(',').map(s => s.trim()).filter(Boolean) : [];
443
+ const rating = selectedRating || undefined;
444
+
445
+ const errorEl = document.getElementById('pr-m-error');
446
+ if (!title) { errorEl.textContent = 'Title is required'; titleInput.focus(); return; }
447
+ if (!content) { errorEl.textContent = 'Content is required'; contentArea.focus(); return; }
448
+
449
+ const body = { title, content, description, category, tags, model, temperature, rating, notes };
450
+
451
+ try {
452
+ let res;
453
+ if (isEdit) {
454
+ res = await api('/entries/' + entry.id, { method: 'PUT', body: JSON.stringify(body) });
455
+ } else {
456
+ res = await api('/entries', { method: 'POST', body: JSON.stringify(body) });
457
+ }
458
+ if (!res.ok) {
459
+ const data = await res.json();
460
+ errorEl.textContent = data.error || 'Failed to save';
461
+ return;
462
+ }
463
+ closeModal();
464
+ loadEntries();
465
+ } catch (err) {
466
+ document.getElementById('pr-m-error').textContent = err.message;
467
+ }
468
+ }
469
+
470
+ document.getElementById('pr-m-submit').addEventListener('click', handleSubmit);
471
+ document.getElementById('pr-m-cancel').addEventListener('click', closeModal);
472
+
473
+ overlay.addEventListener('click', (e) => { if (e.target === overlay) closeModal(); });
474
+
475
+ function onKeyDown(e) {
476
+ if (e.key === 'Escape') closeModal();
477
+ else if (e.key === 'Enter' && e.ctrlKey) handleSubmit();
478
+ }
479
+ document.addEventListener('keydown', onKeyDown);
480
+ overlay._keydownHandler = onKeyDown;
481
+ }
482
+
483
+ function closeModal() {
484
+ const existing = document.getElementById('pr-modal-overlay');
485
+ if (existing) {
486
+ if (existing._keydownHandler) document.removeEventListener('keydown', existing._keydownHandler);
487
+ existing.remove();
488
+ }
489
+ }
490
+
491
+ // ── Events ───────────────────────────────────────────────────────────
492
+
493
+ function bindEvents() {
494
+ if (!_container) return;
495
+
496
+ _container.querySelector('#pr-btn-add').addEventListener('click', () => openModal('add'));
497
+
498
+ _container.querySelector('#pr-filter-category').addEventListener('change', (e) => {
499
+ _activeFilter.category = e.target.value || null;
500
+ loadEntries();
501
+ });
502
+
503
+ _container.querySelector('#pr-search').addEventListener('input', () => {
504
+ clearTimeout(_searchTimer);
505
+ _searchTimer = setTimeout(renderEntries, 150);
506
+ });
507
+
508
+ bindDeleteDialog();
509
+ }
510
+
511
+ // ── Exports ──────────────────────────────────────────────────────────
512
+
513
+ export function mount(container, ctx) {
514
+ _container = container;
515
+ _entries = [];
516
+ _categories = [];
517
+ _activeFilter = { category: null };
518
+
519
+ container.classList.add('page-prompts');
520
+ container.innerHTML = BODY_HTML;
521
+
522
+ bindEvents();
523
+ loadEntries();
524
+ }
525
+
526
+ export function unmount(container) {
527
+ clearTimeout(_searchTimer);
528
+ closeModal();
529
+ container.classList.remove('page-prompts');
530
+ container.innerHTML = '';
531
+ _container = null;
532
+ _entries = [];
533
+ _categories = [];
534
+ _deleteTarget = null;
535
+ _searchTimer = null;
536
+ }
537
+
538
+ export function onProjectChange(project) {
539
+ _activeFilter = { category: null };
540
+ loadEntries();
541
+ }