mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-28 05:46:58 +02:00
* fix(comments): preserve newlines from agent CLI writes Agents (e.g. Codex) routinely emit `multica issue comment add --content "para1\n\npara2"` because Python/JSON-style string literals are their default. Bash does not expand `\n` inside double quotes, so the literal 4-char sequence flowed through the CLI into the database and rendered as text in the issue panel — comments came out as one wall of prose. Three coordinated fixes so the platform behavior no longer depends on whether a given model has strong bash-quoting intuition: - CLI: decode `\n / \r / \t / \\` in `--content` and `--description` for `issue create / update / comment add` (callers needing a literal backslash still have `--content-stdin`). - Agent prompt: rewrite the comment-add example in the injected runtime config to require `--content-stdin` + HEREDOC for any multi-line body, and call out the same rule for `--description`. The previous wording flagged stdin only for "backticks, quotes", which models read as irrelevant to plain paragraphs. - Renderer: add `remark-breaks` to the shared Markdown plugin chain so a bare `\n` becomes a visible line break instead of a CommonMark soft break — protects against models that emit single newlines for formatting. Tests: pin the new CLI helper, and pin the runtime-config guidance so the multi-line wording cannot decay back into a footnote. * fix(comments): address review feedback on newline-rendering PR - Cover the issue panel: ReadonlyContent (used by every comment card and the issue description) has its own react-markdown wiring; add remark-breaks there too so the renderer fix actually applies to the surface the bug was reported on, not just the chat panel. Pinned by ReadonlyContent line-break tests. - Make the prompt's `--description` guidance executable: add `--description-stdin` to `issue create` / `issue update`, refactor comment-add to share a single `resolveTextFlag` helper, and have the injected runtime config name the real flag instead of an imaginary "stdin / a tempfile" path. Pinned by the runtime-config guidance test. - Document the unescape contract on each affected flag's help text and pin the precise boundary in tests: `\n / \r / \t / \\` are decoded; `\d / \w / \s / \u / \0` and other unrecognised escapes pass through verbatim, so regex literals and Windows paths survive intact unless they embed a literal `\n` / `\r` / `\t`. Callers that need the literal sequence have `--content-stdin` / `--description-stdin` as the escape hatch.
75 lines
2.5 KiB
TypeScript
75 lines
2.5 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
import { render } from "@testing-library/react";
|
|
|
|
vi.mock("@multica/core/paths", () => ({
|
|
useWorkspacePaths: () => ({
|
|
issueDetail: (id: string) => `/test/issues/${id}`,
|
|
}),
|
|
useWorkspaceSlug: () => "test",
|
|
}));
|
|
|
|
vi.mock("../navigation", () => ({
|
|
useNavigation: () => ({ push: vi.fn(), openInNewTab: vi.fn() }),
|
|
}));
|
|
|
|
vi.mock("../issues/components/issue-mention-card", () => ({
|
|
IssueMentionCard: ({ issueId, fallbackLabel }: { issueId: string; fallbackLabel?: string }) => (
|
|
<span data-testid="issue-mention-card">{fallbackLabel ?? issueId}</span>
|
|
),
|
|
}));
|
|
|
|
vi.mock("./extensions/image-view", () => ({
|
|
ImageLightbox: () => null,
|
|
}));
|
|
|
|
vi.mock("./link-hover-card", () => ({
|
|
useLinkHover: () => ({}),
|
|
LinkHoverCard: () => null,
|
|
}));
|
|
|
|
vi.mock("./utils/link-handler", () => ({
|
|
openLink: vi.fn(),
|
|
isMentionHref: (href?: string) => Boolean(href?.startsWith("mention://")),
|
|
}));
|
|
|
|
import { ReadonlyContent } from "./readonly-content";
|
|
|
|
describe("ReadonlyContent math rendering", () => {
|
|
it("renders inline and block LaTeX with KaTeX markup", () => {
|
|
const { container } = render(
|
|
<ReadonlyContent
|
|
content={[
|
|
"Inline math: $E = mc^2$",
|
|
"",
|
|
"$$",
|
|
"\\int_0^1 x^2 \\, dx",
|
|
"$$",
|
|
].join("\n")}
|
|
/>,
|
|
);
|
|
|
|
const text = container.textContent?.replace(/\s+/g, " ") ?? "";
|
|
expect(container.querySelectorAll(".katex").length).toBeGreaterThanOrEqual(2);
|
|
expect(container.querySelector(".katex-display")).not.toBeNull();
|
|
expect(text).toContain("E = mc^2");
|
|
expect(text).toContain("\\int_0^1 x^2 \\, dx");
|
|
});
|
|
});
|
|
|
|
describe("ReadonlyContent line breaks", () => {
|
|
// Issue panel comments are the primary user-visible surface for agent
|
|
// output. CommonMark's default soft-break behavior collapses single
|
|
// newlines into spaces; agent text often relies on a single newline as a
|
|
// visible break. remark-breaks must remain wired into ReadonlyContent's
|
|
// remark plugin chain or comments lose their formatting again.
|
|
it("converts a single newline into a <br>", () => {
|
|
const { container } = render(<ReadonlyContent content={"line one\nline two"} />);
|
|
expect(container.querySelector("br")).not.toBeNull();
|
|
});
|
|
|
|
it("renders a blank-line gap as separate paragraphs", () => {
|
|
const { container } = render(<ReadonlyContent content={"para one\n\npara two"} />);
|
|
expect(container.querySelectorAll("p").length).toBeGreaterThanOrEqual(2);
|
|
});
|
|
});
|