lemming-cli 0.1.0__py3-none-any.whl

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 (94) hide show
  1. lemming/__init__.py +1 -0
  2. lemming/api/__init__.py +5 -0
  3. lemming/api/auth.py +34 -0
  4. lemming/api/auth_test.py +40 -0
  5. lemming/api/config.py +85 -0
  6. lemming/api/config_test.py +84 -0
  7. lemming/api/conftest.py +204 -0
  8. lemming/api/context.py +39 -0
  9. lemming/api/context_test.py +62 -0
  10. lemming/api/directories.py +57 -0
  11. lemming/api/directories_test.py +94 -0
  12. lemming/api/files.py +116 -0
  13. lemming/api/files_test.py +163 -0
  14. lemming/api/hooks.py +15 -0
  15. lemming/api/hooks_test.py +33 -0
  16. lemming/api/logging.py +16 -0
  17. lemming/api/logging_test.py +59 -0
  18. lemming/api/loop.py +45 -0
  19. lemming/api/loop_test.py +58 -0
  20. lemming/api/main.py +53 -0
  21. lemming/api/main_test.py +30 -0
  22. lemming/api/tasks.py +170 -0
  23. lemming/api/tasks_test.py +426 -0
  24. lemming/api.py +5 -0
  25. lemming/api_test.py +7 -0
  26. lemming/cli/__init__.py +12 -0
  27. lemming/cli/config.py +67 -0
  28. lemming/cli/config_test.py +50 -0
  29. lemming/cli/context.py +40 -0
  30. lemming/cli/context_test.py +49 -0
  31. lemming/cli/hooks.py +147 -0
  32. lemming/cli/hooks_test.py +50 -0
  33. lemming/cli/main.py +42 -0
  34. lemming/cli/main_test.py +21 -0
  35. lemming/cli/operations.py +226 -0
  36. lemming/cli/operations_test.py +44 -0
  37. lemming/cli/progress.py +54 -0
  38. lemming/cli/progress_test.py +40 -0
  39. lemming/cli/readability_cli.py +28 -0
  40. lemming/cli/readability_cli_test.py +57 -0
  41. lemming/cli/tasks.py +529 -0
  42. lemming/cli/tasks_test.py +168 -0
  43. lemming/cli.py +5 -0
  44. lemming/cli_test.py +22 -0
  45. lemming/conftest.py +13 -0
  46. lemming/hooks.py +145 -0
  47. lemming/hooks_test.py +180 -0
  48. lemming/integration_test.py +299 -0
  49. lemming/main.py +6 -0
  50. lemming/main_test.py +44 -0
  51. lemming/models.py +88 -0
  52. lemming/models_test.py +91 -0
  53. lemming/orchestrator.py +407 -0
  54. lemming/orchestrator_test.py +468 -0
  55. lemming/paths.py +245 -0
  56. lemming/paths_test.py +186 -0
  57. lemming/persistence.py +179 -0
  58. lemming/persistence_test.py +150 -0
  59. lemming/prompts/hooks/readability.md +42 -0
  60. lemming/prompts/hooks/roadmap.md +61 -0
  61. lemming/prompts/hooks/testing.md +44 -0
  62. lemming/prompts/taskrunner.md +69 -0
  63. lemming/prompts.py +354 -0
  64. lemming/prompts_test.py +421 -0
  65. lemming/providers.py +190 -0
  66. lemming/providers_test.py +73 -0
  67. lemming/runner.py +327 -0
  68. lemming/runner_test.py +378 -0
  69. lemming/tasks/__init__.py +75 -0
  70. lemming/tasks/lifecycle.py +331 -0
  71. lemming/tasks/lifecycle_test.py +312 -0
  72. lemming/tasks/operations.py +258 -0
  73. lemming/tasks/operations_test.py +172 -0
  74. lemming/tasks/progress.py +29 -0
  75. lemming/tasks/progress_test.py +22 -0
  76. lemming/tasks/queries.py +128 -0
  77. lemming/tasks/queries_test.py +233 -0
  78. lemming/web/dashboard.spec.js +350 -0
  79. lemming/web/dashboard.test.js +998 -0
  80. lemming/web/favicon.js +40 -0
  81. lemming/web/favicon.spec.js +242 -0
  82. lemming/web/files.html +375 -0
  83. lemming/web/files.spec.js +97 -0
  84. lemming/web/index.html +983 -0
  85. lemming/web/index.js +753 -0
  86. lemming/web/logs.html +358 -0
  87. lemming/web/logs.test.js +195 -0
  88. lemming/web/mancha.js +58 -0
  89. lemming/web/screenshots.spec.js +328 -0
  90. lemming_cli-0.1.0.dist-info/METADATA +314 -0
  91. lemming_cli-0.1.0.dist-info/RECORD +94 -0
  92. lemming_cli-0.1.0.dist-info/WHEEL +4 -0
  93. lemming_cli-0.1.0.dist-info/entry_points.txt +2 -0
  94. lemming_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
