@ycniuqton/devlens 0.1.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 (61) hide show
  1. package/README.md +164 -0
  2. package/bin/devlens.js +2 -0
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.js +205 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/init.d.ts +3 -0
  7. package/dist/init.js +239 -0
  8. package/dist/init.js.map +1 -0
  9. package/dist/routes/diff.d.ts +1 -0
  10. package/dist/routes/diff.js +39 -0
  11. package/dist/routes/diff.js.map +1 -0
  12. package/dist/routes/integrations.d.ts +1 -0
  13. package/dist/routes/integrations.js +132 -0
  14. package/dist/routes/integrations.js.map +1 -0
  15. package/dist/routes/rules.d.ts +1 -0
  16. package/dist/routes/rules.js +115 -0
  17. package/dist/routes/rules.js.map +1 -0
  18. package/dist/routes/tasks.d.ts +4 -0
  19. package/dist/routes/tasks.js +360 -0
  20. package/dist/routes/tasks.js.map +1 -0
  21. package/dist/server.d.ts +7 -0
  22. package/dist/server.js +112 -0
  23. package/dist/server.js.map +1 -0
  24. package/dist/services/claudeTasks.d.ts +23 -0
  25. package/dist/services/claudeTasks.js +160 -0
  26. package/dist/services/claudeTasks.js.map +1 -0
  27. package/dist/services/config.d.ts +3 -0
  28. package/dist/services/config.js +25 -0
  29. package/dist/services/config.js.map +1 -0
  30. package/dist/services/git.d.ts +8 -0
  31. package/dist/services/git.js +90 -0
  32. package/dist/services/git.js.map +1 -0
  33. package/dist/services/jira.d.ts +11 -0
  34. package/dist/services/jira.js +52 -0
  35. package/dist/services/jira.js.map +1 -0
  36. package/dist/services/linear.d.ts +9 -0
  37. package/dist/services/linear.js +69 -0
  38. package/dist/services/linear.js.map +1 -0
  39. package/dist/services/rules.d.ts +14 -0
  40. package/dist/services/rules.js +133 -0
  41. package/dist/services/rules.js.map +1 -0
  42. package/dist/services/taskStore.d.ts +27 -0
  43. package/dist/services/taskStore.js +261 -0
  44. package/dist/services/taskStore.js.map +1 -0
  45. package/dist/services/tunnel.d.ts +8 -0
  46. package/dist/services/tunnel.js +152 -0
  47. package/dist/services/tunnel.js.map +1 -0
  48. package/dist/services/watcher.d.ts +2 -0
  49. package/dist/services/watcher.js +30 -0
  50. package/dist/services/watcher.js.map +1 -0
  51. package/dist/types/index.d.ts +87 -0
  52. package/dist/types/index.js +3 -0
  53. package/dist/types/index.js.map +1 -0
  54. package/package.json +53 -0
  55. package/public/css/style.css +1613 -0
  56. package/public/index.html +395 -0
  57. package/public/js/app.js +104 -0
  58. package/public/js/diff.js +337 -0
  59. package/public/js/integrations.js +194 -0
  60. package/public/js/rules.js +174 -0
  61. package/public/js/tasks.js +301 -0
