@mattli/dotmd 0.1.0 → 0.1.2

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 (45) hide show
  1. package/dist/cli/commands/init.d.ts.map +1 -1
  2. package/dist/cli/commands/init.js +12 -7
  3. package/dist/cli/commands/init.js.map +1 -1
  4. package/dist/cli/commands/serve.d.ts.map +1 -1
  5. package/dist/cli/commands/serve.js +18 -1
  6. package/dist/cli/commands/serve.js.map +1 -1
  7. package/dist/cli/commands/status.d.ts.map +1 -1
  8. package/dist/cli/commands/status.js +10 -5
  9. package/dist/cli/commands/status.js.map +1 -1
  10. package/dist/dashboard/_archive/wizard-client.d.ts +2 -0
  11. package/dist/dashboard/_archive/wizard-client.d.ts.map +1 -0
  12. package/dist/dashboard/_archive/wizard-client.js +412 -0
  13. package/dist/dashboard/_archive/wizard-client.js.map +1 -0
  14. package/dist/dashboard/_archive/wizard.d.ts +9 -0
  15. package/dist/dashboard/_archive/wizard.d.ts.map +1 -0
  16. package/dist/dashboard/_archive/wizard.js +317 -0
  17. package/dist/dashboard/_archive/wizard.js.map +1 -0
  18. package/dist/dashboard/help.d.ts +2 -0
  19. package/dist/dashboard/help.d.ts.map +1 -0
  20. package/dist/dashboard/help.js +73 -0
  21. package/dist/dashboard/help.js.map +1 -0
  22. package/dist/dashboard/layout.d.ts.map +1 -1
  23. package/dist/dashboard/layout.js +1 -0
  24. package/dist/dashboard/layout.js.map +1 -1
  25. package/dist/dashboard/server.d.ts +1 -1
  26. package/dist/dashboard/server.d.ts.map +1 -1
  27. package/dist/dashboard/server.js +39 -51
  28. package/dist/dashboard/server.js.map +1 -1
  29. package/dist/dashboard/settings-client.d.ts +1 -1
  30. package/dist/dashboard/settings-client.d.ts.map +1 -1
  31. package/dist/dashboard/settings-client.js +367 -162
  32. package/dist/dashboard/settings-client.js.map +1 -1
  33. package/dist/dashboard/settings.d.ts +5 -1
  34. package/dist/dashboard/settings.d.ts.map +1 -1
  35. package/dist/dashboard/settings.js +60 -45
  36. package/dist/dashboard/settings.js.map +1 -1
  37. package/dist/dashboard/wizard-client.d.ts +1 -1
  38. package/dist/dashboard/wizard-client.d.ts.map +1 -1
  39. package/dist/dashboard/wizard-client.js +226 -80
  40. package/dist/dashboard/wizard-client.js.map +1 -1
  41. package/dist/dashboard/wizard.d.ts +2 -2
  42. package/dist/dashboard/wizard.d.ts.map +1 -1
  43. package/dist/dashboard/wizard.js +113 -32
  44. package/dist/dashboard/wizard.js.map +1 -1
  45. package/package.json +1 -1
