@sentry/junior-dashboard 0.276.0 → 0.278.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.
Files changed (73) hide show
  1. package/dist/app.js +196 -72
  2. package/dist/client.js +94 -89
  3. package/dist/index.d.ts +1 -1
  4. package/dist/index.js +191 -70
  5. package/dist/tailwind.css +1 -1
  6. package/package.json +5 -4
  7. package/src/api/schema.ts +20 -2
  8. package/src/app.ts +80 -12
  9. package/src/auth.ts +5 -1
  10. package/src/client/App.tsx +9 -4
  11. package/src/client/components/DetailsPanel.tsx +1 -1
  12. package/src/client/components/FormNotice.tsx +11 -14
  13. package/src/client/components/ProfileMenu.tsx +5 -6
  14. package/src/client/components/ui/resizable.tsx +50 -0
  15. package/src/client/conversations/AutoArchiveToggle.tsx +71 -0
  16. package/src/client/conversations/ConversationArchiveNotices.tsx +1 -1
  17. package/src/client/conversations/ConversationChildAgents.tsx +2 -1
  18. package/src/client/conversations/ConversationDetails.tsx +6 -3
  19. package/src/client/conversations/ConversationHeader.tsx +15 -2
  20. package/src/client/conversations/ConversationHeaderActions.tsx +27 -0
  21. package/src/client/conversations/ConversationHomeList.tsx +1 -1
  22. package/src/client/conversations/ConversationMemories.tsx +64 -67
  23. package/src/client/conversations/ConversationMeta.tsx +2 -7
  24. package/src/client/conversations/ConversationPage.tsx +175 -79
  25. package/src/client/conversations/ConversationSidebar.tsx +82 -25
  26. package/src/client/conversations/ConversationTranscript.tsx +11 -8
  27. package/src/client/conversations/ConversationWatches.tsx +15 -21
  28. package/src/client/conversations/ConversationWorkspace.tsx +132 -44
  29. package/src/client/conversations/ConversationWorkspaceLoading.tsx +17 -3
  30. package/src/client/conversations/DetailsList.tsx +8 -14
  31. package/src/client/conversations/EventDetails.tsx +2 -62
  32. package/src/client/conversations/MemoryIcons.tsx +23 -0
  33. package/src/client/conversations/TranscriptActivityGroup.tsx +36 -14
  34. package/src/client/conversations/TranscriptContextEventView.tsx +7 -18
  35. package/src/client/conversations/TranscriptMemoryPopover.tsx +20 -25
  36. package/src/client/conversations/TranscriptRailEvent.tsx +0 -8
  37. package/src/client/conversations/TranscriptTurnContextView.tsx +9 -5
  38. package/src/client/conversations/TranscriptView.tsx +89 -20
  39. package/src/client/conversations/autoArchiveQueries.ts +53 -0
  40. package/src/client/conversations/conversationArchive.ts +255 -0
  41. package/src/client/conversations/conversationMemory.ts +2 -1
  42. package/src/client/conversations/conversationSections.ts +24 -18
  43. package/src/client/conversations/conversationSidebarLayout.ts +61 -0
  44. package/src/client/conversations/eventLog.ts +2 -31
  45. package/src/client/conversations/eventTranscript.ts +9 -42
  46. package/src/client/conversations/queries.ts +121 -250
  47. package/src/client/conversations/transcript.ts +46 -16
  48. package/src/client/conversations/transcriptBottomPinning.ts +40 -24
  49. package/src/client/conversations/transcriptReadPosition.ts +31 -7
  50. package/src/client/conversations/transcriptRenderModel.ts +1 -14
  51. package/src/client/conversations/transcriptSearch.tsx +3 -11
  52. package/src/client/conversations/transcriptUnreadHistory.ts +248 -0
  53. package/src/client/conversations/usePinConversation.ts +88 -0
  54. package/src/client/display-text/DisplayText.tsx +7 -2
  55. package/src/client/display-text/codeBlocks.ts +92 -0
  56. package/src/client/format.ts +3 -67
  57. package/src/client/http.ts +5 -5
  58. package/src/client/markdownExport.ts +32 -42
  59. package/src/client/pages/code/RepositoryTabs.tsx +2 -2
  60. package/src/client/pages/memory/MemoryDetails.tsx +6 -33
  61. package/src/client/pages/memory/MemoryPage.tsx +7 -92
  62. package/src/client/pages/memory/MemoryTimeline.tsx +16 -32
  63. package/src/client/pages/memory/memoryDashboard.ts +2 -38
  64. package/src/client/pages/memory/memoryRecord.ts +1 -6
  65. package/src/client/pages/settings/AutoArchiveSettings.tsx +145 -0
  66. package/src/client/pages/settings/SettingsPage.tsx +2 -0
  67. package/src/client/review-desk/ConversationReviewItems.tsx +7 -9
  68. package/src/client/review-desk/queries.ts +2 -1
  69. package/src/client/types.ts +19 -33
  70. package/src/conversation-cache.ts +7 -9
  71. package/src/mock-reporting/fixtures.ts +126 -60
  72. package/src/mock-reporting/routes.ts +50 -1
  73. package/src/tailwind.css +30 -0
