@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.
- package/CHANGELOG.md +5 -0
- package/assets/skills/ppm/SKILL.md +1 -1
- package/assets/skills/ppm/references/http-api.md +1 -1
- package/dist/web/assets/{audio-preview-C72DxZlh.js → audio-preview-ChW8o94G.js} +1 -1
- package/dist/web/assets/{chat-tab-qgyQnFJ_.js → chat-tab-Cb6WYh48.js} +3 -3
- package/dist/web/assets/{code-editor-aPhcRZIY.js → code-editor-5eDY0yLp.js} +2 -2
- package/dist/web/assets/{conflict-editor-BYD68a6a.js → conflict-editor-BWKRVyiI.js} +1 -1
- package/dist/web/assets/{database-viewer-Cac1mnS6.js → database-viewer-CWLQCCya.js} +1 -1
- package/dist/web/assets/{diff-viewer-CZ5tpt01.js → diff-viewer-B100c5XJ.js} +1 -1
- package/dist/web/assets/{extension-webview-C3i2nQo9.js → extension-webview-Bh3FG7rR.js} +1 -1
- package/dist/web/assets/{glide-data-grid-Cc6jVw3k.js → glide-data-grid-BHQtFmaN.js} +1 -1
- package/dist/web/assets/{image-preview-wFdsrxkr.js → image-preview-BVZxfrHn.js} +1 -1
- package/dist/web/assets/index-DHSpUiaA.js +27 -0
- package/dist/web/assets/keybindings-store-Dnaz-ex6.js +1 -0
- package/dist/web/assets/{markdown-renderer-Dwd_3EY-.js → markdown-renderer-By7jqoGs.js} +1 -1
- package/dist/web/assets/notification-store-CWmtK9r6.js +1 -0
- package/dist/web/assets/{pdf-preview-DcYf-I_N.js → pdf-preview-Dg8VZORX.js} +1 -1
- package/dist/web/assets/{port-forwarding-tab-78AV7Pgf.js → port-forwarding-tab-BYvCKlre.js} +1 -1
- package/dist/web/assets/{postgres-viewer-CNqfv5Bv.js → postgres-viewer-Ch_R_XV3.js} +1 -1
- package/dist/web/assets/{settings-tab-DSaa1AhM.js → settings-tab-DtHKCRVV.js} +1 -1
- package/dist/web/assets/{sql-query-editor-CxhCLyln.js → sql-query-editor-DpVEoDIq.js} +1 -1
- package/dist/web/assets/{sqlite-viewer-YD33R12u.js → sqlite-viewer-N9G3rleA.js} +1 -1
- package/dist/web/assets/{terminal-tab-B7GojM_W.js → terminal-tab-DD8D49vG.js} +1 -1
- package/dist/web/assets/{video-preview-Clb97O7c.js → video-preview-ZyuoVek0.js} +1 -1
- package/dist/web/index.html +1 -1
- package/dist/web/sw.js +1 -1
- package/package.json +1 -1
- package/src/server/ws/chat.ts +2 -2
- package/src/services/db.service.ts +17 -6
- package/src/web/components/layout/notification-bell-popover.tsx +15 -3
- package/src/web/components/layout/tab-pool.tsx +19 -3
- package/dist/web/assets/index-DH-Rf_OH.js +0 -27
- package/dist/web/assets/keybindings-store-guaRvV18.js +0 -1
- 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">
|
|
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
|
|
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 }[] = [];
|