2026 05 25 Pattern Editor No Markdown

Pattern Editor No-Markdown Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Make the task pattern editor accept literal text only, except for <mr-var /> variables and line breaks.

Architecture: Replace TipTap's markdown-oriented starter bundle with a minimal document schema that only supports paragraphs, text, hard breaks, and the custom variable mention node. Keep the existing mrVarToJSON / jsonToMrVar serialization boundary so backend payloads remain unchanged.

Tech Stack: React 19, TypeScript, TipTap, Vitest, React Testing Library


Task 1: Lock the behavior with a component regression test

Files:

import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { vi } from "vitest";

import { PatternEditor } from "./PatternEditor";

it("keeps markdown-like input as literal text", async () => {
  const user = userEvent.setup();
  const onChange = vi.fn();

  render(<PatternEditor value="" onChange={onChange} />);

  await user.click(screen.getByRole("textbox"));
  await user.type(screen.getByRole("textbox"), "## Text");

  expect(onChange).toHaveBeenLastCalledWith("## Text");
});

Run: npm exec vitest run src/components/PatternEditor.test.tsx
Expected: FAIL because the editor turns ## Text into formatted content.

Task 2: Remove markdown formatting support from PatternEditor

Files:

import { useRef, useEffect } from "react";
import { useEditor, EditorContent } from "@tiptap/react";
import Document from "@tiptap/extension-document";
import Paragraph from "@tiptap/extension-paragraph";
import Text from "@tiptap/extension-text";
import HardBreak from "@tiptap/extension-hard-break";
import { Node, InputRule, mergeAttributes } from "@tiptap/core";

const editorExtensions = [Document, Paragraph, Text, HardBreak, VariableMention];

const editor = useEditor({
  extensions: editorExtensions,
  content: mrVarToJSON(value),
  editable: !disabled,
  editorProps: {
    attributes: {
      class: cn("outline-none", className),
      ...(placeholder ? { "data-placeholder": placeholder } : {}),
    },
  },
  onUpdate: ({ editor: ed }) => {
    isInternalUpdate.current = true;
    onChange(jsonToMrVar(ed.getJSON()));
  },
});

Run: npm exec vitest run src/components/PatternEditor.test.tsx
Expected: PASS.

Task 3: Verify the change did not regress serialization

Files:

it("turns hardBreak nodes into newlines", () => {
  const doc: JSONContent = {
    type: "doc",
    content: [
      {
        type: "paragraph",
        content: [
          { type: "text", text: "line one" },
          { type: "hardBreak" },
          { type: "text", text: "line two" },
        ],
      },
    ],
  };

  expect(jsonToMrVar(doc)).toBe("line one\nline two");
});

Run: npm exec vitest run src/components/PatternEditor.test.tsx src/lib/patternSerialization.test.ts && npm run build
Expected: PASS, with build completing successfully.