mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-28 22:17:48 +02:00
v0.4.6
1447 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a56f41cf03 |
feat(projects): switch project list view control to a dropdown menu (MUL-5030) (#5677)
Align the project list view control with the issue list: instead of a button that flips table <-> cards on click, the trigger now opens a dropdown menu with a 'View' header and a radio group of view modes (Table / Cards). Using DropdownMenuRadioGroup keeps it trivial to add future view modes as new menu items. Co-authored-by: Bohan-J <bohan@devv.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
d3fac023c9 |
feat(issues): live-resort board/list on updated_at drift (MUL-5016) (#5671)
* feat(issues): live-resort board/list on updated_at drift (MUL-5016) An open Kanban board / list sorted by "Updated date" did not re-sort in real time when a card's updated_at advanced — it only picked up the new order on the next fetch (navigation, invalidation, etc.). Two triggers now re-sort live: - comment:created: a new comment bumps the parent issue's updated_at server-side (MUL-5009), but the WS handler only invalidated the per-issue timeline cache. It now also invalidates loaded updated_at- sorted issue-list/board/flat keys via invalidateUpdatedAtSortedIssue Lists, so the commented card re-sorts (and an off-window card can surface). Only updated_at-sorted keys are touched. - same-status field edit: applyIssueChange already reconciled flat windows on updated_at-sort drift but the bucketed board only marked stale on membership/status change. It now marks an updated_at-sorted bucketed key stale whenever the patch advances updated_at, mirroring the flat-window rule. Deferred to onSettle for mutations (WS flushes immediately), respecting the existing timing contract. Extracted patchChangesAnyIssueField shared helper. Tests cover the coordinator drift/targeting and the comment:created wiring. Co-authored-by: multica-agent <github@multica.ai> * fix(issues): cover assignee-grouped boards and property/metadata events in updated_at re-sort (MUL-5016) Addresses Elon's review of #5671. Must-fix 1: invalidateUpdatedAtSortedIssueLists missed assignee-grouped boards. They fold the sort into their filter bag (issueAssigneeGroupsOptions does `{ ...filter, ...sort }`) rather than a standalone sort key, so the old bucketed+flat enumeration skipped them. Replace it with a single predicate invalidateQueries over issueKeys.all(wsId) that matches any query key with an object part carrying sort_by: "updated_at" — covering status boards, flat tables, and assignee-grouped boards (workspace + My Issues) with one rule. Must-fix 2: custom-property and metadata edits also bump issue.updated_at server-side (SetIssuePropertyValue / DeleteIssuePropertyValue / SetIssueMetadataKey / DeleteIssueMetadataKey) but flow through issue_properties:changed / issue_metadata:changed, not applyIssueChange. Their handlers patched cards in place and only invalidated property/My-Issues/ grouped windows, so an updated_at-sorted workspace board or flat table stayed in the old order. Call invalidateUpdatedAtSortedIssueLists from both onIssuePropertiesChanged and onIssueMetadataChanged. Both are committed-only paths (WS event + mutation onSuccess); the optimistic leg still uses patchIssueProperties, so no premature refetch. Tests: grouped-board targeting in cache-coordinator; updated_at vs position re-sort for onIssueMetadataChanged / onIssuePropertiesChanged (incl. the optimistic leg not refetching) in ws-updaters. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Bohan-J <bohan@devv.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
4ad75977eb |
fix(agents): reset model and thinking level on runtime change (MUL-4963) (#5615)
Closes #5614 |
||
|
|
a0ec1da425 |
fix(settings): restore mounted flag on remount so auto-save can settle (#5613)
The auto-save mount effect only cleared `mountedRef` in its cleanup and never re-set it to true on setup. Under React StrictMode the initial setup/cleanup/setup cycle therefore leaves `mountedRef.current` pinned to false for the component's whole life. A successful save then never reaches the terminal branch (`succeeded && mountedRef.current`), so the status is stranded on "saving" and the success toast never fires — the settings save indicator spins forever even though the PATCH returned 200. Set `mountedRef.current = true` on every mount so the flag reflects the live component. Shared by all auto-saved settings (repositories, GitHub, etc.). Add a StrictMode regression test asserting onSuccess still fires. |
||
|
|
41315989bd | fix(editor): prevent incorrect comment autolinks (#5665) | ||
|
|
1be1fd3be1 |
fix(issues): label board display-settings button as Display (MUL-5011) (#5664)
The display-settings trigger showed the current sort mode (e.g. "Manual") as its label, which reads as an unclear button name. Show a fixed "Display" label with the sliders icon instead, matching the existing "Display settings" tooltip. The sort mode remains visible inside the popover's Ordering section. Co-authored-by: Bohan-J <bohan@devv.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
1483ce0825 |
fix(agents): always enable AI creation (MUL-4998) (#5660)
Co-authored-by: Eve <eve@multica-ai.local> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
fae7b046f2 |
feat(views): support the send shortcut in manual issue create (MUL-4931) (#5583)
* feat(views): support the send shortcut in manual issue create (MUL-4931) Agent create has had Cmd/Ctrl+Enter all along; manual create had no submit shortcut at all, in either the title or the description. Reuse the configurable `send` action rather than hardcoding the chord, so a rebound or unbound shortcut follows the user's setting and the IME guards and Shift+Enter replay come along for free: - Description: pass `onSubmit` to ContentEditor, which already carries the extension. - Title: add an opt-in `onSubmitShortcut` to the shared TitleEditor. Plain Enter stays inert there — #5532 removed that trigger a day ago because it created from half-typed titles — and a single-line title has no newline to trade Enter against, so the chord path refuses a plain-Enter `send` binding. Hosts relying on plain-Enter submit (create-project, autopilot-dialog) keep `onSubmit` and are untouched. Also fixes a real double-create: `submitting` is state, so two chord presses in one tick both read the stale value and fired two creates. A ref flips synchronously and single-flights both create panels. Accessibility: the empty-title Create button moves from native `disabled` to `aria-disabled`, so it stays focusable and keyboard/SR users can finally reach the "Enter a title to create" tooltip. Keycaps are decorative, keeping the accessible name "Create Issue". Empty-title chord submits now focus the title instead of silently no-oping. Widen the `send` setting description across all four locales — it claimed to cover only chat, comments, replies, feedback and prompts. Tests: real-ProseMirror coverage for the title chord (every other editor test mocks Tiptap, so nothing verified extension ordering), plus chord/plain-Enter/ empty-title/upload-gate/single-flight/keycaps/aria-disabled cases. The single-flight test dispatches both presses inside one act(); it fails against the old state-based guard. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * test(views): cover quick-create single-flight + aria-disabled visuals (MUL-4931) Review follow-up on #5583. The quick-create ref guard shipped without a regression test, so the claim that both create panels were covered was only true of manual create. That path files a real issue, so a double-fire is a duplicate issue rather than a glitch. Add the same-tick regression: both presses dispatch inside one act(), and it fails against the old state-based guard (expected 1 call, got 2). The Button base only styles native `disabled` (disabled:opacity-50 / disabled:pointer-events-none), so the aria-disabled empty-title button stayed a fully lit, pressable-looking primary. Add local aria-disabled opacity, cursor, and press-animation styles, with no pointer-events-none — that would break the tooltip hover and the click that focuses the title. Verified against compiled Tailwind output: aria-disabled:active:translate-y-0 and the base's active:not-aria-[haspopup]:translate-y-px have equal specificity and the override emits later, so it wins without !important. 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> |
||
|
|
fcb4798f12 |
fix(issues): stop cold-load render loop on the Issues route (MUL-4985) (#5643)
Board and Swimlane crashed with "Maximum update depth exceeded" on first paint of the Issues route. During cold load the member/agent/squad directory queries are unresolved, so useActorName's `= []` defaults allocated a fresh array every render and its `getActorName` memo changed identity each render. BoardView's `groups` (and SwimLaneView's `laneGroups`) list `getActorName` as a dep, so they churned every render and re-fired the column-resync effect without end; react-virtuoso escalated the loop into the reported crash. This predates MUL-4797 — it was exposed when board/swimlane columns were virtualized with a real <Virtuoso>. - Share stable empty references for the loading directory snapshot so getActorName is referentially stable across cold-load renders (root cause). - Equality-guard the shared column setter in useDragSettle so a content-equal rebuild returns the previous reference and cannot spin the resync effect (defense-in-depth). Regression coverage: useActorName stability during cold load; the column-setter equality guard; and Board (40 cards) + Swimlane rendered with the REAL react-virtuoso under pending directories, which reproduce "Maximum update depth exceeded" without the fix and pass with it. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
002ea0d879 |
MUL-4797: add configurable issue table view (#5454)
* feat(issues): add configurable table view Co-authored-by: multica-agent <github@multica.ai> * test(issues): cover table columns in page fixture Co-authored-by: multica-agent <github@multica.ai> * fix(issues): make table column picker interactive Co-authored-by: multica-agent <github@multica.ai> * fix(issues): repair quick create and virtualize table rows Co-authored-by: multica-agent <github@multica.ai> * fix(issues): keep pinned table cells opaque Co-authored-by: multica-agent <github@multica.ai> * fix(issues): anchor full-width table rows Co-authored-by: multica-agent <github@multica.ai> * fix(issues): consolidate table controls Co-authored-by: multica-agent <github@multica.ai> * fix(issues): harden table pagination and export Co-authored-by: multica-agent <github@multica.ai> * feat(issues): add table quick search Co-authored-by: multica-agent <github@multica.ai> * fix(issues): make table window filters, selection, and export authoritative Round-2 review fixes for the issues table (MUL-4797): - Send the agents-working filter as a server ids facet so matches on unfetched pages surface and total/pagination/export agree; a present- but-empty id list yields an empty window instead of an unfiltered one. - Reset surface selection when the membership window changes and act on selection ∩ visible rows in the batch toolbar, so batch actions, Export selected, and the count all share one authoritative set. - Materialize the full flat window while table grouping is active, and suspend hierarchy nesting / parent-based grouping until the window is complete so structure cannot reshuffle as pages arrive; suppress header facet-count badges while the table window is partial. - Resolve actor directories and the property catalog at export time and fail the export instead of writing Unknown* actors or dropping configured property columns on cold/errored lookups. - Append a unique id tie-break to the list/grouped ORDER BY and mirror it in compareIssuesForSort so offset pages are stable across same-timestamp ties. Co-authored-by: multica-agent <github@multica.ai> * fix(issues): bound table structure window and align chip/transport/selection Round-3 review fixes for the issues table (MUL-4797): - Cap whole-window materialization at TABLE_STRUCTURE_MAX_WINDOW (1000): below it the remaining pages load automatically — hierarchy applies without scrolling to the last page — and above it grouping/hierarchy suspend with an explicit toolbar notice instead of triggering an unbounded workspace download from a persisted view option. - Give the agents-working chip the authoritative in-window running set (the ids-facet window query, shared key with the filter-on state) so its badge can no longer say 0 while the filter would find matches on unfetched pages; falls back to loaded-row scoping elsewhere. - Route ids-facet windows through a new POST /api/issues/query twin — hundreds of running-issue UUIDs overflow the ~8 KB GET request-line budget of common proxies. The body carries the same key/value pairs; the handler rebuilds the query string and delegates to ListIssues. - Reset surface selection during render (key-change pattern) instead of a post-commit effect, so no frame ever pairs new membership with the old selection. Co-authored-by: multica-agent <github@multica.ai> * fix(issues): harden table auto-pagination against errors and stale totals Round-4 review fixes for the issues table (MUL-4797): - Stop the structure materialization loop (and the scroll sentinel) when the window query is in error state — a persistently failing page left hasNextPage true and isFetchingNextPage false after every attempt, so the ungated effect refired forever. Resuming is an explicit toolbar Retry. The advancement decision now lives in a pure, tested shouldAutoLoadNextStructurePage helper. - Make the structure ceiling a hard stop: the ceiling check reads the LATEST page's total (pagination already advances on it, so a stale small page-1 total could re-open unbounded materialization), and the loop additionally halts on loaded count >= ceiling regardless of any reported total. - Drive the working (ids-facet) window to completion — it is inherently bounded by the running set — and treat it as the chip's authoritative scope only when complete, so >100 running issues no longer under-count as a single page. Co-authored-by: multica-agent <github@multica.ai> * fix(issues): make working-window pagination capped and unknown-aware Round-5 (final) review fixes for the issues table (MUL-4797): - The working (ids-facet) window now advances through the same shouldAutoLoadNextWindowPage gates as the structure loop — it shares the main table's cache key while the agents-working filter is on, so an uncapped chip-driven loop re-opened the very ceiling the table just enforced. An over-ceiling window stops after page one. - A cold-load failure of the flat window is an ERROR state, not an empty workspace: isEmpty only claims empty on a successful zero-result fetch, and the surface renders a dedicated failed-to-load state with a reachable Retry (the in-table Retry never mounted without data). - The chip scope is now tri-state honest: a COMPLETE window (or an empty running set) yields a precise count, keepPreviousData carries the last-known-complete set across re-keys, and everything else — cold resolving, failed, over the ceiling — presents as an explicit unknown ('Agents working: —') instead of a number derived from whichever incomplete window happened to be loaded. Co-authored-by: multica-agent <github@multica.ai> * fix(issues): single pagination owner and placeholder-honest chip scope Round-6 review fixes for the issues table (MUL-4797): - Exclude placeholder data from the working-window completeness gate: on a re-key (running set or facet change) keepPreviousData leaves the OLD key's rows visible, and pairing them with the new task snapshot published a precise-looking number for a scope nobody fetched. The scope now reads unknown until the new key resolves. - Make the shared table query single-owner while the agents-working filter is on: the chip's background loop no longer answers the same render snapshot as TableView's structure loop, and every auto caller (structure loop, working loop, scroll sentinel, retry) now uses fetchNextPage({cancelRefetch: false}) so a concurrent responder no-ops instead of cancel/restarting a fetch whose HTTP request is not abortable — which had been duplicating every offset. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Lambda <lambda@multica.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
28969a8493 |
fix(autopilots): clarify Chinese schedule day-pattern labels (MUL-4927) (#5576)
* fix(autopilots): clarify Chinese schedule day-pattern labels (MUL-4927) Co-authored-by: multica-agent <github@multica.ai> * fix(autopilots): make weekly day-pattern label more explicit (每周特定几天) (MUL-4927) Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Bohan-J <bohan@devv.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
71141559c2 |
fix(shortcuts): ignore synthetic key events without a key
Guard shortcut parsing against synthetic key events that do not expose a string key. |
||
|
|
465546b83b |
feat(autopilots): redesign the autopilot schedule editor (#5457)
Replace the free-form trigger-config form with a structured schedule editor built on an orthogonal cron model: separate frequency, time, and day-of-week/day-of-month dimensions map to and from cron expressions via a dedicated grammar and mapping layer, with validation and a human-readable describe() summary. The grammar suite drives the editor against a combinatorially generated corpus of 51,755 distinct cron expressions - every token form of every field, crossed - each judged against a reference robfig/cron v3 parser. Add a server-side /autopilot/cron-preview endpoint (plus schema and React Query hook) so the editor shows upcoming run times, and echo wildcard-carrying cron lists correctly instead of collapsing them. Supporting pieces: timezone-aware formatting helper, segmented-toggle and debounced-value utilities, a reworked time-input, and refreshed en/ja/ko/zh-Hans locale strings. |
||
|
|
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> |
||
|
|
d1c7e1c56d |
fix(chat): always scroll to bottom when switching chat session (#5566)
The Chat tab (and floating chat window) both render the shared
ChatMessageList, re-keyed by activeSessionId so it fully remounts on
every session switch. The Virtuoso had no initial-position prop, so a
fresh mount rendered from the top; the only downward scroll driver was
followOutput, which reacts to post-mount data growth and is gated on
isNearBottom. Whether it landed at the bottom depended on a race
(cached sessions resolve synchronously and stuck at the top; fetched
ones sometimes caught a growth tick), producing the inconsistent
top-vs-bottom behavior.
Pin the newly-mounted list to the newest message with
initialTopMostItemIndex={{ index: 'LAST', align: 'end' }} so switching
sessions always lands at the bottom. align: 'end' also bottom-aligns a
last message taller than the viewport.
Co-authored-by: Bohan-J <bohan@devv.ai>
Co-authored-by: multica-agent <github@multica.ai>
|
||
|
|
ce15300493 |
MUL-4884: feat(issues): anchor the working chip on agents + real colour tiers (#5540)
* fix(issues): anchor the working chip on issues the filter actually shows (MUL-4884)
The header chip showed three units at once: the number counted distinct
issues, the avatar stack counted agents (with a rival "+N"), and the hover
card counted tasks — under a label with no noun at all. Each figure was
self-consistent; together they read as a miscount.
|
||
|
|
82906bd9e9 |
feat(settings): move create-issue field config into a Settings Issue tab (#5556)
The quick-create field toggles used to live inside the quick create popover; per feedback on MUL-4873 they belong in the Settings page. Adds a My Account 'Issue' tab with one group per create mode backed by a new per-workspace issue-create-settings store, extends field visibility to the manual dialog (status/priority/assignee/labels/ project/due date/start date with value-reveal and overflow re-entry), and routes both dialogs' 'Customize fields' items to the tab. Co-authored-by: Lambda <lambda@multica.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
ed9adc2bbe |
feat: improve create issue field controls (#5532)
Co-authored-by: Lambda <lambda@multica.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
d833ef520d |
fix(editor): preserve ordered list numbering on rich paste (#5538)
Co-authored-by: Lambda <lambda@multica.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
a18cc65b35 |
fix(inbox): render archived rows as read (MUL-4893) (#5549)
Archiving deliberately leaves `read` untouched so unarchiving can restore the real unread state (MUL-3736). InboxListItem did not account for the view, so archived rows kept an unread dot and bold title that no action in the archived view can clear. Gate the unread affordance on the view at the display layer, matching the precedent set for chat in MUL-4360. The `read` field and all archive write paths are unchanged, so unarchiving still restores real unread state. Route all four read-driven styles (dot, title weight, detail label, timestamp) through one `showUnread` flag so an archived row renders consistently as read rather than half-dimmed. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
c52ebeaa0f |
MUL-4864 fix(chat): use a single New Chat draft, keyed by session not agent (#5519)
* fix(chat): use a single New Chat draft, keyed by session not agent (MUL-4864)
An uncreated chat kept one draft per agent (`__new__:<agentId>`), so
switching the agent picker mid-compose swapped the draft out from under
the user and left invisible per-agent drafts behind. That slot shape was
never a product decision: it fell out of
|
||
|
|
6b2097ccbb |
feat(inbox): archived notifications sub-view (MUL-3736) (#5518)
Adds an "Archived" sub-view to the Inbox, reachable from an entry at the
bottom of the main list, with per-row unarchive. Mirrors chat's archived
sub-view so the two surfaces share one mental model.
Backend:
- GET /api/inbox/archived and POST /api/inbox/{id}/unarchive. Kept off the
existing GET /api/inbox so installed clients keep their contract and the
unbounded archive never rides along with the main list.
- The archived query excludes any issue that still has an active row. Archiving
is issue-level, so a new notification on an archived issue leaves old archived
rows beside a fresh active one — without the guard the issue renders in BOTH
lists. The exclusion lives in SQL so neither list depends on the other's cache.
- Unarchive is issue-level (mirroring archive) and leaves `read` untouched, so a
restored unread item raises the unread badge again.
- v1 ships no pagination: LIMIT 200, newest-first, so truncation drops the
oldest rows and never hides a group's newest one.
- inbox:unarchived event, fanned out to the recipient like the other personal
inbox events.
- Two CONCURRENTLY-built indexes; inbox_item previously had none covering
workspace/archived/created_at.
Frontend:
- Separate TanStack cache per list; every inbox event invalidates the workspace
prefix, since any of them can move an item across the boundary.
- View persisted as ?view=archived, so refresh, back/forward, and the mobile
detail-back all return to the list the user was in.
- Batch actions stay main-view only — they archive from the MAIN inbox, so
offering them over the archived list would do the opposite of what it reads.
- Mobile subscribes to inbox:unarchived (its list gains the restored row); its
own archived view remains follow-up.
Known debt: no pagination, so an archive past ~200 rows is truncated silently
in the UI; the entry's count is the deduplicated count of the rows returned.
Verified: pnpm typecheck/test/lint (0 errors), go build/vet, Go inbox suite
against a real Postgres, migrations up+down, and EXPLAIN confirming both new
indexes serve the query.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
|
||
|
|
10391939c0 |
test(chat): de-flake use-chat-draft-restore reconciliation waitFor (MUL-4856) (#5516)
The reconciliation test drives real react-query fetches and mutations, so each waitFor gates on a multi-hop async chain (fetch -> effect -> mutate -> settle -> refetch). The 're-consumes on the next fetch after the consume failed' case chains the most hops and timed out under the default 1s waitFor budget when the full Vitest suite saturated the CI runner (whole run 405s, import 364s), failing with 'expected 2 times, but got 1 times' on the main merge of an unrelated Go-only PR. Widen this file's async-utility and per-test timeouts so whole-suite contention can't starve the chain. Test-only; defaults stay in force elsewhere (Vitest isolates config per file). Co-authored-by: Bohan-J <bohan@devv.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
2d9cf045d0 |
MUL-4861: align runtime CLI update permissions (#5514)
* fix(runtimes): align CLI update permissions Co-authored-by: multica-agent <github@multica.ai> * fix(runtimes): address update review nits Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Eve <eve@multica-ai.local> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
4fcb27a729 |
fix(issues): attach labels in the create transaction (MUL-4832) (#5510)
* fix(issues): attach labels in the create transaction (MUL-4832) Labels chosen at issue creation were attached in a second, non-atomic round-trip after the issue was already committed (web modal looped attachLabelToIssue per label; the create endpoint took no labels). A partial failure of that follow-up left the issue committed but mis-categorized, surfaced only as a toast. Carry label_ids through the create request instead: the service validates them (workspace + resource_type='issue') and attaches them inside the same transaction as the issue insert, so the issue and its labels commit together or not at all. An unknown or wrong-scope label id now fails the whole create with 400 rather than being silently dropped. Duplicate ids are idempotent (dedupe + ON CONFLICT DO NOTHING). - server: IssueCreateParams.LabelIDs + ErrIssueLabelNotFound; validate and attach in IssueService.Create; handler parses label_ids and maps the error to 400. - web: create-issue modal forwards label_ids and drops the post-create attach loop and its dead toast key. - tests: handler coverage for atomic attach, stale-id 400 (no issue left behind), duplicate-id idempotency, wrong-scope rejection; web test asserts label_ids is forwarded. Scope is deliberately labels-only. The contributor PR #5475 also auto-parsed an acceptance_criteria field from description text; that introduces a new user-facing data contract with no defined edit/display rules and is left out for a separate product decision. Co-authored-by: multica-agent <github@multica.ai> * fix(issues): echo created labels + guard old-backend compat (MUL-4832) Addresses review of #5510. 1. Create response + issue:created event now carry the labels attached in the create transaction. IssueService.Create returns the authoritative label snapshot; the handler sets it on both the HTTP response and the issue:created broadcast payload. Without this, online members other than the creator saw the new issue unlabeled indefinitely (staleTime: Infinity, no invalidation) because this PR removed the old post-create issue_labels:changed broadcast. 2. New web + old backend compatibility. During the rolling deploy window the web app can run ahead of the backend (web auto-deploys on merge, backend deploys manually). An older backend ignores label_ids and returns an issue with no labels field. The create modal now falls back to the legacy per-label attach only when the response omits labels, so labels aren't silently dropped; when labels is present the atomic path already ran and no fallback fires. The backend always returns an explicit labels array (empty when none) as the detection signal. Tests: handler issue:created-carries-labels + response-carries-labels + response-always-includes-labels; web modal no-fallback (new backend) and fallback (old backend); ws-updaters keeps the label snapshot in list cache. Co-authored-by: multica-agent <github@multica.ai> * fix(issues): validate create response through a schema (MUL-4832) Addresses review of #5510 (must-fix 3). The create modal keys its label-attach compatibility fallback off `issue.labels` being absent (older backend) vs a validated Label[] (current backend). But api.createIssue cast the raw JSON straight to Issue, so a malformed labels value (null, an object, a garbage array) would be !== undefined and wrongly suppress the fallback, caching a bad shape — violating the repo's API Compatibility rule. Parse the create response through CreateIssueResponseSchema: - labels absent -> undefined (older-backend signal; fallback runs) - labels valid -> Label[] (fully validated elements, not z.unknown()) - labels malformed -> undefined via .catch (safe: never masquerades as handled; worst case a redundant re-attach, never a silent drop) A whole-body parse failure degrades to EMPTY_ISSUE (never throws into React), matching the existing parseWithFallback contract. Tests: schema.test.ts covers absent / valid / null / wrong-element-shape labels and the empty-issue degrade. Co-authored-by: multica-agent <github@multica.ai> * fix(issues): reject a malformed create response instead of faking success (MUL-4832) Follow-up to review of #5510. The prior fix degraded a schema-failed create response to EMPTY_ISSUE, which React Query treats as a successful mutation: the empty issue is written into the list cache, a blank "created" toast shows, the "View issue" link points at an empty id, and with labels the fallback attach runs against an empty issue id. A create that returns an unusable body is a failed mutation, not a safe-empty read. Fall back to null and reject: mutateAsync is already inside the create modal's try/catch, so a controlled rejection preserves the draft and shows the failure toast, and onSettled still refreshes the list so a genuinely-created issue can still surface. - CreateIssueResponseSchema tightens id to non-empty; an id-less body routes to the same reject path. - createIssue throws on parse failure (empty-message Error so the modal renders its localized "failed to create" toast; parseWithFallback already logged the schema issues + raw body). - Dropped the EMPTY_ISSUE fallback constant. - Tests: whole-body malformed and empty-id now assert createIssue rejects; only-labels-malformed still returns the real issue with labels undefined. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Bohan-J <bohan@devv.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
f9f41e991a |
fix(ui): drop native title that stacked on custom hover surfaces (MUL-4836) (#5502)
ActorAvatarBase hardcoded `title={name}` on the avatar div, so every one of
the 155 avatar renders emitted a browser tooltip. On the 34 call sites that
also pass `enableHoverCard`, the native title sat on the inner div while the
PreviewCard trigger sat on an ancestor span — different elements, so neither
suppressed the other and both surfaces showed at once.
Same class of bug in two more places: AgentStatusDot added a third `title`
inside the same trigger on the 16 sites that also pass `showStatusDot`, and
RepoUrlText rendered a `title` carrying the exact string its Tooltip already
showed.
Phase 1 scope only — remove the three duplicate titles. PreviewCard trigger
semantics, `profileLink`, ancestor detection, the 159 call sites, and the
remaining native-title cleanup are deliberately untouched.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
|
||
|
|
310634064f |
fix(transcript): explain unavailable Antigravity live events (#5182)
* docs: design Antigravity live transcript empty state * docs: plan Antigravity live transcript empty state * fix(transcript): explain unavailable Antigravity live events * chore: remove agent planning artifacts --------- Co-authored-by: CAVIN <zzz163519@users.noreply.github.com> |
||
|
|
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> |
||
|
|
e8ada0e89c |
fix(views): stop avatar hover cards opening on brushed-past avatars (MUL-4827) (#5497)
ActorAvatarHoverCardShell deferred mounting the Base UI PreviewCard root until the first pointerenter, then emulated the missing first-dwell open with a manual 600ms timer. Base UI drives hover through native mouseenter/mouseleave listeners on the trigger and installs its close path inside the mouseleave handler, so swapping the node mid-gesture loses both the event that cancels a pending open and the one that closes the popup. Flicking the pointer across a dense list armed one timer per avatar and cancelled none: in real Chromium a single sweep left 5 profile cards open with the pointer nowhere near them, and no hover-close path remained. The same cold span also opened on touch taps (Base UI sets mouseOnly) and opened instantly on focus, with no focus-visible gate and no delay. Drop the cold/warm swap, the manual timer, the manual focus-open and the focus restore, and let Base UI drive hover and focus natively. Appearance, tabIndex and the standalone-ancestor probe are unchanged. Mounting the root eagerly costs ~0.13ms of JS per avatar in real Chromium (+19.5ms at 150 avatars, the widest realistic board) and adds zero DOM while closed -- the popup subtree and its queries stay unmounted until open. Virtualization already caps mounted rows at 30 (10 per board column), and the popup-deferral work that motivated the hack is untouched. Verified: tsc --noEmit, eslint, and @multica/views vitest (212 files / 2102 tests) pass. A real-Chromium harness passes 7/7 hover/touch/keyboard scenarios on this fix and reproduces 3 failures on the pre-fix code. Still owed: real-device Electron trace on the widest/densest board. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
0ec7c22901 | fix(issues): render create picker trigger content (#5491) | ||
|
|
ea8511340e |
MUL-4820: support custom property icons (#5468)
* feat(properties): add custom icons Co-authored-by: multica-agent <github@multica.ai> * fix(migrations): use unique property icon prefix Co-authored-by: multica-agent <github@multica.ai> * fix(properties): replace emoji icons with Lucide picker Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Lambda <lambda@multica.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
032c7038ee |
fix(editor): drop underline mark so pasted rich text stops emitting ++ (#5471)
StarterKit registers Tiptap's Underline extension by default. It maps both `<u>` and `text-decoration: underline` to an underline mark and serializes that mark as `++text++` — a syntax neither CommonMark nor GFM defines. The display layer (ReadonlyContent: react-markdown + remark-gfm) has no rule for it, so a saved comment rendered the delimiters literally. Disable the extension in the shared StarterKit config rather than filtering `u` out of the rich-HTML paste selector: the mark is also reachable via the extension's own Mod-u shortcut, so a paste-only fix would leave Cmd+U able to produce the same unrenderable Markdown. Removing the extension also removes its `++text++` markdown tokenizer, so existing text containing `++` now round-trips as literal text instead of being silently reinterpreted as an underline mark. This is shared editor config: it applies to every ContentEditor surface (issue comments, issue descriptions, chat, agent prompts), not just the comment box. No toolbar control, command, or stylesheet referenced the mark. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
fa17e5c168 |
fix(views): stop server version from stretching the help menu (#5472)
Cap the Help menu width (max-w-56) and let the server version label wrap (break-words) so a long version string no longer widens the whole popup. The menu still keeps its min-w-40 floor when no version is shown. MUL-4828 Co-authored-by: Bohan-J <bohan@devv.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
fff23c1fc3 |
MUL-4818: keep Inbox and sidebar unread counts static (#5461)
* fix(sidebar): keep unread counters static Co-authored-by: multica-agent <github@multica.ai> * fix(inbox): keep title unread count static Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Lambda <lambda@multica.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
19e52e007c |
MUL-4798: make Inbox notification preference updates atomic (#5451)
* fix(notifications): make preference updates atomic Co-authored-by: multica-agent <github@multica.ai> * fix(notifications): serialize preference mutations Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Lambda <lambda@multica.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
449d9887b5 |
fix(help): wrap server-version row in DropdownMenuGroup to stop Help menu crash (#5458)
Base UI's Menu.GroupLabel (rendered by DropdownMenuLabel) calls useMenuGroupRootContext(), which throws when it has no Menu.Group ancestor. The Help launcher rendered the server-version DropdownMenuLabel directly under the popup, so opening the Help menu on any deployment that returns a non-empty server_version threw during render. No error boundary sits above the app sidebar, so the throw unmounted the whole React tree — a black screen with no error, exactly as reported (MUL-4819). Wrap the row in DropdownMenuGroup, matching every other DropdownMenuLabel usage in the codebase. Also harden help-launcher.test.tsx: the old mock flattened the dropdown to plain divs and silently dropped the Group/GroupLabel contract, which is why the bug shipped. The mock now mirrors Base UI's throw, so a bare label fails the test. Co-authored-by: Bohan-J <bohan@devv.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
ea03912baf |
perf(desktop,issues): single-router tab sessions (MUL-4741 Phase 2) + trace-driven surface mount/render overhaul (MUL-4474/4750 reland) (#5403)
* Reapply "perf(issues): virtualize inbox/list/board/swimlane (MUL-4474, 方案2) (#…" (#5395)
This reverts commit
|
||
|
|
c599f47ba4 |
fix(redact): cover GitHub fine-grained PATs and Google API keys (#4678)
The primary server redactor now covers github_pat_ and standard AIza keys, but the client transcript safety net does not. Add matching client patterns and regression coverage so previously stored or otherwise unredacted transcript values are masked at display time. Also make the fixed-length Google key rule redact a key ending in '-' while preserving its trailing delimiter. Keep the 39-character format exact to avoid broadening false positives. Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
b31132edf3 |
feat(agents): add access-scope column, filter, and bulk edit to agents list (#5393)
Adds a three-state access column/filter and a bulk "Set access scope" action to the agents list, plus a fix for a state-update loop in the bulk access dialog (parent/child callback + effect cleanup were re-triggering each other). Co-authored-by: chouti <chouti@users.noreply.github.com> |
||
|
|
50e28d539c |
feat(settings): open issue links in new tab by default, configurable in preferences (#5445)
Issue mention chips in descriptions (tiptap), comments (readonly markdown), and chat now open the linked issue in a new tab on plain click — a browser tab on web, a foreground app tab on desktop. A new Settings → Preferences switch (default on) restores in-place navigation when turned off; the preference persists via a zustand store. AppLink now honors target="_blank": desktop delegates to the navigation adapter's openInNewTab with activate, web leaves the native anchor behavior intact. This also fixes dead cmd/ctrl-click on web for comment and editor mentions, which previously preventDefault'd without an adapter fallback. MUL-4793 Co-authored-by: Lambda <lambda@multica.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
bf64bb9214 |
MUL-4781: keep machine CLI status visible
Co-authored-by: Eve <eve@multica-ai.local> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
56f453fed6 |
feat(agents): add loading skeleton for Recent work list (#5438)
The Recent work section on the agent Activity tab reads a lazily-loaded per-agent task list. On first paint the query is still pending, so the section rendered its 'nothing finished yet' empty state — a wrong answer that flashes before real data arrives. Render a skeleton (bordered, divided placeholder rows matching the real TaskList shell) while the first fetch is in flight, keyed off the query's isLoading. Once the cache is hydrated the tab opens straight into data with no skeleton flash. Co-authored-by: J <j@multica.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
07e2d378bd |
feat(settings): add random color option to label and property color pickers (MUL-4786) (#5437)
* feat(settings): add random color option to label and property color pickers (MUL-4786) Co-authored-by: multica-agent <github@multica.ai> * feat(settings): unified color picker popover with presets and random for labels and property options (MUL-4786) Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Lambda <lambda@multica.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
2c482ab366 |
MUL-4781: move daemon update to machine page (#5434)
* fix(runtimes): move daemon update to machine Co-authored-by: multica-agent <github@multica.ai> * chore(runtimes): remove obsolete update hook Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Eve <eve@multica-ai.local> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
06d79c1750 |
feat(agent): add Grok Build CLI as an ACP runtime (#5285)
Add xAI Grok Build (`grok`) as a first-class Multica runtime over ACP (`grok --no-auto-update agent --always-approve stdio`), reusing hermesClient like traecli and kimi. Includes daemon discovery, protocol_family migration 174, model discovery, MCP passthrough, thinking effort, frontend branding, and product docs. Follows xAI's documented headless ACP flow: after `initialize`, read the advertised `authMethods` and send `authenticate` (preferring xai.api_key when XAI_API_KEY is set, else the cached login token) before any session operation — a real, logged-in CLI rejects session/new and session/load without it. Model discovery performs the same handshake so it returns the live catalog instead of the static fallback. `--no-auto-update` is passed as a global flag (and kept daemon-owned in the blocked custom-arg set) so a background update check can't stall an unattended ACP task. Thinking effort uses the current `--effort` flag, and the minimum grok version is 0.2.89 (ACP + authenticate + session/load + session/set_model + MCP + --effort). Closes #2895 |
||
|
|
101f21d55d | fix(ui): render select labels instead of values (#5435) | ||
|
|
b85bb71a58 |
feat: custom issue properties — typed workspace-defined fields with list-surface support (MUL-4463) (#5335)
* feat(server): custom issue properties — definitions, typed values, CLI (MUL-4463)
Workspace-level property definitions (issue_property table; 7 types:
text/number/select/multi_select/date/checkbox/url) plus a typed value bag
on each issue (issue.properties JSONB keyed by definition UUID, mirroring
the metadata machinery: single-key atomic writes, 16KB cap, GIN index).
- Definitions: owner/admin only; agent actors rejected (agents propose,
humans confirm). 20 active per workspace, 50 options per select,
reserved built-in names blocked, archive instead of delete.
- Values: any member or agent; per-type validation with self-correcting
error messages that enumerate legal option ids.
- API: /api/properties CRUD + PUT/DELETE /api/issues/{id}/properties/{propertyId};
issue responses always emit the properties bag.
- CLI: multica property list/get/create/update/archive/unarchive and
multica issue property list/set/unset with name→id translation.
- Events: property:created/updated, issue_properties:changed.
Co-authored-by: multica-agent <github@multica.ai>
* feat(web): custom properties settings tab + issue sidebar editors (MUL-4463)
- Settings → Properties: definition management mirroring the Labels tab
(list with type badges/option chips/usage counts, create/edit dialog
with option editor, archive/restore, 20-cap indicator). Admin-gated;
members see a read-only catalog.
- Issue detail sidebar: custom properties join the built-in optional
props' progressive disclosure — set values render as rows with
type-appropriate editors (select/multi-select pickers, calendar,
yes/no, inline input for text/number/url), unset ones live in the
same '+ Add property' menu behind a separator. Archived definitions
render read-only until cleared.
- Core: property types, zod schemas (lenient type strings for forward
compat), api client methods, React Query hooks with optimistic
single-key value writes, ws-updaters + realtime wiring for
property:created/updated and issue_properties:changed.
- Locales: en/zh-Hans/ja/ko strings; Issue fixtures gain properties: {}.
Co-authored-by: multica-agent <github@multica.ai>
* fix(properties): address MUL-4463 review round 1 — mobile CI, option guard, mutation safety, schema tolerance
- mobile: EMPTY_ISSUE_FALLBACK gains the required properties field (mobile
typecheck was the red CI check).
- server: PATCH /api/properties/{id} rejects config updates that remove
select options still referenced by issues (409 with a per-option usage
census via jsonb ?); renames keep ids and pass. Integration test included.
- core: property value mutations are serialized per workspace via mutation
scope, snapshot the bag from detail OR list caches (board surfaces have no
detail cache — the old path overwrote whole bags with one key), roll back
to the snapshot or invalidate on error, and the last settled mutation does
an authoritative detail+catalog invalidate (usage counts reconcile).
- schemas: unknown-shaped property values (future server types) are dropped
per-entry in a preprocess step instead of failing the whole IssueSchema
and blanking lists through parseWithFallback; test updated to lock the
tolerant behavior.
- realtime: reconnect invalidation covers the property catalog; every
issue_properties:changed event also refreshes catalog usage counts.
- ui: number editor accepts decimals (step=any); settings usage count
pluralizes (issue/issues) with CJK-safe plural keys.
Co-authored-by: multica-agent <github@multica.ai>
* fix(migrations): renumber issue properties to 179 and build the GIN index concurrently
main's migration sequence advanced twice under this PR (167 collision, then
an upstream renumber wave that claimed 178), so issue properties now sits at
179 — verified against main's current tip by the prefix-uniqueness lint.
The properties GIN index moves to its own single-statement migration (180)
using CREATE INDEX CONCURRENTLY — a plain CREATE INDEX on the hot issue
table would block writes for the duration of the build. Mirrors the
119_user_created_at_index pattern; full-chain dry-run on a fresh database
passes through 180.
Co-authored-by: multica-agent <github@multica.ai>
* feat(web): custom-property list surfaces — filter, cards, sort, board grouping (MUL-4463 M2)
Brings custom properties to the issue list surfaces on top of the M1
definitions/values core:
- Filter: per-definition sections in the Filter dropdown (select /
multi_select options with color dots and counts; checkbox as Yes/No
pseudo-options). OR within a definition, AND across definitions;
client-side in applyIssueFilters, mirrored into filterAssigneeGroups
for the assignee-grouped board. Included in active-filter count and
Clear all.
- Cards: per-property Display toggles (cardPropertyIds) render value
chips on board cards and list rows via CustomPropertyValueDisplay.
- Sort: SortField gains property:<id> for number/date definitions.
Server keeps position order (fixed sort enum); the surface controller
re-sorts client-side, swimlane/gantt reuse the same comparator.
Date-only strings compare lexically; missing values sort last.
- Board grouping: IssueGrouping gains property:<id> for select
definitions — one column per option (definition order) plus a
trailing No-value column, option-colored headings. Drag-drop moves
position via UpdateIssue and applies the value through
useSetIssueProperty/useUnsetIssueProperty (properties are not part
of UpdateIssueRequest). Stale persisted property groupings fall back
to status columns.
View-store: propertyFilters + cardPropertyIds persisted via the
partialize allowlist; clearFilters resets property filters; new fields
deep-merge cleanly into pre-existing persisted snapshots.
Co-authored-by: multica-agent <github@multica.ai>
* fix(properties): address MUL-4463 review round 2 — desc sort, option bucketing, archived-state reconciliation
- sort: direction now applies to value comparison only; issues without a
value sort last in BOTH directions (the whole-array reverse flipped them
to the front on desc). Test covers the desc+missing case.
- board: values referencing an option removed from the definition bucket
into the No-value column instead of vanishing (unmatched column ids
dropped the issue entirely). Defense-in-depth behind the new server-side
in-use guard; drag-utils test locks both behaviors.
- controller: persisted propertyFilters keyed by archived/deleted
definitions are stripped before reaching the filter predicates, and a
persisted property sort on a non-active definition degrades to manual
order — previously both kept silently applying while the header claimed
otherwise. The filter badge counts only active-definition filters.
Co-authored-by: multica-agent <github@multica.ai>
* feat(properties): server-side property filtering and sorting on list endpoints
Property filter/sort now execute in the database, so results are correct
across the full issue set — not just the loaded 50-per-status window
(closes MUL-4493 item 1's filter/sort half; requested on MUL-4463).
- New `properties` query param on ListIssues and ListGroupedIssues:
JSON {definitionId: [values]} compiled to an AND-of-ORs containment
check (double NOT EXISTS over jsonb_array_elements). One value expands
to every storage shape it could match — string (select), array element
(multi_select), boolean (checkbox) — so the handler stays type-agnostic.
Guarded at 20 definitions / 50 values.
- `sort=property:<definitionId>` resolves the definition and orders by a
typed expression (numeric CASE cast for number, NULLIF text for
date/text/url); missing values sort last in both directions. Malformed
ids 400; unknown/archived definitions degrade to position order instead
of breaking stale clients.
- Frontend: the property filter and property sort ride the IssueSortParam
window bag, so every surface (workspace + my-issues variants), query
key, and per-status load-more page carries them automatically. The
client-side re-sort layer is gone; applyIssueFilters keeps its property
predicate as an optimistic-update backstop.
- Regression test seeds 55 issues and proves a match at position 55 is
returned by a filtered 50-row page, plus sort order/missing-last,
AND-across-definitions, and the 400/fallback sort paths.
Co-authored-by: multica-agent <github@multica.ai>
* fix(properties): address review round 3 — cache reconcile, merged-scope order, GIN-indexable filter, pool loader
- Cache reconciliation: property value writes (mutation settle + WS event)
now invalidate every issue window whose server-side shape depends on
property values — queries filtered by `properties` or sorted by
`property:<id>` (detected via query-key predicate), covering flat lists,
assignee groups, and my-issues variants. Windows without property params
keep the cheap in-place patch. Fixes stale ordering/membership/counts
under staleTime:Infinity.
- My Issues "All" scope: merged assigned/created/involves results are
re-sorted with a comparator mirroring the server ORDER BY semantics
(including property sorts and missing-last, created_at DESC tiebreak) in
both the flat and assignee-grouped merge paths — relation concatenation
no longer overrides the user's sort.
- Filter predicate rebuilt as plain bind-parameter containment ORs
(AND across definitions): EXPLAIN now shows BitmapOr over
idx_issue_properties_gin (the correlated jsonb_array_elements form
defeated the index). Alternatives capped at 256 bind params.
- Property-grouped board gains a pool loader strip: one sentinel per
status that still has server rows, keeping every issue reachable until
per-column pagination lands (MUL-4493).
- Windowing regression test hardened: explicit positions + an assertion
that the unfiltered first page excludes the target (the old fixture tied
at position 0 and the created_at DESC tiebreak put the target on page
one, proving nothing).
- Rollback safety: /api/properties 404 (old server) degrades to an empty
catalog instead of a query error, which also keeps property params from
ever being sent to pre-property servers; migration 179's CHECK
constraints switch to NOT VALID + VALIDATE so the exclusive lock is
instantaneous.
Co-authored-by: multica-agent <github@multica.ai>
* fix(properties): harden concurrency and cache coordination from clean-room review
Backend (MUL-4762 F1/F4/F5):
- withPropertyLock: pg_advisory_xact_lock helper; definition create/update
and value writes now serialize config-vs-value and cap-vs-insert races
(workspace-level 'props:' lock + per-definition 'prop:' lock, ordered).
- propertySortExpr degrades archived definitions to position sort.
Frontend (F2/F3/F6):
- onIssuePropertiesChanged invalidates plain assignee-group caches too.
- Property value mutations cancel list refetches in onMutate and roll back
only the touched key against the current bag (concurrent WS writes to
other keys survive a failed write).
- useUpdateIssue reconcile drops the stale properties bag from the server
snapshot; the property pipeline owns that field.
- Surface controller passes persisted property filters/sorts through
until the catalog query settles (cold cache no longer strips them).
Co-authored-by: multica-agent <github@multica.ai>
* fix(properties): open_only branch honors the properties filter
ListOpenIssues takes the parsed AND-of-ORs containment groups as a single
jsonb properties_filter param and unrolls them with a static double
NOT EXISTS; previously the open_only path parsed the properties param and
silently dropped it (clean-room review F7a).
Co-authored-by: multica-agent <github@multica.ai>
* fix(properties): toast on failed board drag to a property column
Property-column drags rolled the card back silently on failure; mirror
the status/assignee drag path (use-issue-surface-actions) so the
snap-back is explained (clean-room review F3, drag half).
Co-authored-by: multica-agent <github@multica.ai>
---------
Co-authored-by: Lambda <lambda@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
|
||
|
|
8f92b5fdeb |
feat(search): add fold/unfold all comments commands to the command palette (#5417)
On an issue page, Cmd+K now offers Fold All Comments / Unfold All Comments. Folding collapses every thread card via the persisted comment-collapse store; unfolding also expands resolved threads, whose session-only expand state moves from issue-detail useState into a new core resolved-expand store so the palette can drive it (MUL-4763). Co-authored-by: Lambda <lambda@multica.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
5999eabd92 |
fix(views): stop showing backfilled attribution as a warning (MUL-4768) (#5421)
* fix(views): stop showing backfilled attribution as a warning (MUL-4768) The transcript/activity AttributionBadge colored the "on behalf of <name>" chip yellow (text-warning) whenever attribution.precise === false. That flag is the backend's attribution-*coverage* health bit — owner_fallback, backfill, and unattributed all fail it — but coverage is an ops metric, not a reader-facing signal. A backfilled attribution names a human the same waterfall resolved, just retroactively, so it is confident; rendering it identically to a genuine owner_fallback guess made a correct "on behalf of Bohan" read like an error. Fire the cautionary tone only for a fallback guess (any non-precise source except backfill). Keeping the precise === false base means a future unknown degraded source still warns (fail-safe). The backfill nuance stays in the tooltip. Co-authored-by: multica-agent <github@multica.ai> * docs(views): correct backfill wording in AttributionBadge (nit MUL-4768) Review nit: describing backfill as "confident / same waterfall resolved" overstated the backend contract, which defines backfill as a historical, non-realtime, non-compliance-grade source. Reword the docblock, the tone rationale, and the test note to the accurate framing: backfill does not mean the displayed name is wrong (so no warning tone), but its historical origin is still preserved in the tooltip and the raw source field. Comments only; no behavior change. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: J <j@multica.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
45c0b38a94 |
fix(attribution): put run avatar on the meta-line middot rhythm (MUL-4767) (#5420)
The accountable-member avatar in the agent Activity 'Recent work' rows was appended to the middot-delimited meta line with only a 2px nudge (ml-0.5), so it read as glued to the duration while every other item (status / time / duration) is spaced by a '·' separator. Give the avatar a leading separator so it shares the same rhythm, and gate both on task.attribution?.initiator — the exact condition the avatar badge itself renders on — so no dangling separator is left for an unattributed run. Co-authored-by: J <j@multica.ai> Co-authored-by: multica-agent <github@multica.ai> |