idea-manager 2.0.0 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/.next/build-manifest.json +2 -2
  2. package/.next/routes-manifest.json +15 -0
  3. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  4. package/.next/server/app/_global-error.html +2 -2
  5. package/.next/server/app/_global-error.rsc +1 -1
  6. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  7. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  8. package/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  12. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  13. package/.next/server/app/_not-found.html +2 -2
  14. package/.next/server/app/_not-found.rsc +2 -2
  15. package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  16. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  17. package/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  18. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  19. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  20. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  21. package/.next/server/app/api/advisor-actions/route.js +15 -0
  22. package/.next/server/app/api/advisor-actions/route_client-reference-manifest.js +1 -0
  23. package/.next/server/app/api/archive/route.js +1 -130
  24. package/.next/server/app/api/archive/route_client-reference-manifest.js +1 -1
  25. package/.next/server/app/api/filesystem/route_client-reference-manifest.js +1 -1
  26. package/.next/server/app/api/filesystem/tree/route_client-reference-manifest.js +1 -1
  27. package/.next/server/app/api/global-advisor/route.js +22 -11
  28. package/.next/server/app/api/global-advisor/route_client-reference-manifest.js +1 -1
  29. package/.next/server/app/api/global-memo/route_client-reference-manifest.js +1 -1
  30. package/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
  31. package/.next/server/app/api/maintenance/route_client-reference-manifest.js +1 -1
  32. package/.next/server/app/api/projects/[id]/advisor/route.js +22 -11
  33. package/.next/server/app/api/projects/[id]/advisor/route_client-reference-manifest.js +1 -1
  34. package/.next/server/app/api/projects/[id]/apply-distribute/route.js +2 -8
  35. package/.next/server/app/api/projects/[id]/apply-distribute/route_client-reference-manifest.js +1 -1
  36. package/.next/server/app/api/projects/[id]/auto-distribute/route.js +126 -3
  37. package/.next/server/app/api/projects/[id]/auto-distribute/route_client-reference-manifest.js +1 -1
  38. package/.next/server/app/api/projects/[id]/brainstorm/route.js +124 -1
  39. package/.next/server/app/api/projects/[id]/brainstorm/route_client-reference-manifest.js +1 -1
  40. package/.next/server/app/api/projects/[id]/git-sync/route.js +124 -1
  41. package/.next/server/app/api/projects/[id]/git-sync/route_client-reference-manifest.js +1 -1
  42. package/.next/server/app/api/projects/[id]/route.js +124 -1
  43. package/.next/server/app/api/projects/[id]/route_client-reference-manifest.js +1 -1
  44. package/.next/server/app/api/projects/[id]/sub-projects/[subId]/route_client-reference-manifest.js +1 -1
  45. package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/chat/route.js +1 -7
  46. package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/chat/route_client-reference-manifest.js +1 -1
  47. package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/prompt/route_client-reference-manifest.js +1 -1
  48. package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/refine/route.js +2 -8
  49. package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/refine/route_client-reference-manifest.js +1 -1
  50. package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/route.js +1 -130
  51. package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/route_client-reference-manifest.js +1 -1
  52. package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/reorder/route.js +124 -0
  53. package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/reorder/route_client-reference-manifest.js +1 -0
  54. package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/route.js +1 -130
  55. package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/route_client-reference-manifest.js +1 -1
  56. package/.next/server/app/api/projects/[id]/sub-projects/route_client-reference-manifest.js +1 -1
  57. package/.next/server/app/api/projects/route.js +124 -1
  58. package/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  59. package/.next/server/app/api/search/route_client-reference-manifest.js +1 -1
  60. package/.next/server/app/api/sync/route_client-reference-manifest.js +1 -1
  61. package/.next/server/app/api/tasks/[taskId]/move/route.js +2 -131
  62. package/.next/server/app/api/tasks/[taskId]/move/route_client-reference-manifest.js +1 -1
  63. package/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  64. package/.next/server/app/api/version/route_client-reference-manifest.js +1 -1
  65. package/.next/server/app/index.html +2 -2
  66. package/.next/server/app/index.rsc +3 -3
  67. package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  68. package/.next/server/app/index.segments/_full.segment.rsc +3 -3
  69. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  70. package/.next/server/app/index.segments/_index.segment.rsc +2 -2
  71. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  72. package/.next/server/app/page.js +10 -10
  73. package/.next/server/app/page_client-reference-manifest.js +1 -1
  74. package/.next/server/app/projects/[id]/page_client-reference-manifest.js +1 -1
  75. package/.next/server/app-paths-manifest.json +13 -11
  76. package/.next/server/chunks/{117.js → 697.js} +8 -2
  77. package/.next/server/pages/404.html +2 -2
  78. package/.next/server/pages/500.html +2 -2
  79. package/.next/static/DgEYXBynAPZFgESajdpuy/_buildManifest.js +1 -0
  80. package/.next/static/chunks/app/_global-error/page-f051f234bea7bddd.js +1 -0
  81. package/.next/static/chunks/app/api/advisor-actions/route-f051f234bea7bddd.js +1 -0
  82. package/.next/static/chunks/app/api/archive/route-f051f234bea7bddd.js +1 -0
  83. package/.next/static/chunks/app/api/filesystem/route-f051f234bea7bddd.js +1 -0
  84. package/.next/static/chunks/app/api/filesystem/tree/route-f051f234bea7bddd.js +1 -0
  85. package/.next/static/chunks/app/api/global-advisor/route-f051f234bea7bddd.js +1 -0
  86. package/.next/static/chunks/app/api/global-memo/route-f051f234bea7bddd.js +1 -0
  87. package/.next/static/chunks/app/api/health/route-f051f234bea7bddd.js +1 -0
  88. package/.next/static/chunks/app/api/maintenance/route-f051f234bea7bddd.js +1 -0
  89. package/.next/static/chunks/app/api/projects/[id]/advisor/route-f051f234bea7bddd.js +1 -0
  90. package/.next/static/chunks/app/api/projects/[id]/apply-distribute/route-f051f234bea7bddd.js +1 -0
  91. package/.next/static/chunks/app/api/projects/[id]/auto-distribute/route-f051f234bea7bddd.js +1 -0
  92. package/.next/static/chunks/app/api/projects/[id]/brainstorm/route-f051f234bea7bddd.js +1 -0
  93. package/.next/static/chunks/app/api/projects/[id]/git-sync/route-f051f234bea7bddd.js +1 -0
  94. package/.next/static/chunks/app/api/projects/[id]/route-f051f234bea7bddd.js +1 -0
  95. package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/route-f051f234bea7bddd.js +1 -0
  96. package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/chat/route-f051f234bea7bddd.js +1 -0
  97. package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/prompt/route-f051f234bea7bddd.js +1 -0
  98. package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/refine/route-f051f234bea7bddd.js +1 -0
  99. package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/route-f051f234bea7bddd.js +1 -0
  100. package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/reorder/route-f051f234bea7bddd.js +1 -0
  101. package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/route-f051f234bea7bddd.js +1 -0
  102. package/.next/static/chunks/app/api/projects/[id]/sub-projects/route-f051f234bea7bddd.js +1 -0
  103. package/.next/static/chunks/app/api/projects/route-f051f234bea7bddd.js +1 -0
  104. package/.next/static/chunks/app/api/search/route-f051f234bea7bddd.js +1 -0
  105. package/.next/static/chunks/app/api/sync/route-f051f234bea7bddd.js +1 -0
  106. package/.next/static/chunks/app/api/tasks/[taskId]/move/route-f051f234bea7bddd.js +1 -0
  107. package/.next/static/chunks/app/api/update/route-f051f234bea7bddd.js +1 -0
  108. package/.next/static/chunks/app/api/version/route-f051f234bea7bddd.js +1 -0
  109. package/.next/static/chunks/app/page-517beed417b9d63a.js +28 -0
  110. package/.next/static/chunks/next/dist/client/components/builtin/app-error-f051f234bea7bddd.js +1 -0
  111. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-f051f234bea7bddd.js +1 -0
  112. package/.next/static/chunks/next/dist/client/components/builtin/not-found-f051f234bea7bddd.js +1 -0
  113. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-f051f234bea7bddd.js +1 -0
  114. package/.next/static/css/31b47c8edfe1a047.css +3 -0
  115. package/package.json +22 -8
  116. package/src/app/api/advisor-actions/route.ts +52 -0
  117. package/src/app/api/projects/[id]/sub-projects/[subId]/tasks/reorder/route.ts +24 -0
  118. package/src/components/advisor/ActionBlock.tsx +124 -0
  119. package/src/components/advisor/AdvisorChat.tsx +19 -9
  120. package/src/components/brainstorm/Editor.tsx +5 -13
  121. package/src/components/task/NoteEditor.tsx +137 -0
  122. package/src/components/task/ProjectTree.tsx +105 -57
  123. package/src/components/task/TaskDetail.tsx +93 -0
  124. package/src/components/ui/ShortcutOverlay.tsx +4 -0
  125. package/src/components/workspace/WorkspacePanel.tsx +69 -3
  126. package/src/lib/advisor-actions/parse.ts +59 -0
  127. package/src/lib/ai/global-context.ts +24 -4
  128. package/src/lib/ai/project-context.ts +22 -2
  129. package/src/types/advisor-actions.ts +25 -0
  130. package/.next/static/5I00mUqwYdcGFbs7ETC9d/_buildManifest.js +0 -1
  131. package/.next/static/chunks/app/_global-error/page-92496bc87f5c29a9.js +0 -1
  132. package/.next/static/chunks/app/api/archive/route-92496bc87f5c29a9.js +0 -1
  133. package/.next/static/chunks/app/api/filesystem/route-92496bc87f5c29a9.js +0 -1
  134. package/.next/static/chunks/app/api/filesystem/tree/route-92496bc87f5c29a9.js +0 -1
  135. package/.next/static/chunks/app/api/global-advisor/route-92496bc87f5c29a9.js +0 -1
  136. package/.next/static/chunks/app/api/global-memo/route-92496bc87f5c29a9.js +0 -1
  137. package/.next/static/chunks/app/api/health/route-92496bc87f5c29a9.js +0 -1
  138. package/.next/static/chunks/app/api/maintenance/route-92496bc87f5c29a9.js +0 -1
  139. package/.next/static/chunks/app/api/projects/[id]/advisor/route-92496bc87f5c29a9.js +0 -1
  140. package/.next/static/chunks/app/api/projects/[id]/apply-distribute/route-92496bc87f5c29a9.js +0 -1
  141. package/.next/static/chunks/app/api/projects/[id]/auto-distribute/route-92496bc87f5c29a9.js +0 -1
  142. package/.next/static/chunks/app/api/projects/[id]/brainstorm/route-92496bc87f5c29a9.js +0 -1
  143. package/.next/static/chunks/app/api/projects/[id]/git-sync/route-92496bc87f5c29a9.js +0 -1
  144. package/.next/static/chunks/app/api/projects/[id]/route-92496bc87f5c29a9.js +0 -1
  145. package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/route-92496bc87f5c29a9.js +0 -1
  146. package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/chat/route-92496bc87f5c29a9.js +0 -1
  147. package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/prompt/route-92496bc87f5c29a9.js +0 -1
  148. package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/refine/route-92496bc87f5c29a9.js +0 -1
  149. package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/route-92496bc87f5c29a9.js +0 -1
  150. package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/route-92496bc87f5c29a9.js +0 -1
  151. package/.next/static/chunks/app/api/projects/[id]/sub-projects/route-92496bc87f5c29a9.js +0 -1
  152. package/.next/static/chunks/app/api/projects/route-92496bc87f5c29a9.js +0 -1
  153. package/.next/static/chunks/app/api/search/route-92496bc87f5c29a9.js +0 -1
  154. package/.next/static/chunks/app/api/sync/route-92496bc87f5c29a9.js +0 -1
  155. package/.next/static/chunks/app/api/tasks/[taskId]/move/route-92496bc87f5c29a9.js +0 -1
  156. package/.next/static/chunks/app/api/update/route-92496bc87f5c29a9.js +0 -1
  157. package/.next/static/chunks/app/api/version/route-92496bc87f5c29a9.js +0 -1
  158. package/.next/static/chunks/app/page-b6580e09a4af9dff.js +0 -28
  159. package/.next/static/chunks/next/dist/client/components/builtin/app-error-92496bc87f5c29a9.js +0 -1
  160. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-92496bc87f5c29a9.js +0 -1
  161. package/.next/static/chunks/next/dist/client/components/builtin/not-found-92496bc87f5c29a9.js +0 -1
  162. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-92496bc87f5c29a9.js +0 -1
  163. package/.next/static/css/a57b4564ec9082ab.css +0 -3
  164. /package/.next/static/{5I00mUqwYdcGFbs7ETC9d → DgEYXBynAPZFgESajdpuy}/_ssgManifest.js +0 -0
