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>
- Board DnD: use local pendingMove state for instant card placement,
bypassing TQ's async setQueryData notification delay
- useUpdateIssue: add list invalidation to onSettled (was only detail)
- use-realtime-sync: add isSelf check to specific issue WS handlers
(prevents redundant cache writes for own mutations)
- Clean up debug console.logs from board-view, issues-page, mutations
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>
P0: Add onSettled: invalidateQueries to 10 mutations that had onMutate
optimistic updates but no server confirmation. With staleTime: Infinity,
missing onSettled means cache could permanently drift from server state.
Mutations fixed:
- useDeleteIssue, useBatchDeleteIssues (issue list)
- useUpdateComment, useDeleteComment, useToggleCommentReaction (timeline)
- useToggleIssueReaction (reactions)
- useToggleIssueSubscriber (subscribers)
- useMarkInboxRead, useArchiveInbox, useMarkAllInboxRead (inbox)
P2: Change refetchOnReconnect from false to true as safety net
for HTTP reconnection before WS reconnection.
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>
- Add core/ layer documentation (queries, mutations, WS updaters)
- Rewrite State Management section: TQ for server state, Zustand for client-only
- Update features table: reflect gutted stores (issues, inbox, workspace)
- Add @core/* import alias examples
- Update Data Flow diagram to include TQ layer
- Restore @core/* path alias in tsconfig + vitest (lost during merge)
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>
- Add ListOpenIssues SQL query (excludes done/cancelled, no LIMIT)
- Add CountIssues SQL query for true total count
- Backend: support open_only=true param, fix total to return real count
- Frontend: two-phase fetch in issue store (all open + first 50 closed)
- Add fetchMoreClosed action for paginated closed issue loading
- Replace all hardcoded limit:200 with store.fetch() calls
Resolves MUL-369
Co-authored-by: Devv <devv@Devvs-Mac-mini.local>
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>
The API can return `config: null` for non-scheduled triggers, but the
type was `Record<string, unknown>` which doesn't reflect this. Update
to `Record<string, unknown> | null` so TypeScript catches unsafe access
at compile time.
Follow-up to #415.
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
- Skip issue refetch when store is cleared during workspace switch by
tracking which issue was already loaded (loadedIdRef pattern)
- Downgrade 404 responses from logger.error to logger.warn in ApiClient
since resource-not-found is a normal business response, not a bug
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the oscillation-prone IntersectionObserver/sentinel pattern with
a simpler always-sticky collapsible card. The card defaults to collapsed
(mini bar) and users toggle it manually. Outer scroll auto-collapses the
timeline to stay out of the way, with scroll-chaining prevention via
overscroll-behavior-y: contain.
Key changes:
- Remove sentinel, IntersectionObserver, and bidirectional isStuck state
- Always sticky at top-4 with unified info color scheme
- Manual toggle via clickable header with grid-rows animation
- Auto-collapse on outer scroll (one-way, prevents oscillation)
- Consolidate three task-end handlers into single handleTaskEnd
- Add hover interaction (muted-foreground → foreground)
- Add aria-expanded for accessibility
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.
- Use google/uuid NewV7() for attachment ID and S3 file key instead of
random hex, so the S3 object name matches the attachment record ID
- Add LinkAttachmentsToIssue query to associate orphaned attachments
with a newly created issue
- Pass attachment_ids in CreateIssue request so uploads during issue
creation (before the issue exists) get linked after commit
- Collect and pass attachment IDs in comment-input and reply-input
so comment creation properly links uploaded files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(web): remove duplicate emoji button on parent comment card
The parent CommentCard rendered two emoji pickers: one in the header
toolbar (QuickEmojiPicker) and another inside ReactionBar (which has
its own QuickEmojiPicker when hideAddButton is not set). Added
hideAddButton to the parent's ReactionBar, matching the pattern
already used in CommentRow for replies.
* fix(web): show emoji button at bottom for long comments
For short comments, the emoji picker only appears in the top-right
toolbar. For long comments (>500 chars or >8 newlines), the ReactionBar
also shows an add button at the bottom so users don't have to scroll
back up to add reactions.
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.
The inbox UI deduplicates items by issue_id (showing only the latest
notification per issue). Previously, clicking archive only archived the
single visible item, so older items for the same issue would reappear.
Now archiving operates at the issue level — both the backend and frontend
archive all inbox items sharing the same issue_id.
When the agent live card collapses to sticky mode, its height drops from
~320px to ~40px. This layout shift caused content below to jump up,
re-triggering IntersectionObserver and creating an infinite loop.
Fix: capture the card's expanded height before collapsing, then set
minHeight on a wrapper div to preserve the space. Content below stays
put, sentinel stays out of view, no oscillation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace hand-rolled Bot icon circle with ActorAvatar component so
agent custom avatars display correctly, consistent with comment cards
and other agent-rendered UI.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Agent live card now uses the sentinel pattern to detect when it scrolls
out of view. When stuck, it collapses to a compact header bar with brand
styling and backdrop blur, with a ChevronUp button to scroll back.
When scrolled back into view, the card seamlessly expands to full view.
Also adds semantic colors to Sonner toast icons (success/info/warning/
error/loading) and fixes icon-to-text alignment in toasts globally.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>