Commit Graph

4382 Commits

Author SHA1 Message Date
Bohan Jiang
1fef98c24f fix(desktop): open in-app links in a tab instead of the browser (MUL-5208) (#5826)
* fix(desktop): open in-app links in a tab instead of the browser (MUL-5208)

A link written as an absolute URL on this deployment's own origin
(`https://<app-host>/acme/issues/1` — what "copy link" produces, and what
agents paste into chat) fell through openLink's external branch to
window.open, which Electron routes to shell.openExternal. Clicking an issue
link in Desktop chat therefore opened a browser window instead of a tab.

openLink now resolves such a URL back to its in-app path and takes the same
route a relative path does. Backend-served prefixes (/api/, /_next/) stay
external so attachment downloads keep working.

Two supporting fixes the change depends on:

- The `multica:navigate` event had no listener on web, so in-app paths in
  content were dead links there; normalizing app URLs would have extended
  that to every pasted app URL. The web platform layer now answers the event
  with a router push.
- The desktop handler opened every path inside the active workspace's tab
  group. A cross-workspace link now goes through switchWorkspace, matching
  what the navigation adapter already does for pushes.

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

* fix(desktop): scope in-app link conversion to workspace pages, answer it in issue windows

Review follow-ups on MUL-5208.

1. The non-page exclusion list only covered /api/ and /_next/, so a
   same-origin /uploads/* link — local-storage attachments, served by the
   backend and proxied by web — was routed as an app page, opening a dead tab
   instead of the file. Replaced the deny-list with the app's own routing
   model: an absolute URL converts to an in-app path only when its first
   segment is a slug a workspace could own, which the existing reserved-slug
   list (shared with the backend) already answers. /api, /uploads, /_next,
   /favicon.ico and the pre-workspace routes all stay external without a
   second list to keep in sync.

2. A dedicated issue window derives the same app origin from its adapter but
   had no multica:navigate listener, so a same-origin link there became a
   silent no-op (it used to reach the browser). The window now answers the
   event: another issue opens in place — matching what its adapter push and
   mention chips already do — and any other app page, which this single-route
   window cannot host, goes to the browser.

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

---------

Co-authored-by: J <agent@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-23 16:50:23 +08:00
Naiyuan Qing
3d9d3c0aa5 Revert "Unify resize interaction feedback (#5779)" (#5831)
This reverts commit 7d6a1dfab1.
2026-07-23 16:39:06 +08:00
YYClaw
e3ebf317ae fix(i18n): capitalize on-behalf-of attribution label (MUL-5026) (#5670) 2026-07-23 16:35:22 +08:00
Naiyuan Qing
4bf17f5786 Revert "fix(resize): keep drag cursor consistent for table & chat resize (MUL…" (#5830)
This reverts commit 975cd49c3f.
2026-07-23 16:30:37 +08:00
Naiyuan Qing
975cd49c3f fix(resize): keep drag cursor consistent for table & chat resize (MUL-5166) (#5824)
* fix(resize): keep the drag cursor consistent for table and chat resize

Table column resize and the chat floating-window resize locked the active
cursor via document.body.style.cursor, which loses the CSS cascade to any
descendant that declares its own cursor (clickable rows, inputs, buttons).
So the resize cursor showed on hover but flipped back to pointer/text/default
once the drag pointer swept over those elements — inconsistent with the
sidebar rail and react-resizable-panels, which force it globally.

Switch both to the same robust contract used by the sidebar/panels: set a
data attribute on <html> during the drag and let a global
`html[...], html[...] * { cursor: ...; user-select: none } !important` rule in
base.css win over every descendant. Chat keys the attribute by direction
(left/top/corner) so col/row/nw-resize stay correct.

MUL-5166

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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(chat): clean up chat resize cursor lock on every drag-end path

The chat window resize started the drag with setPointerCapture but only
removed data-chat-resizing on pointerup. A pointer-capture drag can end via
pointercancel, lostpointercapture, or window blur without ever firing
pointerup; since the attribute now drives a global
`html[...] * { cursor; user-select } !important` rule, a missed cleanup would
strand it and freeze the whole page in the resize cursor with text selection
disabled.

Mirror the sidebar rail: one idempotent finishDrag() wired to pointerup,
pointercancel, lostpointercapture and blur, releasing pointer capture and
removing every listener plus the attribute from a single path. Add a
regression test covering each termination path.

MUL-5166

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

* fix(ui): release table resize cursor lock on window blur too

Table column resize wires stopResize to pointerup/pointercancel but not to
window blur. With the resize cursor now held by a global
`html[data-table-resizing] * { cursor; user-select } !important` rule, an
alt-tab mid-drag (button released while the window is blurred, so no pointerup
arrives) would strand the attribute and freeze the page. stopResize is
idempotent, so also wire it to blur — matching the sidebar rail and chat.

MUL-5166

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-23 16:20:24 +08:00
Multica Eve
40f9ecdd56 MUL-5202: unify Issue Query across List, Board, and Swimlane (#5820)
* MUL-5202: migrate status issue surfaces to table query

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

* MUL-5202: unify grouped issue surfaces

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

* MUL-5202: cover move safety boundaries

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

* MUL-5202: preserve server swimlane semantics

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

* MUL-5202: keep grouped surface facets exact

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

---------

Co-authored-by: Eve <eve@multica-ai.local>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-23 15:58:25 +08:00
Multica Eve
6992c58de3 MUL-5185: add Codex Fast mode (#5821)
* feat(agents): add Codex fast mode (MUL-5185)

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

* fix(agents): make Codex Fast override authoritative

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

* fix(agents): remove Codex Fast config conflicts

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

* chore: refresh checks after conflict resolution

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

---------

Co-authored-by: Eve <eve@multica-ai.local>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-23 15:40:18 +08:00
milymarkovic
09dce598df MUL-5155: KAP-1051: diagnose Codex thread/start timeouts fail-closed (#5759)
* fix(agent/codex): diagnose thread start timeouts (KAP-1051)

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

* fix(agent/codex): validate thread ID before success lifecycle

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

* fix(agent/codex): confirm process tree cleanup

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

* fix(agent/codex): bound Windows pipe cleanup

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

* ci: run bounded Codex cleanup test on Windows

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

* fix(agent): reap initialize timeout process groups

* fix(agent): redact initialize timeout stderr

* fix(agent): redact initialize context failures

---------

Co-authored-by: multica-agent <github@multica.ai>
2026-07-23 15:09:50 +08:00
Naiyuan Qing
7d6a1dfab1 Unify resize interaction feedback (#5779) 2026-07-23 13:30:18 +08:00
Multica Eve
8065cead85 MUL-5198: Restore server-backed issue table grouping (MUL-5100) (#5817)
* revert(issues): restore server-backed table grouping

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

* test(skills): stabilize import completion coverage

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

---------

Co-authored-by: Eve <eve@multica-ai.local>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-23 12:03:27 +08:00
Bohan Jiang
82da943346 fix(chat): scale the composer max height with the chat surface (MUL-5196) (#5815)
The chat composer was capped at a fixed max-h-40 (160px), so a long draft
was previewed through a five-line porthole no matter how large the chat
window was. Cap the composer at 50% of the surface it sits on instead,
with a 384px absolute ceiling, and let the card shrink into that cap via
a flex-column wrapper.

Verified in Chromium across all four surfaces that mount ChatInput
(floating window at min and expanded size, chat tab, agent builder): a
long draft caps at min(50% of the surface, 384px) and scrolls internally,
a short draft still hugs its content, and the message list keeps space.

Co-authored-by: Bohan-J <bohan@devv.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-23 11:25:30 +08:00
Bohan Jiang
f9bc845d11 docs(self-host): add Upgrading section to the self-host quickstart (MUL-5190) (#5814)
* docs(self-host): add Upgrading section to the self-host quickstart (MUL-5190)

The quickstart had no upgrade section — the only mention of upgrades was a
side note that migrations run automatically. Community users had to be walked
through it by hand every time, and the two circulating recipes (`make selfhost`
vs bare `docker compose pull && up -d`) were never documented as equivalent.

Adds an `Upgrading` section between Step 7 and the Kubernetes section covering:

- both command forms, and that `make selfhost` is a wrapper around them
- what `git pull` actually updates (the compose file, not the image version)
- the `MULTICA_IMAGE_TAG` pinning trap — pinned `.env` silently blocks upgrades
- `.env` is only generated when missing, never overwritten
- Postgres backup before upgrading
- migrations run on backend startup, with the log command to watch them
- verify with `/readyz`, not `/health` (liveness passes on failed migrations)
- cross-reference to the existing Kubernetes upgrade path instead of duplicating

Synced to the zh / ja / ko translations.

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

* docs(self-host): fix upgrade guidance for k8s pull policy and pg_dump (MUL-5190)

Review follow-ups on the new Upgrading section.

1. The Kubernetes upgrade path was wrong. Both the new section and the
   pre-existing Kubernetes section presented `kubectl rollout restart` as a way
   to pull new images, but the chart ships `pullPolicy: IfNotPresent`
   (deploy/helm/multica/values.yaml), so a node that already cached the tag
   reuses the old image and the restart silently changes nothing — the same
   failure mode as a pinned MULTICA_IMAGE_TAG. `helm upgrade` with an updated
   `images.*.tag` is now the primary path; the floating-tag route is documented
   only with the `pullPolicy: Always` prerequisite it requires.

2. The Postgres backup masked `pg_dump` failures. `pg_dump … | gzip > out.gz`
   takes the pipeline's exit status from gzip, so a failed dump exits 0 and
   leaves a valid, empty 20-byte archive. Now redirects to a file first so
   pg_dump's own status gates compression via `&&`, with a callout explaining
   why.

3. Dropped the `Makefile:79-107` / `Makefile:80-95` line references. The
   selfhost target actually spans 79-133 and does more than pull + up (creates
   .env when missing, waits on /health, prints a status summary). Reworded to
   "same result on an existing install" and referenced the target by name so
   the claim cannot drift with line numbers.

Applied across all four language versions.

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

---------

Co-authored-by: Bohan-J <bohan@devv.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-23 11:21:19 +08:00
Bohan Jiang
0d8a7ce5c2 fix(inbox): drop the agent activity hover card from inbox rows (MUL-5189) (#5813)
* fix(views): raise hover delay on the agent activity badge

The per-issue agent activity badge sits on the right edge of dense
scrolling lists (inbox rows, issue rows, board cards) and appears on
every issue an agent currently touches. Base UI's 600ms default opened
the 288px activity card on pointer travel rather than on intent.

Raise the open delay to 900ms and drop the close delay to 150ms. The
card body is read-only, so there is no hover bridge to protect.

MUL-5189

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

* fix(inbox): drop the agent activity hover card from inbox rows

The badge already shows who is running and whether they are working or
queued. On a triage surface the card's only incremental fact is elapsed
time, which never changes the one decision an inbox row supports — do I
open this? The row also carries the ActorAvatar hover card on the left,
so a second popup was mostly noise.

Add an opt-out `hoverCard` prop to IssueAgentActivityIndicator and pass
false from the inbox row. Issue lists and board cards keep the card:
monitoring work in flight is what those views are for, and they keep the
900ms dwell delay from the previous commit.

MUL-5189

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

---------

Co-authored-by: Bohan-J <bohan@devv.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-23 11:11:00 +08:00
Bohan Jiang
168620fc15 MUL-5163 fix(agents): rebind the Agent Builder carrier when the runtime is switched (#5780)
Switching the runtime mid-conversation in Build with AI only updated local React state, so the picker could show runtime B while every subsequent message still executed on the runtime frozen at session create time.

- Add PATCH /api/agent-builder/sessions/{id}/runtime to rebind the hidden builder carrier (runtime_id/runtime_mode, model cleared since model ids are per-runtime). Creator-only, builder carriers only, target must be in-workspace, usable by the member, and online; a reply in flight returns 409.
- Serialise rebind against send: both take LockChatSessionForRuntimeBind on the chat_session row and SendDirectChatMessage re-reads the agent inside that transaction, so a send blocked behind a rebind cannot resume and stamp its task with the runtime the switch moved away from.
- Leave chat_session.runtime_id stale on purpose so the daemon starts a fresh provider session on the new runtime while Multica-side history and the draft survive.
- Frontend updates the draft only after the server reports the bound runtime, blocks sending during a rebind, disables the Mine/All filter alongside the trigger, and explains why the picker is locked during a pending reply.

Closes #5773
2026-07-23 10:56:53 +08:00
Jiayuan Zhang
2c749ddf9f fix(runtime): clarify external background work (#5803) 2026-07-23 02:43:17 +08:00
Jiayuan Zhang
ba0b711787 chore(ci): run test-go.sh self-test in Go test entry points (MUL-5169) (#5802)
Co-authored-by: Lambda <lambda@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-23 02:27:11 +08:00
YYClaw
36533bbc2b fix(test): prevent agent CLI execution in default tests (#5789) 2026-07-23 01:59:06 +08:00
Jiayuan Zhang
a61a8ecfed feat(onboarding): merge About-you step, collect source after agents deliver value (#5786)
Flow drops from five steps to three: role + use_case merge into a
single About-you screen (one Skip covers both; Continue stamps skip
markers on whichever group was left unanswered), and the source
question leaves onboarding entirely.

Source is now collected only by the workspace source-backfill prompt,
which additionally waits until agents/squads have completed at least
SOURCE_BACKFILL_MIN_AGENT_DONE_ISSUES (3) issues in the workspace —
attribution is asked after Multica has visibly delivered value, not
before. The count rides a limit:1 issues query keyed under
issueKeys.all so realtime invalidations keep it fresh, enabled only
for users who still owe an answer.

Server: questionnaire complete() narrows to role + use_case so the
funnel step doesn't stall on the now-deferred source; a new
metrics-only onboarding_source_submitted event (+ Prometheus counter)
tracks the backfill prompt's answer/decline transition once per user.

Co-authored-by: Lambda <lambda@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-23 01:57:44 +08:00
Jiayuan Zhang
257e5e4363 fix(desktop): make dev diagnostics best effort (MUL-5148) (#5794)
* fix(desktop): make dev diagnostics best effort

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

* test(desktop): cover the destroyed dev-log sink guard

---------

Co-authored-by: Lambda <lambda@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-23 01:51:45 +08:00
Jiayuan Zhang
e0e9383efe Improve UI animation transitions (MUL-5172) (#5718)
* Improve UI animation transitions

* Remove implementation plan documents

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

---------

Co-authored-by: multica-agent <github@multica.ai>
2026-07-23 01:38:16 +08:00
Jiayuan Zhang
f2ef7ab0d1 MUL-5127: show issue agent activity in Inbox (#5737)
* feat(inbox): show agent status in list (MUL-5127)

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

* fix(inbox): show issue agent activity (MUL-5127)

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

---------

Co-authored-by: Lambda <lambda@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-23 01:32:43 +08:00
Jiayuan Zhang
d4391fda4d fix: preserve new issue draft options (#5770)
Co-authored-by: Lambda <lambda@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-23 01:31:25 +08:00
Jiayuan Zhang
e8746c5030 fix(onboarding): eliminate Desktop runtime-step false-negative flash + parallelize daemon version detection (MUL-5119) (#5756)
* perf(daemon): parallelize runtime version detection during registration (MUL-5119)

Registration probed each agent CLI's `--version` serially, so total latency
was the sum of every probe. On an onboarding host with several coding tools
installed that stacked into many seconds before runtimes registered — long
enough that the desktop runtime step timed out into its empty 'no runtime
found' state while the daemon was still working.

Fan the probes out with a bounded errgroup so total latency tracks the slowest
single probe instead of their sum. Each probe still self-heals a vanished
pinned path and re-detects the live version (no cross-registration caching, so
an in-place upgrade is still reported correctly); failures are logged and
skipped as before. Results are sorted by provider for a deterministic payload.

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

* fix(onboarding): stop the runtime step flashing 'no runtime found' while the daemon probes (MUL-5119)

The runtime step flipped from scanning to the empty 'no runtime found' state on
a fixed 5s wall-clock, so a machine that does have coding tools installed saw a
false-negative flash whenever registration outlasted the timeout (cold start,
slow/wedged CLI, many CLIs).

Gate the empty flip on a desktop-only `runtimesPending` signal derived from the
local daemon's live status (booting, or running with agent CLIs detected on the
host): while pending, keep the scanning skeleton past the soft timeout. An
absolute hard-timeout ceiling still guarantees a fallback so a wedged probe
can't pin the step on the skeleton forever. Web omits the signal and keeps the
plain wall-clock timeout.

Also drop the two dead/duplicated affordances on the step: the permanently
disabled 'Start exploring' button now renders only in the found phase, and the
empty state's duplicate footer 'Skip for now' is removed in favour of its own
Skip card.

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

---------

Co-authored-by: Lambda <lambda@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-23 01:29:47 +08:00
Jiayuan Zhang
4d74db89cb feat(issues): richer sub-issue rows in issue detail (MUL-5098) (#5721)
* feat(issues): richer sub-issue rows in issue detail (MUL-5098)

The sub-issues panel showed only status, identifier, title and assignee —
priority, due dates, labels, live agent activity and nested breakdowns
were invisible without opening each child.

- SubIssueRow now shows priority (checkbox-slot swap like list rows),
  the agent-activity indicator, label chips (+n overflow), the child's
  own done/total progress ring, and an inline-editable due date with
  overdue emphasis (muted when the child is done/cancelled)
- Right-click opens the shared issue actions menu via a section-level
  IssueContextMenuProvider — parity with list/board surfaces
- ListChildIssues + ListChildrenByParents now bulk-load labels
  (same labelsByIssue pattern as the other list endpoints)
- patchIssueLabels patches per-parent children caches;
  invalidateIssueLabelDerivatives refetches the Map-shaped batched
  children caches so label changes stay live everywhere

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

* feat(issues): customizable property display for sub-issue rows (MUL-5098)

The enriched sub-issue rows were a fixed field set — no way to trim
them or surface workspace custom properties.

- New user-level persisted preference (useSubIssueDisplayStore):
  built-in field toggles (priority / labels / sub-issue progress /
  due date / assignee) + opted-in custom property ids. Defaults match
  the previous fixed layout, so existing users see no change.
- SubIssueDisplayPopover on the section header — same switch-row
  interaction as the main views' Display panel, reusing its card_*
  locale keys (no new translations needed).
- Rows render opted-in custom property chips (PropertyIcon +
  CustomPropertyValueDisplay, list-row parity) only when the child
  carries a value; ids resolve against live non-archived definitions,
  so foreign-workspace or archived ids are inert.

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

* fix(issues): reconcile sub-issue cache updates

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

---------

Co-authored-by: Lambda <lambda@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-22 22:43:34 +08:00
YikaJ
fcb370edfd fix(squads): align parent issue status with agent-managed model (MUL-5156) (#5758)
* fix(squads): align parent issue status ownership with agent-managed model

Squad leaders now open assigned parents to in_progress on first dispatch, keep them there while members work, and only move to in_review when overall completion is confirmed—matching ordinary agent status semantics without server auto-flips.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(squads): scope leader parent-status ownership to squad-assigned issues

Review follow-up on the parent-status alignment change. Two boundaries were
left ambiguous, both of which the change's own premise ("don't make the model
resolve a contradiction in the prompt") argues should be closed in-place.

1. Status ownership was granted too widely. The leader briefing is injected on
   every leader path, keyed off is_leader_task — including the MUL-3724 case
   where an issue is assigned to a plain agent and a squad was merely
   @mentioned for help. The unqualified "Own the parent issue status"
   responsibility therefore also reached guest leaders, who could push another
   assignee's in-flight issue to in_review.

   buildSquadLeaderBriefing now takes ownsIssueStatus and selects between two
   variants of responsibility 6: the grant only when the issue's assignee is
   this squad, otherwise an explicit "do NOT change this issue's status".
   Quick-create passes false — no issue exists on that turn. Everything else in
   the protocol (roster, delegation, evaluation) is unchanged for both.

2. The comment-triggered path still contradicted itself. The runtime brief says
   "do not change status unless the comment explicitly asks", and a member's
   delivery comment never asks. Squads that dispatch by @mention create no
   child issues, so no child-done system comment exists to carry the explicit
   ask either — that parent would sit in in_progress indefinitely.
   writeWorkflowComment now names the protocol responsibility as the one
   exception for squad leaders. It is safe to state unconditionally because the
   grant is only present in the instructions when the server decided this squad
   owns the issue; for a guest leader the sentence has nothing to activate.

Tests: two composition tests assemble both halves (server-side briefing +
daemon-side CLAUDE.md) for one real scenario each, since asserting each half
alone is how the original contradiction shipped. Plus execenv coverage that the
carve-out appears only for leaders and the ordinary-agent rule stays absolute.

Docs and the multica-squads skill / source map record the narrower contract.

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

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: J <j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-22 20:33:15 +08:00
Bohan Jiang
dd45f30553 MUL-5164: refactor(issues): drop the table toolbar loaded-count and structure-paused copy (#5778)
* test(issues): repair table-view editing tests orphaned by the grouping revert

Reverting "Move issue table grouping to the server" (#5777) removed the
server-driven `serverIssues` fixture, but two tests still assigned to it,
so packages/views typecheck and vitest have been failing on main. Pass the
issue through the Harness `issues` prop like the sibling tests do.

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

* refactor(issues): drop the table toolbar loaded-count and structure-paused copy

The table toolbar rendered a permanently visible "Loaded X of N" counter
plus a long "Grouping and hierarchy are paused — ..." notice. Neither is
worth the horizontal space it took between search and Export, so remove
both strings and their locale keys.

The load-failure Retry button stays: it only renders on a failed window
fetch and is the explicit resume path for the infinite-scroll sentinel.

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

---------

Co-authored-by: J <agent@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-22 18:40:02 +08:00
Jiayuan Zhang
f6902a5f5b chore(onboarding): remove legacy agent & first-issue steps (#5774)
The "Create your first agent" and "first issue" onboarding steps were
dropped from the in-flow sequence (helper-agent creation moved to the
post-onboarding workspace shell), but their code was left behind. Remove
the now-dead residue:

- Delete unused step components `step-agent.tsx` and `step-first-issue.tsx`
  (not referenced or exported anywhere).
- Delete `recommend-template.ts` (+ test) and drop its core export — it
  was consumed only by `step-agent.tsx`.
- Drop the dead `agent` / `first_issue` members from the `OnboardingStep`
  union.
- Remove the orphaned `step_agent` and `first_issue` i18n sections across
  all four locales (en / zh-Hans / ja / ko); parity test stays green.
- Fix a stale doc path in `step-order.ts` (welcome-after-onboarding.tsx).

No behavior change: the live flow is welcome → source → role → use_case
→ workspace → runtime. Typecheck (core/views/web/desktop) and onboarding
+ locale-parity tests pass.

Co-authored-by: Lambda <lambda@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
v0.4.8
2026-07-22 18:16:41 +08:00
LinYushen
4dc47ef113 Revert "MUL-5100: Move issue table grouping to the server" (#5777)
This reverts commit d43e500ff6.
2026-07-22 18:06:37 +08:00
Bohan Jiang
1157d3f139 fix(repocache): repair promisor config on isolated checkouts of a partial cache (#5772)
The isolated-checkout path used by Linux Codex builds a task-local
repository with `git clone --local` from the workspace's bare cache.
That command has two properties that combine badly when the cache is a
partial clone: it does not carry the promisor remote configuration
across, and it does not treat an incomplete source object store as an
error. The result is a checkout that exits 0 with every tracked file
reported as deleted, so an agent starts work in what looks like a
repository someone emptied.

Swap origin to the real remote and restore
`remote.origin.promisor` / `remote.origin.partialclonefilter` before the
first checkout, so git can lazily fetch the blobs it needs. Do the same
on the reuse path, where a workdir created against a complete cache can
later be resumed against a partial one.

No cache is created as a partial clone today, so this changes nothing
for existing installs; it is a prerequisite for the on-demand clone mode
in MUL-4983 and hardens a path that fails silently rather than loudly.

Co-authored-by: Bohan-J <bohan@devv.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-22 18:00:39 +08:00
Multica Eve
67240cea46 docs(changelog): add v0.4.8 release entry (2026-07-22) (#5769)
Co-authored-by: Eve <eve@multica-ai.local>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-22 17:55:08 +08:00
Jiayuan Zhang
15326c5090 fix(issues): open table issues in new tabs (#5767)
Co-authored-by: Lambda <lambda@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-22 17:03:04 +08:00
Multica Eve
216aee5629 [MUL-5125] Add daily Desktop/Web usage and runtime reporting (#5763)
* feat(analytics): add daily client usage reporting (MUL-5125)

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

* fix(analytics): clarify daily usage semantics (MUL-5125)

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

* fix(analytics): resolve usage review blockers (MUL-5125)

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

---------

Co-authored-by: Eve <eve@multica-ai.local>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-22 16:52:34 +08:00
Jiayuan Zhang
c6fb2c5c50 feat(agents): assign emoji avatars by default (#5764)
Co-authored-by: Lambda <lambda@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-22 16:38:50 +08:00
Jiayuan Zhang
5d9295ac65 feat(agents): add per-agent runtime skill controls (#5686)
* feat(agents): add per-agent runtime skill controls

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

* fix(agents): renumber runtime-skill migration and broadcast agent:status on toggle

Address the MUL-5101 review blockers on PR #5686:

- Rebase onto main and renumber the runtime-skill-disable migration
  202 -> 203. main added 202_runtime_profile_add_qwen, so the pair
  collided on prefix 202 and migrations_lint_test would reject the
  duplicate. 203 is the next free prefix.
- Publish an "agent:status" event after persisting a
  disabled_runtime_skills override, mirroring the workspace-skill toggle
  in writeUpdatedAgentSkills. The realtime layer keys off this event to
  invalidate workspaceKeys.agents, so other open web/desktop/mobile
  clients now drop their stale toggle state instead of only the
  initiating tab refreshing. Reload junction-table skills before the
  broadcast so it doesn't signal cleared skills (#3459).
- Add a handler regression test proving the broadcast fires on both
  disable and enable.

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

---------

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>
2026-07-22 15:38:38 +08:00
Bohan Jiang
4233e82441 fix(skills): tree-first skill import to stop 504 on large repos (MUL-5136) (#5753)
Rework skills.sh and github.com skill imports around a single recursive
git-tree fetch to stop the 504 on large mono-repos (e.g. api-gateway-skill):

- One tree call replaces the per-directory contents crawl.
- Import caps checked arithmetically from tree metadata before any download
  (fail fast with 413 instead of timing out).
- Most-specific skill-dir resolution; repo root only as a last resort, which
  fixes the root SKILL.md name collision.
- Concurrent downloads (errgroup, limit 8).
- Overall 45s fetch deadline; cancellation is fatal on every supporting-file
  path (tree downloader, crawl listing/recursion/download, ClawHub) so a
  mid-download abort never persists a half-populated bundle.
- A skills.sh tree-fetch failure returns a retryable 503 instead of an unsafe
  root-directory fallback.
- Lenient conventional-path acceptance restored for both complete and truncated
  trees.
- maxImportFileCount 128 -> 256 (aligned daemon cap); 8 MiB bundle cap remains
  the real guard.
2026-07-22 14:38:31 +08:00
Jiayuan Zhang
d43e500ff6 MUL-5100: Move issue table grouping to the server
Merge approved after review; CI checks are green.
2026-07-22 14:36:51 +08:00
Rusty Raven
3171e6607f MUL-5141: fix(agent): inject --yolo in Qwen headless runs so shell/edit/write tools are available (#5752)
* fix(agent): inject --yolo in Qwen headless runs so shell/edit/write tools are available (Fixes #5743)

Qwen Code's non-interactive mode (`-p … --output-format stream-json`) uses a
fail-closed approval policy: it silently drops `run_shell_command`, `edit`,
`write_file`, and `monitor` from the tool registry unless bypass mode is
active.  Every other Multica-supported coding adapter already injects its
equivalent permission flag as a daemon-owned argument (e.g. Claude uses
`--permission-mode bypassPermissions`, Grok uses `--always-approve`, Qoder
uses `--yolo --acp`).  Qwen was the only exception.

Changes:
- `buildQwenArgs`: append `--yolo` after the protocol flags and before any
  custom args so headless daemon runs always receive the full tool set.
- `qwenBlockedArgs`: add `--yolo`, `-y`, `--approval-mode`, and
  `--allowed-tools` as daemon-owned flags that are stripped from custom_args.
  This prevents users from accidentally or intentionally disabling bypass mode
  or narrowing the allowed tool set via per-agent settings.  `--exclude-tools`
  is intentionally left unblocked so users can still hard-deny specific tools.
- `TestBuildQwenArgsKeepsProtocolManaged`: extend with the new blocked flags
  and assert daemon-owned `--yolo` appears exactly once.
- `TestBuildQwenArgsYoloAlwaysPresent`: new test asserting `--yolo` is present
  even when `ExecOptions` carries no custom args.

* fix(agent): correct Qwen permission args and docs

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

---------

Co-authored-by: Eve <eve@multica-ai.local>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-22 13:35:59 +08:00
Jiayuan Zhang
fe12278863 feat(issues): add table sub-issue action (#5738)
Co-authored-by: Lambda <lambda@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-22 05:33:18 +00:00
chenow9
d804dedcc6 MUL-5137: fix(agent): parse Grok ACP token usage from session/prompt _meta (#5748)
* fix(agent): parse Grok ACP token usage from session/prompt _meta

Grok Build places per-turn metering under result._meta (and
_meta.usage), not the top-level ACP usage field. Multica's shared
parser only read result.usage, so Grok tasks recorded empty token
usage and cost dashboards stayed at zero.

Fall back to _meta.usage (then flat _meta counters) when top-level
usage is absent. Prefer standard top-level usage when both are
present. Update the Grok fake ACP fixture and add regression tests
against the live 0.2.x payload shape.

* test(agent): cover zero ACP usage meta fallback

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

---------

Co-authored-by: Eve <eve@multica-ai.local>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-22 12:53:49 +08:00
Binteng
8355067723 fix(mobile): prevent hidden My Issues refresh spinner (#5717) 2026-07-22 12:40:55 +08:00
Multica Eve
2795cef41b fix(issues): refresh sub-issues on detail mount (#5750)
Co-authored-by: Eve <eve@multica-ai.local>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-22 12:21:57 +08:00
Naiyuan Qing
2f111037d2 feat(desktop): tab presentation by object identity (MUL-4370) (#5661)
* fix(nav): derive route icons from the URL across all nav surfaces (MUL-4370)

The same route rendered different icons in the sidebar and the desktop tab
bar because the mapping was maintained in three places. Projects had no tab
icon at all; autopilots/chat/squads/usage fell back to ListTodo.

Establish one contract instead: `@multica/core/paths` maps a route segment to
a stable icon *name* (React-free), and `@multica/views/layout` maps that name
to a Lucide component. Every nav surface — sidebar, desktop tab bar, and the
search palette — resolves through `routeIconForPath(path)`, so a route cannot
render two different icons.

Crucially the icon is now derived, not stored. `TabSession.icon` is removed,
so persisted tab state can no longer hold a stale icon name: a user who had
an /autopilots tab from an older build gets the correct icon after upgrade
rather than the one that was persisted. Legacy `icon` values in v4 payloads
are ignored on rehydration and dropped on the next write.

Builds on the design in #5204 by LiangliangSui.

Tests: stale/unknown/absent persisted icon on rehydration, derived icon
rendering per route in the tab bar, name→component registry totality, and
nav-route icon coverage.

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

* feat(desktop): tab presentation by object identity, not route segment (MUL-4370)

Replace the "route segment → icon" tab mapping with a semantic Tab
Presentation Contract: a tab's leading visual and title are derived live
from its URL + the query cache, so a tab shows *what it points at*, not the
module it lives under.

- core `parseTabSubject(url)` classifies a URL as page / resource / actor /
  container (inbox, chat) / flow / unknown, purely (no React, no Lucide).
- core `resolveTabPresentation(subject, data)` maps that + cached entity data
  to a leading visual (issue StatusIcon, ProjectIcon, ActorAvatar, or a type
  icon) and a title spec. Exhaustive: a new route forces an explicit choice.
- views `useTabPresentation` reads the cache (enabled:false, no fetch from the
  tab bar) and `ResourceLeadingVisual` renders it in a fixed 16×16 slot.
- Containers keep their icon; only the title tracks the selection
  (`/inbox?issue=`, `/chat?session=`), so an inbox-opened issue reads
  differently from a direct issue tab.
- Titles are plain text; the project 📁 and autopilot  glyphs are dropped
  from document.title.
- Pin no longer replaces the resource visual. Persisted `tab.icon` cleanup
  from the prior revision is kept; the active tab persists its resolved title
  as a first-frame fallback (the document.title→observer path is removed).

Supersedes the route-segment approach in #5204 per the agreed PRD. No schema,
API, or migration changes; reuses existing queries/caches.

Tests: table-driven parseTabSubject over every desktop route; the
URL/data → visual+title matrix incl. pending/loading, containers, unknown,
runtime custom-name; views cache-integration; tab-bar pin + active-tab
title persistence.

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

* fix(desktop): archived inbox title sync + attachment filename in tab (MUL-4370)

Addresses two PRD gaps from review:

1. Archived Inbox selection now syncs the tab title. `parseTabSubject` captures
   `?view=archived` on the inbox subject, and the presentation hook resolves the
   selection against `archivedInboxListOptions` (its own cache, the one the
   InboxPage populates) instead of only the active list. An
   `/inbox?view=archived&issue=<id>` tab now shows the archived item's title —
   issue (`identifier: title`) or non-issue (display title) — and, being purely
   URL+cache derived, restores correctly on refresh. Previously it fell back to
   "Inbox" and persisted that wrong title.

2. Attachment tabs use the filename. `parseTabSubject` captures the `?name=`
   the preview route already carries; the resolver shows the filename as the
   title and picks a file-type icon from its extension (image/video/audio/
   archive/code/text), falling back to the generic File glyph + "Attachment"
   label only when the name is missing.

Tests: parseTabSubject archived/name cases; the presentation matrix for
attachment filename/extension + missing fallback and iconForAttachment edge
cases; views cache-integration for archived issue/non-issue selection (must
resolve against the archived list, not the active one) and attachment filename.

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

---------

Co-authored-by: multica-agent <github@multica.ai>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 09:36:53 +08:00
Naiyuan Qing
8644423124 fix(chat): 恢复 chat 输入框的 bubble menu 以支持清除格式 (MUL-5106) (#5727)
Chat 输入框是共用的 Tiptap 编辑器,`# ` input rule 与 Markdown/HTML
粘贴都会把行变成标题,但此前 chat 传了 showBubbleMenu={false},关掉了
唯一能把标题降回正文("Normal text" / setParagraph)及切换其它格式的
浮动工具栏,导致格式一旦产生就无法去掉。

组件本就暴露了 showBubbleMenu 配置能力,这里改为启用即可,和 Linear 的
交互一致;attach、@ 提及、图片/文件等内联能力不受影响。

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-22 09:20:36 +08:00
Bohan Jiang
6ced54b44a fix(agent/codex): retry once when the model catalog refresh blocks the first turn (MUL-5110) (#5734)
Retries once when Codex's model catalog refresh blocks the first turn, with a narrow safety gate: first-turn no-progress timeout, zero semantic progress, catalog-refresh evidence in stderr, and a confirmed-reaped process tree. Clears ResumeSessionID on retry so a stalled thread is never resumed, and buffers the leading session pin so a discarded attempt cannot pin the resume pointer.
2026-07-21 21:26:11 +08:00
Bohan Jiang
a5a42846e6 fix(daemon): retry with a fresh session only when the resume was actually rejected (MUL-4966) (#5715)
* fix(daemon): gate fresh-session retry on tools executed, not session id (MUL-4966)

Switching provider accounts leaves the stored session id pointing at a
conversation the new account does not own. The daemon still passes it to
--resume, the provider rejects it, and the task dies before doing any work.

The existing fresh-session fallback was supposed to catch this but was
gated on `result.SessionID == ""`, which is not a lifecycle fact:

- Too narrow: a backend that echoes the requested id back when it rejects
  a resume keeps SessionID non-empty, so the fallback never fired — the
  reported bug.
- Too broad: a provider 401 before the first stream message also leaves
  SessionID empty, so an unrecoverable auth failure burned a second full
  run.

Gate on `tools == 0` instead. That states the property that actually makes
a retry safe — the agent executed no tool, so it mutated nothing, so
re-running cannot double-post a comment (comment creation has no
idempotency key and a duplicate re-fires its @mention triggers), reopen a
PR, or re-plan on top of its own half-finished work in the reused workdir.
Auth failures are excluded, mirroring retryableReasons in service/task.go.

The predicate is extracted to shouldRetryWithFreshSession so the tests
exercise production logic; both existing fallback tests re-implemented the
condition inline and would not have caught a regression in it.

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

* fix(daemon): gate fresh-session retry on a positive resume-rejected signal (MUL-4966)

Review of the previous commit was right: `tools == 0` plus "not an auth
error" answers whether re-running is *safe*, not whether a new session can
*fix* the failure. Those are orthogonal, and answering the second by
exclusion inverts the burden of proof — the failures a fresh session cures
are a small enumerable set, while the ones it cannot are open-ended.

Concretely, the previous predicate fresh-retried on provider_network,
429/529, quota, 5xx and unclassified startup failures. provider_network is
the sharpest conflict: internal/service/task.go marks it resume-safe
(MUL-4910) specifically so the platform retry inherits the session and
continues the truncated conversation. Resetting the session first made that
contract unsatisfiable, silently discarding conversation context on a
transient blip — and rate limits got an immediate no-backoff re-run.

Replace the inference with positive evidence: agent.Result gains an explicit
ResumeRejected field, set only when a backend has proof the resume itself
was refused. claude/codebuddy/qwen derive it from resumeWasRejected, which
promotes the predicate resolveSessionID was already computing and encoding
as the side effect of blanking SessionID — using an empty string to carry
that meaning is what made the original bug possible. SessionID keeps being
dropped for a rejected resume (a dead pointer must not be persisted), but it
is no longer the signal the daemon reads to decide *why* a run failed.

The six ACP backends that recover from "session not found" set the flag at
the same points they already clear the id, so their existing recovery is not
caught by the narrower gate. codex needs nothing: thread/resume already falls
back to thread/start in-process, and deliberately does not on transport
errors.

Matching now includes the account-switch guardrail reported in #5704
(Claude Code 2.1.207, zh-CN): "400 此 session 已绑定另外的ai账号,请执行
/new 开启新 session". The en-US wording of the same guardrail has not been
captured yet, so those variants are marked inferred in the source; a miss
degrades to a terminal failure carrying the provider's raw text rather than
a mis-routed run.

Tests: backend-level fixtures drive ResumeRejected from real stream-json for
both the account-binding 400 and a network drop, and the predicate now
covers network/rate-limit/quota/5xx/auth/unclassified as explicit
non-retries.

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

* fix(daemon): restore fresh-session recovery for backends with no rejection signal (MUL-4966)

Final review caught qwen regressing: its verified rejection string
("No saved session found with ID ...", already captured in
testdata/qwen-code-0.20.0-resume-not-found.stderr.txt) was not in the phrase
list, and qwen reports no session id on that path, so the new inclusion gate
turned a working auto-recovery into a terminal failure.

Auditing the other 17 resume-capable backends showed qwen was not alone.
antigravity, copilot, cursor, deveco and opencode all recovered from a
refused resume purely by reporting an empty SessionID, and none of them has
any rejection detection to convert into ResumeRejected — copilot's own
comment documents the hole (session.error before session.start), and
antigravity's helper returns "" when "the CLI exited before dispatching".
Making ResumeRejected the sole gate silently removed recovery from all five.

Fixing that by guessing rejection phrases for five more CLIs is the wrong
trade: no real output has been captured for any of them, and a false
positive discards a recoverable session pointer. So the gate is now two
tiers. Positive evidence (ResumeRejected) decides on its own where a backend
can produce it. Where none is available, an empty SessionID still gates the
retry — it proves no session was established, which is exactly what the
pre-change behaviour relied on — minus the classes a fresh session provably
cannot cure (network, rate limit, quota, provider 5xx, auth). That keeps the
resume-safe contract in internal/service/task.go intact while restoring what
these five backends had.

Also renames claudeResumeRejectedPhrases to resumeRejectedPhrases: it is
matched by claude, codebuddy and qwen, so a qwen-only string living under a
claude-prefixed name would be actively misleading.

Tests: qwen's existing missing-resume fixture now asserts ResumeRejected
(verified failing without the phrase), and the predicate covers the
no-signal tiers — retry when nothing was established, no retry once a
session exists or the failure classifies as uncurable.

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

* fix(daemon): scope the no-signal fallback to backends that cannot detect rejections (MUL-4966)

Final review caught the compatibility path applying to every backend, not
just the five it was justified for. shouldRetryWithFreshSession only saw
(Result, priorSessionID, tools), so a false ResumeRejected could not be told
apart from a backend that has no way to answer — and claude/codebuddy/qwen/ACP
startup failures with no session id still fell through to the exclusion
branch. That contradicted both the stated intent and the function's own doc
comment ("where a backend can produce it, it is the whole answer").

Make the capability explicit. agent.ResumeRejectionUndetectable names the
five backends that scrape SessionID out of stream output and have no
rejection detection at all; the daemon takes provider and consults it, so a
capable backend reporting false is now taken at its word. Membership is
opt-in, so a new backend fails closed instead of silently inheriting a
guess-based retry.

Also completes the exclusion set: missing config, unavailable model, missing
executable, unsupported runtime version and (defensively) agent timeout all
have defined non-session remedies and were reaching `default: true`. What is
left through stays narrow — unknown, process failure, unparseable output,
context overflow — because a real rejection from these five most likely
surfaces as a non-zero exit or unparseable output, none of them reporting one
explicitly.

Tests: one identical result asserted across all five undetectable backends
(retries), twelve capable ones (no retry), and an unregistered provider
(fails closed), plus table cases for each newly excluded reason. Classifier
inputs were verified to map to the intended reasons rather than passing by
accident.

Also updates the ResumeRejected doc comment, which still said the daemon
gates on it alone.

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

---------

Co-authored-by: Bohan-J <bohan@devv.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-21 19:07:46 +08:00
Jiayuan Zhang
1a37063bba fix(issues): keep table cell editors open across refreshes; title click opens the issue (MUL-5108) (#5730)
* fix(issues): keep table cell editors open across refreshes; title click opens the issue (MUL-5108)

Table view interaction fixes:

- Cell/header renderers are now stable module-level components reading
  data via table.options.meta. flexRender mounts function cells as
  component types, so the per-render closures rebuilt on every data
  refresh (new childProgressMap / property / actor identities) remounted
  every cell and closed any open picker popup.
- One hoisted editingCellKey drives all cell editors (controlled open),
  and while an editor is open the row structure renders from a frozen
  snapshot (live issue values, frozen order) so window materialization,
  hierarchy assembly, and realtime reorders cannot move or unmount the
  popup's anchor row mid-interaction. Structure catches up on close.
- Clicking an issue title now opens the issue (it previously started
  inline rename despite the link-style hover). Renaming moved to a
  hover pencil affordance; dead space in the title cell navigates too.

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

* fix(issues): address table editor review — blur-click nav, virtual-unmount key release, deterministic test (MUL-5108)

Follow-up to the MUL-5108 table fixes, per code review on PR #5730:

- R1#2 (P1): committing a rename by clicking away also navigated into the
  issue. onBlur flips `editing` off synchronously before the commit-click
  lands, stripping the click-time guard so the click bubbled to row
  navigation. Record whether the gesture began while editing (mousedown,
  before blur) and swallow that click in the capture phase, before it can
  reach the row or the title's open handler. Dead-space clicks while not
  editing still open the issue. New blur-click regression test.

- R1#3 (P2): the hoisted editingCellKey (and the frozen row structure keyed
  off it) never cleared when row virtualization unmounted the anchor cell —
  Base UI does not fire onOpenChange(false) on unmount, so the table stayed
  frozen and the picker silently reopened / dropped the rename draft on
  scroll-back. Add useReleaseEditingCellOnUnmount: an unmount responder that
  releases the key iff the unmounting cell still owns it. Focused tests.

- R1#1 (P1): the new integration test exceeded the 5s default under
  concurrent CI worker load (real-timer gaps between userEvent steps). Drive
  userEvent with delay:null and give the heavy full-mount test explicit
  headroom so it is deterministic.

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

---------

Co-authored-by: Lambda <lambda@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-21 18:47:14 +08:00
Bohan Jiang
f72e17e236 docs(project-resources): frame local_directory as an escape hatch, not a default (#5732)
The local_directory section read as a neutral alternative to github_repo,
so users with ordinary clonable repos were picking it and then asking for
worktree-based parallelism — which contradicts the mode's whole premise.

- Add an up-front warning: this exists for people with no other option
  (tens-of-GB game checkouts), and github_repo gives unlimited concurrency.
- Replace the "fine-grained changes, review locally" recommendation with
  the real qualifying cases (clone too expensive, not a git repo at all,
  machine-local state) plus an explicit list of non-reasons.
- State that serial execution is a premise of the mode, not a limitation
  pending a worktree fix.
- Add a pointer at the top of "Attaching a local directory".

Applied to en / zh / ja / ko.

Co-authored-by: Bohan-J <bohan@devv.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-21 18:11:36 +08:00
Jiayuan Zhang
b1d5fa4ff7 fix(auth): remove OR divider from the sign-in card (MUL-5105) (#5722)
Co-authored-by: Lambda <lambda@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
v0.4.7
2026-07-21 17:40:29 +08:00
Multica Eve
365a2287a1 fix(runtimes): normalize Qwen logo import for desktop (#5728)
Co-authored-by: Eve <eve@multica-ai.local>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-21 17:37:25 +08:00
Multica Eve
b03e118aa7 docs(changelog): add 0.4.7 release entry (MUL-5111) (#5724)
Co-authored-by: Eve <eve@multica-ai.local>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-21 17:32:24 +08:00