Add a visible search trigger button next to the create-issue button in
the sidebar header, improving search discoverability (previously only
accessible via ⌘K). Search dialog open state is shared via a Zustand
store so both the button and keyboard shortcut work.
Also restores turbo.json globalEnv config (FRONTEND_PORT, etc.) that was
accidentally dropped during the monorepo extraction, fixing worktree
port conflicts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- README.md / README.zh-CN.md: add X link to top navigation
- layout.tsx: add twitter site/creator metadata (@multica_hq)
- Landing header: add X icon button next to GitHub
- Landing footer: add X and GitHub social icons
- Footer i18n: replace Community link with X (Twitter) in en/zh
- shared.tsx: add twitterUrl constant and XMark icon component
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add creator ownership verification on chat session endpoints (get, archive, send, list messages)
- Add CancelTaskByUser handler with ownership check instead of unrestricted CancelTask
- Show user messages optimistically before server response
- Remove unused streamingContent from chat store and sendMessage mutation import
- Make QueryProvider devtools flag a prop instead of reading process.env in core package
- Add proper FK constraint on chat_session.creator_id → user(id)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- globals.css: use relative path for @multica/ui/styles/tokens.css
since Tailwind v4's @import resolver doesn't follow pnpm workspace
symlinks + package.json#exports
- globals.css: widen @source globs from *.tsx to *.{ts,tsx} so
Tailwind scans .ts config files — fixes bg-info being purged
(Done badge invisible in light mode)
- layout.tsx: hoist WorkspaceIdProvider above SidebarProvider so
AppSidebar (which now calls useWorkspaceId via useMyRuntimesNeedUpdate
from #533) doesn't throw on mount
* feat(runtime): proactive CLI update notifications with per-user filtering
- Add latestCliVersionOptions query (GitHub Releases API, 10-min TanStack cache)
- Add useMyRuntimesNeedUpdate / useUpdatableRuntimeIds hooks using owner_id
- Show red dot on sidebar Runtimes item when user's runtimes need updates
- Show update arrow icon alongside status dot in runtime list items
* fix(core): add runtimes/hooks to package.json exports
Implement the Master Agent chat feature allowing users to chat with agents
directly from a floating window, separate from the issue-based workflow.
Backend:
- New chat_session and chat_message tables (migration 033)
- Make issue_id nullable on agent_task_queue for chat tasks
- REST API: create/list/get/archive sessions, send/list messages
- EnqueueChatTask in TaskService with session_id persistence
- WS events: chat:message, chat:done
- Daemon: chat task type with separate prompt builder
- ClaimTaskByRuntime populates chat context (session, message, repos)
Frontend:
- ChatSession/ChatMessage types + API client methods
- core/chat: TanStack Query options, mutations with optimistic updates, WS updaters
- features/chat: Zustand store, ChatFab (floating button), ChatWindow with
real-time streaming via task:message events
- Mounted in dashboard layout (bottom-right corner)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(search): implement full-text search for issues
Add pg_bigm-based full-text search across issue titles and descriptions,
with API endpoint, CLI subcommand, and web Cmd+K search dialog.
- Migration 032: pg_bigm extension + GIN indexes on title/description
- Server: GET /api/issues/search?q=... with pagination and total count
- CLI: `multica issue search <query>` with table/json output
- Web: Cmd+K command palette using cmdk, with debounced search
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(search): address review feedback on search implementation
1. Escape LIKE special characters (%, _, \) in handler to prevent
matching anomalies from user input.
2. Wire AbortController signal into searchIssues fetch so in-flight
requests are actually cancelled on new input.
3. Fix offset=0 falsy check — use !== undefined instead of truthiness.
4. Merge results + count into single query using COUNT(*) OVER()
window function, eliminating the duplicate DB round-trip.
5. Exclude done/cancelled issues by default; add include_closed
parameter to API, CLI (--include-closed), and web client.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(search): default web search to include all statuses
Pass include_closed: true in the web Cmd+K search so results include
done and cancelled issues by default, matching the reviewer's request.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(search): add comment search with snippet extraction
Extend search to cover issue comments in addition to title/description.
Results are deduplicated at the issue level, with match_source and
matched_snippet fields indicating where and what matched.
- Migration 033: pg_bigm GIN index on comment.content
- SQL: EXISTS subquery for comment matching, correlated subquery for
snippet extraction, 3-tier ranking (title > description > comment)
- Server: SearchIssueResponse with match_source and matched_snippet
- Web: show comment icon + snippet below issue title when matched
- CLI: MATCH column shows source and truncated snippet
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(search): redesign search dialog to match Linear's spacious style
- Widen dialog from sm (384px) to xl (576px) with top-20% positioning
- Larger search input with icon, generous padding, and ESC hint
- Use cmdk primitives directly for full style control
- Taller result list (400px / 50vh), spacious result items (py-2.5)
- Rounded-lg items with accent highlight on selection
- Cleaner border separator between input and results
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The root layout called `await cookies()` to read the locale, which
marked the entire app as dynamic. In Next.js 16, dynamic pages have
Router Cache staleTime=0, causing a fresh RSC server roundtrip on
every navigation — the root cause of ~400ms tab switching delays.
- Remove cookies() from root layout, making it static
- Add LocaleSync client component to read locale cookie on the client
- Add loading.tsx skeleton for dashboard routes as a loading fallback
* feat(issues): add sub-issue support
- Backend: Add ListChildIssues SQL query, add parent_issue_id to UpdateIssue,
add GET /api/issues/{id}/children endpoint
- Frontend: Display parent issue breadcrumb and link in issue detail sidebar,
show child issues list with status icons, add "Create sub-issue" action in
dropdown menu and sidebar, pass parent_issue_id through create issue modal
- Update test mocks for new API method
* fix(issues): add parent validation, cycle detection, and improve child refresh
- CreateIssue: validate parent issue exists in the same workspace
- UpdateIssue: validate parent exists, prevent self-referencing, detect
circular parent chains (up to 10 levels deep)
- Frontend: derive child issues from store when available instead of
refetching on every global issue count change
AppSidebar renders before workspace hydrates. useWorkspaceId() throws
when workspace is null. Fix: read workspace?.id directly from store,
use enabled guard on inbox query. This fix was in commit 030627c but
got reverted by subsequent merge with main.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
issueListOptions now makes 2 api.listIssues calls (open_only + closed page).
Tests that mock the response must return data only for the open_only call,
otherwise issues appear twice in the merged result.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When authenticating via CLI, the login page called api.verifyCode()
directly and redirected to the CLI callback without saving the JWT
to localStorage or setting the logged-in cookie. This meant the
browser had no session after CLI login, forcing users to log in
again when visiting multica.ai.
Now the token is saved to localStorage and the cookie is set before
redirecting to the CLI callback, so both CLI and web app share the
same authentication.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: remove agent triggers config field
Remove the triggers field from agent configuration. The on_assign,
on_comment, and on_mention behaviors are now always enabled (hardcoded),
as decided in the Agentflow design discussion (MUL-372).
Changes:
- Database: migration 032 drops triggers column from agent table
- Backend: remove triggers from create/update agent APIs and response
- Backend: simplify trigger-checking logic to always-enabled
- Frontend: remove TriggersTab UI and AgentTrigger types
- Tests: remove trigger config unit tests (no longer configurable)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: also remove agent tools config field
Remove the tools field from agent configuration alongside triggers.
The tools field was a placeholder — stored in the DB and shown in the
UI but never passed to the daemon or used at runtime.
- Database: migration 032 now also drops tools column
- Backend: remove tools from create/update agent APIs and response
- Frontend: remove ToolsTab UI, AgentTool type, and tools tab
- Update landing page copy
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(test): remove tools/triggers columns from test fixtures
The test fixtures still referenced the dropped tools and triggers
columns when inserting agent rows, causing CI failures.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Devv <devv@Devvs-Mac-mini.local>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The merge with origin/main (fe9479d6) silently reverted all consumer-side
migrations, leaving core/ as dead code. Restored all 39 files from
pre-merge commit 6032b5df, plus main's trigger.config null fix for
agents page.
Verified: 59 @core/ imports across features/ and app/, all stores
gutted/deleted, realtime sync uses queryClient not Zustand.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
AppSidebar renders outside the workspace guard in dashboard layout.
On first login, workspace hasn't hydrated yet → useWorkspaceId() throws.
Fix: read workspace?.id directly from store, use enabled guard on inbox query.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Relax ClaimAgentTask SQL constraint from per-issue to per-(issue, agent)
serialization, allowing different agents to run in parallel on the same issue
- Update GetActiveTaskForIssue API to return all active tasks (array) instead of
just the first one
- Refactor AgentLiveCard to render one card per active task, routing WebSocket
messages by task_id for independent timelines
- Fix shouldEnqueueOnComment to use per-agent dedup so a mentioned agent's
pending task doesn't block the assigned agent's on_comment trigger
Closes MUL-160
- Create core/inbox/ with queries, mutations, ws-updaters
- Migrate inbox page: useQuery + mutation hooks replace useInboxStore + api.*
- Migrate sidebar unread badge to read from TQ cache
- Delete useInboxStore (127 lines) — inbox has no client-only state
- Remove inbox deps from workspace store (hydrate + switch)
- Fix WS sync: use useQueryClient() instead of getQueryClient() singleton
to ensure WS handlers write to the same QueryClient instance that
components read from (singleton is unreliable under Next.js HMR)
- Add onInboxIssueStatusChanged for issue status sync in inbox items
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove mock issues[] and server state fields from useIssueStore mocks
since the store now only holds activeIssueId. Data flows through
TanStack Query (mockListIssues) not the store.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The callback page was placed under app/(auth)/callback/ — a Next.js
route group — which mapped to /callback instead of /auth/callback.
Move it to app/auth/callback/ so the URL matches the Google OAuth
redirect URI.
- Migrate issue-detail.tsx: useQuery for issue data, useUpdateIssue/useDeleteIssue
- Migrate issues-page.tsx, my-issues-page.tsx, board-card.tsx: useQuery for list
- Migrate batch-action-toolbar.tsx, create-issue.tsx: mutation hooks
- Migrate edge consumers: mention-suggestion, mention-view, agents page, issue-mention-card
- Remove Zustand writes from WS sync (TQ cache is now sole source of truth)
- Remove useIssueStore.fetch() dependency from workspace store
- Gut useIssueStore to client-only: { activeIssueId, setActiveIssue }
- Update test wrappers with QueryClientProvider
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Support Google login that links to existing accounts by email.
When a user who registered via email OTP signs in with Google using
the same email, they are linked to the same account.
Backend:
- Add POST /auth/google endpoint that exchanges Google auth code for
tokens, fetches user profile, and calls findOrCreateUser()
- Updates user name and avatar from Google profile on first Google login
Frontend:
- Add "Continue with Google" button on login page (shown when
NEXT_PUBLIC_GOOGLE_CLIENT_ID is configured)
- Add /auth/callback page to handle Google OAuth redirect
- Add loginWithGoogle to auth store and API client
Stop clearing multica_workspace_id from localStorage on logout so it
persists as a preference hint. On fresh login, pass the stored ID to
hydrateWorkspace so the user returns to their last workspace instead
of always landing on the first one.
Add w-auto class to DropdownMenuContent on agent detail panel, matching
the pattern used by other dropdowns in the codebase. The default
w-(--anchor-width) was constraining the popup to the icon button width.