@sentry/junior-dashboard 0.277.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.
- package/dist/app.js +30 -41
- package/dist/client.js +90 -90
- package/dist/index.js +30 -41
- package/dist/tailwind.css +1 -1
- package/package.json +5 -4
- package/src/app.ts +1 -1
- package/src/client/App.tsx +9 -4
- package/src/client/components/DetailsPanel.tsx +1 -1
- package/src/client/components/ui/resizable.tsx +50 -0
- package/src/client/conversations/AutoArchiveToggle.tsx +50 -14
- package/src/client/conversations/ConversationHeader.tsx +6 -2
- package/src/client/conversations/ConversationHeaderActions.tsx +4 -0
- package/src/client/conversations/ConversationMeta.tsx +2 -7
- package/src/client/conversations/ConversationPage.tsx +155 -72
- package/src/client/conversations/ConversationSidebar.tsx +2 -1
- package/src/client/conversations/ConversationTranscript.tsx +11 -8
- package/src/client/conversations/ConversationWorkspace.tsx +129 -41
- package/src/client/conversations/ConversationWorkspaceLoading.tsx +17 -3
- package/src/client/conversations/EventDetails.tsx +2 -62
- package/src/client/conversations/TranscriptActivityGroup.tsx +36 -14
- package/src/client/conversations/TranscriptContextEventView.tsx +7 -18
- package/src/client/conversations/TranscriptRailEvent.tsx +0 -8
- package/src/client/conversations/TranscriptView.tsx +89 -20
- package/src/client/conversations/conversationSidebarLayout.ts +61 -0
- package/src/client/conversations/eventLog.ts +2 -31
- package/src/client/conversations/eventTranscript.ts +9 -42
- package/src/client/conversations/queries.ts +76 -15
- package/src/client/conversations/transcript.ts +46 -16
- package/src/client/conversations/transcriptBottomPinning.ts +40 -24
- package/src/client/conversations/transcriptReadPosition.ts +31 -7
- package/src/client/conversations/transcriptRenderModel.ts +1 -14
- package/src/client/conversations/transcriptSearch.tsx +3 -11
- package/src/client/conversations/transcriptUnreadHistory.ts +248 -0
- package/src/client/http.ts +5 -5
- package/src/client/markdownExport.ts +32 -42
- package/src/client/pages/memory/MemoryDetails.tsx +6 -33
- package/src/client/pages/memory/MemoryPage.tsx +7 -92
- package/src/client/pages/memory/MemoryTimeline.tsx +16 -32
- package/src/client/pages/memory/memoryDashboard.ts +2 -38
- package/src/client/pages/memory/memoryRecord.ts +1 -6
- package/src/client/types.ts +18 -33
- package/src/conversation-cache.ts +6 -8
- package/src/mock-reporting/fixtures.ts +23 -33
- package/src/mock-reporting/routes.ts +3 -1
- package/src/tailwind.css +30 -0
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
ActivityChartXAxis,
|
|
6
6
|
ActivityChartYAxis,
|
|
7
7
|
} from "../../components/charts/ActivityChart";
|
|
8
|
-
import { ChartLegend } from "../../components/charts/ChartLegend";
|
|
9
8
|
import {
|
|
10
9
|
type TimeRangeBucketUnit,
|
|
11
10
|
type TimeRangeDays,
|
|
@@ -14,16 +13,11 @@ import { Card } from "../../components/layout/Card";
|
|
|
14
13
|
import { ChartContainer, ChartTooltip } from "../../components/ui/chart";
|
|
15
14
|
import type { MemoryDay } from "./memoryDashboard";
|
|
16
15
|
|
|
17
|
-
const
|
|
18
|
-
{ color: "var(--color-
|
|
19
|
-
|
|
20
|
-
] as const;
|
|
16
|
+
const chartConfig = {
|
|
17
|
+
count: { color: "var(--color-emerald-300)", label: "Memories" },
|
|
18
|
+
};
|
|
21
19
|
|
|
22
|
-
|
|
23
|
-
series.map((item) => [item.key, { color: item.color, label: item.label }]),
|
|
24
|
-
);
|
|
25
|
-
|
|
26
|
-
/** Render viewer memory creation as a stacked personal/public timeline. */
|
|
20
|
+
/** Render memory creation as a timeline. */
|
|
27
21
|
export function MemoryTimeline(props: {
|
|
28
22
|
bucketUnit?: TimeRangeBucketUnit;
|
|
29
23
|
|
|
@@ -33,7 +27,7 @@ export function MemoryTimeline(props: {
|
|
|
33
27
|
const bucketUnit = props.bucketUnit ?? "day";
|
|
34
28
|
|
|
35
29
|
const days = props.days;
|
|
36
|
-
const totals = days.map((day) => day.
|
|
30
|
+
const totals = days.map((day) => day.count);
|
|
37
31
|
const maximum = Math.max(1, ...totals);
|
|
38
32
|
const hasMemories = totals.some((total) => total > 0);
|
|
39
33
|
|
|
@@ -45,15 +39,13 @@ export function MemoryTimeline(props: {
|
|
|
45
39
|
</h2>
|
|
46
40
|
<p className="mt-1 mb-0 font-sans text-xs leading-relaxed text-dashboard-text-muted">
|
|
47
41
|
{bucketUnit === "hour"
|
|
48
|
-
? "
|
|
42
|
+
? "Memories created each hour."
|
|
49
43
|
: bucketUnit === "6hour"
|
|
50
|
-
? "
|
|
51
|
-
: "
|
|
44
|
+
? "Memories created each 6 hours."
|
|
45
|
+
: "Memories created each day."}
|
|
52
46
|
</p>
|
|
53
47
|
</div>
|
|
54
48
|
|
|
55
|
-
<ChartLegend ariaLabel="Memory visibility legend" items={series} />
|
|
56
|
-
|
|
57
49
|
<div className="relative mt-3 overflow-hidden">
|
|
58
50
|
<ChartContainer
|
|
59
51
|
className="aspect-auto h-50 w-full"
|
|
@@ -71,25 +63,17 @@ export function MemoryTimeline(props: {
|
|
|
71
63
|
<ChartTooltip
|
|
72
64
|
content={
|
|
73
65
|
<ActivityChartTooltipContent
|
|
74
|
-
rows={(day: MemoryDay) => [
|
|
75
|
-
["personal", day.personal],
|
|
76
|
-
["public", day.public],
|
|
77
|
-
["total", day.personal + day.public],
|
|
78
|
-
]}
|
|
66
|
+
rows={(day: MemoryDay) => [["memories", day.count]]}
|
|
79
67
|
/>
|
|
80
68
|
}
|
|
81
69
|
/>
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
maxBarSize={13}
|
|
90
|
-
stackId="visibility"
|
|
91
|
-
/>
|
|
92
|
-
))}
|
|
70
|
+
<Bar
|
|
71
|
+
dataKey="count"
|
|
72
|
+
fill="var(--color-count)"
|
|
73
|
+
fillOpacity={0.82}
|
|
74
|
+
isAnimationActive={false}
|
|
75
|
+
maxBarSize={13}
|
|
76
|
+
/>
|
|
93
77
|
</BarChart>
|
|
94
78
|
</ChartContainer>
|
|
95
79
|
{!hasMemories ? (
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useQuery } from "@tanstack/react-query";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
4
|
import { fetchDashboardJson } from "../../http";
|
|
@@ -7,9 +7,8 @@ const memoryBucketSchema = z.string().regex(/^\d{4}-\d{2}-\d{2}(T\d{2})?$/);
|
|
|
7
7
|
|
|
8
8
|
const memoryDaySchema = z
|
|
9
9
|
.object({
|
|
10
|
+
count: z.number().int().min(0),
|
|
10
11
|
date: memoryBucketSchema,
|
|
11
|
-
personal: z.number().int().min(0),
|
|
12
|
-
public: z.number().int().min(0),
|
|
13
12
|
})
|
|
14
13
|
.strict();
|
|
15
14
|
|
|
@@ -38,10 +37,8 @@ export const memoryDashboardSchema = z
|
|
|
38
37
|
embedded: z.number().int().min(0),
|
|
39
38
|
explicit: z.number().int().min(0),
|
|
40
39
|
knowledge: z.number().int().min(0),
|
|
41
|
-
personal: z.number().int().min(0),
|
|
42
40
|
preference: z.number().int().min(0),
|
|
43
41
|
procedure: z.number().int().min(0),
|
|
44
|
-
public: z.number().int().min(0),
|
|
45
42
|
})
|
|
46
43
|
.strict(),
|
|
47
44
|
})
|
|
@@ -64,36 +61,3 @@ export function useMemoryDashboardData() {
|
|
|
64
61
|
retry: false,
|
|
65
62
|
});
|
|
66
63
|
}
|
|
67
|
-
|
|
68
|
-
const memoryCountsSchema = z
|
|
69
|
-
.object({
|
|
70
|
-
active: z.number().int().min(0),
|
|
71
|
-
personal: z.number().int().min(0),
|
|
72
|
-
public: z.number().int().min(0),
|
|
73
|
-
})
|
|
74
|
-
.strict();
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Load viewer-scoped memory totals that match a search query. The query stays
|
|
78
|
-
* idle without a search, because the dashboard summary owns unfiltered totals.
|
|
79
|
-
*/
|
|
80
|
-
export function useMemorySearchCounts(searchQuery: string) {
|
|
81
|
-
return useQuery({
|
|
82
|
-
enabled: Boolean(searchQuery),
|
|
83
|
-
placeholderData: keepPreviousData,
|
|
84
|
-
queryFn: ({ signal }) =>
|
|
85
|
-
fetchDashboardJson(
|
|
86
|
-
memoryCountsSchema,
|
|
87
|
-
`/api/plugins/memory/counts?${new URLSearchParams({ q: searchQuery })}`,
|
|
88
|
-
signal,
|
|
89
|
-
),
|
|
90
|
-
queryKey: [
|
|
91
|
-
"dashboard",
|
|
92
|
-
"plugin-user-page",
|
|
93
|
-
"memory",
|
|
94
|
-
"counts",
|
|
95
|
-
searchQuery,
|
|
96
|
-
],
|
|
97
|
-
retry: false,
|
|
98
|
-
});
|
|
99
|
-
}
|
|
@@ -14,7 +14,6 @@ const memoryRecordSchema = z
|
|
|
14
14
|
observedAt: z.iso.datetime(),
|
|
15
15
|
origin: z.enum(["automatic", "explicit", "other"]),
|
|
16
16
|
sourcePlatform: z.enum(["local", "slack", "web"]),
|
|
17
|
-
visibility: z.enum(["private", "public"]),
|
|
18
17
|
})
|
|
19
18
|
.strict();
|
|
20
19
|
|
|
@@ -43,10 +42,7 @@ export function memoryPageRecord(memory: MemoryRecord): PluginUserPageRecord {
|
|
|
43
42
|
return {
|
|
44
43
|
actions: [
|
|
45
44
|
{
|
|
46
|
-
confirmation:
|
|
47
|
-
memory.visibility === "public"
|
|
48
|
-
? "Forget this memory for everyone?"
|
|
49
|
-
: "Forget this memory?",
|
|
45
|
+
confirmation: "Forget this memory for everyone?",
|
|
50
46
|
href: `/api/plugins/memory/memories/${encodeURIComponent(memory.id)}`,
|
|
51
47
|
label: "Forget",
|
|
52
48
|
method: "DELETE",
|
|
@@ -58,7 +54,6 @@ export function memoryPageRecord(memory: MemoryRecord): PluginUserPageRecord {
|
|
|
58
54
|
{ label: "Type", value: titleCase(memory.kind) },
|
|
59
55
|
{ label: "Learned", value: learned },
|
|
60
56
|
{ label: "Source", value: titleCase(memory.sourcePlatform) },
|
|
61
|
-
{ label: "Visibility", value: titleCase(memory.visibility) },
|
|
62
57
|
{ label: "Remembered", value: formattedDate(memory.createdAt) },
|
|
63
58
|
{ label: "Observed", value: formattedDate(memory.observedAt) },
|
|
64
59
|
{
|
package/src/client/types.ts
CHANGED
|
@@ -56,32 +56,22 @@ export type TranscriptViewSubagentPart = {
|
|
|
56
56
|
export type TranscriptViewContextEventPart = {
|
|
57
57
|
/** People mentioned in the summary, resolved by the API. */
|
|
58
58
|
mentions?: ActorIdentity[];
|
|
59
|
-
event:
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
| {
|
|
78
|
-
createdAt: string;
|
|
79
|
-
modelId: string;
|
|
80
|
-
modelProfile: string;
|
|
81
|
-
reasoningLevel?: string;
|
|
82
|
-
summary?: string;
|
|
83
|
-
type: "handoff";
|
|
84
|
-
};
|
|
59
|
+
event: {
|
|
60
|
+
createdAt: string;
|
|
61
|
+
type: "compaction";
|
|
62
|
+
modelId?: string;
|
|
63
|
+
summary?: string;
|
|
64
|
+
details?: {
|
|
65
|
+
reason: "capacity";
|
|
66
|
+
estimatedInputTokens: number;
|
|
67
|
+
replacementInputTokens?: number;
|
|
68
|
+
triggerTokens: number;
|
|
69
|
+
inputLimitTokens: number;
|
|
70
|
+
inputMessageCount: number;
|
|
71
|
+
retainedMessageCount: number;
|
|
72
|
+
summaryChars: number;
|
|
73
|
+
};
|
|
74
|
+
};
|
|
85
75
|
type: "context_event";
|
|
86
76
|
};
|
|
87
77
|
|
|
@@ -138,6 +128,8 @@ export type TranscriptViewMessage = {
|
|
|
138
128
|
events?: ConversationReportMessageEvent[];
|
|
139
129
|
/** Whether the source message addressed Junior directly. */
|
|
140
130
|
explicitMention?: boolean;
|
|
131
|
+
/** Quiet message: stored, but not delivered to the Location. */
|
|
132
|
+
quiet?: true;
|
|
141
133
|
/** Person changes to Review items that this message records. */
|
|
142
134
|
reviewItemChanges?: ReviewItemChangeRecord[];
|
|
143
135
|
/** People mentioned in the event text, resolved by the API. */
|
|
@@ -148,13 +140,6 @@ export type TranscriptViewMessage = {
|
|
|
148
140
|
messageId?: string;
|
|
149
141
|
/** True while the message is accepted in the mailbox but not yet in history. */
|
|
150
142
|
pending?: boolean;
|
|
151
|
-
route?: {
|
|
152
|
-
confidence?: number;
|
|
153
|
-
modelId: string;
|
|
154
|
-
modelProfile: string;
|
|
155
|
-
reasoningLevel: string;
|
|
156
|
-
source: "configured" | "inherited" | "router";
|
|
157
|
-
};
|
|
158
143
|
eventId?: string;
|
|
159
144
|
failureCode?: ConversationTurnFailureCode;
|
|
160
145
|
failureReason?: ConversationTurnFailureReason;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { CONVERSATION_DETAIL_VERSION_HEADER } from "@sentry/junior/api/schema";
|
|
2
3
|
import type { MiddlewareHandler } from "hono";
|
|
3
4
|
|
|
4
5
|
/** Revalidate mock detail reads on content, after the route checks viewer access. */
|
|
@@ -15,15 +16,12 @@ export const revalidateConversation: MiddlewareHandler = async (
|
|
|
15
16
|
// generatedAt is the read time, not a change to the Conversation. The mailbox
|
|
16
17
|
// has its own live watermark and is never handled by this middleware.
|
|
17
18
|
const { generatedAt: _generatedAt, ...content } = report;
|
|
18
|
-
const
|
|
19
|
+
const version = createHash("sha256")
|
|
20
|
+
.update(JSON.stringify(content))
|
|
21
|
+
.digest("hex");
|
|
19
22
|
context.header("cache-control", "private, no-store");
|
|
20
|
-
context.header(
|
|
21
|
-
|
|
22
|
-
if (
|
|
23
|
-
validators?.some(
|
|
24
|
-
(value) => value.trim().replace(/^W\//, "") === etag.slice(2),
|
|
25
|
-
)
|
|
26
|
-
) {
|
|
23
|
+
context.header(CONVERSATION_DETAIL_VERSION_HEADER, version);
|
|
24
|
+
if (context.req.header(CONVERSATION_DETAIL_VERSION_HEADER) === version) {
|
|
27
25
|
context.res = new Response(null, {
|
|
28
26
|
status: 304,
|
|
29
27
|
headers: context.res.headers,
|
|
@@ -476,16 +476,27 @@ function activeConversation(nowMs: number): ConversationDetailReport {
|
|
|
476
476
|
],
|
|
477
477
|
},
|
|
478
478
|
}),
|
|
479
|
-
reportEvent(
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
479
|
+
reportEvent(
|
|
480
|
+
3,
|
|
481
|
+
iso(Date.parse(startedAt), 9_000),
|
|
482
|
+
{
|
|
483
|
+
type: "assistant_message",
|
|
484
|
+
parts: [
|
|
485
|
+
{
|
|
486
|
+
type: "reasoning",
|
|
487
|
+
text: "Compare the latest deploy with the previous baseline.",
|
|
488
|
+
},
|
|
489
|
+
],
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
model: { modelId: "openai/gpt-5.6-sol" },
|
|
493
|
+
modelCall: {
|
|
494
|
+
provider: "vercel-ai-gateway",
|
|
495
|
+
api: "openai-responses",
|
|
496
|
+
stopReason: "toolUse",
|
|
497
|
+
},
|
|
498
|
+
},
|
|
499
|
+
),
|
|
489
500
|
reportEvent(
|
|
490
501
|
4,
|
|
491
502
|
iso(Date.parse(startedAt), 10_000),
|
|
@@ -500,11 +511,7 @@ function activeConversation(nowMs: number): ConversationDetailReport {
|
|
|
500
511
|
],
|
|
501
512
|
},
|
|
502
513
|
{
|
|
503
|
-
model: {
|
|
504
|
-
modelId: "openai/gpt-5.6-sol",
|
|
505
|
-
modelProfile: "handoff",
|
|
506
|
-
reasoningLevel: "high",
|
|
507
|
-
},
|
|
514
|
+
model: { modelId: "openai/gpt-5.6-sol" },
|
|
508
515
|
modelCall: {
|
|
509
516
|
provider: "vercel-ai-gateway",
|
|
510
517
|
api: "openai-responses",
|
|
@@ -552,11 +559,7 @@ function activeConversation(nowMs: number): ConversationDetailReport {
|
|
|
552
559
|
],
|
|
553
560
|
},
|
|
554
561
|
{
|
|
555
|
-
model: {
|
|
556
|
-
modelId: "openai/gpt-5.6-sol",
|
|
557
|
-
modelProfile: "handoff",
|
|
558
|
-
reasoningLevel: "high",
|
|
559
|
-
},
|
|
562
|
+
model: { modelId: "openai/gpt-5.6-sol" },
|
|
560
563
|
},
|
|
561
564
|
),
|
|
562
565
|
// Mixed markdown keeps font/legibility QA honest for long assistant replies.
|
|
@@ -913,7 +916,6 @@ function dashboardQaConversation(nowMs: number): ConversationDetailReport {
|
|
|
913
916
|
}),
|
|
914
917
|
reportEvent(11, iso(Date.parse(startedAt), 56_000), {
|
|
915
918
|
type: "compaction",
|
|
916
|
-
modelProfile: "standard",
|
|
917
919
|
modelId: "openai/gpt-5.4",
|
|
918
920
|
details: {
|
|
919
921
|
reason: "capacity",
|
|
@@ -1119,7 +1121,6 @@ function longConversation(nowMs: number): ConversationDetailReport {
|
|
|
1119
1121
|
events.push(
|
|
1120
1122
|
reportEvent(nextSeq, iso(Date.parse(startedAt), 53_000), {
|
|
1121
1123
|
type: "compaction",
|
|
1122
|
-
modelProfile: "standard",
|
|
1123
1124
|
modelId: "openai/gpt-5.4",
|
|
1124
1125
|
summary:
|
|
1125
1126
|
"The release is complete. Preserve the published version and continue by checking the update pull request.",
|
|
@@ -1136,17 +1137,6 @@ function longConversation(nowMs: number): ConversationDetailReport {
|
|
|
1136
1137
|
}),
|
|
1137
1138
|
);
|
|
1138
1139
|
nextSeq += 1;
|
|
1139
|
-
events.push(
|
|
1140
|
-
reportEvent(nextSeq, iso(Date.parse(startedAt), 90_000), {
|
|
1141
|
-
type: "handoff",
|
|
1142
|
-
modelProfile: "fast",
|
|
1143
|
-
modelId: "openai/gpt-5-mini",
|
|
1144
|
-
reasoningLevel: "medium",
|
|
1145
|
-
summary:
|
|
1146
|
-
"Investigate the remaining deployment checks and report any actionable failure.",
|
|
1147
|
-
}),
|
|
1148
|
-
);
|
|
1149
|
-
nextSeq += 1;
|
|
1150
1140
|
events.push(
|
|
1151
1141
|
reportEvent(nextSeq, iso(Date.parse(startedAt), 166_000), {
|
|
1152
1142
|
type: "message",
|
|
@@ -197,7 +197,9 @@ export function createMockReportingApi(): Hono<{
|
|
|
197
197
|
if (!params.success) {
|
|
198
198
|
return errorResponse("Invalid route parameters.", 400);
|
|
199
199
|
}
|
|
200
|
-
|
|
200
|
+
// Mock details fit one page and have no unread cursor, so mock history
|
|
201
|
+
// only pages backward.
|
|
202
|
+
if (!query.success || !("before" in query.data)) {
|
|
201
203
|
return errorResponse("Invalid query parameters.", 400);
|
|
202
204
|
}
|
|
203
205
|
if (!readMockConversationDetail(params.data.conversationId)) {
|
package/src/tailwind.css
CHANGED
|
@@ -327,6 +327,36 @@
|
|
|
327
327
|
}
|
|
328
328
|
}
|
|
329
329
|
|
|
330
|
+
@utility thin-scrollbar {
|
|
331
|
+
--thin-scrollbar-thumb: color-mix(
|
|
332
|
+
in srgb,
|
|
333
|
+
var(--color-dashboard-scrollbar-thumb) 80%,
|
|
334
|
+
transparent
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
&::-webkit-scrollbar {
|
|
338
|
+
width: 6px;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
&::-webkit-scrollbar-track {
|
|
342
|
+
background: transparent;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
&::-webkit-scrollbar-thumb {
|
|
346
|
+
border-inline: 1px solid transparent;
|
|
347
|
+
background: var(--thin-scrollbar-thumb) padding-box;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
&::-webkit-scrollbar-thumb:hover {
|
|
351
|
+
background-color: var(--color-dashboard-scrollbar-thumb-hover);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
@supports not selector(::-webkit-scrollbar-thumb) {
|
|
355
|
+
scrollbar-color: var(--thin-scrollbar-thumb) transparent;
|
|
356
|
+
scrollbar-width: thin;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
330
360
|
@source "./app.ts";
|
|
331
361
|
@source "./dashboardLoader.ts";
|
|
332
362
|
@source "./client.tsx";
|