feat: align merge request review workflow with selector refs
This commit is contained in:
@@ -31,6 +31,12 @@ const detailLoader = await Deno.readTextFile(
|
||||
import.meta.url,
|
||||
),
|
||||
);
|
||||
const detailPage = await Deno.readTextFile(
|
||||
new URL(
|
||||
"../../../routes/w/[workspaceId]/merge-requests/[mergeRequestId]/+page.svelte",
|
||||
import.meta.url,
|
||||
),
|
||||
);
|
||||
const sidebar = await Deno.readTextFile(
|
||||
new URL("../sidebar/WorkspaceSidebar.svelte", import.meta.url),
|
||||
);
|
||||
@@ -64,3 +70,29 @@ Deno.test("Workspace exposes Merge Request collection and detail pages", () => {
|
||||
"MR resources are absent from navigation",
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("Merge Request UI separates source review freshness from target integration", () => {
|
||||
for (const source of [detailPage, ticketPage]) {
|
||||
assert(
|
||||
source.includes("Fresh source review required"),
|
||||
"missing source-review freshness diagnostic",
|
||||
);
|
||||
assert(
|
||||
source.includes("Target integration"),
|
||||
"missing target-integration status",
|
||||
);
|
||||
assert(
|
||||
source.includes("Target-only movement") &&
|
||||
source.includes("does not invalidate approval for an unchanged source"),
|
||||
"source review and target integration semantics are conflated",
|
||||
);
|
||||
}
|
||||
assert(
|
||||
detailPage.includes("selector_from moved from"),
|
||||
"source ref mismatch is not explained",
|
||||
);
|
||||
assert(
|
||||
detailPage.includes("CompleteMergeRequest"),
|
||||
"target integration authority is not named",
|
||||
);
|
||||
});
|
||||
|
||||
+58
-1
@@ -1,5 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { mergeRequestPagePath } from "$lib/workspace/api/merge-requests";
|
||||
import {
|
||||
mergeRequestPagePath,
|
||||
type MergeRequestDetail,
|
||||
} from "$lib/workspace/api/merge-requests";
|
||||
import type { PageData } from "./$types";
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
@@ -13,6 +16,52 @@
|
||||
const value = event[key];
|
||||
return typeof value === "string" && value.length > 0 ? value : null;
|
||||
}
|
||||
|
||||
function sourceReviewFreshness(mergeRequest: MergeRequestDetail): string {
|
||||
const source = mergeRequest.source.ref;
|
||||
if (!source) return "Source review unavailable: selector_from is unresolved.";
|
||||
|
||||
const effectiveReview = [...mergeRequest.thread].reverse().find((event) => {
|
||||
if (event.kind !== "review" || event.subject_ref !== source) return false;
|
||||
return !mergeRequest.thread.some(
|
||||
(candidate) =>
|
||||
candidate.kind === "review_revoked" &&
|
||||
candidate.review_event_id === event.event_id,
|
||||
);
|
||||
});
|
||||
if (effectiveReview) {
|
||||
return effectiveReview.decision === "approve"
|
||||
? `Current source approved at exact ref ${source}.`
|
||||
: `Current source requests changes at exact ref ${source}.`;
|
||||
}
|
||||
|
||||
const latestEvidence = [...mergeRequest.thread].reverse().find(
|
||||
(event) =>
|
||||
(event.kind === "review" || event.kind === "review_requested") &&
|
||||
typeof event.subject_ref === "string",
|
||||
);
|
||||
if (latestEvidence?.subject_ref && latestEvidence.subject_ref !== source) {
|
||||
return `Fresh source review required: selector_from moved from ${latestEvidence.subject_ref} to ${source}.`;
|
||||
}
|
||||
if (
|
||||
mergeRequest.thread.some(
|
||||
(event) => event.kind === "review_requested" && event.subject_ref === source,
|
||||
)
|
||||
) {
|
||||
return `Current source review pending for exact ref ${source}.`;
|
||||
}
|
||||
return `Fresh source review required: no effective verdict exists for ${source}.`;
|
||||
}
|
||||
|
||||
function targetIntegrationStatus(mergeRequest: MergeRequestDetail): string {
|
||||
if (mergeRequest.state === "merged") {
|
||||
return "Target integration recorded by CompleteMergeRequest.";
|
||||
}
|
||||
if (!mergeRequest.target.ref) {
|
||||
return "Target integration unavailable: selector_to is unresolved.";
|
||||
}
|
||||
return `Target integration awaits Orchestrator action at ${mergeRequest.target.ref}. Target-only movement refreshes integration evidence; it does not invalidate approval for an unchanged source.`;
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head><title>Merge Request · Yoi</title></svelte:head>
|
||||
@@ -53,6 +102,14 @@
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<section class="ticket-detail-section">
|
||||
<div class="ticket-section-heading"><h2>Review and integration status</h2></div>
|
||||
<dl class="ticket-facts">
|
||||
<div><dt>Source review freshness</dt><dd>{sourceReviewFreshness(mergeRequest)}</dd></div>
|
||||
<div><dt>Target integration</dt><dd>{targetIntegrationStatus(mergeRequest)}</dd></div>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<section class="ticket-detail-section">
|
||||
<div class="ticket-section-heading">
|
||||
<h2>Thread</h2><span>{mergeRequest.thread.length}</span>
|
||||
|
||||
@@ -556,14 +556,22 @@
|
||||
<section class="ticket-control-card">
|
||||
<header><h2>Merge Request</h2></header>
|
||||
{#if mergeRequest}
|
||||
<p><strong>{mergeRequest.state}</strong> · review {mergeRequest.review_status}</p>
|
||||
<p><strong>{mergeRequest.state}</strong></p>
|
||||
<p>
|
||||
From <code>{mergeRequest.selector_from ?? "requires repair"}</code>
|
||||
to <code>{mergeRequest.selector_to}</code>
|
||||
</p>
|
||||
{#if mergeRequest.current_subject_ref}
|
||||
<p>Current source <code>{mergeRequest.current_subject_ref}</code></p>
|
||||
{#if mergeRequest.current_subject_ref && mergeRequest.review_subject_ref === mergeRequest.current_subject_ref}
|
||||
<p><strong>Source review:</strong> {mergeRequest.review_status} for exact ref <code>{mergeRequest.current_subject_ref}</code></p>
|
||||
{:else if mergeRequest.current_subject_ref && mergeRequest.review_subject_ref}
|
||||
<p><strong>Fresh source review required:</strong> selector_from moved from <code>{mergeRequest.review_subject_ref}</code> to <code>{mergeRequest.current_subject_ref}</code>.</p>
|
||||
{:else if mergeRequest.current_subject_ref}
|
||||
<p><strong>Fresh source review required:</strong> no effective verdict exists for <code>{mergeRequest.current_subject_ref}</code>.</p>
|
||||
{:else}
|
||||
<p><strong>Source review unavailable:</strong> selector_from is unresolved.</p>
|
||||
{/if}
|
||||
<p><strong>Target integration:</strong> {mergeRequest.state === "merged" ? "recorded" : `awaiting Orchestrator integration into ${mergeRequest.selector_to}`}.</p>
|
||||
<p class="workspace-empty-copy">Target-only movement refreshes integration evidence; it does not invalidate approval for an unchanged source.</p>
|
||||
<a
|
||||
class="workspace-secondary-button"
|
||||
href={mergeRequestPagePath(data.workspaceId, mergeRequest.merge_request_id)}
|
||||
|
||||
Reference in New Issue
Block a user