Files
shd.build/src/routes/isac/community/+page.svelte
T

469 lines
30 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script lang="ts">
import AgentIdentityLink from '$lib/components/AgentIdentityLink.svelte';
import KpiFilters from '$lib/components/KpiFilters.svelte';
import MarkdownContent from '$lib/components/MarkdownContent.svelte';
import PendingForm from '$lib/components/PendingForm.svelte';
import RegistryPagination from '$lib/components/RegistryPagination.svelte';
type AgentSummary = { id: string; displayName: string; avatarUrl: string };
type CommunityComment = {
id: string;
bodyHtml: string;
status: 'visible' | 'removed';
createdAt: string;
updatedAt: string;
moderationReason: string | null;
targetType: string;
targetTitle: string;
targetHref: string;
author: AgentSummary;
};
type CommunityBio = {
id: string;
bodyHtml: string;
status: 'visible' | 'hidden';
updatedAt: string;
moderationReason: string | null;
agent: AgentSummary;
};
type QueueItem = {
id: string;
source: 'scan' | 'report';
contentType: 'comment' | 'profile_bio' | 'guide';
contentId: string;
bodyHtml: string;
queuedAt: string;
scanStatus: string | null;
riskLevel: string;
reason: string;
category: string | null;
provider: string | null;
model: string | null;
providerRequestId: string | null;
reporterName: string | null;
scores: Array<{ category: string; score: number }>;
flags: string[];
ruleMatches: Array<{ label: string; excerpt: string }>;
targetType: string;
targetTitle: string;
targetHref: string;
author: AgentSummary;
};
type CommunityItem = CommunityComment | CommunityBio;
type ModerationAction = 'removeComment' | 'restoreComment' | 'hideBio' | 'restoreBio';
type PageData = {
tab: 'queue' | 'comments' | 'bios';
status: string;
search: string;
summary: {
queue: { total: number; automated: number; reported: number; priority: number };
comments: { visible: number; removed: number };
bios: { visible: number; hidden: number };
};
items: Array<CommunityItem | QueueItem>;
pagination: any;
permissions: { comments: boolean; profiles: boolean };
};
let { data, form }: { data: PageData; form?: Record<string, any> | null } = $props();
let reviewing = $state<CommunityItem | null>(null);
let moderationAction = $state<ModerationAction | null>(null);
let moderationReason = $state('');
let confirming = $state(false);
let queueReviewing = $state<QueueItem | null>(null);
let queueDecision = $state<'approve' | 'reject'>('approve');
let queueReason = $state('');
let queueConfirming = $state(false);
let handledResultToken = $state('');
const canModerateComments = $derived(data.permissions.comments);
const canModerateProfiles = $derived(data.permissions.profiles);
const isQueueItem = (item: CommunityItem | QueueItem): item is QueueItem => 'source' in item;
const isComment = (item: CommunityItem): item is CommunityComment => 'author' in item;
const itemAgent = (item: CommunityItem) => isComment(item) ? item.author : item.agent;
const itemTimestamp = (item: CommunityItem) => isComment(item) ? item.createdAt : item.updatedAt;
const date = (value: string) => {
const parsed = new Date(value);
return Number.isNaN(parsed.valueOf())
? 'DATE UNKNOWN'
: new Intl.DateTimeFormat(undefined, { dateStyle: 'medium', timeStyle: 'short' }).format(parsed);
};
const actionRoute = $derived(
moderationAction === 'removeComment' ? '?/removeComment'
: moderationAction === 'restoreComment' ? '?/restoreComment'
: moderationAction === 'hideBio' ? '?/hideBio'
: '?/restoreBio'
);
const actionLabel = $derived(
moderationAction === 'removeComment' ? 'REMOVE COMMENT'
: moderationAction === 'restoreComment' ? 'RESTORE COMMENT'
: moderationAction === 'hideBio' ? 'HIDE PROFILE BIO'
: 'RESTORE PROFILE BIO'
);
const actionConsequence = $derived(
moderationAction === 'removeComment'
? 'The comment will no longer appear on its public target or the author’s public activity.'
: moderationAction === 'restoreComment'
? 'The comment will return to its public target and the author’s public activity.'
: moderationAction === 'hideBio'
? 'The current bio will be hidden from the Agent’s public profile.'
: 'The bio will return to the Agent’s public profile.'
);
const statusItems = $derived(
data.tab === 'queue'
? [
{ value: 'all', label: 'PENDING', count: data.summary.queue.total, description: 'All items requiring human review' },
{ value: 'priority', label: 'PRIORITY', count: data.summary.queue.priority, description: 'High-risk or failed automated scans' },
{ value: 'automated', label: 'AUTOMATED', count: data.summary.queue.automated, description: 'Items routed here by moderation scans' },
{ value: 'reported', label: 'USER REPORTS', count: data.summary.queue.reported, description: 'Content flagged by community members' }
]
: data.tab === 'comments'
? [
{ value: 'all', label: 'ALL', count: data.summary.comments.visible + data.summary.comments.removed, description: 'All public and moderated comments' },
{ value: 'visible', label: 'VISIBLE', count: data.summary.comments.visible, description: 'Comments currently visible to the community' },
{ value: 'removed', label: 'REMOVED', count: data.summary.comments.removed, description: 'Comments removed by Oversight' }
]
: [
{ value: 'all', label: 'ALL', count: data.summary.bios.visible + data.summary.bios.hidden, description: 'All public and moderated bios' },
{ value: 'visible', label: 'VISIBLE', count: data.summary.bios.visible, description: 'Bios currently visible on Agent profiles' },
{ value: 'hidden', label: 'HIDDEN', count: data.summary.bios.hidden, description: 'Bios hidden by Oversight' }
]
);
const queueActionLabel = $derived(
queueDecision === 'approve'
? queueReviewing?.source === 'report' ? 'KEEP CONTENT' : queueReviewing?.contentType === 'comment' ? 'APPROVE & PUBLISH' : 'APPROVE REVISION'
: queueReviewing?.source === 'report'
? queueReviewing?.contentType === 'comment' ? 'REMOVE COMMENT' : `HIDE ${queueReviewing?.contentType === 'profile_bio' ? 'BIO' : 'GUIDE'}`
: queueReviewing?.contentType === 'comment' ? 'REJECT & REMOVE' : 'REJECT REVISION'
);
const queueConsequence = $derived(
queueDecision === 'approve'
? queueReviewing?.source === 'report'
? 'This report will be dismissed. Other reports for the same content will remain pending.'
: 'This scan will be approved and the queued content will become public.'
: queueReviewing?.source === 'report'
? 'The content will be hidden or removed, and all pending reports for it will be resolved.'
: 'This scan will be rejected. The queued revision will not be published.'
);
function openModeration(item: CommunityItem) {
reviewing = item;
moderationAction = isComment(item)
? item.status === 'removed' ? 'restoreComment' : 'removeComment'
: item.status === 'hidden' ? 'restoreBio' : 'hideBio';
moderationReason = '';
confirming = false;
}
function closeModeration() {
reviewing = null;
moderationAction = null;
moderationReason = '';
confirming = false;
}
function openQueueReview(item: QueueItem) {
queueReviewing = item;
queueDecision = 'approve';
queueReason = '';
queueConfirming = false;
}
function closeQueueReview() {
queueReviewing = null;
queueReason = '';
queueConfirming = false;
}
$effect(() => {
const token = String(form?.communityResultToken ?? '');
if (!token || token === handledResultToken) return;
handledResultToken = token;
if (form?.communitySaved) {
closeModeration();
closeQueueReview();
}
});
</script>
<svelte:head><title>Community Oversight — ISAC</title></svelte:head>
<svelte:window onkeydown={(event) => {
if (event.key === 'Escape' && reviewing) closeModeration();
if (event.key === 'Escape' && queueReviewing) closeQueueReview();
}}/>
<div class="module-toolbar moderation-toolbar">
<div>
<p class="eyebrow">COMMUNITY OVERSIGHT / PUBLIC CONTENT CONTROL</p>
<h2>COMMUNITY OVERSIGHT</h2>
<p>Resolve automated safety flags and community reports, then inspect the retained comment and biography history when needed.</p>
</div>
<div class="queue-readout">
<span>{data.tab === 'queue' ? 'PENDING REVIEW' : data.tab === 'comments' ? 'VISIBLE COMMENTS' : 'VISIBLE BIOS'}</span>
<strong>{data.tab === 'queue' ? data.summary.queue.total : data.tab === 'comments' ? data.summary.comments.visible : data.summary.bios.visible}</strong>
<small>{data.tab === 'queue' ? 'EXCEPTIONS ONLY' : 'PUBLIC RECORDS'}</small>
</div>
</div>
<nav class="community-tabs" aria-label="Community oversight channels">
<a class:active={data.tab === 'queue'} aria-current={data.tab === 'queue' ? 'page' : undefined} href="/isac/community?tab=queue&status=all">
<span>REVIEW QUEUE</span><b>{data.summary.queue.total}</b>
</a>
{#if canModerateComments}
<a class:active={data.tab === 'comments'} aria-current={data.tab === 'comments' ? 'page' : undefined} href="/isac/community?tab=comments&status=all">
<span>COMMENTS</span><b>{data.summary.comments.visible + data.summary.comments.removed}</b>
</a>
{/if}
{#if canModerateProfiles}
<a class:active={data.tab === 'bios'} aria-current={data.tab === 'bios' ? 'page' : undefined} href="/isac/community?tab=bios&status=all">
<span>PROFILE BIOS</span><b>{data.summary.bios.visible + data.summary.bios.hidden}</b>
</a>
{/if}
</nav>
<KpiFilters parameter="status" active={data.status} items={statusItems}/>
<form class="audit-filter community-filter">
<input type="hidden" name="tab" value={data.tab}/>
<input type="hidden" name="status" value={data.status}/>
<input name="search" type="search" value={data.search} placeholder={data.tab === 'queue' ? 'AUTHOR, TARGET, CATEGORY, OR CONTENT' : data.tab === 'comments' ? 'AUTHOR, TARGET, OR COMMENT TEXT' : 'AGENT OR BIO TEXT'}/>
<button class="secondary">{data.tab === 'queue' ? 'SEARCH QUEUE →' : 'SEARCH COMMUNITY →'}</button>
</form>
<RegistryPagination pagination={data.pagination}/>
<div class="community-registry">
{#each data.items as item}
{#if isQueueItem(item)}
<article class="queue-item" class:priority={item.riskLevel === 'high' || item.scanStatus === 'error'}>
<header>
<AgentIdentityLink agent={item.author} meta="CONTENT AUTHOR" />
<div class="community-state">
<span>{date(item.queuedAt)}</span>
<b class:visible={item.source === 'report'}>{item.source === 'scan' ? 'AUTOMATED SCAN' : 'USER REPORT'}</b>
</div>
</header>
<div class="community-target">
<span>{item.contentType.replace('_', ' ').toUpperCase()}</span>
<a href={item.targetHref}>{item.targetTitle} →</a>
</div>
<div class="queue-evidence">
<div class="queue-signal">
<span class:priority={item.riskLevel === 'high'}>{item.riskLevel.toUpperCase()}</span>
<strong>{item.category ? item.category.replace('_', ' ').toUpperCase() : item.scanStatus?.replace('_', ' ').toUpperCase()}</strong>
</div>
<p>{item.reason}</p>
{#if item.reporterName}<small>REPORTED BY {item.reporterName}</small>{/if}
{#if item.provider}<small>{item.provider.toUpperCase()} / {item.model ?? 'MODEL UNKNOWN'}</small>{/if}
</div>
<div class="community-markdown-preview">
<MarkdownContent html={item.bodyHtml} compact emptyMessage="The queued content is no longer available."/>
</div>
<footer>
<span>{item.source === 'scan' ? `SCAN ${item.id.slice(0,8).toUpperCase()}` : `REPORT ${item.id.slice(0,8).toUpperCase()}`}</span>
<button class="secondary" onclick={() => openQueueReview(item)}>REVIEW EVIDENCE →</button>
</footer>
</article>
{:else}
<article class:moderated={item.status === 'removed' || item.status === 'hidden'}>
<header>
<AgentIdentityLink agent={itemAgent(item)} meta={isComment(item) ? 'COMMENT AUTHOR' : 'PROFILE OWNER'} />
<div class="community-state">
<span>{date(itemTimestamp(item))}</span>
<b class:visible={item.status === 'visible'}>{item.status.toUpperCase()}</b>
</div>
</header>
{#if isComment(item)}
<div class="community-target">
<span>{item.targetType.toUpperCase()}</span>
<a href={item.targetHref}>{item.targetTitle} →</a>
</div>
{:else}
<div class="community-target"><span>PUBLIC PROFILE BIO</span><a href={`/agents/${encodeURIComponent(item.agent.id)}`}>VIEW AGENT PROFILE →</a></div>
{/if}
<div class="community-markdown-preview">
<MarkdownContent html={item.bodyHtml} compact emptyMessage={isComment(item) ? 'Comment body unavailable.' : 'No bio content.'}/>
</div>
{#if item.moderationReason}
<div class="moderation-evidence"><span>LAST OVERSIGHT REASON</span><p>{item.moderationReason}</p></div>
{/if}
<footer>
<span>{isComment(item) ? `COMMENT ${item.id.slice(0,8).toUpperCase()}` : `AGENT ${item.id.slice(0,8).toUpperCase()}`}</span>
{#if (isComment(item) && canModerateComments) || (!isComment(item) && canModerateProfiles)}
<button class={item.status === 'visible' ? 'danger' : 'secondary'} onclick={() => openModeration(item)}>
{item.status === 'visible' ? isComment(item) ? 'REMOVE COMMENT →' : 'HIDE BIO →' : isComment(item) ? 'RESTORE COMMENT →' : 'RESTORE BIO →'}
</button>
{/if}
</footer>
</article>
{/if}
{:else}
<div class="empty-dashboard">
<strong>NO MATCHING {data.tab === 'queue' ? 'REVIEW EXCEPTIONS' : data.tab === 'comments' ? 'COMMENTS' : 'PROFILE BIOS'}</strong>
<span>{data.tab === 'queue' ? 'Automated checks have not routed any matching content to human Oversight.' : 'Adjust the status or search filters to inspect another part of the community record.'}</span>
</div>
{/each}
</div>
{#if queueReviewing}
<div class="isac-modal-backdrop" role="presentation">
<div class="isac-confirm-modal community-review-modal" role="dialog" aria-modal="true" aria-labelledby="queue-review-title">
<div class="editor-title">
<div>
<p class="eyebrow">{queueReviewing.source === 'scan' ? 'AUTOMATED SAFETY FLAG' : 'COMMUNITY REPORT'} / {queueReviewing.contentType.replace('_', ' ').toUpperCase()}</p>
<h3 id="queue-review-title">REVIEW CONTENT EXCEPTION</h3>
</div>
<button aria-label="Close content review" onclick={closeQueueReview}>×</button>
</div>
<div class="community-review-identity">
<AgentIdentityLink agent={queueReviewing.author} meta="CONTENT AUTHOR" />
<span>{date(queueReviewing.queuedAt)}</span>
</div>
<div class="community-target review-target">
<span>{queueReviewing.contentType.replace('_', ' ').toUpperCase()}</span>
<a href={queueReviewing.targetHref}>{queueReviewing.targetTitle} →</a>
</div>
<div class="queue-review-evidence">
<div>
<span>SOURCE</span><strong>{queueReviewing.source === 'scan' ? 'AUTOMATED SCAN' : 'USER REPORT'}</strong>
</div>
<div>
<span>RISK / CATEGORY</span><strong>{queueReviewing.riskLevel.toUpperCase()} · {(queueReviewing.category ?? queueReviewing.scanStatus ?? 'REVIEW').replace('_', ' ').toUpperCase()}</strong>
</div>
{#if queueReviewing.provider}
<div><span>PROVIDER / MODEL</span><strong>{queueReviewing.provider.toUpperCase()} · {queueReviewing.model ?? 'UNKNOWN'}</strong></div>
{/if}
{#if queueReviewing.providerRequestId}
<div><span>REQUEST ID</span><strong>{queueReviewing.providerRequestId}</strong></div>
{/if}
{#if queueReviewing.reporterName}
<div><span>REPORTER</span><strong>{queueReviewing.reporterName}</strong></div>
{/if}
</div>
<div class="moderation-evidence queue-reason-evidence"><span>ROUTING REASON</span><p>{queueReviewing.reason}</p></div>
{#if queueReviewing.flags.length || queueReviewing.ruleMatches.length || queueReviewing.scores.length}
<div class="queue-signals">
{#if queueReviewing.flags.length}<div><span>FLAGGED CATEGORIES</span><p>{queueReviewing.flags.join(' · ')}</p></div>{/if}
{#if queueReviewing.ruleMatches.length}<div><span>LOCAL RULES</span>{#each queueReviewing.ruleMatches as match}<p><b>{match.label}</b>{match.excerpt ? ` — ${match.excerpt}` : ''}</p>{/each}</div>{/if}
{#if queueReviewing.scores.length}<div><span>TOP PROVIDER SCORES</span>{#each queueReviewing.scores as score}<p><b>{score.category}</b> {(score.score * 100).toFixed(1)}%</p>{/each}</div>{/if}
</div>
{/if}
<div class="community-review-content"><MarkdownContent html={queueReviewing.bodyHtml} emptyMessage="The queued content is no longer available."/></div>
<div class="decision-tabs queue-decision-tabs" role="group" aria-label="Moderation decision">
<button type="button" class:active={queueDecision === 'approve'} aria-pressed={queueDecision === 'approve'} onclick={() => { queueDecision = 'approve'; queueConfirming = false; }}>
<b>✓</b><span>{queueReviewing.source === 'report' ? 'KEEP' : 'APPROVE'}<small>{queueReviewing.source === 'report' ? 'DISMISS THIS REPORT' : 'PUBLISH QUEUED CONTENT'}</small></span>
</button>
<button type="button" class:active={queueDecision === 'reject'} aria-pressed={queueDecision === 'reject'} onclick={() => { queueDecision = 'reject'; queueConfirming = false; }}>
<b>×</b><span>{queueReviewing.source === 'report' ? 'REMOVE' : 'REJECT'}<small>{queueReviewing.source === 'report' ? 'RESOLVE ALL REPORTS' : 'KEEP CONTENT HIDDEN'}</small></span>
</button>
</div>
{#if !queueConfirming}
<label class="field moderation-reason">
OVERSIGHT REASON — REQUIRED
<textarea bind:value={queueReason} minlength="3" maxlength="500" required placeholder="Record the evidence and policy basis for this decision…"></textarea>
</label>
{#if form?.communityQueueError}<p class="error queue-error">{form.communityQueueError}</p>{/if}
<div class="moderation-warning" class:rejecting={queueDecision === 'reject'}>
<b>!</b><span>{queueConsequence} The review decision and reason will be added to the audit history.</span>
</div>
<div class="form-actions">
<button type="button" class="secondary" onclick={closeQueueReview}>CANCEL</button>
<button type="button" class="primary" disabled={queueReason.trim().length < 3} onclick={() => queueConfirming = true}>REVIEW ACTION →</button>
</div>
{:else}
{@const queueReviewingId = queueReviewing.id}
{@const queueReviewingSource = queueReviewing.source}
<div class="community-final-confirm" role="alert">
<div><span>FINAL CONFIRMATION</span><strong>{queueActionLabel}?</strong><p>{queueConsequence}</p></div>
<dl><dt>OVERSIGHT REASON</dt><dd>{queueReason}</dd></dl>
</div>
<PendingForm action={queueDecision === 'approve' ? '?/approveQueue' : '?/rejectQueue'}>
{#snippet children(pending)}
<input type="hidden" name="id" value={queueReviewingId}/>
<input type="hidden" name="source" value={queueReviewingSource}/>
<input type="hidden" name="reason" value={queueReason}/>
{#if form?.communityQueueError}<p class="error">{form.communityQueueError}</p>{/if}
<div class="form-actions">
<button type="button" class="secondary" disabled={pending} onclick={() => queueConfirming = false}>BACK</button>
<button class={queueDecision === 'reject' ? 'danger' : 'primary'} disabled={pending} aria-busy={pending}>
{pending ? 'PROCESSING…' : `CONFIRM ${queueActionLabel} →`}
</button>
</div>
{/snippet}
</PendingForm>
{/if}
</div>
</div>
{/if}
{#if reviewing && moderationAction}
<div class="isac-modal-backdrop" role="presentation">
<div class="isac-confirm-modal community-review-modal" role="dialog" aria-modal="true" aria-labelledby="community-review-title">
<div class="editor-title">
<div><p class="eyebrow">COMMUNITY CONTROL / {reviewing.status.toUpperCase()}</p><h3 id="community-review-title">{actionLabel}</h3></div>
<button aria-label="Close community moderation review" onclick={closeModeration}>×</button>
</div>
<div class="community-review-identity">
<AgentIdentityLink agent={itemAgent(reviewing)} meta={isComment(reviewing) ? 'COMMENT AUTHOR' : 'PROFILE OWNER'} />
<span>{date(itemTimestamp(reviewing))}</span>
</div>
{#if isComment(reviewing)}
<div class="community-target review-target"><span>{reviewing.targetType.toUpperCase()}</span><a href={reviewing.targetHref}>{reviewing.targetTitle} →</a></div>
{/if}
<div class="community-review-content"><MarkdownContent html={reviewing.bodyHtml} emptyMessage="No content is available for review."/></div>
{#if reviewing.moderationReason}<div class="moderation-evidence"><span>PREVIOUS OVERSIGHT REASON</span><p>{reviewing.moderationReason}</p></div>{/if}
{#if !confirming}
<label class="field moderation-reason">
OVERSIGHT REASON — REQUIRED
<textarea bind:value={moderationReason} maxlength="500" required placeholder="Record a specific policy, safety, or restoration reason…"></textarea>
</label>
{#if form?.communityError}<p class="error">{form.communityError}</p>{/if}
<div class="moderation-warning" class:rejecting={moderationAction === 'removeComment' || moderationAction === 'hideBio'}>
<b>!</b><span>{actionConsequence} The original content and this reason remain available to authorized Oversight.</span>
</div>
<div class="form-actions">
<button type="button" class="secondary" onclick={closeModeration}>CANCEL</button>
<button type="button" class="primary" disabled={!moderationReason.trim()} onclick={() => confirming = true}>REVIEW ACTION →</button>
</div>
{:else}
{@const reviewingId = reviewing.id}
<div class="community-final-confirm" role="alert">
<div><span>FINAL CONFIRMATION</span><strong>{actionLabel}?</strong><p>{actionConsequence}</p></div>
<dl><dt>OVERSIGHT REASON</dt><dd>{moderationReason}</dd></dl>
</div>
<PendingForm action={actionRoute}>
{#snippet children(pending)}
<input type="hidden" name="id" value={reviewingId}/>
<input type="hidden" name="reason" value={moderationReason}/>
{#if form?.communityError}<p class="error">{form.communityError}</p>{/if}
<div class="form-actions">
<button type="button" class="secondary" disabled={pending} onclick={() => confirming = false}>BACK</button>
<button class={moderationAction === 'removeComment' || moderationAction === 'hideBio' ? 'danger' : 'primary'} disabled={pending} aria-busy={pending}>
{pending ? 'PROCESSING…' : `CONFIRM ${actionLabel} →`}
</button>
</div>
{/snippet}
</PendingForm>
{/if}
</div>
</div>
{/if}
<style>
.community-tabs{height:auto;display:grid;grid-template-columns:repeat(3,minmax(0,1fr));margin:0 0 8px;border:1px solid var(--shd-border);background:#0d1011}
.community-tabs a{min-height:46px;display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:8px;padding:8px 11px;border-left:1px solid var(--shd-border);border-bottom:2px solid transparent;color:var(--shd-text-muted)}.community-tabs a:first-child{border-left:0}.community-tabs a span,.community-tabs a b{font-size:var(--shd-type-telemetry);font-weight:900;letter-spacing:.8px}.community-tabs a b{color:var(--shd-text-dim)}.community-tabs a:hover,.community-tabs a:focus-visible{background:var(--shd-surface-active);color:var(--shd-text)}.community-tabs a.active{border-bottom-color:var(--shd-orange);background:#211a0e;color:var(--shd-orange)}.community-tabs a.active b{color:var(--shd-orange)}
.community-filter{grid-template-columns:minmax(0,1fr) auto}.community-registry{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:7px;margin-top:7px}.community-registry>article{min-width:0;display:grid;grid-template-rows:auto auto minmax(100px,1fr) auto;border:1px solid var(--shd-border);border-left:2px solid var(--shd-info);background:var(--shd-surface)}.community-registry>article.moderated{border-left-color:var(--shd-danger)}
.community-registry article>header{min-height:55px;display:flex;align-items:center;justify-content:space-between;gap:10px;padding:8px 10px;border-bottom:1px solid var(--shd-border);background:var(--shd-surface-raised)}.community-registry article>header :global(.agent-identity){flex:1}.community-state{display:grid;justify-items:end;gap:4px}.community-state span,.community-state b{font-size:var(--shd-type-telemetry);font-weight:900;letter-spacing:.7px}.community-state span{color:var(--shd-text-muted)}.community-state b{padding:3px 5px;border:1px solid var(--shd-danger);color:var(--shd-danger)}.community-state b.visible{border-color:var(--shd-success);color:var(--shd-success)}
.community-target{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:7px 10px;border-bottom:1px solid var(--shd-border-soft);background:#0d1011}.community-target span,.community-target a{font-size:var(--shd-type-telemetry);font-weight:900;letter-spacing:.7px}.community-target span{color:var(--shd-text-muted)}.community-target a{overflow:hidden;color:var(--shd-orange);text-overflow:ellipsis;white-space:nowrap}.community-markdown-preview{max-height:150px;overflow:hidden;padding:11px;position:relative}.community-markdown-preview:after{position:absolute;right:0;bottom:0;left:0;height:25px;background:linear-gradient(transparent,var(--shd-surface));content:"";pointer-events:none}
.community-registry>article.queue-item{grid-template-rows:auto auto auto minmax(100px,1fr) auto;border-left:1px solid var(--shd-info)}.community-registry>article.queue-item.priority{border-color:var(--shd-danger)}
.queue-evidence{display:grid;gap:5px;padding:9px 10px;border-bottom:1px solid var(--shd-border);background:var(--shd-surface-raised)}.queue-evidence p{margin:0;color:var(--shd-text);line-height:1.4;overflow-wrap:anywhere}.queue-evidence small{color:var(--shd-text-muted);font-size:var(--shd-type-telemetry);font-weight:900;letter-spacing:.7px;overflow-wrap:anywhere}.queue-signal{display:flex;align-items:center;gap:7px}.queue-signal span,.queue-signal strong{padding:3px 5px;border:1px solid var(--shd-border-strong);color:var(--shd-text-muted);font-size:var(--shd-type-telemetry);font-weight:900;letter-spacing:.7px}.queue-signal span.priority{border-color:var(--shd-danger);color:var(--shd-danger)}
.moderation-evidence{display:grid;gap:4px;margin:0 10px 9px;padding:8px;border:1px solid var(--shd-border);border-left:2px solid var(--shd-danger);background:#1b1111}.moderation-evidence span{color:var(--shd-danger);font-size:var(--shd-type-telemetry);font-weight:900;letter-spacing:.7px}.moderation-evidence p{margin:0;color:var(--shd-text-muted);font-size:var(--shd-type-supporting);line-height:1.4}
.community-registry article>footer{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:8px 10px;border-top:1px solid var(--shd-border);background:var(--shd-surface-raised)}.community-registry article>footer>span{color:var(--shd-text-dim);font-size:var(--shd-type-telemetry);font-weight:900;letter-spacing:.7px}.community-registry article>footer button{min-height:36px;padding:0 10px}
.community-review-modal{width:min(900px,calc(100vw - 28px));max-height:calc(100vh - 28px);overflow:auto}.community-review-identity{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:10px;border-bottom:1px solid var(--shd-border)}.community-review-identity :global(.agent-identity){flex:1}.community-review-identity>span{color:var(--shd-text-muted);font-size:var(--shd-type-telemetry);font-weight:900}.review-target{border-bottom:1px solid var(--shd-border)}.community-review-content{max-height:330px;overflow:auto;padding:14px;border-bottom:1px solid var(--shd-border);background:#0d1011}.community-review-content :global(.markdown-content){max-width:none}.moderation-reason{margin:10px}.moderation-reason textarea{min-height:90px}.community-final-confirm{display:grid;grid-template-columns:minmax(0,1fr) minmax(220px,.65fr);gap:9px;margin:10px;padding:10px;border:1px solid var(--shd-orange);background:#1b160d}.community-final-confirm>div{display:grid;gap:4px}.community-final-confirm span,.community-final-confirm dt{color:var(--shd-orange);font-size:var(--shd-type-telemetry);font-weight:900;letter-spacing:.8px}.community-final-confirm p,.community-final-confirm dd{margin:0;color:var(--shd-text-muted);font-size:var(--shd-type-supporting);line-height:1.4}.community-final-confirm dl{display:grid;align-content:start;gap:5px;margin:0;padding:8px;border:1px solid var(--shd-border);background:#0d1011}.community-review-modal>.moderation-warning,.community-review-modal>.form-actions,.community-review-modal :global(form > .form-actions),.community-review-modal :global(form > .error){margin-right:10px;margin-left:10px}
.queue-review-evidence{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));border-bottom:1px solid var(--shd-border)}.queue-review-evidence>div{min-width:0;display:grid;gap:3px;padding:8px 10px;border-right:1px solid var(--shd-border);border-top:1px solid var(--shd-border-soft)}.queue-review-evidence span,.queue-signals span{color:var(--shd-text-muted);font-size:var(--shd-type-telemetry);font-weight:900;letter-spacing:.7px}.queue-review-evidence strong{overflow-wrap:anywhere}.queue-reason-evidence{margin-top:10px}.queue-signals{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:1px;margin:0 10px 10px;border:1px solid var(--shd-border);background:var(--shd-border)}.queue-signals>div{min-width:0;padding:8px;background:#0d1011}.queue-signals p{margin:4px 0 0;color:var(--shd-text-muted);font-size:var(--shd-type-supporting);overflow-wrap:anywhere}.queue-decision-tabs{margin:10px}.queue-error{margin:0 10px}
@media(max-width:900px){.community-registry{grid-template-columns:1fr}}
@media(max-width:650px){.community-tabs{grid-template-columns:1fr}.community-tabs a{min-height:52px;border-top:1px solid var(--shd-border);border-left:0}.community-tabs a:first-child{border-top:0}.community-filter{grid-template-columns:1fr}.community-filter button{min-height:44px}.community-registry article>header,.community-registry article>footer,.community-review-identity,.community-target{align-items:stretch;flex-direction:column}.community-state{justify-items:start}.community-target a{white-space:normal}.community-registry article>footer button{width:100%;min-height:44px}.community-final-confirm,.queue-review-evidence,.queue-signals{grid-template-columns:1fr}.community-review-modal>.form-actions,.community-review-modal :global(.form-actions){display:grid;grid-template-columns:1fr 1fr}.community-review-modal button{min-height:44px}}
@media(prefers-reduced-motion:reduce){.community-tabs a,.community-registry button{transition:none}}
</style>