nlm-memory 0.5.0 → 0.5.1
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/README.md +72 -34
- package/dist/cli/nlm.js +2 -1
- package/dist/cli/nlm.js.map +1 -1
- package/dist/http/app.js +2 -1
- package/dist/http/app.js.map +1 -1
- package/dist/mcp/server.js +20 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/ui/assets/{index-C8cpwbYJ.css → index-Beo8psd-.css} +1 -1
- package/dist/ui/assets/{index-CB50QnL-.js → index-CSPTTeeM.js} +8 -8
- package/dist/ui/index.html +2 -2
- package/package.json +26 -1
- package/.agents/plugins/marketplace.json +0 -20
- package/.github/workflows/ci.yml +0 -30
- package/docs/methodology/re-derivation-rate.md +0 -112
- package/docs/methodology/useful-hit-rate.md +0 -79
- package/docs/plans/2026-05-20-fts5-lexical-recall.md +0 -1088
- package/docs/plans/2026-05-20-recall-daemon-wedge-fix.md +0 -662
- package/docs/plans/2026-05-20-recall-hook-design.md +0 -131
- package/docs/plans/2026-05-20-recall-hook-implementation.md +0 -1222
- package/docs/plans/desktop-product.md +0 -69
- package/docs/plans/factstore-design.md +0 -236
- package/logs/CHANGELOG/CHANGELOG-2026.md +0 -1575
- package/logs/CHANGELOG/CHANGELOG.md +0 -209
- package/migrations/000_initial_schema.sql +0 -174
- package/migrations/001_entity_type_rename.sql +0 -17
- package/migrations/002_adapter_state_extend.sql +0 -12
- package/migrations/003_session_embeddings.sql +0 -11
- package/migrations/004_facts.sql +0 -46
- package/migrations/005_sources.sql +0 -31
- package/migrations/006_providers.sql +0 -33
- package/migrations/007_source_tokens.sql +0 -17
- package/migrations/008_fts_rebuild.sql +0 -9
- package/migrations/009_session_embedding_chunks.sql +0 -46
- package/migrations/010_sources_opencode.sql +0 -30
- package/migrations/011_sources_hermes_agent.sql +0 -30
- package/migrations/012_sources_aider.sql +0 -30
- package/migrations/013_adapter_state_failure_count.sql +0 -12
- package/migrations/014_sources_cursor.sql +0 -30
- package/migrations/015_sources_windsurf.sql +0 -30
- package/plugin-hermes-agent/README.md +0 -49
- package/plugin-hermes-agent/__init__.py +0 -75
- package/plugin-hermes-agent/plugin.yaml +0 -15
- package/scripts/backfill-citations.mjs +0 -0
- package/scripts/build-codex-plugin.mjs +0 -61
- package/scripts/deepseek-probe.mjs +0 -67
- package/scripts/extract-triples.mjs +0 -207
- package/scripts/longmemeval/embedding-cache.ts +0 -77
- package/scripts/longmemeval/fetch-dataset.sh +0 -25
- package/scripts/longmemeval/run-harness.ts +0 -315
- package/scripts/longmemeval/scorer.ts +0 -99
- package/scripts/longmemeval/tsconfig.json +0 -9
- package/scripts/longmemeval/types.ts +0 -35
- package/scripts/nlm-daily-digest.py +0 -239
- package/scripts/nlm-daily-digest.sh +0 -28
- package/src/cli/classify-parity.ts +0 -257
- package/src/cli/launchctl-helpers.ts +0 -49
- package/src/cli/nlm.ts +0 -1078
- package/src/core/actions/actions-log.ts +0 -118
- package/src/core/actions/overlay.ts +0 -117
- package/src/core/adapters/aider.ts +0 -205
- package/src/core/adapters/claude-code.ts +0 -293
- package/src/core/adapters/common.ts +0 -54
- package/src/core/adapters/cursor.ts +0 -486
- package/src/core/adapters/from-source.ts +0 -67
- package/src/core/adapters/hermes-agent.ts +0 -240
- package/src/core/adapters/hermes.ts +0 -277
- package/src/core/adapters/jsonl-generic.ts +0 -208
- package/src/core/adapters/opencode.ts +0 -281
- package/src/core/adapters/pi.ts +0 -264
- package/src/core/adapters/windsurf.ts +0 -386
- package/src/core/classifier/prompt.ts +0 -200
- package/src/core/dataset/build-dataset.ts +0 -463
- package/src/core/embedding/chunk-body.ts +0 -76
- package/src/core/embedding/embed-backfill.ts +0 -210
- package/src/core/embedding/embed-normalize.ts +0 -135
- package/src/core/facts/backfill-facts.ts +0 -254
- package/src/core/facts/extract-facts.ts +0 -50
- package/src/core/hook/citation-detect.ts +0 -124
- package/src/core/hook/cite-memo.ts +0 -68
- package/src/core/hook/claude-settings.ts +0 -187
- package/src/core/hook/gate.ts +0 -25
- package/src/core/hook/hook-log.ts +0 -41
- package/src/core/hook/memo-sweep.ts +0 -164
- package/src/core/hook/memo.ts +0 -67
- package/src/core/hook/pointer-block.ts +0 -26
- package/src/core/hook/select.ts +0 -32
- package/src/core/hook/transcript.ts +0 -121
- package/src/core/ingest/ingest-session.ts +0 -111
- package/src/core/providers/provider-models.ts +0 -100
- package/src/core/providers/provider-registry.ts +0 -196
- package/src/core/recall/citation-log.ts +0 -108
- package/src/core/recall/filter.ts +0 -27
- package/src/core/recall/index.ts +0 -6
- package/src/core/recall/match-fields.ts +0 -40
- package/src/core/recall/query-log.ts +0 -149
- package/src/core/recall/query-shape.ts +0 -66
- package/src/core/recall/recall-service.ts +0 -320
- package/src/core/recall/recent-log.ts +0 -59
- package/src/core/recall/tokenize.ts +0 -18
- package/src/core/recall/useful-scan.ts +0 -336
- package/src/core/recall-facts/fact-query-log.ts +0 -150
- package/src/core/recall-facts/fact-recall-service.ts +0 -327
- package/src/core/scheduler/scan-once.ts +0 -142
- package/src/core/scheduler/scheduler.ts +0 -225
- package/src/core/sources/source-registry.ts +0 -278
- package/src/core/storage/db-restore.ts +0 -133
- package/src/core/storage/live-status.ts +0 -45
- package/src/core/storage/migrate.ts +0 -72
- package/src/core/storage/sqlite-fact-store.ts +0 -304
- package/src/core/storage/sqlite-session-store.ts +0 -810
- package/src/hook/hook-auth.ts +0 -18
- package/src/hook/prompt-recall-hook.ts +0 -180
- package/src/hook/session-end-hook.ts +0 -81
- package/src/hook/session-start-hook.ts +0 -168
- package/src/hook/stop-hook.ts +0 -239
- package/src/http/app.ts +0 -1215
- package/src/install/claude-code.ts +0 -128
- package/src/install/codex.ts +0 -367
- package/src/install/cursor.ts +0 -68
- package/src/install/hermes-agent.ts +0 -76
- package/src/install/hermes.ts +0 -78
- package/src/install/nlm-dir-perms.ts +0 -55
- package/src/install/ollama.ts +0 -284
- package/src/install/setup.ts +0 -489
- package/src/install/windsurf.ts +0 -68
- package/src/llm/classifier-box.ts +0 -64
- package/src/llm/deepseek-client.ts +0 -150
- package/src/llm/env-autoload.ts +0 -55
- package/src/llm/ollama-client.ts +0 -189
- package/src/mcp/server.ts +0 -534
- package/src/ports/fact-store.ts +0 -102
- package/src/ports/llm-client.ts +0 -52
- package/src/ports/logger.ts +0 -16
- package/src/ports/session-store.ts +0 -45
- package/src/ports/transcript-adapter.ts +0 -55
- package/src/shared/types.ts +0 -149
- package/src/ui/App.tsx +0 -58
- package/src/ui/components/PromoteOpenButton.tsx +0 -65
- package/src/ui/components/SessionDrawer.tsx +0 -199
- package/src/ui/components/SideNav.tsx +0 -162
- package/src/ui/components/Skeleton.tsx +0 -107
- package/src/ui/index.html +0 -13
- package/src/ui/lib/actions.ts +0 -30
- package/src/ui/lib/api.ts +0 -92
- package/src/ui/lib/dataset.ts +0 -141
- package/src/ui/lib/registries.ts +0 -155
- package/src/ui/lib/view-settings.ts +0 -41
- package/src/ui/main.tsx +0 -15
- package/src/ui/pages/Live.tsx +0 -229
- package/src/ui/pages/Pulse.tsx +0 -415
- package/src/ui/pages/Recall.tsx +0 -190
- package/src/ui/pages/River.tsx +0 -354
- package/src/ui/pages/Search.tsx +0 -386
- package/src/ui/pages/Stub.tsx +0 -9
- package/src/ui/pages/Thread.tsx +0 -473
- package/src/ui/pages/settings/Classifier.tsx +0 -227
- package/src/ui/pages/settings/Data.tsx +0 -190
- package/src/ui/pages/settings/Index.tsx +0 -65
- package/src/ui/pages/settings/Labels.tsx +0 -224
- package/src/ui/pages/settings/Providers.tsx +0 -305
- package/src/ui/pages/settings/SettingsSubnav.tsx +0 -28
- package/src/ui/pages/settings/Sources.tsx +0 -326
- package/src/ui/pages/settings/Views.tsx +0 -96
- package/src/ui/styles.css +0 -1890
- package/src/ui/tsconfig.json +0 -21
- package/src/ui/vite.config.ts +0 -19
- package/tests/fixtures/claude_code/short_session.jsonl +0 -2
- package/tests/fixtures/claude_code/standard_iso.jsonl +0 -4
- package/tests/fixtures/claude_code/tool_heavy.jsonl +0 -8
- package/tests/fixtures/claude_code/with_subagent.jsonl +0 -7
- package/tests/fixtures/facts.ts +0 -17
- package/tests/fixtures/golden-corpus.ts +0 -85
- package/tests/fixtures/hermes/paired_request_dump.json +0 -24
- package/tests/fixtures/hermes/paired_session.json +0 -23
- package/tests/fixtures/hermes/request_dump.json +0 -28
- package/tests/fixtures/hermes/session_iso.json +0 -38
- package/tests/fixtures/hermes/session_unix.json +0 -38
- package/tests/fixtures/hermes/system_only.json +0 -18
- package/tests/fixtures/pi/error-connection-abort.jsonl +0 -8
- package/tests/fixtures/pi/short-successful.jsonl +0 -5
- package/tests/fixtures/pi/with-custom-message.jsonl +0 -6
- package/tests/fixtures/sessions.ts +0 -22
- package/tests/integration/backfill-facts.test.ts +0 -362
- package/tests/integration/citation-explicit.test.ts +0 -111
- package/tests/integration/cite-event.test.ts +0 -169
- package/tests/integration/cite-memo.test.ts +0 -87
- package/tests/integration/db-restore.test.ts +0 -153
- package/tests/integration/embed-backfill.test.ts +0 -176
- package/tests/integration/fact-supersedence.test.ts +0 -313
- package/tests/integration/fts-index.test.ts +0 -60
- package/tests/integration/getbyids-sqlite.test.ts +0 -100
- package/tests/integration/hermes-agent-hooks.test.ts +0 -248
- package/tests/integration/hook-claude-settings.test.ts +0 -218
- package/tests/integration/hook-log.test.ts +0 -54
- package/tests/integration/hook-memo.test.ts +0 -68
- package/tests/integration/hook-pre-compact.test.ts +0 -105
- package/tests/integration/hook-subagent-start.test.ts +0 -102
- package/tests/integration/http.test.ts +0 -401
- package/tests/integration/keyword-search-fts.test.ts +0 -66
- package/tests/integration/mcp-recall-logging.test.ts +0 -88
- package/tests/integration/mcp.test.ts +0 -260
- package/tests/integration/memo-sweep.test.ts +0 -91
- package/tests/integration/prompt-recall-hook.test.ts +0 -88
- package/tests/integration/provider-registry.test.ts +0 -107
- package/tests/integration/recall-golden.test.ts +0 -59
- package/tests/integration/recall-sqlite.test.ts +0 -169
- package/tests/integration/scheduler.test.ts +0 -391
- package/tests/integration/session-end-hook.test.ts +0 -48
- package/tests/integration/session-start-hook.test.ts +0 -126
- package/tests/integration/source-registry.test.ts +0 -122
- package/tests/integration/sqlite-fact-store.test.ts +0 -346
- package/tests/integration/stop-hook.test.ts +0 -560
- package/tests/integration/wal-checkpoint.test.ts +0 -49
- package/tests/unit/cli/launchctl-helpers.test.ts +0 -60
- package/tests/unit/core/adapters/aider.test.ts +0 -230
- package/tests/unit/core/adapters/claude-code.test.ts +0 -118
- package/tests/unit/core/adapters/cursor.test.ts +0 -485
- package/tests/unit/core/adapters/hermes-agent.test.ts +0 -329
- package/tests/unit/core/adapters/hermes.test.ts +0 -81
- package/tests/unit/core/adapters/jsonl-generic.test.ts +0 -142
- package/tests/unit/core/adapters/opencode.test.ts +0 -354
- package/tests/unit/core/adapters/pi.test.ts +0 -110
- package/tests/unit/core/adapters/windsurf.test.ts +0 -416
- package/tests/unit/core/classifier/prompt.test.ts +0 -126
- package/tests/unit/core/embedding/chunk-body.test.ts +0 -100
- package/tests/unit/core/facts/extract-facts.test.ts +0 -117
- package/tests/unit/core/filter.test.ts +0 -40
- package/tests/unit/core/hook/citation-detect-cite-session.test.ts +0 -96
- package/tests/unit/core/hook/citation-detect.test.ts +0 -124
- package/tests/unit/core/hook/gate.test.ts +0 -29
- package/tests/unit/core/hook/pointer-block.test.ts +0 -22
- package/tests/unit/core/hook/select.test.ts +0 -66
- package/tests/unit/core/match-fields.test.ts +0 -39
- package/tests/unit/core/mcp-cite-session.test.ts +0 -51
- package/tests/unit/core/providers/provider-models.test.ts +0 -101
- package/tests/unit/core/query-shape.test.ts +0 -92
- package/tests/unit/core/recall-facts/fact-recall-service.test.ts +0 -258
- package/tests/unit/core/recall-service.test.ts +0 -200
- package/tests/unit/core/storage/live-status.test.ts +0 -54
- package/tests/unit/core/tokenize.test.ts +0 -32
- package/tests/unit/core/useful-scan.test.ts +0 -537
- package/tests/unit/llm/embed.test.ts +0 -93
- package/tests/unit/llm/ollama-client.test.ts +0 -124
- package/tests/unit/scripts/longmemeval-scorer.test.ts +0 -114
- package/tsconfig.json +0 -31
- package/tsconfig.test.json +0 -11
- package/vitest.config.ts +0 -22
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* View preferences — per-device UI settings persisted in localStorage.
|
|
3
|
-
*
|
|
4
|
-
* Written by Settings → Views; read by the root route (landing), River
|
|
5
|
-
* (density), and Thread (sort). localStorage is the right home: these are
|
|
6
|
-
* device-local display choices, not corpus data the daemon should own.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
export type LandingView = "live" | "pulse" | "river" | "thread" | "search";
|
|
10
|
-
export type RiverDensity = "compact" | "comfortable" | "spacious";
|
|
11
|
-
export type ThreadSort = "recent" | "oldest";
|
|
12
|
-
|
|
13
|
-
export interface ViewSettings {
|
|
14
|
-
landing: LandingView;
|
|
15
|
-
riverDensity: RiverDensity;
|
|
16
|
-
threadSort: ThreadSort;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export const VIEW_SETTINGS_KEY = "nlm.settings.views";
|
|
20
|
-
|
|
21
|
-
export const VIEW_SETTINGS_DEFAULT: ViewSettings = {
|
|
22
|
-
landing: "live",
|
|
23
|
-
riverDensity: "comfortable",
|
|
24
|
-
threadSort: "recent",
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
export function readViewSettings(): ViewSettings {
|
|
28
|
-
if (typeof window === "undefined") return VIEW_SETTINGS_DEFAULT;
|
|
29
|
-
try {
|
|
30
|
-
const raw = window.localStorage.getItem(VIEW_SETTINGS_KEY);
|
|
31
|
-
if (!raw) return VIEW_SETTINGS_DEFAULT;
|
|
32
|
-
return { ...VIEW_SETTINGS_DEFAULT, ...(JSON.parse(raw) as Partial<ViewSettings>) };
|
|
33
|
-
} catch {
|
|
34
|
-
return VIEW_SETTINGS_DEFAULT;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function writeViewSettings(values: ViewSettings): void {
|
|
39
|
-
if (typeof window === "undefined") return;
|
|
40
|
-
window.localStorage.setItem(VIEW_SETTINGS_KEY, JSON.stringify(values));
|
|
41
|
-
}
|
package/src/ui/main.tsx
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { createRoot } from "react-dom/client";
|
|
3
|
-
import { BrowserRouter } from "react-router-dom";
|
|
4
|
-
import { App } from "./App.js";
|
|
5
|
-
|
|
6
|
-
const root = document.getElementById("root");
|
|
7
|
-
if (!root) throw new Error("missing #root");
|
|
8
|
-
|
|
9
|
-
createRoot(root).render(
|
|
10
|
-
<React.StrictMode>
|
|
11
|
-
<BrowserRouter basename="/ui">
|
|
12
|
-
<App />
|
|
13
|
-
</BrowserRouter>
|
|
14
|
-
</React.StrictMode>,
|
|
15
|
-
);
|
package/src/ui/pages/Live.tsx
DELETED
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef, useState } from "react";
|
|
2
|
-
import { usePolledEndpoint, relativeTime } from "../lib/api.js";
|
|
3
|
-
import type { PolledResult, RecentMarker, RecentRead, RecentWrite } from "../lib/api.js";
|
|
4
|
-
import { SessionDrawer } from "../components/SessionDrawer.js";
|
|
5
|
-
|
|
6
|
-
const POLL_MS = 3000;
|
|
7
|
-
/** Past this many ms with no successful fetch, the board is treated as stale. */
|
|
8
|
-
const STALE_MS = POLL_MS * 3;
|
|
9
|
-
|
|
10
|
-
interface ReadsResponse { entries: RecentRead[] }
|
|
11
|
-
interface WritesResponse { writes: RecentWrite[] }
|
|
12
|
-
interface MarkersResponse { markers: RecentMarker[] }
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Tracks which row keys are newly arrived so they can flash once. The first
|
|
16
|
-
* populated render seeds the seen-set silently — only genuinely new rows
|
|
17
|
-
* after that are reported fresh, for ~1.2s each.
|
|
18
|
-
*/
|
|
19
|
-
function useFreshKeys(keys: string[]): Set<string> {
|
|
20
|
-
const seen = useRef<Set<string> | null>(null);
|
|
21
|
-
const [fresh, setFresh] = useState<Set<string>>(new Set());
|
|
22
|
-
const signature = keys.join("|");
|
|
23
|
-
|
|
24
|
-
useEffect(() => {
|
|
25
|
-
if (keys.length === 0) return;
|
|
26
|
-
if (seen.current === null) {
|
|
27
|
-
seen.current = new Set(keys);
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
const added = keys.filter((k) => !seen.current!.has(k));
|
|
31
|
-
for (const k of keys) seen.current!.add(k);
|
|
32
|
-
if (added.length === 0) return;
|
|
33
|
-
setFresh((prev) => new Set([...prev, ...added]));
|
|
34
|
-
const t = setTimeout(() => {
|
|
35
|
-
setFresh((prev) => {
|
|
36
|
-
const next = new Set(prev);
|
|
37
|
-
for (const k of added) next.delete(k);
|
|
38
|
-
return next;
|
|
39
|
-
});
|
|
40
|
-
}, 1200);
|
|
41
|
-
return () => clearTimeout(t);
|
|
42
|
-
// signature captures the ordered key list; keys identity is unstable.
|
|
43
|
-
}, [signature]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
44
|
-
|
|
45
|
-
return fresh;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function LivePage() {
|
|
49
|
-
const reads = usePolledEndpoint<ReadsResponse>("/api/recall/recent?limit=50", POLL_MS, { entries: [] });
|
|
50
|
-
const writes = usePolledEndpoint<WritesResponse>("/api/live/recent-writes?limit=50", POLL_MS, { writes: [] });
|
|
51
|
-
const markers = usePolledEndpoint<MarkersResponse>("/api/live/recent-markers?limit=50", POLL_MS, { markers: [] });
|
|
52
|
-
|
|
53
|
-
const [sessionId, setSessionId] = useState<string | null>(null);
|
|
54
|
-
|
|
55
|
-
const readKeys = reads.data.entries.map((r) => `${r.ts}|${r.source}|${r.query ?? ""}`);
|
|
56
|
-
const writeKeys = writes.data.writes.map((w) => w.id);
|
|
57
|
-
const markerKeys = markers.data.markers.map((m) => `${m.sessionId}|${m.kind}|${m.text}`);
|
|
58
|
-
const freshReads = useFreshKeys(readKeys);
|
|
59
|
-
const freshWrites = useFreshKeys(writeKeys);
|
|
60
|
-
const freshMarkers = useFreshKeys(markerKeys);
|
|
61
|
-
|
|
62
|
-
return (
|
|
63
|
-
<div className="live-page">
|
|
64
|
-
<ConnectionBar reads={reads} writes={writes} markers={markers} />
|
|
65
|
-
|
|
66
|
-
<div className="live-board">
|
|
67
|
-
<Column
|
|
68
|
-
title="Reads"
|
|
69
|
-
count={reads.data.entries.length}
|
|
70
|
-
loading={reads.loading}
|
|
71
|
-
emptyLabel="no recent recall"
|
|
72
|
-
>
|
|
73
|
-
{reads.data.entries.map((r, i) => {
|
|
74
|
-
const key = readKeys[i]!;
|
|
75
|
-
return (
|
|
76
|
-
<div className={`live-row${freshReads.has(key) ? " is-new" : ""}`} key={key}>
|
|
77
|
-
<span className="live-tag">{r.source}</span>
|
|
78
|
-
<span className="label">{r.query ?? "(no query)"}</span>
|
|
79
|
-
<div className="body">{r.mode} · {r.nResults} hit{r.nResults === 1 ? "" : "s"}</div>
|
|
80
|
-
<div className="meta">{relativeTime(r.ts)}</div>
|
|
81
|
-
</div>
|
|
82
|
-
);
|
|
83
|
-
})}
|
|
84
|
-
</Column>
|
|
85
|
-
|
|
86
|
-
<Column
|
|
87
|
-
title="Writes"
|
|
88
|
-
count={writes.data.writes.length}
|
|
89
|
-
loading={writes.loading}
|
|
90
|
-
emptyLabel="no recent writes"
|
|
91
|
-
>
|
|
92
|
-
{writes.data.writes.map((w) => (
|
|
93
|
-
<Row
|
|
94
|
-
key={w.id}
|
|
95
|
-
fresh={freshWrites.has(w.id)}
|
|
96
|
-
onOpen={() => setSessionId(w.id)}
|
|
97
|
-
>
|
|
98
|
-
<span className="live-tag">{w.runtime.split("/")[0]}</span>
|
|
99
|
-
<span className="label">{w.label}</span>
|
|
100
|
-
<div className="body">{w.summary}</div>
|
|
101
|
-
<div className="meta">{relativeTime(w.createdAt)} · {w.id}</div>
|
|
102
|
-
</Row>
|
|
103
|
-
))}
|
|
104
|
-
</Column>
|
|
105
|
-
|
|
106
|
-
<Column
|
|
107
|
-
title="Markers"
|
|
108
|
-
count={markers.data.markers.length}
|
|
109
|
-
loading={markers.loading}
|
|
110
|
-
emptyLabel="no recent markers"
|
|
111
|
-
>
|
|
112
|
-
{markers.data.markers.map((m, i) => {
|
|
113
|
-
const key = markerKeys[i]!;
|
|
114
|
-
return (
|
|
115
|
-
<Row
|
|
116
|
-
key={key}
|
|
117
|
-
fresh={freshMarkers.has(key)}
|
|
118
|
-
onOpen={() => setSessionId(m.sessionId)}
|
|
119
|
-
>
|
|
120
|
-
<span className="live-tag" data-kind={m.kind}>{m.kind}</span>
|
|
121
|
-
<span className="label">{m.text}</span>
|
|
122
|
-
<div className="body">{m.label}</div>
|
|
123
|
-
<div className="meta">{relativeTime(m.createdAt)}</div>
|
|
124
|
-
</Row>
|
|
125
|
-
);
|
|
126
|
-
})}
|
|
127
|
-
</Column>
|
|
128
|
-
</div>
|
|
129
|
-
|
|
130
|
-
{sessionId && (
|
|
131
|
-
<SessionDrawer sessionId={sessionId} onClose={() => setSessionId(null)} />
|
|
132
|
-
)}
|
|
133
|
-
</div>
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function ConnectionBar({
|
|
138
|
-
reads,
|
|
139
|
-
writes,
|
|
140
|
-
markers,
|
|
141
|
-
}: {
|
|
142
|
-
reads: PolledResult<unknown>;
|
|
143
|
-
writes: PolledResult<unknown>;
|
|
144
|
-
markers: PolledResult<unknown>;
|
|
145
|
-
}) {
|
|
146
|
-
const all = [reads, writes, markers];
|
|
147
|
-
const loading = all.every((p) => p.loading);
|
|
148
|
-
const stamps = all.map((p) => p.lastUpdated).filter((t): t is number => t !== null);
|
|
149
|
-
const freshest = stamps.length > 0 ? Math.max(...stamps) : null;
|
|
150
|
-
const anyError = all.some((p) => p.error !== null);
|
|
151
|
-
const stale = freshest !== null && Date.now() - freshest > STALE_MS;
|
|
152
|
-
|
|
153
|
-
let status: "connecting" | "live" | "reconnecting";
|
|
154
|
-
if (loading) status = "connecting";
|
|
155
|
-
else if (stale || anyError) status = "reconnecting";
|
|
156
|
-
else status = "live";
|
|
157
|
-
|
|
158
|
-
const label =
|
|
159
|
-
status === "connecting" ? "Connecting…"
|
|
160
|
-
: status === "reconnecting" ? "Reconnecting…"
|
|
161
|
-
: "Live";
|
|
162
|
-
|
|
163
|
-
return (
|
|
164
|
-
<div className="live-status">
|
|
165
|
-
<span className={`live-status-dot live-status-${status}`} aria-hidden="true" />
|
|
166
|
-
<span className="live-status-label">{label}</span>
|
|
167
|
-
{freshest !== null && (
|
|
168
|
-
<span className="muted small">
|
|
169
|
-
updated {relativeTime(new Date(freshest).toISOString())}
|
|
170
|
-
</span>
|
|
171
|
-
)}
|
|
172
|
-
<span className="header-spacer" />
|
|
173
|
-
<span className="muted small mono">polling every {POLL_MS / 1000}s</span>
|
|
174
|
-
</div>
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function Column({
|
|
179
|
-
title,
|
|
180
|
-
count,
|
|
181
|
-
loading,
|
|
182
|
-
emptyLabel,
|
|
183
|
-
children,
|
|
184
|
-
}: {
|
|
185
|
-
title: string;
|
|
186
|
-
count: number;
|
|
187
|
-
loading: boolean;
|
|
188
|
-
emptyLabel: string;
|
|
189
|
-
children: React.ReactNode;
|
|
190
|
-
}) {
|
|
191
|
-
const isEmpty = count === 0;
|
|
192
|
-
return (
|
|
193
|
-
<section className="live-col">
|
|
194
|
-
<header className="live-col-head">
|
|
195
|
-
<span className="live-col-title">{title}</span>
|
|
196
|
-
<span className="live-col-count">{count}</span>
|
|
197
|
-
</header>
|
|
198
|
-
<div className="live-col-body">
|
|
199
|
-
{isEmpty
|
|
200
|
-
? <div className="live-empty">{loading ? "loading…" : emptyLabel}</div>
|
|
201
|
-
: children}
|
|
202
|
-
</div>
|
|
203
|
-
</section>
|
|
204
|
-
);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function Row({
|
|
208
|
-
fresh,
|
|
209
|
-
onOpen,
|
|
210
|
-
children,
|
|
211
|
-
}: {
|
|
212
|
-
fresh: boolean;
|
|
213
|
-
onOpen: () => void;
|
|
214
|
-
children: React.ReactNode;
|
|
215
|
-
}) {
|
|
216
|
-
return (
|
|
217
|
-
<div
|
|
218
|
-
className={`live-row clickable${fresh ? " is-new" : ""}`}
|
|
219
|
-
onClick={onOpen}
|
|
220
|
-
role="button"
|
|
221
|
-
tabIndex={0}
|
|
222
|
-
onKeyDown={(e) => {
|
|
223
|
-
if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onOpen(); }
|
|
224
|
-
}}
|
|
225
|
-
>
|
|
226
|
-
{children}
|
|
227
|
-
</div>
|
|
228
|
-
);
|
|
229
|
-
}
|