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>
Move setPendingTask() before invalidateQueries() so that
pendingTaskRef is set earlier, reducing the window where incoming
WS task:message events would be dropped.
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>
- CancelTaskByUser: verify task belongs to current workspace for both
chat and issue tasks, preventing cross-workspace cancellation
- Log errors for TouchChatSession and CreateChatMessage instead of
silently discarding them
- Add ON DELETE CASCADE to chat_session.creator_id FK
- Add staleTime: Infinity to chat query options (project convention)
- Remove dead useSendChatMessage mutation (replaced by direct api call)
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>
Add owner_id to agent_runtime table to track who registered each runtime.
Backend: new delete endpoint with role-based permissions (owner/admin can
delete any, members only their own), list filtering by owner (?owner=me),
and agent dependency check before deletion.
Frontend: Mine/All filter toggle in runtime list, owner display in list
items and detail view, delete button with AlertDialog confirmation.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
P0: Replace all localStorage calls in packages/core with StorageAdapter
- Create StorageAdapter interface (getItem/setItem/removeItem)
- Auth store factory now requires storage parameter
- Workspace store factory accepts optional storage parameter
- WSProvider accepts storage prop for token retrieval
- apps/web/platform/ passes localStorage as the web implementation
P1: Remove sonner UI dependency from packages/core
- Replace toast.error() in workspace store with onError callback
- Move sonner import to apps/web/platform/workspace.ts
- Remove sonner from packages/core/package.json dependencies
P2: Delete 5 pure re-export barrel files in apps/web/features/
- features/issues/index.ts, modals/index.ts, navigation/index.ts,
workspace/index.ts, inbox/index.ts — all had zero consumers
- features/ now only contains auth/ (web-only cookie + initializer)
and landing/ (web-only pages)
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>
* fix(web): multi-agent sticky card with expand/collapse pattern
- Move sticky positioning to the wrapper div so the entire agent area
sticks together instead of each card independently
- Show first agent card always visible, with "N more agents working"
expand button for additional agents
- Remove scrollContainerRef prop (no longer needed with native sticky)
- Simplify SingleAgentLiveCard by removing auto-collapse-on-scroll logic
* fix(web): pin primary agent card to top and drop collapse UI
- Remove the mt-4 wrapper around AgentLiveCard in issue-detail so the
sticky wrapper is a direct child of the Activity section — sticky now
has a tall enough parent to stay pinned through TaskRunHistory and
the full comment timeline
- Simplify multi-agent rendering: only the first running agent sticks
to the top, any additional agents render below it and scroll with
the page. Removes the expand/collapse "N more agents working" button
* feat(issues): add fullscreen agent execution transcript view
Adds a new "expand" button (Maximize2 icon) to both the live agent card
and execution history entries. Clicking it opens a fullscreen dialog with:
- A colored timeline progress bar showing execution flow at a glance
(green = agent text, violet = thinking, blue = tool calls,
gray = results, red = errors)
- Detailed event list with type labels, summaries, and expandable detail
- Click-to-scroll: clicking a timeline segment scrolls to that event
- Copy-all button for the full transcript
Inspired by Anthropic's Cloud Managed Agents session transcript UI.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(issues): add runtime and agent metadata to transcript dialog
Adds metadata chips to the transcript dialog header showing:
- Runtime provider (e.g., "Claude Code", "Codex")
- Runtime environment name + mode (local/cloud)
- Agent description
- Duration, tool count, event count, and creation time
Metadata is fetched on dialog open via existing API endpoints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously the sub-issues section only rendered when child issues were
present. This adds a Linear-style "+ Add sub-issues" button below the
description area so users can create sub-issues from an empty state.
- Add Linear-style "Sub-issue of …" breadcrumb under the title with a
parent progress ring
- Refresh sub-issues section: progress ring badge, identifier column,
bordered list, collapse toggle, dashed assignee placeholder
- useUpdateIssue + onIssueUpdated WS handler now also patch and
invalidate the parent's children query so sub-issue status/assignee
changes show up on the parent page without a refresh
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
- Add chevron collapse indicator in header
- Show completion progress (done/total) with tabular-nums
- Use left border indentation for child items (tree view)
- Increase icon size, row padding, and spacing
- Larger + button with better hover state
- Only show section when child issues exist
* fix(board): show total count in Done column header and auto-load on scroll
- Column header now shows server-side doneTotal instead of loaded count
- Replace "Load more" button with IntersectionObserver sentinel for
infinite scroll in the Done column
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(board): move sentinel below imports and stabilize observer
- Move InfiniteScrollSentinel after all import statements
- Use callback ref to avoid recreating IntersectionObserver on every render
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(board): add optional chaining for IntersectionObserver entry
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. Move sub-issues section from sidebar to main content area (below
description), matching Linear's layout. Shows status icon, title,
and assignee avatar for each child issue.
2. Fix real-time refresh: invalidate parent's childIssuesOptions query
in useCreateIssue mutation (onSuccess), onIssueCreated WS handler,
and onIssueDeleted WS handler so sub-issues list updates immediately
without page refresh.
* fix(board): show total count in Done column header and auto-load on scroll
- Column header now shows server-side doneTotal instead of loaded count
- Replace "Load more" button with IntersectionObserver sentinel for
infinite scroll in the Done column
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(board): move sentinel below imports and stabilize observer
- Move InfiniteScrollSentinel after all import statements
- Use callback ref to avoid recreating IntersectionObserver on every render
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The sub-issue code was using direct `api` calls, but the codebase was
refactored to TanStack Query and the `api` import was removed from
issue-detail.tsx, causing a build error on Vercel.
Replace useState+useEffect with useQuery for both parent and child
issue fetching, consistent with the TQ migration.
* 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
- Extract ToggleCommentReactionVars and ToggleIssueReactionVars shared
types so mutation definitions and useMutationState consumers stay in
sync without as-casts on inline types
- Replace new Date().toISOString() with empty string in optimistic
reaction objects to avoid unstable references in useMemo
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace cache-level optimistic updates (onMutate with temp IDs) with
TQ v5's UI-level pattern (useMutationState + render-time derivation)
for both issue-level and comment-level reaction toggles.
The cache-level approach caused a race condition: temp IDs in the cache
couldn't be deduplicated against real IDs from WS events, causing
reaction counts to briefly flash incorrect values (e.g. 0→1→2→1).
The UI pattern keeps the cache clean (always server-confirmed data) and
derives optimistic state at render time from pending mutation variables.
WS events safely update the cache without conflicting with temp data.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Per-issue WS events (comments, activities, reactions, subscribers) were
only handled by component-level useWSEvent hooks that unsubscribe on
unmount. With staleTime: Infinity, this left timeline/reactions/subscribers
caches silently stale — reopening an issue served cached data without
refetching, causing missing comments in inbox and issue detail views.
Add global fallback handlers in useRealtimeSync that invalidateQueries
for the affected issue on every per-issue WS event, ensuring caches are
marked stale even when IssueDetail is unmounted.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(web): add load-more pagination for Done column on issue board
The Done column was capped at 50 issues with no way to load more.
Track doneTotal in the TQ cache and add a useLoadMoreDoneIssues hook
that fetches the next page and merges it into the unified issue cache.
The Done column now shows a "Load more" button when there are
additional items.
- shared/types/api.ts: add doneTotal to ListIssuesResponse
- core/issues/queries.ts: store doneTotal from the done-status response
- core/issues/mutations.ts: add useLoadMoreDoneIssues hook, update
create/delete mutations to maintain doneTotal
- core/issues/ws-updaters.ts: maintain doneTotal on WS events
- features/issues/components/board-column.tsx: accept optional footer
- features/issues/components/board-view.tsx: render Load more button
in Done column
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(web): address review issues in done-column load-more
1. Fix total over-counting: loadMore no longer inflates total since
the initial query already includes all done issues in total count.
2. Fix onIssueUpdated: maintain doneTotal when issue status changes
to/from done via WS events.
3. Make doneTotal optional in ListIssuesResponse since it's a
frontend-only field not returned by the backend API. All reads
now use ?? 0 fallback.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add allowedDevOrigins to Next.js config for non-localhost dev access
Wire CORS_ALLOWED_ORIGINS env var into Next.js allowedDevOrigins config
so that cross-origin HMR/webpack requests from Tailscale or other
non-localhost IPs are not blocked during development.
Fixes#317
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: keep port in allowedDevOrigins
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>