Commit Graph

9 Commits

Author SHA1 Message Date
Naiyuan Qing
17ee59ccc5 feat(editor): standard Mermaid viewer with pan/zoom, exports and a real dialog (MUL-4908) (#5564)
* feat(editor): standard Mermaid viewer with pan/zoom, exports and a real dialog (MUL-4908)

Mermaid's fullscreen view was a bespoke lightbox: no visible close button, no
canvas interaction, and a toolbar that scrolled away with wide diagrams. Escape
also stopped working once the iframe took focus, which is why it read as
"impossible to close".

Replace it with a viewer built on the shared Dialog, so it inherits the
backdrop, focus trap, scroll lock, focus restore and Escape handling that HTML
preview already had.

The diagram stays in an `sandbox=""` iframe — isolation is not relaxed to buy
interactivity. Instead the iframe is `pointer-events: none` and pan/zoom is
applied from the host document as a transform on its wrapper. That inversion is
also what keeps Escape working: the iframe can never take focus.

- Viewer: fit-on-open, drag pan, wheel/pinch/keyboard zoom (25%-400%) anchored
  at the pointer, Fit / 100% / Reset, and a header toolbar that cannot scroll
  away. Panning is clamped so the canvas can never be lost.
- Export: `.mmd`, `.svg` and `.png`. Requires `htmlLabels: false` — browsers do
  not rasterize Mermaid's default HTML-in-foreignObject labels through an
  `<img>`; verified in Chromium that they paint zero pixels AND taint the
  canvas, so PNG export silently produced nothing.
- Inline: toolbar lifted out of the scroll container, natural-size rendering
  (small diagrams centered, wide ones scroll at a readable size with edge
  fades), copy source, and a parser message on the error fallback.
- Theme switches no longer close the viewer or discard zoom/position; the
  previous diagram stays on screen until its replacement is ready.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* fix(editor): stop Mermaid drags selecting text and misfiring the viewer (MUL-4908)

Two gesture defects on the inline diagram, reported by Naiyuan.

1. Dragging a diagram started a native text selection. The iframe is a replaced
   element, so the whole diagram box got painted with the selection highlight
   and the drag ran on into the surrounding comment text. Fixed with
   `user-select: none` on the inline scroller and the viewer canvas.

   Deliberately NOT by preventDefault-ing pointerdown: verified in Chromium that
   it also drops the default focus, which silently kills the viewer's keyboard
   controls (+/-, 0, arrows).

2. Any drag ended in a `click`, so trying to look along a wide diagram opened
   the viewer on top of the user. The inline diagram had no drag handling at
   all — only `onClick`. Suppressing the selection alone would have made this
   fire more reliably, not less, so both had to land together.

   Past a 5px threshold the gesture is a drag for good: releasing no longer
   taps, and a horizontal drag pans the scroller instead. This holds even when
   the diagram has no room to scroll, so an unscrollable diagram cannot open on
   release either. A still click, a sub-threshold jitter, and the expand button
   all still open the viewer.

Touch is left alone — it already pans natively and its vertical drags belong to
the page; the browser announces its takeover with pointercancel. Mouse and pen
have no native drag-to-scroll and are panned here.

Verified in Chromium: still click opens; a 150px drag pans and does not open; a
drag on an unscrollable diagram does not open; 3px jitter still opens; and no
gesture selects text.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: Walt <walt@multica.ai>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-17 16:07:37 +08:00
Naiyuan Qing
3b08456264 feat(views): block draft-fixing actions while attachments upload (#5465)
Submitting a composer mid-upload silently lost the file: the editor holds a
`blob:` placeholder that gets stripped during serialization, and the
attachment id does not exist yet, so the send succeeded without the file.
Chat and Quick Create gated this; manual issue create, Feedback's button,
comments, replies and comment edit did not.

Gate every action that FIXES a draft — Send/Create/Save, Cmd/Ctrl+Enter,
Enter on the title, and the manual/agent mode switches — behind one source
of truth: the editor document, which IS the upload queue. ContentEditor
publishes queue transitions via `onUploadingChange` (a transaction listener,
not `onUpdate` — that path is debounced and skips no-change emissions, and a
failed upload leaves byte-identical markdown, so the un-gate would never
fire). `useUploadGate` pairs that render state with an `isBlocked()` re-read
at submit time, since the shortcut paths never consult the button.

The publisher emits its current answer on subscribe rather than only on
flips: hosts outlive editor instances (comment edit remounts on cancel,
chat swaps by key on agent switch), and an editor torn down mid-upload would
otherwise leave submit wedged shut with no pending node left to reopen it.

Also fixes the failure toast that never fired: `uploadWithToast` only
reported errors if a caller passed `onError`, and none did, so failed uploads
removed their placeholder and vanished unexplained. `useEditorUpload` now
supplies it once for every composer.

Per MUL-4808: drops Quick Create's upload-time lock on the attach button
(files can queue), and leaves description autosave ungated by design.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-16 09:56:24 +08:00
Jiayuan Zhang
343ace89a7 feat(editor): MUL-3557 add one-click task-list toggle to bubble menu (#4538)
Add a dedicated checkbox/task-list toggle button to the editor bubble menu, between the List dropdown and Quote. It turns the current line(s) into a `- [ ]` task item or back to a paragraph in one tap, reusing the existing toggleTaskList() command and the same Toggle/Tooltip pattern as the neighboring controls. Adds bubble_menu.task_list locale keys for en/zh-Hans/ja/ko.

Co-authored-by: Lambda <lambda@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-06-24 17:04:05 +02:00
Bohan Jiang
dfc159e1aa feat: skip agent triggering on /note-prefixed comments (MUL-3115, #3649) (#3885)
* feat(comments): skip agent triggering on /note-prefixed comments

A comment whose first token is the reserved /note prefix (case-insensitive)
is stored like any other comment but never wakes an agent. The guard sits at
the top of triggerTasksForComment, the single chokepoint, so it covers all
three trigger paths — assignee, squad leader, and @mentioned agents. Gating
only shouldEnqueueOnComment (as originally proposed) would still let
"/note @agent ..." through the mention path.

Lets members leave human-only tips/notes on agent-assigned issues without
burning an agent run. MUL-3115, closes #3649.

Co-authored-by: multica-agent <github@multica.ai>

* feat(editor): add /note built-in slash command to comment composer

Enable the `/` menu in the issue comment and reply composers in a new
"command" mode that lists fixed built-in commands instead of the chat
skill picker. Currently one command, /note, which marks a comment as a
human-only note that won't trigger the assigned agent.

Selecting it inserts the plain-text "/note " prefix (not a rich node), so
a menu pick and a hand-typed command are byte-identical and the backend
detects either with a simple prefix match. The command menu renders nothing
on a non-matching `/` (hideOnEmpty) so typing a date like 6/8 isn't noisy.
The chat skill picker is unchanged. MUL-3115.

Co-authored-by: multica-agent <github@multica.ai>

* refactor(editor): match /note by label prefix and localize its description

Address PR review feedback:
- buildBuiltinCommandItems now matches the command label as a prefix only,
  dropping the description substring match copied from the skill picker. With
  one command this keeps the menu predictable (/no surfaces note; /deploy or a
  description word like /agent shows nothing) and avoids Enter selecting note
  unexpectedly.
- The command description is now a localized UI string: added
  slash_command.commands.note to all four editor locales (en/ja/ko/zh-Hans)
  and the menu renders it via the typed translator. The /label itself stays
  literal since it's the typed token the backend matches.

MUL-3115.

Co-authored-by: multica-agent <github@multica.ai>

* fix(editor): shorten /note command description to avoid truncation

The slash menu item is single-line (truncate, w-72), so the longer copy was
cut off. Shorten to "won't trigger any agents" across all four locales — also
more accurate, since /note skips assignee, squad leader, and @mentioned agents,
not just the assigned one.

MUL-3115.

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: J <j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-06-08 14:50:52 +08:00
Naiyuan Qing
b0d479c6e7 fix: use mentions for chat context (#3755) 2026-06-04 16:45:10 +08:00
Bohan Jiang
0d38288dbd MUL-2926 feat(editor): support markdown checkbox task lists (#3593) (#3657)
* feat(editor): support markdown checkbox task lists (#3593)

Render `- [ ]` / `- [x]` as interactive checkboxes in the issue content
editor, matching GitHub / Notion.

- Register TaskList + a patched TaskItem in the shared extension factory.
  Both ship their own markdown tokenizer / renderMarkdown, input rules, and
  a checkbox NodeView; the taskList tokenizer is consulted before marked's
  built-in list tokenizer, so `- [ ]` becomes a task while a plain `- ` still
  falls through to the bullet list.
- Patch TaskItem's keymap to share PatchedListItem's split -> lift Enter
  chain (double-Enter on an empty item exits the list); nested: true enables
  sub-tasks and nested round-trips.
- Add a "Task list" entry to the bubble-menu list dropdown (+ i18n for en /
  zh-Hans / ja / ko).
- Style task lists in prose.css for both the editor ([data-type="taskList"])
  and the readonly remark-gfm output (.contains-task-list); completed items
  render muted.

Readonly already rendered task lists via remark-gfm; this brings the editable
view to parity. Adds markdown round-trip and readonly checked-state tests.

MUL-2926

Co-authored-by: multica-agent <github@multica.ai>

* fix(editor): keep readonly nested task lists block-laid-out (#3593)

The shared `display: flex` rule on task-list items broke nested task lists in
the readonly view. remark-gfm renders a task item as
`<li><input> text <ul>…</ul></li>` — no body wrapper — so a nested list is a
direct sibling of the checkbox and text, and flex pulled it onto the same row.
The editor's Tiptap NodeView wraps the body in a `<div>`, so it was unaffected.

Split the task-list CSS into separate editor and readonly blocks: the editor
keeps the flex row; readonly stays a block list item with an inline checkbox so
a nested `<ul>` drops below and indents under its parent. Adds a readonly test
that pins the nested DOM shape (nested `<ul>` inside the parent `<li>`), so a
future remark-gfm change that wraps the body fails loudly.

MUL-2926

Co-authored-by: multica-agent <github@multica.ai>

* feat(editor): convert `- [ ] ` typing into a task list (#3593)

TaskItem's built-in input rule only converts `[ ] ` / `[x] ` typed at the start
of a plain paragraph. When the user types the GitHub-style `- [ ] ` the leading
`- ` first turns the line into a bullet, and the built-in rule no longer fires —
so `[ ]` stayed as literal text and nothing became a checkbox.

Add an input rule on PatchedTaskItem that catches the checkbox token when it is
the entire content of a freshly-typed list item (bullet or ordered) and converts
just that item into a task item (deleteRange → liftListItem → toggleList). The
anchored regex means it only fires on an item whose whole content is `[ ] ` /
`[x] `, so sibling items in the same list are left untouched.

Adds typing-level tests (real input-rule simulation) covering `[ ] `, `[x] `,
`- [ ] `, `- [x] `, the mixed-list split case, and the plain-bullet no-op.

MUL-2926

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: J <j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-06-04 16:36:46 +08:00
Jiayuan Zhang
0d51614c9c feat(editor): text highlight (==text==) in description & comments [MUL-2934] (#3661)
* feat(editor): support text highlight (==text==) in description & comments

Adds a single-color (yellow) text highlight mark to the shared rich-text
editor, round-tripped through stored Markdown as ==text==.

- HighlightExtension: @tiptap/extension-highlight + @tiptap/markdown hooks
  (markdownTokenizer/parseMarkdown/renderMarkdown) so ==text== <-> <mark>
  round-trips; inner inline formatting preserved via inlineTokens.
- Bubble menu: highlight toggle button (Mod-Shift-H), i18n in 4 locales.
- Read-only renderer: highlightToHtml lowers ==text== -> <mark> (skips code
  and math); rehype-sanitize schema whitelists <mark>. Nested Markdown inside
  a highlight still parses via the existing rehype-raw step.
- prose.css: single yellow <mark> style, legible in light/dark.

Pinned @tiptap/extension-highlight to exact 3.22.1 to match @tiptap/core
(>=3.23 expects a getStyleProperty export core 3.22.1 doesn't have).

Web/desktop only. Mobile (native md4c, no == syntax, no custom renderers)
is tracked as a follow-up. MUL-2934.

Tests: editor round-trip (cross-process serialization protocol), readonly
<mark> rendering + sanitize, and the ==->mark transform incl. code-skip.

Co-authored-by: multica-agent <github@multica.ai>

* fix(editor): align highlight boundary rules across editor & readonly

Addresses two boundary bugs from review (PR #3661):

1. A == inside inline code/math could close a highlight when the opening
   == was outside the literal span (e.g. ==a `b==c` d== wrongly became
   <mark>a `b</mark>c` d==). Both the editor tokenizer's lazy regex and the
   readonly transform only guarded the opening fence, not the closing one.
2. The readonly transform matched across blank lines (==a\n\nb==) while the
   editor lexes those as two literal paragraphs — a storage↔editor↔readonly
   mismatch.

Fix: extract one shared matcher (utils/highlight-match.ts) used by BOTH the
editor tokenizer and the readonly lowering, so the rules can't drift. It skips
fences that fall inside code/math literal ranges (open or close) and caps the
inner span at the first blank line.

Tests: shared-matcher unit tests + both repros covered on the editor
(round-trip/HTML) and readonly (transform + rendered DOM) sides.

Co-authored-by: multica-agent <github@multica.ai>

* fix(editor): handle CRLF in highlight blank-line boundary

BLANK_LINE_RE only matched LF, so a CRLF blank line (==a\r\n\r\nb==) was not
recognized as a block boundary and got highlighted. Widen to \r?\n[ \t]*\r?\n.

Tests: CRLF blank-line (no highlight) + CRLF soft-break (still highlights) on
the matcher, readonly transform, and editor sides.

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: Lambda <lambda@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-06-02 17:24:55 +02:00
Qi Yijiazhen
91c1e51411 feat(editor): add / slash-command palette for invoking agent skills (#3159)
* feat(editor): add / slash-command palette for invoking agent skills

Adds a `/` trigger in the chat box that opens a popover listing the active
agent's skills. Selecting an item inserts a `[/label](slash://skill/<id>)`
token; the daemon extracts those IDs in `buildChatPrompt` and emits an
"Explicitly selected skills:" block using the canonical names from the
agent's skill registry — labels are display-only and never trusted.

Built on Tiptap's `Mention` extension so the suggestion lifecycle,
keyboard routing, and IME handling mirror the existing `@` mention UX.
Item list is sourced from the React Query workspace cache (no per-keystroke
fetch). Gated behind a new `enableSlashCommands` prop so only `chat-input`
opts in; other `ContentEditor` consumers (issue editor, comments) are
unaffected. Read-only markdown surfaces render the token as a `.slash-command`
pill via a custom link renderer + sanitize-schema/url-transform allowlists.

Closes #3108

* fix(i18n): add slash_command editor copy for ko/ja

The PR added slash_command popover empty-state keys to en + zh-Hans only;
locales/parity.test.ts requires every locale to cover every EN key, so ko
and ja failed CI. Add the two keys (no_skills_configured, no_results)
matching existing skill terminology (스킬 / スキル).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Naiyuan Qing <145280634+NevilleQingNY@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 15:57:42 +08:00
Anderson Shindy Oki
1aa742053b i18n: add japanese locale (MUL-2893) (#3538)
* i18n: add japanese locale

* fix: spacing issues

* refactor

* fix(desktop): set <html lang> before paint to avoid JA Kanji font flash

Switch the documentElement.lang sync from useEffect to useLayoutEffect so
lang is committed before the first paint. Otherwise Japanese desktop users
saw one frame of Kanji rendered with the Chinese-first fallback stack before
the html[lang|="ja"] CJK override applied. Also fix the stale selector in the
HTML_LANG comment (html[lang^="ja"] -> html[lang|="ja"]).

Addresses review nits on MUL-2893.

Co-authored-by: multica-agent <github@multica.ai>

* fix(docs): tokenize the ideographic iteration mark in JA search

Add U+3005 (々) to the Japanese search tokenizer character class. It sits just
below the kana blocks, so words like 様々 / 日々 / 個々 previously dropped the
mark and split awkwardly, hurting recall.

Addresses a review nit on MUL-2893.

Co-authored-by: multica-agent <github@multica.ai>

* fix(i18n): restore ja locale parity after merging main

Merging main brought new EN strings into agents/chat/onboarding/settings/
squads that the ja bundle (authored against an older snapshot) lacked, breaking
the locales parity test. Add the Japanese translations for the new keys
(workspace logo upload, agents runtime filter, chat session-history stop
dialog, onboarding social_github, squad archived status) and drop the two
renamed chat window keys (active_group / archived_group) that EN removed in
favour of history_group.

Fixes the failing @multica/views parity.test.ts on the FE CI for MUL-2893.

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: J <j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-06-02 14:29:29 +08:00