mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-10 00:11:19 +00:00
fix(ui): keep Workboard detail drawer actions in parity (#101658)
* Fix Workboard detail action parity * test(ui): cover Workboard detail drawer actions * fix(ui): satisfy Workboard action lint
This commit is contained in:
parent
d5e4ed93fa
commit
fdc98242a1
4 changed files with 303 additions and 127 deletions
|
|
@ -149,6 +149,7 @@ describe("renderWorkboard", () => {
|
|||
const request = vi.fn(async () => ({ card: state.cards[0] }));
|
||||
state.loaded = true;
|
||||
state.dispatching = true;
|
||||
state.detailCardId = "card-1";
|
||||
state.cards = [
|
||||
{
|
||||
id: "card-1",
|
||||
|
|
@ -184,6 +185,15 @@ describe("renderWorkboard", () => {
|
|||
expect(
|
||||
container.querySelector<HTMLSelectElement>(".workboard-card__move-select")?.disabled,
|
||||
).toBe(true);
|
||||
const detailActions = container.querySelector<HTMLElement>(".workboard-detail__actions");
|
||||
expect(detailActions).not.toBeNull();
|
||||
expect(buttonByLabel(detailActions!, "Edit card")?.disabled).toBe(true);
|
||||
expect(buttonByLabel(detailActions!, "Archive card")?.disabled).toBe(true);
|
||||
expect(buttonByLabel(detailActions!, "Stop session")?.disabled).toBe(true);
|
||||
expect(buttonByLabel(detailActions!, "Delete card")?.disabled).toBe(true);
|
||||
expect(
|
||||
detailActions!.querySelector<HTMLSelectElement>(".workboard-card__move-select")?.disabled,
|
||||
).toBe(true);
|
||||
expect(container.querySelector<HTMLElement>(".workboard-card")?.getAttribute("draggable")).toBe(
|
||||
"false",
|
||||
);
|
||||
|
|
@ -1243,6 +1253,69 @@ describe("renderWorkboard", () => {
|
|||
expect(onOpenSession).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("mirrors compact card actions in the detail drawer", () => {
|
||||
const host = {};
|
||||
const state = getWorkboardState(host);
|
||||
const sessionKey = "agent:main:detail-parity";
|
||||
state.loaded = true;
|
||||
state.detailCardId = "card-1";
|
||||
state.cards = [
|
||||
{
|
||||
id: "card-1",
|
||||
title: "Detail parity",
|
||||
status: "running",
|
||||
priority: "normal",
|
||||
labels: [],
|
||||
position: 1000,
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
sessionKey,
|
||||
},
|
||||
];
|
||||
const container = document.createElement("div");
|
||||
const onOpenSession = vi.fn();
|
||||
|
||||
render(
|
||||
renderWorkboard({
|
||||
host,
|
||||
client: { request: vi.fn() } as unknown as GatewayBrowserClient,
|
||||
connected: true,
|
||||
pluginEnabled: true,
|
||||
agentsList: null,
|
||||
sessions: [
|
||||
{
|
||||
key: sessionKey,
|
||||
kind: "direct",
|
||||
displayName: "Detail parity session",
|
||||
updatedAt: 2,
|
||||
hasActiveRun: true,
|
||||
status: "running",
|
||||
},
|
||||
],
|
||||
onOpenSession,
|
||||
}),
|
||||
container,
|
||||
);
|
||||
|
||||
const actions = container.querySelector<HTMLElement>(".workboard-detail__actions");
|
||||
expect(actions).not.toBeNull();
|
||||
expect(buttonByLabel(actions!, "Edit card")).not.toBeNull();
|
||||
expect(buttonByLabel(actions!, "Archive card")).not.toBeNull();
|
||||
expect(buttonByLabel(actions!, "Stop session")).not.toBeNull();
|
||||
expect(buttonByLabel(actions!, "Open session")).not.toBeNull();
|
||||
expect(buttonByLabel(actions!, "Delete card")).not.toBeNull();
|
||||
|
||||
const moveSelect = actions!.querySelector<HTMLSelectElement>(".workboard-card__move-select");
|
||||
expect(moveSelect?.getAttribute("aria-label")).toBe("Status: Detail parity");
|
||||
expect([...moveSelect!.options].map((option) => option.textContent?.trim())).toContain(
|
||||
"Review",
|
||||
);
|
||||
|
||||
buttonByLabel(actions!, "Edit card")?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
expect(state.draftOpen).toBe(true);
|
||||
expect(state.editingCardId).toBe("card-1");
|
||||
});
|
||||
|
||||
it("keeps focus inside the card modal and restores focus on Escape", async () => {
|
||||
const host = {};
|
||||
const state = getWorkboardState(host);
|
||||
|
|
|
|||
|
|
@ -1135,7 +1135,12 @@ function moveCardToStatus(
|
|||
});
|
||||
}
|
||||
|
||||
function renderCardMoveControl(props: WorkboardProps, card: WorkboardCard, busy: boolean) {
|
||||
function renderCardMoveControl(
|
||||
props: WorkboardProps,
|
||||
card: WorkboardCard,
|
||||
busy: boolean,
|
||||
options: { wide?: boolean } = {},
|
||||
) {
|
||||
const state = getWorkboardState(props.host);
|
||||
const statuses = state.statuses.includes(card.status)
|
||||
? state.statuses
|
||||
|
|
@ -1144,7 +1149,10 @@ function renderCardMoveControl(props: WorkboardProps, card: WorkboardCard, busy:
|
|||
return nothing;
|
||||
}
|
||||
return html`
|
||||
<label class="workboard-card__move" title=${t("workboard.fieldStatus")}>
|
||||
<label
|
||||
class="workboard-card__move ${options.wide ? "workboard-card__move--wide" : ""}"
|
||||
title=${t("workboard.fieldStatus")}
|
||||
>
|
||||
<span class="workboard-card__move-icon" aria-hidden="true">${icons.cornerDownRight}</span>
|
||||
<select
|
||||
class="workboard-card__move-select"
|
||||
|
|
@ -1200,6 +1208,166 @@ function renderCardActionSlot(content: TemplateResult | typeof nothing) {
|
|||
`;
|
||||
}
|
||||
|
||||
function getCardActionState(props: WorkboardProps, card: WorkboardCard) {
|
||||
const state = getWorkboardState(props.host);
|
||||
const task = state.tasksByCardId.get(card.id);
|
||||
const session = findWorkboardSession(card, props.sessions);
|
||||
const busy = state.busyCardIds.has(card.id) || state.dispatching;
|
||||
const activeTask = cardHasActiveOrRunningUnresolvedTask(card, task, state.missingTaskIds);
|
||||
const writable = canMutate(props);
|
||||
const live =
|
||||
activeTask ||
|
||||
cardHasUnresolvedStartedRun(card) ||
|
||||
session?.hasActiveRun === true ||
|
||||
(session?.hasActiveRun !== false && session?.status === "running");
|
||||
return {
|
||||
state,
|
||||
task,
|
||||
session,
|
||||
busy,
|
||||
activeTask,
|
||||
live,
|
||||
linkedSessionKey: card.sessionKey ?? card.execution?.sessionKey,
|
||||
writable,
|
||||
showStartControls: writable && cardCanStart(state, props.sessions, card),
|
||||
archived: Boolean(card.metadata?.archivedAt),
|
||||
};
|
||||
}
|
||||
|
||||
function renderCardActionButton(params: {
|
||||
label: string;
|
||||
icon: TemplateResult;
|
||||
iconOnly?: boolean;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
ariaHaspopup?: "dialog";
|
||||
onClick: (event: MouseEvent) => void;
|
||||
}) {
|
||||
const button = html`
|
||||
<button
|
||||
class=${params.iconOnly
|
||||
? `btn btn--icon workboard-card__icon ${params.className ?? ""}`
|
||||
: `btn ${params.className ?? ""}`}
|
||||
type="button"
|
||||
aria-label=${params.label}
|
||||
aria-haspopup=${params.ariaHaspopup ?? nothing}
|
||||
?disabled=${params.disabled}
|
||||
@click=${params.onClick}
|
||||
>
|
||||
${params.icon}${params.iconOnly ? nothing : html`<span>${params.label}</span>`}
|
||||
</button>
|
||||
`;
|
||||
return params.iconOnly
|
||||
? html`<openclaw-tooltip .content=${params.label}>${button}</openclaw-tooltip>`
|
||||
: button;
|
||||
}
|
||||
|
||||
function renderEditCardAction(
|
||||
props: WorkboardProps,
|
||||
card: WorkboardCard,
|
||||
options: { iconOnly?: boolean } = {},
|
||||
) {
|
||||
const state = getWorkboardState(props.host);
|
||||
return renderCardActionButton({
|
||||
label: t("workboard.editCard"),
|
||||
icon: icons.edit,
|
||||
iconOnly: options.iconOnly,
|
||||
ariaHaspopup: "dialog",
|
||||
disabled: state.dispatching,
|
||||
onClick: (event) => {
|
||||
rememberWorkboardReturnFocus(event.currentTarget);
|
||||
openEditModal(state, card);
|
||||
props.onRequestUpdate?.();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function renderArchiveCardAction(
|
||||
props: WorkboardProps,
|
||||
card: WorkboardCard,
|
||||
busy: boolean,
|
||||
archived: boolean,
|
||||
options: { iconOnly?: boolean } = {},
|
||||
) {
|
||||
const label = archived ? t("workboard.unarchiveCard") : t("workboard.archiveCard");
|
||||
return renderCardActionButton({
|
||||
label,
|
||||
icon: archived ? icons.archiveRestore : icons.archive,
|
||||
iconOnly: options.iconOnly,
|
||||
disabled: busy,
|
||||
onClick: () => {
|
||||
void archiveWorkboardCard({
|
||||
host: props.host,
|
||||
client: props.client,
|
||||
cardId: card.id,
|
||||
archived: !archived,
|
||||
requestUpdate: props.onRequestUpdate,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function renderOpenSessionCardAction(
|
||||
props: WorkboardProps,
|
||||
linkedSessionKey: string | undefined,
|
||||
options: { iconOnly?: boolean } = {},
|
||||
) {
|
||||
if (!linkedSessionKey) {
|
||||
return nothing;
|
||||
}
|
||||
return renderCardActionButton({
|
||||
label: t("workboard.openSession"),
|
||||
icon: icons.messageSquare,
|
||||
iconOnly: options.iconOnly,
|
||||
onClick: () => props.onOpenSession(linkedSessionKey),
|
||||
});
|
||||
}
|
||||
|
||||
function renderStopCardAction(
|
||||
props: WorkboardProps,
|
||||
card: WorkboardCard,
|
||||
busy: boolean,
|
||||
options: { iconOnly?: boolean } = {},
|
||||
) {
|
||||
return renderCardActionButton({
|
||||
label: t("workboard.stopSession"),
|
||||
icon: icons.stop,
|
||||
iconOnly: options.iconOnly,
|
||||
disabled: busy || !props.connected,
|
||||
onClick: () => {
|
||||
void stopWorkboardCard({
|
||||
host: props.host,
|
||||
client: props.client,
|
||||
card,
|
||||
requestUpdate: props.onRequestUpdate,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function renderDeleteCardAction(
|
||||
props: WorkboardProps,
|
||||
card: WorkboardCard,
|
||||
busy: boolean,
|
||||
options: { iconOnly?: boolean } = {},
|
||||
) {
|
||||
return renderCardActionButton({
|
||||
label: t("workboard.deleteCard"),
|
||||
icon: icons.trash,
|
||||
iconOnly: options.iconOnly,
|
||||
className: "workboard-card__delete",
|
||||
disabled: busy,
|
||||
onClick: () => {
|
||||
void deleteWorkboardCard({
|
||||
host: props.host,
|
||||
client: props.client,
|
||||
cardId: card.id,
|
||||
requestUpdate: props.onRequestUpdate,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function openCardDetails(state: WorkboardUiState, card: WorkboardCard) {
|
||||
state.detailCardId = card.id;
|
||||
state.detailCommentBody = "";
|
||||
|
|
@ -1863,12 +2031,12 @@ function renderCardDetailsPanel(props: WorkboardProps) {
|
|||
if (!card) {
|
||||
return nothing;
|
||||
}
|
||||
const task = state.tasksByCardId.get(card.id);
|
||||
const cardActions = getCardActionState(props, card);
|
||||
const { task, busy, activeTask, live, linkedSessionKey, writable, showStartControls, archived } =
|
||||
cardActions;
|
||||
const lifecycle = getWorkboardLifecycle(card, props.sessions, task);
|
||||
const formatted = formatLifecycle(lifecycle);
|
||||
const taskIsAuthoritative = task ? taskMatchesLifecycle(task, lifecycle) : false;
|
||||
const linkedSessionKey = card.sessionKey ?? card.execution?.sessionKey;
|
||||
const writable = canMutate(props);
|
||||
const comments = card.metadata?.comments ?? [];
|
||||
const attempts = card.metadata?.attempts ?? [];
|
||||
const links = card.metadata?.links ?? [];
|
||||
|
|
@ -1880,8 +2048,6 @@ function renderCardDetailsPanel(props: WorkboardProps) {
|
|||
const workerProtocol = card.metadata?.workerProtocol;
|
||||
const automation = card.metadata?.automation;
|
||||
const events = (card.events ?? []).slice(-6).toReversed();
|
||||
const busy = state.busyCardIds.has(card.id) || state.dispatching;
|
||||
const showStartControls = writable && cardCanStart(state, props.sessions, card);
|
||||
const dependencies = getWorkboardDependencyState(card, state.cards);
|
||||
return html`
|
||||
<aside
|
||||
|
|
@ -2085,17 +2251,14 @@ function renderCardDetailsPanel(props: WorkboardProps) {
|
|||
</section>
|
||||
|
||||
<div class="workboard-detail__actions">
|
||||
${linkedSessionKey
|
||||
? html`
|
||||
<button
|
||||
class="btn"
|
||||
type="button"
|
||||
@click=${() => props.onOpenSession(linkedSessionKey)}
|
||||
>
|
||||
${icons.messageSquare} ${t("workboard.openSession")}
|
||||
</button>
|
||||
`
|
||||
${writable && !archived ? renderEditCardAction(props, card) : nothing}
|
||||
${writable ? renderArchiveCardAction(props, card, busy, archived) : nothing}
|
||||
${writable ? renderCardMoveControl(props, card, busy, { wide: true }) : nothing}
|
||||
${writable && (linkedSessionKey ? live : activeTask)
|
||||
? renderStopCardAction(props, card, busy)
|
||||
: nothing}
|
||||
${renderOpenSessionCardAction(props, linkedSessionKey)}
|
||||
${writable ? renderDeleteCardAction(props, card, busy) : nothing}
|
||||
${showStartControls ? renderStartExecutionControls(props, card) : nothing}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -2217,21 +2380,19 @@ const viewPresetOptions: Array<{ value: WorkboardUiState["viewPreset"]; labelKey
|
|||
];
|
||||
|
||||
function renderCard(props: WorkboardProps, card: WorkboardCard) {
|
||||
const state = getWorkboardState(props.host);
|
||||
const task = state.tasksByCardId.get(card.id);
|
||||
const session = findWorkboardSession(card, props.sessions);
|
||||
const busy = state.busyCardIds.has(card.id) || state.dispatching;
|
||||
const cardActions = getCardActionState(props, card);
|
||||
const {
|
||||
state,
|
||||
task,
|
||||
busy,
|
||||
activeTask,
|
||||
live,
|
||||
linkedSessionKey,
|
||||
writable,
|
||||
showStartControls,
|
||||
archived,
|
||||
} = cardActions;
|
||||
const syncing = state.syncingCardIds.has(card.id);
|
||||
const activeTask = cardHasActiveOrRunningUnresolvedTask(card, task, state.missingTaskIds);
|
||||
const live =
|
||||
activeTask ||
|
||||
cardHasUnresolvedStartedRun(card) ||
|
||||
session?.hasActiveRun === true ||
|
||||
(session?.hasActiveRun !== false && session?.status === "running");
|
||||
const linkedSessionKey = card.sessionKey ?? card.execution?.sessionKey;
|
||||
const writable = canMutate(props);
|
||||
const showStartControls = writable && cardCanStart(state, props.sessions, card);
|
||||
const archived = Boolean(card.metadata?.archivedAt);
|
||||
const healthHighlighted = state.activeHealthHighlight
|
||||
? workboardCardMatchesHealthKey(card, state.activeHealthHighlight, props.sessions, task)
|
||||
: false;
|
||||
|
|
@ -2240,49 +2401,9 @@ function renderCard(props: WorkboardProps, card: WorkboardCard) {
|
|||
? renderStartExecutionButton(props, card, null, "autonomous", { iconOnly: true })
|
||||
: nothing;
|
||||
const topEditAction =
|
||||
writable && !archived
|
||||
? html`
|
||||
<openclaw-tooltip .content=${t("workboard.editCard")}>
|
||||
<button
|
||||
class="btn btn--icon workboard-card__icon"
|
||||
type="button"
|
||||
aria-label=${t("workboard.editCard")}
|
||||
aria-haspopup="dialog"
|
||||
?disabled=${state.dispatching}
|
||||
@click=${(event: MouseEvent) => {
|
||||
rememberWorkboardReturnFocus(event.currentTarget);
|
||||
openEditModal(state, card);
|
||||
props.onRequestUpdate?.();
|
||||
}}
|
||||
>
|
||||
${icons.edit}
|
||||
</button>
|
||||
</openclaw-tooltip>
|
||||
`
|
||||
: nothing;
|
||||
writable && !archived ? renderEditCardAction(props, card, { iconOnly: true }) : nothing;
|
||||
const topArchiveAction = writable
|
||||
? html`
|
||||
<openclaw-tooltip
|
||||
.content=${archived ? t("workboard.unarchiveCard") : t("workboard.archiveCard")}
|
||||
>
|
||||
<button
|
||||
class="btn btn--icon workboard-card__icon"
|
||||
type="button"
|
||||
aria-label=${archived ? t("workboard.unarchiveCard") : t("workboard.archiveCard")}
|
||||
?disabled=${busy}
|
||||
@click=${() =>
|
||||
archiveWorkboardCard({
|
||||
host: props.host,
|
||||
client: props.client,
|
||||
cardId: card.id,
|
||||
archived: !archived,
|
||||
requestUpdate: props.onRequestUpdate,
|
||||
})}
|
||||
>
|
||||
${archived ? icons.archiveRestore : icons.archive}
|
||||
</button>
|
||||
</openclaw-tooltip>
|
||||
`
|
||||
? renderArchiveCardAction(props, card, busy, archived, { iconOnly: true })
|
||||
: nothing;
|
||||
const detailAction = html`
|
||||
<openclaw-tooltip .content=${t("workboard.viewDetails")}>
|
||||
|
|
@ -2302,61 +2423,14 @@ function renderCard(props: WorkboardProps, card: WorkboardCard) {
|
|||
</button>
|
||||
</openclaw-tooltip>
|
||||
`;
|
||||
const sessionAction = linkedSessionKey
|
||||
? html`
|
||||
<openclaw-tooltip .content=${t("workboard.openSession")}>
|
||||
<button
|
||||
class="btn btn--icon workboard-card__icon"
|
||||
aria-label=${t("workboard.openSession")}
|
||||
@click=${() => props.onOpenSession(linkedSessionKey)}
|
||||
>
|
||||
${icons.messageSquare}
|
||||
</button>
|
||||
</openclaw-tooltip>
|
||||
`
|
||||
: nothing;
|
||||
const sessionAction = renderOpenSessionCardAction(props, linkedSessionKey, { iconOnly: true });
|
||||
const stopAction =
|
||||
writable && (linkedSessionKey ? live : activeTask)
|
||||
? html`
|
||||
<openclaw-tooltip .content=${t("workboard.stopSession")}>
|
||||
<button
|
||||
class="btn btn--icon workboard-card__icon"
|
||||
aria-label=${t("workboard.stopSession")}
|
||||
?disabled=${busy || !props.connected}
|
||||
@click=${() =>
|
||||
stopWorkboardCard({
|
||||
host: props.host,
|
||||
client: props.client,
|
||||
card,
|
||||
requestUpdate: props.onRequestUpdate,
|
||||
})}
|
||||
>
|
||||
${icons.stop}
|
||||
</button>
|
||||
</openclaw-tooltip>
|
||||
`
|
||||
? renderStopCardAction(props, card, busy, { iconOnly: true })
|
||||
: nothing;
|
||||
const moveAction = writable ? renderCardMoveControl(props, card, busy) : nothing;
|
||||
const deleteAction = writable
|
||||
? html`
|
||||
<openclaw-tooltip .content=${t("workboard.deleteCard")}>
|
||||
<button
|
||||
class="btn btn--icon workboard-card__icon workboard-card__delete"
|
||||
type="button"
|
||||
aria-label=${t("workboard.deleteCard")}
|
||||
?disabled=${busy}
|
||||
@click=${() =>
|
||||
deleteWorkboardCard({
|
||||
host: props.host,
|
||||
client: props.client,
|
||||
cardId: card.id,
|
||||
requestUpdate: props.onRequestUpdate,
|
||||
})}
|
||||
>
|
||||
${icons.trash}
|
||||
</button>
|
||||
</openclaw-tooltip>
|
||||
`
|
||||
? renderDeleteCardAction(props, card, busy, { iconOnly: true })
|
||||
: nothing;
|
||||
return html`
|
||||
<article
|
||||
|
|
|
|||
|
|
@ -423,6 +423,13 @@ describeControlUiE2e("Control UI Workboard mocked Gateway E2E", () => {
|
|||
await details.getByText("Acceptance: mocked Gateway browser proof").waitFor({
|
||||
state: "visible",
|
||||
});
|
||||
await details.locator(".workboard-card__move-select").waitFor({ state: "visible" });
|
||||
expect(await details.getByRole("button", { name: "Open session" }).count()).toBe(1);
|
||||
expect(await details.getByRole("button", { name: "Edit card" }).count()).toBe(1);
|
||||
expect(await details.getByRole("button", { name: "Archive card" }).count()).toBe(1);
|
||||
expect(await details.getByRole("button", { name: "Delete card" }).count()).toBe(1);
|
||||
expect(await details.getByRole("button", { name: "Stop session" }).count()).toBe(0);
|
||||
await captureScreenshot(writable.page, artifacts, "05-detail-actions");
|
||||
await details.locator('button[aria-label="Cancel"]').click();
|
||||
|
||||
await writableGateway.deferNext("workboard.cards.move");
|
||||
|
|
@ -443,7 +450,7 @@ describeControlUiE2e("Control UI Workboard mocked Gateway E2E", () => {
|
|||
await cardInColumn(writable.page, "Running", editedCard.title).waitFor({
|
||||
state: "visible",
|
||||
});
|
||||
await captureScreenshot(writable.page, artifacts, "05-moved-running");
|
||||
await captureScreenshot(writable.page, artifacts, "06-moved-running");
|
||||
|
||||
await writableGateway.deferNext("workboard.cards.update");
|
||||
const syncBefore = (await writableGateway.getRequests("workboard.cards.update")).length;
|
||||
|
|
@ -473,7 +480,7 @@ describeControlUiE2e("Control UI Workboard mocked Gateway E2E", () => {
|
|||
await writable.page.locator(".workboard-detail").getByText("Moved to Review").waitFor({
|
||||
state: "visible",
|
||||
});
|
||||
await captureScreenshot(writable.page, artifacts, "06-lifecycle-review");
|
||||
await captureScreenshot(writable.page, artifacts, "07-lifecycle-review");
|
||||
await details.locator('button[aria-label="Cancel"]').click();
|
||||
await details.waitFor({ state: "hidden" });
|
||||
|
||||
|
|
@ -492,7 +499,7 @@ describeControlUiE2e("Control UI Workboard mocked Gateway E2E", () => {
|
|||
await writable.page.getByText("Acceptance: mocked Gateway browser proof").waitFor({
|
||||
state: "visible",
|
||||
});
|
||||
await captureScreenshot(writable.page, artifacts, "07-reloaded-review");
|
||||
await captureScreenshot(writable.page, artifacts, "08-reloaded-review");
|
||||
} finally {
|
||||
await closeRecordedPage(writable, artifacts, "workboard-writable");
|
||||
}
|
||||
|
|
@ -515,7 +522,7 @@ describeControlUiE2e("Control UI Workboard mocked Gateway E2E", () => {
|
|||
await cardInColumn(readOnly.page, "Running", editedCard.title).waitFor({
|
||||
state: "visible",
|
||||
});
|
||||
await captureScreenshot(readOnly.page, artifacts, "08-read-only-board");
|
||||
await captureScreenshot(readOnly.page, artifacts, "09-read-only-board");
|
||||
expect(await readOnly.page.getByRole("button", { name: /New card/u }).count()).toBe(0);
|
||||
expect(await readOnly.page.locator('button[aria-label="Edit card"]').count()).toBe(0);
|
||||
expect(await readOnly.page.locator('button[aria-label="Delete card"]').count()).toBe(0);
|
||||
|
|
@ -528,6 +535,11 @@ describeControlUiE2e("Control UI Workboard mocked Gateway E2E", () => {
|
|||
await readOnly.page.locator(".workboard-detail").getByText(editedCard.title).waitFor({
|
||||
state: "visible",
|
||||
});
|
||||
const readOnlyDetail = readOnly.page.locator(".workboard-detail");
|
||||
expect(await readOnlyDetail.locator(".workboard-card__move-select").count()).toBe(0);
|
||||
expect(await readOnlyDetail.getByRole("button", { name: "Edit card" }).count()).toBe(0);
|
||||
expect(await readOnlyDetail.getByRole("button", { name: "Archive card" }).count()).toBe(0);
|
||||
expect(await readOnlyDetail.getByRole("button", { name: "Delete card" }).count()).toBe(0);
|
||||
expect(await readOnly.page.locator(".workboard-detail__note").count()).toBe(0);
|
||||
expect(await readOnly.page.getByRole("button", { name: /Add note/u }).count()).toBe(0);
|
||||
expect(await readOnlyGateway.getRequests("workboard.cards.update")).toHaveLength(0);
|
||||
|
|
|
|||
|
|
@ -1014,6 +1014,10 @@
|
|||
color: var(--muted);
|
||||
}
|
||||
|
||||
.workboard-card__move--wide {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.workboard-card__move-icon {
|
||||
position: absolute;
|
||||
left: 7px;
|
||||
|
|
@ -1075,6 +1079,11 @@
|
|||
opacity: 0.58;
|
||||
}
|
||||
|
||||
.workboard-card__move--wide .workboard-card__move-select {
|
||||
max-width: none;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.workboard-card__quick-actions .workboard-card__icon,
|
||||
.workboard-card__quick-actions .workboard-card__start--icon {
|
||||
width: 26px;
|
||||
|
|
@ -1493,6 +1502,7 @@
|
|||
|
||||
.workboard-detail__actions {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
margin-top: auto;
|
||||
padding-top: 12px;
|
||||
|
|
@ -1501,6 +1511,13 @@
|
|||
|
||||
.workboard-detail__actions > .btn {
|
||||
justify-content: center;
|
||||
min-width: 0;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.workboard-detail__actions > .workboard-card__move,
|
||||
.workboard-detail__actions > .workboard-card__execution-controls {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
@media (max-width: 860px) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue