From 36efc06d7fe9b0c0b8f25945fd989155fbcbed35 Mon Sep 17 00:00:00 2001 From: Cyber Preacher <72062250+Cyber-preacher@users.noreply.github.com> Date: Wed, 19 Aug 2026 20:03:42 +0400 Subject: [PATCH 1/2] Clarify Court report routing choices --- src/pages/courts/CourtReportCreate.tsx | 10 +++++--- .../courts/forms/CourtReportFormSections.tsx | 3 ++- src/pages/courts/model/courtPresentation.ts | 25 +++++++++++++++++++ tests/unit/court-presentation.test.ts | 19 ++++++++++++++ 4 files changed, 53 insertions(+), 4 deletions(-) diff --git a/src/pages/courts/CourtReportCreate.tsx b/src/pages/courts/CourtReportCreate.tsx index 840000a..1f4257f 100644 --- a/src/pages/courts/CourtReportCreate.tsx +++ b/src/pages/courts/CourtReportCreate.tsx @@ -47,7 +47,8 @@ import { import { courtLaneDisplay, courtOffenseDisplay, - courtStandingDisplay, + courtReportLaneChoiceLabel, + courtReportRouteDescription, } from "./model/courtPresentation"; import { courtErrorIssue, @@ -499,7 +500,7 @@ const CourtReportCreate: React.FC = () => { value={`${reason.offenseCode}:${reason.lane}`} > {courtOffenseDisplay(reason.offenseCode).label} ·{" "} - {courtLaneDisplay(reason.lane).label} + {courtReportLaneChoiceLabel(reason.lane)} ))} @@ -527,7 +528,10 @@ const CourtReportCreate: React.FC = () => { direct={selectedReason.standing.directStanding} source={selectedReason.standing.source} /> - {`. ${courtStandingDisplay(selectedReason.standing).description}`} + {`. ${courtReportRouteDescription( + selectedReason.reason.lane, + selectedReason.standing, + )}`} ) : capabilityLoading ? ( "The server is verifying the target, incident time, standing, and available lanes." diff --git a/src/pages/courts/forms/CourtReportFormSections.tsx b/src/pages/courts/forms/CourtReportFormSections.tsx index 5ae8e91..335a5f9 100644 --- a/src/pages/courts/forms/CourtReportFormSections.tsx +++ b/src/pages/courts/forms/CourtReportFormSections.tsx @@ -17,6 +17,7 @@ import { import { courtLaneDisplay, courtOffenseDisplay, + courtReportRouteDescription, } from "../model/courtPresentation"; import { courtEvidenceAccessLabel } from "./courtEvidence"; @@ -137,7 +138,7 @@ export function CourtReportReview({ {selectedReason ? ( diff --git a/src/pages/courts/model/courtPresentation.ts b/src/pages/courts/model/courtPresentation.ts index 0579e72..13f0226 100644 --- a/src/pages/courts/model/courtPresentation.ts +++ b/src/pages/courts/model/courtPresentation.ts @@ -62,6 +62,31 @@ export function courtStandingDisplay(standing: { }; } +export function courtReportLaneChoiceLabel(lane: CourtReportLaneV2Dto): string { + if (lane === "correction") return "Correction only"; + if (lane === "court_report") return "Court review"; + return courtLaneDisplay(lane).label; +} + +export function courtReportRouteDescription( + lane: CourtReportLaneV2Dto, + standing: { direct?: boolean; directStanding?: boolean }, +): string { + if (lane === "correction") { + return "This sends a correction request to the module that owns the record. It does not join a Court trigger or open a case."; + } + if (lane === "scoped_moderation") { + return "This sends the record to the authorized moderation process. It does not join a Court trigger or open a case."; + } + if (lane === "safety_or_protocol_incident") { + return "This sends evidence to the authorized safety or protocol incident process instead of the community Court trigger."; + } + const direct = standing.direct ?? standing.directStanding ?? false; + return direct + ? "This enters Court review and can open a case through verified direct standing." + : "This enters the private community trigger. A case opens only after the protected reporting threshold is reached."; +} + export function courtReportProcessContext( report: Pick< CourtMyReportItemV2Dto, diff --git a/tests/unit/court-presentation.test.ts b/tests/unit/court-presentation.test.ts index ed6c78c..54306a1 100644 --- a/tests/unit/court-presentation.test.ts +++ b/tests/unit/court-presentation.test.ts @@ -11,6 +11,8 @@ import { courtLaneDisplay, courtOffenseDisplay, courtRemedyLabel, + courtReportLaneChoiceLabel, + courtReportRouteDescription, courtReportStateDisplay, courtRemedyExpiry, courtEventDisplay, @@ -129,6 +131,23 @@ test("standing copy separates the referenced legal term from its verification", ); }); +test("report route copy distinguishes non-Court routing from Court triggers", () => { + assert.equal(courtReportLaneChoiceLabel("correction"), "Correction only"); + assert.equal(courtReportLaneChoiceLabel("court_report"), "Court review"); + assert.match( + courtReportRouteDescription("correction", { directStanding: false }), + /does not join a Court trigger or open a case/, + ); + assert.match( + courtReportRouteDescription("court_report", { directStanding: false }), + /private community trigger/, + ); + assert.match( + courtReportRouteDescription("court_report", { directStanding: true }), + /verified direct standing/, + ); +}); + test("long audit values stay readable while preserving their identifying ends", () => { const digest = `sha256:${"a".repeat(64)}`; const compact = compactCourtAuditValue(digest); From 5f7078cbd26f610d46cae0848c6f5af3fb3cd21f Mon Sep 17 00:00:00 2001 From: Cyber Preacher <72062250+Cyber-preacher@users.noreply.github.com> Date: Fri, 21 Aug 2026 13:18:13 +0400 Subject: [PATCH 2/2] Show Court trigger requirements --- src/pages/courts/CourtReportCreate.tsx | 57 ++++++++++++-- src/pages/courts/CourtReportDetail.tsx | 28 +++++++ .../courts/components/CourtPrimitives.tsx | 57 ++++++++++++++ .../courts/components/CourtRecordCards.tsx | 19 +++++ .../courts/forms/CourtReportFormSections.tsx | 10 +++ src/types/api.ts | 9 +++ tests/e2e/courts-v2.spec.ts | 74 +++++++++++++++++-- 7 files changed, 238 insertions(+), 16 deletions(-) diff --git a/src/pages/courts/CourtReportCreate.tsx b/src/pages/courts/CourtReportCreate.tsx index 1f4257f..493ceb3 100644 --- a/src/pages/courts/CourtReportCreate.tsx +++ b/src/pages/courts/CourtReportCreate.tsx @@ -26,6 +26,7 @@ import { courtLabel, CourtStandingReference, CourtTargetPreview, + CourtTriggerCounter, formatCourtInstant, } from "./components/CourtPrimitives"; import { @@ -317,6 +318,11 @@ const CourtReportCreate: React.FC = () => { setSubmissionError("Choose a report reason."); return; } + if (!respondentId.trim()) { + setSubmissionError("A respondent address is required."); + focusCourtField("court-report-respondent"); + return; + } const startsAt = Date.parse(incidentStartsAt); const endsAt = incidentEndsAt ? Date.parse(incidentEndsAt) : null; if (endsAt !== null && endsAt < startsAt) { @@ -539,12 +545,40 @@ const CourtReportCreate: React.FC = () => { "Reasons are limited to those verified for this exact target and incident time." )}

- {selectedReason && capability?.population ? ( -

- Population basis: {courtLabel(capability.population.basis)} ·{" "} - effective{" "} - {formatCourtInstant(capability.population.effectiveAt)}. -

+ {selectedReason ? ( +
+ {selectedReason.reason.lane === "court_report" && + !selectedReason.standing.directStanding ? ( + capability?.population?.communityThreshold ? ( + + ) : ( +

+ A community Court trigger is unavailable because fewer + than three eligible Governors remain after excluding the + respondent. +

+ ) + ) : ( + + )} + {capability?.population ? ( +

+ Governor population:{" "} + {capability.population.eligibleGovernorCount} ·{" "} + {courtLabel(capability.population.basis)} · effective{" "} + {formatCourtInstant(capability.population.effectiveAt)}. +

+ ) : null} +
) : null} {capabilityError ? (
@@ -574,14 +608,19 @@ const CourtReportCreate: React.FC = () => { hint={ capability?.defaults.respondentIdSource === "sole_target_owner" - ? "Suggested from the record owner. Change it if another Human Node is responsible." - : "The Human Node whose conduct is being reported, when known." + ? "Set from the owner of the reported record." + : "The Human Node whose conduct is being reported." } > setRespondentId(event.target.value)} + readOnly={ + capability?.defaults.respondentIdSource === + "sole_target_owner" + } + required /> { incidentStartsAt={incidentStartsAt} onGoodFaithAttestedChange={setGoodFaithAttested} protectiveReviewRequested={immediateProtectionRequested} + respondentId={respondentId} selectedReason={selectedReason} statementAccess={statementAccess} statementLength={statement.trim().length} @@ -719,6 +759,7 @@ const CourtReportCreate: React.FC = () => { !capability || capabilityLoading || !reasonKey || + !respondentId.trim() || statement.trim().length < COURT_STATEMENT_MIN_LENGTH || statement.trim().length > COURT_STATEMENT_MAX_LENGTH || !goodFaithAttested || diff --git a/src/pages/courts/CourtReportDetail.tsx b/src/pages/courts/CourtReportDetail.tsx index 55de05e..06bdcd0 100644 --- a/src/pages/courts/CourtReportDetail.tsx +++ b/src/pages/courts/CourtReportDetail.tsx @@ -48,6 +48,7 @@ import { CourtStandingReference, CourtStateSummary, CourtTargetPreview, + CourtTriggerCounter, courtTone, formatCourtInstant, } from "./components/CourtPrimitives"; @@ -349,6 +350,17 @@ const CourtReportDetail: React.FC = () => { label="Target" value={courtLabel(report.target.type)} /> + {report.respondentId ? ( + + } + /> + ) : null} { } /> + {report.triggerProgress ? ( + + ) : null} {process ? (

Next: {process.nextStep} @@ -492,7 +511,15 @@ const CourtReportDetail: React.FC = () => { onChange={(event) => setRespondentId(event.target.value) } + readOnly={Boolean(report.respondentId)} + required /> + {report.respondentId ? ( + + The respondent is fixed to the reported record and + cannot be changed by amendment. + + ) : null}

+
+

{label}

+

+ {current === undefined + ? `${required} required` + : `${completed} / ${required}`} +

+
+ {current !== undefined ? ( +
+
+
+ ) : null} +

+ {description ?? + (current === undefined + ? `This action needs matching reports from ${required} eligible Governors.` + : completed >= required + ? "The Governor threshold has been reached." + : `${required - completed} more matching Governor ${required - completed === 1 ? "report" : "reports"} required.`)} + {viewerCounts === undefined + ? "" + : viewerCounts + ? " Your report counts toward this threshold." + : " Your report is recorded, but it does not add Governor support."} +

+
+ ); +} + export function courtTone( value: string, ): "danger" | "neutral" | "ok" | "primary" | "warn" { diff --git a/src/pages/courts/components/CourtRecordCards.tsx b/src/pages/courts/components/CourtRecordCards.tsx index c86815f..d6cb3ef 100644 --- a/src/pages/courts/components/CourtRecordCards.tsx +++ b/src/pages/courts/components/CourtRecordCards.tsx @@ -26,6 +26,7 @@ import { CourtCopyValue, CourtDeadline, CourtStandingReference, + CourtTriggerCounter, courtLabel, courtTone, formatCourtInstant, @@ -171,6 +172,17 @@ export function CourtReportCard({ label="Updated" value={formatCourtInstant(report.updatedAt)} /> + {report.respondentId ? ( + + } + /> + ) : null} {report.amendmentDueAt ? ( ) : null} + {report.triggerProgress ? ( + + ) : null}
{report.target.route ? (