@hienlh/ppm 0.12.0 → 0.12.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/web/assets/{audio-preview-D4AxF10w.js → audio-preview-DBOZOGw7.js} +1 -1
  3. package/dist/web/assets/{chat-tab-Bq2hmJ-B.js → chat-tab-BrdZcg-D.js} +3 -3
  4. package/dist/web/assets/{code-editor-CMcDjype.js → code-editor-BjB4rgPf.js} +2 -2
  5. package/dist/web/assets/{conflict-editor-Br-ugFiK.js → conflict-editor-BnXrp55a.js} +1 -1
  6. package/dist/web/assets/{database-viewer-DxP0GmQK.js → database-viewer-D39etQBx.js} +1 -1
  7. package/dist/web/assets/{diff-viewer-oEyE9UwV.js → diff-viewer-NVkEMuQW.js} +1 -1
  8. package/dist/web/assets/{extension-webview-CVqfQGjg.js → extension-webview-AjxJYBif.js} +1 -1
  9. package/dist/web/assets/{image-preview-CY3sVd25.js → image-preview-BDhESiBw.js} +1 -1
  10. package/dist/web/assets/index-BeD-Flot.js +23 -0
  11. package/dist/web/assets/{markdown-renderer-DwqWhkri.js → markdown-renderer-CRA2pyBy.js} +1 -1
  12. package/dist/web/assets/{pdf-preview-Cl95qWE_.js → pdf-preview-BbA8cq4C.js} +1 -1
  13. package/dist/web/assets/{port-forwarding-tab-iJ3MAjXa.js → port-forwarding-tab-D-Ka0Xhw.js} +1 -1
  14. package/dist/web/assets/{postgres-viewer-Do_w0Cji.js → postgres-viewer-B4YwHwTt.js} +1 -1
  15. package/dist/web/assets/{settings-tab-DyBeLmUh.js → settings-tab-ycdscbD8.js} +1 -1
  16. package/dist/web/assets/{sqlite-viewer-oZkGJfW2.js → sqlite-viewer-CBR5cebG.js} +1 -1
  17. package/dist/web/assets/{terminal-tab-UoDiWvzG.js → terminal-tab-DATStOUT.js} +1 -1
  18. package/dist/web/assets/{video-preview-3MbkDYcA.js → video-preview-B-DptaDM.js} +1 -1
  19. package/dist/web/index.html +1 -1
  20. package/dist/web/sw.js +1 -1
  21. package/package.json +1 -1
  22. package/src/providers/claude-agent-sdk.ts +12 -2
  23. package/src/web/components/layout/draggable-tab.tsx +12 -0
  24. package/src/web/components/layout/tab-bar.tsx +66 -0
  25. package/dist/web/assets/index-BDRoldC9.js +0 -23
@@ -20,6 +20,12 @@ import { useTabDrag } from "@/hooks/use-tab-drag";
20
20
  import { useTouchTabDrag, wasTouchDragRecent } from "@/hooks/use-touch-tab-drag";
21
21
  import { openCommandPalette } from "@/hooks/use-global-keybindings";
22
22
  import { api, projectUrl } from "@/lib/api-client";
23
+ import { useProjectTags } from "@/components/chat/tag-filter-chips";
24
+ import {
25
+ ContextMenuSub, ContextMenuSubTrigger, ContextMenuSubContent,
26
+ ContextMenuItem, ContextMenuSeparator,
27
+ } from "@/components/ui/context-menu";
28
+ import { Tag, Check } from "lucide-react";
23
29
  import { useNotificationStore, notificationColor } from "@/stores/notification-store";
24
30
  import { useStreamingStore } from "@/stores/streaming-store";
25
31
  import { useTabOverflow, getHiddenUnreadDirection } from "@/hooks/use-tab-overflow";
