@hienlh/ppm 0.13.35 → 0.13.36

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 (34) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/assets/skills/ppm/SKILL.md +1 -1
  3. package/assets/skills/ppm/references/http-api.md +1 -1
  4. package/dist/web/assets/{audio-preview-C72DxZlh.js → audio-preview-ChW8o94G.js} +1 -1
  5. package/dist/web/assets/{chat-tab-qgyQnFJ_.js → chat-tab-Cb6WYh48.js} +3 -3
  6. package/dist/web/assets/{code-editor-aPhcRZIY.js → code-editor-5eDY0yLp.js} +2 -2
  7. package/dist/web/assets/{conflict-editor-BYD68a6a.js → conflict-editor-BWKRVyiI.js} +1 -1
  8. package/dist/web/assets/{database-viewer-Cac1mnS6.js → database-viewer-CWLQCCya.js} +1 -1
  9. package/dist/web/assets/{diff-viewer-CZ5tpt01.js → diff-viewer-B100c5XJ.js} +1 -1
  10. package/dist/web/assets/{extension-webview-C3i2nQo9.js → extension-webview-Bh3FG7rR.js} +1 -1
  11. package/dist/web/assets/{glide-data-grid-Cc6jVw3k.js → glide-data-grid-BHQtFmaN.js} +1 -1
  12. package/dist/web/assets/{image-preview-wFdsrxkr.js → image-preview-BVZxfrHn.js} +1 -1
  13. package/dist/web/assets/index-DHSpUiaA.js +27 -0
  14. package/dist/web/assets/keybindings-store-Dnaz-ex6.js +1 -0
  15. package/dist/web/assets/{markdown-renderer-Dwd_3EY-.js → markdown-renderer-By7jqoGs.js} +1 -1
  16. package/dist/web/assets/notification-store-CWmtK9r6.js +1 -0
  17. package/dist/web/assets/{pdf-preview-DcYf-I_N.js → pdf-preview-Dg8VZORX.js} +1 -1
  18. package/dist/web/assets/{port-forwarding-tab-78AV7Pgf.js → port-forwarding-tab-BYvCKlre.js} +1 -1
  19. package/dist/web/assets/{postgres-viewer-CNqfv5Bv.js → postgres-viewer-Ch_R_XV3.js} +1 -1
  20. package/dist/web/assets/{settings-tab-DSaa1AhM.js → settings-tab-DtHKCRVV.js} +1 -1
  21. package/dist/web/assets/{sql-query-editor-CxhCLyln.js → sql-query-editor-DpVEoDIq.js} +1 -1
  22. package/dist/web/assets/{sqlite-viewer-YD33R12u.js → sqlite-viewer-N9G3rleA.js} +1 -1
  23. package/dist/web/assets/{terminal-tab-B7GojM_W.js → terminal-tab-DD8D49vG.js} +1 -1
  24. package/dist/web/assets/{video-preview-Clb97O7c.js → video-preview-ZyuoVek0.js} +1 -1
  25. package/dist/web/index.html +1 -1
  26. package/dist/web/sw.js +1 -1
  27. package/package.json +1 -1
  28. package/src/server/ws/chat.ts +2 -2
  29. package/src/services/db.service.ts +17 -6
  30. package/src/web/components/layout/notification-bell-popover.tsx +15 -3
  31. package/src/web/components/layout/tab-pool.tsx +19 -3
  32. package/dist/web/assets/index-DH-Rf_OH.js +0 -27
  33. package/dist/web/assets/keybindings-store-guaRvV18.js +0 -1
  34. package/dist/web/assets/notification-store-DyA4wmbn.js +0 -1
@@ -2,17 +2,19 @@ import { useState, useRef } from "react";
2
2
  import { createPortal } from "react-dom";
3
3
  import { useShallow } from "zustand/shallow";
4
4
  import { useNotificationStore, selectTotalUnread, notificationTint } from "@/stores/notification-store";
5
- import { useProjectStore } from "@/stores/project-store";
5
+ import { useProjectStore, resolveOrder } from "@/stores/project-store";
6
6
  import { useTabStore } from "@/stores/tab-store";
7
7
  import { Bell, BellOff } from "lucide-react";
8
8
  import { cn } from "@/lib/utils";
9
+ import { resolveProjectColor } from "@/lib/project-palette";
9
10
 
10
11
  /** Bell button with unread count badge + popover listing unread sessions */
