feat(web): write descriptions and comments with a visual editor

ts-rich-editor (TipTap) shows the formatting while writing: bold,
italic, strike, code, headings, lists, task lists, quotes, code blocks
and links, with @ suggesting the project's members and pasted
screenshots uploaded as attachments. It reads and writes the same
Markdown the API keeps (mentions as @[Name](id), images as
attachment:<id>, tables too), so nothing already written changes.

Used for comments, replies, editing a comment or a description, a new
ticket from the board and the public proposal form; the Markdown hint
and the paste directive are gone. List markers sat on a line of their
own, and task lists showed a stray bullet, in the editor and when read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-13 23:00:41 +02:00
parent 9892443eda
commit 836c6f63bc
16 changed files with 1714 additions and 318 deletions

View File

@@ -162,8 +162,9 @@ stateDiagram-v2
que ser del mismo ticket. Quien escribió el comentario recibe un aviso (`REPLIED`). Un comentario borrado que tiene
respuestas se queda, sin texto (`deleted: true`), para que el hilo se siga entendiendo, y desaparece con su última
respuesta; no cuenta en `commentCount`.
- **Formato**: descripciones, comentarios y notas de versión se escriben en Markdown; la API los guarda tal cual y la web
los muestra limpiando el HTML (DOMPurify). Una imagen pegada se sube como adjunto y queda en el texto como
- **Formato**: descripciones, comentarios y notas de versión se guardan en Markdown; la API los guarda tal cual y la web
los muestra limpiando el HTML (DOMPurify). En la web se escriben con un editor visual que lee y escribe ese mismo
Markdown, así que un cliente de la API (o el MCP) sigue mandando Markdown. Una imagen pegada se sube como adjunto y queda en el texto como
`![nombre](attachment:<id>)`: la subida responde `attachmentId`, y la web la descarga con la sesión de quien lee, así
que sirve también en proyectos privados.
- **Reacciones**: 👍 👎 😄 🎉 😕 ❤️ 🚀 👀 en los comentarios (`reactToComment(commentId, emoji, on)`,