@@ -0,0 +1,92 @@
1
+ import type { BundledLanguage } from "shiki/bundle/web";
2
+
3
+ import { dashboardCodeLanguages } from "../code-languages";
4
+ import { detectOutputLanguage, prettyJsonData } from "../format";
5
+ import type { CodeBlock } from "../types";
6
+
7
+ // Shiki has no language detection, and agents often leave fences untagged.
8
+ // Only clear signals select a language, so prose in a fence stays plain.
9
+ const SCRIPT_PATTERN =
10
+ /^javascript:|=>|\b(?:const|let|var)\s+[\w$]+\s*=|\bfunction\b\s*[\w$]*\s*\(|^\s*(?:import\s.+\sfrom\s|export\s+(?:(?:async|class|const|default|enum|function|interface|let|type|var)\s|[{*]))/m;
11
+ const PYTHON_PATTERN =
12
+ /^\s*(?:(?:async\s+)?def\s+\w+\s*\(.*\)\s*(?:->.*)?:|class\s+\w+(?:\([^)]*\))?\s*:|from\s+[\w.]+\s+import\s|import\s+[\w.]+\s*$)/m;
13
+ const SHELL_PATTERN =
14
+ /^\s*(?:\$\s|(?:bun|brew|cargo|cat|cd|cp|curl|docker|echo|export|gh|git|go|grep|kubectl|ls|make|mkdir|mv|npm|npx|pip|pnpm|rm|ssh|sudo|uv|wget|yarn)\s)/;
15
+
16
+ const LANGUAGE_ALIASES: Record<string, BundledLanguage> = {
17
+ console: "shellscript",
18
+ htm: "html",
19
+ js: "javascript",
20
+ jsonl: "json",
21
+ md: "markdown",
22
+ ndjson: "json",
23
+ sh: "shellscript",
24
+ text: "markdown",
25
+ txt: "markdown",
26
+ xml: "xml",
27
+ yml: "yaml",
28
+ };
29
+
30
+ /** Parse transcript prose as Markdown or JSON while preserving fenced code. */
31
+ export function parseMarkdownBlocks(text: string): CodeBlock[] {
32
+ const blocks: CodeBlock[] = [];
33
+ // The info string can hold more than the language, such as `c++` or
34
+ // `js title="x"`. Only its first word names the language.
35
+ const fence = /^([ \t]*)```([^\n`]*)\n([\s\S]*?)```/gm;
36
+ let cursor = 0;
37
+ let match: RegExpExecArray | null;
38
+ while ((match = fence.exec(text))) {
39
+ const prose = text.slice(cursor, match.index).trim();
40
+ if (prose) blocks.push(proseBlock(prose));
41
+ const tag = match[2]?.trim().split(/\s+/)[0];
42
+ const code = stripFenceIndent(match[3] ?? "", match[1]?.length ?? 0);
43
+ const language = tag ? normalizeLanguage(tag) : guessLanguage(code);
44
+ blocks.push({
45
+ code: formatCodeBlock(code, language),
46
+ fenced: true,
47
+ language,
48
+ });
49
+ cursor = match.index + match[0].length;
50
+ }
51
+ const rest = text.slice(cursor).trim();
52
+ if (rest) blocks.push(proseBlock(rest));
53
+ return blocks.length > 0 ? blocks : [proseBlock(text)];
54
+ }
55
+
56
+ function proseBlock(text: string): CodeBlock {
57
+ const language = detectOutputLanguage(text);
58
+ return { code: formatCodeBlock(text, language), fenced: false, language };
59
+ }
60
+
61
+ function normalizeLanguage(tag: string): BundledLanguage {
62
+ const normalized = tag.toLowerCase();
63
+ const candidate = LANGUAGE_ALIASES[normalized] ?? normalized;
64
+ return Object.hasOwn(dashboardCodeLanguages, candidate)
65
+ ? (candidate as BundledLanguage)
66
+ : "markdown";
67
+ }
68
+
69
+ function guessLanguage(code: string): BundledLanguage {
70
+ if (detectOutputLanguage(code) === "json") return "json";
71
+ if (SCRIPT_PATTERN.test(code)) return "tsx";
72
+ if (PYTHON_PATTERN.test(code)) return "python";
73
+ if (SHELL_PATTERN.test(code)) return "shellscript";
74
+ return "markdown";
75
+ }
76
+
77
+ // A fence in a list item is indented, and so are its code lines. Remove that
78
+ // indent, as CommonMark does.
79
+ function stripFenceIndent(code: string, indent: number): string {
80
+ if (indent === 0) return code;
81
+ return code
82
+ .split("\n")
83
+ .map((line) => {
84
+ const lead = line.length - line.trimStart().length;
85
+ return line.slice(Math.min(indent, lead));
86
+ })
87
+ .join("\n");
88
+ }
89
+
90
+ function formatCodeBlock(code: string, language: BundledLanguage): string {
91
+ return language === "json" ? (prettyJsonData(code) ?? code) : code;
92
+ }
@@ -1,5 +1,4 @@
1
1
  import type { BundledLanguage } from "shiki/bundle/web";
2
- import { dashboardCodeLanguages } from "./code-languages";
3
2
  import type {
4
3
  ActorIdentity,
5
4
  ConversationAuxiliaryCosts,
@@ -8,7 +7,6 @@ import type {
8
7
  ConversationUsage,
9
8
  } from "@sentry/junior/api/schema";
10
9
  import type {
11
- CodeBlock,
12
10
  Conversation,
13
11
  ConversationTranscript,
14
12
  TranscriptViewMessage,
@@ -815,28 +813,6 @@ export function automationPath(
815
813
  return tab === "overview" ? base : `${base}/${tab}`;
816
814
  }
817
815
 
818
- function normalizeLanguage(language: string | undefined): BundledLanguage {
819
- const normalized = language?.trim().toLowerCase();
820
- if (!normalized) return "markdown";
821
- const aliases: Record<string, BundledLanguage> = {
822
- console: "shellscript",
823
- htm: "html",
824
- js: "javascript",
825
- jsonl: "json",
826
- md: "markdown",
827
- ndjson: "json",
828
- sh: "shellscript",
829
- text: "markdown",
830
- txt: "markdown",
831
- xml: "xml",
832
- yml: "yaml",
833
- };
834
- const candidate = aliases[normalized] ?? normalized;
835
- return Object.hasOwn(dashboardCodeLanguages, candidate)
836
- ? (candidate as BundledLanguage)
837
- : "markdown";
838
- }
839
-
840
816
  function prettyJson(text: string): string | undefined {
841
817
  const trimmed = text.trim();
842
818
  if (!trimmed) return undefined;
@@ -863,14 +839,11 @@ function prettyJsonl(text: string): string | undefined {
863
839
  return formatted.join("\n");
864
840
  }
865
841
 
866
- function prettyJsonData(text: string): string | undefined {
842
+ /** Pretty-print JSON or JSONL text, or return undefined for other text. */
843
+ export function prettyJsonData(text: string): string | undefined {
867
844
  return prettyJson(text) ?? prettyJsonl(text);
868
845
  }
869
846
 
870
- function formatCodeBlock(code: string, language: BundledLanguage): string {
871
- return language === "json" ? (prettyJsonData(code) ?? code) : code;
872
- }
873
-
874
847
  /**
875
848
  * Detect the language for LLM text output prose: json if the text is valid
876
849
  * JSON or JSONL, markdown otherwise. Never auto-detects XML, HTML, TypeScript,
@@ -889,44 +862,6 @@ export function detectOutputLanguage(text: string): BundledLanguage {
889
862
  return "markdown";
890
863
  }
891
864
 
892
- /** Parse transcript prose as Markdown or JSON while preserving fenced code. */
893
- export function parseMarkdownBlocks(text: string): CodeBlock[] {
894
- const blocks: CodeBlock[] = [];
895
- const fence = /```([A-Za-z0-9_-]+)?\n([\s\S]*?)```/g;
896
- let cursor = 0;
897
- let match: RegExpExecArray | null;
898
- while ((match = fence.exec(text))) {
899
- const prose = text.slice(cursor, match.index).trim();
900
- if (prose) {
901
- const language = detectOutputLanguage(prose);
902
- blocks.push({
903
- code: formatCodeBlock(prose, language),
904
- fenced: false,
905
- language,
906
- });
907
- }
908
- const language = normalizeLanguage(match[1]);
909
- blocks.push({
910
- code: formatCodeBlock(match[2] ?? "", language),
911
- fenced: true,
912
- language,
913
- });
914
- cursor = match.index + match[0].length;
915
- }
916
- const rest = text.slice(cursor).trim();
917
- if (rest) {
918
- const language = detectOutputLanguage(rest);
919
- blocks.push({
920
- code: formatCodeBlock(rest, language),
921
- fenced: false,
922
- language,
923
- });
924
- }
925
- if (blocks.length > 0) return blocks;
926
- const language = detectOutputLanguage(text);
927
- return [{ code: formatCodeBlock(text, language), fenced: false, language }];
928
- }
929
-
930
865
  /** Convert SQL conversation summaries into dashboard rows. */
931
866
  export function buildConversations(
932
867
  summaries: ConversationSummaryReport[],
@@ -937,6 +872,7 @@ export function buildConversations(
937
872
  annotations: summary.annotations,
938
873
  sidebarAnnotations: summary.sidebarAnnotations,
939
874
  archivedAt: summary.archivedAt,
875
+ pinnedAt: summary.pinnedAt,
940
876
  auxiliaryCosts: summary.auxiliaryCosts,
941
877
  channel: summary.channel,
942
878
  channelName: summary.channelName,
@@ -174,21 +174,21 @@ export async function fetchDashboardJson<T>(
174
174
  return schema.parse(await response.json());
175
175
  }
176
176
 
177
- /** Read an authenticated response, allowing 304 only for an explicit validator. */
177
+ /** Read an authenticated response, allowing 304 only for explicit validator headers. */
178
178
  export async function readDashboardResponse(
179
179
  path: string,
180
180
  signal?: AbortSignal,
181
- etag?: string,
181
+ validator?: Record<string, string>,
182
182
  ): Promise<Response> {
183
183
  const response = await fetchDashboard(path, {
184
184
  credentials: "same-origin",
185
185
  ...(signal ? { signal } : undefined),
186
- ...(etag
187
- ? { cache: "no-store" as const, headers: { "if-none-match": etag } }
186
+ ...(validator
187
+ ? { cache: "no-store" as const, headers: validator }
188
188
  : undefined),
189
189
  });
190
190
  if (response.status === 401) restartDashboardSignIn();
191
- if (!response.ok && !(etag && response.status === 304)) {
191
+ if (!response.ok && !(validator && response.status === 304)) {
192
192
  await throwDashboardApiError(path, response);
193
193
  }
194
194
  return response;
@@ -247,53 +247,43 @@ function appendContextEvent(
247
247
  timestamp: number | undefined,
248
248
  ): void {
249
249
  const event = part.event;
250
- lines.push(
251
- "",
252
- event.type === "handoff" ? "### Model handoff" : "### Context compacted",
253
- );
250
+ lines.push("", "### Context compacted");
254
251
  addEventMeta(lines, conversationTranscript, timestamp);
255
- if (event.type === "handoff") {
256
- addMetaLine(lines, "Model", event.modelId);
257
- addMetaLine(lines, "Profile", event.modelProfile);
258
- addMetaLine(lines, "Reasoning", event.reasoningLevel);
259
- } else {
260
- addMetaLine(lines, "Profile", event.modelProfile);
261
- addMetaLine(lines, "Model", event.modelId);
262
- if (event.details) {
263
- addMetaLine(
264
- lines,
265
- "Estimated input tokens",
266
- String(event.details.estimatedInputTokens),
267
- );
268
- if (event.details.replacementInputTokens !== undefined) {
269
- addMetaLine(
270
- lines,
271
- "Replacement input tokens",
272
- String(event.details.replacementInputTokens),
273
- );
274
- }
275
- addMetaLine(
276
- lines,
277
- "Compaction trigger",
278
- String(event.details.triggerTokens),
279
- );
280
- addMetaLine(lines, "Input limit", String(event.details.inputLimitTokens));
281
- addMetaLine(
282
- lines,
283
- "Input messages",
284
- String(event.details.inputMessageCount),
285
- );
286
- addMetaLine(
287
- lines,
288
- "Retained messages",
289
- String(event.details.retainedMessageCount),
290
- );
252
+ addMetaLine(lines, "Model", event.modelId);
253
+ if (event.details) {
254
+ addMetaLine(
255
+ lines,
256
+ "Estimated input tokens",
257
+ String(event.details.estimatedInputTokens),
258
+ );
259
+ if (event.details.replacementInputTokens !== undefined) {
291
260
  addMetaLine(
292
261
  lines,
293
- "Summary characters",
294
- String(event.details.summaryChars),
262
+ "Replacement input tokens",
263
+ String(event.details.replacementInputTokens),
295
264
  );
296
265
  }
266
+ addMetaLine(
267
+ lines,
268
+ "Compaction trigger",
269
+ String(event.details.triggerTokens),
270
+ );
271
+ addMetaLine(lines, "Input limit", String(event.details.inputLimitTokens));
272
+ addMetaLine(
273
+ lines,
274
+ "Input messages",
275
+ String(event.details.inputMessageCount),
276
+ );
277
+ addMetaLine(
278
+ lines,
279
+ "Retained messages",
280
+ String(event.details.retainedMessageCount),
281
+ );
282
+ addMetaLine(
283
+ lines,
284
+ "Summary characters",
285
+ String(event.details.summaryChars),
286
+ );
297
287
  }
298
288
  if (event.summary) {
299
289
  lines.push("", "#### Continuation summary", "", event.summary);
@@ -16,7 +16,7 @@ import {
16
16
  } from "../../components/controls/TimeRangeSelector";
17
17
  import { Card } from "../../components/layout/Card";
18
18
  import { ConversationHomeList } from "../../conversations/ConversationHomeList";
19
- import { attentionFirst } from "../../conversations/conversationSections";
19
+ import { pinnedAndAttentionFirst } from "../../conversations/conversationSections";
20
20
  import { buildConversations, getDashboardTimeZone } from "../../format";
21
21
  import { useDebouncedValue } from "../../debouncedValue";
22
22
  import { useWaitingConversationIds } from "../../review-desk/queries";
@@ -54,7 +54,7 @@ export function RepositoryConversationsTab(props: { repositoryId: string }) {
54
54
  ) : (
55
55
  <ConversationHomeList
56
56
  conversations={pageItems(
57
- attentionFirst(conversations, waitingConversationIds),
57
+ pinnedAndAttentionFirst(conversations, waitingConversationIds),
58
58
  page,
59
59
  PAGE_SIZE,
60
60
  )}
@@ -1,4 +1,4 @@
1
- import { Globe2, Lock, Trash2 } from "lucide-react";
1
+ import { Trash2 } from "lucide-react";
2
2
  import { useState, type ReactNode } from "react";
3
3
 
4
4
  import { DetailsPanel } from "../../components/DetailsPanel";
@@ -24,14 +24,8 @@ import type {
24
24
  PluginUserPageRecordAction,
25
25
  } from "../user/pluginUserPageData";
26
26
 
27
- // The heading, origin, and scope rows already show these values.
28
- const SUMMARIZED_METADATA = new Set([
29
- "Learned",
30
- "Memory ID",
31
- "Source",
32
- "Type",
33
- "Visibility",
34
- ]);
27
+ // The heading and origin rows already show these values.
28
+ const SUMMARIZED_METADATA = new Set(["Learned", "Memory ID", "Source", "Type"]);
35
29
 
36
30
  /** Show one memory beside the library, or in a bottom sheet on small screens. */
37
31
  export function MemoryDetails(props: {
@@ -43,8 +37,6 @@ export function MemoryDetails(props: {
43
37
  const { record } = props;
44
38
  const [confirmOpen, setConfirmOpen] = useState(false);
45
39
  const heading = record ? memoryHeading(record) : "Memory details";
46
- const isPublic = record && metadataValue(record, "Visibility") === "Public";
47
- const VisibilityIcon = isPublic ? Globe2 : Lock;
48
40
  const forgetAction = record?.actions?.find(
49
41
  (recordAction) => recordAction.tone === "danger",
50
42
  );
@@ -56,13 +48,6 @@ export function MemoryDetails(props: {
56
48
  sheetTitle={heading}
57
49
  title={
58
50
  <h2 className="m-0 flex items-center gap-2 font-display text-base font-medium text-dashboard-text">
59
- {record ? (
60
- <VisibilityIcon
61
- aria-hidden="true"
62
- className="shrink-0 text-dashboard-text-muted"
63
- size={14}
64
- />
65
- ) : null}
66
51
  <span className="truncate">{heading}</span>
67
52
  </h2>
68
53
  }
@@ -147,15 +132,11 @@ function MemoryFacts(props: { record: PluginUserPageRecord }) {
147
132
  const { record } = props;
148
133
  const learned = metadataValue(record, "Learned");
149
134
  const source = metadataValue(record, "Source");
150
- const visibility = metadataValue(record, "Visibility");
151
- const isPublic = visibility === "Public";
152
135
  const origin =
153
136
  learned === "Automatic"
154
137
  ? `From a ${source ?? "past"} conversation`
155
138
  : learned === "Explicit"
156
- ? isPublic
157
- ? "Someone asked Junior"
158
- : "You asked Junior"
139
+ ? "Someone asked Junior"
159
140
  : learned
160
141
  ? "Before origin tracking"
161
142
  : undefined;
@@ -169,11 +150,6 @@ function MemoryFacts(props: { record: PluginUserPageRecord }) {
169
150
  return (
170
151
  <dl className="m-0 grid min-w-0 gap-2 rounded-lg border border-dashboard-border-subtle bg-dashboard-surface-panel p-3 text-xs leading-snug">
171
152
  {origin ? <MemoryFact label="Learned">{origin}</MemoryFact> : null}
172
- {visibility ? (
173
- <MemoryFact label="Used in">
174
- {isPublic ? "Future workspace channels" : "Your future conversations"}
175
- </MemoryFact>
176
- ) : null}
177
153
  {dates.map((item) => (
178
154
  <MemoryFact key={item.label} label={item.label}>
179
155
  {item.value}
@@ -194,12 +170,9 @@ function MemoryFact(props: { children: ReactNode; label: string }) {
194
170
  );
195
171
  }
196
172
 
197
- /** Name a memory by visibility and type, such as "Private preference". */
173
+ /** Name a memory by type, such as "Preference". */
198
174
  function memoryHeading(record: PluginUserPageRecord): string {
199
- const kind = metadataValue(record, "Type");
200
- const visibility = metadataValue(record, "Visibility");
201
- if (!kind) return visibility ? `${visibility} memory` : "Memory";
202
- return visibility ? `${visibility} ${kind.toLowerCase()}` : kind;
175
+ return metadataValue(record, "Type") ?? "Memory";
203
176
  }
204
177
 
205
178
  function metadataValue(
@@ -3,8 +3,6 @@ import {
3
3
  BrainCircuit,
4
4
  CircleAlert,
5
5
  Database,
6
- Globe2,
7
- Lock,
8
6
  Sparkles,
9
7
  UserRound,
10
8
  } from "lucide-react";
@@ -12,7 +10,6 @@ import { useEffect, useRef, useState } from "react";
12
10
  import { Navigate, useLocation, useNavigate, useParams } from "react-router";
13
11
  import type { PluginUserPageLink } from "@sentry/junior-plugin-api";
14
12
 
15
- import { FilterTabList } from "../../components/FilterBar";
16
13
  import { InlineError } from "../../components/InlineError";
17
14
  import { PageContentSkeleton } from "../../components/PageContentSkeleton";
18
15
  import { LoadMorePagination } from "../../components/Pagination";
@@ -36,7 +33,6 @@ import { cn } from "../../styles";
36
33
  import {
37
34
  type MemoryDashboardData,
38
35
  useMemoryDashboardData,
39
- useMemorySearchCounts,
40
36
  } from "./memoryDashboard";
41
37
  import { MemoryDetails } from "./MemoryDetails";
42
38
  import { MemoryPageLayout } from "./MemoryPageLayout";
@@ -47,11 +43,9 @@ import { useMemoryRecord } from "./memoryRecord";
47
43
 
48
44
  /**
49
45
  * Leading memory column flexes; type and date columns stay equal fixed
50
- * widths on `sm` and up. Below `sm` those columns are hidden, so the grid
51
- * collapses to just the label and trailing visibility icon.
46
+ * widths on `sm` and up. Below `sm` those columns are hidden.
52
47
  */
53
- const MEMORY_GRID =
54
- "grid-cols-[minmax(0,1fr)_auto] sm:grid-cols-[minmax(0,1fr)_7rem_9rem_auto]";
48
+ const MEMORY_GRID = "grid-cols-1 sm:grid-cols-[minmax(0,1fr)_7rem_9rem]";
55
49
 
56
50
  /** Render the temporary first-class dashboard experience for memory. */
57
51
  export function MemoryPage(props: { page: PluginUserPageLink }) {
@@ -111,7 +105,7 @@ function MemoryOverview(props: { range: TimeRangeDays }) {
111
105
  days: dashboardQuery.data.days,
112
106
  hours: dashboardQuery.data.hours,
113
107
  range: props.range,
114
- emptySixHour: (date) => ({ date, personal: 0, public: 0 }),
108
+ emptySixHour: (date) => ({ count: 0, date }),
115
109
  })}
116
110
  range={props.range}
117
111
  />
@@ -148,21 +142,13 @@ function MemoryLibrary(props: {
148
142
  const {
149
143
  action,
150
144
  content,
151
- filter,
152
145
  query,
153
146
  records,
154
147
  runAction,
155
148
  searchQuery,
156
149
  searchText,
157
- setFilter,
158
150
  setSearchText,
159
151
  } = usePluginUserPageData(props.page);
160
- const dashboardQuery = useMemoryDashboardData();
161
- const searchCountsQuery = useMemorySearchCounts(searchQuery);
162
- // Tab totals follow the search. Without a search, the summary owns them.
163
- const counts = searchQuery
164
- ? searchCountsQuery.data
165
- : dashboardQuery.data?.stats;
166
152
  const navigate = useNavigate();
167
153
  const location = useLocation();
168
154
  const { memoryId } = useParams();
@@ -269,29 +255,6 @@ function MemoryLibrary(props: {
269
255
  ) : null}
270
256
  </div>
271
257
 
272
- <FilterTabList
273
- ariaLabel="Memory collections"
274
- items={[
275
- {
276
- count: counts?.active,
277
- label: "All",
278
- value: "",
279
- },
280
- {
281
- count: counts?.personal,
282
- label: "Private",
283
- value: "private",
284
- },
285
- {
286
- count: counts?.public,
287
- label: "Public",
288
- value: "public",
289
- },
290
- ]}
291
- onChange={setFilter}
292
- value={filter}
293
- />
294
-
295
258
  {loading ? (
296
259
  <PageContentSkeleton label="Loading memories" variant="panel" />
297
260
  ) : query.error ? (
@@ -378,7 +341,6 @@ function MemoryListHeader() {
378
341
  <div>Memory</div>
379
342
  <div>Type</div>
380
343
  <div>Learned</div>
381
- <div className="sr-only">Visibility</div>
382
344
  </div>
383
345
  );
384
346
  }
@@ -387,20 +349,10 @@ function MemorySummary(props: { data: MemoryDashboardData }) {
387
349
  const { stats } = props.data;
388
350
  const items = [
389
351
  {
390
- detail: "personal + public",
352
+ detail: "shared across the workspace",
391
353
  label: "Total active",
392
- value: stats.active.toLocaleString("en-US"),
393
- },
394
- {
395
- detail: "private to you",
396
- label: "Personal",
397
- value: stats.personal.toLocaleString("en-US"),
398
- },
399
- {
400
- detail: "workspace shareable",
401
- label: "Public",
402
354
  tone: "text-dashboard-accent-text",
403
- value: stats.public.toLocaleString("en-US"),
355
+ value: stats.active.toLocaleString("en-US"),
404
356
  },
405
357
  {
406
358
  detail: "in the last 30 days",
@@ -411,7 +363,7 @@ function MemorySummary(props: { data: MemoryDashboardData }) {
411
363
  return (
412
364
  <section
413
365
  aria-label="Memory summary"
414
- className="grid grid-cols-2 gap-px border-y border-dashboard-border-subtle bg-dashboard-fill-hover lg:grid-cols-4"
366
+ className="grid grid-cols-2 gap-px border-y border-dashboard-border-subtle bg-dashboard-fill-hover"
415
367
  >
416
368
  {items.map((item) => (
417
369
  <div className="bg-dashboard-bg px-4 py-4 sm:px-5" key={item.label}>
@@ -442,12 +394,9 @@ function MemoryKindPanel(props: { data: MemoryDashboardData }) {
442
394
  <h2 className="m-0 font-display text-xl font-medium text-dashboard-text">
443
395
  What Junior remembers
444
396
  </h2>
445
- <p className="mt-1 mb-0 font-sans text-xs leading-relaxed text-dashboard-text-muted">
446
- Across personal and public scopes.
447
- </p>
448
397
  <div className="mt-5 grid gap-px overflow-hidden rounded-md border border-dashboard-border-subtle bg-dashboard-fill-hover">
449
398
  <OverviewBreakdownRow
450
- detail="Almost entirely personal"
399
+ detail="How people like to work"
451
400
  icon={UserRound}
452
401
  label="Preferences"
453
402
  value={stats.preference}
@@ -541,8 +490,6 @@ function MemoryRow(props: {
541
490
  const kind = metadataValue(props.record, "Type");
542
491
  const remembered = metadataValue(props.record, "Remembered");
543
492
  const source = metadataValue(props.record, "Source");
544
- const visibility = metadataValue(props.record, "Visibility");
545
- const isPublic = visibility === "Public";
546
493
  return (
547
494
  <SelectableRow
548
495
  className={cn(
@@ -565,11 +512,6 @@ function MemoryRow(props: {
565
512
  >
566
513
  <div className="min-w-0 overflow-hidden">
567
514
  <div className="flex min-w-0 items-center gap-2 overflow-hidden">
568
- <VisibilityIcon
569
- className="sm:hidden"
570
- isPublic={isPublic}
571
- size={14}
572
- />
573
515
  <h3 className="m-0 truncate font-display text-base font-medium leading-snug text-dashboard-text">
574
516
  <MemorySearchHighlight
575
517
  query={props.searchQuery}
@@ -591,38 +533,11 @@ function MemoryRow(props: {
591
533
  <span className="hidden truncate font-mono text-xs text-dashboard-text sm:block">
592
534
  {shortDate(remembered)}
593
535
  </span>
594
- <VisibilityIcon
595
- className="hidden justify-self-center sm:inline-flex"
596
- isPublic={isPublic}
597
- size={15}
598
- />
599
536
  </button>
600
537
  </SelectableRow>
601
538
  );
602
539
  }
603
540
 
604
- /** Render the lock (private) or globe (public) mark for one memory's visibility. */
605
- function VisibilityIcon(props: {
606
- className?: string;
607
- isPublic: boolean;
608
- size: number;
609
- }) {
610
- const Icon = props.isPublic ? Globe2 : Lock;
611
- const label = props.isPublic ? "Public" : "Private";
612
- return (
613
- <span
614
- className={cn(
615
- "inline-flex shrink-0 items-center text-dashboard-text-muted",
616
- props.className,
617
- )}
618
- title={label}
619
- >
620
- <Icon aria-hidden="true" size={props.size} />
621
- <span className="sr-only">{label}</span>
622
- </span>
623
- );
624
- }
625
-
626
541
  function metadataValue(record: PluginUserPageRecord, label: string): string {
627
542
  return record.metadata?.find((item) => item.label === label)?.value ?? "—";
628
543
  }