Link prior moderation actions to case decisions

This commit is contained in:
2026-10-17 19:42:05 -04:00
parent ef96a2c308
commit aca728b557
13 changed files with 948 additions and 17 deletions
+61 -5
View File
@@ -12,6 +12,7 @@
import { contextHref } from '$lib/navigation-context.js';
import {
moderationActionLabels,
moderationActionName,
moderationEventDetail,
moderationGameName,
moderationHref
@@ -29,6 +30,7 @@
import ServerIdentity from '$ui/components/ServerIdentity.svelte';
import ModerationDialog from '$ui/components/moderation/ModerationDialog.svelte';
import EvidenceMessages from '$ui/components/moderation/EvidenceMessages.svelte';
import LinkCaseActionDialog from '$ui/components/moderation/LinkCaseActionDialog.svelte';
import { preservedConversation } from '$ui/moderation-evidence.js';
let { data }: { data: { moderationCase: ModerationCaseDetail } } = $props();
@@ -55,6 +57,7 @@
let generation = 0;
let controller: AbortController | undefined;
let dialogOpen = $state(false);
let linkActionOpen = $state(false);
let dialogKey = $state(0);
let mode = $state<ComponentProps<typeof ModerationDialog>['mode']>('case-note');
let seed = $state<NonNullable<ComponentProps<typeof ModerationDialog>['seed']>>({});
@@ -67,6 +70,7 @@
refreshError = '';
notice = '';
dialogOpen = false;
linkActionOpen = false;
updatedAt = Date.parse(initial.checkedAt);
});
});
@@ -149,6 +153,12 @@
cancelRefresh();
void refresh();
}
function actionLinked() {
notice =
'Existing action linked to this case. Its original date and outcome are preserved.';
cancelRefresh();
void refresh();
}
function archiveContext(raw: string | null | undefined) {
if (!raw) return null;
@@ -218,8 +228,8 @@
</SectionHeader>
{#if !permitted}<p class="notice">
Read-only moderation access. You can inspect this case, its evidence, and linked
restrictions.
Read-only moderation access. You can inspect this case, its evidence, and linked actions
and restrictions.
</p>{/if}
{#if notice}<p class="notice" role="status">{notice}</p>{/if}
{#if refreshError}<div class="ui-error" role="alert">
@@ -400,6 +410,45 @@
>
</section>
<section class="panel" aria-labelledby="actions-title">
<div class="section-heading">
<h2 id="actions-title">Linked actions</h2>
{#if permitted}<Button
size="sm"
disabled={!report.availableActions?.length}
onclick={() => (linkActionOpen = true)}>Link existing action</Button
>{/if}
</div>
{#if report.linkedActions?.length}<ol class="records">
{#each report.linkedActions as action (`${action.type}:${action.id}`)}<li
class="record"
>
<div class="row">
<strong>{moderationActionName(action.kind)}</strong><StatusBadge
state={`moderation-${action.state}`}
/>
</div>
<p class="record-text">{action.reason}</p>
<p class="hint">
{action.actor} · <RelativeTime value={action.createdAt} />
</p>
<CopyValue
value={action.id}
label={action.type === 'restriction'
? 'Restriction ID'
: 'Action ID'}
/>
</li>{/each}
</ol>{:else}<p class="hint">
No existing actions are linked to this case.
</p>{/if}
{#if permitted}<p class="hint">
{report.availableActions?.length
? `${report.availableActions.length} previous actions available for this player.`
: 'No additional recorded actions are available for this player.'}
</p>{/if}
</section>
<section class="panel" aria-labelledby="restrictions-title">
<div class="section-heading">
<h2 id="restrictions-title">Linked restrictions</h2>
@@ -466,9 +515,7 @@
})}>Revoke restriction</Button
>{/if}
</li>{/each}
</ol>{:else}<p class="hint">
No restrictions have been applied from this case.
</p>{/if}
</ol>{:else}<p class="hint">No restrictions are linked to this case.</p>{/if}
<p class="hint">
Applying or revoking a restriction does not change the case outcome.
</p>
@@ -506,9 +553,18 @@
{admin}
coverage={report.coverage}
assignees={report.assignees}
linkedActions={report.linkedActions}
onsubmitted={submitted}
/>{/key}{/if}
{#if permitted && linkActionOpen}<LinkCaseActionDialog
bind:open={linkActionOpen}
caseId={item.id}
revision={item.revision}
actions={report.availableActions ?? []}
onsubmitted={actionLinked}
/>{/if}
<style>
.case-page,
.case-main,