diff --git a/packages/views/issues/components/table-inline-title.test.tsx b/packages/views/issues/components/table-inline-title.test.tsx
index 2791798a8..3b10b4a96 100644
--- a/packages/views/issues/components/table-inline-title.test.tsx
+++ b/packages/views/issues/components/table-inline-title.test.tsx
@@ -55,9 +55,11 @@ function makeRow(title: string): Extract<
const baseProps = {
onUpdate: vi.fn(),
onOpen: vi.fn(),
+ onCreateSubIssue: vi.fn(),
onToggleParent: vi.fn(),
toggleLabel: "Toggle sub-issues",
renameLabel: "Rename issue",
+ createSubIssueLabel: "Create sub-issue",
};
/** Editing state lives in the table (one editor at a time); mirror that. */
@@ -66,11 +68,13 @@ function Harness({
onOpen,
onUpdate,
onEditingChange,
+ onCreateSubIssue,
}: {
title: string;
onOpen?: () => void;
onUpdate?: (updates: unknown) => void;
onEditingChange?: (editing: boolean) => void;
+ onCreateSubIssue?: () => void;
}) {
const [editing, setEditing] = useState(false);
return (
@@ -84,6 +88,7 @@ function Harness({
}}
onOpen={onOpen ?? baseProps.onOpen}
onUpdate={onUpdate ?? baseProps.onUpdate}
+ onCreateSubIssue={onCreateSubIssue ?? baseProps.onCreateSubIssue}
/>
);
}
@@ -120,6 +125,23 @@ describe("InlineTitle", () => {
expect(screen.queryByRole("textbox")).toBeNull();
});
+ it("opens sub-issue creation without also navigating into the issue", () => {
+ const onCreateSubIssue = vi.fn();
+ const rowClick = vi.fn();
+ render(
+
+
+
,
+ );
+
+ fireEvent.click(
+ screen.getByRole("button", { name: "Create sub-issue" }),
+ );
+
+ expect(onCreateSubIssue).toHaveBeenCalledTimes(1);
+ expect(rowClick).not.toHaveBeenCalled();
+ });
+
it("commits on click-away without also navigating into the issue", async () => {
const user = userEvent.setup({ delay: null });
const onUpdate = vi.fn();
diff --git a/packages/views/issues/components/table-view-editing.test.tsx b/packages/views/issues/components/table-view-editing.test.tsx
index 088c608cd..78b37acf4 100644
--- a/packages/views/issues/components/table-view-editing.test.tsx
+++ b/packages/views/issues/components/table-view-editing.test.tsx
@@ -20,6 +20,7 @@ import type { Issue } from "@multica/core/types";
import { renderWithI18n } from "../../test/i18n";
import { IssueSurfaceSelectionProvider } from "../surface/selection-context";
import type { IssueSurfaceSelection } from "../surface/selection-context";
+import type { IssueCreateDefaults } from "../surface/types";
import type { ChildProgress } from "./list-row";
import { TableView, useReleaseEditingCellOnUnmount } from "./table-view";
@@ -131,10 +132,12 @@ function Harness({
issues,
childProgressMap,
surfaceKey,
+ onCreateIssue = () => {},
}: {
issues: Issue[];
childProgressMap: Map;
surfaceKey: string;
+ onCreateIssue?: (defaults: IssueCreateDefaults) => void;
}) {
return (
@@ -149,6 +152,7 @@ function Harness({
total={issues.length}
search=""
onSearchChange={() => {}}
+ onCreateIssue={onCreateIssue}
exportIssues={() => Promise.resolve(issues)}
resolveExportLookups={() =>
Promise.resolve({
@@ -241,6 +245,37 @@ describe("TableView cell editors under data refresh", () => {
expect(screen.queryByRole("button", { name: /Backlog/ })).toBeNull();
expect(identifiers()).toEqual(["MUL-b", "MUL-a"]);
}, 20_000);
+
+ it("opens creation with the row as parent and inherits its project", async () => {
+ const user = userEvent.setup({ delay: null, pointerEventsCheck: 0 });
+ const onCreateIssue = vi.fn();
+ const issue = {
+ ...makeIssue("a", "Alpha task", "todo"),
+ project_id: "project-1",
+ };
+
+ renderWithI18n(
+
+
+ ,
+ );
+
+ const row = screen.getByText("MUL-a").closest("tr")!;
+ await user.click(
+ within(row).getByRole("button", { name: "Create sub-issue" }),
+ );
+
+ expect(onCreateIssue).toHaveBeenCalledWith({
+ parent_issue_id: "a",
+ parent_issue_identifier: "MUL-a",
+ project_id: "project-1",
+ });
+ });
});
// Row virtualization unmounts a cell when its row scrolls out of the window
diff --git a/packages/views/issues/components/table-view.tsx b/packages/views/issues/components/table-view.tsx
index f14f42844..cdfe5dda3 100644
--- a/packages/views/issues/components/table-view.tsx
+++ b/packages/views/issues/components/table-view.tsx
@@ -99,6 +99,7 @@ import { ProjectPicker } from "../../projects/components/project-picker";
import { useT } from "../../i18n";
import { useIssueSurfaceActionsOptional } from "../surface/actions-context";
import { useIssueSurfaceSelection } from "../surface/selection-context";
+import type { IssueCreateDefaults } from "../surface/types";
import { ProgressRing } from "./progress-ring";
import {
AssigneePicker,
@@ -137,6 +138,7 @@ type TableViewProps = {
total: number;
search: string;
onSearchChange: (query: string) => void;
+ onCreateIssue: (defaults: IssueCreateDefaults) => void;
exportIssues: () => Promise;
resolveExportLookups: (needs: {
projects: boolean;
@@ -476,9 +478,11 @@ export function InlineTitle({
onEditingChange,
onUpdate,
onOpen,
+ onCreateSubIssue,
onToggleParent,
toggleLabel,
renameLabel,
+ createSubIssueLabel,
}: {
row: Extract;
/** Rename state is owned by the table (one editor at a time) so it also
@@ -488,9 +492,11 @@ export function InlineTitle({
onUpdate: (updates: Partial) => void;
/** Navigate to the issue — clicking the title is the primary way IN. */
onOpen: () => void;
+ onCreateSubIssue: () => void;
onToggleParent: () => void;
toggleLabel: string;
renameLabel: string;
+ createSubIssueLabel: string;
}) {
const [draft, setDraft] = useState(row.issue.title);
const editingRef = useRef(editing);
@@ -585,6 +591,17 @@ export function InlineTitle({
>
{row.issue.title}
+