setOpen(false)}
diff --git a/src/components/ui/date-picker.tsx b/src/components/ui/date-picker.tsx
index 21a529d7..e43f2add 100644
--- a/src/components/ui/date-picker.tsx
+++ b/src/components/ui/date-picker.tsx
@@ -177,6 +177,8 @@ export function DatePicker({
onClose={() => setOpen(false)}
role="dialog"
ariaLabel="Date picker"
+ align="right"
+ minSpaceBelow={280}
className="w-64 rounded-lg border border-border bg-card p-3 shadow-xl"
>
diff --git a/tests/e2e/issue-flow.spec.ts b/tests/e2e/issue-flow.spec.ts
index e0a90b01..29004d6c 100644
--- a/tests/e2e/issue-flow.spec.ts
+++ b/tests/e2e/issue-flow.spec.ts
@@ -1,4 +1,4 @@
-import { test, expect } from "@playwright/test";
+import { test, expect, type Page } from "@playwright/test";
/**
* Golden-path E2E: create an issue via the quick-create dialog, verify it
@@ -6,8 +6,20 @@ import { test, expect } from "@playwright/test";
* storageState minted in global-setup against the seeded `forge` workspace.
*/
+function escapeRegExp(value: string) {
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
+}
+
+async function selectComboboxOption(page: Page, name: string, option: string) {
+ const combo = page.getByRole("combobox", { name });
+ await expect(combo).toBeVisible();
+ await combo.click();
+ await page.getByRole("option", { name: option, exact: true }).click();
+ await expect(combo).toContainText(option);
+}
+
test.describe("Issue flow", () => {
- test("create and transition an issue", async ({ page }) => {
+ test("create, pin/unpin from navbar, and transition an issue", async ({ page }) => {
await page.goto("/w/forge/inbox");
// QuickCreate opens on the ⇧C global hotkey (src/components/quick-create.tsx).
@@ -28,8 +40,20 @@ test.describe("Issue flow", () => {
await expect(page).toHaveURL(/\/w\/forge\/issues\//, { timeout: 20_000 });
await expect(page.getByText(title).first()).toBeVisible();
+ // The issue-detail pin control targets the navbar pin bucket. Once pinned,
+ // the navbar chip exposes its own direct unpin button so users do not have
+ // to hunt through other issue controls to remove the pin.
+ await page.getByRole("button", { name: "Pin to navbar (p)" }).click();
+ await expect(page.getByRole("button", { name: "Unpin from navbar (p)" })).toBeVisible();
+ const navbarUnpin = page.getByRole("button", {
+ name: new RegExp(`^Unpin from navbar: .*${escapeRegExp(title)}`),
+ });
+ await expect(navbarUnpin).toBeVisible();
+ await navbarUnpin.click();
+ await expect(page.getByRole("button", { name: "Pin to navbar (p)" })).toBeVisible();
+ await expect(navbarUnpin).toHaveCount(0);
+
// Move status on the detail page and confirm it sticks.
- await page.locator("select").first().selectOption({ label: "In Progress" });
- await expect(page.locator("select").first()).toHaveValue(/.+/);
+ await selectComboboxOption(page, "Status", "In Progress");
});
});
diff --git a/tests/e2e/mobile-smoke.spec.ts b/tests/e2e/mobile-smoke.spec.ts
index 36312342..3014f843 100644
--- a/tests/e2e/mobile-smoke.spec.ts
+++ b/tests/e2e/mobile-smoke.spec.ts
@@ -43,6 +43,14 @@ async function createIssueFromMobileTopbar(page: Page, title: string) {
await expect(page.getByText(title).first()).toBeVisible();
}
+async function selectComboboxOption(page: Page, name: string, option: string) {
+ const combo = page.getByRole("combobox", { name });
+ await expect(combo).toBeVisible();
+ await combo.click();
+ await page.getByRole("option", { name: option, exact: true }).click();
+ await expect(combo).toContainText(option);
+}
+
test.describe("Mobile smoke", () => {
test.beforeEach(async ({ page }) => {
await page.addInitScript(() => {
@@ -199,9 +207,7 @@ test.describe("Mobile smoke", () => {
await page.getByRole("button", { name: /^Comment/ }).click();
await expect(page.locator("span", { hasText: comment }).first()).toBeVisible();
- const status = page.getByRole("combobox", { name: "Status" });
- await status.selectOption({ label: "In Progress" });
- await expect(status).toHaveValue(/.+/);
+ await selectComboboxOption(page, "Status", "In Progress");
await page.getByTitle(/Assign agent/i).click();
await expect(page.getByRole("dialog")).toBeVisible();
diff --git a/tests/e2e/sprints-roadmap.spec.ts b/tests/e2e/sprints-roadmap.spec.ts
index 34694ccd..c6712b11 100644
--- a/tests/e2e/sprints-roadmap.spec.ts
+++ b/tests/e2e/sprints-roadmap.spec.ts
@@ -1,4 +1,22 @@
-import { expect, test } from "@playwright/test";
+import { expect, test, type Page } from "@playwright/test";
+
+function monthDelta(from: Date, to: Date) {
+ return (to.getFullYear() - from.getFullYear()) * 12 + to.getMonth() - from.getMonth();
+}
+
+async function pickDate(page: Page, label: string, target: Date) {
+ await page.getByRole("button", { name: label, exact: true }).click();
+ const picker = page.getByRole("dialog", { name: "Date picker" });
+ await expect(picker).toBeVisible();
+
+ const delta = monthDelta(new Date(), target);
+ const direction = delta >= 0 ? "Next month" : "Previous month";
+ for (let i = 0; i < Math.abs(delta); i += 1) {
+ await picker.getByRole("button", { name: direction }).click();
+ }
+
+ await picker.getByRole("button", { name: String(target.getDate()), exact: true }).click();
+}
test.describe("Sprints and roadmap management", () => {
test("sprints exposes management, rollover, and collapsible backlog", async ({ page }) => {
@@ -45,9 +63,11 @@ test.describe("Sprints and roadmap management", () => {
await dateButton.click();
await expect(page.getByRole("dialog", { name: "Project roadmap dates" })).toBeVisible();
- const dates = page.locator('input[type="date"]');
- await dates.nth(0).fill("2026-08-03");
- await dates.nth(1).fill("2026-08-14");
+ const now = new Date();
+ const startDate = new Date(now.getFullYear(), now.getMonth() + 1, 3);
+ const targetDate = new Date(now.getFullYear(), now.getMonth() + 1, 14);
+ await pickDate(page, "Start date", startDate);
+ await pickDate(page, "Target date", targetDate);
await page.getByRole("button", { name: "Save dates" }).click();
await expect(page.getByText("Roadmap dates updated.")).toBeVisible();
});