@@ -0,0 +1,395 @@
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>Devlens</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
10
+ <link rel="stylesheet" href="https://unpkg.com/diff2html/bundles/css/diff2html.min.css">
11
+ <link rel="stylesheet" href="/css/style.css">
12
+ </head>
13
+ <body>
14
+
15
+ <!-- Sidebar Navigation -->
16
+ <aside id="sidebar">
17
+ <div class="sidebar-brand">
18
+ <div class="brand-icon">
19
+ <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
20
+ <circle cx="12" cy="12" r="3"/><path d="M12 1v4M12 19v4M4.22 4.22l2.83 2.83M16.95 16.95l2.83 2.83M1 12h4M19 12h4M4.22 19.78l2.83-2.83M16.95 7.05l2.83-2.83"/>
21
+ </svg>
22
+ </div>
23
+ <span class="brand-name">Devlens</span>
24
+ </div>
25
+
26
+ <nav class="sidebar-nav">
27
+ <button class="nav-item active" data-tab="diff" aria-label="View diffs">
28
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
29
+ <path d="M12 3v18M3 12h18"/><rect x="3" y="3" width="18" height="18" rx="2"/>
30
+ </svg>
31
+ <span>Diffs</span>
32
+ </button>
33
+ <button class="nav-item" data-tab="tasks" aria-label="View tasks">
34
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
35
+ <rect x="3" y="3" width="7" height="9" rx="1"/><rect x="14" y="3" width="7" height="5" rx="1"/><rect x="14" y="12" width="7" height="9" rx="1"/><rect x="3" y="16" width="7" height="5" rx="1"/>
36
+ </svg>
37
+ <span>Tasks</span>
38
+ </button>
39
+ <button class="nav-item" data-tab="rules" aria-label="View rules">
40
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
41
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="9" y1="13" x2="15" y2="13"/><line x1="9" y1="17" x2="15" y2="17"/>
42
+ </svg>
43
+ <span>Rules</span>
44
+ </button>
45
+ <button class="nav-item" data-tab="integrations" aria-label="View integrations">
46
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
47
+ <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>
48
+ </svg>
49
+ <span>Integrations</span>
50
+ </button>
51
+ </nav>
52
+
53
+ <div class="sidebar-footer">
54
+ <div class="connection-status" id="ws-status" title="Disconnected">
55
+ <span class="status-indicator"></span>
56
+ <span class="status-label">Disconnected</span>
57
+ </div>
58
+ </div>
59
+ </aside>
60
+
61
+ <!-- Main Content -->
62
+ <main id="main-content">
63
+
64
+ <!-- Diffs View -->
65
+ <section id="diff-view" class="tab-content active">
66
+ <header class="view-header">
67
+ <h1>Changes</h1>
68
+ <div class="header-actions">
69
+ <div class="btn-group">
70
+ <button class="btn btn-ghost active" data-filter="all">All</button>
71
+ <button class="btn btn-ghost" data-filter="unstaged">Unstaged</button>
72
+ <button class="btn btn-ghost" data-filter="staged">Staged</button>
73
+ </div>
74
+ <div class="divider-v"></div>
75
+ <div class="btn-group">
76
+ <button class="btn btn-ghost" data-view="side-by-side">
77
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="8" height="18" rx="1"/><rect x="13" y="3" width="8" height="18" rx="1"/></svg>
78
+ Split
79
+ </button>
80
+ <button class="btn btn-ghost active" data-view="line-by-line">
81
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="1"/><line x1="3" y1="12" x2="21" y2="12"/></svg>
82
+ Unified
83
+ </button>
84
+ </div>
85
+ <div class="divider-v"></div>
86
+ <div class="btn-group">
87
+ <button class="btn btn-ghost" id="toggle-wrap">
88
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 6H3M15 12H3M17 12h1a2 2 0 0 1 0 4h-1l-2 2v-2H3"/></svg>
89
+ Wrap
90
+ </button>
91
+ </div>
92
+ </div>
93
+ </header>
94
+ <div class="diff-container">
95
+ <aside id="file-list" class="file-list">
96
+ <div class="file-list-header">
97
+ <h3>Files</h3>
98
+ <div class="file-list-actions">
99
+ <button class="btn-icon btn-icon-sm" id="expand-all-btn" title="Expand all files" aria-label="Expand all">
100
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
101
+ </button>
102
+ <button class="btn-icon btn-icon-sm" id="collapse-all-btn" title="Collapse all files" aria-label="Collapse all">
103
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"/></svg>
104
+ </button>
105
+ <div class="file-list-divider"></div>
106
+ <button class="btn-icon btn-icon-sm" id="file-view-tree" title="Folder view" aria-label="Folder view">
107
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>
108
+ </button>
109
+ <button class="btn-icon btn-icon-sm active" id="file-view-flat" title="Flat list" aria-label="Flat list">
110
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></svg>
111
+ </button>
112
+ <span class="file-count" id="file-count">0</span>
113
+ </div>
114
+ </div>
115
+ <ul id="file-list-items">
116
+ <li class="empty-state-inline">No changed files</li>
117
+ </ul>
118
+ </aside>
119
+ <div id="diff-output" class="diff-output">
120
+ <div class="empty-state">
121
+ <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" opacity="0.3">
122
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/>
123
+ </svg>
124
+ <p>No changes detected</p>
125
+ <span>Edit files in your project to see diffs here</span>
126
+ </div>
127
+ </div>
128
+ </div>
129
+ </section>
130
+
131
+ <!-- Tasks View -->
132
+ <section id="tasks-view" class="tab-content">
133
+ <header class="view-header">
134
+ <h1>Task Board</h1>
135
+ <div class="header-actions">
136
+ <div class="session-filter">
137
+ <select id="session-filter" class="session-select">
138
+ <option value="">All Sessions</option>
139
+ </select>
140
+ </div>
141
+ <button class="btn btn-primary" id="add-task-btn">
142
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
143
+ New Task
144
+ </button>
145
+ </div>
146
+ </header>
147
+ <div id="kanban-board" class="kanban-board">
148
+ <div class="kanban-column" data-status="pending">
149
+ <div class="column-header">
150
+ <div class="column-title">
151
+ <span class="column-dot pending"></span>
152
+ <h3>Pending</h3>
153
+ </div>
154
+ <span class="count">0</span>
155
+ </div>
156
+ <div class="column-cards" data-status="pending"></div>
157
+ </div>
158
+ <div class="kanban-column" data-status="in-progress">
159
+ <div class="column-header">
160
+ <div class="column-title">
161
+ <span class="column-dot in-progress"></span>
162
+ <h3>In Progress</h3>
163
+ </div>
164
+ <span class="count">0</span>
165
+ </div>
166
+ <div class="column-cards" data-status="in-progress"></div>
167
+ </div>
168
+ <div class="kanban-column" data-status="completed">
169
+ <div class="column-header">
170
+ <div class="column-title">
171
+ <span class="column-dot completed"></span>
172
+ <h3>Completed</h3>
173
+ </div>
174
+ <span class="count">0</span>
175
+ </div>
176
+ <div class="column-cards" data-status="completed"></div>
177
+ </div>
178
+ <div class="kanban-column column-archived" data-status="archived">
179
+ <div class="column-header">
180
+ <div class="column-title">
181
+ <span class="column-dot archived"></span>
182
+ <h3>Archived</h3>
183
+ </div>
184
+ <span class="count">0</span>
185
+ </div>
186
+ <div class="column-cards" data-status="archived"></div>
187
+ </div>
188
+ </div>
189
+
190
+ </section>
191
+
192
+ <!-- Rules View -->
193
+ <section id="rules-view" class="tab-content">
194
+ <header class="view-header">
195
+ <h1>Rules</h1>
196
+ <div class="header-actions">
197
+ <button class="btn btn-primary" id="add-rule-btn">
198
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
199
+ New Rule
200
+ </button>
201
+ </div>
202
+ </header>
203
+
204
+ <!-- Commit Approval Banner -->
205
+ <div id="commit-approval-banner" class="commit-approval-banner" style="display:none">
206
+ <div class="commit-approval-icon">
207
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
208
+ </div>
209
+ <div class="commit-approval-body">
210
+ <div class="commit-approval-title">Claude is waiting for commit approval</div>
211
+ <div class="commit-approval-message" id="commit-approval-message"></div>
212
+ </div>
213
+ <div class="commit-approval-actions">
214
+ <button class="btn btn-ghost" id="reject-commit-btn">Reject</button>
215
+ <button class="btn btn-primary" id="approve-commit-btn">Approve Commit</button>
216
+ </div>
217
+ </div>
218
+
219
+ <!-- Approved confirmation -->
220
+ <div id="commit-approved-banner" class="commit-approved-banner" style="display:none">
221
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
222
+ <span>Commit approved at <span id="commit-approved-time"></span></span>
223
+ </div>
224
+
225
+ <div class="rules-container">
226
+ <div class="preset-rules">
227
+ <div class="preset-label">Quick add:</div>
228
+ <button class="btn btn-ghost btn-sm preset-btn" data-preset="Pause after every 3 file changes">+ Pause after every 3 file changes</button>
229
+ <button class="btn btn-ghost btn-sm preset-btn" data-preset="Always write tests before implementation">+ Tests first</button>
230
+ <button class="btn btn-ghost btn-sm preset-btn" data-preset="No direct edits to production files">+ No prod edits</button>
231
+ </div>
232
+
233
+ <div id="rules-list" class="rules-list">
234
+ <p class="panel-empty">Loading rules...</p>
235
+ </div>
236
+ </div>
237
+ </section>
238
+
239
+ <!-- Integrations View -->
240
+ <section id="integrations-view" class="tab-content">
241
+ <header class="view-header">
242
+ <h1>Integrations</h1>
243
+ </header>
244
+ <div class="integrations-container">
245
+
246
+ <!-- Public Access / Tunnel -->
247
+ <div class="integration-section">
248
+ <div class="section-header">
249
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>
250
+ <h2>Public Access</h2>
251
+ <span class="tunnel-status-badge" id="tunnel-badge">disconnected</span>
252
+ </div>
253
+ <p class="section-desc">Expose your Devlens dashboard over the internet using a tunnel. No public IP required.</p>
254
+
255
+ <div id="tunnel-active" style="display:none" class="tunnel-active-card">
256
+ <div class="tunnel-url-row">
257
+ <span class="tunnel-label">Public URL</span>
258
+ <a id="tunnel-url" href="#" target="_blank" class="tunnel-url"></a>
259
+ <button class="btn btn-ghost btn-sm" onclick="copyTunnelUrl()">Copy</button>
260
+ </div>
261
+ <button class="btn btn-ghost" onclick="stopTunnelAction()" style="color:var(--color-danger)">Disconnect</button>
262
+ </div>
263
+
264
+ <div id="tunnel-controls">
265
+ <div class="tunnel-buttons">
266
+ <button class="btn" onclick="startTunnelAction('cloudflare')">
267
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9z"/></svg>
268
+ Cloudflare Tunnel
269
+ </button>
270
+ <button class="btn" onclick="startTunnelAction('ngrok')">
271
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
272
+ ngrok
273
+ </button>
274
+ </div>
275
+ <p class="section-hint">Requires <code>cloudflared</code> or <code>ngrok</code> installed on your system.</p>
276
+ </div>
277
+ </div>
278
+
279
+ <!-- Task Managers -->
280
+ <div class="integration-section">
281
+ <div class="section-header">
282
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="9" rx="1"/><rect x="14" y="3" width="7" height="5" rx="1"/><rect x="14" y="12" width="7" height="9" rx="1"/><rect x="3" y="16" width="7" height="5" rx="1"/></svg>
283
+ <h2>Task Managers</h2>
284
+ </div>
285
+ <p class="section-desc">Connect Jira or Linear to view external tickets alongside Claude's tasks.</p>
286
+ <div id="integrations-content"></div>
287
+ </div>
288
+
289
+ </div>
290
+ </section>
291
+ </main>
292
+
293
+ <!-- Task Modal -->
294
+ <div id="task-modal" class="modal hidden">
295
+ <div class="modal-backdrop"></div>
296
+ <div class="modal-content">
297
+ <!-- Header: title | task# + status badge | close -->
298
+ <div class="modal-header">
299
+ <h3 id="modal-title">New Task</h3>
300
+ <div class="modal-header-center">
301
+ <span id="modal-task-number" class="modal-task-num"></span>
302
+ <span id="modal-status-badge" class="modal-status-badge"></span>
303
+ </div>
304
+ <button type="button" class="btn btn-icon" id="modal-close" aria-label="Close modal">
305
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
306
+ </button>
307
+ </div>
308
+
309
+ <form id="task-form">
310
+ <input type="hidden" id="task-id">
311
+ <div class="modal-body">
312
+ <!-- Row 1: Title (full width) -->
313
+ <div class="form-group">
314
+ <input type="text" id="task-title-input" placeholder="Task title..." required class="input-title">
315
+ </div>
316
+
317
+ <!-- Row 2: Priority | Status | Tags -->
318
+ <div class="modal-row-3col">
319
+ <div class="form-group">
320
+ <label for="task-priority-input">Priority</label>
321
+ <select id="task-priority-input">
322
+ <option value="low">Low</option>
323
+ <option value="medium" selected>Medium</option>
324
+ <option value="high">High</option>
325
+ </select>
326
+ </div>
327
+ <div class="form-group">
328
+ <label for="task-status-input">Status</label>
329
+ <select id="task-status-input">
330
+ <option value="pending">Pending</option>
331
+ <option value="in-progress">In Progress</option>
332
+ <option value="completed">Completed</option>
333
+ <option value="archived">Archived</option>
334
+ </select>
335
+ </div>
336
+ <div class="form-group" style="flex:2">
337
+ <label for="task-tags-input">Tags</label>
338
+ <input type="text" id="task-tags-input" placeholder="bug, frontend, urgent">
339
+ </div>
340
+ </div>
341
+
342
+ <!-- Row 3: Description | Claude's Reasoning -->
343
+ <div class="modal-row-2col">
344
+ <div class="form-group">
345
+ <label for="task-desc-input">Description</label>
346
+ <textarea id="task-desc-input" rows="6" placeholder="Add details..."></textarea>
347
+ </div>
348
+ <div class="form-group" id="task-reasoning-group" style="display:none">
349
+ <label>Claude's Reasoning</label>
350
+ <div id="task-context-reasoning" class="context-display tall"></div>
351
+ </div>
352
+ </div>
353
+
354
+ <!-- Row 4: Files | Metadata -->
355
+ <div class="modal-row-2col" id="modal-row-files" style="display:none">
356
+ <div class="form-group" id="task-files-group" style="display:none">
357
+ <label>Files</label>
358
+ <div id="task-context-files" class="context-display mono"></div>
359
+ </div>
360
+ <div class="form-group" id="task-claude-meta-group" style="display:none">
361
+ <label>Metadata</label>
362
+ <div id="task-claude-meta" class="context-display mono"></div>
363
+ </div>
364
+ </div>
365
+
366
+ <!-- Hidden context fields for data storage -->
367
+ <div style="display:none">
368
+ <div id="task-context-group"><div id="task-context-prompt"></div></div>
369
+ <div id="task-completion-group"><div id="task-completion-display"></div></div>
370
+ <div id="task-completion-files-group"><div id="task-completion-files"></div></div>
371
+ </div>
372
+ </div>
373
+
374
+ <!-- Footer: session hint | buttons -->
375
+ <div class="modal-actions">
376
+ <span id="modal-session-hint" class="modal-session-hint"></span>
377
+ <div class="modal-actions-right">
378
+ <button type="button" class="btn btn-ghost" id="modal-cancel">Cancel</button>
379
+ <button type="submit" class="btn btn-primary">Save Task</button>
380
+ </div>
381
+ </div>
382
+ </form>
383
+ </div>
384
+ </div>
385
+
386
+ <div id="toast-container"></div>
387
+
388
+ <script src="https://unpkg.com/diff2html/bundles/js/diff2html.min.js"></script>
389
+ <script src="/js/app.js"></script>
390
+ <script src="/js/diff.js"></script>
391
+ <script src="/js/tasks.js"></script>
392
+ <script src="/js/rules.js"></script>
393
+ <script src="/js/integrations.js"></script>
394
+ </body>
395
+ </html>
@@ -0,0 +1,104 @@
1
+ // Tab switching — sidebar nav with URL routing
2
+ const navItems = document.querySelectorAll('.nav-item');
3
+ const tabContents = document.querySelectorAll('.tab-content');
4
+
5
+ function switchTab(tab) {
6
+ navItems.forEach(n => n.classList.remove('active'));
7
+ tabContents.forEach(c => c.classList.remove('active'));
8
+ const navItem = document.querySelector(`.nav-item[data-tab="${tab}"]`);
9
+ if (navItem) navItem.classList.add('active');
10
+ const view = document.getElementById(tab + '-view');
11
+ if (view) view.classList.add('active');
12
+ }
13
+
14
+ navItems.forEach(item => {
15
+ item.addEventListener('click', () => {
16
+ const tab = item.dataset.tab;
17
+ switchTab(tab);
18
+ history.pushState(null, '', '/' + tab);
19
+ });
20
+ });
21
+
22
+ // Handle browser back/forward
23
+ window.addEventListener('popstate', () => {
24
+ const tab = location.pathname.replace('/', '') || 'diff';
25
+ switchTab(tab);
26
+ });
27
+
28
+ // Load initial tab from URL — redirect / to /diff
29
+ (function() {
30
+ const path = location.pathname.replace('/', '');
31
+ const tab = ['diff', 'tasks', 'rules', 'integrations'].includes(path) ? path : 'diff';
32
+ if (!path || path === '') {
33
+ history.replaceState(null, '', '/diff');
34
+ }
35
+ switchTab(tab);
36
+ })();
37
+
38
+ // WebSocket
39
+ let ws = null;
40
+ let reconnectDelay = 1000;
41
+ const statusEl = document.getElementById('ws-status');
42
+ const statusIndicator = statusEl.querySelector('.status-indicator');
43
+ const statusLabel = statusEl.querySelector('.status-label');
44
+
45
+ function connectWebSocket() {
46
+ const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
47
+ ws = new WebSocket(`${protocol}//${location.host}/ws`);
48
+
49
+ ws.onopen = () => {
50
+ statusIndicator.classList.add('connected');
51
+ statusLabel.textContent = 'Connected';
52
+ statusEl.title = 'WebSocket connected';
53
+ reconnectDelay = 1000;
54
+ };
55
+
56
+ ws.onclose = () => {
57
+ statusIndicator.classList.remove('connected');
58
+ statusLabel.textContent = 'Disconnected';
59
+ statusEl.title = 'WebSocket disconnected';
60
+ setTimeout(connectWebSocket, reconnectDelay);
61
+ reconnectDelay = Math.min(reconnectDelay * 2, 30000);
62
+ };
63
+
64
+ ws.onerror = () => ws.close();
65
+
66
+ ws.onmessage = (event) => {
67
+ try {
68
+ const msg = JSON.parse(event.data);
69
+ if (msg.type === 'diff-update' && typeof handleDiffUpdate === 'function') {
70
+ handleDiffUpdate(msg.payload);
71
+ }
72
+ if (msg.type === 'status-update' && typeof handleStatusUpdate === 'function') {
73
+ handleStatusUpdate(msg.payload);
74
+ }
75
+ if (msg.type === 'task-update' && typeof handleTaskUpdate === 'function') {
76
+ handleTaskUpdate(msg.payload);
77
+ }
78
+ if (msg.type === 'rules-update' && typeof handleRulesUpdate === 'function') {
79
+ handleRulesUpdate(msg.payload);
80
+ }
81
+ if (msg.type === 'commit-approval-update' && typeof handleCommitApprovalUpdate === 'function') {
82
+ handleCommitApprovalUpdate(msg.payload);
83
+ }
84
+ } catch (e) {
85
+ console.error('WebSocket message parse error:', e);
86
+ }
87
+ };
88
+ }
89
+
90
+ connectWebSocket();
91
+
92
+ // Toast notifications
93
+ function showToast(message, type = 'info') {
94
+ const container = document.getElementById('toast-container');
95
+ const toast = document.createElement('div');
96
+ toast.className = `toast ${type}`;
97
+ toast.textContent = message;
98
+ container.appendChild(toast);
99
+ setTimeout(() => {
100
+ toast.style.opacity = '0';
101
+ toast.style.transition = `opacity ${250}ms`;
102
+ setTimeout(() => toast.remove(), 250);
103
+ }, 3000);
104
+ }