diff --git a/apps/frontend/src/components/ui/moderation/GlobalDetailLocalTraceCard.vue b/apps/frontend/src/components/ui/moderation/GlobalDetailLocalTraceCard.vue
index a6756ce443..8329b64b33 100644
--- a/apps/frontend/src/components/ui/moderation/GlobalDetailLocalTraceCard.vue
+++ b/apps/frontend/src/components/ui/moderation/GlobalDetailLocalTraceCard.vue
@@ -5,14 +5,8 @@
{{ trace.project_name }}
-
- {{ trace.version_number }}
-
- {{ decodeTracePath(trace.file_name) }}
-
-
- {{ decodeTracePath(trace.jar) }}
-
+
+
@@ -33,9 +27,11 @@
diff --git a/apps/frontend/src/components/ui/moderation/GlobalDetailTracesList.vue b/apps/frontend/src/components/ui/moderation/GlobalDetailTracesList.vue
index 8d42abbc1e..1d4fd8cb8a 100644
--- a/apps/frontend/src/components/ui/moderation/GlobalDetailTracesList.vue
+++ b/apps/frontend/src/components/ui/moderation/GlobalDetailTracesList.vue
@@ -65,7 +65,7 @@
Path
- {{ decodeTracePath(getLatestLocalTrace(trace)?.file_path ?? '') }}
+
@@ -133,6 +133,7 @@ import {
} from '@modrinth/ui'
import GlobalDetailLocalTraceCard from '~/components/ui/moderation/GlobalDetailLocalTraceCard.vue'
+import IssueDetailPath from '~/components/ui/moderation/IssueDetailPath.vue'
const client = injectModrinthClient()
const { addNotification } = injectNotificationManager()
@@ -165,14 +166,6 @@ function getLatestLocalTrace(trace: Labrinth.TechReview.Internal.GlobalIssueDeta
return trace.local_traces.at(-1)
}
-function decodeTracePath(path: string): string {
- try {
- return decodeURIComponent(path)
- } catch {
- return path
- }
-}
-
function getSeverityBadgeColor(
severity: Labrinth.TechReview.Internal.DelphiSeverity | undefined,
): string {
diff --git a/apps/frontend/src/components/ui/moderation/IssueDetailPath.vue b/apps/frontend/src/components/ui/moderation/IssueDetailPath.vue
new file mode 100644
index 0000000000..6289a9cd56
--- /dev/null
+++ b/apps/frontend/src/components/ui/moderation/IssueDetailPath.vue
@@ -0,0 +1,74 @@
+
+
+
+
+
+ {{ formatSegment(segment) }}
+
+
+
+
+
+
diff --git a/apps/frontend/src/components/ui/moderation/ModerationTechRevCard.vue b/apps/frontend/src/components/ui/moderation/ModerationTechRevCard.vue
index f91bd67552..3e595510ca 100644
--- a/apps/frontend/src/components/ui/moderation/ModerationTechRevCard.vue
+++ b/apps/frontend/src/components/ui/moderation/ModerationTechRevCard.vue
@@ -6,7 +6,6 @@ import {
CheckCheckIcon,
CheckIcon,
ChevronDownIcon,
- ChevronRightIcon,
ClipboardCopyIcon,
CodeIcon,
CopyIcon,
@@ -49,6 +48,7 @@ import {
import dayjs from 'dayjs'
import { computed, nextTick, reactive, ref, watch } from 'vue'
+import IssueDetailPath from '~/components/ui/moderation/IssueDetailPath.vue'
import type { UnsafeFile } from '~/components/ui/moderation/MaliciousSummaryModal.vue'
import ThreadView from '~/components/ui/thread/ThreadView.vue'
@@ -601,6 +601,29 @@ async function copyToClipboard(code: string, detailId: string) {
}
}
+async function copyDetailCelInput(detailId: string) {
+ if (copyingCelDetails.has(detailId)) return
+
+ copyingCelDetails.add(detailId)
+ try {
+ const input = await client.labrinth.tech_review_internal.getDetailRuleInput(detailId)
+ await navigator.clipboard.writeText(JSON.stringify(input, null, 2))
+ copiedCelDetails.add(detailId)
+ setTimeout(() => {
+ copiedCelDetails.delete(detailId)
+ }, 2000)
+ } catch (error) {
+ console.error('Failed to copy CEL input:', error)
+ addNotification({
+ type: 'error',
+ title: 'Failed to copy CEL input',
+ text: 'An error occurred while loading the trace rule input.',
+ })
+ } finally {
+ copyingCelDetails.delete(detailId)
+ }
+}
+
function getDetailDecision(
detailId: string,
backendStatus: Labrinth.TechReview.Internal.DelphiReportIssueStatus,
@@ -999,6 +1022,8 @@ async function updateGlobalDetailStatus(
const expandedClasses = reactive>(new Set())
const autoExpandedFileIds = reactive>(new Set())
const showCopyFeedback = reactive