Skip to content

Commit e5acae4

Browse files
authored
fix(ui): show Workboard comments in edit modal
Show existing Workboard card comments in the edit modal and allow operators to append a new comment through the existing `workboard.cards.comment` gateway method. Refs #88592. Verification: - node scripts/run-vitest.mjs ui/src/ui/views/workboard.test.ts - pnpm tsgo:test:ui - git diff --check origin/main...HEAD - .agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main Co-authored-by: Ted Li <[email protected]>
1 parent 8076eea commit e5acae4

3 files changed

Lines changed: 126 additions & 8 deletions

File tree

ui/src/ui/controllers/workboard.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,7 @@ export type WorkboardUiState = {
310310
draftAgentId: string;
311311
draftSessionKey: string;
312312
draftTemplateId: WorkboardTemplateId | "";
313+
draftCommentBody: string;
313314
busyCardId: string | null;
314315
draggedCardId: string | null;
315316
syncingCardIds: Set<string>;
@@ -352,6 +353,7 @@ function createDefaultState(): WorkboardUiState {
352353
draftAgentId: "",
353354
draftSessionKey: "",
354355
draftTemplateId: "",
356+
draftCommentBody: "",
355357
busyCardId: null,
356358
draggedCardId: null,
357359
syncingCardIds: new Set(),
@@ -947,6 +949,7 @@ function resetDraftState(state: WorkboardUiState) {
947949
state.draftAgentId = "";
948950
state.draftSessionKey = "";
949951
state.draftTemplateId = "";
952+
state.draftCommentBody = "";
950953
}
951954

952955
function normalizeDraftLabels(value: string): string[] {
@@ -1418,6 +1421,34 @@ export async function saveWorkboardCardDraft(params: {
14181421
}
14191422
}
14201423

1424+
export async function addWorkboardCardComment(params: {
1425+
host: WorkboardHost;
1426+
client: GatewayBrowserClient | null;
1427+
requestUpdate?: () => void;
1428+
}) {
1429+
const state = getWorkboardState(params.host);
1430+
const body = state.draftCommentBody.trim();
1431+
if (!state.editingCardId || !params.client || !body) {
1432+
return;
1433+
}
1434+
state.loading = true;
1435+
state.error = null;
1436+
params.requestUpdate?.();
1437+
try {
1438+
const payload = await params.client.request("workboard.cards.comment", {
1439+
id: state.editingCardId,
1440+
body,
1441+
});
1442+
replaceCard(state, normalizeCardPayload(payload));
1443+
state.draftCommentBody = "";
1444+
} catch (error) {
1445+
state.error = formatError(error);
1446+
} finally {
1447+
state.loading = false;
1448+
params.requestUpdate?.();
1449+
}
1450+
}
1451+
14211452
export async function moveWorkboardCard(params: {
14221453
host: WorkboardHost;
14231454
client: GatewayBrowserClient | null;

ui/src/ui/views/workboard.test.ts

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -609,16 +609,33 @@ describe("renderWorkboard", () => {
609609
position: 1000,
610610
createdAt: 1,
611611
updatedAt: 1,
612+
metadata: {
613+
comments: [{ id: "comment-1", body: "Needs owner check", createdAt: 2 }],
614+
},
612615
},
613616
];
614-
const request = vi.fn(async () => ({
615-
card: {
616-
...state.cards[0],
617-
title: "Renamed",
618-
priority: "high",
619-
updatedAt: 2,
620-
},
621-
}));
617+
const request = vi.fn(async (method: string) =>
618+
method === "workboard.cards.comment"
619+
? {
620+
card: {
621+
...state.cards[0],
622+
metadata: {
623+
comments: [
624+
...(state.cards[0]?.metadata?.comments ?? []),
625+
{ id: "comment-2", body: "Ship after CI", createdAt: 3 },
626+
],
627+
},
628+
},
629+
}
630+
: {
631+
card: {
632+
...state.cards[0],
633+
title: "Renamed",
634+
priority: "high",
635+
updatedAt: 2,
636+
},
637+
},
638+
);
622639
const props = {
623640
host,
624641
client: { request } as unknown as GatewayBrowserClient,
@@ -638,6 +655,24 @@ describe("renderWorkboard", () => {
638655
render(renderWorkboard(props), container);
639656

640657
expect(container.querySelector('[role="dialog"]')?.textContent).toContain("Edit card");
658+
expect(container.querySelector('[role="dialog"]')?.textContent).toContain("Needs owner check");
659+
const commentInput = container.querySelector<HTMLTextAreaElement>(".workboard-comments__input");
660+
commentInput!.value = "Ship after CI";
661+
commentInput!.dispatchEvent(new InputEvent("input", { bubbles: true }));
662+
render(renderWorkboard(props), container);
663+
[...container.querySelectorAll<HTMLButtonElement>("button")]
664+
.find((button) => button.textContent?.includes("Create"))
665+
?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
666+
await Promise.resolve();
667+
await Promise.resolve();
668+
669+
expect(request).toHaveBeenCalledWith("workboard.cards.comment", {
670+
id: "card-1",
671+
body: "Ship after CI",
672+
});
673+
expect(state.cards[0]?.metadata?.comments?.at(-1)?.body).toBe("Ship after CI");
674+
render(renderWorkboard(props), container);
675+
641676
const title = container.querySelector<HTMLInputElement>(".workboard-draft__title");
642677
expect(title?.value).toBe("Rename me");
643678
title!.value = "Renamed";

ui/src/ui/views/workboard.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { html, nothing } from "lit";
22
import { t } from "../../i18n/index.ts";
33
import {
4+
addWorkboardCardComment,
45
archiveWorkboardCard,
56
deleteWorkboardCard,
67
dispatchWorkboard,
@@ -352,6 +353,7 @@ function resetDraft(state: WorkboardUiState) {
352353
state.draftAgentId = "";
353354
state.draftSessionKey = "";
354355
state.draftTemplateId = "";
356+
state.draftCommentBody = "";
355357
}
356358

357359
function openCreateModal(state: WorkboardUiState) {
@@ -405,6 +407,7 @@ function openEditModal(state: WorkboardUiState, card: WorkboardCard) {
405407
state.draftAgentId = card.agentId ?? "";
406408
state.draftSessionKey = card.sessionKey ?? "";
407409
state.draftTemplateId = card.metadata?.templateId ?? "";
410+
state.draftCommentBody = "";
408411
}
409412

410413
function applyTemplate(state: WorkboardUiState, templateId: WorkboardTemplateId) {
@@ -569,6 +572,10 @@ function renderCardModal(props: WorkboardProps) {
569572
return nothing;
570573
}
571574
const editing = Boolean(state.editingCardId);
575+
const editingCard = state.editingCardId
576+
? (state.cards.find((card) => card.id === state.editingCardId) ?? null)
577+
: null;
578+
const comments = editingCard?.metadata?.comments ?? [];
572579
return html`
573580
<div
574581
class="workboard-modal"
@@ -745,6 +752,51 @@ function renderCardModal(props: WorkboardProps) {
745752
/>
746753
</label>
747754
</div>
755+
${editing
756+
? html`
757+
<section
758+
class="workboard-field workboard-field--wide"
759+
aria-labelledby="workboard-card-comments-title"
760+
>
761+
<span id="workboard-card-comments-title">
762+
${t("workboard.badgeComments", { count: String(comments.length) })}
763+
</span>
764+
${comments.length
765+
? html`
766+
<ol>
767+
${comments.map((comment) => html`<li>${comment.body}</li>`)}
768+
</ol>
769+
`
770+
: nothing}
771+
<textarea
772+
class="input workboard-comments__input"
773+
aria-labelledby="workboard-card-comments-title"
774+
maxlength="2000"
775+
.value=${state.draftCommentBody}
776+
@input=${(event: InputEvent) => {
777+
state.draftCommentBody = (event.currentTarget as HTMLTextAreaElement).value;
778+
props.onRequestUpdate?.();
779+
}}
780+
></textarea>
781+
<div class="workboard-modal__actions">
782+
<button
783+
class="btn"
784+
type="button"
785+
?disabled=${state.loading || !state.draftCommentBody.trim()}
786+
@click=${() => {
787+
void addWorkboardCardComment({
788+
host: props.host,
789+
client: props.client,
790+
requestUpdate: props.onRequestUpdate,
791+
});
792+
}}
793+
>
794+
${icons.plus} ${t("common.create")}
795+
</button>
796+
</div>
797+
</section>
798+
`
799+
: nothing}
748800
<div class="workboard-modal__actions">
749801
<button class="btn primary" ?disabled=${state.loading || !state.draftTitle.trim()}>
750802
${editing ? t("common.save") : t("common.create")}

0 commit comments

Comments
 (0)