@geminilight/mindos 0.6.29 → 0.6.31

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 (110) hide show
  1. package/README.md +10 -4
  2. package/README_zh.md +10 -4
  3. package/app/app/api/acp/config/route.ts +82 -0
  4. package/app/app/api/acp/detect/route.ts +71 -48
  5. package/app/app/api/acp/install/route.ts +51 -0
  6. package/app/app/api/acp/session/route.ts +141 -11
  7. package/app/app/api/ask/route.ts +126 -18
  8. package/app/app/api/export/route.ts +105 -0
  9. package/app/app/api/workflows/route.ts +156 -0
  10. package/app/app/globals.css +2 -2
  11. package/app/app/page.tsx +7 -2
  12. package/app/app/trash/page.tsx +7 -0
  13. package/app/app/view/[...path]/ViewPageClient.tsx +234 -2
  14. package/app/components/ActivityBar.tsx +12 -4
  15. package/app/components/AskModal.tsx +4 -1
  16. package/app/components/ExportModal.tsx +220 -0
  17. package/app/components/FileTree.tsx +42 -11
  18. package/app/components/HomeContent.tsx +92 -20
  19. package/app/components/MarkdownView.tsx +45 -10
  20. package/app/components/Panel.tsx +1 -0
  21. package/app/components/RightAskPanel.tsx +5 -1
  22. package/app/components/Sidebar.tsx +10 -1
  23. package/app/components/SidebarLayout.tsx +6 -0
  24. package/app/components/TrashPageClient.tsx +263 -0
  25. package/app/components/agents/AgentDetailContent.tsx +263 -47
  26. package/app/components/agents/AgentsContentPage.tsx +11 -0
  27. package/app/components/agents/AgentsPanelA2aTab.tsx +285 -46
  28. package/app/components/agents/AgentsPanelSessionsTab.tsx +166 -0
  29. package/app/components/agents/agents-content-model.ts +2 -2
  30. package/app/components/ask/AgentSelectorCapsule.tsx +218 -0
  31. package/app/components/ask/AskContent.tsx +197 -239
  32. package/app/components/ask/FileChip.tsx +82 -17
  33. package/app/components/ask/MentionPopover.tsx +21 -3
  34. package/app/components/ask/MessageList.tsx +30 -9
  35. package/app/components/ask/SlashCommandPopover.tsx +21 -3
  36. package/app/components/ask/ToolCallBlock.tsx +102 -18
  37. package/app/components/changes/ChangesContentPage.tsx +58 -14
  38. package/app/components/explore/ExploreContent.tsx +4 -7
  39. package/app/components/explore/UseCaseCard.tsx +18 -1
  40. package/app/components/explore/use-cases.generated.ts +76 -0
  41. package/app/components/explore/use-cases.yaml +185 -0
  42. package/app/components/panels/AgentsPanel.tsx +1 -0
  43. package/app/components/panels/AgentsPanelHubNav.tsx +9 -2
  44. package/app/components/panels/DiscoverPanel.tsx +1 -1
  45. package/app/components/panels/WorkflowsPanel.tsx +206 -0
  46. package/app/components/renderers/workflow-yaml/StepEditor.tsx +164 -0
  47. package/app/components/renderers/workflow-yaml/WorkflowEditor.tsx +211 -0
  48. package/app/components/renderers/workflow-yaml/WorkflowRunner.tsx +269 -0
  49. package/app/components/renderers/workflow-yaml/WorkflowYamlRenderer.tsx +126 -0
  50. package/app/components/renderers/workflow-yaml/execution.ts +229 -0
  51. package/app/components/renderers/workflow-yaml/index.ts +6 -0
  52. package/app/components/renderers/workflow-yaml/manifest.ts +21 -0
  53. package/app/components/renderers/workflow-yaml/parser.ts +172 -0
  54. package/app/components/renderers/workflow-yaml/selectors.tsx +574 -0
  55. package/app/components/renderers/workflow-yaml/serializer.ts +56 -0
  56. package/app/components/renderers/workflow-yaml/types.ts +46 -0
  57. package/app/components/settings/AiTab.tsx +191 -174
  58. package/app/components/settings/AppearanceTab.tsx +168 -77
  59. package/app/components/settings/KnowledgeTab.tsx +131 -136
  60. package/app/components/settings/McpTab.tsx +11 -11
  61. package/app/components/settings/Primitives.tsx +60 -0
  62. package/app/components/settings/SettingsContent.tsx +15 -8
  63. package/app/components/settings/SyncTab.tsx +12 -12
  64. package/app/components/settings/UninstallTab.tsx +8 -18
  65. package/app/components/settings/UpdateTab.tsx +82 -82
  66. package/app/components/settings/types.ts +17 -8
  67. package/app/hooks/useAcpConfig.ts +96 -0
  68. package/app/hooks/useAcpDetection.ts +69 -14
  69. package/app/hooks/useAcpRegistry.ts +46 -11
  70. package/app/hooks/useAskModal.ts +12 -5
  71. package/app/hooks/useAskPanel.ts +8 -5
  72. package/app/hooks/useAskSession.ts +19 -2
  73. package/app/hooks/useImageUpload.ts +152 -0
  74. package/app/lib/acp/acp-tools.ts +3 -1
  75. package/app/lib/acp/agent-descriptors.ts +274 -0
  76. package/app/lib/acp/bridge.ts +6 -0
  77. package/app/lib/acp/index.ts +20 -4
  78. package/app/lib/acp/registry.ts +74 -7
  79. package/app/lib/acp/session.ts +490 -28
  80. package/app/lib/acp/subprocess.ts +307 -21
  81. package/app/lib/acp/types.ts +158 -20
  82. package/app/lib/actions.ts +57 -3
  83. package/app/lib/agent/model.ts +18 -3
  84. package/app/lib/agent/stream-consumer.ts +18 -0
  85. package/app/lib/agent/to-agent-messages.ts +25 -2
  86. package/app/lib/agent/tools.ts +56 -9
  87. package/app/lib/core/export.ts +116 -0
  88. package/app/lib/core/trash.ts +241 -0
  89. package/app/lib/fs.ts +47 -0
  90. package/app/lib/hooks/usePinnedFiles.ts +90 -0
  91. package/app/lib/i18n/generated/explore-i18n.generated.ts +138 -0
  92. package/app/lib/i18n/index.ts +3 -0
  93. package/app/lib/i18n/modules/knowledge.ts +124 -6
  94. package/app/lib/i18n/modules/navigation.ts +2 -0
  95. package/app/lib/i18n/modules/onboarding.ts +2 -134
  96. package/app/lib/i18n/modules/panels.ts +146 -2
  97. package/app/lib/i18n/modules/settings.ts +12 -0
  98. package/app/lib/pi-integration/skills.ts +21 -6
  99. package/app/lib/renderers/index.ts +2 -2
  100. package/app/lib/settings.ts +10 -0
  101. package/app/lib/types.ts +12 -1
  102. package/app/next-env.d.ts +1 -1
  103. package/app/package.json +11 -3
  104. package/app/scripts/generate-explore.ts +145 -0
  105. package/package.json +1 -1
  106. package/templates/en/.mindos/workflows/Sprint Release.flow.yaml +130 -0
  107. package/templates/zh/.mindos/workflows//345/221/250/350/277/255/344/273/243/346/243/200/346/237/245.flow.yaml +84 -0
  108. package/app/components/explore/use-cases.ts +0 -58
  109. package/app/components/renderers/workflow/WorkflowRenderer.tsx +0 -409
  110. package/app/components/renderers/workflow/manifest.ts +0 -14