@@ -64,6 +70,37 @@ export const TabBar = memo(function TabBar({ panelId }: TabBarProps) {
64
70
  useTabDrag(effectivePanelId);
65
71
  const { handleTouchStart, handleTouchMove, handleTouchEnd } = useTouchTabDrag(effectivePanelId);
66
72
 
73
+ const { projectTags, loadTags } = useProjectTags(activeProject?.name);
74
+ const [sessionTagMap, setSessionTagMap] = useState<Record<string, { id: number; name: string; color: string }>>({});
75
+
76
+ // Fetch session tags for open chat tabs
77
+ const chatSessionIds = tabs.filter((t) => t.type === "chat" && t.metadata?.sessionId).map((t) => t.metadata!.sessionId as string);
78
+ useEffect(() => {
79
+ if (!activeProject?.name || chatSessionIds.length === 0) return;
80
+ api.get<{ sessions: { id: string; tag?: { id: number; name: string; color: string } | null }[] }>(
81
+ `${projectUrl(activeProject.name)}/chat/sessions?limit=50`,
82
+ ).then((data) => {
83
+ const map: Record<string, { id: number; name: string; color: string }> = {};
84
+ for (const s of data.sessions) { if (s.tag) map[s.id] = s.tag; }
85
+ setSessionTagMap(map);
86
+ }).catch(() => {});
87
+ }, [activeProject?.name, chatSessionIds.join(",")]); // eslint-disable-line react-hooks/exhaustive-deps
88
+
89
+ const assignTagToSession = useCallback(async (sessionId: string, tagId: number | null) => {
90
+ if (!activeProject?.name) return;
91
+ try {
92
+ if (tagId !== null) {
93
+ await api.patch(`${projectUrl(activeProject.name)}/chat/sessions/${sessionId}/tag`, { tagId });
94
+ const tag = projectTags.find((t) => t.id === tagId);
95
+ if (tag) setSessionTagMap((prev) => ({ ...prev, [sessionId]: { id: tag.id, name: tag.name, color: tag.color } }));
96
+ } else {
97
+ await api.del(`${projectUrl(activeProject.name)}/chat/sessions/${sessionId}/tag`);
98
+ setSessionTagMap((prev) => { const n = { ...prev }; delete n[sessionId]; return n; });
99
+ }
100
+ loadTags();
101
+ } catch { /* silent */ }
102
+ }, [activeProject?.name, projectTags, loadTags]);
103
+
67
104
  const notifications = useNotificationStore((s) => s.notifications);
68
105
  const streamingSessions = useStreamingStore((s) => s.sessions);
69
106
  const { canScrollLeft, canScrollRight, scrollLeft: doScrollLeft, scrollRight: doScrollRight } =
@@ -219,6 +256,35 @@ export const TabBar = memo(function TabBar({ panelId }: TabBarProps) {
219
256
  }}
220
257
  onRename={tab.type === "chat" ? (title) => handleRenameTab(tab, title) : undefined}
221
258
  onContextAction={(action) => handleTabContextAction(tab, action)}
259
+ tagColor={sessionId ? sessionTagMap[sessionId]?.color : undefined}
260
+ extraMenuContent={sessionId && projectTags.length > 0 ? (
261
+ <>
262
+ <ContextMenuSub>
263
+ <ContextMenuSubTrigger>
264
+ <Tag className="size-3.5 mr-2" />
265
+ Set Tag
266
+ </ContextMenuSubTrigger>
267
+ <ContextMenuSubContent>
268
+ {projectTags.map((pt) => (
269
+ <ContextMenuItem key={pt.id} onClick={() => assignTagToSession(sessionId, pt.id)}>
270
+ <span className="size-2.5 rounded-full mr-2 shrink-0" style={{ backgroundColor: pt.color }} />
271
+ {pt.name}
272
+ {sessionTagMap[sessionId]?.id === pt.id && <Check className="size-3 ml-auto" />}
273
+ </ContextMenuItem>
274
+ ))}
275
+ {sessionTagMap[sessionId] && (
276
+ <>
277
+ <ContextMenuSeparator />
278
+ <ContextMenuItem onClick={() => assignTagToSession(sessionId, null)}>
279
+ Remove tag
280
+ </ContextMenuItem>
281
+ </>
282
+ )}
283
+ </ContextMenuSubContent>
284
+ </ContextMenuSub>
285
+ <ContextMenuSeparator />
286
+ </>
287
+ ) : undefined}
222
288
  />
223
289
  );
224
290
  })}