From 7f92d39adf260ee7e9b34c09a180bb86e31667a5 Mon Sep 17 00:00:00 2001
From: aecsocket <43144841+aecsocket@users.noreply.github.com>
Date: Sun, 12 Jul 2026 10:21:08 +0100
Subject: [PATCH 1/8] remaining globally safe/unsafe buttons
---
.../ui/moderation/ModerationTechRevCard.vue | 213 +++++++++++++++---
1 file changed, 181 insertions(+), 32 deletions(-)
diff --git a/apps/frontend/src/components/ui/moderation/ModerationTechRevCard.vue b/apps/frontend/src/components/ui/moderation/ModerationTechRevCard.vue
index e99376989b..a23d0e0f37 100644
--- a/apps/frontend/src/components/ui/moderation/ModerationTechRevCard.vue
+++ b/apps/frontend/src/components/ui/moderation/ModerationTechRevCard.vue
@@ -4,7 +4,6 @@ import {
BanIcon,
BugIcon,
CheckCheckIcon,
- CheckCircleIcon,
CheckIcon,
ChevronDownIcon,
ChevronRightIcon,
@@ -20,7 +19,6 @@ import {
ShieldCheckIcon,
SpinnerIcon,
TimerIcon,
- TriangleAlertIcon,
XIcon,
} from '@modrinth/assets'
import { type TechReviewContext, techReviewQuickReplies } from '@modrinth/moderation'
@@ -615,6 +613,18 @@ const remainingUnmarkedCount = computed(() => {
return getFileDetailCount(selectedFile.value) - getFileMarkedCount(selectedFile.value)
})
+function getSelectedFileFlags(): ClassGroup['flags'] {
+ if (!selectedFile.value) return []
+
+ return selectedFile.value.issues.flatMap((issue) =>
+ issue.details.map((detail) => ({
+ issueId: issue.id,
+ issueType: issue.issue_type,
+ detail,
+ })),
+ )
+}
+
function getJarFlags(jarGroup: JarGroup): ClassGroup['flags'] {
return jarGroup.classes.flatMap((classItem) => classItem.flags)
}
@@ -629,6 +639,74 @@ function getJarRemainingUnmarkedCount(jarGroup: JarGroup): number {
const isBatchUpdating = ref(false)
+function getRemainingGlobalDetailCount(flags: ClassGroup['flags']): number {
+ return new Set(
+ flags
+ .filter(
+ (flag) =>
+ getDetailDecision(flag.detail.id, flag.detail.status) === 'pending' &&
+ canUpdateGlobalDetail(flag.detail),
+ )
+ .map((flag) => flag.detail.key),
+ ).size
+}
+
+async function batchMarkRemainingGlobally(flags: ClassGroup['flags'], verdict: 'safe' | 'unsafe') {
+ if (isBatchUpdating.value) return
+
+ const detailsByKey = new Map(
+ flags
+ .filter(
+ (flag) =>
+ getDetailDecision(flag.detail.id, flag.detail.status) === 'pending' &&
+ canUpdateGlobalDetail(flag.detail),
+ )
+ .map((flag) => [flag.detail.key, flag.detail]),
+ )
+ const details = [...detailsByKey.values()]
+
+ if (details.length === 0) return
+
+ isBatchUpdating.value = true
+ try {
+ await client.labrinth.tech_review_internal.updateGlobalIssueDetails(
+ details.map((detail) => ({ detail_key: detail.key, verdict })),
+ )
+
+ applyDecisionToRelatedDetails(
+ details.map((detail) => detail.id),
+ verdictToDecision(verdict),
+ 'global',
+ )
+
+ addNotification({
+ type: 'success',
+ title: `Globally marked ${details.length} trace keys as ${verdict}`,
+ text: `All remaining eligible traces have been globally marked as ${
+ verdict === 'safe' ? 'false positives' : 'malicious'
+ }.`,
+ })
+
+ if (
+ selectedFile.value &&
+ getFileMarkedCount(selectedFile.value) === getFileDetailCount(selectedFile.value)
+ ) {
+ backToFileList()
+ }
+
+ emit('refetch')
+ } catch (error) {
+ console.error('Failed to batch update global traces:', error)
+ addNotification({
+ type: 'error',
+ title: 'Global batch update failed',
+ text: 'An error occurred while globally updating traces.',
+ })
+ } finally {
+ isBatchUpdating.value = false
+ }
+}
+
async function batchMarkRemaining(verdict: 'safe' | 'unsafe') {
if (!selectedFile.value || isBatchUpdating.value) return
@@ -1590,20 +1668,53 @@ function copyId() {
-
-
-
- Remaining safe ({{ remainingUnmarkedCount }})
+
+ Remaining issues ({{ remainingUnmarkedCount }})
+
+
-
-
-
-
- Remaining malware ({{ remainingUnmarkedCount }})
+
+
-
+
+
+
+
+
+
+
-
-
-
-
- Remaining safe ({{ getJarRemainingUnmarkedCount(jarGroup) }})
-
-
-
-
-
- Remaining malware ({{ getJarRemainingUnmarkedCount(jarGroup) }})
-
-
+
+
+ Remaining issues ({{ getJarRemainingUnmarkedCount(jarGroup) }})
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1968,6 +2109,14 @@ pre {
background: var(--surface-3);
}
+.remaining-verdict-label {
+ padding-inline: 0.75rem;
+ font-size: 0.875rem;
+ font-weight: 600;
+ white-space: nowrap;
+ color: var(--color-secondary);
+}
+
.detail-verdict-button {
display: flex;
width: 2rem;
From e1236ce7bf0c73147d022674092637e84fe1fb86 Mon Sep 17 00:00:00 2001
From: aecsocket <43144841+aecsocket@users.noreply.github.com>
Date: Sun, 12 Jul 2026 10:27:48 +0100
Subject: [PATCH 2/8] tweak global traces layout
---
.../moderation/GlobalDetailLocalTraceCard.vue | 48 ++++++--------
.../ui/moderation/GlobalDetailTracesList.vue | 65 +++++++++++++++----
.../pages/moderation/global-traces/[key].vue | 60 ++++++++++++++---
3 files changed, 126 insertions(+), 47 deletions(-)
diff --git a/apps/frontend/src/components/ui/moderation/GlobalDetailLocalTraceCard.vue b/apps/frontend/src/components/ui/moderation/GlobalDetailLocalTraceCard.vue
index 46850664df..a6756ce443 100644
--- a/apps/frontend/src/components/ui/moderation/GlobalDetailLocalTraceCard.vue
+++ b/apps/frontend/src/components/ui/moderation/GlobalDetailLocalTraceCard.vue
@@ -5,16 +5,21 @@
{{ trace.project_name }}
-
- Project {{ trace.project_slug ?? trace.project_id }} / Version
- {{ trace.version_number }} / File {{ trace.file_name }}
+
+ {{ trace.version_number }}
+
+ {{ decodeTracePath(trace.file_name) }}
+
+
+ {{ decodeTracePath(trace.jar) }}
+
- Local
-
- Effective
-
+
+ Local
+
+
@@ -23,31 +28,12 @@
-
-
-
- Issue
- {{ trace.issue_type }}
-
-
- Severity
- {{ trace.severity }}
-
-
- Path
- {{ trace.file_path }}
-
-
- JAR
- {{ trace.jar }}
-
-
diff --git a/apps/frontend/src/components/ui/moderation/GlobalDetailTracesList.vue b/apps/frontend/src/components/ui/moderation/GlobalDetailTracesList.vue
index ec6cf6237a..11cf98ad77 100644
--- a/apps/frontend/src/components/ui/moderation/GlobalDetailTracesList.vue
+++ b/apps/frontend/src/components/ui/moderation/GlobalDetailTracesList.vue
@@ -44,16 +44,30 @@
>
-
+
Trace
{{ trace.detail_key }}
+
+ {{ getLatestLocalTrace(trace)?.severity }}
+
+
+
+
+ Issue
+ {{ getLatestLocalTrace(trace)?.issue_type }}
+
+
+ Path
+ {{ decodeTracePath(getLatestLocalTrace(trace)?.file_path ?? '') }}
+
-
- {{ formatTraceCount(trace.local_trace_count) }}
-
@@ -70,15 +84,14 @@
-
+
- Showing first {{ getPreviewLocalTraces(trace).length }} of
- {{ getVisibleLocalTraceTotal(trace) }} local traces
+ Showing {{ getPreviewLocalTraces(trace).length }} of
+ {{ formatTraceCount(getVisibleLocalTraceTotal(trace)) }}
-
+
View all
@@ -129,7 +142,7 @@ const isLoading = ref(false)
const loadError = ref(false)
const currentPage = ref(1)
const itemsPerPage = 20
-const localTracePreviewLimit = 10
+const localTracePreviewLimit = 3
const total = ref(0)
const traces = ref([])
const removingTraceKeys = reactive>(new Set())
@@ -148,6 +161,34 @@ function getPreviewLocalTraces(trace: Labrinth.TechReview.Internal.GlobalIssueDe
return trace.local_traces.slice(0, localTracePreviewLimit)
}
+function getLatestLocalTrace(trace: Labrinth.TechReview.Internal.GlobalIssueDetail) {
+ 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 {
+ switch (severity) {
+ case 'severe':
+ return 'border-red/60 bg-highlight-red text-red'
+ case 'high':
+ return 'border-orange/60 bg-highlight-orange text-orange'
+ case 'medium':
+ return 'border-green/60 bg-highlight-green text-green'
+ case 'low':
+ default:
+ return 'border-blue/60 bg-highlight-blue text-blue'
+ }
+}
+
function getVisibleLocalTraceTotal(trace: Labrinth.TechReview.Internal.GlobalIssueDetail) {
return Math.max(trace.local_trace_count, trace.local_traces.length)
}
diff --git a/apps/frontend/src/pages/moderation/global-traces/[key].vue b/apps/frontend/src/pages/moderation/global-traces/[key].vue
index 23d3f9a622..4861faa11c 100644
--- a/apps/frontend/src/pages/moderation/global-traces/[key].vue
+++ b/apps/frontend/src/pages/moderation/global-traces/[key].vue
@@ -22,16 +22,30 @@
-
+
Trace
{{ trace.detail_key }}
+
+ {{ latestLocalTrace.severity }}
+
+
+
+
+ Issue
+ {{ latestLocalTrace.issue_type }}
+
+
+ Path
+ {{ decodeTracePath(latestLocalTrace.file_path) }}
+
-
- {{ pageStart }}-{{ pageEnd }} of {{ trace.local_trace_count }} local traces
-
@@ -45,11 +59,18 @@
-
-
Loading page...
+
+ {{ pageStart }}-{{ pageEnd }} of {{ trace.local_trace_count }} local traces
+ ยท Loading page...
+
+
@@ -114,6 +135,29 @@ const pageStart = computed(() =>
const pageEnd = computed(() =>
Math.min(currentPage.value * localTracePageSize, trace.value?.local_trace_count ?? 0),
)
+const latestLocalTrace = computed(() => trace.value?.local_traces.at(-1))
+
+function decodeTracePath(path: string): string {
+ try {
+ return decodeURIComponent(path)
+ } catch {
+ return path
+ }
+}
+
+function getSeverityBadgeColor(severity: Labrinth.TechReview.Internal.DelphiSeverity): string {
+ switch (severity) {
+ case 'severe':
+ return 'border-red/60 bg-highlight-red text-red'
+ case 'high':
+ return 'border-orange/60 bg-highlight-orange text-orange'
+ case 'medium':
+ return 'border-green/60 bg-highlight-green text-green'
+ case 'low':
+ default:
+ return 'border-blue/60 bg-highlight-blue text-blue'
+ }
+}
async function fetchTracePage(afterDetailId: string | null) {
return await client.labrinth.tech_review_internal.getGlobalIssueDetail({
From 7df31bdd380c52c45adcaa17e7f864d34e42c00d Mon Sep 17 00:00:00 2001
From: aecsocket <43144841+aecsocket@users.noreply.github.com>
Date: Sun, 12 Jul 2026 10:38:27 +0100
Subject: [PATCH 3/8] deterministic local trace ordering
---
...c71b89daddcc8805a0874d1c5ad8469dd83133ac771b86ea4e.json} | 4 ++--
...aa880ddbe05f741e2a83b50d89f5954ab8d951a090e702de73.json} | 4 ++--
...30a7c7894f7682015c170b2fb325aaef554f08aac34457a3fa.json} | 4 ++--
...601c7ba74d63608ff40af3b6b56f43540ec3798ef30e5c0b9d.json} | 4 ++--
...52f15c3c073e65e8eff6516004d0636357f146047789f8f8c1.json} | 4 ++--
apps/labrinth/src/routes/internal/moderation/tech_review.rs | 6 +++++-
6 files changed, 15 insertions(+), 11 deletions(-)
rename apps/labrinth/.sqlx/{query-50d087248322373b2d5fc40e1c7b8a855b55d907d7fba0af0738c37366cb93e0.json => query-147e143aaaff82c71b89daddcc8805a0874d1c5ad8469dd83133ac771b86ea4e.json} (77%)
rename apps/labrinth/.sqlx/{query-9070b1b6a5b1e93eb1fd1838c522fa6084ad6a5c86fafbfe8448ea7cc86f38ba.json => query-63a9a89443566baa880ddbe05f741e2a83b50d89f5954ab8d951a090e702de73.json} (94%)
rename apps/labrinth/.sqlx/{query-8e38571ffae8bf246973d328c890a45b87c0f71d53cf46f390c4b286677b2b31.json => query-72012eb0655d9130a7c7894f7682015c170b2fb325aaef554f08aac34457a3fa.json} (89%)
rename apps/labrinth/.sqlx/{query-10e2a3b31ba94b93ed2d6c9753a5aabf13190a0b336089e6521022069813cf17.json => query-8d37c8c2633b7d601c7ba74d63608ff40af3b6b56f43540ec3798ef30e5c0b9d.json} (88%)
rename apps/labrinth/.sqlx/{query-632b79f51dd39a75ebff838ca4beab463f84aa32246288cc251881ec41d207f5.json => query-c69a7a5296941a52f15c3c073e65e8eff6516004d0636357f146047789f8f8c1.json} (81%)
diff --git a/apps/labrinth/.sqlx/query-50d087248322373b2d5fc40e1c7b8a855b55d907d7fba0af0738c37366cb93e0.json b/apps/labrinth/.sqlx/query-147e143aaaff82c71b89daddcc8805a0874d1c5ad8469dd83133ac771b86ea4e.json
similarity index 77%
rename from apps/labrinth/.sqlx/query-50d087248322373b2d5fc40e1c7b8a855b55d907d7fba0af0738c37366cb93e0.json
rename to apps/labrinth/.sqlx/query-147e143aaaff82c71b89daddcc8805a0874d1c5ad8469dd83133ac771b86ea4e.json
index a6b04534b8..aed2ace773 100644
--- a/apps/labrinth/.sqlx/query-50d087248322373b2d5fc40e1c7b8a855b55d907d7fba0af0738c37366cb93e0.json
+++ b/apps/labrinth/.sqlx/query-147e143aaaff82c71b89daddcc8805a0874d1c5ad8469dd83133ac771b86ea4e.json
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
- "query": "SELECT id FROM versions WHERE mod_id = ANY($1::bigint[])",
+ "query": "SELECT id FROM versions WHERE mod_id = ANY($1::bigint[]) ORDER BY id",
"describe": {
"columns": [
{
@@ -18,5 +18,5 @@
false
]
},
- "hash": "50d087248322373b2d5fc40e1c7b8a855b55d907d7fba0af0738c37366cb93e0"
+ "hash": "147e143aaaff82c71b89daddcc8805a0874d1c5ad8469dd83133ac771b86ea4e"
}
diff --git a/apps/labrinth/.sqlx/query-9070b1b6a5b1e93eb1fd1838c522fa6084ad6a5c86fafbfe8448ea7cc86f38ba.json b/apps/labrinth/.sqlx/query-63a9a89443566baa880ddbe05f741e2a83b50d89f5954ab8d951a090e702de73.json
similarity index 94%
rename from apps/labrinth/.sqlx/query-9070b1b6a5b1e93eb1fd1838c522fa6084ad6a5c86fafbfe8448ea7cc86f38ba.json
rename to apps/labrinth/.sqlx/query-63a9a89443566baa880ddbe05f741e2a83b50d89f5954ab8d951a090e702de73.json
index 5693d97e31..b9c6d3543c 100644
--- a/apps/labrinth/.sqlx/query-9070b1b6a5b1e93eb1fd1838c522fa6084ad6a5c86fafbfe8448ea7cc86f38ba.json
+++ b/apps/labrinth/.sqlx/query-63a9a89443566baa880ddbe05f741e2a83b50d89f5954ab8d951a090e702de73.json
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
- "query": "\n SELECT\n didws.id AS \"id!: DelphiReportIssueDetailsId\",\n didws.issue_id AS \"issue_id!: DelphiReportIssueId\",\n didws.key AS \"key!: String\",\n didws.jar AS \"jar?: String\",\n didws.file_path AS \"file_path!: String\",\n didws.data AS \"data!: sqlx::types::Json
>\",\n didws.severity AS \"severity!: DelphiSeverity\",\n didws.local_status AS \"local_status?: DelphiStatus\",\n didws.global_status AS \"global_status?: DelphiStatus\",\n didws.status AS \"status!: DelphiStatus\"\n FROM delphi_issue_details_with_statuses didws\n WHERE didws.issue_id = ANY($1::bigint[])\n ",
+ "query": "\n SELECT\n didws.id AS \"id!: DelphiReportIssueDetailsId\",\n didws.issue_id AS \"issue_id!: DelphiReportIssueId\",\n didws.key AS \"key!: String\",\n didws.jar AS \"jar?: String\",\n didws.file_path AS \"file_path!: String\",\n didws.data AS \"data!: sqlx::types::Json>\",\n didws.severity AS \"severity!: DelphiSeverity\",\n didws.local_status AS \"local_status?: DelphiStatus\",\n didws.global_status AS \"global_status?: DelphiStatus\",\n didws.status AS \"status!: DelphiStatus\"\n FROM delphi_issue_details_with_statuses didws\n WHERE didws.issue_id = ANY($1::bigint[])\n ORDER BY didws.issue_id, didws.id\n ",
"describe": {
"columns": [
{
@@ -117,5 +117,5 @@
true
]
},
- "hash": "9070b1b6a5b1e93eb1fd1838c522fa6084ad6a5c86fafbfe8448ea7cc86f38ba"
+ "hash": "63a9a89443566baa880ddbe05f741e2a83b50d89f5954ab8d951a090e702de73"
}
diff --git a/apps/labrinth/.sqlx/query-8e38571ffae8bf246973d328c890a45b87c0f71d53cf46f390c4b286677b2b31.json b/apps/labrinth/.sqlx/query-72012eb0655d9130a7c7894f7682015c170b2fb325aaef554f08aac34457a3fa.json
similarity index 89%
rename from apps/labrinth/.sqlx/query-8e38571ffae8bf246973d328c890a45b87c0f71d53cf46f390c4b286677b2b31.json
rename to apps/labrinth/.sqlx/query-72012eb0655d9130a7c7894f7682015c170b2fb325aaef554f08aac34457a3fa.json
index aeb96383c0..3f11083654 100644
--- a/apps/labrinth/.sqlx/query-8e38571ffae8bf246973d328c890a45b87c0f71d53cf46f390c4b286677b2b31.json
+++ b/apps/labrinth/.sqlx/query-72012eb0655d9130a7c7894f7682015c170b2fb325aaef554f08aac34457a3fa.json
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
- "query": "\n SELECT\n id AS \"file_id: DBFileId\",\n version_id AS \"version_id: DBVersionId\",\n url,\n filename,\n size\n FROM files\n WHERE version_id = ANY($1::bigint[])\n ",
+ "query": "\n SELECT\n id AS \"file_id: DBFileId\",\n version_id AS \"version_id: DBVersionId\",\n url,\n filename,\n size\n FROM files\n WHERE version_id = ANY($1::bigint[])\n ORDER BY version_id, id\n ",
"describe": {
"columns": [
{
@@ -42,5 +42,5 @@
false
]
},
- "hash": "8e38571ffae8bf246973d328c890a45b87c0f71d53cf46f390c4b286677b2b31"
+ "hash": "72012eb0655d9130a7c7894f7682015c170b2fb325aaef554f08aac34457a3fa"
}
diff --git a/apps/labrinth/.sqlx/query-10e2a3b31ba94b93ed2d6c9753a5aabf13190a0b336089e6521022069813cf17.json b/apps/labrinth/.sqlx/query-8d37c8c2633b7d601c7ba74d63608ff40af3b6b56f43540ec3798ef30e5c0b9d.json
similarity index 88%
rename from apps/labrinth/.sqlx/query-10e2a3b31ba94b93ed2d6c9753a5aabf13190a0b336089e6521022069813cf17.json
rename to apps/labrinth/.sqlx/query-8d37c8c2633b7d601c7ba74d63608ff40af3b6b56f43540ec3798ef30e5c0b9d.json
index 53cf350301..475dba1b98 100644
--- a/apps/labrinth/.sqlx/query-10e2a3b31ba94b93ed2d6c9753a5aabf13190a0b336089e6521022069813cf17.json
+++ b/apps/labrinth/.sqlx/query-8d37c8c2633b7d601c7ba74d63608ff40af3b6b56f43540ec3798ef30e5c0b9d.json
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
- "query": "\n SELECT\n id AS \"report_id!: DelphiReportId\",\n file_id AS \"file_id!: DBFileId\",\n created,\n severity AS \"severity!: DelphiSeverity\"\n FROM delphi_reports\n WHERE file_id = ANY($1::bigint[])\n ",
+ "query": "\n SELECT\n id AS \"report_id!: DelphiReportId\",\n file_id AS \"file_id!: DBFileId\",\n created,\n severity AS \"severity!: DelphiSeverity\"\n FROM delphi_reports\n WHERE file_id = ANY($1::bigint[])\n ORDER BY file_id, created, id\n ",
"describe": {
"columns": [
{
@@ -48,5 +48,5 @@
false
]
},
- "hash": "10e2a3b31ba94b93ed2d6c9753a5aabf13190a0b336089e6521022069813cf17"
+ "hash": "8d37c8c2633b7d601c7ba74d63608ff40af3b6b56f43540ec3798ef30e5c0b9d"
}
diff --git a/apps/labrinth/.sqlx/query-632b79f51dd39a75ebff838ca4beab463f84aa32246288cc251881ec41d207f5.json b/apps/labrinth/.sqlx/query-c69a7a5296941a52f15c3c073e65e8eff6516004d0636357f146047789f8f8c1.json
similarity index 81%
rename from apps/labrinth/.sqlx/query-632b79f51dd39a75ebff838ca4beab463f84aa32246288cc251881ec41d207f5.json
rename to apps/labrinth/.sqlx/query-c69a7a5296941a52f15c3c073e65e8eff6516004d0636357f146047789f8f8c1.json
index a48b3a89a0..06a2b641ef 100644
--- a/apps/labrinth/.sqlx/query-632b79f51dd39a75ebff838ca4beab463f84aa32246288cc251881ec41d207f5.json
+++ b/apps/labrinth/.sqlx/query-c69a7a5296941a52f15c3c073e65e8eff6516004d0636357f146047789f8f8c1.json
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
- "query": "\n SELECT\n id AS \"id: DelphiReportIssueId\",\n report_id AS \"report_id: DelphiReportId\",\n issue_type\n FROM delphi_report_issues\n WHERE report_id = ANY($1::bigint[])\n ",
+ "query": "\n SELECT\n id AS \"id: DelphiReportIssueId\",\n report_id AS \"report_id: DelphiReportId\",\n issue_type\n FROM delphi_report_issues\n WHERE report_id = ANY($1::bigint[])\n ORDER BY report_id, id\n ",
"describe": {
"columns": [
{
@@ -30,5 +30,5 @@
false
]
},
- "hash": "632b79f51dd39a75ebff838ca4beab463f84aa32246288cc251881ec41d207f5"
+ "hash": "c69a7a5296941a52f15c3c073e65e8eff6516004d0636357f146047789f8f8c1"
}
diff --git a/apps/labrinth/src/routes/internal/moderation/tech_review.rs b/apps/labrinth/src/routes/internal/moderation/tech_review.rs
index 745509e834..2bd5097c58 100644
--- a/apps/labrinth/src/routes/internal/moderation/tech_review.rs
+++ b/apps/labrinth/src/routes/internal/moderation/tech_review.rs
@@ -443,7 +443,7 @@ async fn fetch_project_reports(
}
let version_id_rows = sqlx::query!(
- "SELECT id FROM versions WHERE mod_id = ANY($1::bigint[])",
+ "SELECT id FROM versions WHERE mod_id = ANY($1::bigint[]) ORDER BY id",
&project_ids.iter().map(|id| id.0).collect::>()
)
.fetch_all(pool)
@@ -469,6 +469,7 @@ async fn fetch_project_reports(
size
FROM files
WHERE version_id = ANY($1::bigint[])
+ ORDER BY version_id, id
"#,
&version_ids.iter().map(|id| id.0).collect::>()
)
@@ -485,6 +486,7 @@ async fn fetch_project_reports(
severity AS "severity!: DelphiSeverity"
FROM delphi_reports
WHERE file_id = ANY($1::bigint[])
+ ORDER BY file_id, created, id
"#,
&file_rows.iter().map(|f| f.file_id.0).collect::>()
)
@@ -500,6 +502,7 @@ async fn fetch_project_reports(
issue_type
FROM delphi_report_issues
WHERE report_id = ANY($1::bigint[])
+ ORDER BY report_id, id
"#,
&report_rows
.iter()
@@ -528,6 +531,7 @@ async fn fetch_project_reports(
didws.status AS "status!: DelphiStatus"
FROM delphi_issue_details_with_statuses didws
WHERE didws.issue_id = ANY($1::bigint[])
+ ORDER BY didws.issue_id, didws.id
"#,
&issue_ids.iter().map(|i| i.0).collect::>()
)
From fd8d4c77468abdbbc536c8c3c82e359d4016af00 Mon Sep 17 00:00:00 2001
From: aecsocket <43144841+aecsocket@users.noreply.github.com>
Date: Sun, 12 Jul 2026 11:09:28 +0100
Subject: [PATCH 4/8] fix preview
---
.github/workflows/frontend-preview.yml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/.github/workflows/frontend-preview.yml b/.github/workflows/frontend-preview.yml
index 66b9883997..0c9d5f2d20 100644
--- a/.github/workflows/frontend-preview.yml
+++ b/.github/workflows/frontend-preview.yml
@@ -76,6 +76,8 @@ jobs:
- name: Deploy Storybook preview
uses: cloudflare/wrangler-action@9acf94ace14e7dc412b076f2c5c20b8ce93c79cd # v3.15.0
+ env:
+ NODE_OPTIONS: --max-old-space-size=6144
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}
From 211b0928c273aa56ec4fc94340e16e4477cc87bf Mon Sep 17 00:00:00 2001
From: aecsocket <43144841+aecsocket@users.noreply.github.com>
Date: Fri, 17 Jul 2026 13:29:20 +0100
Subject: [PATCH 5/8] ignore severity for ordering
---
.../ui/moderation/ModerationTechRevCard.vue | 24 ++++---------------
1 file changed, 4 insertions(+), 20 deletions(-)
diff --git a/apps/frontend/src/components/ui/moderation/ModerationTechRevCard.vue b/apps/frontend/src/components/ui/moderation/ModerationTechRevCard.vue
index a23d0e0f37..8ddf5666ad 100644
--- a/apps/frontend/src/components/ui/moderation/ModerationTechRevCard.vue
+++ b/apps/frontend/src/components/ui/moderation/ModerationTechRevCard.vue
@@ -395,10 +395,7 @@ watch(
allFiles.value = [...reports].sort((a, b) => {
const aComplete = getFileMarkedCount(a) === getFileDetailCount(a)
const bComplete = getFileMarkedCount(b) === getFileDetailCount(b)
- if (aComplete !== bComplete) return aComplete ? 1 : -1
- const aSeverity = getFileHighestSeverity(a)
- const bSeverity = getFileHighestSeverity(b)
- return (severityOrder[bSeverity] ?? 0) - (severityOrder[aSeverity] ?? 0)
+ return aComplete === bComplete ? 0 : aComplete ? 1 : -1
})
},
{ immediate: true },
@@ -1037,20 +1034,11 @@ const groupedByClass = computed(() => {
classGroup.flags.sort((a, b) => {
const aPreReviewed = isPreReviewed(a.detail.id, a.detail.status)
const bPreReviewed = isPreReviewed(b.detail.id, b.detail.status)
-
- if (aPreReviewed !== bPreReviewed) {
- return aPreReviewed ? 1 : -1
- }
-
- return (severityOrder[b.detail.severity] ?? 0) - (severityOrder[a.detail.severity] ?? 0)
+ return aPreReviewed === bPreReviewed ? 0 : aPreReviewed ? 1 : -1
})
}
- return Array.from(classMap.values()).sort((a, b) => {
- const aSeverity = getHighestSeverityInClass(a.flags)
- const bSeverity = getHighestSeverityInClass(b.flags)
- return (severityOrder[bSeverity] ?? 0) - (severityOrder[aSeverity] ?? 0)
- })
+ return Array.from(classMap.values())
})
const groupedByJar = computed(() => {
@@ -1072,11 +1060,7 @@ const groupedByJar = computed(() => {
return Array.from(jarMap.values()).sort((a, b) => {
const aRoot = isRootJarGroup(a)
const bRoot = isRootJarGroup(b)
- if (aRoot !== bRoot) return aRoot ? -1 : 1
-
- const aSeverity = getHighestSeverityInClass(a.classes.flatMap((classItem) => classItem.flags))
- const bSeverity = getHighestSeverityInClass(b.classes.flatMap((classItem) => classItem.flags))
- return (severityOrder[bSeverity] ?? 0) - (severityOrder[aSeverity] ?? 0)
+ return aRoot === bRoot ? 0 : aRoot ? -1 : 1
})
})
From b65a00047cd2585e72c29027a7103269ecbcf588 Mon Sep 17 00:00:00 2001
From: aecsocket <43144841+aecsocket@users.noreply.github.com>
Date: Fri, 17 Jul 2026 14:28:08 +0100
Subject: [PATCH 6/8] show globally resolved traces toggle
---
.../ui/moderation/ModerationTechRevCard.vue | 66 ++++++++++++++++++-
1 file changed, 63 insertions(+), 3 deletions(-)
diff --git a/apps/frontend/src/components/ui/moderation/ModerationTechRevCard.vue b/apps/frontend/src/components/ui/moderation/ModerationTechRevCard.vue
index 8ddf5666ad..5a6420f1b9 100644
--- a/apps/frontend/src/components/ui/moderation/ModerationTechRevCard.vue
+++ b/apps/frontend/src/components/ui/moderation/ModerationTechRevCard.vue
@@ -33,6 +33,7 @@ import {
injectNotificationManager,
NavTabs,
OverflowMenu,
+ Toggle,
type OverflowMenuOption,
useFormatBytes,
useFormatDateTime,
@@ -245,6 +246,42 @@ function getAllDetails(): Labrinth.TechReview.Internal.ReportIssueDetail[] {
return props.item.reports.flatMap((report) => report.issues.flatMap((issue) => issue.details))
}
+const hideGloballyPassed = ref(true)
+
+function isDetailGloballyPassed(detail: Labrinth.TechReview.Internal.ReportIssueDetail): boolean {
+ if (detailDecisionScopes.get(detail.id) === 'global') {
+ return detailDecisions.get(detail.id) === 'safe'
+ }
+
+ return detail.global_status === 'safe'
+}
+
+function isDetailGloballyResolved(detail: Labrinth.TechReview.Internal.ReportIssueDetail): boolean {
+ if (detailDecisionScopes.get(detail.id) === 'global') {
+ return detailDecisions.get(detail.id) !== 'pending'
+ }
+
+ return detail.global_status === 'safe' || detail.global_status === 'unsafe'
+}
+
+const globallyPassedSelectedFileCount = computed(() => {
+ if (!selectedFile.value) return 0
+
+ return selectedFile.value.issues.reduce(
+ (count, issue) => count + issue.details.filter(isDetailGloballyPassed).length,
+ 0,
+ )
+})
+
+const globallyResolvedSelectedFileCount = computed(() => {
+ if (!selectedFile.value) return 0
+
+ return selectedFile.value.issues.reduce(
+ (count, issue) => count + issue.details.filter(isDetailGloballyResolved).length,
+ 0,
+ )
+})
+
function applyDecisionToRelatedDetails(
detailIds: string[],
decision: DetailDecision,
@@ -1009,6 +1046,10 @@ const groupedByClass = computed(() => {
for (const issue of selectedFile.value.issues) {
for (const detail of issue.details) {
+ if (hideGloballyPassed.value && isDetailGloballyPassed(detail)) {
+ continue
+ }
+
const classKey = `${detail.jar ?? ''}::${detail.file_path}`
if (!classMap.has(classKey)) {
classMap.set(classKey, {
@@ -1651,10 +1692,15 @@ function copyId() {
-
Date: Fri, 17 Jul 2026 14:43:14 +0100
Subject: [PATCH 7/8] minor frontend changes
---
.../src/components/ui/moderation/GlobalDetailTracesList.vue | 4 ++--
.../src/components/ui/moderation/ModerationTechRevCard.vue | 2 +-
apps/frontend/src/pages/moderation/global-traces/[key].vue | 4 ++--
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/apps/frontend/src/components/ui/moderation/GlobalDetailTracesList.vue b/apps/frontend/src/components/ui/moderation/GlobalDetailTracesList.vue
index 11cf98ad77..8d42abbc1e 100644
--- a/apps/frontend/src/components/ui/moderation/GlobalDetailTracesList.vue
+++ b/apps/frontend/src/components/ui/moderation/GlobalDetailTracesList.vue
@@ -42,7 +42,7 @@
:key="trace.detail_key"
class="universal-card flex flex-col gap-3"
>
-
+
-
+
decodeURIComponent(s.trim()))
.filter((s) => s.length > 0)
// Skip the first segment if it matches the current file tab (it's already shown in the file list)
diff --git a/apps/frontend/src/pages/moderation/global-traces/[key].vue b/apps/frontend/src/pages/moderation/global-traces/[key].vue
index 4861faa11c..96121bc2c0 100644
--- a/apps/frontend/src/pages/moderation/global-traces/[key].vue
+++ b/apps/frontend/src/pages/moderation/global-traces/[key].vue
@@ -20,7 +20,7 @@
heading="Failed to load global detail trace"
/>
-
+
-
+
From d3953d73e97a61a11dc3c08b59ec5eaf7c0cefc5 Mon Sep 17 00:00:00 2001
From: aecsocket <43144841+aecsocket@users.noreply.github.com>
Date: Fri, 17 Jul 2026 15:20:08 +0100
Subject: [PATCH 8/8] prepr
---
.../src/components/ui/moderation/ModerationTechRevCard.vue | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/apps/frontend/src/components/ui/moderation/ModerationTechRevCard.vue b/apps/frontend/src/components/ui/moderation/ModerationTechRevCard.vue
index 7be33f4cfa..f91bd67552 100644
--- a/apps/frontend/src/components/ui/moderation/ModerationTechRevCard.vue
+++ b/apps/frontend/src/components/ui/moderation/ModerationTechRevCard.vue
@@ -33,8 +33,8 @@ import {
injectNotificationManager,
NavTabs,
OverflowMenu,
- Toggle,
type OverflowMenuOption,
+ Toggle,
useFormatBytes,
useFormatDateTime,
useVIntl,
@@ -1748,7 +1748,7 @@ function copyId() {
Hide globally passed
-
+
{{ globallyResolvedSelectedFileCount }}/{{ getFileDetailCount(selectedFile) }}
traces globally resolved