@@ -0,0 +1,317 @@
1
+ import os from "os";
2
+ import { expandHome } from "../config/defaults.js";
3
+ function escapeHtml(str) {
4
+ return str
5
+ .replace(/&/g, "&")
6
+ .replace(/</g, "&lt;")
7
+ .replace(/>/g, "&gt;")
8
+ .replace(/"/g, "&quot;");
9
+ }
10
+ function displayPath(p) {
11
+ return p.replace(os.homedir(), "~");
12
+ }
13
+ function stepIndicator(current) {
14
+ const steps = ["Folders", "Patterns", "Preview"];
15
+ return `<div class="flex items-center justify-center gap-2 mb-8">
16
+ ${steps
17
+ .map((label, i) => {
18
+ const num = i + 1;
19
+ const isActive = num === current;
20
+ const isDone = num < current;
21
+ const circleClass = isActive
22
+ ? "bg-blue-600 text-white"
23
+ : isDone
24
+ ? "bg-blue-200 text-blue-800"
25
+ : "bg-gray-200 text-gray-500";
26
+ const labelClass = isActive
27
+ ? "text-blue-600 font-semibold"
28
+ : "text-gray-400";
29
+ const connector = i < steps.length - 1
30
+ ? `<div class="w-12 h-px ${num < current ? "bg-blue-300" : "bg-gray-200"}"></div>`
31
+ : "";
32
+ return `<div class="flex items-center gap-2">
33
+ <span class="w-7 h-7 rounded-full flex items-center justify-center text-sm font-medium ${circleClass}">${num}</span>
34
+ <span class="text-sm ${labelClass}">${label}</span>
35
+ </div>${connector}`;
36
+ })
37
+ .join("")}
38
+ </div>`;
39
+ }
40
+ function chipHtml(value, label, selected) {
41
+ const selectedClasses = selected
42
+ ? "bg-blue-100 text-blue-800 border-blue-300"
43
+ : "bg-gray-100 text-gray-600 border-gray-300";
44
+ return `<span data-chip data-value="${escapeHtml(value)}" data-selected="${selected}"
45
+ class="inline-flex items-center px-3 py-1.5 rounded-full text-sm font-medium border cursor-pointer select-none transition-colors ${selectedClasses}">
46
+ ${escapeHtml(label)}
47
+ </span>`;
48
+ }
49
+ function folderChipHtml(value, label) {
50
+ return `<span data-folder-chip="${escapeHtml(value)}"
51
+ class="inline-flex items-center gap-1 px-3 py-1.5 rounded-full text-sm font-medium bg-blue-100 text-blue-800 border border-blue-300">
52
+ ${escapeHtml(label)} <button type="button" class="ml-1 text-blue-500 hover:text-blue-700 font-bold">&times;</button>
53
+ </span>`;
54
+ }
55
+ function patternChipHtml(value) {
56
+ return `<span data-pattern-chip="${escapeHtml(value)}"
57
+ class="inline-flex items-center gap-1 px-3 py-1.5 rounded-full text-sm font-medium bg-blue-100 text-blue-800 border border-blue-300">
58
+ ${escapeHtml(value)} <button type="button" class="ml-1 text-blue-500 hover:text-blue-700 font-bold">&times;</button>
59
+ </span>`;
60
+ }
61
+ function customChipHtml(value, label) {
62
+ return `<span data-custom-value="${escapeHtml(value)}"
63
+ class="inline-flex items-center gap-1 px-3 py-1.5 rounded-full text-sm font-medium bg-blue-100 text-blue-800 border border-blue-300">
64
+ ${escapeHtml(label)} <button type="button" class="ml-1 text-blue-500 hover:text-blue-700 font-bold">&times;</button>
65
+ </span>`;
66
+ }
67
+ export function setupFoldersPage(existingDirs, preservedRoots) {
68
+ const hasPreserved = preservedRoots && preservedRoots.length > 0;
69
+ const selectedRoots = hasPreserved ? preservedRoots : existingDirs;
70
+ const chips = selectedRoots
71
+ .map((root) => folderChipHtml(root, root))
72
+ .join("\n ");
73
+ return `
74
+ <div class="max-w-2xl mx-auto">
75
+ ${stepIndicator(1)}
76
+ <h1 class="text-2xl font-bold mb-2">Where are your projects?</h1>
77
+ <p class="text-gray-500 mb-6">Select the folders where dotmd should look for instruction files.</p>
78
+
79
+ <div class="mb-6">
80
+ <h2 class="text-sm font-semibold text-gray-500 uppercase tracking-wide mb-3">Folders</h2>
81
+ <div id="folder-list" class="flex flex-wrap gap-2">
82
+ ${chips}
83
+ </div>
84
+ <p id="no-folders-msg" class="text-gray-400 text-sm mt-2 ${selectedRoots.length > 0 ? "hidden" : ""}">No folders selected. Use the browser below to add folders.</p>
85
+ </div>
86
+
87
+ <div class="mb-8">
88
+ <button id="open-browser" type="button"
89
+ class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg text-sm font-medium hover:bg-gray-200 transition-colors border border-gray-300">
90
+ Browse for folder...
91
+ </button>
92
+ <div id="folder-browser" class="hidden mt-3 bg-white border border-gray-200 rounded-lg overflow-hidden">
93
+ <div id="browser-header" class="px-4 py-2 bg-gray-50 border-b border-gray-200 flex items-center gap-2 text-sm">
94
+ <button id="browser-up" type="button" class="text-blue-600 hover:underline font-medium">Up</button>
95
+ <span id="browser-path" class="text-gray-500 font-mono text-xs"></span>
96
+ </div>
97
+ <div id="browser-list" class="max-h-64 overflow-y-auto divide-y divide-gray-100"></div>
98
+ </div>
99
+ </div>
100
+
101
+ <div class="flex justify-end">
102
+ <button id="next-btn" data-step="1" type="button"
103
+ class="px-6 py-2.5 bg-blue-600 text-white rounded-lg text-sm font-medium hover:bg-blue-700 transition-colors">
104
+ Next
105
+ </button>
106
+ </div>
107
+ </div>`;
108
+ }
109
+ export function setupPatternsPage(selectedRoots, suggestedPatterns, selectedPatterns) {
110
+ const selectedSet = new Set(selectedPatterns);
111
+ // Selected patterns — shown as removable chips
112
+ const selectedChips = selectedPatterns
113
+ .map((p) => patternChipHtml(p))
114
+ .join("\n ");
115
+ // Unselected suggested patterns — shown as addable chips
116
+ const unselectedSuggested = suggestedPatterns.filter((p) => !selectedSet.has(p));
117
+ const suggestedChips = unselectedSuggested
118
+ .map((p) => `<span data-suggested-pattern="${escapeHtml(p)}"
119
+ class="inline-flex items-center gap-1 px-3 py-1.5 rounded-full text-sm font-medium bg-gray-100 text-gray-600 border border-gray-300 cursor-pointer hover:bg-gray-200 transition-colors select-none">
120
+ + ${escapeHtml(p)}
121
+ </span>`)
122
+ .join("\n ");
123
+ const rootsParam = selectedRoots
124
+ .map((r) => `roots=${encodeURIComponent(r)}`)
125
+ .join("&");
126
+ return `
127
+ <div class="max-w-2xl mx-auto">
128
+ ${stepIndicator(2)}
129
+ <h1 class="text-2xl font-bold mb-2">What files should we look for?</h1>
130
+ <p class="text-gray-500 mb-6">Select the filename patterns to search for in your selected folders.</p>
131
+
132
+ <div class="mb-4">
133
+ <h2 class="text-sm font-semibold text-gray-500 uppercase tracking-wide mb-3">Patterns</h2>
134
+ <div id="pattern-list" class="flex flex-wrap gap-2">
135
+ ${selectedChips}
136
+ </div>
137
+ <p id="no-patterns-msg" class="text-gray-400 text-sm mt-2 ${selectedPatterns.length > 0 ? "hidden" : ""}">No patterns selected.</p>
138
+ </div>
139
+
140
+ ${unselectedSuggested.length > 0 ? `<div class="mb-4">
141
+ <h2 class="text-sm font-semibold text-gray-500 uppercase tracking-wide mb-3">Suggestions</h2>
142
+ <div id="suggested-patterns" class="flex flex-wrap gap-2">
143
+ ${suggestedChips}
144
+ </div>
145
+ </div>` : ""}
146
+
147
+ <div class="mb-8">
148
+ <div class="flex gap-2">
149
+ <input id="custom-input" type="text" placeholder="rules/*.md"
150
+ class="flex-1 px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:border-blue-400">
151
+ <button id="add-custom" type="button"
152
+ class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg text-sm font-medium hover:bg-gray-200 transition-colors border border-gray-300">Add</button>
153
+ </div>
154
+ </div>
155
+
156
+ <div class="flex justify-between">
157
+ <button id="back-btn" type="button"
158
+ class="px-6 py-2.5 bg-gray-100 text-gray-700 rounded-lg text-sm font-medium hover:bg-gray-200 transition-colors">
159
+ Back
160
+ </button>
161
+ <button id="next-btn" data-step="2" type="button"
162
+ class="px-6 py-2.5 bg-blue-600 text-white rounded-lg text-sm font-medium hover:bg-blue-700 transition-colors">
163
+ Preview Files
164
+ </button>
165
+ </div>
166
+ </div>`;
167
+ }
168
+ export function setupPreviewPage(files, roots, patterns) {
169
+ const rootsParam = roots
170
+ .map((r) => `roots=${encodeURIComponent(r)}`)
171
+ .join("&");
172
+ const patternsParam = patterns
173
+ .map((p) => `patterns=${encodeURIComponent(p)}`)
174
+ .join("&");
175
+ const backParams = `${rootsParam}&${patternsParam}`;
176
+ if (files.length === 0) {
177
+ return `
178
+ <div class="max-w-2xl mx-auto">
179
+ ${stepIndicator(3)}
180
+ <h1 class="text-2xl font-bold mb-2">No files found</h1>
181
+ <p class="text-gray-500 mb-6">No matching files were found in the selected folders with the selected patterns. Try going back to adjust your selections.</p>
182
+ <a href="/setup/step2?${rootsParam}"
183
+ class="px-6 py-2.5 bg-gray-100 text-gray-700 rounded-lg text-sm font-medium hover:bg-gray-200 transition-colors">
184
+ Back
185
+ </a>
186
+ </div>`;
187
+ }
188
+ // Group by root (scan folder)
189
+ const groups = {};
190
+ const rootOrder = [];
191
+ for (const file of files) {
192
+ const root = file.root || "Other";
193
+ if (!groups[root]) {
194
+ groups[root] = [];
195
+ rootOrder.push(root);
196
+ }
197
+ groups[root].push(file);
198
+ }
199
+ function renderCheckbox(filePath, displayPath) {
200
+ return `
201
+ <label class="flex items-center gap-3 px-3 py-2 rounded-lg hover:bg-gray-50 cursor-pointer">
202
+ <input type="checkbox" name="files" value="${escapeHtml(filePath)}" checked
203
+ class="rounded border-gray-300 text-blue-600 focus:ring-blue-500">
204
+ <span class="font-mono text-sm">${escapeHtml(displayPath)}</span>
205
+ </label>`;
206
+ }
207
+ let fileListHtml = "";
208
+ for (const root of rootOrder) {
209
+ const rootFiles = groups[root];
210
+ if (!rootFiles || rootFiles.length === 0)
211
+ continue;
212
+ const expanded = expandHome(root);
213
+ // Calculate relative paths and group by first subdirectory
214
+ const subGroups = new Map();
215
+ const subGroupOrder = [];
216
+ for (const file of rootFiles) {
217
+ let relPath = file.path;
218
+ if (file.path.startsWith(expanded + "/")) {
219
+ relPath = file.path.slice(expanded.length + 1);
220
+ }
221
+ const firstSlash = relPath.indexOf("/");
222
+ const groupName = firstSlash === -1 ? "" : relPath.slice(0, firstSlash);
223
+ if (!subGroups.has(groupName)) {
224
+ subGroups.set(groupName, []);
225
+ subGroupOrder.push(groupName);
226
+ }
227
+ subGroups.get(groupName).push({ path: file.path, relPath });
228
+ }
229
+ const hasSubGroups = subGroupOrder.length > 1 || subGroupOrder[0] !== "";
230
+ fileListHtml += `<div class="mb-6">
231
+ <div class="flex items-center justify-between mb-2">
232
+ <h3 class="text-sm font-semibold text-gray-500 uppercase tracking-wide">${escapeHtml(root)}</h3>
233
+ <div class="flex gap-3">
234
+ <button type="button" data-group-select="${escapeHtml(root)}" class="text-xs text-blue-600 hover:underline">Select all</button>
235
+ <button type="button" data-group-deselect="${escapeHtml(root)}" class="text-xs text-blue-600 hover:underline">Deselect all</button>
236
+ </div>
237
+ </div>
238
+ <div class="space-y-1" data-group="${escapeHtml(root)}">`;
239
+ if (!hasSubGroups) {
240
+ // Flat list — no subdirectories
241
+ for (const file of subGroups.get("")) {
242
+ fileListHtml += renderCheckbox(file.path, file.relPath);
243
+ }
244
+ }
245
+ else {
246
+ // Render root-level files first (no subdirectory)
247
+ const rootLevel = subGroups.get("");
248
+ if (rootLevel) {
249
+ for (const file of rootLevel) {
250
+ fileListHtml += renderCheckbox(file.path, file.relPath);
251
+ }
252
+ }
253
+ // Render each subdirectory as a collapsible group
254
+ for (const groupName of subGroupOrder) {
255
+ if (groupName === "")
256
+ continue;
257
+ const groupFiles = subGroups.get(groupName);
258
+ fileListHtml += `
259
+ <details class="mt-1">
260
+ <summary class="cursor-pointer text-sm text-gray-600 hover:text-gray-800 px-3 py-1.5 rounded-lg hover:bg-gray-50 select-none">
261
+ ${escapeHtml(groupName)}/ <span class="text-gray-400">(${groupFiles.length})</span>
262
+ </summary>
263
+ <div class="ml-4">`;
264
+ for (const file of groupFiles) {
265
+ fileListHtml += renderCheckbox(file.path, file.relPath);
266
+ }
267
+ fileListHtml += `
268
+ </div>
269
+ </details>`;
270
+ }
271
+ }
272
+ fileListHtml += `</div></div>`;
273
+ }
274
+ return `
275
+ <div class="max-w-2xl mx-auto">
276
+ ${stepIndicator(3)}
277
+ <h1 class="text-2xl font-bold mb-2">Review discovered files</h1>
278
+ <p class="text-gray-500 mb-4">Found <strong>${files.length}</strong> file${files.length === 1 ? "" : "s"}. Uncheck any files you don't want to track.</p>
279
+
280
+ <div class="flex gap-3 mb-4">
281
+ <button id="select-all" type="button" class="text-sm text-blue-600 hover:underline">Select all</button>
282
+ <button id="deselect-all" type="button" class="text-sm text-blue-600 hover:underline">Deselect all</button>
283
+ </div>
284
+
285
+ <div class="bg-white border border-gray-200 rounded-lg p-4 mb-6">
286
+ ${fileListHtml}
287
+ </div>
288
+
289
+ <div class="flex justify-between">
290
+ <a href="/setup/step2?${backParams}"
291
+ class="px-6 py-2.5 bg-gray-100 text-gray-700 rounded-lg text-sm font-medium hover:bg-gray-200 transition-colors">
292
+ Back
293
+ </a>
294
+ <button id="confirm-btn" type="button"
295
+ class="px-6 py-2.5 bg-blue-600 text-white rounded-lg text-sm font-medium hover:bg-blue-700 transition-colors">
296
+ Confirm & Start Tracking
297
+ </button>
298
+ </div>
299
+ </div>`;
300
+ }
301
+ export function setupCompletePage(fileCount) {
302
+ return `
303
+ <div class="max-w-2xl mx-auto text-center py-16">
304
+ <div class="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-6">
305
+ <svg class="w-8 h-8 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
306
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
307
+ </svg>
308
+ </div>
309
+ <h1 class="text-2xl font-bold mb-2">Setup complete</h1>
310
+ <p class="text-gray-500 mb-8">Now tracking <strong>${fileCount}</strong> file${fileCount === 1 ? "" : "s"}. dotmd will snapshot changes whenever you scan.</p>
311
+ <a href="/"
312
+ class="px-6 py-2.5 bg-blue-600 text-white rounded-lg text-sm font-medium hover:bg-blue-700 transition-colors">
313
+ Go to Dashboard
314
+ </a>
315
+ </div>`;
316
+ }
317
+ //# sourceMappingURL=wizard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wizard.js","sourceRoot":"","sources":["../../../src/dashboard/_archive/wizard.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function helpPage(): string;
2
+ //# sourceMappingURL=help.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../../src/dashboard/help.ts"],"names":[],"mappings":"AAAA,wBAAgB,QAAQ,IAAI,MAAM,CAuEjC"}
@@ -0,0 +1,73 @@
1
+ export function helpPage() {
2
+ return `
3
+ <div class="max-w-3xl mx-auto pb-24">
4
+ <h1 class="text-2xl font-bold mb-6">Help</h1>
5
+
6
+ <!-- Commands -->
7
+ <section class="mb-8">
8
+ <h2 class="text-lg font-semibold mb-3">Commands</h2>
9
+ <div class="bg-white border border-gray-200 rounded-lg divide-y divide-gray-100">
10
+ <div class="px-4 py-3">
11
+ <code class="text-sm font-mono text-blue-700">dotmd init</code>
12
+ <p class="text-gray-600 text-sm mt-1">Launch the setup page to pick folders and file patterns to track.</p>
13
+ </div>
14
+ <div class="px-4 py-3">
15
+ <code class="text-sm font-mono text-blue-700">dotmd status</code>
16
+ <p class="text-gray-600 text-sm mt-1">Scan for changes and print a summary in your terminal.</p>
17
+ </div>
18
+ <div class="px-4 py-3">
19
+ <code class="text-sm font-mono text-blue-700">dotmd serve</code>
20
+ <p class="text-gray-600 text-sm mt-1">Start this dashboard at <code class="text-xs">http://localhost:3333</code>.</p>
21
+ </div>
22
+ </div>
23
+ </section>
24
+
25
+ <!-- Scanning -->
26
+ <section class="mb-8 pt-8 border-t border-gray-200">
27
+ <h2 class="text-lg font-semibold mb-3">When does dotmd scan?</h2>
28
+ <ul class="space-y-2 text-sm text-gray-600">
29
+ <li class="flex gap-2"><span class="text-gray-400">&bull;</span>Every time you load a dashboard page (Timeline, Files, or File Detail)</li>
30
+ <li class="flex gap-2"><span class="text-gray-400">&bull;</span>When you run <code class="font-mono text-xs text-blue-700">dotmd status</code> in your terminal</li>
31
+ <li class="flex gap-2"><span class="text-gray-400">&bull;</span>When you click the Refresh button in the nav bar</li>
32
+ </ul>
33
+ <p class="text-sm text-gray-600 mt-3">Each scan compares your tracked files against their last snapshot. If a file changed, dotmd saves the new version and a diff.</p>
34
+ </section>
35
+
36
+ <!-- Dashboard views -->
37
+ <section class="mb-8 pt-8 border-t border-gray-200">
38
+ <h2 class="text-lg font-semibold mb-3">Dashboard views</h2>
39
+ <div class="space-y-4 text-sm text-gray-600">
40
+ <div>
41
+ <h3 class="font-medium text-gray-900">Timeline</h3>
42
+ <p>A chronological feed of every change across all your tracked files. See what changed and when, with collapsible diffs.</p>
43
+ </div>
44
+ <div>
45
+ <h3 class="font-medium text-gray-900">Files</h3>
46
+ <p>Browse all tracked files organized by folder. Yellow dots indicate files that changed since you last viewed them.</p>
47
+ </div>
48
+ <div>
49
+ <h3 class="font-medium text-gray-900">Settings</h3>
50
+ <p>Add or remove folders and file patterns. Changes take effect on the next scan.</p>
51
+ </div>
52
+ </div>
53
+ </section>
54
+
55
+ <!-- Data & Privacy -->
56
+ <section class="mb-8 pt-8 border-t border-gray-200">
57
+ <h2 class="text-lg font-semibold mb-3">Data &amp; privacy</h2>
58
+ <p class="text-sm text-gray-600 mb-2">All data stays on your machine. Nothing is sent anywhere.</p>
59
+ <ul class="space-y-2 text-sm text-gray-600">
60
+ <li class="flex gap-2"><span class="text-gray-400">&bull;</span>Snapshots and diffs are stored in a local SQLite database at <code class="font-mono text-xs">~/.dotmd/history.db</code></li>
61
+ <li class="flex gap-2"><span class="text-gray-400">&bull;</span>Configuration is stored at <code class="font-mono text-xs">~/.dotmd/config.yaml</code></li>
62
+ <li class="flex gap-2"><span class="text-gray-400">&bull;</span>To remove everything: <code class="font-mono text-xs">npm uninstall -g @mattli/dotmd && rm -rf ~/.dotmd</code></li>
63
+ </ul>
64
+ </section>
65
+
66
+ <!-- Feedback -->
67
+ <section class="pt-8 border-t border-gray-200">
68
+ <h2 class="text-lg font-semibold mb-3">Feedback</h2>
69
+ <p class="text-sm text-gray-600">Questions, suggestions, or bugs? <a href="https://github.com/mattli/dotmd/issues" class="text-blue-600 hover:underline" target="_blank" rel="noopener">Open an issue on GitHub</a>.</p>
70
+ </section>
71
+ </div>`;
72
+ }
73
+ //# sourceMappingURL=help.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"help.js","sourceRoot":"","sources":["../../src/dashboard/help.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,QAAQ;IACtB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAqEE,CAAC;AACZ,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../src/dashboard/layout.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,MAAM,CA8CtF"}
1
+ {"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../src/dashboard/layout.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,MAAM,CA+CtF"}
@@ -7,6 +7,7 @@ export function layout(title, content, options) {
7
7
  <a href="/timeline" class="text-gray-600 hover:text-gray-900">Timeline</a>
8
8
  <a href="/files" class="text-gray-600 hover:text-gray-900">Files</a>
9
9
  <a href="/settings" class="text-gray-600 hover:text-gray-900">Settings</a>
10
+ <a href="/help" class="text-gray-600 hover:text-gray-900">Help</a>
10
11
  <button
11
12
  onclick="fetch('/api/scan', { method: 'POST' }).then(() => location.reload())"
12
13
  class="ml-auto flex items-center gap-1.5 text-gray-400 hover:text-gray-700 transition-colors"
@@ -1 +1 @@
1
- {"version":3,"file":"layout.js","sourceRoot":"","sources":["../../src/dashboard/layout.ts"],"names":[],"mappings":"AAKA,MAAM,UAAU,MAAM,CAAC,KAAa,EAAE,OAAe,EAAE,OAAuB;IAC5E,MAAM,GAAG,GAAG,OAAO,EAAE,OAAO;QAC1B,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC;;;;;;;;;;;;;;;SAeG,CAAC;IAER,OAAO;;;;;WAKE,KAAK;;;;;;;IAOZ,OAAO,EAAE,OAAO,IAAI,EAAE;;;;;;;;IAQtB,GAAG;;MAED,OAAO;;;QAGL,CAAC;AACT,CAAC"}
1
+ {"version":3,"file":"layout.js","sourceRoot":"","sources":["../../src/dashboard/layout.ts"],"names":[],"mappings":"AAKA,MAAM,UAAU,MAAM,CAAC,KAAa,EAAE,OAAe,EAAE,OAAuB;IAC5E,MAAM,GAAG,GAAG,OAAO,EAAE,OAAO;QAC1B,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC;;;;;;;;;;;;;;;;SAgBG,CAAC;IAER,OAAO;;;;;WAKE,KAAK;;;;;;;IAOZ,OAAO,EAAE,OAAO,IAAI,EAAE;;;;;;;;IAQtB,GAAG;;MAED,OAAO;;;QAGL,CAAC;AACT,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import { Hono } from "hono";
2
2
  import Database from "better-sqlite3";
3
3
  export declare function createApp(db: Database.Database): Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
4
- export declare function startServer(port?: number): void;
4
+ export declare function startServer(port?: number, onAlreadyRunning?: () => void): void;
5
5
  //# sourceMappingURL=server.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/dashboard/server.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AA8BtC,wBAAgB,SAAS,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,8EA4U9C;AAED,wBAAgB,WAAW,CAAC,IAAI,GAAE,MAAa,GAAG,IAAI,CAWrD"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/dashboard/server.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAwBtC,wBAAgB,SAAS,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,8EAmT9C;AAED,wBAAgB,WAAW,CAAC,IAAI,GAAE,MAAa,EAAE,gBAAgB,CAAC,EAAE,MAAM,IAAI,GAAG,IAAI,CAmBpF"}
@@ -11,10 +11,9 @@ import { scanFiles, discoverFiles } from "../scanner/index.js";
11
11
  import { CONFIG_PATH, DEFAULT_CONFIG, SUGGESTED_ROOTS, SUGGESTED_PATTERNS, expandHome, } from "../config/defaults.js";
12
12
  import { layout } from "./layout.js";
13
13
  import { fileListPage, fileDetailPage, timelinePage } from "./views.js";
14
- import { setupFoldersPage, setupPatternsPage, setupPreviewPage, setupCompletePage, } from "./wizard.js";
15
- import { WIZARD_CLIENT_SCRIPT } from "./wizard-client.js";
16
- import { settingsPage } from "./settings.js";
14
+ import { setupPage } from "./settings.js";
17
15
  import { SETTINGS_CLIENT_SCRIPT } from "./settings-client.js";
16
+ import { helpPage } from "./help.js";
18
17
  export function createApp(db) {
19
18
  const app = new Hono();
20
19
  // Prevent back-forward cache so unseen indicators stay accurate
@@ -161,63 +160,42 @@ export function createApp(db) {
161
160
  }
162
161
  const editor = process.env.VISUAL || process.env.EDITOR;
163
162
  const [cmd, ...args] = editor ? editor.split(/\s+/) : ["open", "-t"];
164
- execFile(cmd, [...args, filePath], (err) => {
165
- if (err)
166
- console.error("Failed to open file:", err.message);
163
+ return new Promise((resolve) => {
164
+ execFile(cmd, [...args, filePath], (err) => {
165
+ if (err) {
166
+ const msg = err.code === "ENOENT"
167
+ ? `Editor "${cmd}" not found. Set $EDITOR to an installed editor.`
168
+ : `Failed to open file: ${err.message}`;
169
+ resolve(c.json({ error: msg }, 500));
170
+ }
171
+ else {
172
+ resolve(c.json({ success: true }));
173
+ }
174
+ });
167
175
  });
168
- return c.json({ success: true });
176
+ });
177
+ // Help page
178
+ app.get("/help", (c) => {
179
+ return c.html(layout("Help", helpPage()));
169
180
  });
170
181
  // Settings page
171
182
  app.get("/settings", (c) => {
172
183
  const config = loadConfig();
173
- const html = settingsPage(config, SUGGESTED_ROOTS, SUGGESTED_PATTERNS);
184
+ const existingSuggestedRoots = SUGGESTED_ROOTS.filter((r) => fs.existsSync(expandHome(r)));
185
+ const html = setupPage(config, existingSuggestedRoots, SUGGESTED_PATTERNS);
174
186
  return c.html(layout("Settings", html, { scripts: SETTINGS_CLIENT_SCRIPT }));
175
187
  });
176
- // --- Setup wizard pages ---
188
+ // Setup page (same UI as settings, but for first-time init)
177
189
  app.get("/setup", (c) => {
178
190
  const existingDirs = SUGGESTED_ROOTS.filter((r) => fs.existsSync(expandHome(r)));
179
- const html = setupFoldersPage(SUGGESTED_ROOTS, existingDirs);
180
- return c.html(layout("Setup", html, { hideNav: true, scripts: WIZARD_CLIENT_SCRIPT }));
181
- });
182
- app.get("/setup/step2", (c) => {
183
- const roots = c.req.queries("roots") ?? [];
184
- const html = setupPatternsPage(roots, SUGGESTED_PATTERNS, DEFAULT_CONFIG.patterns);
185
- return c.html(layout("Setup — Patterns", html, {
186
- hideNav: true,
187
- scripts: WIZARD_CLIENT_SCRIPT,
188
- }));
189
- });
190
- app.get("/setup/step3", (c) => {
191
- const roots = c.req.queries("roots") ?? [];
192
- const patterns = c.req.queries("patterns") ?? [];
193
- const tempConfig = {
194
- scan_roots: roots,
195
- patterns,
191
+ // For init, start with existing dirs as roots but no patterns selected
192
+ const initConfig = {
193
+ scan_roots: existingDirs,
194
+ patterns: [],
196
195
  exclude: DEFAULT_CONFIG.exclude,
197
196
  };
198
- const filePaths = discoverFiles(tempConfig);
199
- const expandedRoots = roots.map((r) => ({ original: r, expanded: expandHome(r) }));
200
- expandedRoots.sort((a, b) => b.expanded.length - a.expanded.length);
201
- const files = filePaths.map((p) => {
202
- let root = "Other";
203
- for (const r of expandedRoots) {
204
- if (p.startsWith(r.expanded + "/") || p === r.expanded) {
205
- root = r.original;
206
- break;
207
- }
208
- }
209
- return { path: p, category: categorizeFile(p), root };
210
- });
211
- const html = setupPreviewPage(files, roots, patterns);
212
- return c.html(layout("Setup — Preview", html, {
213
- hideNav: true,
214
- scripts: WIZARD_CLIENT_SCRIPT,
215
- }));
216
- });
217
- app.get("/setup/complete", (c) => {
218
- const count = parseInt(c.req.query("count") || "0", 10);
219
- const html = setupCompletePage(count);
220
- return c.html(layout("Setup Complete", html, { hideNav: true }));
197
+ const html = setupPage(initConfig, existingDirs, SUGGESTED_PATTERNS, { isInit: true });
198
+ return c.html(layout("Setup", html, { hideNav: true, scripts: SETTINGS_CLIENT_SCRIPT }));
221
199
  });
222
200
  // --- Setup wizard API ---
223
201
  app.get("/api/setup/browse", (c) => {
@@ -293,13 +271,23 @@ export function createApp(db) {
293
271
  });
294
272
  return app;
295
273
  }
296
- export function startServer(port = 3333) {
274
+ export function startServer(port = 3333, onAlreadyRunning) {
297
275
  const db = getDb();
298
276
  // Run a scan on startup
299
277
  const config = loadConfig();
300
278
  scanFiles(db, config);
301
279
  const app = createApp(db);
280
+ const server = serve({ fetch: app.fetch, port });
281
+ server.on("error", (err) => {
282
+ if (err.code === "EADDRINUSE") {
283
+ console.log(`dotmd is already running at http://localhost:${port}`);
284
+ if (onAlreadyRunning)
285
+ onAlreadyRunning();
286
+ }
287
+ else {
288
+ throw err;
289
+ }
290
+ });
302
291
  console.log(`dotmd dashboard running at http://localhost:${port}`);
303
- serve({ fetch: app.fetch, port });
304
292
  }
305
293
  //# sourceMappingURL=server.js.map