mirror of
https://github.com/multica-ai/multica.git
synced 2026-06-17 11:48:42 +02:00
agent/lambda/header-chip-queued-label
192 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a02b3dfb4a |
feat(issues): move agent live signal into the issue-detail header (#3879)
* feat(issues): move agent live signal into the issue-detail header Replace the in-body sticky "agent is working" card (AgentLiveCard) with a compact chip in the issue-detail header, so the live signal sits in one fixed place and never competes with sticky banners in the content column. - New IssueAgentHeaderChip: avatar(s) + live-ticking blue elapsed time; click opens a popover listing every active task. - Popover reuses ExecutionLogSection's ActiveTaskRow (now exported) so the popover and the right panel are literally the same row — no duplication. - PopoverContent gains an optional keepMounted so the row's confirm dialog survives the popover closing on Stop. - Running rows in ExecutionLogSection drop the blue spinner for a live-ticking blue elapsed timer (panel + popover share this). - Source the chip from the workspace agent-task snapshot filtered by issue (same source as board/list indicators, zero extra network); delete the old AgentLiveCard + its test and its heavy per-issue WS machinery. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(issues): live event count on the agent chip + execution-log rows Show a live "N events (elapsed)" on running agents, consistent across the header chip, its popover rows, and the right-panel execution log. - Read the shared per-task message cache (taskMessagesOptions, kept live by useRealtimeSync's global task:message handler) instead of a bespoke subscription — one source of truth, deduped across chip / popover / panel / transcript, no extra WS wiring. - Extract <RunningStat> (event count in info-blue + elapsed in muted parens) so all surfaces render the running stat identically. - ExecutionLogSection running rows now show the same "N events (elapsed)"; the transcript opened from them streams live from the shared cache. - Chip: single running shows events (elapsed); multiple shows "N working". - i18n: add agent_live.event_count (4 locales). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
d6540a1869 |
fix(clipboard): support copy over http:// via execCommand fallback (#3810)
navigator.clipboard is only exposed in a secure context (https or localhost). On self-hosted instances served over plain http:// it is undefined, so every copy / "copy all" / export button silently failed and left the clipboard empty (GitHub #3781). Add a shared copyText(text): Promise<boolean> helper in @multica/ui/lib/clipboard that prefers the async Clipboard API and falls back to a hidden <textarea> + document.execCommand('copy') for non-secure contexts. Migrate all direct navigator.clipboard.writeText call sites (code blocks, agent transcript copy-all, token / webhook / issue-link copy, etc.) to it, gating success side-effects on the returned boolean, and remove the now-redundant copyMarkdown wrapper. Secure-context users keep the native path unchanged. MUL-3068 Co-authored-by: J <j@multica.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
b0d479c6e7 | fix: use mentions for chat context (#3755) | ||
|
|
91c1e51411 |
feat(editor): add / slash-command palette for invoking agent skills (#3159)
* feat(editor): add / slash-command palette for invoking agent skills Adds a `/` trigger in the chat box that opens a popover listing the active agent's skills. Selecting an item inserts a `[/label](slash://skill/<id>)` token; the daemon extracts those IDs in `buildChatPrompt` and emits an "Explicitly selected skills:" block using the canonical names from the agent's skill registry — labels are display-only and never trusted. Built on Tiptap's `Mention` extension so the suggestion lifecycle, keyboard routing, and IME handling mirror the existing `@` mention UX. Item list is sourced from the React Query workspace cache (no per-keystroke fetch). Gated behind a new `enableSlashCommands` prop so only `chat-input` opts in; other `ContentEditor` consumers (issue editor, comments) are unaffected. Read-only markdown surfaces render the token as a `.slash-command` pill via a custom link renderer + sanitize-schema/url-transform allowlists. Closes #3108 * fix(i18n): add slash_command editor copy for ko/ja The PR added slash_command popover empty-state keys to en + zh-Hans only; locales/parity.test.ts requires every locale to cover every EN key, so ko and ja failed CI. Add the two keys (no_skills_configured, no_results) matching existing skill terminology (스킬 / スキル). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Naiyuan Qing <145280634+NevilleQingNY@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
d013a31db9 |
fix: escape special chars in image alt and file-card filename (MUL-2899) (#3644)
* fix: escape special chars in image alt and file-card filename during Markdown serialization Filenames containing Markdown label characters ([, ], \, (, )) broke the  and !file[name](url) syntax, causing raw Markdown to render instead of the image/file card. - Add shared escapeMarkdownLabel utility - Apply escaping in file-card renderMarkdown - Add renderMarkdown to ImageExtension for alt text escaping - Add regression tests Closes #3616 Co-authored-by: multica-agent <github@multica.ai> * fix: address review — fix tokenizer regex, unescape labels, add regression tests - Remove unused tokenizeFn (TS6133) - Change file-card regex to (?:\\.|[^\]])* to handle escaped brackets - Unescape labels in tokenize() and preprocessFileCards() - Export ImageExtension for testability - Rewrite tests: 3 describe blocks covering ImageExtension.renderMarkdown, file-card tokenizer round-trip, and preprocessFileCards (6 tests total) - typecheck and vitest both pass Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
1aa742053b |
i18n: add japanese locale (MUL-2893) (#3538)
* i18n: add japanese locale * fix: spacing issues * refactor * fix(desktop): set <html lang> before paint to avoid JA Kanji font flash Switch the documentElement.lang sync from useEffect to useLayoutEffect so lang is committed before the first paint. Otherwise Japanese desktop users saw one frame of Kanji rendered with the Chinese-first fallback stack before the html[lang|="ja"] CJK override applied. Also fix the stale selector in the HTML_LANG comment (html[lang^="ja"] -> html[lang|="ja"]). Addresses review nits on MUL-2893. Co-authored-by: multica-agent <github@multica.ai> * fix(docs): tokenize the ideographic iteration mark in JA search Add U+3005 (々) to the Japanese search tokenizer character class. It sits just below the kana blocks, so words like 様々 / 日々 / 個々 previously dropped the mark and split awkwardly, hurting recall. Addresses a review nit on MUL-2893. Co-authored-by: multica-agent <github@multica.ai> * fix(i18n): restore ja locale parity after merging main Merging main brought new EN strings into agents/chat/onboarding/settings/ squads that the ja bundle (authored against an older snapshot) lacked, breaking the locales parity test. Add the Japanese translations for the new keys (workspace logo upload, agents runtime filter, chat session-history stop dialog, onboarding social_github, squad archived status) and drop the two renamed chat window keys (active_group / archived_group) that EN removed in favour of history_group. Fixes the failing @multica/views parity.test.ts on the FE CI for MUL-2893. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: J <j@multica.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
5aa4fb7487 |
MUL-2760: feat(i18n): add Korean locale support (#3369)
* feat: add korean locale support * feat(i18n): localize Korean landing page * fix(i18n): refine Korean landing copy * fix(i18n): refine Korean translations * fix(i18n): translate Korean landing subpages * fix(i18n): route Korean landing docs links * fix(i18n): add Korean use case content * fix(i18n): polish Korean locale copy * fix(i18n): improve Korean landing copy * fix(onboarding): persist Korean helper artifacts Co-authored-by: multica-agent <github@multica.ai> * fix(web): add use case locale fallback Co-authored-by: multica-agent <github@multica.ai> * Align Korean pull requests wording Co-authored-by: multica-agent <github@multica.ai> * fix(i18n): dedupe docs href helper Co-authored-by: multica-agent <github@multica.ai> * fix(i18n): localize changelog dates Co-authored-by: multica-agent <github@multica.ai> * fix(docs): prerender Korean fallback pages Co-authored-by: multica-agent <github@multica.ai> * fix(docs): align fallback hreflang metadata Co-authored-by: multica-agent <github@multica.ai> * fix(i18n): preserve Chinese CJK font fallback order Co-authored-by: multica-agent <github@multica.ai> * chore(onboarding): update localized comment wording Co-authored-by: multica-agent <github@multica.ai> * test(i18n): harden CJK font fallback assertions Co-authored-by: multica-agent <github@multica.ai> * fix(docs): keep Chinese font fallbacks first Co-authored-by: multica-agent <github@multica.ai> * test(i18n): harden locale fallback coverage Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
27b473151b |
refactor(ui): move CODE_LIGATURE_CLASS to zero-dep code-style module (#3463)
Extracts CODE_LIGATURE_CLASS and CODE_LIGATURE_DESCENDANT_CLASS into packages/ui/lib/code-style.ts. Non-markdown CLI command surfaces (onboarding/cli-install-instructions, runtimes/connect-remote-dialog) can now import the class strings without pulling in the shiki + react-markdown + katex dependency graph via the markdown barrel. CodeBlock and Markdown continue to consume the constants from the new module; the markdown barrel no longer re-exports CODE_LIGATURE_CLASS. MUL-2793 Co-authored-by: J <j@multica.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
2662291cf2 | fix(runtimes): disable ligatures in CLI command snippets (#3357) | ||
|
|
bd1fb10afa |
chore: react-doctor cleanup — button types, useContext→use(), toSorted, error fixes (#3350)
- Add explicit type="button" to 61 <button> elements missing the attribute - Replace useContext() with React 19 use() across 16 context consumers - Replace [...arr].sort() with arr.toSorted() in 12 web/desktop files (mobile excluded — Hermes lacks toSorted support) - Fix rules-of-hooks violation: useSidebar try/catch → useSidebarSafe null check - Fix nested component definition: useMemo wrapping HeaderRight → useCallback - Fix missing ARIA: add aria-expanded + aria-controls to combobox in create-squad React Doctor score: 23 → 30. No behavioral changes, no business logic modified. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
963ed5cd0e | feat(comments): allow selecting multiple attachments | ||
|
|
ec1589f7b6 |
fix(deps): add eslint phantom dep detection + fix existing violations (MUL-2654) (#3249)
* fix(deps): add eslint phantom dep detection + fix existing violations (MUL-2654) Introduce eslint-plugin-import-x/no-extraneous-dependencies rule to prevent phantom deps from causing production build splits when pnpm creates peer-dep variants. Fix all existing phantom deps across the monorepo, unify catalog references, and enable desktop smoke CI on PRs. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * revert(ci): remove desktop smoke PR trigger per user feedback The existing smoke workflow only verifies packaging completes — it does not actually start the app or check rendering. This means it wouldn't have caught the white-screen bug (which was a runtime issue, not a build failure). Adding it to PRs would slow CI without providing meaningful protection. The ESLint no-extraneous-dependencies rule is the actual prevention mechanism. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(deps): sync pnpm-lock.yaml for rehype-sanitize dep classification Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(ui): move rehype-sanitize to deps + declare eslint-config (MUL-2654) - Move rehype-sanitize from devDependencies to dependencies (used in production Markdown.tsx) - Add @multica/eslint-config to devDependencies (imported by eslint.config.mjs but previously undeclared) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
90455abd8d |
fix(desktop): preserve tab scroll position across Activity visibility cycles (MUL-2602) (#3196)
Closes #3183. Tabs render under `<Activity mode="visible|hidden">`, which keeps React state but drops DOM scrollTop when the subtree leaves layout. Switching to another tab and back sent users to the top of long discussions. `useTabScrollRestore` records the scrollTop of every element marked with `data-tab-scroll-root` while the tab is visible (capture-phase scroll listener) and restores them in a useLayoutEffect on the next visible transition, before paint. Saved offsets are dropped when the tab's path changes so intra-tab navigation lands at scroll=0 instead of inheriting the previous route's position. Mark scroll containers in views with `data-tab-scroll-root` (issue detail + chat message list ship with the marker; other views can adopt the convention as needed). `useAutoScroll` previously called `scrollToBottom()` on every effect mount, which would have overwritten the restored offset every time a chat tab cycled back to visible. Guard it with a once-per-instance ref. Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
9d5c023145 | fix(markdown): disable code ligatures (#3038) | ||
|
|
fd0fe1d08a |
feat(mobile): Multica for iOS — first version (#2337)
* docs(mobile): establish independence rules and tech-stack baseline - Refactor root CLAUDE.md sharing rules into a single Sharing Principles section, replacing scattered mentions across 10 places with one source of truth + minimal "(web + desktop)" qualifiers on existing sections - Add apps/mobile/CLAUDE.md with locked tech-stack baseline: Expo SDK 54, React Native 0.81, NativeWind 4 + Tailwind 3.4, react-native-reusables, TanStack Query 5, Zustand, expo-secure-store - Mobile pins React directly (does NOT track root catalog:) so the Expo SDK / RN release schedule isn't blocked by web/desktop upgrades - Visual tokens are mobile-owned (transcribed from packages/ui/styles/ tokens.css by hand, not imported); Tailwind v3.4 vs v4 mismatch makes file sharing impractical anyway - Document mobile build/release pipeline (main CI excludes mobile, separate mobile-verify and mobile-release workflows, EAS Update for OTA) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(mobile): v1 shell — auth, workspace switching, inbox + my-issues - Auth: email OTP login mirroring packages/core/auth/store.ts behavior (401 clears token, non-401 preserves; token written only on verify success); expo-secure-store with key "multica_token" matching desktop - Workspace context: /[workspace]/ URL slug as source of truth (deep- link friendly), ApiClient auto-injects X-Workspace-Slug, SecureStore persists last-selected slug for cold-start restore - Bottom tabs (Ionicons): Inbox / My Issues / Settings - Inbox: actor avatar, unread brand-dot, status icon, time-ago + body subtitle. getInboxDisplayTitle mirrored from packages/views/inbox/ components/inbox-display.ts - My Issues: priority bars (matching IssuePriority bar counts from packages/core/issues/config/priority.ts), status dot, identifier, title, assignee avatar - Settings: account info + workspace switcher; switching replaces nav to /[newSlug]/inbox so back stack doesn't trail to old workspace - Multi-env: .env.staging / .env.production / .env.development.local with EXPO_PUBLIC_API_URL; APP_ENV in app.config.ts swaps bundleIdentifier so dev/staging/prod coexist on a device - Build: dev:mobile + dev:mobile:staging scripts; main turbo build/typecheck/lint/test filter excludes @multica/mobile Tech-stack (locked in apps/mobile/CLAUDE.md): - Expo SDK 55, RN 0.83.6, React 19.2.0 (pinned, NOT catalog) - NativeWind 4 + Tailwind 3.4 (intentional mismatch w/ web's Tailwind 4; visual tokens transcribed by hand from packages/ui/styles/tokens.css) - TanStack Query 5 with AppState focus listener; Zustand 5 Not in this commit (intentional): issue detail page, mark-read mutation, pull-to-refresh polish — next iteration. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(mobile): unignore data/ + dedup, layout, mark-read, SVG icons, issue page Critical: previous commit ( |
||
|
|
fbd965e5bf |
feat(onboarding): v3 — thin server, frontend-orchestrated welcome (#3008)
* feat(onboarding): Multica Helper as general workspace assistant + blocking modal
Reshape Multica Helper from an onboarding-only guide into the workspace's
general-purpose AI assistant. The agent's permanent identity (injected as
`## Agent Identity` into every task's CLAUDE.md / AGENTS.md / GEMINI.md
via execenv.InjectRuntimeConfig) is rewritten to three sections that don't
overlap with what the brief already provides:
- Who I am (built-in workspace assistant, not onboarding-only)
- What Multica is + docs/source/issues URLs as knowledge sources
- What I can do (CLI = manifest, `multica --help` is the source of truth)
- Tone (concise, like a colleague, match user's language)
Bootstrap moves out of the in-flow Step 4. Runtime step now exits the
onboarding shell with no bootstrap call; a blocking OnboardingHelperModal
mounts inside the workspace layout (web + desktop) and gates purely on
`me.onboarded_at == null`. The user picks one of three starter prompts
(intro / assign / second_agent) and the modal calls
BootstrapOnboardingRuntime with a new optional `starter_prompt` field that
becomes the seeded onboarding issue's description.
Side effects required to make `onboarded_at == null` an honest signal:
- CreateWorkspace no longer marks onboarded (was atomic with CreateMember).
The "member exists ⟹ onboarded_at != null" invariant is intentionally
broken; guards (useDashboardGuard / desktop App.tsx) already tolerate
this — comments updated to reflect the new contract.
- AcceptInvitation still marks (invitee skips the modal in someone
else's workspace). Code comment added warning future removers.
- resolvePostAuthDestination flips to workspace-presence-first: a user
with a workspace lands in it regardless of `onboarded_at`, so the
modal can pick up an interrupted setup on relogin.
Other backend changes:
- `onboardingAssistantDescription` rewritten ("Built-in workspace assistant…")
- `onboardingAssistantInstructions` rewritten to the 3-section identity
- `bootstrapOnboardingRuntimeRequest.StarterPrompt` (optional, 2 KiB rune
cap, empty-falls-back-to onboardingIssueDescription)
Frontend changes:
- Delete `packages/views/onboarding/steps/step-teammate.tsx` (no longer a
persisted step)
- `ONBOARDING_STEP_ORDER` and `OnboardingStep` type drop `"teammate"`
- `handleRuntimeNext` exits via `onComplete(workspace, undefined)` — no
bootstrap, `onboarded_at` stays NULL so the modal fires
- Runtime step next-button copy → "Start exploring" / "开始探索"
- New `packages/views/workspace/onboarding-helper-modal.tsx`:
Base UI Dialog, dismissible=false, three localized cards, mutation
invalidates agents + issues queries then navigates to the seeded issue
- Mounted in both `apps/web/app/[workspaceSlug]/layout.tsx` and
`apps/desktop/src/renderer/src/components/workspace-route-layout.tsx`
Tests:
- Backend: TestBootstrapOnboardingRuntime_{With,No}StarterPrompt and
TestCreateWorkspace_DoesNotMarkOnboarded
- Frontend: onboarding-helper-modal.test.tsx covers all four gating
conditions, three-card behavior, mutation pending state, and the
"no close button" invariant
Compatibility:
- Already-onboarded users: zero impact (modal can't fire)
- Invitees: AcceptInvitation still marks → modal can't fire
- Skip-runtime path: BootstrapOnboardingNoRuntime still marks → modal can't fire
- Old desktop / web clients: legacy teammate-step path keeps working
(bootstrap accepts missing starter_prompt) — the new modal only fires
on the new frontend bundle
- Avatar SVG kept (asterisk variant) — no migration of existing Helper
agents, only newly-created Helpers pick up the new instructions/description
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(desktop): suppress OnboardingHelperModal while a WindowOverlay is open
On desktop, App.tsx auto-creates a tab pointing at the user's first
workspace as soon as workspaces.length flips from 0 → 1 (during onboarding
Step 2). The new tab mounts WorkspaceRouteLayout under the overlay,
which mounts OnboardingHelperModal. The modal's Portal renders to
document.body — appearing AFTER the WindowOverlay in DOM order, so its
z-50 wins and the modal floats in front of the still-active onboarding
Step 3 (runtime).
Suppress the modal whenever any WindowOverlay is active. When the overlay
closes (onComplete fires after the user finishes onboarding), the modal
re-evaluates `me.onboarded_at == null` and pops on its own.
Web is unaffected (onboarding flow lives at /onboarding, not under
/[workspaceSlug]/, so WorkspaceRouteLayout never mounts during the
onboarding flow).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(onboarding): add v2 refactor plan
Captures the design + 8-step implementation order for collapsing the
onboarding state machine: single mark-onboarded entry point, persisted
Step 3 user choice, dumb Modal, single install-runtime seed call site.
Includes old-user compatibility analysis (4 existing gates) and per-PR
risk/rollback.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(db): persist Step 3 runtime choice on user record (MUL-onboarding-v2)
Adds onboarding_runtime_id UUID NULL + onboarding_runtime_skipped BOOLEAN
columns to "user" and the CHECK constraint enforcing the 3-state machine
(unset / picked-runtime / explicit-skip; the fourth combination is
forbidden). ON DELETE SET NULL on the FK so a deleted runtime degrades
to "unset" rather than dangling.
PatchUserOnboarding gains the two narg fields plus CASE expressions that
collapse the runtime/skipped pair atomically — a follow-up PATCH that
flips one side now clears the other in the same statement, instead of
preserving it via per-field COALESCE and tripping the CHECK constraint.
Backwards compatible for existing users: both new fields default to
(NULL, false), which is the "unset" leaf of the state machine, and four
upstream gates on me.onboarded_at != null already short-circuit the
new fields' readers for everyone who's already onboarded.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(server): collapse onboarding side effects to service layer
Introduces OnboardingService.MarkComplete and
WorkspaceContentService.{Ensure,Seed}InstallRuntimeIssue as the single
authorities for the two onboarding side effects that used to be
duplicated across four handlers:
- MarkUserOnboarded + claim starter_content_state +
optional install-runtime fallback seed: was inline in
BootstrapOnboardingRuntime, BootstrapOnboardingNoRuntime,
AcceptInvitation, and CompleteOnboarding.
- install-runtime issue seeding: was inline in CreateWorkspace and
AcceptInvitation as a "no runtime yet" fallback.
After this refactor:
- MarkUserOnboarded is called from exactly one place (the service).
- install-runtime issue is seeded from exactly one place (the service).
- CreateWorkspace deliberately does not seed — the new
/ensure-onboarding-content endpoint (also added here) lets the
workspace-entry init component request the seed on first mount, so
workspaces created but never opened don't accumulate stale issues.
- The PatchOnboarding handler now accepts the new runtime_id /
runtime_skipped fields and rejects (uuid, skipped=true) up front.
- UserResponse exposes the two new persisted fields so the frontend
can read them off `me` without an extra round-trip.
Handler-side tests added: TestPatchOnboarding_RuntimeChoiceSwitch (the
explicit cross-request switch path that the original COALESCE design
would have 500'd on) + TestPatchOnboarding_PreserveUntouched.
Old handler-local file no_runtime_issue.go is deleted; its content
moved to service/workspace_content.go with the helpers exported.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(core): API + types for persisted onboarding runtime choice
User type / Zod schema gain onboarding_runtime_id (string | null) and
onboarding_runtime_skipped (boolean); EMPTY_USER + test fixture updated
to match. api.patchOnboarding accepts the new optional fields and the
new api.ensureOnboardingContent endpoint is wired so the workspace
shell can request the fallback seed.
Two new store helpers — recordOnboardingRuntimeChoice(runtimeId) and
recordOnboardingRuntimeSkipped() — replace the prior pattern of
Step 3 calling bootstrap directly. They PATCH the user's choice, sync
the auth store, and return. Mutually exclusive on the server side via
the CHECK constraint; the client just ships one intent at a time.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(workspace): WorkspaceOnboardingInit single decision point + dumb Modal
Replaces OnboardingHelperModal's self-gating render path with a 4-branch
dispatcher that runs once on workspace-shell mount:
branch 0 me.onboarded_at != null → ensure install-runtime issue
fallback, render nothing
branch 1 me.onboarding_runtime_skipped → SkipBootstrapping component:
loading veil → bootstrap →
navigate. On failure shows
a Retry UI instead of
silently freezing the veil
branch 2 me.onboarding_runtime_id → render Modal with the
runtime id from `me` (no
internal list query)
branch 3 (none of the above) → useEffect navigate back to
/onboarding so the user
walks Step 3 again
The Modal itself is now a dumb component — receives `workspace` and
`runtimeId` as props, no internal gates, no runtimeListOptions query.
Tests rewritten to cover the props-driven render + pick-card paths;
the prior gating tests move into the new
workspace-onboarding-init.test.tsx alongside the M2 retry-on-failure
behaviour.
Mounted in both apps/web/app/[workspaceSlug]/layout.tsx and the desktop
workspace-route-layout. Desktop keeps its `!overlayActive` suppression
guard so the init doesn't portal-jump in front of an active
WindowOverlay.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(onboarding): Step 3 records user choice instead of calling bootstrap
handleRuntimeNext now PATCHes the user's pick (recordOnboardingRuntime
{Choice,Skipped}) and navigates straight into the workspace shell. The
workspace-entry WorkspaceOnboardingInit reads the persisted choice off
`me` and runs the appropriate branch — Step 3 is pure intent capture
with zero side effects on its own.
PATCH must succeed before navigation: if it fails the user stays on
Step 3 with a toast, because navigating with no persisted intent would
land them in WorkspaceOnboardingInit's branch 3 "no decision yet" rescue
and trigger a redirect loop back to /onboarding.
The prior asymmetry (Connect deferred bootstrap to the workspace, Skip
ran bootstrap inline) is gone — both paths defer to the workspace
shell now.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(onboarding): v3 — thin server, frontend-orchestrated welcome
Collapse v2's persisted runtime-choice fields + 4-branch dispatcher +
OnboardingService/WorkspaceContentService stack down to a single rule:
`onboarded_at` is the only state field, layout hard-gates on it, and the
welcome experience after Step 3 is owned entirely by the frontend.
V3 flow
- Step 3 button: await POST /api/me/onboarding/complete (mark only) +
park a transient signal in `useWelcomeStore` + navigate
- Workspace layout: hard gate `onboarded_at == null` -> /onboarding
- `<WelcomeAfterOnboarding />` reads the welcome-store signal:
- runtime path: find-or-create Multica Helper via generic createAgent
with bilingual instructions from `templates/helper-instructions.ts`,
blocking modal with 3 starter cards, pick -> createIssue + navigate
- skip path: provision install-runtime (in_progress) -> agent-guide
(todo, body embeds install-runtime mention chip) -> follow-up comment
on install-runtime mentioning agent-guide; then pop celebration
modal with 🎉 emoji pop animation, 2 read-only preview cards, single
[Got it] CTA that navigates to install-runtime
Server cleanup
- Drop OnboardingService, WorkspaceContentService, v2 runtime-choice
columns/CHECK on user, EnsureOnboardingContent endpoint
- CompleteOnboarding/AcceptInvitation call qtx.MarkUserOnboarded
directly (no service indirection)
- BootstrapOnboardingRuntime / BootstrapOnboardingNoRuntime kept as a
deprecation shim in onboarding_shim.go for desktop < v3 during the
rollout window — handlers inlined to qtx.* calls, no service layer
Localization
- Persisted strings (issue titles/bodies, Helper instructions/
description, comment prefix) live as TS const `{en, zh}` maps in
`packages/views/onboarding/templates/` — i18n bundle staleness can no
longer write raw key paths into DB
- UI-rendered strings (modal copy, status chips, buttons) stay in
`packages/views/locales/{en,zh-Hans}/onboarding.json`
- Language picked from live `i18n.language` (not `me.language`, which is
null for new users until they pick a preference)
Race protection
- Module-level promise dedupe (`findOrCreateHelper`, `seedIssueDeduped`,
`postCommentDeduped`) so React StrictMode double-mount can't fire two
parallel API calls that the server would then 409
Cross-references between the two skip-path issues render via Multica's
mention-chip protocol `[<identifier>](mention://issue/<uuid>)` so they
match the styled IssueChip pills used elsewhere.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(onboarding): welcome-after-onboarding modal redesign + cross-user safety
Welcome modal polish (the post-Step-3 surface this branch already
introduced):
Runtime path
- Helper avatar replaces the bouncy 🎉 hero; tone-down animation to
fade. New copy: "Hi, welcome to Multica / I'm your first Agent
assistant" + capability hint sentence so users discover assignment +
chat from the first screen.
- Cards changed from "click = submit" to multi-select with the existing
border-primary + ring selection pattern used by compact-runtime-row;
bottom CTA "Assign N tasks to me →" appears only with N>0.
- New starter cards: intro / tour / welcome_page (the last one tells
Helper to paste an HTML welcome page into the issue comment — works
on any runtime regardless of fs access).
- Success state added between createIssue and navigation: 🎉 +
"All set!" + "Sit tight ☕ — your {agentName} is on it" + inbox/chat
hints, single [Got it] button.
- Title/prompt for starter cards now live in TS const
HELPER_STARTER_PROMPTS (persisted to DB — must not depend on i18n
bundle being loaded); subtitle stays in onboarding.json.
Skip path
- Body restructured into three independent ```md blocks (Name /
Description / Instructions) so each picks up the markdown renderer's
per-block copy button — no manual extraction.
- ZH body now embeds the ZH Helper Description + Instructions (was
Chinese-around-English-block).
- Follow-up comment uses Multica's mention-chip protocol
[identifier](mention://issue/uuid) so it renders as the styled
IssueChip pill.
- Issue titles bilingual with "Step 1 / Step 2" prefix.
Cross-user / cross-workspace safety (code review feedback)
- web onLogout + desktop handleDaemonLogout now call
useWelcomeStore.reset() so user B logging into the same browser
doesn't inherit user A's signal.
- WelcomeAfterOnboarding gates on
currentWorkspace.id === signal.workspaceId — prevents firing the
modal in workspace B when the signal was parked for workspace A
(desktop multi-tab, back/forward, deep-link).
- Module-level promise dedupes (pendingHelperSetup,
pendingIssueSeed, pendingCommentSeed) for the three API calls so
React 18+ StrictMode dev double-mount can't race-create duplicates.
Other small fixes carried in this commit
- Helper instructions / agent description / starter card titles all
read i18n.language (not me.language, which is null for new users
who haven't picked a UI language preference yet).
- Reverted welcome-emoji-pop animation to a small fade for the runtime
avatar (kept the bouncy variant for the skip 🎉 hero where the
celebration is the whole point).
- Removed the duplicate 🎉 from the skip modal title (kept the hero
one only).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(views): i18n hardcoded "Close" in welcome FullScreenError
CI lint (i18next/no-literal-string) blocked on a literal "Close" string
inside `FullScreenError` — surfaced as a nit in the original code
review but missed in the merge. Add `error_close` to onboarding.json
(EN: "Close" / ZH: "关闭") and thread it through as a `closeLabel`
prop, matching the existing `retryLabel` plumbing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
39f43a9a98 |
refactor(editor): unify attachment rendering into a single <Attachment> component (#2850)
Collapse the five separate attachment render paths (file-card NodeView,
image NodeView, readonly markdown img/fileCard renderers, AttachmentList
standalone fallback, and the parallel packages/ui/markdown renderer) into
one <Attachment attachment={a} /> dispatcher.
Fixes a P0 visual regression: a PNG attached to a message but not inlined
in the markdown body used to render as a gray "file card" because
getPreviewKind() lacked an "image" branch and image rendering bypassed
the dispatcher entirely. Now every surface routes through <Attachment>,
so the same PNG renders as a real <img> with hover toolbar and
preview-modal everywhere.
Key changes:
- PreviewKind gains "image"; getPreviewKind() detects image/* + common
extensions before the html/text branches (so svg stays image, not text).
- AttachmentPreviewModal gains case "image" (replaces the standalone
ImageLightbox, which is deleted).
- New packages/views/editor/attachment.tsx owns all kind-aware routing
(image | html | file) and dispatches preview modal + download via the
existing useAttachmentPreview / useDownloadAttachment hooks. Subsumes
the deleted AttachmentBlock.
- AttachmentInput.url accepts a forceKind hint so callers that *know*
the structural kind (markdown , Tiptap image node) skip the
filename-based autodetect — fixes a regression where empty or
descriptive alt text would route an image to the file-card chrome.
- Tiptap NodeViews (file-card.tsx, image-view.tsx) shrink to thin
wrappers that forward editor hints (selected, deleteNode, uploading)
to <Attachment>.
- ReadonlyContent and AttachmentList each mount their own
AttachmentDownloadProvider so url → record resolution works outside
ContentEditor's provider.
- packages/ui/markdown gains optional renderImage / renderFileCard slot
props; packages/views/common/markdown.tsx injects <Attachment> into
those slots and threads message attachments through to chat /
skill-file viewers.
- chat-message-list passes message.attachments to every <Markdown> call
site and renders a standalone AttachmentList under each bubble for
attachments not referenced in the body.
Tests: attachment.test.tsx covers 9 scenarios (record image / pdf / html;
url-only image with resolver hit and miss; uploading state; editable
delete; forceKind regression). attachment-preview-modal.test.tsx gains
image-dispatch cases. 652/652 unit tests pass.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
d9ae891064 |
fix(avatar): stop bg-muted bleeding through transparent images (#2670)
ActorAvatar applies bg-muted on its container regardless of whether an image is loaded, so transparent regions of PNG/SVG avatars reveal the grey placeholder. agent-detail-inspector also wraps ActorAvatar in an outer bg-muted div, layering a second grey square. Make bg-muted conditional on the fallback state in ActorAvatar, and drop the redundant bg-muted from avatar-picker's image-loaded branch and the two inspector wrappers. Empty-state placeholders unchanged. |
||
|
|
8cc48b1176 | fix(ui): vertically center SelectItem content (#2782) | ||
|
|
3698fd85d5 |
feat(views): show Total in daily token/cost chart tooltips (MUL-2282) (#2704)
* feat(views): show Total in daily token/cost chart tooltips (MUL-2282) Add a Total row at the bottom of the daily-tokens-chart and daily-cost-chart tooltips so users can see the precise stack sum on hover, in addition to the per-stack breakdown. Implemented by extending shared ChartTooltipContent with an optional `footer` prop (ReactNode | (payload) => ReactNode) that renders below the items with a top divider; backwards-compatible (no behavior change when footer is omitted). Co-authored-by: multica-agent <github@multica.ai> * fix(views): i18n Total label in chart tooltips (MUL-2282) Lint rule i18next/no-literal-string flagged the hardcoded "Total" string in daily-cost-chart and daily-tokens-chart tooltips. Move it to runtimes.charts.tooltip_total and read via useT. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
1cb926d52d |
feat(views): refine navigation progress bar with brand color and glow (MUL-2269) (#2681)
* feat(views): refine navigation progress bar with brand color and glow (MUL-2269) The previous 1px bg-primary bar read as near-black on light theme and snapped on/off in a single frame, which felt abrupt despite being a small visual element. Switch to a 2px brand-colored sweep with right-edge glow, slower 1.4s cubic-bezier easing, and a 200ms fade-out so completion doesn't pop. - Container: h-px → h-0.5 (2px); always mounted with opacity-driven fade - Bar: bg-primary → bg-brand + two-layer box-shadow glow via color-mix - Keyframe: 1.1s ease-in-out → 1.4s cubic-bezier(0.4, 0, 0.2, 1) Zero new design tokens (reuses existing --brand) and zero tailwind config changes. Desktop unaffected — same component, same prefetch=no-op path. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(views): unmount nav progress sweep when hidden (MUL-2269) Hiding the bar with opacity-0 left the inner element's `infinite` keyframe animation running on every dashboard page, defeating the perceived-perf goal. Mount the sweep only while navigating, plus the 200ms fade tail (unmount on opacity transitionend), so nothing animates while hidden. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
e8d6c912c4 |
feat(views): prefetch + transition + skeleton for snappy web navigation (MUL-2269) (#2677)
Internal navigation on web feels laggy because clicking a sidebar link blocks 0.2–0.6s with zero visual feedback — no prefetch, no Suspense fallback in the dashboard segment, and no React transition to mark the route commit as pending. This change adds the three pieces App Router needs to make the click→commit window feel instant, scoped to the (dashboard) segment so auth/landing keep their existing chrome: - NavigationAdapter gains an optional prefetch(path). The web adapter wires it to router.prefetch; desktop leaves it undefined (react-router has no equivalent and doesn't need one). AppLink prefetches on hover/focus and preserves caller-supplied onMouseEnter/onFocus/onClick. - NavigationProvider wraps push/replace in useTransition and exposes the pending flag via useIsNavigating(). Every useNavigation().push caller — sidebar AppLink, command palette, post-create modal jumps — picks this up automatically. - New apps/web/app/[workspaceSlug]/(dashboard)/loading.tsx renders a minimal skeleton during cold transitions inside the dashboard segment only. - DashboardLayout renders a 1px top progress bar driven by useIsNavigating. packages/views remains free of next/* imports; desktop is unaffected by construction (no prefetch, transition flips quickly, no loading.tsx). Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
681d720671 |
fix(issues): file-card render for self-host with local storage (#2349)
* fix(issues): file-card render for self-host with local storage Fixes #1520. When self-hosting without S3, the upload handler returns site-relative URLs like /uploads/workspaces/<wsId>/<file>. Four frontend regexes only matched https?://, so persisted !file[name](/uploads/...) markdown failed to parse and leaked through as raw text in the issue view, chat, skill file viewer, and board card preview. Narrow allow-list: the relative branch only accepts /uploads/ — not any /-prefixed href — so protocol-relative //evil.com/x, path-traversal /../api/x, and other internal /api/... paths are rejected. Without this, a stored file-card with an attacker-chosen filename and a //host/x href would turn into a one-click external-site jump via window.open from inside an issue (per review feedback on #2349). Single source of truth: packages/ui/markdown/file-cards.ts now exports isAllowedFileCardHref + FILE_CARD_URL_PATTERN. The four sites use one of them, so the next regression is cheaper than restoring four parallel regexes. - packages/ui/markdown/file-cards.ts: helper + URL pattern. - packages/views/editor/extensions/file-card.tsx: Tiptap tokenizer composes from FILE_CARD_URL_PATTERN. - packages/views/editor/readonly-content.tsx: sanitiser uses helper. - packages/ui/markdown/Markdown.tsx: sanitiser uses helper. - packages/views/issues/components/board-card.tsx: strip markdown tokens from the line-clamped board preview so raw !file[...] no longer leaks there either. - packages/ui/markdown/file-cards.test.ts: covers accept (/uploads/ok, https://cdn/x) and reject (javascript:, data:, //evil.com/x, /../api/x, /api/x, empty, ftp:, bare 'uploads/x') for both the helper and the parser composed from the pattern. javascript:, data:, and other dangerous schemes remain rejected. * test(markdown): move file-card href allow-list test into @multica/views Per review feedback on #2349: keep the test where vitest is already running instead of bootstrapping a new test runner inside @multica/ui. The test now lives at packages/views/editor/file-card-href.test.ts and imports isAllowedFileCardHref / FILE_CARD_URL_PATTERN / preprocessFileCards from the @multica/ui/markdown public surface, exercising the same 30 cases. Reverts the @multica/ui package.json test script + vitest devDep + the local vitest.config.ts that the previous commit added; the package goes back to typecheck + lint only, matching every other ui-only package in the monorepo. --------- Co-authored-by: Lalbadshah <11599756+Lalbadshah@users.noreply.github.com> |
||
|
|
cde3867d3b |
feat(sidebar): top/bottom scroll fade mask (MUL-2150) (#2536)
* feat(sidebar): top/bottom scroll fade mask (MUL-2150) Apply useScrollFade to SidebarContent so the menu list softly fades into the header / footer when overflowing, matching the existing pattern used in chat list and onboarding steps. Co-authored-by: multica-agent <github@multica.ai> * fix(ui): useScrollFade re-evaluates on content mutations ResizeObserver only fires on the observed element's own box. When a flex / auto-height container's children grow asynchronously (sidebar pinned items loading from TanStack Query, collapsibles expanding), scrollHeight changes but clientHeight does not — mask stayed 'none' until the user scrolled. Add a MutationObserver on childList to recompute fade when content is inserted or removed. Co-authored-by: multica-agent <github@multica.ai> * test(paths): include squads in workspace route consistency check main added the squads parameterless route to paths.workspace() in #2505 but the C4 consistency assertion wasn't updated, turning frontend CI red on every PR. Add 'squads' to both the parameterless-method set and the segment-mapping table. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
29082f7cfe |
feat: implement Squad feature MVP (#2505)
* feat: implement Squad feature MVP
- Add migration 084_squad: squad, squad_member, squad_activity_log tables
- Extend issue.assignee_type to support 'squad'
- Add sqlc queries for squad CRUD, member management, activity logs
- Add Go handler with full Squad API (CRUD, members, activity log)
- Register routes: /api/squads/*, /api/issues/{id}/squad-activity, /api/squad-activity
- Add Squad trigger logic:
- Assign Squad immediately triggers leader
- Every external comment on squad-assigned issue triggers leader
- Anti-loop: squad members' comments don't trigger leader
- Dedup: skip if leader already has pending task
- Add squad activity log API (方案 B) for leader no-op recording
- Add frontend TypeScript types (Squad, SquadMember, SquadActivityLog)
- Add protocol events: squad:created, squad:updated, squad:deleted
Co-authored-by: multica-agent <github@multica.ai>
* fix: address PR review blocking issues
1. validateAssigneePair now accepts 'squad' assignee_type
2. All squad endpoints validate workspace ownership via GetSquadInWorkspace
3. CreateSquadActivityLog restricted to squad leader agent only
4. AddSquadMember validates member exists in workspace
5. UpdateSquad auto-adds new leader to squad members
6. DeleteSquad transfers assigned issues to leader before deletion
7. IssueAssigneeType includes 'squad' in frontend types
Co-authored-by: multica-agent <github@multica.ai>
* feat: soft-delete squads via archive instead of hard delete
- Add migration 085: archived_at + archived_by columns on squad table
- ListSquads now excludes archived squads (ListAllSquads for admin)
- DeleteSquad → ArchiveSquad (sets archived_at, preserves all records)
- Transfer squad-assigned issues to leader before archiving
- SquadResponse includes archived_at/archived_by fields
- Frontend Squad type updated with nullable archived fields
Co-authored-by: multica-agent <github@multica.ai>
* feat: re-add Squads frontend entry (sidebar nav + pages)
Re-applies the frontend squad entry that was lost during a merge:
- Sidebar nav: Squads item with Users icon
- Paths: squads() and squadDetail() in workspace paths
- Routes: /squads and /squads/[id] pages
- Views: SquadsPage (list) and SquadDetailPage
- i18n: en 'Squads' / zh '小队'
- Reserved slug: 'squads'
Co-authored-by: multica-agent <github@multica.ai>
* fix: fix SquadsPage rendering - use PageHeader children pattern
PageHeader takes children, not title/actions props. The incorrect
usage caused a React rendering error. Now matches the pattern used
by autopilots and agents pages.
Co-authored-by: multica-agent <github@multica.ai>
* fix(squads): add API client methods and package export for squads pages
* feat: complete Squad frontend - create dialog, member management, API methods
- Add CreateSquadModal with name/description/leader selection
- Register 'create-squad' in modal registry
- Wire 'New Squad' button to open the modal
- Add full API client methods: createSquad, updateSquad, deleteSquad,
addSquadMember, removeSquadMember
- Rewrite SquadDetailPage with:
- Member list showing resolved names
- Add/remove member UI
- Archive squad button
- Back navigation to squads list
Co-authored-by: multica-agent <github@multica.ai>
* feat: improve Squad UI - match create agent dialog style
- CreateSquadModal: proper Dialog with Header/Description/Footer,
agent picker with avatars, textarea for description
- SquadDetailPage: centered max-w-2xl layout, ActorAvatar for members,
Crown badge for leader, textarea for member description,
improved spacing and visual hierarchy
- Renamed 'role' field label to 'Description' in add member form
(describes the member's responsibilities in the squad)
Co-authored-by: multica-agent <github@multica.ai>
* feat(squad): add avatar, instructions; drop unique-name constraint
- 086: add squad.avatar_url
- 087: drop unique constraint on squad.name (squads with the same
name are legitimate across teams; uniqueness was an accidental
product constraint)
- 088: add squad.instructions (text, default '')
- UpdateSquad now COALESCEs avatar_url + instructions
- handler exposes Instructions in SquadResponse and accepts it in
UpdateSquad
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(squad): assignable + mention target; trigger leader on assign
- assignee picker and @mention suggestion list squads alongside
agents and members; renders squad avatar/icon
- creating or updating an issue with assignee_type=squad enqueues
a task for the squad's current leader (mirrors agent-assignee
parking-lot rule: skip backlog only)
- workspace queries/hooks expose squads where needed for the
pickers
- locales updated for new picker copy
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(squad): agent-style detail page with members + instructions tabs
- restructure squad detail page to mirror the agent detail page:
320px inspector (creator, leader, created/updated) + tabbed
pane (Members | Instructions) with dirty-guard AlertDialog
- inline name + avatar editing on the inspector
- inline description editor (modal textarea)
- members tab: leader + member picker with role descriptions,
swap leader, edit member roles, remove
- instructions tab: ContentEditor + Save (mirrors agent pattern)
- squads list shows the squad avatar/icon
- core types + api.updateSquad accept avatar_url + instructions
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(squad): inject leader briefing on claim (protocol + roster + instructions)
When a squad's leader agent claims a task on a squad-assigned issue,
append a system-level briefing to the agent's Instructions composed of:
1. Squad Operating Protocol — hard-coded rules: leader is a
coordinator, dispatch via @mention, stop after dispatching,
resume on re-trigger, do not work outside the roster.
2. Squad Roster — leader self-row plus one row per non-archived
member with a literal mention markdown string ([@Name](mention://
agent|member/<UUID>)) the leader can paste verbatim. Round-trips
through util.ParseMentions, enforced by a contract test.
3. Squad Instructions — the user-defined squad.instructions block,
omitted entirely when empty so we do not leave a dangling heading.
Non-leader members claiming the same issue receive no briefing.
Tests cover: full squad with mixed agent/human members, lone leader,
archived agents skipped, empty user instructions, mention round-trip,
and the leader/non-leader claim-handler gate.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(squad): tell leader not to restate issue context in dispatch comment
After observing leaders padding their delegation comments with full
re-summaries of the issue body and prior discussion, make the
Operating Protocol explicit:
- assignees on Multica already have the full issue (title,
description, all comments, attachments) and workspace context;
- delegation comments should add only what cannot be inferred
(who is picked, why, extra constraints), aim for two or three
sentences;
- restating context is now an explicit hard rule violation.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(squad): unify leader evaluation into activity_log, add CLI command
- Squad member comments now trigger leader (only leader self-excluded)
- Replace squad_activity_log with activity_log (action: squad_leader_evaluated)
- Add CLI: multica squad activity <issue-id> <outcome> --reason
- Add API: POST /api/issues/{id}/squad-evaluated
- Update squad operating protocol to require evaluation recording
- Remove squad_activity_log table from schema and generated code
* feat(cli): add squad list, get, member list commands
* fix(squad): address review findings (P1+P2)
P1 fixes:
- Add 'squads' to reserved_slugs.json (source of truth)
- Add 'create-squad' to ModalType union
- Remove unused leaderOpen/selectedLeader in create-squad modal
- Replace literal JSX strings with i18n selectors (en + zh-Hans)
P2 fixes:
- Add 'squad' to mention regex (MentionRe)
- Fix human member lookup in squad briefing (use GetUser directly)
- Add squads routes to desktop app
- Add squad:created/updated/deleted to WSEventType + invalidation
- Reject archived squads as issue assignees
* fix(squad): restore zh-Hans key, publish activity event, invalidate issues on archive
- Restore create_project.title in zh-Hans modals.json (dropped by prior edit)
- Publish activity:created WS event after squad leader evaluation
- Invalidate issue queries on squad:deleted (archive transfers assignees)
- Add creator info to squad list cards
* fix(squad): realtime sync, rerun support, leader validation
- Use workspaceKeys.squads prefix for detail/member queries (realtime invalidation)
- Publish squad:updated after add/remove/role-change member mutations
- Support rerun for squad-assigned issues (targets leader agent)
- Reject assignment to squads whose leader is archived
---------
Co-authored-by: multica-agent <github@multica.ai>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
||
|
|
19c40c5d68 |
fix(ui): translate hardcoded English strings in shared ui package (#2526)
The four user-visible strings exposed by packages/ui rendered untranslated on every page that used them: - file-upload-button.tsx — "Attach file" aria-label/title - sidebar.tsx — "Toggle Sidebar" sr-only label/aria-label/title - pagination.tsx — "Go to previous/next page" aria-labels - CodeBlock.tsx — "plain text" language fallback + "Copy code" aria-label/tooltip Root cause: the package had no i18n hookup at all because the package boundary rule forbids importing @multica/core. Replicating the pattern five times would have been the same hack five times. Hooking up react-i18next directly is the structurally clean fix — i18next is a generic library, not business logic, and the upstream I18nextProvider already exposes the instance via context. To let packages/ui typecheck the selector form standalone (i.e. without the views resource-types augmentation in scope), the augmentation is split: views declares everything except the `ui` namespace on a new global `I18nResources` interface, and packages/ui contributes the `ui` slice via declaration merging in packages/ui/types/i18next.ts. Views' resources-types side-effect-imports that file so both packages see the merged shape during downstream typechecks. Scope intentionally excludes: - packages/ui/components/common/error-boundary.tsx — keeping its fallback in English so a render-time crash never depends on i18n being healthy. - apps/desktop/src/renderer/src/components/update-notification.tsx — ships with the next desktop release, not via this PR. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
e3e61c161c |
fix(inbox): show Multica logo for system-actor notifications (#2479)
Notifications from system actors (e.g. GitHub PR closed) were rendering with an "S" initials fallback. The avatar now shows the Multica icon when actor_type === "system", matching the platform's brand. Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
34a7ba9865 |
fix(chat): unify chat and comment send shortcut to Mod+Enter (#2398)
Chat input had `submitOnEnter` enabled while the comment editor used `Mod+Enter`. Two consequences: - Inconsistent muscle memory between the two inputs. - In chat, bare Enter sending stole the only key that continues a TipTap bullet/ordered list. Shift+Enter falls through to HardBreak (a <br> inside the same list item), so bullet lists were stuck at one item. Drop `submitOnEnter` from the chat input so it follows the editor default. Mod+Enter (⌘↵ / Ctrl+Enter) sends in both places; bare Enter now continues lists and inserts paragraphs as users expect. Surface the shortcut on the SubmitButton via a new optional `tooltip` prop, and route the comment input through SubmitButton instead of an ad-hoc Button — same affordance, deduped. Add unit coverage for the submit-shortcut extension that pins Mod-Enter, the submitOnEnter=false case, IME, and code-block guards. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
4872dc50bd |
fix(priority): align dropdown badge colors with PriorityIcon semantic tokens (#2315)
The priority badge in the issue/project priority picker dropdown used a parallel `bg-priority` orange color family (with opacity gradient for level intensity), while the standalone PriorityIcon outside the dropdown used semantic tokens — destructive for Urgent, warning for High/Medium, info for Low. The two languages produced an inconsistency users noticed most clearly on Low: blue in the list, orange in the picker. Switch the dropdown badges to the same semantic tokens as the icon, and remove the now-unused `--priority` / `--color-priority` design token from both `packages/ui/styles/tokens.css` and `apps/web/app/custom.css`. Closes multica-ai/multica#2289 Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
bb3d2b70ea |
fix(ui): let DropdownMenu popup size to content (#2306)
DropdownMenuContent had `w-(--anchor-width)` which locks the popup width to the trigger. With icon-sm kebab triggers (~32px) the popup was clamped by `min-w-32` to 128px, and longer items like "Unresolve thread" / "标记为已解决" wrapped onto two lines. Anchor-width matching is the right behavior for Select / Combobox (both keep that class), but a generic kebab menu should size to its own content. Drop the `w-(--anchor-width)` and keep `min-w-32` as the floor. Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
48e3131bf9 |
feat: harden desktop frontend against API response drift (MUL-1828) (#2208)
* docs(claude): add API Response Compatibility section Narrows the existing "no backwards compat" rule to internal code only, and adds a new section that codifies the defensive boundary at API edges: parse-don't-cast, never pin UI to a single field, enum drift must downgrade not crash. Driven by #2143/#2147/#2192 — all three were the desktop client white- screening on backend response shape changes the client wasn't built against. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * feat(core): add zod-based API response validation layer Introduces a defensive boundary so a malformed backend response degrades into a safe fallback (empty page, [], etc.) instead of throwing inside React render. - Adds zod to the pnpm catalog and as a @multica/core dependency. - New parseWithFallback helper in core/api/schema.ts that runs safeParse, logs a warn with the endpoint + zod issues on failure, and returns the caller-supplied fallback. Never throws. - Schemas in core/api/schemas.ts are deliberately lenient (string enums kept as z.string() so unknown values still parse, optional fields default, nested records use .loose() for unknown keys). - Wires setSchemaLogger from CoreProvider so warnings flow through the same logger as the rest of the API client. This is the primitive — see the next commit for the call-site wiring. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * feat(api): guard top 5 high-risk endpoints with parseWithFallback Wraps the response of the five endpoints whose UIs white-screened in past incidents (#2143/#2147/#2192) so a contract drift returns a safe fallback instead of crashing the consumer: - listIssues → ListIssuesResponseSchema, fallback { issues: [], total: 0 } - listTimeline → TimelinePageSchema, fallback empty page - listComments → CommentsListSchema, fallback [] - listIssueSubscribers → SubscribersListSchema, fallback [] - listChildIssues → ChildIssuesResponseSchema, fallback { issues: [] } getIssue is intentionally NOT wrapped: there is no sensible "empty issue" — the entire detail page depends on real fields. The page-level ErrorBoundary (separate commit) catches that case. Adds schema.test.ts with 9 cases covering the five failure modes listed in MUL-1828: missing fields, wrong types, enum drift, null body, and null arrays. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * feat(ui): add ErrorBoundary and wrap high-risk pages Section-level error boundary (no third-party dep — class component + default fallback in @multica/ui). Supports a fallback render prop and resetKeys for auto-recovery on resource navigation. Wraps the surfaces that white-screened in past incidents: - IssueDetail (web + desktop + inbox split-pane) — keyed on issueId so navigating to a different issue clears the boundary automatically. - IssuesPage (web + desktop). Boundaries are placed at consumer call sites rather than inside IssueDetail itself so we don't have to refactor the 1100-line component, and so a crash inside one inbox split-pane doesn't take down the inbox list next to it. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(core): make all API schemas .loose() to preserve unknown fields zod 4 z.object() defaults to STRIP, which silently drops fields the schema didn't list. That makes the schema layer a sync point: a future PR adding a TS field but forgetting the schema would have the field disappear at runtime while TS still claims it exists — the exact bug- class this PR is meant to prevent, just inverted. Apply .loose() to every object schema (TimelineEntry, TimelinePage, Comment, Issue, ListIssuesResponse, Subscriber, ChildIssuesResponse) so unknown server-side fields pass through unchanged. Add a regression test that feeds a payload with extra fields at both entry and page level, and a direct unit test for parseWithFallback decoupled from any endpoint. Update the listIssues fallback test to use a wrong-type payload — under .loose() the previous "{ unexpected: true }" payload parses successfully (every declared field has a default) instead of triggering the fallback path it was meant to exercise. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(claude): strip field-specific examples from API Compatibility section The original wording embedded current schema field names (entries, has_more_before, has_more_after, cursor, status, type) directly in the rules. CLAUDE.md should state the rule, not the implementation — once a field is renamed the doc drifts out of sync with the code, and the specific names don't add anything the abstract rule doesn't. Keep the rule, drop the field-level archaeology. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
c3ddb57b82 |
feat(create-issue): add border beam to switch-to-agent button (#2157)
* feat(create-issue): add border beam to "switch to agent" button Draws the eye to the manual→agent affordance so users discover quick capture mode. Adds a reusable .border-beam utility (conic-gradient ring on ::before, driven by an @property-animated angle) and applies it to the switch-to-agent button alongside a brand-tinted background tint and a hover icon flip. Honors prefers-reduced-motion. Co-authored-by: multica-agent <github@multica.ai> * style(border-beam): switch to magic-ui colorful palette Replaces the single brand-color sweep with a rainbow trail (#ffbe7b → #ff777f → #ff8ab4 → #a07cfe → #5b9dff), matching the `colorVariant="colorful"` look from magic-ui's border-beam reference. Static fallback under prefers-reduced-motion uses the same palette as a linear gradient. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
700e6f3f24 |
fix: prevent mobile input focus zoom
Add a shared mobile/coarse-pointer CSS guard that keeps focused text-editing controls at 16px to avoid iOS Safari page zoom. |
||
|
|
949dffdf7e |
feat: permission-aware UI across agent/comment/runtime/skill surfaces (#1915)
* feat(permissions): add core permission module and shared UI primitives
Foundation for permission-aware UI: pure rules that mirror the Go backend
permission gates, lightweight per-resource hooks, and two reusable display
components used across agent/skill/runtime detail pages.
- packages/core/permissions: types, rules, hooks (Decision-shaped — carries
reason + message so UI can render disabled state, tooltip, and banner
copy from one source)
- packages/core/agents/visibility-label: VISIBILITY_LABEL/DESCRIPTION/TOOLTIP
constants ("Personal" / "Workspace") to replace scattered hard-coded copy
- packages/views/agents/visibility-badge: read-only visibility chip used on
hover cards, list rows, and inspector when not editable
- packages/ui/components/common/capability-banner: "View only — only X and
admins can edit Y" banner shown on agent / skill detail when current user
lacks edit permission
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(views): permission-aware UI across agent/comment/runtime/skill surfaces
Apply the new permission rules to every surface where the UI was either
lying about who can do what or letting users hit 403s by clicking buttons
the backend would reject.
Agent detail
- Hide archive/restore actions for non-owner non-admin
- Replace inline editors (avatar, name, description, runtime/model/visibility/
concurrency picker, skill-attach) with read-only display when canEdit is
false — value is information, the editor is the action
- Show CapabilityBanner under the header explaining who can edit
Visibility surfaces
- visibility-picker / create-agent-dialog: replace "only you can assign"
(false) with "Only you and workspace admins can assign" via shared
VISIBILITY_DESCRIPTION constants
- agent-columns: truthful tooltip + "You" badge on agents the current user
owns
Comments
- Restore admin override on comment edit/delete (backend already permits
it via comment.go:507-512; the frontend was incorrectly hiding the menu).
canModerate is computed once in issue-detail and threaded down.
Other
- Members tab: disable "demote" options for the last owner with tooltip
- Assignee picker: tooltip on disabled personal agents the user can't assign
- Runtime delete: tooltip and dialog explain the gate; owner column gains
a name label next to the avatar in All scope
- Skill detail: page-level CapabilityBanner alongside the existing lock chip
- Issue delete (single + batch): note that any workspace member can delete
issues — by-design semantics, made transparent
Backend is unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(agents): hide personal agents from list and @mention for non-owners
Until now an agent's "Personal" visibility only narrowed the assign-to-issue
gate — every workspace member still saw every personal agent in the list
and the @mention dropdown. Members would see, click, and fail.
This filters those surfaces with the canonical canAssignAgentToIssue rule:
regular members only see workspace-visibility agents and the personal
agents they own; workspace owners and admins continue to see everything
(admin override path is intact).
- agents-page: visibleInView layer between active/archived and Mine/All
scope so segment counts also reflect the filter
- mention-suggestion: filter agentItems before they enter the recency-
ranked list; expand the test mock to cover the auth + visibility paths
and add two assertions (member hides others' personal agents; admin
still sees them)
Backend keeps returning every agent — admin tools and direct API access
are unaffected. This is a UI-only filter.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
b9118ae9b8 |
Refine Quick Create agent modal (#1879)
* fix: refine quick create agent modal * fix: align quick create toolbar feedback * fix: sync create mode toolbar options --------- Co-authored-by: Devv <devv@Devvs-Mac-mini.local> |
||
|
|
06880d6ba2 |
fix: make workspace table columns resizable (#1881)
Co-authored-by: Devv <devv@Devvs-Mac-mini.local> Co-authored-by: Jiayuan Zhang <forrestchang7@gmail.com> |
||
|
|
4ad0a0b847 |
feat(chat): presence v4 — status pill, failure bubble, elapsed timing (#1856)
A complete UX upgrade for chat sending → receiving → recovering.
* StatusPill replaces the orphan spinner — stage-aware copy
("Reading files · 12s", "Searching the web · 14s", "Typing · 24s"),
shimmer text, monotonic timer, derived effective status, > 60s
warning tone, > 5min cancel button.
* WS writethrough on task:queued / task:dispatch / task:cancelled so
pendingTask cache stays in sync with the daemon state machine without
invalidate-refetch latency. broadcastTaskDispatch now includes
chat_session_id when the task is for a chat session — the existing
payload only carried it on the generic task: events, leaving the pill
stuck at "Queued" until completion.
* Failure fallback — FailTask writes a chat_message tagged with
failure_reason (mirrors the issue path's system comment, gated on
retried==nil). Front-end renders an inline note ("Connection failed",
with a Show details collapsible) instead of the previous black hole.
* Elapsed timing — chat_message.elapsed_ms persists task.completed_at -
task.created_at on success/failure rows. UI shows "Replied in 38s" /
"Failed after 12s" beneath assistant bubbles. Format helper shared
between StatusPill and the persisted caption so the live timer and
final reading never disagree.
* Optimistic burst rebalanced — pendingTask seed + created_at moved
before the HTTP roundtrip so the pill appears the instant the user
hits send; handleStop is fire-and-forget so cancel feels immediate
(server confirmation arrives via task:cancelled WS).
* Presence integration — chat avatars use ActorAvatar (status dot +
hover card); OfflineBanner above the input on offline/unstable;
SessionDropdown shows per-row in-flight/unread pip plus a
cross-session aggregate pip on the closed trigger.
* Editor blur on send so the caret stops competing with the StatusPill
/ streaming reply for the user's attention.
* Chat panel isOpen now persists globally; defaults to OPEN for new
users (storage key absence) so the feature is discoverable. Existing
users' prior choice is respected.
* DB: migrations 062 (failure_reason) + 063 (elapsed_ms), both
ADD COLUMN NULL — fast, non-blocking, backwards compatible.
* WS: task:failed chat path now invalidates chatKeys.messages — fixes
a pre-existing bug where the failure bubble required a page refresh
to appear.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
9baa72cc68 |
fix: polish quick-create UX (kind labeling, dark toast, placeholder) (#1831)
* fix: polish quick-create UX (kind labeling, dark toast, placeholder)
Three small fixes shaken out from using the agent-create flow:
- AgentTaskResponse now carries a `kind` discriminator
("comment" | "autopilot" | "chat" | "quick_create" | "direct"), computed
from the existing FK shape with no extra DB access. The Activity row
uses it to label quick-create tasks as "Creating issue" instead of
falling through to the generic "Untracked" — once the agent finishes
and the new issue is linked, the row transitions to the normal
identifier+title display.
- Sonner Toaster reads `resolvedTheme` instead of `theme`, so toasts
follow the actual dark/light state. Forwarding "system" let sonner
pick its own answer from `prefers-color-scheme`, which in the Electron
renderer can disagree with next-themes' `html.dark` class — the toast
rendered light on a dark UI.
- Agent-create placeholder rephrased to a more conversational example
with a project reference: "let Bohan fix the inbox loading slowness
in the Web project". Drops the priority hint (priority isn't widely
used) and matches how people actually instruct the agent.
* fix(quick-create): link new issue back to task on completion
Addresses the review on PR #1831: completed quick-create tasks were
left with issue_id=NULL forever, so the activity row stayed on
"Creating issue" instead of transitioning to the normal MUL-XXX +
title rendering once the agent finished.
- Server: notifyQuickCreateCompleted now writes the resolved issue id
back to agent_task_queue.issue_id via a new LinkTaskToIssue query
(guarded by `issue_id IS NULL` so it only ever fills the unset
quick-create case). Best-effort: a write failure logs but doesn't
block the inbox notification.
- Frontend: defensive wording fallback — kind=quick_create rows in
terminal status (completed/failed/cancelled) now render as
"Quick create" instead of the active "Creating issue" label,
covering rows whose link write failed or whose agent never
produced an issue at all.
|
||
|
|
f745a3bbbe |
feat(agent): presence v3 + execution log + trigger summary (#1823)
* refactor(views): migrate agent/runtime/skill lists to TanStack DataTable
Replace the per-page CSS Grid + minmax(min, fr) + sticky-first-col + truncate
implementation with a TanStack Table backend rendered through a Dice UI-style
DataTable shell. Column widths are now px-based via column.size, so cells
no longer shrink or auto-truncate as the viewport narrows; when the sum of
columns exceeds the viewport, the container scrolls horizontally instead.
- Add @tanstack/react-table to the catalog (8.21.3) and wire it into
packages/ui (dep) and packages/views (peerDep).
- packages/ui: new DataTable + DataTableColumnHeader + lib/data-table.ts
(getColumnPinningStyle), adapted from Dice UI's registry. The shell
renders <table> directly (skipping shadcn's <Table> wrapper) so its own
outer overflow controls both axes — no nested overflow conflicts.
- packages/views: each list now declares ColumnDef[] with explicit
cell renderers. Row click navigates to detail via onRowClick (instead of
wrapping <tr> in <a>, which is invalid HTML); kebab dropdowns
stopPropagation so they don't trigger the row navigation.
- Drop the previous AGENT_LIST_GRID / GRID_WITH_OWNER / ROW_GRID
templates and the sticky-first-col / subgrid mechanics that came with
them. agent-list-item.tsx is removed; runtime-list.tsx and
skills-page.tsx are trimmed to thin wrappers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(agent): cap description at 255 chars (db + api + ui)
Symmetric enforcement across DB, server, and UI:
- Migration 060: pre-flight truncate of any oversize rows, then ADD
CONSTRAINT NOT VALID + VALIDATE CONSTRAINT so the new check doesn't
block writes during validation.
- Server handler validates utf8.RuneCountInString on Create/Update and
rejects over-limit input with 400.
- Front-end gets AGENT_DESCRIPTION_MAX_LENGTH in core/agents/constants
(single source of truth shared by the create dialog + edit modal +
test suite) and a CharCounter component that warns at 90% and errors
past the cap.
- Description editor moves from a 288px popover to a roomy modal.
Editor body is mounted only while the dialog is open, so the local
draft state is locked in at mount time and never reset by an external
WS update — the React-recommended replacement for the
useEffect(reset, [value]) anti-pattern.
Counted in code points everywhere (rune count / spread length /
char_length) so multibyte input agrees across all three layers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(views): data-table polish across runtime + skill lists
Builds on the DataTable migration in
|
||
|
|
f0c845b777 |
fix: popover click bubble + resilient presence loading (#1798)
* fix(popover): stop click bubble + resilient presence loading Two related bugs surfacing on production after #1794: * Click-through: clicking a Detail link inside an agent hover card, or a kebab item in agents/runtimes list rows, also fired the parent row link's onClick. Base UI portals popovers in the DOM but React's synthetic events still bubble through the React tree, so the ancestor <a> wrapping the trigger still received the click. Fix at the primitive level (HoverCardContent + DropdownMenuContent) so every existing and future popover gets it for free — stopPropagation on the popup's onClick, then forward consumer-supplied handlers. * Presence loading forever: useAgentPresenceDetail returned "loading" whenever any of its three queries had data === undefined. With prod backend missing the new agent-task-snapshot endpoint (404), or with an issue assignee referencing an archived agent (not in ListAgents), the UI spun forever. Now: query errors degrade to empty arrays, and a missing agent yields a synthesised offline+idle detail. The dot still renders gray, hover card still shows "Agent unavailable" — but no infinite skeleton. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(inbox): enable hover card on notification actor avatar Originally excluded from the hover-card opt-in pass, but inbox notifications are exactly the kind of "who sent me this?" surface where seeing the actor profile on dwell is useful. Click-through to the wrong target is no longer a concern — the popover stop-bubble fix in this branch handles it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(autopilot): show agent presence dot on autopilots list rows Autopilot detail / picker / dialog already render the dot — the list was the lone holdout. With the autopilot-agent dependency this strong ("autopilot is dead if its agent is offline"), an at-a-glance dot is the most useful signal in the row. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
21e3cfaa01 |
Agent runtime status redesign: split presence into availability + last-task (#1794)
* feat(agent-status): add workspace live-tasks endpoint and TaskFailureReason type Lays the API + type contract for the front-end agent presence cache: - New `GET /api/active-tasks` returns active (queued/dispatched/running) tasks plus failed tasks within the last 2 minutes for the current workspace. The 2-minute window powers a UI-side auto-clearing "Failed" agent state without back-end pollers. - `agent_task_queue` has no workspace_id column, so the query JOINs agent; `SELECT atq.*` keeps `failure_reason` (migration 055) on the wire. - Adds `TaskFailureReason` to `AgentTask` so the UI can map the 5 backend classifiers (agent_error / timeout / runtime_offline / runtime_recovery / manual) to copy without parsing free-text errors. - New `api.getActiveTasksForWorkspace()` client method; workspace is resolved server-side from the X-Workspace-Slug header (no path param, matching /api/agents and /api/runtimes conventions). Includes the joint engineering plan and designer brief that scope the broader Agent / Runtime status redesign — Phase 0 is this contract plus the front-end derivation layer landing in the next commit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(agent-status): derive presence/health states with WS sync and desktop IPC bridge Adds the front-end derivation layer that turns raw server data into the user-facing 5-state agent / 4-state runtime enums. UI files are deliberately untouched in this commit — derivation lives behind hooks (useAgentPresence, useRuntimeHealth) that any component can call with zero additional network traffic. Architecture: - Derivation is pure functions in packages/core/{agents,runtimes}; the back-end stays free of UI translation. Agents algorithm: runtime offline > recent failed (2-min window) > running > queued > available. Runtimes algorithm: status + last_seen_at -> online / recently_lost / offline / about_to_gc. - A single workspace-wide active-tasks query backs all per-agent presence reads, eliminating N+1 across hover cards, list rows, and pickers. 30-second tick re-renders the hooks so the failed window expires even when no underlying data changes. - WS task lifecycle events (dispatch / completed / failed / cancelled) invalidate active-tasks via the prefix dispatcher. completed/failed were removed from specificEvents so they go through both the prefix invalidate and the existing chat ws.on() handlers. Reconnect refetch picks up active-tasks too. - Desktop bridges window.daemonAPI.onStatusChange directly into the runtimes cache via setQueryData, giving the local daemon sub-second feedback (vs. 75s server sweep). Bridge is wsId-bound so workspace switches automatically rebind the subscription; daemon_id matching covers the same-daemon-multiple-providers case. 24 derivation unit tests cover all branches plus null/empty/boundary inputs (FAILED_WINDOW_MS edges, null last_seen_at, missing completed_at). Full core suite: 112 tests passing. Typecheck green across all 8 workspace packages. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(agent-status): redesign agent runtime status as two orthogonal dimensions Splits the conflated 5-state agent presence into two independent axes: - AgentAvailability (3-state): online / unstable / offline — drives the dot indicator everywhere a dot appears. Pure runtime reachability; never sticky-red because of a past task outcome. - LastTaskState (5-state): running / completed / failed / cancelled / idle — surfaced as text + icon on focused surfaces (hover card, agent detail page, agents list, runtime detail). Never colours the dot. Major changes: * Domain layer: AgentPresence union → AgentAvailability + LastTaskState. derive-presence split into deriveAgentAvailability + deriveLastTaskState + deriveAgentPresenceDetail orchestrator. Tests reorganised into three groups (availability invariants, last-task invariants, composition). * Visual config: presenceConfig (5 entries) → availabilityConfig (3) + taskStateConfig (5). availabilityOrder + lastTaskOrder for filter chips. * Workspace-level presence prefetch: new useWorkspacePresencePrefetch hook + WorkspacePresencePrefetch mount component, wired into DashboardLayout (web) and WorkspaceRouteLayout (desktop). Hover cards render synchronously with no skeleton flash on first hover. * ActorAvatar hover: flipped default — disableHoverCard removed, enableHoverCard added (default false). Opt-in at ~14 decision-moment surfaces; pickers / decoration sub-chips stay plain. Status dot decoupled (showStatusDot prop) so picker rows can show presence without nesting popovers. * Hover cards: AgentProfileCard simplified — availability dot only, Detail link top-right (logs live on the detail page). New MemberProfileCard mirrors the structure: name + role + email + top-2 owned agents (sorted by 30d run count) with click-through to agent detail. * Agents list: split Status into two columns — availability (3-color dot + label) and Last run (task icon + label, optional running counts). Two independent filter chip groups (Status + Last run); combination acts as intersection ("online + failed" finds broken- but-alive agents). * Other UI surfaces (issue list/board/detail, comments, autopilots, projects, runtimes, mention autocomplete, subscribers picker) updated to the new dot semantics; status dot now strictly 3-color. Server changes accompany the client redesign — workspace-wide agent-task-snapshot endpoint, runtime usage queries, etc. — to feed the derive layer with the data it needs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(agent-detail): drop last-task chip from detail header + inspector The Recent work section on the agent detail page already shows the same data (with task titles, timestamps, error context) — surfacing "Completed" / "Failed" / etc. up in the header was redundant chrome. Detail surfaces now show only the 3-state availability dot. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(tables): handle narrow viewports across agents / skills / runtimes Three table layouts were squeezing content into adjacent cells at intermediate widths. Each fix is small and targeted: * runtime-list: the Runtime cell's base name had `shrink-0`, so it refused to truncate when its grid column was narrowed under width pressure — the name visually overflowed into the Health column ("ClaudeOnline" etc). Removed shrink-0, added truncate. The Health column was also a fixed 9.5rem reservation for the worst-case "Recently lost · 2m 14s ago" copy; switched to minmax(0,1fr) so it competes fairly with Runtime. * skills-page: had a single grid template with no responsive breakpoints — all 6 columns were rendered at any width and got visually jammed below md. Added a <md template that drops Source + Updated; the row markup hides those cells via `hidden md:block` / `md:contents`. * agent-list-item: the new Last run column was reserved at minmax(8rem, max-content); on narrow md viewports the 8rem floor pushed the row past available width. Changed to minmax(0,max-content) so the cell shrinks under pressure (its content already truncates). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(agent-card): hover-only Detail + add Runtime row + breathing room Three small polish tweaks to the agent hover card: - Detail link gets `mr-1` + fades in only on card hover (group-hover). It was visually flush against the popover edge and competing for attention; now it stays out of the way during a quick glance and surfaces only when the user is dwelling on the card. - Runtime row is back, in the meta block (cloud/local icon + runtime name). The earlier removal was over-aggressive — knowing where an agent runs is part of "who is this agent". The wifi badge stays dropped because the availability dot in the header already conveys reachability. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(runtime): wifi-style health icon (4-state) for runtime list + agent card Replaces the 6px coloured dot with a wifi-shape icon that carries both state (Wifi vs WifiOff) and severity (success/warning/muted/destructive). Mapping: - online → Wifi (success) - recently_lost → WifiHigh (warning) — transient hiccup, fewer bars - offline → WifiOff (muted) — long unreachable - about_to_gc → WifiOff (destructive) — sweeper coming soon Used in two places: - Runtime list: replaces HealthDot in the dedicated leading-icon column. Bumped the column from 0.5rem (dot-sized) to 0.875rem (icon-sized). - Agent profile card RuntimeRow: derives runtime health from runtime + clock (matching the 4-state semantics) and renders HealthIcon next to the runtime name. Cloud runtimes always read as online. The duplicate signal with the header availability dot is intentional — it confirms WHICH runtime is the one currently in the dot's state. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
c381d59c7a |
fix: preserve authored markdown links during linkify (#1761)
Co-authored-by: Eve <eve@multica.ai> |
||
|
|
d14265de2a |
fix(comments): preserve newlines from agent CLI writes (#1744)
* fix(comments): preserve newlines from agent CLI writes Agents (e.g. Codex) routinely emit `multica issue comment add --content "para1\n\npara2"` because Python/JSON-style string literals are their default. Bash does not expand `\n` inside double quotes, so the literal 4-char sequence flowed through the CLI into the database and rendered as text in the issue panel — comments came out as one wall of prose. Three coordinated fixes so the platform behavior no longer depends on whether a given model has strong bash-quoting intuition: - CLI: decode `\n / \r / \t / \\` in `--content` and `--description` for `issue create / update / comment add` (callers needing a literal backslash still have `--content-stdin`). - Agent prompt: rewrite the comment-add example in the injected runtime config to require `--content-stdin` + HEREDOC for any multi-line body, and call out the same rule for `--description`. The previous wording flagged stdin only for "backticks, quotes", which models read as irrelevant to plain paragraphs. - Renderer: add `remark-breaks` to the shared Markdown plugin chain so a bare `\n` becomes a visible line break instead of a CommonMark soft break — protects against models that emit single newlines for formatting. Tests: pin the new CLI helper, and pin the runtime-config guidance so the multi-line wording cannot decay back into a footnote. * fix(comments): address review feedback on newline-rendering PR - Cover the issue panel: ReadonlyContent (used by every comment card and the issue description) has its own react-markdown wiring; add remark-breaks there too so the renderer fix actually applies to the surface the bug was reported on, not just the chat panel. Pinned by ReadonlyContent line-break tests. - Make the prompt's `--description` guidance executable: add `--description-stdin` to `issue create` / `issue update`, refactor comment-add to share a single `resolveTextFlag` helper, and have the injected runtime config name the real flag instead of an imaginary "stdin / a tempfile" path. Pinned by the runtime-config guidance test. - Document the unescape contract on each affected flag's help text and pin the precise boundary in tests: `\n / \r / \t / \\` are decoded; `\d / \w / \s / \u / \0` and other unrecognised escapes pass through verbatim, so regex literals and Windows paths survive intact unless they embed a literal `\n` / `\r` / `\t`. Callers that need the literal sequence have `--content-stdin` / `--description-stdin` as the escape hatch. |
||
|
|
04f813a70f | fix PR 1573 follow-up colors (#1699) | ||
|
|
c3ae212b40 |
fix(markdown): treat CJK full-width punctuation as URL boundary (#1630)
linkify-it only recognizes ASCII characters as URL boundaries. In Chinese or Japanese text a URL followed by "。" (or any other full-width punctuation) was greedily swallowed into the URL along with everything up to the next whitespace, producing hrefs like `https://.../pull/1623。merge` that 404 when clicked. Truncate the detected URL at the first CJK full-width punctuation character and re-scan the tail, so adjacent URLs separated only by full-width punctuation are still each linked individually. The terminator character set mirrors the fix applied in mattermost/marked#22. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
fe84e29b64 |
fix(ui): stop menu hover from overriding icon colors (#1612)
Menu primitives (context/dropdown/menubar/select/command) had rules like `focus:**:text-accent-foreground` and `*:[svg]:text-destructive` that forced descendant svg colors on focus, overriding icons that set their own color (e.g. StatusIcon's `text-warning`). Remove them so icon color comes from inheritance only: colored icons keep their color on hover, uncolored icons still inherit the item's focus/destructive color as before. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
40cea8454d |
feat(autopilot): redesign modal — simpler schema, consistent schedule UI (#1595)
Drop priority and project_id from autopilot. project_id was never exposed in the UI and priority duplicated the agent's own task queue priority. Redesign the create/edit modal as a Runbook (left) + Configuration (right) layout. Rework the Schedule section around a single visual shell so every picker aligns pixel-for-pixel on the same row: - TimeInput (new): segmented HH:MM control adapted from openstatusHQ/time-picker, driven by keyboard (ArrowUp/Down to step, ArrowLeft/Right to jump segment, digit typing with a 2s two-digit window). Replaces <input type="time">, whose native UI broke the design system. Supports a minuteOnly variant for hourly schedules. - TimezonePicker (new): searchable Popover with a fixed-width left check slot so rows stay aligned and GMT offsets never collide with the selected indicator. - Runbook editor now lives in a bordered card, giving the placeholder an input surface instead of bare document flow. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
17136742b9 |
fix(runtimes): fix dark mode chart visibility and invalid CSS color syntax (#1573)
All chart components used `hsl(var(--chart-X))` but `--chart-X` holds a full oklch value, not bare HSL components — making the expression invalid CSS. Browsers silently fell back to black, so bars/areas/heatmap cells were invisible against the dark background. - Replace `hsl(var(--chart-X))` with `var(--color-chart-X)` across all runtime chart components and the landing feature section - Fix heatmap opacity using `color-mix(in oklch, ...)` instead of the invalid `hsl(var(--chart-3) / 0.3)` syntax; switch to foreground color so cells blend with the neutral theme in both light and dark mode - Raise dark-mode chart-2 through chart-5 lightness values so they contrast clearly against the dark background Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
fa7e4cbdca |
Feat/la te x (#1365)
* 排除提交文件 * feat(editor): 添加数学公式渲染支持 - 集成 KaTeX 库用于数学公式渲染 - 在编辑器样式中添加数学节点相关 CSS 样式 - 实现 BlockMathExtension 和 InlineMathExtension 两个数学公式扩展 - 为 Markdown 组件添加 remarkMath 和 rehypeKatex 插件支持 - 在 package.json 中添加 katex、remark-math、rehype-katex 依赖 - 更新 pnpm-lock.yaml 文件以包含新的依赖包 - 为只读内容组件添加数学公式渲染功能 - 创建 math.tsx 文件实现数学公式节点的完整功能 - 添加只读内容的数学公式渲染测试用例 |
||
|
|
3fd2fb2ae3 |
feat(onboarding): redesigned flow + post-landing starter content opt-in (#1411)
* docs(onboarding): add redesign proposal Captures motivation (two activation funnels), research-backed principles, final 5-step flow (welcome+questionnaire → workspace → runtime → agent → first-issue), Q1/Q2/Q3 personalization matrix, backend user_onboarding schema, API design, resume policy, and development ordering (frontend-first with Zustand stub, backend-last, server swap). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(onboarding): scaffold redesigned flow and state foundation Work-in-progress scaffold toward the redesign documented in docs/onboarding-redesign-proposal.md. This commit is intentionally broad — subsequent commits will replace step content and wire real personalization. Not ready for merge. Included: - packages/views/onboarding/: flow orchestrator + 5 step components (welcome/workspace/runtime/agent/complete) and the CLI install card. Step content is the placeholder version; Step 1 (questionnaire) and Step 5 (first issue) are the next changes. - packages/core/onboarding/: dev-phase Zustand store + types. Not persisted — every page refresh starts at Step 1 so each step can be iterated in isolation. Will swap to TanStack Query + PATCH /api/me/onboarding once the backend user_onboarding table ships (keeps the exported hook surface stable). - packages/core/paths/resolve.ts + .test.ts: centralized resolvePostAuthDestination. Priority is flipped so !hasOnboarded wins over workspace presence — during frontend development every login re-enters /onboarding. useHasOnboarded() reads from the store so the real onboarded_at semantic lands automatically once the backend ships. - Post-auth wiring: callback page, login page, landing redirect, dashboard guard, realtime workspace-loss handler, settings leave/ delete, invite acceptance, and desktop app shell all delegate to the shared resolver instead of inline logic. - Desktop overlay: 'onboarding' added as a WindowOverlay type alongside new-workspace / invite, with a navigation-adapter interception so push('/onboarding') opens the overlay. - packages/core/package.json / packages/views/package.json: add new subpath exports. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(onboarding): revise questionnaire to role-driven 3-question form Aligns the proposal with the corrected product positioning: Multica is an AI agent orchestration platform for diverse users (developers, product leads, writers, founders), not a coding-focused tool. Key changes: - Drop Q1 "which agents do you already use?" — daemon auto-detects installed CLIs on PATH; asking is both redundant and less accurate - Add Q2 "what best describes you?" (role) to drive Step 4 template default and Onboarding Project sub-issue filtering - Keep Q1 team_size, refine Q3 use_case (recover writing/research option); all three now have "Other" with an 80-char text field - Q3 use_case_other is embedded into Step 5 first issue prompt so Other users get maximally personalized aha moments, not generic ones - Agent templates: 3 → 4 (Coding / Planning / Writing / Assistant), matrix driven by Q2 × Q3 - Onboarding Project sub-issues: surface Autopilot and Workspace Context (product differentiators), replace "orchestration" wording - Schema JSONB example and §5/§9 execution plan updated to match Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(onboarding): align questionnaire shape with role-driven redesign Prepares the core state layer for the Step 1 questionnaire rewrite. Type-only and initial-value changes; no behavior changes (nothing was reading the removed `existing_agents` field, since no questionnaire UI exists yet). - Add `Role` type (Q2: developer / product_lead / writer / founder / other) - Add `*_other` sibling fields for team_size / role / use_case so each question's "Other" selection can carry 80-char free text - Drop `existing_agents` — daemon auto-detects CLIs on PATH at Step 3, so the signal no longer belongs in the questionnaire - Extend `TeamSize` / `UseCase` unions with `"other"` member - Refine `UseCase` option label (`writing` → `writing_research`) so it matches the widened Q3 scope in the proposal Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(onboarding): implement Step 1 questionnaire Replaces the placeholder welcome step with the 3-question questionnaire defined in docs/onboarding-redesign-proposal.md §3.4. Answers land in the core onboarding store for later use by Steps 4 and 5. Added: - packages/views/onboarding/components/option-card.tsx — OptionCard + OtherOptionCard. Radio-group ARIA semantics; Enter/Space select; Other variant reveals an 80-char input that auto-focuses on mount. - packages/views/onboarding/steps/step-questionnaire.tsx — merges welcome + Q1/Q2/Q3 into one screen. Local draft state for responsiveness; writes to the core store only on submit. Skip/ Continue CTA swap driven by "any answered?"; the only disabled case is "picked Other but the text box is blank". - Test coverage for the CTA rules, Other-clear-on-switch behavior, initial-answers pre-fill, and full payload shape. Modified: - packages/views/onboarding/onboarding-flow.tsx — render questionnaire as the first step; persist answers and advance the stored current_step on submit. Other steps still run off local useState for now; full store-driven orchestration follows when Step 5 lands. Removed: - packages/views/onboarding/steps/step-welcome.tsx — superseded. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(onboarding): split welcome + questionnaire, unblock scroll, drop Q1 evaluating Three fixes prompted by first real browser testing of the Step 1 questionnaire. All three are about making the flow usable before pursuing visual polish. 1. Split Welcome and Questionnaire into two screens The previous merge-welcome-into-questionnaire decision dropped Multica's product introduction entirely. For a product with no established mental model (AI agents as first-class teammates in a task platform), first-time users need 5 seconds of framing before the questionnaire makes sense. StepWelcome carries that framing; it's UI-only (not a persisted step), shown only on first entry (pristine store), and skipped automatically on resume. 2. Remove `my-auto` vertical centering from both platform shells Long questionnaire content pushed the centered block's top above the scroll origin, making Continue/Skip unreachable. Top-alignment + natural body/overlay scroll is the boring-but-correct baseline for content of variable height. 3. Drop Q1 "Just exploring for now" option Q1 asks about team structure, not attitude. "Evaluating" was a category error. Low-commitment users already have a zero-friction path (skip all questions). Removing the option simplifies the question and the downstream mapping table. Types, store initial value, proposal doc (§3.1 flow diagram, §3.4 options, §3.5 sub-issue sorting, §3.6 conditionals, §4.1 JSONB schema, §5.2 file list, §7 decisions row, §9.2 execution order) all synced. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(onboarding): center short steps, scroll long ones — correctly this time Previous attempt removed `my-auto` thinking it was responsible for blocked scrolling. That diagnosis was wrong: the real blocker was the root layout's \`body { overflow: hidden }\` (an app-shell convention so sidebar/topbar stay put while the inner content region scrolls). Removing `my-auto` broke vertical centering of short steps (Welcome) without fixing the scroll issue. Correct fix: - Web: page now owns its own scroll container — `h-full overflow-y-auto` on the outermost div decouples from the body's overflow-hidden. - Desktop: the overlay's existing `flex-1 overflow-auto` container already provided scroll; just restoring `my-auto` was sufficient. - Both platforms: inner `flex min-h-full flex-col items-center` + content `my-auto` gives the "short centers, long top-aligns and overflows down" behavior. Per the flex spec, auto margins are ignored on overflowing boxes (they overflow in the end direction), so Continue/Skip remain reachable via scroll even on long steps like the questionnaire. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(onboarding): add progress indicator + stable header anchor Adds a consistent visual anchor at the top of every step (except Welcome), so transitioning between steps of different content heights no longer shifts the vertical baseline. - packages/core/onboarding/step-order.ts — single source of truth for step order; indicator math reads from here so adding/reordering a step touches only one line - packages/views/onboarding/components/step-header.tsx — dot row + "Step N of M" counter; three dot states (done/current/pending); accessible progressbar semantics - onboarding-flow.tsx — non-welcome steps now render under a shared `<div flex flex-col gap-8>` wrapper with StepHeader on top. Maps the local `complete` render step to the store's `first_issue` until Step 5 lands (one-line function, self-deleting). - step-welcome.tsx — keeps its own min-h-[60vh] + justify-center so the short intro still feels centered once the shell drops my-auto - apps/web + apps/desktop shells — removed `my-auto`. Every non-welcome step now anchors to the same top position, so only the content below the header changes during transitions. Welcome's own internal centering handles its "short content, no header" case. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(onboarding): add web Step 3 platform fork (Desktop / CLI / waitlist) Web users now see a three-way choice at the runtime step instead of being dropped directly into CLI install instructions: - Primary CTA: Download Multica Desktop (bundled runtime) - Alternate: install the CLI (reveals existing StepRuntimeConnect) - Alternate: join the cloud waitlist (captures email, completes onboarding early with cloud_waitlist_email set) Desktop unchanged — its platform shell doesn't pass cliInstructions, so OnboardingFlow routes it straight to StepRuntimeConnect for the bundled-daemon auto-connect path. Rename step-runtime.tsx → step-runtime-connect.tsx to reflect its new single responsibility (connect UI only; platform choice lives in StepPlatformFork). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(onboarding): capture optional use-case on cloud waitlist Adds a textarea to the waitlist form asking what the user wants to use Multica for. Optional (submit still works with email alone) but surfaces a clear prompt + placeholder example so most users will fill it in. Stored as cloud_waitlist_description alongside the email. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(onboarding): make !hasOnboarded a first-class gate on both platforms Triggering condition was wrong on both sides. Web's dashboard-guard only checked hasOnboarded when the URL slug failed to resolve; desktop's App.tsx effect returned early when wsCount > 0 before even looking at hasOnboarded. Users with existing workspaces never got routed into onboarding regardless of their flag state. Also wire store.complete() into the happy-path finish — previously only the waitlist branch wrote onboarded_at, so every normal completion left the flag false and (now that triggers work) would loop users back into onboarding on refresh. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(onboarding): Step 5 auto-bootstrap — welcome issue + Getting Started project After agent creation, the flow transitions to a loader screen that runs the bootstrap in the background: - Creates a welcome issue with a Q3-driven prompt, assigned to the new agent (so it starts working immediately) - Creates a "Getting Started" project with tutorial sub-issues filtered by Q1/Q2/Q3 - Stores first_issue_id + onboarding_project_id via store.complete() - Navigates the user straight into the welcome issue detail page, where they see the agent already responding Degraded path: if welcome issue fails, shows error with Retry / Continue anyway. If project or sub-issues fail, logs and proceeds with just the welcome issue — the aha moment still happens. No-agent paths (runtime skip, agent skip) short-circuit to onComplete without bootstrap. Local flow step union now aligns with the store enum; removed the mapLocalToStoreStep bridge and deleted the old step-complete.tsx placeholder. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(onboarding): converge all no-agent paths to a single bootstrap step Before: skip-runtime, skip-agent, and waitlist each finished onboarding independently, bypassing Step 5 entirely. Users without an agent landed in an empty workspace with no tutorial project — the "self-serve" case had no bootstrap at all. Now: all three paths converge on the first_issue step with agent=null. Bootstrap branches on agent presence: - agent ✓ → welcome issue (assigned to agent) + project + agent-guided sub-issues ("watch your agent do X"). Lands on the welcome issue. - agent ✗ → project only + self-serve sub-issues ("try X yourself" — configure runtime, create agent, write first issue, etc.). Lands on the workspace issues list with the Getting Started project in the sidebar. Both web and desktop shells already handle firstIssueId=undefined → fall back to /<slug>/issues, so no shell-side change was needed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(onboarding): pin starter project + assign sub-issues to the user Bootstrap now also: - Pins the Getting Started project so users see it in the sidebar immediately (both paths) - Pins the welcome issue too (path A only) so the first conversation with the agent stays one click away - Assigns every sub-issue to the current user (via their workspace member record). Only the welcome issue stays assigned to the agent — that's the aha-moment hand-off; everything else is for the user to work through Pin calls are fire-and-forget (failure logged but non-blocking). Member lookup is defensive — if listMembers fails or the user isn't found, sub-issues gracefully fall back to unassigned rather than breaking the bootstrap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(onboarding): remove cloud waitlist option Cloud runtime is not on the immediate roadmap and there's no backend table to persist emails. Keeping the UI around would silently drop user submissions — small trust leak. Revisit once cloud product lands alongside a proper waitlist table + notification pipeline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(onboarding): persist onboarded_at end-to-end Phase 1 of bringing onboarding from dev stub to production. A single persisted column drives every trigger — no separate user_onboarding table yet (that's a later phase for questionnaire persistence, cloud waitlist, analytics). Backend - Migration 050: ALTER TABLE "user" ADD COLUMN onboarded_at TIMESTAMPTZ (no backfill — existing users see onboarding next login, Skip affordance lands later) - sqlc: MarkUserOnboarded with COALESCE for idempotency - UserResponse DTO + userToResponse now emit onboarded_at via existing util.TimestampToPtr helper — single edit covers GetMe, VerifyCode, GoogleLogin, LoginWithToken - New handler POST /api/me/onboarding/complete - Route registered in the authenticated user-scoped group Frontend - User type gets onboarded_at: string | null - api.markOnboardingComplete() - Auth store adds refreshMe() — lightweight getMe + setUser, complements existing initialize() - useHasOnboarded switches source from onboarding-store (dev stub) to auth-store (user.onboarded_at). Every call site — dashboard guard, desktop App.tsx, invite page fallback, realtime workspace-loss handler, settings leave/delete — picks up the real signal without any direct change - onboarding-store.complete() now hits the server: POST + refreshMe before local state update, so the next router effect sees the non-null timestamp and won't bounce the user back Triggers + route guards - StepWorkspace drops the Skip button — every onboarding user must create their own workspace even if invited into one - /onboarding page redirects already-onboarded users away (guards against manual URL access) - login page + auth callback: onboarding wins over ?next= for unonboarded users; invite links are revisitable after onboarding Tests - apps/web callback tests updated: mocks now return User objects so onboarded_at is readable; new "onboarded user honors next" scenario added, "unonboarded ignores next" scenario kept - test/helpers mockUser gets onboarded_at field - questionnaire already-existing strict-required tests bundled in from a prior uncommitted change Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(onboarding): review findings — dead state, error recovery, cache races From independent review of the prior onboarded_at commit. - Remove the dead OnboardingState.onboarded_at field, its INITIAL_STATE entry, and its write in store.complete(). useHasOnboarded now reads auth-store exclusively; leaving a parallel field here violates the "don't duplicate server data in Zustand" rule and risks drifting into a second source of truth. - Wrap handleBootstrapDone/handleBootstrapSkip in try/catch with toast recovery. complete() is idempotent server-side (COALESCE), so a retry after a failed POST/refreshMe is free — letting the error bubble into the React error boundary trapped the user with no way forward. - RedirectIfAuthenticated: swap `!list` for `isFetched`-gated check, matching the pattern added on the /onboarding page. Same one-tick race where a stale cache [] could fire a premature replace before the fresh list settles. - (Self-review fixups picked up along the way) /onboarding page now waits for workspacesFetched before redirecting already-onboarded users, and login handleSuccess reads useAuthStore.getState() so the hasOnboarded value is fresh after setUser (the closure captured a stale pre-login value otherwise). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(onboarding): shrink store surface + firm up flow invariants Post-review cleanup. End-to-end flow is already complete (user.onboarded_at is the single source of truth); these are quality-of-life fixes on top. Store surface - Drop six dead fields from OnboardingState (workspace_id, runtime_id, agent_id, first_issue_id, onboarding_project_id, platform_preference) and the PlatformPreference type. None had readers — they were stub placeholders for a future user_onboarding table that isn't coming this phase. CLAUDE.md "don't design for hypothetical future". - store.complete() signature simplifies to () — no more patch arg, since the only patch fields were the ones just deleted. Welcome as a first-class step - Add "welcome" to OnboardingStep enum and make it INITIAL_STATE's current_step. Removes the pristine-heuristic "did user see welcome?" check, which could misfire on remount. - pickInitialStep() collapses to `state.current_step ?? "welcome"`. - ONBOARDING_STEP_ORDER stays unchanged (welcome isn't a progress point). advance() chain - Every transition handler now persists the new current_step to the store (handleWorkspaceCreated, handleRuntimeNext, handleAgentCreated, handleAgentSkip). Refresh lands on the right step instead of jumping back to Step 2. Invariants - OnboardingFlow throws on null user instead of spreading defensive `?? ""` and `if (userId)` that silently degraded to unassigned sub-issues. Shell guards already ensure user is present. - Desktop WindowOverlay's onComplete gains a paths.root() fallback when workspace is undefined — matches web's symmetry. docs/product-overview.md: committed from untracked. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(onboarding): persist questionnaire + current_step; resume + Back End-to-end questionnaire persistence + resume capability. User answers are now server-side (analytics-ready); refreshing or revisiting lands on the furthest reached step with previous answers pre-filled; a Back button on each step lets users edit earlier answers without losing progress. Backend - Migration 051: ALTER TABLE "user" ADD onboarding_current_step TEXT, onboarding_questionnaire JSONB NOT NULL DEFAULT '{}'::jsonb - sqlc: new PatchUserOnboarding with sqlc.narg for optional fields (COALESCE preserves unspecified columns). MarkUserOnboarded also clears current_step — once complete, the step pointer has no meaning - Handler PATCH /api/me/onboarding accepting partial {current_step, questionnaire}. Questionnaire passthrough via json.RawMessage, no server-side validation of inner shape (keeps schema evolution free) - UserResponse DTO emits both new fields; userToResponse coalesces JSONB to '{}' defensively Frontend - User type gains onboarding_current_step + onboarding_questionnaire - api.patchOnboarding(payload) - Delete Zustand onboarding store — replaced with plain async advanceOnboarding() / completeOnboarding() that call the API and sync auth store. Source of truth is the user object, no client-side shadow state that could drift - pickInitialStep reads user.onboarding_current_step; StepQuestionnaire initial pre-fills from user.onboarding_questionnaire - Monotonic furthestStepRef: Back edits don't regress server-side progress, and re-submit returns the user to where they were - Back buttons on Steps 2/3/4. Back is local-only — just changes the rendered step, no PATCH - Loading indicator on Welcome + Questionnaire submit buttons while PATCH is in flight - CreateWorkspaceForm.onSuccess accepts Promise<void> so the flow can await advance() from its onCreated handler Test mocks (helpers + callback test) updated with new User fields. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(onboarding): resume to Step 3+ needs workspace/runtime fallback Self-review caught: resume lands the user on their saved step, but React state (workspace, runtime, agent) is empty on fresh mount. The render conditions gate on those — without fallbacks the page stays blank. - workspaceListOptions() query fills runtimeWorkspace from cache when stepping past Step 2. Only one workspace exists during onboarding (StepWorkspace always creates one), so [0] is unambiguous. - StepWorkspace accepts an `existing` prop. On resume / Back to Step 2 with a pre-existing workspace, render a "Continue with <name>" confirmation instead of the create form, which would otherwise hit a slug conflict the moment the user clicks Create. - runtimeListOptions(wsId, "me") similarly seeds Step 4's runtime — prefer first online, fall back to first. Step 5 resume path unchanged: if `agent` React state is null on re-entry, bootstrap runs the self-serve branch. Not ideal (user may have actually created an agent), but bootstrap's list-check approach (future work) will handle orphan detection symmetrically. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(onboarding): delete all skip/resume jump logic Flow always starts from Welcome. Questionnaire answers still pre-fill from user.onboarding_questionnaire. current_step is still PATCHed for future analytics but no UI code reads it for navigation. Removed from onboarding-flow.tsx: - pickInitialStep + isOnboardingStep (no server-driven entry point) - furthestStepRef + resolveNextStep (no edit-vs-first-pass branching) - runtimes useQuery + stepRuntime fallback (user walks through Step 3 linearly, so runtime React state is always populated by Step 4) - workspace resume fallback in runtimeWorkspace (same reasoning) Kept: - advanceOnboarding({ current_step, questionnaire? }) — server persistence, analytics-ready - StepQuestionnaire's initial prop from stored answers - workspaces useQuery (gated to step === "workspace" only) for existing-workspace detection on Step 2 to prevent slug conflicts when a previous onboarding was abandoned - Back buttons + handleBack (local-only navigation) - Error recovery on completeOnboarding via try/catch + toast Every transition handler is now a straight advance + setStep line. Users who close mid-flow and return walk the full flow from Welcome again — slight extra clicks, but each step shows meaningful confirm UI (existing workspace, connected runtimes, etc.) so it doesn't feel like repeated work. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(onboarding): grandfather existing users in the onboarded_at migration Folded the backfill into 050 itself (branch has not shipped to prod, so editing the migration in place is clean). Without this, once this branch deploys, every pre-existing user would be walled off into onboarding on their next login — a real production incident. Uses created_at rather than NOW() so analytics like "signup → onboarded interval" read correctly for pre-launch users. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(onboarding): Step 1 questionnaire — two-column editorial layout Matches the onboarding(3) design spec: full-bleed two-column on lg+ (main + "Why we ask" side rail), collapses to single column below. - StepQuestionnaire rewritten with: - Mono 01/02/03 markers per question - Serif question headings (22px) - Editorial serif title ("Three answers. We'll handle the rest.") - Right-side rationale panel explaining what each answer unlocks - Sticky footer with hint + Continue CTA - Embeds StepHeader on the left column so it escapes the flow's narrow max-w-xl wrapper, same pattern Welcome uses - OptionCard redesigned: radio-dot marker + inset ring on select, matches design's .opt pattern - OtherOptionCard: text input appears below the row (not inside the card) with bottom-border-only styling, aligned under the label - onboarding-flow: questionnaire now early-returns full-bleed, joining Welcome as a hero-layout step Placeholder copy updated to match design examples; tests adjusted. * fix(onboarding): questionnaire uses 3-region app-shell layout Previous version had everything in a single scroll container with a sticky footer. As the user scrolled into the questions, the Back button and StepHeader progress indicator scrolled out of view, and sticky-bottom had edge cases with width-constrained flex nesting. Classic 3-region shell now: - Fixed header row: Back button (left) + StepHeader progress indicator — persistently visible regardless of scroll position - Scrollable middle: eyebrow / serif title / lede / 3 question blocks. Uses `flex-1 overflow-y-auto min-h-0` — the min-h-0 is the critical bit that lets a flex-1 child shrink below content height inside a flex column - Fixed footer row: hint (hidden < sm) + Continue CTA — always reachable, never scrolled off Right "Why we ask" panel is now an independent grid column with its own overflow, so the two columns scroll independently instead of the whole page having one shared scrollbar. Side panel width reduced 520 → 480 to give the question column more room on 1280/1366 screens where 1fr_520 left ~760px for content; 1fr_480 gives ~800-900px which comfortably fits the 620px max-w content column plus breathing room. * fix(onboarding): questionnaire needs DragStrip like every full-window view Traffic lights were overlapping the StepHeader progress dots because Step 1 escaped onboarding-flow's non-welcome wrapper (which renders <DragStrip />) without rendering its own. The codebase convention per packages/views/platform/drag-strip.tsx is: every full-window view places a DragStrip as the first flex child of each visible column. Adds DragStrip at the top of both the left (shell) and right ("Why we ask") columns, matching step-welcome.tsx which already did this. Traffic lights now land in the 48px transparent strip with no content collision; dragging from any top edge moves the window on Electron; border-l between columns runs edge-to-edge. Also made the right column's scroll container use `min-h-0 flex-1 overflow-y-auto` so its internal scroll activates independently of the left column. (Separately investigated: useImmersiveMode is no longer called anywhere in production code — the codebase has fully committed to the DragStrip pattern. No action needed on the hook itself.) * style(onboarding): drop top/bottom borders on questionnaire shell * style(onboarding): use chat-style scroll fade mask instead of border The questionnaire's scroll area now fades softly at top/bottom edges via `useScrollFade` (already used by chat-message-list.tsx) — the same mask-image linear-gradient pattern that fades content under the header/footer based on scroll position: - At top: only bottom fades (hint: more content below) - At bottom: only top fades (hint: content above) - In middle: both fade - Fits entirely: no mask This replaces the removed border-b/border-t on the header/footer with a softer, more editorial visual separation while giving an actual scroll-position affordance the border can't. * feat(onboarding): show "n of 3 answered" progress next to Continue Gives the user a glance-able progress signal as they fill the questionnaire. Static text, no extra UI primitives, no dynamic state variants — just `{n} of 3 answered` updating in place, left of the Continue button. Replaces the static "Your answers shape the next screens..." hint, which was always there regardless of progress and added noise. Same canContinue gate as before (all 3 answered), just derived from the new per-question check so we don't compute validity twice. * style(onboarding): drop redundant lede under questionnaire title The title already conveys the "we'll handle the rest for you" promise — the lede just rephrased it at length. Removed; bumped the question-list top margin (mt-8 → mt-10) to keep breathing room. * feat(onboarding): land redesigned flow + post-landing starter content opt-in This commit bundles the final onboarding-redesign work that sat in the working tree with today's architectural reshape of how starter content is handled. Splitting across sqlc-regenerated files would be fragile, so it ships as one logical unit — "onboarding is ready for production". Flow redesign (Steps 1–5) ------------------------- - Editorial two-column shells on Steps 1/2/3/4 (DragStrip + hero column + aside panel) — Welcome, Questionnaire, Workspace, Runtime, Agent - Web-only Step 3 fork (Download desktop / Install CLI / Cloud waitlist) lives alongside desktop's direct runtime picker; cloud path is interest-capture only, doesn't advance the flow - DragStrip extracted to packages/views/platform as a cross-platform component — 48px transparent drag row, no-op on web - recommend-template.ts + test: Q1–Q3 → AgentTemplate mapping Cloud waitlist -------------- - Migration 052: cloud_waitlist_email VARCHAR(254) + cloud_waitlist_reason TEXT - Handler: net/mail.ParseAddress + length bounds + reason trim - Frontend: CloudWaitlistExpand component + api.joinCloudWaitlist Drop persisted onboarding_current_step -------------------------------------- - The interim implementation persisted the user's furthest-reached step; the final design starts every entry at Welcome, so the column is dead - Migration 051 no longer adds it; migration 053 drops it IF EXISTS on any environment that ran the interim 051 — schema converges cleanly - UserResponse / User type / patchOnboarding signature all drop the field Post-landing starter content (new architecture) ----------------------------------------------- Why: the old design ran bootstrap inside Step 5 (welcome issue + Getting Started project + sub-issues, all in one try block). That had three defects — (1) non-idempotent: Retry after partial failure created duplicates; (2) sub-issue assignee raced listMembers → showed as "Unknown"; (3) skipped users (paths A/C/D) never got any starter content. All three are structural, not patchable. New design: onboarding ends at completeOnboarding() as before (gate is unchanged for useDashboardGuard). The 4 completion paths (Welcome skip / full flow / Runtime skip / Error recover) all just call completeOnboarding() and navigate to workspace. On landing, a StarterContentPrompt dialog renders exactly once per user (starter_content_state == null) with Import / No thanks. The dialog is mandatory — no X, no ESC, no outside-click — so state always ends in a terminal value. - Migration 054: starter_content_state TEXT, backfill 'skipped_legacy' for pre-feature onboarded users so they're never prompted - Server POST /api/me/starter-content/import: transactional claim (NULL → 'imported') + bulk create project + optional welcome issue + sub-issues + pins, all in one tx. 409 Conflict on second call - Server POST /api/me/starter-content/dismiss: transactional NULL → 'dismissed' - Import decides agent-guided vs self-serve by inspecting the workspace's agent list at dialog time — fixes path A (Welcome skip + existing agent) which was previously excluded from starter content - starter-content-templates.ts replaces bootstrap.ts: pure template builders, no API calls. Copy is reviewed as UI; server owns atomicity - StepFirstIssue is now just completeOnboarding() + navigate; error surface collapses to a Retry button (no more "Continue anyway" branch) - OnboardingCelebration + just-completed.ts removed (replaced by StarterContentPrompt which reads server state, not sessionStorage) Handler hardening ----------------- - PatchOnboarding: MaxBytesReader 16KB so the JSONB column can't be weaponized as bulk storage (every /api/me read returns the payload) - JoinCloudWaitlist: net/mail format check + explicit 254-char cap - ImportStarterContent: MaxBytesReader 64KB (templates are markdown-heavy but still bounded); welcome issue's agent_id verified in-workspace Tests ----- - Existing onboarding_test.go (waitlist) passes - step-platform-fork.test.tsx + recommend-template.test.ts (new) - apps/web test helpers updated for User.starter_content_state Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(onboarding): resolve Unknown assignee/creator + tighten prompt copy Two surface issues on the post-landing starter content dialog: 1. Unknown assignee & Created by ------------------------------- ImportStarterContent stored `member.id` (the membership row UUID) in `assignee_id` and `creator_id` for sub-issues. That mismatched the rest of the codebase — AssigneePicker and resolveActor in issue.go both store `user_id` for type="member", and `useActorName.getMemberName` looks members up by `user_id`. The mismatch meant the lookup never matched any member and fell through to the "Unknown" fallback. Fix: use `parseUUID(userID)` for both fields. The existing membership check stays for the 403 signal; we just no longer need the returned `member.ID`. 2. Dialog copy too long, button labels unclear ---------------------------------------------- Old copy was 3–4 paragraphs of instruction; users need to read less than that to make a binary choice. Buttons "Import starter tasks" and "No thanks" also didn't make it clear what "No thanks" actually does — it starts a blank workspace, so say so. New: - Title: "Welcome — add starter tasks?" - Body: one sentence describing the seeded content - Left button: "Start blank workspace" - Right button: "Add starter tasks" Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(onboarding): server decides starter content branch Problem: the old ImportStarterContent gated the agent-guided vs self-serve branch on a client-supplied `welcome_issue.agent_id` or null `welcome_issue`. The client made that decision by reading its React Query cache of the workspace's agent list — any timing quirk (cache not populated, stale, race with WS event) could lie to the server, and there was no way for the server to disagree. Users with an agent in the DB could still end up on the self-serve branch. Fix: the server is now authoritative. The client always sends both template arrays (agent_guided_sub_issues, self_serve_sub_issues) and a welcome_issue_template (title + description + priority, NO agent_id). Inside the import transaction the server runs ListAgents on the workspace — if there's at least one agent, it picks agents[0] (same ordering the client used: created_at ASC), uses agent_guided_sub_issues, and creates the welcome issue assigned to that agent. Otherwise it uses self_serve_sub_issues and skips the welcome issue. Side effect: the Unknown assignee/creator bug is structurally gone — no client-supplied id flows into assignee_id/creator_id for type= "member". The server uses actorID = parseUUID(userID) everywhere, matching resolveActor in issue.go. Client surface also simplifies: StarterContentPrompt drops useQuery(agentListOptions), the hasAgent check, the agentsFetched button gate, and the branch-specific copy. Dialog description is a single generic line ("If you already have an agent, we'll also seed a welcome issue it replies to right away"). buildImportPayload no longer takes an agentId parameter — one unconditional return shape. Payload grows ~15 KB (both sub-issue arrays always present); still well under the 64 KB MaxBytesReader cap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(onboarding): clarify runtime prerequisite, revert dialog agent list Step 3 runtime (desktop step-runtime-connect.tsx) — scanning and empty subtitles now name the local AI coding tools Multica drives (Claude Code, Codex, Cursor, and others), so users understand a runtime alone isn't enough: they also need one of those tools installed on the machine. Uses "and others" rather than a closed list so we don't lock the copy to exactly three integrations. StarterContentPrompt dialog — reverted the short-lived "try Coding, Planning, Writing agents and more" rewrite. That was a misread of feedback meant for the Step 3 prerequisite, not the dialog. The dialog's current single-sentence "how agents, issues, and context work in Multica" is enough. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |