fix: separate console pending groups and align row typography

This commit is contained in:
2026-10-02 01:55:54 +09:00
parent 17b7bdc769
commit 4ac448d66c
7 changed files with 403 additions and 28 deletions
@@ -1892,10 +1892,10 @@
/>
{#if pendingSubmissionItems.length > 0 || pendingSubmissions.notification_count > 0}
<section class="pending-submissions" aria-label="Pending activations">
<div class="pending-column pending-queue" role="group" aria-label="Queued inputs">
<h2 class="pending-submissions-label">{pendingSubmissionItems.length} Queued</h2>
{#if pendingSubmissionItems.length > 0}
<section class="pending-submissions" class:has-both={pendingSubmissionItems.length > 0 && pendingSubmissions.notification_count > 0} aria-label="Pending activations">
{#if pendingSubmissionItems.length > 0}
<div class="pending-column pending-queue" role="group" aria-label="Queued inputs">
<h2 class="pending-submissions-label">{pendingSubmissionItems.length} Queued</h2>
<ol aria-label="Queued inputs">
{#each pendingSubmissionItems as submission, index (submission.submission_id)}
<li>
@@ -1923,11 +1923,11 @@
</li>
{/each}
</ol>
{/if}
</div>
<div class="pending-column pending-notifications" role="group" aria-label="Notifications">
<h2 class="pending-submissions-label">Notifications</h2>
{#if pendingSubmissions.notification_count > 0}
</div>
{/if}
{#if pendingSubmissions.notification_count > 0}
<div class="pending-column pending-notifications" role="group" aria-label="Notifications">
<h2 class="pending-submissions-label">Notifications</h2>
<ol aria-label="Pending notifications">
{#each pendingSubmissions.notification_previews ?? [] as preview}
<li>
@@ -1941,8 +1941,8 @@
</li>
{/each}
</ol>
{/if}
</div>
</div>
{/if}
</section>
{/if}
@@ -2309,24 +2309,34 @@
.pending-submissions {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
grid-template-columns: minmax(0, 1fr);
gap: var(--space-3);
flex: 0 0 auto;
min-width: 0;
margin: 0 var(--space-3);
color: var(--text-muted);
font-family: var(--font-mono);
font-size: var(--font-size-compact);
line-height: var(--line-height-compact);
}
.pending-submissions.has-both {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.pending-column {
display: grid;
align-content: start;
gap: var(--space-1);
min-width: 0;
}
.pending-submissions li {
display: grid;
grid-template-columns: minmax(0, 1fr);
min-height: var(--space-6);
height: var(--line-height-compact);
margin: 0;
padding: 0;
align-items: center;
}
@@ -2340,16 +2350,16 @@
}
.pending-submissions-label {
margin: 0 0 var(--space-1);
font-size: var(--font-size-compact);
line-height: var(--line-height-compact);
font-weight: 500;
margin: 0;
padding: 0;
font: inherit;
color: inherit;
}
.pending-submissions ol {
display: grid;
gap: var(--space-1);
max-height: calc(var(--space-6) * 4);
max-height: calc(var(--line-height-compact) * 4 + var(--space-1) * 3);
overflow-y: auto;
margin: 0;
padding: 0;
@@ -2371,8 +2381,8 @@
align-items: center;
justify-content: center;
width: var(--space-6);
height: var(--space-6);
padding: var(--space-2);
height: var(--line-height-compact);
padding: 0;
border: 0;
border-radius: var(--radius-soft);
background: transparent;
@@ -369,7 +369,8 @@ test("notification-only state displays previews without actions and supports leg
session.pending_submissions.head_id = "notification-head";
latestListener().onFrame(subscribedFrame(session));
const region = await screen.findByRole("region", { name: "Pending activations" });
expect(region.textContent).toContain("0 Queued");
expect(screen.queryByRole("group", { name: "Queued inputs" })).toBeNull();
expect(screen.queryByRole("heading", { name: "0 Queued" })).toBeNull();
expect(region.textContent).toContain("一件目の通知");
expect(region.textContent).toContain("二件目の通知");
expect(region.querySelectorAll("li")).toHaveLength(2);
@@ -382,7 +383,7 @@ test("notification-only state displays previews without actions and supports leg
await waitFor(() => expect(screen.queryByRole("region", { name: "Pending activations" })).toBeNull());
});
test("queue-only state keeps the notification column empty and disables cancellation when disconnected", async () => {
test("queue-only state hides notifications and disables cancellation when disconnected", async () => {
vi.stubGlobal("fetch", vi.fn(async () => Response.json({ availability: "live_protocol" })));
render(ConsolePage, { data: pageData() });
await waitFor(() => expect(multiplexer.subscribe).toHaveBeenCalledOnce());
@@ -390,7 +391,8 @@ test("queue-only state keeps the notification column empty and disables cancella
session.pending_submissions.submissions = [{ submission_id: "queue-1", preview: "送信内容", accepted_at_ms: 1, segment_count: 1, byte_len: 12 }];
latestListener().onFrame(subscribedFrame(session));
await screen.findByText("送信内容");
expect(screen.getByRole("group", { name: "Notifications" }).querySelector("ol")).toBeNull();
expect(screen.queryByRole("group", { name: "Notifications" })).toBeNull();
expect(screen.queryByRole("heading", { name: "Notifications" })).toBeNull();
latestListener().onStatus?.("closed", "connection lost");
await waitFor(() => expect((screen.getByRole("button", { name: "Cancel queued input 1" }) as HTMLButtonElement).disabled).toBe(true));
});
@@ -1137,3 +1139,28 @@ function expectHistoryTurn(text: string) {
expect(screen.getAllByText(text)).toHaveLength(1);
expect(screen.getByRole("button", { name: new RegExp("^Turn \\d+: " + text + "$") })).not.toBeNull();
}
test("pending groups independently follow authoritative changes, including zero count with stale previews", async () => {
vi.stubGlobal("fetch", vi.fn(async () => Response.json({ availability: "live_protocol" })));
render(ConsolePage, { data: pageData() });
await waitFor(() => expect(multiplexer.subscribe).toHaveBeenCalledOnce());
const session = emptySession();
session.pending_submissions.submissions = [{ submission_id: "queue-1", preview: "Queued", accepted_at_ms: 1, segment_count: 1, byte_len: 6 }];
session.pending_submissions.notification_count = 1;
session.pending_submissions.notification_previews = ["Notice"];
latestListener().onFrame(subscribedFrame(session));
await screen.findByRole("group", { name: "Queued inputs" });
expect(screen.getByRole("group", { name: "Notifications" })).toBeTruthy();
session.pending_submissions.notification_count = 0;
latestListener().onFrame(subscribedFrame(session));
await waitFor(() => expect(screen.queryByRole("group", { name: "Notifications" })).toBeNull());
expect(screen.getByRole("group", { name: "Queued inputs" })).toBeTruthy();
session.pending_submissions.submissions = [];
session.pending_submissions.notification_count = 1;
latestListener().onFrame(subscribedFrame(session));
await waitFor(() => expect(screen.queryByRole("group", { name: "Queued inputs" })).toBeNull());
expect(screen.getByRole("group", { name: "Notifications" })).toBeTruthy();
session.pending_submissions.notification_count = 0;
latestListener().onFrame(subscribedFrame(session));
await waitFor(() => expect(screen.queryByRole("region", { name: "Pending activations" })).toBeNull());
});