11
12
  export function NotificationBellPopover({ expanded }: { expanded: boolean }) {
12
13
  const notifications = useNotificationStore((s) => s.notifications);
13
14
  const clearAll = useNotificationStore((s) => s.clearAll);
14
15
  const totalUnread = useNotificationStore(selectTotalUnread);
15
- const { projects, setActiveProject } = useProjectStore(useShallow((s) => ({ projects: s.projects, setActiveProject: s.setActiveProject })));
16
+ const { projects, setActiveProject, customOrder } = useProjectStore(useShallow((s) => ({ projects: s.projects, setActiveProject: s.setActiveProject, customOrder: s.customOrder })));
17
+ const ordered = resolveOrder(projects, customOrder);
16
18
  const openTab = useTabStore((s) => s.openTab);
17
19
  const [open, setOpen] = useState(false);
18
20
  const btnRef = useRef<HTMLButtonElement>(null);
@@ -39,6 +41,13 @@ export function NotificationBellPopover({ expanded }: { expanded: boolean }) {
39
41
  setOpen(false);
40
42
  };
41
43
 
44
+ // Resolve project color by name
45
+ const getProjectColor = (name: string) => {
46
+ const idx = ordered.findIndex((p) => p.name === name);
47
+ if (idx === -1) return undefined;
48
+ return resolveProjectColor(ordered[idx]!.color, idx);
49
+ };
50
+
42
51
  // Group notifications by projectName
43
52
  const grouped = new Map<string, Array<{ sessionId: string; count: number; type: string; sessionTitle: string | null }>>();
44
53
  for (const [sessionId, entry] of notifications) {
@@ -97,7 +106,10 @@ export function NotificationBellPopover({ expanded }: { expanded: boolean }) {
97
106
  <div className="py-1">
98
107
  {[...grouped.entries()].map(([projectName, sessions]) => (
99
108
  <div key={projectName}>
100
- <div className="px-3 py-1 text-[10px] font-medium text-text-subtle uppercase tracking-wider">{projectName}</div>
109
+ <div className="px-3 py-1 text-[10px] font-medium text-text-subtle uppercase tracking-wider flex items-center gap-1.5">
110
+ <span className="size-2 rounded-full shrink-0" style={{ background: getProjectColor(projectName) || "currentColor" }} />
111
+ {projectName}
112
+ </div>
101
113
  {sessions.map(({ sessionId, type, sessionTitle }) => (
102
114
  <button
103
115
  key={sessionId}
@@ -80,6 +80,8 @@ export function registerPanelSlot(panelId: string, el: HTMLDivElement | null) {
80
80
  // TabPool — renders all tabs in a hidden container, reparents into slots
81
81
  // ---------------------------------------------------------------------------
82
82
  export function TabPool() {
83
+ const hiddenRef = useRef<HTMLDivElement>(null);
84
+
83
85
  // Re-render when slots change (panel mount/unmount)
84
86
  useSyncExternalStore(
85
87
  (cb) => registry.subscribe(cb),
@@ -115,7 +117,7 @@ export function TabPool() {
115
117
  return (
116
118
  // Off-screen mount point. React mounts tab wrappers here, then
117
119
  // useLayoutEffect moves them into panel slots before the browser paints.
118
- <div style={{ position: "fixed", top: 0, left: 0, width: 0, height: 0, overflow: "hidden", pointerEvents: "none", visibility: "hidden" }}>
120
+ <div ref={hiddenRef} style={{ position: "fixed", top: 0, left: 0, width: 0, height: 0, overflow: "hidden", pointerEvents: "none", visibility: "hidden" }}>
119
121
  {tabEntries.map((entry) => (
120
122
  <ReparentingTab
121
123
  key={entry.tabId}
@@ -124,6 +126,7 @@ export function TabPool() {
124
126
  type={entry.type}
125
127
  metadata={entry.metadata}
126
128
  isActive={entry.isActive}
129
+ hiddenContainer={hiddenRef}
127
130
  />
128
131
  ))}
129
132
  </div>
@@ -139,9 +142,10 @@ interface ReparentingTabProps {
139
142
  type: TabType;
140
143
  metadata?: Record<string, unknown>;
141
144
  isActive: boolean;
145
+ hiddenContainer: React.RefObject<HTMLDivElement | null>;
142
146
  }
143
147
 
144
- function ReparentingTab({ tabId, panelId, type, metadata, isActive }: ReparentingTabProps) {
148
+ function ReparentingTab({ tabId, panelId, type, metadata, isActive, hiddenContainer }: ReparentingTabProps) {
145
149
  const wrapperRef = useRef<HTMLDivElement>(null);
146
150
  const Component = TAB_COMPONENTS[type];
147
151
 
@@ -154,7 +158,19 @@ function ReparentingTab({ tabId, panelId, type, metadata, isActive }: Reparentin
154
158
  useLayoutEffect(() => {
155
159
  const wrapper = wrapperRef.current;
156
160
  const slot = registry.get(panelId);
157
- if (!wrapper || !slot || wrapper.parentElement === slot) return;
161
+ if (!wrapper) return;
162
+
163
+ // Panel slot not mounted (e.g., mobile renders only the focused panel).
164
+ // Move wrapper back to hidden container so it doesn't overlap in the wrong slot.
165
+ if (!slot) {
166
+ const hidden = hiddenContainer.current;
167
+ if (hidden && wrapper.parentElement !== hidden) {
168
+ hidden.appendChild(wrapper);
169
+ }
170
+ return;
171
+ }
172
+
173
+ if (wrapper.parentElement === slot) return;
158
174
 
159
175
  // Save scroll positions — appendChild resets them during the DOM move
160
176
  const scrollables: { el: Element; top: number; left: number }[] = [];