lemming/web/index.html ADDED
@@ -0,0 +1,983 @@
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>Lemming</title>
7
+ <link
8
+ rel="icon"
9
+ href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🐹</text></svg>"
10
+ />
11
+ <style id="mancha-cloak">
12
+ html {
13
+ background-color: #f9fafb; /* Gray 50 */
14
+ }
15
+ @media (prefers-color-scheme: dark) {
16
+ html {
17
+ background-color: #1f2937; /* Gray 800 */
18
+ }
19
+ }
20
+ body {
21
+ opacity: 0 !important;
22
+ }
23
+ </style>
24
+ <style>
25
+ /* Loading indicator visible during cloaking */
26
+ html.mancha-loading::after {
27
+ content: 'Loading Lemming...';
28
+ position: fixed;
29
+ inset: 0;
30
+ display: flex;
31
+ align-items: center;
32
+ justify-content: center;
33
+ font-family: ui-sans-serif, system-ui, sans-serif;
34
+ font-size: 1.25rem;
35
+ font-weight: 600;
36
+ color: #4f46e5; /* Indigo 600 */
37
+ z-index: 9999;
38
+ animation: mancha-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
39
+ }
40
+
41
+ @media (prefers-color-scheme: dark) {
42
+ html.mancha-loading::after {
43
+ color: #818cf8; /* Indigo 400 */
44
+ }
45
+ }
46
+
47
+ @keyframes mancha-pulse {
48
+ 0%,
49
+ 100% {
50
+ opacity: 1;
51
+ }
52
+ 50% {
53
+ opacity: 0.5;
54
+ }
55
+ }
56
+ </style>
57
+ <script src="/static/mancha.js" css="utils"></script>
58
+ <script src="/static/favicon.js"></script>
59
+ <script src="/static/index.js" defer></script>
60
+ </head>
61
+ <body
62
+ class="bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 font-sans min-h-screen flex flex-col"
63
+ >
64
+ <div class="max-w-3xl mx-auto p-4 flex-grow w-full flex flex-col">
65
+ <div :show="loading" class="flex-grow flex items-center justify-center">
66
+ <span class="text-gray-400 text-sm animate-pulse">Loading...</span>
67
+ </div>
68
+ <header :show="!loading" class="mb-4 shrink-0" role="banner">
69
+ <h1
70
+ class="text-2xl font-bold text-indigo-600 dark:text-indigo-400"
71
+ title="Lemming Task Runner"
72
+ >
73
+ Lemming Task Runner
74
+ </h1>
75
+ </header>
76
+
77
+ <!-- Runner Metadata Card -->
78
+ <section
79
+ :show="!loading"
80
+ class="mb-4 bg-white dark:bg-gray-900 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden shrink-0"
81
+ aria-labelledby="metadata-heading"
82
+ >
83
+ <div
84
+ class="py-2 px-4 border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900 flex justify-between items-center"
85
+ >
86
+ <h2
87
+ id="metadata-heading"
88
+ class="text-xs font-semibold text-gray-500 uppercase tracking-wider"
89
+ >
90
+ Runner Metadata
91
+ </h2>
92
+ <div class="flex items-center gap-3">
93
+ <button
94
+ type="submit"
95
+ form="run-form"
96
+ :prop:disabled="loopRunning"
97
+ :attr:class="'px-3 py-1 transition duration-200 border-none rounded-md text-xs font-medium whitespace-nowrap ' + (loopRunning ? 'bg-gray-400 cursor-not-allowed text-gray-200' : 'bg-indigo-600 text-white hover:bg-indigo-700')"
98
+ :attr:title="loopRunning ? 'Tasks are already executing' : 'Execute autonomous tasks'"
99
+ aria-label="Execute Tasks"
100
+ >
101
+ {{ loopRunning ? 'Executing...' : 'Execute Tasks' }}
102
+ </button>
103
+ </div>
104
+ </div>
105
+ <div class="p-4 space-y-4">
106
+ <form
107
+ class="flex flex-wrap gap-6 items-start"
108
+ :on:submit.prevent="runLemming()"
109
+ id="run-form"
110
+ aria-label="Run autonomous loop"
111
+ >
112
+ <div class="flex-grow min-w-0 md:min-w-[200px]">
113
+ <div class="flex items-center gap-1 mb-1">
114
+ <label class="text-xs font-semibold text-gray-400 uppercase"
115
+ >Project Directory</label
116
+ >
117
+ <a
118
+ :attr:href="'/files/' + ($$project || '')"
119
+ target="_blank"
120
+ class="p-0 text-gray-400 hover:text-indigo-600 shrink-0 transition duration-200 inline-flex items-center"
121
+ title="Browse files"
122
+ >
123
+ <svg
124
+ xmlns="http://www.w3.org/2000/svg"
125
+ class="h-3 w-3"
126
+ fill="none"
127
+ viewBox="0 0 24 24"
128
+ stroke="currentColor"
129
+ >
130
+ <path
131
+ stroke-linecap="round"
132
+ stroke-linejoin="round"
133
+ stroke-width="2"
134
+ d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"
135
+ />
136
+ </svg>
137
+ </a>
138
+ <button
139
+ type="button"
140
+ :on:click="openFolderPicker()"
141
+ class="p-0 text-gray-400 hover:text-indigo-600 bg-transparent border-none cursor-pointer shrink-0 transition duration-200 inline-flex items-center"
142
+ title="Switch project"
143
+ >
144
+ <svg
145
+ xmlns="http://www.w3.org/2000/svg"
146
+ class="h-3 w-3"
147
+ fill="none"
148
+ viewBox="0 0 24 24"
149
+ stroke="currentColor"
150
+ >
151
+ <path
152
+ stroke-linecap="round"
153
+ stroke-linejoin="round"
154
+ stroke-width="2"
155
+ d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
156
+ />
157
+ </svg>
158
+ </button>
159
+ </div>
160
+ <span class="block text-sm font-mono truncate" :attr:title="cwd"
161
+ >{{ cwd }}</span
162
+ >
163
+ </div>
164
+ <div class="flex-1 min-w-[200px]">
165
+ <label
166
+ class="block text-xs font-semibold text-gray-400 uppercase mb-1"
167
+ >Task Summary</label
168
+ >
169
+ <div
170
+ class="text-sm font-medium text-gray-700 dark:text-gray-300 gap-x-2 flex flex-wrap"
171
+ >
172
+ <span :show="tasks.length === 0" class="text-gray-400"
173
+ >No tasks</span
174
+ >
175
+ <span
176
+ :show="runningCount > 0"
177
+ class="text-blue-600 dark:text-blue-400"
178
+ >{{ runningCount }} running</span
179
+ >
180
+ <span :show="pendingCount > 0" class="text-gray-500"
181
+ >{{ pendingCount }} pending</span
182
+ >
183
+ <span
184
+ :show="completedCount > 0"
185
+ class="text-green-600 dark:text-green-400"
186
+ >{{ completedCount }} done</span
187
+ >
188
+ <span
189
+ :show="failedCount > 0"
190
+ class="text-red-600 dark:text-red-400"
191
+ >{{ failedCount }} failed</span
192
+ >
193
+ </div>
194
+ </div>
195
+ <!-- Runner Command -->
196
+ <div class="flex-1 min-w-[180px]">
197
+ <label
198
+ class="block text-xs font-semibold text-gray-400 uppercase mb-1"
199
+ >Runner Command</label
200
+ >
201
+ <input
202
+ type="text"
203
+ list="runner-options"
204
+ :bind="selectedRunner"
205
+ :on:change="saveRunnerPreference()"
206
+ placeholder="Runner command..."
207
+ title="Runner command to use for the autonomous loop"
208
+ aria-label="Runner command"
209
+ class="px-2 py-1 border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded-md text-sm focus:ring-indigo-500 focus:border-indigo-500 w-full"
210
+ />
211
+ <datalist id="runner-options">
212
+ <option :for="r in runners" :key="r" :attr:value="r"></option>
213
+ </datalist>
214
+ </div>
215
+ <!-- Retries -->
216
+ <div class="w-24 shrink-0">
217
+ <label
218
+ class="block text-xs font-semibold text-gray-400 uppercase mb-1"
219
+ >Retries</label
220
+ >
221
+ <input
222
+ type="number"
223
+ min="1"
224
+ :bind="retries"
225
+ :on:change="saveRetriesPreference()"
226
+ placeholder="Max"
227
+ title="Maximum number of retries per task"
228
+ aria-label="Retries"
229
+ class="px-2 py-1 border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded-md text-sm focus:ring-indigo-500 focus:border-indigo-500 w-full"
230
+ />
231
+ </div>
232
+ <!-- Time Limit -->
233
+ <div class="w-28 shrink-0">
234
+ <label
235
+ class="block text-xs font-semibold text-gray-400 uppercase mb-1"
236
+ >Time Limit</label
237
+ >
238
+ <select
239
+ :bind="timeLimit"
240
+ :on:change="saveTimeLimitPreference()"
241
+ title="Max execution time per task"
242
+ aria-label="Time limit"
243
+ class="px-2 py-1 border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded-md text-sm focus:ring-indigo-500 focus:border-indigo-500 w-full"
244
+ >
245
+ <option value="0">None</option>
246
+ <option value="15">15m</option>
247
+ <option value="30">30m</option>
248
+ <option value="60">1h</option>
249
+ <option value="120">2h</option>
250
+ <option value="240">4h</option>
251
+ </select>
252
+ </div>
253
+ </form>
254
+
255
+ <div class="pt-2 border-t border-gray-100 dark:border-gray-700">
256
+ <div class="flex items-baseline justify-between mb-2">
257
+ <label class="block text-xs font-semibold text-gray-400 uppercase"
258
+ >Orchestrator Hooks</label
259
+ >
260
+ <button
261
+ :show="config.hooks !== null"
262
+ type="button"
263
+ role="button"
264
+ :on:click="resetHooks()"
265
+ class="text-[10px] font-semibold text-indigo-600 hover:text-indigo-800 uppercase tracking-wider bg-transparent border-none cursor-pointer p-0 transition duration-200"
266
+ title="Reset hooks to default (all available)"
267
+ aria-label="Reset hooks"
268
+ >
269
+ Reset
270
+ </button>
271
+ </div>
272
+ <div class="flex flex-wrap items-center gap-4">
273
+ <label
274
+ :for="hook in availableHooks"
275
+ :key="hook"
276
+ class="flex items-center gap-2 cursor-pointer shrink-0"
277
+ :attr:title="'Run the ' + hook + ' orchestrator hook after each task'"
278
+ >
279
+ <input
280
+ type="checkbox"
281
+ :prop:checked="config.hooks === null ? true : config.hooks.includes(hook)"
282
+ :on:change="toggleHook(hook)"
283
+ class="rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 h-4 w-4"
284
+ :attr:aria-label="'Enable ' + hook + ' hook'"
285
+ />
286
+ <span
287
+ class="text-sm font-medium text-gray-700 dark:text-gray-300"
288
+ >{{ hook }}</span
289
+ >
290
+ </label>
291
+ </div>
292
+ </div>
293
+
294
+ <div class="pt-2 border-t border-gray-100 dark:border-gray-700">
295
+ <label
296
+ class="block text-xs font-semibold text-gray-400 uppercase mb-2"
297
+ >Environment Overrides</label
298
+ >
299
+ <div class="space-y-2">
300
+ <div
301
+ :for="override in envOverrides"
302
+ :key="override.id"
303
+ class="flex gap-2"
304
+ >
305
+ <input
306
+ type="text"
307
+ :bind="override.key"
308
+ :on:input="saveEnvOverrides()"
309
+ placeholder="KEY (e.g. OPENAI_API_KEY)"
310
+ class="flex-1 px-2 py-1 border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded-md text-sm font-mono focus:ring-indigo-500 focus:border-indigo-500"
311
+ />
312
+ <input
313
+ type="text"
314
+ :bind="override.value"
315
+ :on:input="saveEnvOverrides()"
316
+ placeholder="VALUE"
317
+ class="flex-1 px-2 py-1 border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded-md text-sm font-mono focus:ring-indigo-500 focus:border-indigo-500"
318
+ />
319
+ <button
320
+ type="button"
321
+ role="button"
322
+ :on:click="removeEnvOverride($index)"
323
+ class="px-2 py-1 text-red-500 hover:bg-red-50 dark:hover:bg-red-900/30 transition duration-200 rounded-md border-none bg-transparent cursor-pointer font-bold"
324
+ title="Remove override"
325
+ aria-label="Remove override"
326
+ >
327
+ &times;
328
+ </button>
329
+ </div>
330
+ <button
331
+ type="button"
332
+ role="button"
333
+ :on:click="addEnvOverride()"
334
+ class="text-xs font-semibold text-indigo-600 hover:text-indigo-800 uppercase tracking-wider bg-transparent border-none cursor-pointer p-0 transition duration-200"
335
+ title="Add new environment variable override"
336
+ aria-label="Add override"
337
+ >
338
+ + Add Override
339
+ </button>
340
+ </div>
341
+ </div>
342
+
343
+ <div>
344
+ <label
345
+ class="block text-xs font-semibold text-gray-400 uppercase mb-1"
346
+ >Add New Task</label
347
+ >
348
+ <form
349
+ class="flex gap-2"
350
+ :on:submit.prevent="addTask()"
351
+ aria-label="Add new task"
352
+ >
353
+ <input
354
+ type="text"
355
+ :bind="newTask"
356
+ placeholder="Task description..."
357
+ title="Enter task description"
358
+ aria-label="New task description"
359
+ class="flex-1 px-2 py-1 border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded-md text-sm focus:ring-indigo-500 focus:border-indigo-500"
360
+ />
361
+ <button
362
+ type="submit"
363
+ role="button"
364
+ :prop:disabled="!newTask || !newTask.trim()"
365
+ :attr:class="'px-3 py-1 transition duration-200 border-none rounded-md text-sm font-medium whitespace-nowrap ' + (!newTask || !newTask.trim() ? 'bg-gray-400 cursor-not-allowed text-gray-200' : 'bg-indigo-600 text-white hover:bg-indigo-700')"
366
+ title="Add new task"
367
+ aria-label="Add task"
368
+ >
369
+ Add Task
370
+ </button>
371
+ </form>
372
+ </div>
373
+ </div>
374
+ </section>
375
+
376
+ <section
377
+ :show="!loading"
378
+ class="mb-4 bg-white dark:bg-gray-900 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden shrink-0"
379
+ aria-labelledby="context-heading"
380
+ >
381
+ <div
382
+ class="py-2 px-4 border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900"
383
+ >
384
+ <h2
385
+ id="context-heading"
386
+ class="text-xs font-semibold text-gray-500 uppercase tracking-wider"
387
+ >
388
+ Project Context
389
+ </h2>
390
+ </div>
391
+ <div class="p-4">
392
+ <textarea
393
+ :bind="context"
394
+ :on:input="updateContext()"
395
+ class="w-full h-24 p-2 border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded-md focus:ring-indigo-500 focus:border-indigo-500 font-mono text-sm"
396
+ placeholder="Enter project guidelines and context..."
397
+ title="Project Context and Guidelines"
398
+ aria-label="Project context and guidelines"
399
+ ></textarea>
400
+ </div>
401
+ </section>
402
+
403
+ <section
404
+ :show="!loading"
405
+ class="bg-white dark:bg-gray-900 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden flex flex-col grow min-h-0"
406
+ aria-labelledby="tasks-heading"
407
+ >
408
+ <div
409
+ class="py-2 px-4 border-b border-gray-200 dark:border-gray-700 flex justify-between items-center bg-gray-50 dark:bg-gray-900 shrink-0"
410
+ >
411
+ <div class="flex items-center gap-3 flex-grow">
412
+ <h2
413
+ id="tasks-heading"
414
+ class="text-xs font-semibold text-gray-500 uppercase tracking-wider shrink-0"
415
+ >
416
+ Task Queue
417
+ </h2>
418
+ <label
419
+ class="flex items-center gap-1 cursor-pointer shrink-0"
420
+ title="Hide or show completed tasks"
421
+ >
422
+ <input
423
+ type="checkbox"
424
+ :bind="hideCompleted"
425
+ :on:change="saveHideCompletedPreference()"
426
+ class="rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 h-3 w-3"
427
+ aria-label="Hide completed tasks"
428
+ />
429
+ <span
430
+ class="font-medium text-gray-500 uppercase tracking-wider text-10px"
431
+ >Hide Completed</span
432
+ >
433
+ </label>
434
+ </div>
435
+ <button
436
+ :show="completedCount > 0"
437
+ type="button"
438
+ role="button"
439
+ :on:click="deleteCompletedTasks()"
440
+ class="text-xs font-semibold text-red-500 hover:text-red-700 uppercase tracking-wider bg-transparent border-none cursor-pointer"
441
+ title="Delete all completed tasks"
442
+ aria-label="Delete all completed tasks"
443
+ >
444
+ Delete Completed
445
+ </button>
446
+ </div>
447
+
448
+ <div class="grow overflow-auto" role="list">
449
+ <div
450
+ :for="task in filteredTasks"
451
+ :key="task.id"
452
+ class="flex flex-col border-b border-gray-200 dark:border-gray-600"
453
+ role="listitem"
454
+ >
455
+ <div
456
+ class="pt-1 pb-2 px-4 flex items-center justify-between"
457
+ :attr:aria-label="'Task: ' + task.description"
458
+ :attr:title="'Task: ' + task.description"
459
+ >
460
+ <div
461
+ class="flex items-center gap-2 overflow-hidden flex-grow min-w-0"
462
+ >
463
+ <button
464
+ type="button"
465
+ role="button"
466
+ :on:click="expanded[task.id] = !expanded[task.id]"
467
+ class="p-1 text-gray-600 dark:text-gray-400 hover:text-indigo-600 hover:bg-gray-100 dark:hover:bg-gray-700 transition duration-200 rounded-md inline-flex items-center justify-center bg-transparent border-none cursor-pointer"
468
+ :attr:title="expanded[task.id] ? 'Hide details' : 'Show details'"
469
+ :attr:aria-label="expanded[task.id] ? 'Hide details' : 'Show details'"
470
+ :attr:aria-expanded="expanded[task.id]"
471
+ >
472
+ <svg
473
+ xmlns="http://www.w3.org/2000/svg"
474
+ :attr:class="'h-3 w-3 transition-transform '"
475
+ :attr:style="expanded[task.id] ? 'transform: rotate(180deg)' : ''"
476
+ fill="none"
477
+ viewBox="0 0 24 24"
478
+ stroke="currentColor"
479
+ aria-hidden="true"
480
+ role="img"
481
+ >
482
+ <path
483
+ stroke-linecap="round"
484
+ stroke-linejoin="round"
485
+ stroke-width="2"
486
+ d="M19 9l-7 7-7-7"
487
+ />
488
+ </svg>
489
+ </button>
490
+ <div class="overflow-hidden flex-grow min-w-0">
491
+ <p
492
+ :attr:class="'text-sm font-medium truncate ' + (task.status === 'completed' ? 'text-gray-400 line-through' : (task.status === 'cancelled' ? 'text-orange-600 line-through' : (task.status === 'in_progress' ? 'text-blue-600' : (task.attempts > 0 ? 'text-red-600' : ''))))"
493
+ :attr:title="task.description"
494
+ >
495
+ {{ task.description }}
496
+ </p>
497
+ <div class="flex items-center gap-2">
498
+ <span
499
+ role="status"
500
+ class="px-2 py-0.5 rounded-full text-xs font-semibold shrink-0 text-center min-w-20 inline-block"
501
+ :class="task.status === 'completed' ? 'bg-green-100 text-green-700' : (task.status === 'failed' ? 'bg-red-100 text-red-700' : (task.status === 'cancelled' ? 'bg-orange-100 text-orange-700' : (task.status === 'in_progress' ? 'bg-blue-100 text-blue-700 animate-pulse' : (task.attempts > 0 ? 'bg-red-100 text-red-700' : 'bg-gray-100 text-gray-500'))))"
502
+ :attr:aria-label="task.status === 'completed' ? 'Completed' : (task.status === 'failed' ? 'Failed' : (task.status === 'cancelled' ? 'Cancelled' : (task.status === 'in_progress' ? (task.requested_status ? 'Finalizing' : 'Running') : (task.attempts > 0 ? 'Failed' : 'Pending'))))"
503
+ :attr:title="task.status === 'completed' ? 'Status: Completed' : (task.status === 'failed' ? 'Status: Failed' : (task.status === 'cancelled' ? 'Status: Cancelled' : (task.status === 'in_progress' ? (task.requested_status ? 'Status: Finalizing (hooks running)' : 'Status: Running') : (task.attempts > 0 ? 'Status: Failed' : 'Status: Pending'))))"
504
+ >
505
+ {{ task.status === 'completed' ? 'Completed' :
506
+ (task.status === 'failed' ? 'Failed' : (task.status ===
507
+ 'cancelled' ? 'Cancelled' : (task.status === 'in_progress'
508
+ ? (task.requested_status ? 'Finalizing' : 'Running') :
509
+ (task.attempts > 0 ? 'Failed' : 'Pending')))) }}
510
+ </span>
511
+ <p
512
+ class="text-gray-400 font-mono uppercase text-10px flex items-center flex-wrap gap-1 whitespace-nowrap"
513
+ :attr:aria-label="'Task ID ' + task.id + ', ' + task.attempts + ' attempts'"
514
+ title="Task ID and number of attempts"
515
+ >
516
+ <span class="inline-flex items-center gap-1">
517
+ {{ task.id }}
518
+ <button
519
+ type="button"
520
+ role="button"
521
+ :on:click="copyToClipboard(task.id)"
522
+ class="p-0.5 text-gray-400 hover:text-indigo-600 hover:bg-gray-100 transition duration-200 rounded-md inline-flex items-center justify-center bg-transparent border-none cursor-pointer"
523
+ title="Copy Task ID"
524
+ aria-label="Copy Task ID"
525
+ >
526
+ <svg
527
+ xmlns="http://www.w3.org/2000/svg"
528
+ class="h-2.5 w-2.5"
529
+ fill="none"
530
+ viewBox="0 0 24 24"
531
+ stroke="currentColor"
532
+ >
533
+ <path
534
+ stroke-linecap="round"
535
+ stroke-linejoin="round"
536
+ stroke-width="2"
537
+ d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"
538
+ />
539
+ </svg>
540
+ </button>
541
+ </span>
542
+ <span>&middot; Attempts: {{ task.attempts }}</span>
543
+ <span
544
+ :if="task.parent"
545
+ :attr:title="'Parent Task: ' + (getParent(task.parent)?.description || task.parent)"
546
+ >
547
+ &middot; Parent: {{ task.parent }}
548
+ </span>
549
+ </p>
550
+ </div>
551
+ </div>
552
+ </div>
553
+ <div class="flex gap-1 shrink-0">
554
+ <button
555
+ type="button"
556
+ role="button"
557
+ :on:click="openTaskActionModal(task.id)"
558
+ class="p-1 text-gray-600 dark:text-gray-400 hover:text-indigo-600 hover:bg-gray-100 dark:hover:bg-gray-700 transition duration-200 rounded-md inline-flex items-center justify-center bg-transparent border-none cursor-pointer"
559
+ title="Task Actions"
560
+ aria-label="Task Actions"
561
+ >
562
+ <svg
563
+ xmlns="http://www.w3.org/2000/svg"
564
+ class="h-4 w-4"
565
+ fill="none"
566
+ viewBox="0 0 24 24"
567
+ stroke="currentColor"
568
+ aria-hidden="true"
569
+ role="img"
570
+ >
571
+ <path
572
+ stroke-linecap="round"
573
+ stroke-linejoin="round"
574
+ stroke-width="2"
575
+ d="M12 5v.01M12 12v.01M12 19v.01"
576
+ />
577
+ </svg>
578
+ </button>
579
+ </div>
580
+ </div>
581
+
582
+ <!-- Task Details -->
583
+ <div
584
+ :show="expanded[task.id]"
585
+ class="px-12 pb-3 text-xs text-gray-600 dark:text-gray-400 border-t border-gray-100 dark:border-gray-700"
586
+ >
587
+ <div
588
+ class="mt-2 text-gray-700 dark:text-gray-300 font-normal break-words whitespace-pre-wrap"
589
+ :text="task.description"
590
+ ></div>
591
+ <div class="mt-2 flex flex-wrap gap-4">
592
+ <div :if="task.runner">
593
+ <span
594
+ class="font-semibold uppercase text-gray-400 block mb-1 text-9px"
595
+ >Custom Runner:</span
596
+ >
597
+ <span
598
+ class="font-mono bg-gray-200 dark:bg-gray-600 px-1 rounded"
599
+ >{{ task.runner }}</span
600
+ >
601
+ </div>
602
+ <div :if="task.pid">
603
+ <span
604
+ class="font-semibold uppercase text-gray-400 block mb-1 text-9px"
605
+ >Process PID:</span
606
+ >
607
+ <span class="font-mono">{{ task.pid }}</span>
608
+ </div>
609
+ <div :if="task.status === 'in_progress' && task.started_at">
610
+ <span
611
+ class="font-semibold uppercase text-gray-400 block mb-1 text-9px"
612
+ >Started At:</span
613
+ >
614
+ <span class="text-gray-700 dark:text-gray-300"
615
+ >{{ formatDate(task.started_at) }}</span
616
+ >
617
+ </div>
618
+ <div :if="task.status === 'completed' && task.completed_at">
619
+ <span
620
+ class="font-semibold uppercase text-gray-400 block mb-1 text-9px"
621
+ >Completed At:</span
622
+ >
623
+ <span class="text-gray-700 dark:text-gray-300"
624
+ >{{ formatDate(task.completed_at) }}</span
625
+ >
626
+ </div>
627
+ <div :if="task.run_time || task.status === 'in_progress'">
628
+ <span
629
+ class="font-semibold uppercase text-gray-400 block mb-1 text-9px"
630
+ >Run Time:</span
631
+ >
632
+ <span class="text-gray-700 dark:text-gray-300"
633
+ >{{ formatTaskRunTime(task) }}</span
634
+ >
635
+ </div>
636
+ <div :if="task.has_runner_log || task.status === 'in_progress'">
637
+ <span
638
+ class="font-semibold uppercase text-gray-400 block mb-1 text-9px"
639
+ >Logs:</span
640
+ >
641
+ <div class="flex items-center gap-3">
642
+ <a
643
+ :if="task.has_runner_log || task.status === 'in_progress'"
644
+ :attr:href="'/tasks/' + task.id + '/log?name=runner' + ($$project ? '&project=' + encodeURIComponent($$project) : '')"
645
+ target="_blank"
646
+ class="text-indigo-600 hover:text-indigo-800 font-medium transition duration-200"
647
+ title="View runner log (includes orchestrator hooks)"
648
+ >Runner</a
649
+ >
650
+ </div>
651
+ </div>
652
+ </div>
653
+ <div :if="task.parent" class="mt-2">
654
+ <span
655
+ class="font-semibold uppercase text-gray-400 block mb-1 text-9px"
656
+ >Parent Task:</span
657
+ >
658
+ <div class="flex items-center gap-2">
659
+ <span
660
+ :attr:class="'font-mono px-2 py-0.5 rounded text-10px font-medium ' + (getParent(task.parent)?.status === 'completed' ? 'bg-green-100 text-green-700' : (getParent(task.parent)?.status === 'cancelled' ? 'bg-orange-100 text-orange-700' : (getParent(task.parent)?.status === 'in_progress' ? 'bg-blue-100 text-blue-700 animate-pulse' : (getParent(task.parent)?.attempts > 0 ? 'bg-red-100 text-red-700' : 'bg-gray-100 text-gray-500'))))"
661
+ >{{ task.parent }}</span
662
+ >
663
+ <span
664
+ :if="getParent(task.parent)"
665
+ class="text-gray-500 italic truncate"
666
+ :attr:title="getParent(task.parent).description"
667
+ >{{ getParent(task.parent).description }}</span
668
+ >
669
+ </div>
670
+ </div>
671
+ <div :show="task.progress?.length > 0" class="mt-3">
672
+ <span
673
+ class="font-semibold uppercase text-gray-400 block mb-1 text-9px"
674
+ >Progress:</span
675
+ >
676
+ <ul class="list-disc ml-4 space-y-1 mb-2">
677
+ <li
678
+ :for="entry in task.progress"
679
+ :key="task.id + '-' + $index"
680
+ class="text-gray-700 dark:text-gray-300 whitespace-pre-wrap"
681
+ :text="entry"
682
+ ></li>
683
+ </ul>
684
+ </div>
685
+ </div>
686
+ </div>
687
+ <div
688
+ :show="tasks.length === 0"
689
+ class="p-8 text-center text-gray-500 text-sm"
690
+ role="status"
691
+ title="No tasks available"
692
+ >
693
+ No tasks yet. Add one to get started!
694
+ </div>
695
+ </div>
696
+ </section>
697
+ </div>
698
+
699
+ <div
700
+ class="fixed bottom-4 right-4 flex flex-col items-end gap-2 z-50 pointer-events-none"
701
+ role="log"
702
+ aria-live="polite"
703
+ >
704
+ <div
705
+ :for="toast in toasts"
706
+ :key="toast.id"
707
+ :attr:class="'px-4 py-2 rounded-lg shadow-lg text-white text-sm pointer-events-auto min-w-32 max-w-72 sm:max-w-80 flex justify-between items-center gap-3 transition-all duration-300 backdrop-blur ' + (toast.type === 'success' ? 'bg-green-700' : (toast.type === 'error' ? 'bg-red-700' : 'bg-indigo-700'))"
708
+ role="alert"
709
+ >
710
+ <span
711
+ class="truncate flex-1 min-w-0"
712
+ :attr:aria-label="toast.message"
713
+ :attr:title="toast.message"
714
+ >{{ toast.message }}</span
715
+ >
716
+ <button
717
+ type="button"
718
+ role="button"
719
+ :on:click="toasts = toasts.filter((t) => (t.id !== toast.id))"
720
+ class="text-white hover:text-gray-200 font-bold shrink-0"
721
+ title="Dismiss notification"
722
+ aria-label="Dismiss notification"
723
+ >
724
+ <span aria-hidden="true">&times;</span>
725
+ </button>
726
+ </div>
727
+ </div>
728
+ <dialog
729
+ id="task-action-modal"
730
+ class="p-6 rounded-xl shadow-2xl backdrop:bg-black/50 backdrop:backdrop-blur-sm border border-gray-100 dark:border-gray-700 w-full max-w-sm bg-white dark:bg-gray-900 dark:text-gray-100"
731
+ >
732
+ <h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
733
+ Task Action
734
+ </h3>
735
+ <p class="text-sm text-gray-500 dark:text-gray-400 mb-6">
736
+ Choose an action for this task.
737
+ </p>
738
+ <div class="flex flex-col gap-3">
739
+ <button
740
+ type="button"
741
+ :on:click="editTaskFromModal()"
742
+ :show="taskActionTargetStatus === 'pending'"
743
+ class="w-full px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 transition duration-200 cursor-pointer"
744
+ >
745
+ Edit Task
746
+ </button>
747
+ <button
748
+ type="button"
749
+ :on:click="confirmReopenTask()"
750
+ :show="taskActionTargetStatus === 'completed' || taskActionTargetStatus === 'failed' || taskActionTargetStatus === 'cancelled'"
751
+ class="w-full px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 transition duration-200 cursor-pointer"
752
+ >
753
+ Re-open Task
754
+ </button>
755
+ <button
756
+ type="button"
757
+ :on:click="confirmCancelTask()"
758
+ :show="taskActionTargetStatus === 'pending' || taskActionTargetStatus === 'in_progress'"
759
+ class="w-full px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 transition duration-200 cursor-pointer"
760
+ >
761
+ Cancel Task
762
+ </button>
763
+ <button
764
+ type="button"
765
+ :on:click="clearTaskFromModal()"
766
+ :show="taskActionTargetStatus !== 'completed' && taskActionTargetStatus !== 'in_progress' && taskActionTargetHasAttempts"
767
+ class="w-full px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 transition duration-200 cursor-pointer"
768
+ >
769
+ Clear Attempts
770
+ </button>
771
+ <button
772
+ type="button"
773
+ :on:click="confirmDeleteTask()"
774
+ :show="taskActionTargetStatus !== 'in_progress'"
775
+ class="w-full px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 transition duration-200 cursor-pointer"
776
+ >
777
+ Delete Task
778
+ </button>
779
+ <button
780
+ type="button"
781
+ :on:click="closeTaskActionModal()"
782
+ class="w-full px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 transition duration-200 cursor-pointer"
783
+ >
784
+ Dismiss
785
+ </button>
786
+ </div>
787
+ </dialog>
788
+ <dialog
789
+ id="edit-modal"
790
+ class="p-6 rounded-xl shadow-2xl backdrop:bg-black/50 backdrop:backdrop-blur-sm border border-gray-100 dark:border-gray-700 w-full max-w-lg bg-white dark:bg-gray-900 dark:text-gray-100"
791
+ >
792
+ <h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
793
+ Edit Task
794
+ </h3>
795
+ <form
796
+ method="dialog"
797
+ class="flex flex-col gap-4"
798
+ :on:submit.prevent="submitEditTask()"
799
+ >
800
+ <div>
801
+ <label
802
+ class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"
803
+ >Description</label
804
+ >
805
+ <textarea
806
+ :bind="editFormData.description"
807
+ class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
808
+ rows="3"
809
+ required
810
+ ></textarea>
811
+ </div>
812
+ <div>
813
+ <label
814
+ class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"
815
+ >Parent Task ID (optional)</label
816
+ >
817
+ <input
818
+ type="text"
819
+ :bind="editFormData.parent"
820
+ class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
821
+ />
822
+ </div>
823
+ <div class="mt-2 flex justify-end gap-3">
824
+ <button
825
+ type="button"
826
+ :on:click="closeEditModal()"
827
+ class="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
828
+ >
829
+ Cancel
830
+ </button>
831
+ <button
832
+ type="submit"
833
+ class="px-4 py-2 text-sm font-medium text-white bg-indigo-600 border border-transparent rounded-md shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
834
+ >
835
+ Save
836
+ </button>
837
+ </div>
838
+ </form>
839
+ </dialog>
840
+ <dialog
841
+ id="folder-picker-modal"
842
+ class="p-6 rounded-xl shadow-2xl backdrop:bg-black/50 backdrop:backdrop-blur-sm border border-gray-100 dark:border-gray-700 w-full max-w-lg bg-white dark:bg-gray-900 dark:text-gray-100"
843
+ >
844
+ <h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-3">
845
+ Select Project Directory
846
+ </h3>
847
+ <nav class="flex items-center gap-1 text-sm mb-3 flex-wrap">
848
+ <button
849
+ :for="crumb in folderPickerBreadcrumbs"
850
+ :key="crumb.path"
851
+ type="button"
852
+ :on:click="folderPickerNavigate(crumb.path)"
853
+ class="text-indigo-600 hover:text-indigo-800 dark:text-indigo-400 dark:hover:text-indigo-300 bg-transparent border-none cursor-pointer p-0 font-medium transition duration-200"
854
+ >
855
+ {{ crumb.name }}<span class="text-gray-400 ml-1">/</span>
856
+ </button>
857
+ </nav>
858
+ <div
859
+ class="border border-gray-200 dark:border-gray-600 rounded-md max-h-64 overflow-auto"
860
+ >
861
+ <button
862
+ :show="folderPickerPath"
863
+ type="button"
864
+ :on:click="folderPickerUp()"
865
+ class="w-full text-left px-3 py-2 text-sm hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors bg-transparent border-none border-b border-gray-200 dark:border-gray-600 cursor-pointer flex items-center gap-2 text-indigo-600 dark:text-indigo-400 font-medium"
866
+ >
867
+ <svg
868
+ xmlns="http://www.w3.org/2000/svg"
869
+ class="h-4 w-4"
870
+ fill="none"
871
+ viewBox="0 0 24 24"
872
+ stroke="currentColor"
873
+ >
874
+ <path
875
+ stroke-linecap="round"
876
+ stroke-linejoin="round"
877
+ stroke-width="2"
878
+ d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6"
879
+ />
880
+ </svg>
881
+ ..
882
+ </button>
883
+ <button
884
+ :for="dir in folderPickerDirs"
885
+ :key="dir.path"
886
+ type="button"
887
+ :on:click="folderPickerNavigate(dir.path)"
888
+ class="w-full text-left px-3 py-2 text-sm hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors bg-transparent border-none cursor-pointer flex items-center gap-2 text-gray-700 dark:text-gray-300"
889
+ >
890
+ <svg
891
+ xmlns="http://www.w3.org/2000/svg"
892
+ class="h-4 w-4 text-indigo-400 shrink-0"
893
+ fill="none"
894
+ viewBox="0 0 24 24"
895
+ stroke="currentColor"
896
+ >
897
+ <path
898
+ stroke-linecap="round"
899
+ stroke-linejoin="round"
900
+ stroke-width="2"
901
+ d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"
902
+ />
903
+ </svg>
904
+ {{ dir.name }}
905
+ </button>
906
+ <div
907
+ :show="!folderPickerLoading && folderPickerDirs.length === 0"
908
+ class="px-3 py-4 text-center text-gray-400 text-sm"
909
+ >
910
+ No subdirectories.
911
+ </div>
912
+ </div>
913
+ <div class="mt-4">
914
+ <button
915
+ :if="!showNewFolderInput"
916
+ type="button"
917
+ :on:click="startNewFolder()"
918
+ title="Create new folder"
919
+ class="text-xs text-indigo-600 hover:text-indigo-700 font-medium flex items-center gap-1 bg-transparent border-none cursor-pointer p-0"
920
+ >
921
+ <svg
922
+ xmlns="http://www.w3.org/2000/svg"
923
+ class="h-3 w-3"
924
+ fill="none"
925
+ viewBox="0 0 24 24"
926
+ stroke="currentColor"
927
+ >
928
+ <path
929
+ stroke-linecap="round"
930
+ stroke-linejoin="round"
931
+ stroke-width="2"
932
+ d="M12 4v16m8-8H4"
933
+ />
934
+ </svg>
935
+ New Folder
936
+ </button>
937
+ <form
938
+ :if="showNewFolderInput"
939
+ class="flex items-center gap-2"
940
+ :on:submit.prevent="createFolder()"
941
+ >
942
+ <input
943
+ type="text"
944
+ :bind="newFolderName"
945
+ placeholder="Folder name"
946
+ class="flex-grow px-2 py-1 text-xs border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded focus:ring-1 focus:ring-indigo-500 focus:outline-none"
947
+ required
948
+ :attr:autofocus="showNewFolderInput"
949
+ />
950
+ <button
951
+ type="submit"
952
+ class="px-2 py-1 text-xs font-medium text-white bg-indigo-600 rounded hover:bg-indigo-700"
953
+ >
954
+ Create
955
+ </button>
956
+ <button
957
+ type="button"
958
+ :on:click="showNewFolderInput = false"
959
+ class="px-2 py-1 text-xs font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-800 rounded hover:bg-gray-200 dark:hover:bg-gray-700 border-none"
960
+ >
961
+ Cancel
962
+ </button>
963
+ </form>
964
+ </div>
965
+ <div class="mt-4 flex justify-end gap-3">
966
+ <button
967
+ type="button"
968
+ :on:click="closeFolderPicker()"
969
+ class="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm hover:bg-gray-50 dark:hover:bg-gray-600 transition duration-200 cursor-pointer"
970
+ >
971
+ Cancel
972
+ </button>
973
+ <button
974
+ type="button"
975
+ :on:click="folderPickerSelect(folderPickerPath)"
976
+ class="px-4 py-2 text-sm font-medium text-white bg-indigo-600 border border-transparent rounded-md shadow-sm hover:bg-indigo-700 transition duration-200 cursor-pointer"
977
+ >
978
+ Select This Folder
979
+ </button>
980
+ </div>
981
+ </dialog>
982
+ </body>
983
+ </html>