@@ -50,6 +50,7 @@ export default function ProjectTree({
50
50
  onTodayToggle,
51
51
  onDeleteTask,
52
52
  onReorderSubs,
53
+ onReorderTasks,
53
54
  onAutoDistribute,
54
55
  chatStates,
55
56
  }: {
@@ -67,6 +68,7 @@ export default function ProjectTree({
67
68
  onTodayToggle: (taskId: string, isToday: boolean) => void;
68
69
  onDeleteTask: (taskId: string) => void;
69
70
  onReorderSubs?: (orderedIds: string[]) => void;
71
+ onReorderTasks?: (orderedIds: string[]) => void;
70
72
  onAutoDistribute?: () => void;
71
73
  chatStates?: Record<string, 'idle' | 'loading' | 'done'>;
72
74
  }) {
@@ -172,6 +174,7 @@ export default function ProjectTree({
172
174
  onStatusChange={onStatusChange}
173
175
  onTodayToggle={onTodayToggle}
174
176
  onDeleteTask={onDeleteTask}
177
+ onReorderTasks={onReorderTasks}
175
178
  onAddTask={handleAddTask}
176
179
  onSetAddingTaskFor={setAddingTaskFor}
177
180
  onSetNewTaskTitle={setNewTaskTitle}
@@ -201,6 +204,7 @@ function SortableSubProject({
201
204
  onStatusChange,
202
205
  onTodayToggle,
203
206
  onDeleteTask,
207
+ onReorderTasks,
204
208
  onAddTask,
205
209
  onSetAddingTaskFor,
206
210
  onSetNewTaskTitle,
@@ -221,6 +225,7 @@ function SortableSubProject({
221
225
  onStatusChange: (taskId: string, status: TaskStatus) => void;
222
226
  onTodayToggle: (taskId: string, isToday: boolean) => void;
223
227
  onDeleteTask: (taskId: string) => void;
228
+ onReorderTasks?: (orderedIds: string[]) => void;
224
229
  onAddTask: (subId: string) => void;
225
230
  onSetAddingTaskFor: (subId: string | null) => void;
226
231
  onSetNewTaskTitle: (title: string) => void;
@@ -229,6 +234,23 @@ function SortableSubProject({
229
234
  const [editing, setEditing] = useState(false);
230
235
  const [editValue, setEditValue] = useState(sp.name);
231
236
 
237
+ const taskSensors = useSensors(
238
+ useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),
239
+ useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),
240
+ );
241
+
242
+ const handleTaskDragEnd = (event: DragEndEvent) => {
243
+ const { active, over } = event;
244
+ if (!over || active.id === over.id || !onReorderTasks) return;
245
+ const oldIndex = subTasks.findIndex(t => t.id === active.id);
246
+ const newIndex = subTasks.findIndex(t => t.id === over.id);
247
+ if (oldIndex === -1 || newIndex === -1) return;
248
+ const newOrder = [...subTasks];
249
+ const [moved] = newOrder.splice(oldIndex, 1);
250
+ newOrder.splice(newIndex, 0, moved);
251
+ onReorderTasks(newOrder.map(t => t.id));
252
+ };
253
+
232
254
  const {
233
255
  attributes,
234
256
  listeners,
@@ -348,63 +370,22 @@ function SortableSubProject({
348
370
  No tasks
349
371
  </div>
350
372
  )}
351
- {subTasks.map((task) => (
352
- <div
353
- key={task.id}
354
- onClick={() => onSelectTask(task.id)}
355
- className={`group/task flex items-center gap-1.5 pl-4 pr-2 py-1.5 cursor-pointer transition-colors text-sm border-l-2 ${
356
- selectedTaskId === task.id
357
- ? 'bg-card-hover border-l-primary'
358
- : 'border-l-transparent hover:bg-card-hover/50'
359
- }`}
360
- >
361
- <button
362
- onClick={(e) => {
363
- e.stopPropagation();
364
- const nextStatus = getNextStatus(task.status);
365
- onStatusChange(task.id, nextStatus);
366
- }}
367
- className="flex-shrink-0 text-sm"
368
- title={`Status: ${task.status}`}
369
- >
370
- {statusIcon(task.status)}
371
- </button>
372
- <span className={`tree-priority-dot ${PRIORITY_COLORS[task.priority]}`} />
373
- <span className={`flex-1 truncate ${task.status === 'done' ? 'text-muted-foreground line-through' : ''}`}>
374
- {task.title}
375
- </span>
376
- {chatStates?.[task.id] === 'loading' && (
377
- <span className="flex-shrink-0 flex items-center gap-1 text-[10px] text-warning" title="AI 응답 대기 중">
378
- <span className="inline-block w-1.5 h-1.5 rounded-full bg-warning animate-pulse" />
379
- AI...
380
- </span>
381
- )}
382
- {chatStates?.[task.id] === 'done' && (
383
- <span className="flex-shrink-0 text-[10px] text-success" title="AI 응답 완료">
384
-
385
- </span>
386
- )}
387
- {task.is_today && (
388
- <button
389
- onClick={(e) => { e.stopPropagation(); onTodayToggle(task.id, false); }}
390
- className="text-xs flex-shrink-0 text-primary" title="Remove from today"
391
- >
392
- *
393
- </button>
394
- )}
395
- <button
396
- onClick={(e) => {
397
- e.stopPropagation();
398
- onDeleteTask(task.id);
399
- }}
400
- className="flex-shrink-0 text-muted-foreground/0 group-hover/task:text-muted-foreground
401
- hover:!text-destructive transition-colors text-xs px-0.5"
402
- title="Delete task"
403
- >
404
- ×
405
- </button>
406
- </div>
407
- ))}
373
+ <DndContext sensors={taskSensors} collisionDetection={closestCenter} onDragEnd={handleTaskDragEnd}>
374
+ <SortableContext items={subTasks.map(t => t.id)} strategy={verticalListSortingStrategy}>
375
+ {subTasks.map((task) => (
376
+ <SortableTask
377
+ key={task.id}
378
+ task={task}
379
+ isSelected={selectedTaskId === task.id}
380
+ chatState={chatStates?.[task.id]}
381
+ onSelect={() => onSelectTask(task.id)}
382
+ onStatusChange={onStatusChange}
383
+ onTodayToggle={onTodayToggle}
384
+ onDelete={() => onDeleteTask(task.id)}
385
+ />
386
+ ))}
387
+ </SortableContext>
388
+ </DndContext>
408
389
 
409
390
  {/* Add task input */}
410
391
  {addingTaskFor === sp.id ? (
@@ -455,6 +436,73 @@ function SortableSubProject({
455
436
  );
456
437
  }
457
438
 
439
+ function SortableTask({
440
+ task,
441
+ isSelected,
442
+ chatState,
443
+ onSelect,
444
+ onStatusChange,
445
+ onTodayToggle,
446
+ onDelete,
447
+ }: {
448
+ task: ITask;
449
+ isSelected: boolean;
450
+ chatState?: 'idle' | 'loading' | 'done';
451
+ onSelect: () => void;
452
+ onStatusChange: (taskId: string, status: TaskStatus) => void;
453
+ onTodayToggle: (taskId: string, isToday: boolean) => void;
454
+ onDelete: () => void;
455
+ }) {
456
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: task.id });
457
+ const style = { transform: CSS.Transform.toString(transform), transition, opacity: isDragging ? 0.5 : 1 };
458
+
459
+ return (
460
+ <div
461
+ ref={setNodeRef}
462
+ style={style}
463
+ onClick={onSelect}
464
+ className={`group/task flex items-center gap-1 pl-2 pr-2 py-1.5 cursor-pointer transition-colors text-sm border-l-2 ${
465
+ isSelected ? 'bg-card-hover border-l-primary' : 'border-l-transparent hover:bg-card-hover/50'
466
+ }`}
467
+ >
468
+ <span
469
+ {...attributes}
470
+ {...listeners}
471
+ className="w-3 h-4 flex items-center justify-center text-[10px] text-muted-foreground/30 hover:text-muted-foreground cursor-grab active:cursor-grabbing flex-shrink-0"
472
+ onClick={(e) => e.stopPropagation()}
473
+ >
474
+
475
+ </span>
476
+ <button
477
+ onClick={(e) => { e.stopPropagation(); onStatusChange(task.id, getNextStatus(task.status)); }}
478
+ className="flex-shrink-0 text-sm"
479
+ title={`Status: ${task.status}`}
480
+ >
481
+ {statusIcon(task.status)}
482
+ </button>
483
+ <span className={`tree-priority-dot ${PRIORITY_COLORS[task.priority]}`} />
484
+ <span className={`flex-1 truncate ${task.status === 'done' ? 'text-muted-foreground line-through' : ''}`}>
485
+ {task.title}
486
+ </span>
487
+ {chatState === 'loading' && (
488
+ <span className="flex-shrink-0 flex items-center gap-1 text-[10px] text-warning">
489
+ <span className="inline-block w-1.5 h-1.5 rounded-full bg-warning animate-pulse" />
490
+ </span>
491
+ )}
492
+ {chatState === 'done' && <span className="flex-shrink-0 text-[10px] text-success">✓</span>}
493
+ {task.is_today && (
494
+ <button onClick={(e) => { e.stopPropagation(); onTodayToggle(task.id, false); }} className="text-xs flex-shrink-0 text-primary">*</button>
495
+ )}
496
+ <button
497
+ onClick={(e) => { e.stopPropagation(); onDelete(); }}
498
+ className="flex-shrink-0 text-muted-foreground/0 group-hover/task:text-muted-foreground hover:!text-destructive transition-colors text-xs px-0.5"
499
+ >
500
+ ×
501
+ </button>
502
+ </div>
503
+ );
504
+ }
505
+
458
506
  function getNextStatus(current: TaskStatus): TaskStatus {
459
507
  const flow: TaskStatus[] = ['idea', 'doing', 'done'];
460
508
  const idx = flow.indexOf(current);
@@ -37,7 +37,10 @@ export default function TaskDetail({
37
37
  const [description, setDescription] = useState(task.description);
38
38
  const [editingTitle, setEditingTitle] = useState(false);
39
39
  const [copied, setCopied] = useState(false);
40
+ const [tagInput, setTagInput] = useState('');
41
+ const [showTagInput, setShowTagInput] = useState(false);
40
42
  const [chatOpen, setChatOpen] = useState(false);
43
+ const [focusMode, setFocusMode] = useState(false);
41
44
  const chatWasManuallyToggled = useRef(false);
42
45
 
43
46
  // Auto-open the chat panel while the task is being executed by the watcher —
@@ -328,6 +331,48 @@ export default function TaskDetail({
328
331
 
329
332
  const priorities: ItemPriority[] = ['high', 'medium', 'low'];
330
333
 
334
+ // Focus mode: Esc to exit (when not in slash autocomplete or other modal)
335
+ useEffect(() => {
336
+ if (!focusMode) return;
337
+ const onKey = (e: KeyboardEvent) => {
338
+ if (e.key === 'Escape') { setFocusMode(false); }
339
+ };
340
+ window.addEventListener('keydown', onKey);
341
+ return () => window.removeEventListener('keydown', onKey);
342
+ }, [focusMode]);
343
+
344
+ if (focusMode) {
345
+ return (
346
+ <div className="fixed inset-0 z-40 bg-background flex flex-col">
347
+ <div className="flex items-center justify-between px-6 py-3 border-b border-border flex-shrink-0">
348
+ <div className="flex items-center gap-3">
349
+ <h2 className="text-lg font-semibold text-foreground">{task.title}</h2>
350
+ <span className="text-xs text-muted-foreground">Focus Mode</span>
351
+ </div>
352
+ <button
353
+ onClick={() => setFocusMode(false)}
354
+ className="text-xs px-3 py-1 rounded border border-border text-muted-foreground hover:text-foreground transition-colors"
355
+ >
356
+ Esc 닫기
357
+ </button>
358
+ </div>
359
+ <div className="flex-1 min-h-0">
360
+ <NoteEditor
361
+ ref={editorRef}
362
+ value={description}
363
+ onChange={setDescription}
364
+ onBlur={saveDescription}
365
+ onOpenCommand={openPalette}
366
+ onPromoteLine={promoteCheckbox}
367
+ extraCorpus={extraCorpus}
368
+ placeholder="집중 모드 — 자유롭게 작성하세요…"
369
+ />
370
+ </div>
371
+ <CommandPalette open={paletteOpen} hasSelection={hasSelection} onClose={() => setPaletteOpen(false)} onRun={runRefine} />
372
+ </div>
373
+ );
374
+ }
375
+
331
376
  return (
332
377
  <div className="flex flex-col h-full">
333
378
  {/* Header */}
@@ -397,6 +442,14 @@ export default function TaskDetail({
397
442
  >
398
443
  {copied ? '✓ Copied' : 'Copy as Prompt'}
399
444
  </button>
445
+ <button
446
+ onClick={() => setFocusMode(true)}
447
+ title="포커스 모드 — 노트만 풀스크린"
448
+ className="text-xs px-2 py-0.5 rounded transition-colors border border-border
449
+ text-muted-foreground hover:text-foreground hover:border-muted-foreground"
450
+ >
451
+ Focus
452
+ </button>
400
453
  <button
401
454
  onClick={() => { chatWasManuallyToggled.current = true; setChatOpen(v => !v); }}
402
455
  className={`text-xs px-2 py-0.5 rounded transition-colors border ${
@@ -421,6 +474,46 @@ export default function TaskDetail({
421
474
  Delete
422
475
  </button>
423
476
  </div>
477
+
478
+ {/* Tags */}
479
+ <div className="flex items-center gap-1.5 flex-wrap">
480
+ {(task.tags ?? []).map(tag => (
481
+ <span key={tag} className="inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full bg-muted text-muted-foreground border border-border">
482
+ {tag}
483
+ <button
484
+ onClick={() => onUpdate({ tags: task.tags.filter(t => t !== tag) })}
485
+ className="text-muted-foreground/60 hover:text-destructive text-[10px] leading-none"
486
+ >×</button>
487
+ </span>
488
+ ))}
489
+ {showTagInput ? (
490
+ <input
491
+ value={tagInput}
492
+ onChange={(e) => setTagInput(e.target.value)}
493
+ onKeyDown={(e) => {
494
+ if (e.key === 'Enter') {
495
+ const t = tagInput.trim();
496
+ if (t && !(task.tags ?? []).includes(t)) {
497
+ onUpdate({ tags: [...(task.tags ?? []), t] });
498
+ }
499
+ setTagInput('');
500
+ }
501
+ if (e.key === 'Escape') { setTagInput(''); setShowTagInput(false); }
502
+ }}
503
+ onBlur={() => { setTagInput(''); setShowTagInput(false); }}
504
+ placeholder="태그 입력…"
505
+ className="text-xs bg-transparent border-b border-border focus:border-primary focus:outline-none px-1 py-0.5 w-24"
506
+ autoFocus
507
+ />
508
+ ) : (
509
+ <button
510
+ onClick={() => setShowTagInput(true)}
511
+ className="text-[10px] text-muted-foreground/50 hover:text-muted-foreground transition-colors"
512
+ >
513
+ + tag
514
+ </button>
515
+ )}
516
+ </div>
424
517
  </div>
425
518
 
426
519
  {/* Note editor */}
@@ -31,6 +31,10 @@ const SECTIONS: { title: string; shortcuts: { keys: string; desc: string }[] }[]
31
31
  shortcuts: [
32
32
  { keys: '⌘K', desc: 'AI 명령 팔레트' },
33
33
  { keys: '⌘⇧T', desc: '체크박스/불릿 → 태스크 승격' },
34
+ { keys: '/', desc: '슬래시 명령 (/todo, /table, /code…)' },
35
+ { keys: '⌘↵', desc: '체크박스 토글 [ ] ↔ [x]' },
36
+ { keys: '⌘⇧↵', desc: '테이블 행 추가' },
37
+ { keys: '⌘⇧⌫', desc: '테이블 행 삭제' },
34
38
  { keys: 'Tab', desc: '고스트 자동완성 수락' },
35
39
  { keys: 'Esc', desc: '고스트 해제' },
36
40
  { keys: 'Enter', desc: '리스트 자동 이어쓰기' },
@@ -70,6 +70,7 @@ export default function WorkspacePanel({
70
70
  const [showFileTree, setShowFileTree] = useState(false);
71
71
  const [showAutoDistribute, setShowAutoDistribute] = useState(false);
72
72
  const [chatStates, setChatStates] = useState<Record<string, 'idle' | 'loading' | 'done'>>({});
73
+ const [deleteToast, setDeleteToast] = useState<{ taskId: string; title: string; timer: ReturnType<typeof setTimeout> } | null>(null);
73
74
  const syncingRef = useRef(false);
74
75
 
75
76
  // Resizable panel widths
@@ -156,6 +157,16 @@ export default function WorkspacePanel({
156
157
 
157
158
  const selectedTask = tasks.find(t => t.id === selectedTaskId) ?? null;
158
159
 
160
+ // Refresh when advisor actions are applied (from any advisor — project or global)
161
+ useEffect(() => {
162
+ const refresh = () => { loadSubProjects(); if (selectedSubId) {
163
+ fetch(`/api/projects/${id}/sub-projects/${selectedSubId}/tasks`)
164
+ .then(r => r.json()).then(setTasks).catch(() => {});
165
+ }};
166
+ window.addEventListener('advisor-action-applied', refresh);
167
+ return () => window.removeEventListener('advisor-action-applied', refresh);
168
+ }, [id, selectedSubId, loadSubProjects]);
169
+
159
170
  const handleCreateSubProject = async () => {
160
171
  if (!newSubName.trim()) return;
161
172
  const res = await fetch(`/api/projects/${id}/sub-projects`, {
@@ -244,10 +255,34 @@ export default function WorkspacePanel({
244
255
  });
245
256
  };
246
257
 
247
- const handleTaskDelete = (taskId?: string) => {
258
+ const handleTaskDelete = async (taskId?: string) => {
248
259
  const tid = taskId || selectedTaskId;
249
- if (!tid) return;
250
- setConfirmAction({ type: 'delete-task', id: tid });
260
+ if (!tid || !selectedSubId) return;
261
+ const task = tasks.find(t => t.id === tid);
262
+ if (!task) return;
263
+
264
+ // Immediate archive (soft delete) + undo toast
265
+ await fetch(`/api/projects/${id}/sub-projects/${selectedSubId}/tasks/${tid}?mode=archive`, { method: 'DELETE' });
266
+ setTasks(prev => prev.filter(t => t.id !== tid));
267
+ if (selectedTaskId === tid) setSelectedTaskId(null);
268
+ loadSubProjects();
269
+
270
+ if (deleteToast?.timer) clearTimeout(deleteToast.timer);
271
+ const timer = setTimeout(() => setDeleteToast(null), 30000);
272
+ setDeleteToast({ taskId: tid, title: task.title, timer });
273
+ };
274
+
275
+ const undoTaskDelete = async () => {
276
+ if (!deleteToast) return;
277
+ clearTimeout(deleteToast.timer);
278
+ await fetch(`/api/archive?action=restore&taskId=${deleteToast.taskId}`, { method: 'POST' });
279
+ // Re-fetch tasks to get the restored one
280
+ if (selectedSubId) {
281
+ const res = await fetch(`/api/projects/${id}/sub-projects/${selectedSubId}/tasks`);
282
+ if (res.ok) setTasks(await res.json());
283
+ }
284
+ loadSubProjects();
285
+ setDeleteToast(null);
251
286
  };
252
287
 
253
288
  const handleConfirmAction = async (mode?: 'archive' | 'permanent') => {
@@ -540,6 +575,18 @@ export default function WorkspacePanel({
540
575
  onCreateTask={handleCreateTask} onStatusChange={handleTaskStatusChange}
541
576
  onTodayToggle={handleTaskTodayToggle} onDeleteTask={handleTaskDelete}
542
577
  onReorderSubs={handleReorderSubs}
578
+ onReorderTasks={async (orderedIds) => {
579
+ if (!selectedSubId) return;
580
+ setTasks(prev => {
581
+ const map = new Map(prev.map(t => [t.id, t]));
582
+ return orderedIds.map(id => map.get(id)!).filter(Boolean);
583
+ });
584
+ await fetch(`/api/projects/${id}/sub-projects/${selectedSubId}/tasks/reorder`, {
585
+ method: 'POST',
586
+ headers: { 'Content-Type': 'application/json' },
587
+ body: JSON.stringify({ orderedIds }),
588
+ });
589
+ }}
543
590
  onAutoDistribute={() => setShowAutoDistribute(true)}
544
591
  chatStates={chatStates} />
545
592
  </div>
@@ -632,6 +679,25 @@ export default function WorkspacePanel({
632
679
  onClose={() => setShowAdvisor(false)}
633
680
  />
634
681
  )}
682
+ {deleteToast && (
683
+ <div className="fixed bottom-4 left-1/2 -translate-x-1/2 z-50 bg-card border border-border rounded-lg shadow-xl px-4 py-2.5 flex items-center gap-3 animate-dialog-in">
684
+ <span className="text-xs text-muted-foreground truncate max-w-[200px]">
685
+ &quot;{deleteToast.title}&quot; 삭제됨
686
+ </span>
687
+ <button
688
+ onClick={undoTaskDelete}
689
+ className="text-xs px-2 py-1 bg-primary text-primary-foreground rounded hover:opacity-90 transition-opacity"
690
+ >
691
+ 되돌리기
692
+ </button>
693
+ <button
694
+ onClick={() => { clearTimeout(deleteToast.timer); setDeleteToast(null); }}
695
+ className="text-xs text-muted-foreground hover:text-foreground"
696
+ >
697
+ ×
698
+ </button>
699
+ </div>
700
+ )}
635
701
  </div>
636
702
  );
637
703
  }
@@ -0,0 +1,59 @@
1
+ import type { AdvisorAction } from '@/types/advisor-actions';
2
+
3
+ const ACTION_BLOCK_RE = /```action\s*\n([\s\S]*?)```/g;
4
+
5
+ export interface ParsedContent {
6
+ segments: ({ type: 'markdown'; text: string } | { type: 'actions'; actions: AdvisorAction[] })[];
7
+ }
8
+
9
+ function validateAction(obj: unknown): AdvisorAction | null {
10
+ if (!obj || typeof obj !== 'object') return null;
11
+ const a = obj as Record<string, unknown>;
12
+ if (a.type === 'create_task') {
13
+ if (typeof a.subProjectId !== 'string' || typeof a.title !== 'string') return null;
14
+ return {
15
+ type: 'create_task',
16
+ subProjectId: a.subProjectId,
17
+ projectId: typeof a.projectId === 'string' ? a.projectId : undefined,
18
+ title: a.title,
19
+ description: typeof a.description === 'string' ? a.description : undefined,
20
+ priority: ['high', 'medium', 'low'].includes(a.priority as string) ? a.priority as 'high' | 'medium' | 'low' : undefined,
21
+ status: typeof a.status === 'string' ? (a.status as string) : undefined,
22
+ } as AdvisorAction;
23
+ }
24
+ if (a.type === 'update_task') {
25
+ if (typeof a.taskId !== 'string' || !a.changes || typeof a.changes !== 'object') return null;
26
+ return { type: 'update_task', taskId: a.taskId, changes: a.changes as Record<string, unknown> } as AdvisorAction;
27
+ }
28
+ return null;
29
+ }
30
+
31
+ export function parseAdvisorContent(content: string): ParsedContent {
32
+ const segments: ParsedContent['segments'] = [];
33
+ let lastIndex = 0;
34
+
35
+ for (const match of content.matchAll(ACTION_BLOCK_RE)) {
36
+ const before = content.slice(lastIndex, match.index);
37
+ if (before.trim()) segments.push({ type: 'markdown', text: before });
38
+
39
+ try {
40
+ const raw = JSON.parse(match[1]);
41
+ const arr = Array.isArray(raw) ? raw : [raw];
42
+ const valid = arr.map(validateAction).filter((a): a is AdvisorAction => a !== null);
43
+ if (valid.length > 0) {
44
+ segments.push({ type: 'actions', actions: valid });
45
+ } else {
46
+ segments.push({ type: 'markdown', text: match[0] });
47
+ }
48
+ } catch {
49
+ segments.push({ type: 'markdown', text: match[0] });
50
+ }
51
+
52
+ lastIndex = (match.index ?? 0) + match[0].length;
53
+ }
54
+
55
+ const remaining = content.slice(lastIndex);
56
+ if (remaining.trim()) segments.push({ type: 'markdown', text: remaining });
57
+
58
+ return { segments };
59
+ }
@@ -4,6 +4,25 @@ import { getTasksByProject } from '../db/queries/tasks';
4
4
  import { getBrainstorm } from '../db/queries/brainstorms';
5
5
  import type { ITask, TaskStatus } from '../../types';
6
6
 
7
+ const ACTION_INSTRUCTIONS = `
8
+ ## Actions
9
+ 사용자가 요청하면 태스크 생성/수정을 제안할 수 있습니다. \`\`\`action 블록을 사용하세요.
10
+ 사용자가 명시적으로 요청하지 않으면 action 블록을 넣지 마세요.
11
+
12
+ 형식:
13
+ \`\`\`action
14
+ [
15
+ {"type":"create_task","subProjectId":"<sub_id값>","projectId":"<project_id값>","title":"제목","priority":"high|medium|low"},
16
+ {"type":"update_task","taskId":"<task_id값>","changes":{"status":"done"}}
17
+ ]
18
+ \`\`\`
19
+
20
+ 규칙:
21
+ - subProjectId, taskId, projectId는 아래 컨텍스트의 [sub_id:...], [task_id:...], [project_id:...] 값을 정확히 사용
22
+ - action 블록 앞에 항상 무엇을 제안하는지 설명
23
+ - 사용자가 승인해야 실행됨
24
+ `;
25
+
7
26
  const MAX_BRAINSTORM = 1500;
8
27
  const NOTE_LIMIT = 120;
9
28
  const MAX_HISTORY_MESSAGES = 20;
@@ -25,7 +44,8 @@ export function buildGlobalAdvisorPrompt(): string {
25
44
  const parts: string[] = [];
26
45
  parts.push('당신은 사용자의 전체 워크스페이스를 조망하는 AI 어드바이저입니다.');
27
46
  parts.push('여러 프로젝트의 현황을 파악하고, 우선순위·방향·빠진 부분·크로스-프로젝트 이슈 등을 논의합니다.');
28
- parts.push('한국어로 간결하게 답하세요. 긴 설교 금지.\n');
47
+ parts.push('한국어로 간결하게 답하세요. 긴 설교 금지.');
48
+ parts.push(ACTION_INSTRUCTIONS);
29
49
 
30
50
  parts.push('=== ALL WORKSPACES ===\n');
31
51
 
@@ -52,7 +72,7 @@ export function buildGlobalAdvisorPrompt(): string {
52
72
  }
53
73
 
54
74
  const lines: string[] = [];
55
- lines.push(`## ${project.name}`);
75
+ lines.push(`## ${project.name} [project_id:${project.id}]`);
56
76
  if (project.description) lines.push(project.description);
57
77
  const statsStr = Object.entries(counts).map(([k, v]) => `${k}:${v}`).join(' / ');
58
78
  lines.push(`태스크 ${allTasks.length}개 (${statsStr})`);
@@ -67,12 +87,12 @@ export function buildGlobalAdvisorPrompt(): string {
67
87
  for (const sub of subs) {
68
88
  const subTasks = allTasks.filter(t => t.sub_project_id === sub.id && t.status !== 'done');
69
89
  if (!subTasks.length) continue;
70
- lines.push(`\n### ${sub.name}`);
90
+ lines.push(`\n### ${sub.name} [sub_id:${sub.id}]`);
71
91
  for (const t of subTasks) {
72
92
  const note = truncate(t.description, NOTE_LIMIT);
73
93
  const flags = [t.priority === 'high' ? 'HIGH' : null, t.is_today ? 'today' : null].filter(Boolean).join(', ');
74
94
  const flagStr = flags ? ` (${flags})` : '';
75
- lines.push(`- [${STATUS_ICON[t.status] ?? t.status}] **${t.title}**${flagStr}${note ? ' — ' + note : ''}`);
95
+ lines.push(`- [${STATUS_ICON[t.status] ?? t.status}] **${t.title}** [task_id:${t.id}]${flagStr}${note ? ' — ' + note : ''}`);
76
96
  }
77
97
  }
78
98
 
@@ -4,6 +4,25 @@ import { getTasksByProject } from '../db/queries/tasks';
4
4
  import { getBrainstorm } from '../db/queries/brainstorms';
5
5
  import type { ITask, TaskStatus } from '../../types';
6
6
 
7
+ const ACTION_INSTRUCTIONS = `
8
+ ## Actions
9
+ 사용자가 요청하면 태스크 생성/수정을 제안할 수 있습니다. \`\`\`action 블록을 사용하세요.
10
+ 사용자가 명시적으로 요청하지 않으면 action 블록을 넣지 마세요.
11
+
12
+ 형식:
13
+ \`\`\`action
14
+ [
15
+ {"type":"create_task","subProjectId":"<sub_id값>","title":"제목","priority":"high|medium|low","status":"idea"},
16
+ {"type":"update_task","taskId":"<task_id값>","changes":{"status":"done"}}
17
+ ]
18
+ \`\`\`
19
+
20
+ 규칙:
21
+ - subProjectId, taskId는 아래 컨텍스트의 [sub_id:...], [task_id:...] 값을 정확히 사용
22
+ - action 블록 앞에 항상 무엇을 제안하는지 설명
23
+ - 사용자가 승인해야 실행됨 (자동 실행 아님)
24
+ `;
25
+
7
26
  const MAX_BRAINSTORM = 4000;
8
27
  const NOTE_LIMIT_ACTIVE = 500;
9
28
  const NOTE_LIMIT_DEFAULT = 200;
@@ -49,7 +68,7 @@ export function buildProjectAdvisorPrompt(projectId: string): string {
49
68
  continue;
50
69
  }
51
70
  const lines: string[] = [];
52
- lines.push(`### ${sub.name}`);
71
+ lines.push(`### ${sub.name} [sub_id:${sub.id}]`);
53
72
  if (sub.description) lines.push(sub.description);
54
73
  lines.push(`태스크 ${tasks.length}개:`);
55
74
  for (const t of tasks) {
@@ -58,7 +77,7 @@ export function buildProjectAdvisorPrompt(projectId: string): string {
58
77
  const flags = [t.priority === 'high' ? 'HIGH' : null, t.is_today ? 'today' : null].filter(Boolean).join(', ');
59
78
  const flagStr = flags ? ` (${flags})` : '';
60
79
  const noteStr = note ? ` — ${note}` : '';
61
- lines.push(`- [${STATUS_ICON[t.status] ?? t.status}] **${t.title}**${flagStr}${noteStr}`);
80
+ lines.push(`- [${STATUS_ICON[t.status] ?? t.status}] **${t.title}** [task_id:${t.id}]${flagStr}${noteStr}`);
62
81
  }
63
82
  subSections.push(lines.join('\n'));
64
83
  }
@@ -86,6 +105,7 @@ export function buildProjectAdvisorPrompt(projectId: string): string {
86
105
  parts.push(`당신은 프로젝트 "${project.name}"의 어드바이저입니다.`);
87
106
  parts.push(`사용자가 프로젝트 방향, 우선순위, 빠진 부분, 다음 단계 등을 논의하면 프로젝트 전체 맥락을 바탕으로 간결하게 답합니다.`);
88
107
  parts.push(`태스크를 언급할 때는 정확한 제목을 쓰세요. 한국어로 답하세요. 긴 설교는 금지.`);
108
+ parts.push(ACTION_INSTRUCTIONS);
89
109
 
90
110
  if (project.ai_context) {
91
111
  parts.push(`\nProject AI Policy:\n${project.ai_context}`);
@@ -0,0 +1,25 @@
1
+ import type { TaskStatus } from './index';
2
+
3
+ export interface CreateTaskAction {
4
+ type: 'create_task';
5
+ subProjectId: string;
6
+ projectId?: string;
7
+ title: string;
8
+ description?: string;
9
+ priority?: 'high' | 'medium' | 'low';
10
+ status?: TaskStatus;
11
+ }
12
+
13
+ export interface UpdateTaskAction {
14
+ type: 'update_task';
15
+ taskId: string;
16
+ changes: {
17
+ status?: TaskStatus;
18
+ priority?: 'high' | 'medium' | 'low';
19
+ description?: string;
20
+ title?: string;
21
+ is_today?: boolean;
22
+ };
23
+ }
24
+
25
+ export type AdvisorAction = CreateTaskAction | UpdateTaskAction;
@@ -1 +0,0 @@
1
- var a,b,c,d;self.__BUILD_MANIFEST=(a=0,b=0,c=0,d=0,{__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},__routerFilterStatic:{numItems:15,errorRate:1e-4,numBits:288,numHashes:14,bitArray:[0,1,1,0,0,1,1,1,0,1,0,0,0,1,1,1,0,0,0,1,0,0,1,0,0,1,0,1,1,1,1,1,0,1,1,0,0,1,0,1,0,0,0,0,1,1,0,1,0,0,1,0,1,0,0,0,1,1,1,1,1,1,0,1,1,1,0,1,1,1,0,0,1,0,0,0,0,0,1,1,1,0,0,0,1,0,1,0,0,0,1,1,0,1,1,1,1,0,1,0,1,1,0,1,1,0,0,1,0,0,0,0,1,1,1,1,0,1,0,0,1,1,1,1,1,0,1,1,1,0,0,0,1,1,1,0,0,1,0,0,1,0,1,1,1,0,1,0,1,0,0,1,0,1,1,0,0,0,0,0,1,1,1,0,1,0,0,1,0,0,1,0,0,0,0,1,1,1,1,1,0,0,1,0,0,1,1,0,1,1,1,1,1,1,1,0,0,0,1,0,0,1,0,1,1,0,0,0,0,1,1,1,0,1,1,0,0,1,1,1,1,1,0,0,1,0,1,1,0,0,0,0,1,0,1,1,0,0,0,1,0,0,1,0,1,1,0,0,1,0,1,0,1,1,0,1,0,0,1,1,0,1,1,1,1,0,1,1,0,0,1,0,0,1,0,1,1,0,1,1,1,1,0,0,0,0,1,1]},__routerFilterDynamic:{numItems:3,errorRate:1e-4,numBits:58,numHashes:14,bitArray:[1,0,1,1,1,1,0,0,1,1,0,1,1,0,0,1,1,0,0,0,0,1,1,1,0,0,0,1,1,0,1,1,1,1,0,1,1,0,1,0,1,1,1,0,0,1,1,0,0,1,0,0,1,0,0,1,1,1]},sortedPages:["/_app"]}),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();
@@ -1 +0,0 @@
1
- (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2,64,86,94,123,167,175,177,178,181,202,212,307,363,424,437,460,514,595,711,755,770,772,819,822,851,860,896,922,980,988],{4441:()=>{}},_=>{_.O(0,[441,794,358],()=>_(_.s=4441)),_N_E=_.O()}]);
@@ -1 +0,0 @@
1
- (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2,64,86,94,123,167,175,177,178,181,202,212,307,363,424,437,460,514,595,711,755,770,772,819,822,851,860,896,922,980,988],{4441:()=>{}},_=>{_.O(0,[441,794,358],()=>_(_.s=4441)),_N_E=_.O()}]);
@@ -1 +0,0 @@
1
- (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2,64,86,94,123,167,175,177,178,181,202,212,307,363,424,437,460,514,595,711,755,770,772,819,822,851,860,896,922,980,988],{4441:()=>{}},_=>{_.O(0,[441,794,358],()=>_(_.s=4441)),_N_E=_.O()}]);
@@ -1 +0,0 @@
1
- (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2,64,86,94,123,167,175,177,178,181,202,212,307,363,424,437,460,514,595,711,755,770,772,819,822,851,860,896,922,980,988],{4441:()=>{}},_=>{_.O(0,[441,794,358],()=>_(_.s=4441)),_N_E=_.O()}]);