diff --git a/desktop/src/features/messages/lib/useRichTextEditor.ts b/desktop/src/features/messages/lib/useRichTextEditor.ts
index dc65ccad9..69dc079eb 100644
--- a/desktop/src/features/messages/lib/useRichTextEditor.ts
+++ b/desktop/src/features/messages/lib/useRichTextEditor.ts
@@ -112,7 +112,9 @@ export function useRichTextEditor({
{
extensions: [
StarterKit.configure({
- // Use hard breaks (Shift+Enter) — Enter submits the message.
+ // Hard-break node is kept enabled so pasted markdown that contains
+ // explicit hard breaks still round-trips, but Shift+Enter is rebound
+ // below (`smartShiftEnter`) to split into a real new paragraph.
hardBreak: {
keepMarks: true,
},
@@ -258,8 +260,12 @@ export function useRichTextEditor({
// Non-empty → split the paragraph within the blockquote.
return ed.chain().splitBlock().focus().run();
}
- // Default: hard break (StarterKit handles it).
- return false;
+ // Default: split into a real new paragraph. WYSIWYG parity
+ // with the rendered timeline — two paragraphs always have
+ // margin between them. Previously inserted a hard break
+ // (
), which produced a single \n on send and rendered
+ // visibly tighter than the post-send paragraph spacing.
+ return ed.chain().splitBlock().focus().run();
},
ArrowDown: ({ editor: ed }) => {
// Empty last list item + Down → exit list to paragraph below.
@@ -320,7 +326,7 @@ export function useRichTextEditor({
editorProps: {
attributes: {
class:
- "min-h-0 resize-none overflow-y-hidden border-0 bg-transparent px-0 py-0 text-sm leading-6 md:leading-6 shadow-none focus-visible:ring-0 caret-foreground outline-hidden prose-sm max-w-none",
+ "min-h-0 resize-none overflow-y-hidden border-0 bg-transparent px-0 py-0 text-sm leading-7 md:leading-7 shadow-none focus-visible:ring-0 caret-foreground outline-hidden max-w-none",
"data-testid": "message-input",
},
// ArrowUp in an empty composer → edit your last message (Slack
diff --git a/desktop/src/shared/styles/globals.css b/desktop/src/shared/styles/globals.css
index 60bd597ec..843a62014 100644
--- a/desktop/src/shared/styles/globals.css
+++ b/desktop/src/shared/styles/globals.css
@@ -221,13 +221,64 @@
}
/* ── Tiptap rich-text composer ──────────────────────────────────────── */
+/*
+ * Composer styles mirror the rendered Markdown timeline (see
+ * desktop/src/shared/ui/markdown.tsx, default variant) so the editor is
+ * visually WYSIWYG with the sent message. The owl rules `& > * + *` drive
+ * inter-block spacing, leaving an isolated single block (e.g. an empty
+ *
when a list is freshly toggled) with zero added margin — so the
+ * composer height stays static until additional items are added.
+ */
.rich-text-composer .tiptap {
- outline: none;
- min-height: 1.5rem; /* single line height */
- font-size: 0.875rem;
- line-height: 1.5rem;
+ @apply text-sm leading-7 outline-none;
+ min-height: 1.75rem; /* one line at leading-7 (28px) */
}
+/* Inter-block spacing — matches markdown.tsx default variant. */
+.rich-text-composer .tiptap > * + * {
+ @apply mt-3;
+}
+.rich-text-composer .tiptap > *:first-child {
+ margin-top: 0;
+}
+.rich-text-composer .tiptap > *:last-child {
+ margin-bottom: 0;
+}
+
+/* Headings push and pull. */
+.rich-text-composer .tiptap > * + h1,
+.rich-text-composer .tiptap > * + h2,
+.rich-text-composer .tiptap > * + h3 {
+ @apply mt-3.5;
+}
+.rich-text-composer .tiptap > h1 + *,
+.rich-text-composer .tiptap > h2 + *,
+.rich-text-composer .tiptap > h3 + * {
+ @apply mt-0.5;
+}
+
+/* Blockquote, code block, table, and hr breathe more. */
+.rich-text-composer .tiptap > * + blockquote,
+.rich-text-composer .tiptap > blockquote + * {
+ @apply mt-3.5;
+}
+.rich-text-composer .tiptap > * + pre,
+.rich-text-composer .tiptap > pre + * {
+ @apply mt-3.5;
+}
+.rich-text-composer .tiptap > * + hr,
+.rich-text-composer .tiptap > hr + * {
+ @apply mt-4;
+}
+
+/* Lists immediately after a paragraph hug a little tighter. */
+.rich-text-composer .tiptap > p + ul,
+.rich-text-composer .tiptap > p + ol {
+ @apply mt-1.5;
+}
+
+/* Paragraph placeholder. Paragraph itself has no margin — the owl rule
+ * above provides spacing between sibling blocks. */
.rich-text-composer .tiptap p {
margin: 0;
}
@@ -254,81 +305,77 @@
color: hsl(var(--foreground));
}
+/* Inline marks. */
.rich-text-composer .tiptap strong {
- font-weight: 600;
+ @apply font-semibold;
}
-
.rich-text-composer .tiptap em {
- font-style: italic;
+ @apply italic;
}
-
.rich-text-composer .tiptap s {
text-decoration: line-through;
}
+.rich-text-composer .tiptap a {
+ @apply font-medium text-primary underline underline-offset-4 cursor-pointer;
+}
+/* Inline code. */
.rich-text-composer .tiptap code {
- border-radius: 0.375rem;
- background: hsl(var(--muted));
- padding: 0.125rem 0.375rem;
- font-family: ui-monospace, monospace;
- font-size: 0.8125rem;
+ @apply rounded-md bg-muted px-1.5 py-0.5 font-mono text-[13px] text-foreground;
}
+/* Code block. */
.rich-text-composer .tiptap pre {
- border-radius: 0.75rem;
- border: 1px solid hsl(var(--border) / 0.7);
- background: hsl(var(--muted) / 0.6);
- padding: 0.375rem 0.75rem;
- overflow-x: auto;
- margin: 0.25rem 0;
+ @apply max-h-[400px] overflow-x-auto overflow-y-auto rounded-xl border border-border/70 bg-muted/60 px-3 py-1.5 shadow-xs;
}
-
.rich-text-composer .tiptap pre code {
- background: none;
- padding: 0;
- border-radius: 0;
- font-size: 0.8125rem;
- line-height: 1.5rem;
+ @apply bg-transparent p-0 rounded-none text-[13px] leading-6;
}
+/* Blockquote. */
.rich-text-composer .tiptap blockquote {
- border-left: 2px solid hsl(var(--border));
- padding-left: 1rem;
- font-style: italic;
- color: hsl(var(--muted-foreground));
- margin: 0.25rem 0;
+ @apply border-l-2 border-border pl-4 italic text-muted-foreground;
+}
+.rich-text-composer .tiptap blockquote > *:first-child {
+ margin-top: 0;
+}
+.rich-text-composer .tiptap blockquote > * + * {
+ @apply mt-2;
}
+/* Lists. The owl rule (& > * + *) above handles spacing relative to
+ * surrounding blocks; per-list margin is intentionally not set, so
+ * toggling an empty list does not change the editor height. */
.rich-text-composer .tiptap ul {
- list-style-type: disc;
- padding-left: 1.5rem;
- margin: 0.25rem 0;
+ @apply list-disc pl-6 space-y-1 marker:text-muted-foreground;
+ margin: 0;
}
-
.rich-text-composer .tiptap ol {
- list-style-type: decimal;
- padding-left: 1.5rem;
- margin: 0.25rem 0;
+ @apply list-decimal pl-6 space-y-1 marker:text-muted-foreground;
+ margin: 0;
}
-
.rich-text-composer .tiptap li {
- margin: 0.125rem 0;
+ @apply my-1;
}
-
.rich-text-composer .tiptap li p {
display: inline;
}
-.rich-text-composer .tiptap a {
- color: hsl(var(--primary));
- text-decoration: underline;
- text-underline-offset: 4px;
- cursor: pointer;
+/* Headings (only reachable via paste, since heading input rules are
+ * disabled in StarterKit — but pasted markdown round-trips). */
+.rich-text-composer .tiptap h1 {
+ @apply text-xl font-semibold leading-8 tracking-tight;
+}
+.rich-text-composer .tiptap h2 {
+ @apply text-lg font-semibold leading-7 tracking-tight;
+}
+.rich-text-composer .tiptap h3 {
+ @apply text-base font-semibold leading-6 tracking-tight;
}
+/* Horizontal rule. */
.rich-text-composer .tiptap hr {
- border-color: hsl(var(--border) / 0.8);
- margin: 0.5rem 0;
+ @apply border-border/80;
}
.rich-text-composer .tiptap .mention-highlight {