@@ -6,11 +6,12 @@ import { FileNode } from '@/lib/types';
6
6
  import { encodePath } from '@/lib/utils';
7
7
  import {
8
8
  ChevronDown, FileText, Table, Folder, FolderOpen, Plus, Loader2,
9
- Trash2, Pencil, Layers, ScrollText, FolderInput, Copy,
9
+ Trash2, Pencil, Layers, ScrollText, FolderInput, Copy, MoreHorizontal, Star,
10
10
  } from 'lucide-react';
11
11
  import { createFileAction, deleteFileAction, renameFileAction, renameSpaceAction, deleteSpaceAction, convertToSpaceAction, deleteFolderAction } from '@/lib/actions';
12
12
  import { useLocale } from '@/lib/LocaleContext';
13
13
  import { ConfirmDialog } from '@/components/agents/AgentsPrimitives';
14
+ import { usePinnedFiles } from '@/lib/hooks/usePinnedFiles';
14
15
 
15
16
  function notifyFilesChanged() {
16
17
  window.dispatchEvent(new Event('mindos:files-changed'));
@@ -134,9 +135,15 @@ function SpaceContextMenu({ x, y, node, onClose, onRename, onImport, onDelete }:
134
135
  }) {
135
136
  const router = useRouter();
136
137
  const { t } = useLocale();
138
+ const { isPinned, togglePin } = usePinnedFiles();
139
+ const pinned = isPinned(node.path);
137
140
 
138
141
  return (
139
142
  <ContextMenuShell x={x} y={y} onClose={onClose}>
143
+ <button className={MENU_ITEM} onClick={() => { togglePin(node.path); onClose(); }}>
144
+ <Star size={14} className={`shrink-0 ${pinned ? 'fill-[var(--amber)] text-[var(--amber)]' : ''}`} />
145
+ {pinned ? t.fileTree.removeFromFavorites : t.fileTree.pinToFavorites}
146
+ </button>
140
147
  <button className={MENU_ITEM} onClick={() => { router.push(`/view/${encodePath(`${node.path}/INSTRUCTION.md`)}`); onClose(); }}>
141
148
  <ScrollText size={14} className="shrink-0" /> {t.fileTree.editRules}
142
149
  </button>
@@ -168,9 +175,15 @@ function FolderContextMenu({ x, y, node, onClose, onRename, onDelete }: {
168
175
  const router = useRouter();
169
176
  const { t } = useLocale();
170
177
  const [isPending, startTransition] = useTransition();
178
+ const { isPinned, togglePin } = usePinnedFiles();
179
+ const pinned = isPinned(node.path);
171
180
 
172
181
  return (
173
- <ContextMenuShell x={x} y={y} onClose={onClose} menuHeight={140}>
182
+ <ContextMenuShell x={x} y={y} onClose={onClose} menuHeight={180}>
183
+ <button className={MENU_ITEM} onClick={() => { togglePin(node.path); onClose(); }}>
184
+ <Star size={14} className={`shrink-0 ${pinned ? 'fill-[var(--amber)] text-[var(--amber)]' : ''}`} />
185
+ {pinned ? t.fileTree.removeFromFavorites : t.fileTree.pinToFavorites}
186
+ </button>
174
187
  <button className={MENU_ITEM} disabled={isPending} onClick={() => {
175
188
  startTransition(async () => {
176
189
  const result = await convertToSpaceAction(node.path);
@@ -567,9 +580,11 @@ function FileNodeItem({ node, depth, currentPath, onNavigate }: {
567
580
  const [renaming, setRenaming] = useState(false);
568
581
  const [renameValue, setRenameValue] = useState(node.name);
569
582
  const [isPending, startTransition] = useTransition();
570
- const [isPendingDelete, startDeleteTransition] = useTransition();
583
+ const [, startDeleteTransition] = useTransition();
571
584
  const renameRef = useRef<HTMLInputElement>(null);
572
585
  const { t } = useLocale();
586
+ const { isPinned, togglePin } = usePinnedFiles();
587
+ const pinned = isPinned(node.path);
573
588
  const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
574
589
  const [contextMenu, setContextMenu] = useState<{ x: number; y: number } | null>(null);
575
590
 
@@ -659,16 +674,20 @@ function FileNodeItem({ node, depth, currentPath, onNavigate }: {
659
674
  >
660
675
  {getIcon(node)}
661
676
  <span className="truncate leading-5" suppressHydrationWarning>{node.name}</span>
677
+ {pinned && <Star size={10} className="shrink-0 fill-[var(--amber)] text-[var(--amber)] opacity-60" />}
662
678
  </button>
663
679
  <div className="absolute right-1 top-1/2 -translate-y-1/2 hidden group-hover/file:flex items-center gap-0.5">
664
- <button onClick={() => copyPathToClipboard(node.path)} className="p-0.5 rounded text-muted-foreground hover:text-foreground hover:bg-muted transition-colors" title={t.fileTree.copyPath}>
665
- <Copy size={12} />
666
- </button>
667
- <button onClick={startRename} className="p-0.5 rounded text-muted-foreground hover:text-foreground hover:bg-muted transition-colors" title={t.fileTree.rename}>
668
- <Pencil size={12} />
669
- </button>
670
- <button onClick={handleDelete} className="p-0.5 rounded text-muted-foreground hover:text-error hover:bg-muted transition-colors" title={t.fileTree.delete}>
671
- {isPendingDelete ? <Loader2 size={12} className="animate-spin" /> : <Trash2 size={12} />}
680
+ <button
681
+ type="button"
682
+ onClick={(e) => {
683
+ e.stopPropagation();
684
+ const rect = e.currentTarget.getBoundingClientRect();
685
+ setContextMenu({ x: rect.left, y: rect.bottom + 4 });
686
+ }}
687
+ className="p-0.5 rounded text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
688
+ title="More"
689
+ >
690
+ <MoreHorizontal size={14} />
672
691
  </button>
673
692
  </div>
674
693
  {contextMenu && (
@@ -676,10 +695,22 @@ function FileNodeItem({ node, depth, currentPath, onNavigate }: {
676
695
  x={contextMenu.x}
677
696
  y={contextMenu.y}
678
697
  onClose={() => setContextMenu(null)}
698
+ menuHeight={140}
679
699
  >
680
700
  <button className={MENU_ITEM} onClick={() => { copyPathToClipboard(node.path); setContextMenu(null); }}>
681
701
  <Copy size={14} className="shrink-0" /> {t.fileTree.copyPath}
682
702
  </button>
703
+ <button className={MENU_ITEM} onClick={() => { togglePin(node.path); setContextMenu(null); }}>
704
+ <Star size={14} className={`shrink-0 ${pinned ? 'fill-[var(--amber)] text-[var(--amber)]' : ''}`} />
705
+ {pinned ? t.fileTree.removeFromFavorites : t.fileTree.pinToFavorites}
706
+ </button>
707
+ <button className={MENU_ITEM} onClick={(e) => { setContextMenu(null); startRename(e); }}>
708
+ <Pencil size={14} className="shrink-0" /> {t.fileTree.rename}
709
+ </button>
710
+ <div className={MENU_DIVIDER} />
711
+ <button className={MENU_DANGER} onClick={(e) => { setContextMenu(null); handleDelete(e); }}>
712
+ <Trash2 size={14} className="shrink-0" /> {t.fileTree.delete}
713
+ </button>
683
714
  </ContextMenuShell>
684
715
  )}
685
716
  <ConfirmDialog
@@ -1,11 +1,12 @@
1
1
  'use client';
2
2
 
3
3
  import Link from 'next/link';
4
- import { FileText, Table, Clock, Sparkles, ArrowRight, FilePlus, Search, ChevronDown, Compass, Folder, Puzzle, Brain, Plus, Trash2, Check, Loader2, X, FolderInput, Zap, History, SlidersHorizontal, ListTodo } from 'lucide-react';
4
+ import { FileText, Table, Clock, Sparkles, ArrowRight, FilePlus, Search, ChevronDown, Compass, Folder, Puzzle, Brain, Plus, Trash2, Check, Loader2, X, FolderInput, Zap, History, SlidersHorizontal, ListTodo, Star } from 'lucide-react';
5
5
  import type { LucideIcon } from 'lucide-react';
6
6
  import { useState, useEffect, useMemo, useCallback } from 'react';
7
7
  import { useLocale } from '@/lib/LocaleContext';
8
8
  import { encodePath, relativeTime, extractEmoji, stripEmoji } from '@/lib/utils';
9
+ import { usePinnedFiles } from '@/lib/hooks/usePinnedFiles';
9
10
  import { getAllRenderers, getPluginRenderers } from '@/lib/renderers/registry';
10
11
  import OnboardingView from './OnboardingView';
11
12
  import GuideCard from './GuideCard';
@@ -168,6 +169,7 @@ const TOOL_ICONS: Record<string, LucideIcon> = {
168
169
  'agent-inspector': Search,
169
170
  'config-panel': SlidersHorizontal,
170
171
  'todo': ListTodo,
172
+ 'workflow-yaml': Zap,
171
173
  };
172
174
 
173
175
  /** Mini-card for built-in tools — visually distinct from plugin chips */
@@ -251,14 +253,14 @@ export default function HomeContent({ recent, existingFiles, spaces }: { recent:
251
253
  <ExampleCleanupBanner />
252
254
 
253
255
  {/* ── Hero ── */}
254
- <div className="mb-10">
256
+ <div className="mb-14">
255
257
  <div className="flex items-center gap-2 mb-3">
256
- <div className="w-1 h-5 rounded-full bg-[var(--amber)]" />
258
+ <div className="w-1 h-6 rounded-full bg-gradient-to-b from-[var(--amber)] to-[var(--amber)]/30" />
257
259
  <h1 className="text-2xl font-semibold tracking-tight font-display text-foreground">
258
260
  MindOS
259
261
  </h1>
260
262
  </div>
261
- <p className="text-sm leading-relaxed mb-5 text-muted-foreground pl-4">
263
+ <p className="text-base leading-relaxed mb-5 text-muted-foreground pl-4 max-w-md">
262
264
  {t.app.tagline}
263
265
  </p>
264
266
 
@@ -268,7 +270,7 @@ export default function HomeContent({ recent, existingFiles, spaces }: { recent:
268
270
  onClick={triggerAsk}
269
271
  title="⌘/"
270
272
  data-walkthrough="ask-button"
271
- className="flex-1 flex items-center gap-3 px-4 py-3 rounded-xl border border-border bg-card transition-all duration-150 hover:border-[var(--amber)]/50 hover:bg-[var(--amber-dim)]"
273
+ className="flex-1 flex items-center gap-3 px-4 py-3 rounded-xl border border-border/60 shadow-sm bg-card transition-all duration-150 hover:border-[var(--amber)]/50 hover:bg-[var(--amber-dim)]"
272
274
  >
273
275
  <Sparkles size={15} className="shrink-0 text-[var(--amber)]" />
274
276
  <span className="text-sm flex-1 text-left text-foreground">
@@ -292,7 +294,7 @@ export default function HomeContent({ recent, existingFiles, spaces }: { recent:
292
294
  </div>
293
295
 
294
296
  {/* Quick Actions */}
295
- <div className="flex flex-wrap gap-2.5 mt-4 pl-4">
297
+ <div className="flex flex-wrap gap-3 mt-5 pl-4">
296
298
  <button
297
299
  onClick={() => window.dispatchEvent(new CustomEvent('mindos:open-import'))}
298
300
  className="inline-flex items-center gap-2 px-3.5 py-2 rounded-lg text-sm font-medium transition-all duration-150 hover:translate-x-0.5 bg-[var(--amber-dim)] text-[var(--amber-text)]"
@@ -330,8 +332,11 @@ export default function HomeContent({ recent, existingFiles, spaces }: { recent:
330
332
  </div>
331
333
  </div>
332
334
 
333
- {/* ── Section 1: Spaces ── */}
334
- <section className="mb-8">
335
+ {/* ── Section: Pinned Files ── */}
336
+ <PinnedFilesSection formatTime={formatTime} />
337
+
338
+ {/* ── Section: Spaces ── */}
339
+ <section className="mb-12">
335
340
  <SectionTitle
336
341
  icon={<Brain size={13} />}
337
342
  count={spaceList.length > 0 ? spaceList.length : undefined}
@@ -391,9 +396,9 @@ export default function HomeContent({ recent, existingFiles, spaces }: { recent:
391
396
  )}
392
397
  </section>
393
398
 
394
- {/* ── Section 2: Tools ── */}
399
+ {/* ── Section: Tools ── */}
395
400
  {builtinFeatures.length > 0 && (
396
- <section className="mb-8">
401
+ <section className="mb-12">
397
402
  <SectionTitle icon={<Zap size={13} />} count={builtinFeatures.length}>
398
403
  {t.home.builtinFeatures}
399
404
  </SectionTitle>
@@ -418,9 +423,9 @@ export default function HomeContent({ recent, existingFiles, spaces }: { recent:
418
423
  </section>
419
424
  )}
420
425
 
421
- {/* ── Section 3: Extensions ── */}
426
+ {/* ── Section: Extensions ── */}
422
427
  {availablePlugins.length > 0 && (
423
- <section className="mb-8">
428
+ <section className="mb-12">
424
429
  <SectionTitle
425
430
  icon={<Puzzle size={13} />}
426
431
  count={availablePlugins.length}
@@ -445,7 +450,7 @@ export default function HomeContent({ recent, existingFiles, spaces }: { recent:
445
450
  </section>
446
451
  )}
447
452
 
448
- {/* ── Section 4: Recently Edited ── */}
453
+ {/* ── Section: Recently Edited ── */}
449
454
  {recent.length > 0 && (
450
455
  <section className="mb-12">
451
456
  <SectionTitle
@@ -464,8 +469,30 @@ export default function HomeContent({ recent, existingFiles, spaces }: { recent:
464
469
  {t.home.recentlyEdited}
465
470
  </SectionTitle>
466
471
 
472
+ {/* Spotlight — latest file */}
473
+ <Link
474
+ href={`/view/${encodePath(recent[0].path)}`}
475
+ className="block mb-5 p-4 rounded-xl border border-border/50 bg-gradient-to-r from-[var(--amber-subtle)] to-transparent hover:border-[var(--amber)]/40 hover:shadow-sm transition-all group"
476
+ >
477
+ <div className="flex items-center gap-3">
478
+ <div className="w-10 h-10 rounded-lg bg-[var(--amber)]/10 flex items-center justify-center shrink-0">
479
+ {recent[0].path.endsWith('.csv')
480
+ ? <Table size={18} className="text-[var(--amber)]" />
481
+ : <FileText size={18} className="text-[var(--amber)]" />}
482
+ </div>
483
+ <div className="flex-1 min-w-0">
484
+ <span className="text-base font-medium text-foreground block truncate">
485
+ {recent[0].path.split('/').pop()}
486
+ </span>
487
+ <span className="text-xs text-muted-foreground mt-0.5 block" suppressHydrationWarning>
488
+ {recent[0].path.split('/').slice(0, -1).join('/') || 'Root'} · {formatTime(recent[0].mtime)}
489
+ </span>
490
+ </div>
491
+ <ArrowRight size={16} className="text-muted-foreground group-hover:text-[var(--amber)] transition-colors shrink-0" />
492
+ </div>
493
+ </Link>
494
+
467
495
  {groups.length > 0 ? (
468
- /* Space-Grouped View */
469
496
  <div className="flex flex-col gap-4">
470
497
  {groups.map((group) => {
471
498
  const visibleFiles = showAll ? group.files : group.files.slice(0, FILES_PER_GROUP);
@@ -504,7 +531,6 @@ export default function HomeContent({ recent, existingFiles, spaces }: { recent:
504
531
  );
505
532
  })}
506
533
 
507
- {/* Root-level files (Other) */}
508
534
  {rootFiles.length > 0 && (
509
535
  <div>
510
536
  <div className="flex items-center gap-2 px-1 py-1.5">
@@ -521,7 +547,6 @@ export default function HomeContent({ recent, existingFiles, spaces }: { recent:
521
547
  </div>
522
548
  )}
523
549
 
524
- {/* Show more / less */}
525
550
  {groups.some(g => g.files.length > FILES_PER_GROUP) && (
526
551
  <ToggleButton
527
552
  expanded={showAll}
@@ -533,7 +558,6 @@ export default function HomeContent({ recent, existingFiles, spaces }: { recent:
533
558
  )}
534
559
  </div>
535
560
  ) : (
536
- /* Flat Timeline Fallback */
537
561
  <div className="relative pl-4">
538
562
  <div className="absolute left-0 top-1 bottom-1 w-px bg-border" />
539
563
  <div className="flex flex-col gap-0.5">
@@ -547,8 +571,8 @@ export default function HomeContent({ recent, existingFiles, spaces }: { recent:
547
571
  aria-hidden="true"
548
572
  className={`absolute -left-4 top-1/2 -translate-y-1/2 rounded-full transition-all duration-150 group-hover:scale-150 ${
549
573
  idx === 0
550
- ? 'w-2 h-2 bg-[var(--amber)] outline-2 outline-[var(--amber-dim)]'
551
- : 'w-1.5 h-1.5 bg-border'
574
+ ? 'w-2.5 h-2.5 bg-[var(--amber)] ring-2 ring-[var(--amber)]/20'
575
+ : 'w-1.5 h-1.5 bg-muted-foreground/30'
552
576
  }`}
553
577
  />
554
578
  <Link
@@ -586,7 +610,7 @@ export default function HomeContent({ recent, existingFiles, spaces }: { recent:
586
610
  )}
587
611
 
588
612
  {/* Footer */}
589
- <div className="mt-16 flex items-center gap-1.5 text-xs font-display text-muted-foreground opacity-60">
613
+ <div className="py-6 border-t border-border/30 flex items-center gap-1.5 text-xs font-display text-muted-foreground opacity-40">
590
614
  <Sparkles size={10} className="text-[var(--amber)]" />
591
615
  <span>{t.app.footer}</span>
592
616
  </div>
@@ -594,6 +618,54 @@ export default function HomeContent({ recent, existingFiles, spaces }: { recent:
594
618
  );
595
619
  }
596
620
 
621
+ /* ── Pinned Files Section ── */
622
+ function PinnedFilesSection({ formatTime }: { formatTime: (t: number) => string }) {
623
+ const { t } = useLocale();
624
+ const { pinnedFiles, removePin } = usePinnedFiles();
625
+
626
+ if (pinnedFiles.length === 0) return null;
627
+
628
+ return (
629
+ <section className="mb-12">
630
+ <SectionTitle icon={<Star size={13} />} count={pinnedFiles.length}>
631
+ {t.pinnedFiles.title}
632
+ </SectionTitle>
633
+ <div className="flex flex-col gap-0.5">
634
+ {pinnedFiles.map((filePath) => {
635
+ const name = filePath.split('/').pop() || filePath;
636
+ const dir = filePath.split('/').slice(0, -1).join('/');
637
+ const isCSV = filePath.endsWith('.csv');
638
+ return (
639
+ <div key={filePath} className="group/pin relative">
640
+ <Link
641
+ href={`/view/${encodePath(filePath)}`}
642
+ className="flex items-center gap-3 px-3 py-2 rounded-lg transition-all duration-100 hover:translate-x-0.5 hover:bg-muted"
643
+ >
644
+ <Star size={12} className="shrink-0 fill-[var(--amber)] text-[var(--amber)]" />
645
+ {isCSV
646
+ ? <Table size={12} className="shrink-0 text-success" />
647
+ : <FileText size={12} className="shrink-0 text-muted-foreground" />
648
+ }
649
+ <div className="flex-1 min-w-0">
650
+ <span className="text-sm truncate block text-foreground" suppressHydrationWarning>{name}</span>
651
+ {dir && <span className="text-xs truncate block text-muted-foreground opacity-50" suppressHydrationWarning>{dir}</span>}
652
+ </div>
653
+ </Link>
654
+ <button
655
+ onClick={(e) => { e.preventDefault(); e.stopPropagation(); removePin(filePath); }}
656
+ className="absolute right-2 top-1/2 -translate-y-1/2 hidden group-hover/pin:flex p-1 rounded text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
657
+ title={t.pinnedFiles.removedToast}
658
+ >
659
+ <X size={12} />
660
+ </button>
661
+ </div>
662
+ );
663
+ })}
664
+ </div>
665
+ </section>
666
+ );
667
+ }
668
+
597
669
  /* ── Create Space: title-bar button ── */
598
670
  function CreateSpaceButton({ t }: { t: ReturnType<typeof useLocale>['t'] }) {
599
671
  return (
@@ -6,13 +6,17 @@ import rehypeHighlight from 'rehype-highlight';
6
6
  import rehypeRaw from 'rehype-raw';
7
7
  import rehypeSlug from 'rehype-slug';
8
8
  import { useState, useCallback } from 'react';
9
- import { Copy, Check } from 'lucide-react';
9
+ import { Copy, Check, X } from 'lucide-react';
10
10
  import { copyToClipboard } from '@/lib/clipboard';
11
11
  import { toast } from '@/lib/toast';
12
12
  import type { Components } from 'react-markdown';
13
13
 
14
14
  interface MarkdownViewProps {
15
15
  content: string;
16
+ /** Lines changed by AI (1-indexed). Shows banner + fades after timeout. */
17
+ highlightLines?: number[];
18
+ /** Callback to dismiss the highlight banner */
19
+ onDismissHighlight?: () => void;
16
20
  }
17
21
 
18
22
  function CopyButton({ code }: { code: string }) {
@@ -111,16 +115,47 @@ function extractText(node: React.ReactNode): string {
111
115
  return '';
112
116
  }
113
117
 
114
- export default function MarkdownView({ content }: MarkdownViewProps) {
118
+ export default function MarkdownView({ content, highlightLines, onDismissHighlight }: MarkdownViewProps) {
119
+ const hasHighlights = highlightLines && highlightLines.length > 0;
120
+
115
121
  return (
116
- <div className="prose max-w-none">
117
- <ReactMarkdown
118
- remarkPlugins={[remarkGfm]}
119
- rehypePlugins={[rehypeSlug, rehypeHighlight, rehypeRaw]}
120
- components={components}
121
- >
122
- {content}
123
- </ReactMarkdown>
122
+ <div>
123
+ {/* Change indicator banner */}
124
+ {hasHighlights && (
125
+ <div
126
+ className="mb-4 flex items-center gap-2 rounded-md border px-3 py-2 text-xs animate-in fade-in-0 duration-300"
127
+ style={{
128
+ borderColor: 'color-mix(in srgb, var(--amber) 40%, var(--border))',
129
+ background: 'color-mix(in srgb, var(--amber) 8%, var(--card))',
130
+ color: 'var(--amber)',
131
+ }}
132
+ data-highlight-line
133
+ >
134
+ <span className="w-1.5 h-1.5 rounded-full bg-[var(--amber)] animate-pulse shrink-0" />
135
+ <span className="font-display font-medium flex-1">
136
+ {highlightLines.length} line{highlightLines.length !== 1 ? 's' : ''} updated by AI
137
+ </span>
138
+ {onDismissHighlight && (
139
+ <button
140
+ type="button"
141
+ onClick={onDismissHighlight}
142
+ className="p-0.5 rounded hover:bg-[var(--amber)]/15 transition-colors shrink-0"
143
+ aria-label="Dismiss"
144
+ >
145
+ <X size={12} />
146
+ </button>
147
+ )}
148
+ </div>
149
+ )}
150
+ <div className="prose max-w-none">
151
+ <ReactMarkdown
152
+ remarkPlugins={[remarkGfm]}
153
+ rehypePlugins={[rehypeSlug, rehypeHighlight, rehypeRaw]}
154
+ components={components}
155
+ >
156
+ {content}
157
+ </ReactMarkdown>
158
+ </div>
124
159
  </div>
125
160
  );
126
161
  }
@@ -30,6 +30,7 @@ const DEFAULT_PANEL_WIDTH: Record<PanelId, number> = {
30
30
  echo: 280,
31
31
  agents: 280,
32
32
  discover: 280,
33
+ workflows: 280,
33
34
  };
34
35
 
35
36
  const MIN_PANEL_WIDTH = 240;
@@ -10,11 +10,14 @@ const MIN_WIDTH = 300;
10
10
  const MAX_WIDTH_ABS = 700;
11
11
  const MAX_WIDTH_RATIO = 0.45;
12
12
 
13
+ import type { AcpAgentSelection } from '@/hooks/useAskModal';
14
+
13
15
  interface RightAskPanelProps {
14
16
  open: boolean;
15
17
  onClose: () => void;
16
18
  currentFile?: string;
17
19
  initialMessage?: string;
20
+ initialAcpAgent?: AcpAgentSelection | null;
18
21
  onFirstMessage?: () => void;
19
22
  width: number;
20
23
  onWidthChange: (w: number) => void;
@@ -28,7 +31,7 @@ interface RightAskPanelProps {
28
31
  }
29
32
 
30
33
  export default function RightAskPanel({
31
- open, onClose, currentFile, initialMessage, onFirstMessage,
34
+ open, onClose, currentFile, initialMessage, initialAcpAgent, onFirstMessage,
32
35
  width, onWidthChange, onWidthCommit, askMode, onModeSwitch,
33
36
  maximized = false, onMaximize, sidebarOffset = 0,
34
37
  }: RightAskPanelProps) {
@@ -73,6 +76,7 @@ export default function RightAskPanel({
73
76
  variant="panel"
74
77
  currentFile={open ? currentFile : undefined}
75
78
  initialMessage={initialMessage}
79
+ initialAcpAgent={initialAcpAgent}
76
80
  onFirstMessage={onFirstMessage}
77
81
  onClose={onClose}
78
82
  askMode={askMode}
@@ -3,7 +3,7 @@
3
3
  import { useState, useEffect } from 'react';
4
4
  import Link from 'next/link';
5
5
  import { useRouter, usePathname } from 'next/navigation';
6
- import { Search, PanelLeftClose, PanelLeftOpen, Menu, X, Settings } from 'lucide-react';
6
+ import { Search, PanelLeftClose, PanelLeftOpen, Menu, X, Settings, Trash2 } from 'lucide-react';
7
7
  import FileTree from './FileTree';
8
8
  import SearchModal from './SearchModal';
9
9
  import AskModal from './AskModal';
@@ -117,6 +117,15 @@ export default function Sidebar({ fileTree, collapsed = false, onCollapse, onExp
117
117
  <div className="flex-1 overflow-y-auto min-h-0 px-2 py-2">
118
118
  <FileTree nodes={fileTree} onNavigate={() => setMobileOpen(false)} />
119
119
  </div>
120
+ <div className="px-2 pb-1">
121
+ <Link
122
+ href="/trash"
123
+ className="flex items-center gap-2 px-2 py-1.5 rounded-md text-xs text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
124
+ >
125
+ <Trash2 size={13} />
126
+ <span>{t.trash?.title ?? 'Trash'}</span>
127
+ </Link>
128
+ </div>
120
129
  <SyncStatusBar
121
130
  collapsed={collapsed}
122
131
  onOpenSyncSettings={openSyncSettings}
@@ -13,6 +13,7 @@ import SearchPanel from './panels/SearchPanel';
13
13
  import AgentsPanel from './panels/AgentsPanel';
14
14
  import DiscoverPanel from './panels/DiscoverPanel';
15
15
  import EchoPanel from './panels/EchoPanel';
16
+ import WorkflowsPanel from './panels/WorkflowsPanel';
16
17
 
17
18
  import RightAskPanel from './RightAskPanel';
18
19
  import RightAgentDetailPanel, {
@@ -407,6 +408,9 @@ export default function SidebarLayout({ fileTree, children }: SidebarLayoutProps
407
408
  <div className={`flex flex-col h-full ${lp.activePanel === 'discover' ? '' : 'hidden'}`}>
408
409
  <DiscoverPanel active={lp.activePanel === 'discover'} maximized={lp.panelMaximized} onMaximize={lp.handlePanelMaximize} />
409
410
  </div>
411
+ <div className={`flex flex-col h-full ${lp.activePanel === 'workflows' ? '' : 'hidden'}`}>
412
+ <WorkflowsPanel active={lp.activePanel === 'workflows'} maximized={lp.panelMaximized} onMaximize={lp.handlePanelMaximize} />
413
+ </div>
410
414
  </Panel>
411
415
 
412
416
  {/* ── Right-side Ask AI Panel ── */}
@@ -415,6 +419,7 @@ export default function SidebarLayout({ fileTree, children }: SidebarLayoutProps
415
419
  onClose={ap.closeAskPanel}
416
420
  currentFile={currentFile}
417
421
  initialMessage={ap.askInitialMessage}
422
+ initialAcpAgent={ap.askAcpAgent}
418
423
  onFirstMessage={handleFirstMessage}
419
424
  width={ap.askPanelWidth}
420
425
  onWidthChange={ap.handleAskWidthChange}
@@ -441,6 +446,7 @@ export default function SidebarLayout({ fileTree, children }: SidebarLayoutProps
441
446
  onClose={ap.closeDesktopAskPopup}
442
447
  currentFile={currentFile}
443
448
  initialMessage={ap.askInitialMessage}
449
+ initialAcpAgent={ap.askAcpAgent}
444
450
  onFirstMessage={handleFirstMessage}
445
451
  askMode={ap.askMode}
446
452
  onModeSwitch={ap.handleAskModeSwitch}