nlm-memory 0.4.1 → 0.5.0
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/dist/cli/nlm.js +221 -32
- package/dist/cli/nlm.js.map +1 -1
- package/dist/core/adapters/cursor.d.ts +45 -0
- package/dist/core/adapters/cursor.js +397 -0
- package/dist/core/adapters/cursor.js.map +1 -0
- package/dist/core/adapters/from-source.js +10 -0
- package/dist/core/adapters/from-source.js.map +1 -1
- package/dist/core/adapters/windsurf.d.ts +44 -0
- package/dist/core/adapters/windsurf.js +299 -0
- package/dist/core/adapters/windsurf.js.map +1 -0
- package/dist/core/hook/claude-settings.d.ts +12 -5
- package/dist/core/hook/claude-settings.js +21 -6
- package/dist/core/hook/claude-settings.js.map +1 -1
- package/dist/core/sources/source-registry.d.ts +1 -1
- package/dist/core/sources/source-registry.js +18 -0
- package/dist/core/sources/source-registry.js.map +1 -1
- package/dist/core/storage/sqlite-session-store.d.ts +2 -0
- package/dist/core/storage/sqlite-session-store.js +38 -2
- package/dist/core/storage/sqlite-session-store.js.map +1 -1
- package/dist/hook/hook-auth.d.ts +13 -0
- package/dist/hook/hook-auth.js +19 -0
- package/dist/hook/hook-auth.js.map +1 -0
- package/dist/hook/prompt-recall-hook.js +7 -1
- package/dist/hook/prompt-recall-hook.js.map +1 -1
- package/dist/hook/session-start-hook.js +4 -1
- package/dist/hook/session-start-hook.js.map +1 -1
- package/dist/hook/stop-hook.js +4 -1
- package/dist/hook/stop-hook.js.map +1 -1
- package/dist/http/app.d.ts +2 -0
- package/dist/http/app.js +74 -0
- package/dist/http/app.js.map +1 -1
- package/dist/install/claude-code.js +1 -1
- package/dist/install/claude-code.js.map +1 -1
- package/dist/install/cursor.d.ts +25 -0
- package/dist/install/cursor.js +43 -0
- package/dist/install/cursor.js.map +1 -0
- package/dist/install/nlm-dir-perms.d.ts +19 -0
- package/dist/install/nlm-dir-perms.js +43 -0
- package/dist/install/nlm-dir-perms.js.map +1 -0
- package/dist/install/ollama.d.ts +18 -1
- package/dist/install/ollama.js +68 -10
- package/dist/install/ollama.js.map +1 -1
- package/dist/install/setup.d.ts +4 -0
- package/dist/install/setup.js +141 -18
- package/dist/install/setup.js.map +1 -1
- package/dist/install/windsurf.d.ts +25 -0
- package/dist/install/windsurf.js +43 -0
- package/dist/install/windsurf.js.map +1 -0
- package/dist/shared/types.d.ts +4 -0
- package/dist/ui/assets/{index-BA6IpU8g.css → index-C8cpwbYJ.css} +1 -1
- package/dist/ui/assets/index-CB50QnL-.js +69 -0
- package/dist/ui/index.html +2 -2
- package/logs/CHANGELOG/CHANGELOG-2026.md +186 -0
- package/logs/CHANGELOG/CHANGELOG.md +107 -235
- package/migrations/014_sources_cursor.sql +30 -0
- package/migrations/015_sources_windsurf.sql +30 -0
- package/package.json +1 -1
- package/plugin/scripts/prompt-recall-hook.mjs +55 -4
- package/plugin/scripts/stop-hook.mjs +57 -6
- package/src/cli/nlm.ts +224 -31
- package/src/core/adapters/cursor.ts +486 -0
- package/src/core/adapters/from-source.ts +10 -0
- package/src/core/adapters/windsurf.ts +386 -0
- package/src/core/hook/claude-settings.ts +30 -9
- package/src/core/sources/source-registry.ts +19 -1
- package/src/core/storage/sqlite-session-store.ts +46 -1
- package/src/hook/hook-auth.ts +18 -0
- package/src/hook/prompt-recall-hook.ts +7 -1
- package/src/hook/session-start-hook.ts +4 -1
- package/src/hook/stop-hook.ts +4 -1
- package/src/http/app.ts +78 -0
- package/src/install/claude-code.ts +1 -1
- package/src/install/cursor.ts +68 -0
- package/src/install/nlm-dir-perms.ts +55 -0
- package/src/install/ollama.ts +86 -10
- package/src/install/setup.ts +138 -17
- package/src/install/windsurf.ts +68 -0
- package/src/shared/types.ts +4 -0
- package/src/ui/components/SessionDrawer.tsx +97 -34
- package/src/ui/pages/River.tsx +90 -44
- package/src/ui/pages/Search.tsx +357 -64
- package/src/ui/pages/Thread.tsx +267 -56
- package/src/ui/styles.css +129 -5
- package/tests/integration/getbyids-sqlite.test.ts +40 -0
- package/tests/integration/hook-claude-settings.test.ts +14 -1
- package/tests/integration/mcp.test.ts +12 -0
- package/tests/integration/source-registry.test.ts +5 -3
- package/tests/unit/core/adapters/cursor.test.ts +485 -0
- package/tests/unit/core/adapters/windsurf.test.ts +416 -0
- package/dist/ui/assets/index-B_qIVV0k.js +0 -69
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SessionDrawer — right-side detail panel for a single session.
|
|
3
|
-
* Fetches /api/session/:id on open. Shared between Thread (entity timeline)
|
|
4
|
-
* and Pulse (Recent sessions) — and ready for any future caller.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
1
|
import { useEffect, useState } from "react";
|
|
8
2
|
import { Link } from "react-router-dom";
|
|
9
3
|
import { SessionDrawerSkeleton } from "./Skeleton.js";
|
|
@@ -21,16 +15,20 @@ interface SessionDetail {
|
|
|
21
15
|
entities: string[];
|
|
22
16
|
decisions: string[];
|
|
23
17
|
open: string[];
|
|
18
|
+
supersededBy: string | null;
|
|
19
|
+
supersedes: string[];
|
|
24
20
|
}
|
|
25
21
|
|
|
26
22
|
interface SessionDrawerProps {
|
|
27
23
|
sessionId: string;
|
|
28
24
|
onClose: () => void;
|
|
29
|
-
/** Optional dot color (Thread passes the entity color). */
|
|
30
25
|
entityColor?: string;
|
|
26
|
+
onNavigate?: (id: string) => void;
|
|
27
|
+
prevSessionId?: string | null;
|
|
28
|
+
nextSessionId?: string | null;
|
|
31
29
|
}
|
|
32
30
|
|
|
33
|
-
export function SessionDrawer({ sessionId, onClose, entityColor }: SessionDrawerProps) {
|
|
31
|
+
export function SessionDrawer({ sessionId, onClose, entityColor, onNavigate, prevSessionId, nextSessionId }: SessionDrawerProps) {
|
|
34
32
|
const [session, setSession] = useState<SessionDetail | null>(null);
|
|
35
33
|
const [error, setError] = useState<string | null>(null);
|
|
36
34
|
|
|
@@ -54,16 +52,22 @@ export function SessionDrawer({ sessionId, onClose, entityColor }: SessionDrawer
|
|
|
54
52
|
entities: Array.isArray(raw["entities"]) ? (raw["entities"] as string[]) : [],
|
|
55
53
|
decisions: Array.isArray(raw["decisions"]) ? (raw["decisions"] as string[]) : [],
|
|
56
54
|
open: Array.isArray(raw["open"]) ? (raw["open"] as string[]) : [],
|
|
55
|
+
supersededBy: typeof raw["supersededBy"] === "string" ? (raw["supersededBy"] as string) : null,
|
|
56
|
+
supersedes: Array.isArray(raw["supersedes"]) ? (raw["supersedes"] as string[]) : [],
|
|
57
57
|
});
|
|
58
58
|
})
|
|
59
59
|
.catch((e: unknown) => setError(e instanceof Error ? e.message : String(e)));
|
|
60
60
|
}, [sessionId]);
|
|
61
61
|
|
|
62
62
|
useEffect(() => {
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
const handler = (e: KeyboardEvent) => {
|
|
64
|
+
if (e.key === "Escape") { onClose(); return; }
|
|
65
|
+
if (e.key === "ArrowLeft" && prevSessionId != null && onNavigate) onNavigate(prevSessionId);
|
|
66
|
+
if (e.key === "ArrowRight" && nextSessionId != null && onNavigate) onNavigate(nextSessionId);
|
|
67
|
+
};
|
|
68
|
+
window.addEventListener("keydown", handler);
|
|
69
|
+
return () => window.removeEventListener("keydown", handler);
|
|
70
|
+
}, [onClose, onNavigate, prevSessionId, nextSessionId]);
|
|
67
71
|
|
|
68
72
|
return (
|
|
69
73
|
<>
|
|
@@ -72,32 +76,75 @@ export function SessionDrawer({ sessionId, onClose, entityColor }: SessionDrawer
|
|
|
72
76
|
<header className="drawer-head">
|
|
73
77
|
{entityColor && <span className="dot" style={{ background: entityColor }} />}
|
|
74
78
|
<h3 className="drawer-title">{session?.label ?? sessionId}</h3>
|
|
79
|
+
{(prevSessionId != null || nextSessionId != null) && onNavigate && (
|
|
80
|
+
<div className="drawer-nav">
|
|
81
|
+
<button
|
|
82
|
+
type="button"
|
|
83
|
+
className="drawer-nav-btn"
|
|
84
|
+
disabled={prevSessionId == null}
|
|
85
|
+
onClick={() => prevSessionId && onNavigate(prevSessionId)}
|
|
86
|
+
aria-label="Previous session"
|
|
87
|
+
>
|
|
88
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M15 18l-6-6 6-6"/></svg>
|
|
89
|
+
</button>
|
|
90
|
+
<button
|
|
91
|
+
type="button"
|
|
92
|
+
className="drawer-nav-btn"
|
|
93
|
+
disabled={nextSessionId == null}
|
|
94
|
+
onClick={() => nextSessionId && onNavigate(nextSessionId)}
|
|
95
|
+
aria-label="Next session"
|
|
96
|
+
>
|
|
97
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M9 18l6-6-6-6"/></svg>
|
|
98
|
+
</button>
|
|
99
|
+
</div>
|
|
100
|
+
)}
|
|
75
101
|
<button type="button" className="drawer-close" onClick={onClose} aria-label="Close">×</button>
|
|
76
102
|
</header>
|
|
77
103
|
{error && <div className="muted error drawer-body">{error}</div>}
|
|
78
104
|
{!session && !error && <SessionDrawerSkeleton />}
|
|
79
105
|
{session && (
|
|
80
106
|
<div className="drawer-body">
|
|
81
|
-
|
|
82
|
-
<
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
107
|
+
{session.supersededBy && (
|
|
108
|
+
<div className="supersedence-banner supersedence-banner--superseded">
|
|
109
|
+
<span className="supersedence-label">Superseded by</span>
|
|
110
|
+
{onNavigate ? (
|
|
111
|
+
<button
|
|
112
|
+
type="button"
|
|
113
|
+
className="supersedence-link"
|
|
114
|
+
onClick={() => onNavigate(session.supersededBy!)}
|
|
115
|
+
>
|
|
116
|
+
{session.supersededBy}
|
|
117
|
+
</button>
|
|
118
|
+
) : (
|
|
119
|
+
<span className="supersedence-id mono small">{session.supersededBy}</span>
|
|
120
|
+
)}
|
|
121
|
+
</div>
|
|
122
|
+
)}
|
|
123
|
+
{session.supersedes.length > 0 && (
|
|
124
|
+
<div className="supersedence-banner supersedence-banner--supersedes">
|
|
125
|
+
<span className="supersedence-label">Supersedes</span>
|
|
126
|
+
<span className="supersedence-ids">
|
|
127
|
+
{session.supersedes.map((sid) => (
|
|
128
|
+
onNavigate ? (
|
|
129
|
+
<button
|
|
130
|
+
key={sid}
|
|
131
|
+
type="button"
|
|
132
|
+
className="supersedence-link"
|
|
133
|
+
onClick={() => onNavigate(sid)}
|
|
134
|
+
>
|
|
135
|
+
{sid}
|
|
136
|
+
</button>
|
|
137
|
+
) : (
|
|
138
|
+
<span key={sid} className="supersedence-id mono small">{sid}</span>
|
|
139
|
+
)
|
|
99
140
|
))}
|
|
100
|
-
</
|
|
141
|
+
</span>
|
|
142
|
+
</div>
|
|
143
|
+
)}
|
|
144
|
+
{session.summary && (
|
|
145
|
+
<>
|
|
146
|
+
<h4 className="drawer-section">Summary</h4>
|
|
147
|
+
<p className="drawer-paragraph">{session.summary}</p>
|
|
101
148
|
</>
|
|
102
149
|
)}
|
|
103
150
|
{session.decisions.length > 0 && (
|
|
@@ -116,12 +163,28 @@ export function SessionDrawer({ sessionId, onClose, entityColor }: SessionDrawer
|
|
|
116
163
|
</ul>
|
|
117
164
|
</>
|
|
118
165
|
)}
|
|
119
|
-
{session.
|
|
166
|
+
{session.entities.length > 0 && (
|
|
120
167
|
<>
|
|
121
|
-
<h4 className="drawer-section">
|
|
122
|
-
<
|
|
168
|
+
<h4 className="drawer-section">Entities</h4>
|
|
169
|
+
<div className="entity-chips">
|
|
170
|
+
{session.entities.map((e) => (
|
|
171
|
+
<Link key={e} to={`/thread?entity=${encodeURIComponent(e)}`} className="chip" onClick={onClose}>{e}</Link>
|
|
172
|
+
))}
|
|
173
|
+
</div>
|
|
123
174
|
</>
|
|
124
175
|
)}
|
|
176
|
+
<dl className="kv-list">
|
|
177
|
+
<dt className="kv-label">Status</dt>
|
|
178
|
+
<dd className="kv-value"><span className={`chip-inline status-${session.status}`}>{session.status}</span></dd>
|
|
179
|
+
<dt className="kv-label">Started</dt>
|
|
180
|
+
<dd className="kv-value mono small">{session.startedAt ?? "—"}</dd>
|
|
181
|
+
<dt className="kv-label">Duration</dt>
|
|
182
|
+
<dd className="kv-value">{session.durationMin ?? "—"} min</dd>
|
|
183
|
+
<dt className="kv-label">Runtime</dt>
|
|
184
|
+
<dd className="kv-value mono small">{session.runtime}</dd>
|
|
185
|
+
<dt className="kv-label">Session ID</dt>
|
|
186
|
+
<dd className="kv-value mono small">{session.id}</dd>
|
|
187
|
+
</dl>
|
|
125
188
|
{session.body && (
|
|
126
189
|
<>
|
|
127
190
|
<h4 className="drawer-section">Transcript excerpt</h4>
|
package/src/ui/pages/River.tsx
CHANGED
|
@@ -3,6 +3,7 @@ import { useNavigate } from "react-router-dom";
|
|
|
3
3
|
import { useDataset, relativeAge } from "../lib/dataset.js";
|
|
4
4
|
import type { DatasetSession } from "../lib/dataset.js";
|
|
5
5
|
import { SessionDrawer } from "../components/SessionDrawer.js";
|
|
6
|
+
import { Skeleton } from "../components/Skeleton.js";
|
|
6
7
|
import { readViewSettings } from "../lib/view-settings.js";
|
|
7
8
|
|
|
8
9
|
type Span = "7d" | "30d" | "90d" | "all";
|
|
@@ -68,7 +69,12 @@ export function RiverPage() {
|
|
|
68
69
|
}))
|
|
69
70
|
.sort((a, b) => b.total - a.total)
|
|
70
71
|
.slice(0, 24);
|
|
71
|
-
|
|
72
|
+
const recentEntities = new Set<string>(
|
|
73
|
+
filtered
|
|
74
|
+
.filter(s => s.started_at && (now - Date.parse(s.started_at)) < 86_400_000)
|
|
75
|
+
.flatMap(s => s.entities)
|
|
76
|
+
);
|
|
77
|
+
return { dates, laneRows, total: filtered.length, recentEntities };
|
|
72
78
|
}, [data, span]);
|
|
73
79
|
|
|
74
80
|
const onCellClick = (entity: string, date: string) => {
|
|
@@ -135,7 +141,19 @@ export function RiverPage() {
|
|
|
135
141
|
setDragRange(null);
|
|
136
142
|
};
|
|
137
143
|
|
|
138
|
-
if (loading && !data) return
|
|
144
|
+
if (loading && !data) return (
|
|
145
|
+
<div className="page-pad">
|
|
146
|
+
<div className="river-toolbar"><Skeleton h={22} w={80} /></div>
|
|
147
|
+
<div className="card" style={{ padding: 12 }}>
|
|
148
|
+
{Array.from({ length: 8 }).map((_, i) => (
|
|
149
|
+
<div key={i} className="river-row" style={{ marginBottom: 3 }}>
|
|
150
|
+
<Skeleton h={14} w={160} />
|
|
151
|
+
<Skeleton h={20} />
|
|
152
|
+
</div>
|
|
153
|
+
))}
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
);
|
|
139
157
|
if (error && !data) return <div className="page-pad"><div className="muted error">{error}</div></div>;
|
|
140
158
|
if (!data || !view) return null;
|
|
141
159
|
|
|
@@ -153,6 +171,13 @@ export function RiverPage() {
|
|
|
153
171
|
onClick={() => setSpan(s)}
|
|
154
172
|
>{s}</button>
|
|
155
173
|
))}
|
|
174
|
+
<div className="river-legend" aria-label="Activity scale">
|
|
175
|
+
<span className="muted small">less</span>
|
|
176
|
+
{([0, 1, 2, 3, 4] as const).map((t) => (
|
|
177
|
+
<span key={t} className={`river-legend-cell tier-${t}`} />
|
|
178
|
+
))}
|
|
179
|
+
<span className="muted small">more</span>
|
|
180
|
+
</div>
|
|
156
181
|
</div>
|
|
157
182
|
|
|
158
183
|
<div
|
|
@@ -173,40 +198,52 @@ export function RiverPage() {
|
|
|
173
198
|
<div className="river-row river-row-dates">
|
|
174
199
|
<div className="river-lane-label river-lane-label--header" aria-hidden="true" />
|
|
175
200
|
<div className="river-cells">
|
|
176
|
-
{
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
<div key={entity} className="river-row">
|
|
183
|
-
<button
|
|
184
|
-
type="button"
|
|
185
|
-
className="river-lane-label"
|
|
186
|
-
onClick={() => navigate(`/thread?entity=${encodeURIComponent(entity)}`)}
|
|
187
|
-
>
|
|
188
|
-
<span className="dot" style={{ background: data.entity_colors[entity] ?? "#666" }} />
|
|
189
|
-
<span className="river-lane-name">{entity}</span>
|
|
190
|
-
<span className="muted small">{total}</span>
|
|
191
|
-
</button>
|
|
192
|
-
<div className="river-cells">
|
|
193
|
-
{view.dates.map((d) => {
|
|
194
|
-
const v = perDate.get(d) ?? 0;
|
|
201
|
+
{(() => {
|
|
202
|
+
const len = view.dates.length;
|
|
203
|
+
const stride = len <= 60 ? 1 : len <= 120 ? 2 : len <= 250 ? 7 : 14;
|
|
204
|
+
return view.dates.map((d, index) => {
|
|
205
|
+
const isMonthStart = d.slice(8, 10) === "01";
|
|
206
|
+
const cls = ["river-date-cell", isMonthStart ? "river-date-cell--month-start" : ""].filter(Boolean).join(" ");
|
|
195
207
|
return (
|
|
196
|
-
<div
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
onMouseEnter={(e) => setHover({ entity, date: d, count: v, x: e.clientX, y: e.clientY })}
|
|
200
|
-
onMouseMove={(e) => setHover({ entity, date: d, count: v, x: e.clientX, y: e.clientY })}
|
|
201
|
-
onMouseLeave={() => setHover(null)}
|
|
202
|
-
onClick={() => v > 0 && onCellClick(entity, d)}
|
|
203
|
-
style={v > 0 ? { cursor: "pointer" } : {}}
|
|
204
|
-
/>
|
|
208
|
+
<div key={d} className={cls} title={d}>
|
|
209
|
+
{index % stride === 0 ? d.slice(5) : ""}
|
|
210
|
+
</div>
|
|
205
211
|
);
|
|
206
|
-
})
|
|
207
|
-
|
|
212
|
+
});
|
|
213
|
+
})()}
|
|
208
214
|
</div>
|
|
209
|
-
|
|
215
|
+
</div>
|
|
216
|
+
{view.laneRows.map(({ entity, perDate, total }) => {
|
|
217
|
+
const isRecent = view.recentEntities.has(entity);
|
|
218
|
+
return (
|
|
219
|
+
<div key={entity} className="river-row">
|
|
220
|
+
<button
|
|
221
|
+
type="button"
|
|
222
|
+
className="river-lane-label"
|
|
223
|
+
onClick={() => navigate(`/thread?entity=${encodeURIComponent(entity)}`)}
|
|
224
|
+
>
|
|
225
|
+
<span className={`dot${isRecent ? " dot-pulse" : ""}`} style={{ background: data.entity_colors[entity] ?? "#666" }} />
|
|
226
|
+
<span className="river-lane-name">{entity}</span>
|
|
227
|
+
<span className="muted small">{total}</span>
|
|
228
|
+
</button>
|
|
229
|
+
<div className="river-cells">
|
|
230
|
+
{view.dates.map((d) => {
|
|
231
|
+
const v = perDate.get(d) ?? 0;
|
|
232
|
+
return (
|
|
233
|
+
<div
|
|
234
|
+
key={d}
|
|
235
|
+
className={`river-cell tier-${tier(v)}`}
|
|
236
|
+
onMouseEnter={(e) => setHover({ entity, date: d, count: v, x: e.clientX, y: e.clientY })}
|
|
237
|
+
onMouseLeave={() => setHover(null)}
|
|
238
|
+
onClick={() => v > 0 && onCellClick(entity, d)}
|
|
239
|
+
style={v > 0 ? { cursor: "pointer" } : {}}
|
|
240
|
+
/>
|
|
241
|
+
);
|
|
242
|
+
})}
|
|
243
|
+
</div>
|
|
244
|
+
</div>
|
|
245
|
+
);
|
|
246
|
+
})}
|
|
210
247
|
</div>
|
|
211
248
|
|
|
212
249
|
{view.laneRows.length === 0 && <div className="muted">No entities in this window.</div>}
|
|
@@ -229,17 +266,26 @@ export function RiverPage() {
|
|
|
229
266
|
/>
|
|
230
267
|
)}
|
|
231
268
|
|
|
232
|
-
{sessionId && (
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
269
|
+
{sessionId && (() => {
|
|
270
|
+
const siblingList = cellDrawer
|
|
271
|
+
? cellDrawer.sessions
|
|
272
|
+
: [...data.sessions].filter((s) => s.started_at !== null).sort((a, b) => (b.started_at ?? "").localeCompare(a.started_at ?? ""));
|
|
273
|
+
const idx = siblingList.findIndex((s) => s.id === sessionId);
|
|
274
|
+
const prevId = idx < siblingList.length - 1 ? siblingList[idx + 1]!.id : null;
|
|
275
|
+
const nextId = idx > 0 ? siblingList[idx - 1]!.id : null;
|
|
276
|
+
const s = data.sessions.find((x) => x.id === sessionId);
|
|
277
|
+
const e = s?.entities[0];
|
|
278
|
+
return (
|
|
279
|
+
<SessionDrawer
|
|
280
|
+
sessionId={sessionId}
|
|
281
|
+
onClose={() => setSessionId(null)}
|
|
282
|
+
onNavigate={setSessionId}
|
|
283
|
+
prevSessionId={prevId}
|
|
284
|
+
nextSessionId={nextId}
|
|
285
|
+
entityColor={e ? data.entity_colors[e] : undefined}
|
|
286
|
+
/>
|
|
287
|
+
);
|
|
288
|
+
})()}
|
|
243
289
|
</div>
|
|
244
290
|
);
|
|
245
291
|
}
|