mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-26 04:25:46 +02:00
v0.2.26
742 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e3dd31cbe5 |
feat(notifications): add system notifications toggle in settings (#2132)
* feat(notifications): add system notifications toggle in settings Add a per-user, per-workspace toggle to enable/disable native OS notification banners. Reuses the existing notification-preferences endpoint by introducing a `system_notifications` key alongside the inbox event groups; the realtime handler reads the cached preference and skips desktopAPI.showNotification when muted. Co-authored-by: multica-agent <github@multica.ai> * fix(notifications): fetch system_notifications pref lazily Settings is the only mounted reader of notificationPreferenceOptions, so a fresh app start (or any session that never visits Settings) left the cache empty and the muted preference silently fell back to default "all". Switch the inbox:new handler to ensureQueryData so the value is fetched on first use and cached for subsequent events. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
5cf1d01076 |
feat(settings): rename Appearance tab to Preferences and persist active tab in URL (#2131)
- Rename appearance-tab → preferences-tab; AppearanceTab → PreferencesTab - i18n top-level key appearance → preferences; tab label "Appearance" → "Preferences" / "偏好设置" - Swap icon Palette → SlidersHorizontal (preferences semantic) - SettingsPage: read active tab from ?tab= via NavigationAdapter, write back with replace() on change; whitelist valid tabs (incl. desktop extras daemon/updates), unknown values fall back to profile - Update conventions.mdx (en + zh) references to renamed file and i18n key Why preferences over appearance: the tab held both theme and language; "Appearance" semantically excludes localization. "Preferences" follows Linear/Slack/Discord and leaves room to add timezone/date format later. Why query param over path: settings tabs are UI modifier state, not resources; query persistence keeps the existing single Next.js route file and desktop memory router unchanged, gives a natural fallback for unknown values, and avoids 404 risk. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
6d59505575 |
fix(quick-create): remove duplicate keyboard shortcut on agent submit button (#2130)
The agent submit button rendered the shortcut hint twice — the i18n string already contained '(⌘↵)' and the JSX appended another formatShortcut() suffix. Drop the hardcoded shortcut from the translations and rely on the platform-aware formatShortcut() in JSX. Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
58db751089 |
ci(lint): enable lint in CI + fix existing lint debt (#2129)
CI was running build + typecheck + test, but never lint. The i18n guardrail (eslint-plugin-i18next on packages/views/**/*.tsx) was configured but not enforced, so PRs kept landing user-facing English strings (chat session delete, project resources, mermaid fallback, invitations batch page). Changes: - .github/workflows/ci.yml: add `lint` to the turbo command - packages/eslint-config/react.js: split React rules (JSX-only) from react-hooks rules (apply to .ts too) — hooks live in .ts modules like use-agent-presence.ts, and inline-disable comments need the rule registered to resolve - Translate the 10 lint errors that surfaced: - editor/readonly-content.tsx mermaid render-error + rendering - issues/issue-detail.tsx Archive tooltip - invitations/invitations-page.tsx full page (new invite.batch.*) - invitations-page.test.tsx wrap with I18nProvider so getByRole queries match translated button labels - core/auth/utils.ts intentional control-char regex: add eslint-disable Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
ba147708a6 |
fix(timeline): cursor-paginated timeline to stop long-issue freeze (#1968) (#2128)
* fix(timeline): cursor-paginated timeline to stop long-issue freeze (#1968) Opening an issue from Inbox with thousands of timeline entries used to hard-freeze the browser tab on a synchronous render of every comment + activity. The whole pipeline was unbounded: the API returned every row, TanStack Query cached the full array, and IssueDetail mounted N CommentCards (each running a full react-markdown + lowlight pipeline) in one frame. This swaps the timeline endpoint to keyset cursor pagination and rewires the frontend to useInfiniteQuery so a long issue costs the same as a short one on first paint. API: - GET /issues/:id/timeline now accepts ?before / ?after / ?around (mutex) + ?limit (default 50, max 100); response wraps entries with next/prev cursors and has_more flags. Cursors are opaque base64 (created_at, id). - ?around=<entry_id> anchors a window on the target so Inbox notifications pointing at an old comment never trigger the freeze. - New composite indexes on (issue_id, created_at DESC, id DESC) replace the redundant single-column ones so keyset queries are index-only scans. - /issues/:id/comments default branch now caps at 50 instead of returning every row unbounded; the unbounded ListComments / ListActivities sqlc queries are deleted. Frontend: - useIssueTimeline switches to useInfiniteQuery, exposes fetchOlder/fetchNewer/jumpToLatest + isAtLatest + newEntriesBelowCount. - WS handlers respect the at-latest invariant: comment/activity:created prepends to pages[0] only when the user is reading the live tail; otherwise it just bumps a counter so the UI offers a "Jump to latest" affordance without yanking scroll. - Optimistic mutations adapted to the InfiniteData shape via shared helpers (mapAllEntries / filterAllEntries / prependToLatestPage in core/issues/timeline-cache.ts) and use setQueriesData so all open windows of the same issue stay in sync. - IssueDetail Activity section gets a TimelineSkeleton placeholder during the brief load window plus subtle text-link load-more buttons matching the existing Subscribe affordance (no Button chrome). Top uses a divider for boundary clarity; bottom shows "Jump to latest · N new" weighted slightly heavier when there's unread state. - highlightCommentId now flows into the hook's around parameter so Inbox jumps fetch the surrounding 50 entries directly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(agent): default comment list to 50 + prompt hint about long issues The CLI's "multica issue comment list" used to default to --limit 0 (meaning "fetch every comment"), which lets an agent on a long issue fill its context window with thousands of rows. The default is now 50; agents that need older history can pass --limit or --since explicitly. The local-coding-agent prompt also gains a single-line note about this in both the comment-triggered and on-assign flows so the agent knows to scope its fetches when issue size is unknown. Autopilot run-only mode is intentionally unchanged — it has no issue context to query. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
3447764b03 |
feat(i18n): full rollout — 21 namespaces translated (en + zh-Hans) (#1853)
* feat(i18n): rollout phase — translate 9 namespaces (WIP)
Phase 1 complete (基建 + login + Settings language switcher),
phase 2 partial (Wave 4 done, search done). Pending namespaces
documented inline; another developer can pick up from here.
Infrastructure
--------------
- server: add users.language column + extend PATCH /api/me
(TestUpdateMeAcceptsLanguage / TestUpdateMePreservesLanguage)
- packages/core/i18n: types / pickLocale (intl-localematcher) /
browser-cookie-adapter / createI18n (initAsync false +
useSuspense false) / I18nProvider / LocaleAdapterProvider
- Split server-safe vs React entries:
@multica/core/i18n — for proxy/RSC/middleware (no React)
@multica/core/i18n/react — for client trees (createContext)
(RSC vendored React lacks createContext; mixed import would crash
proxy.ts at module load.)
- packages/views/i18n: useT hook + selector API augmentation
(i18next v26 default; auto-propagates to apps via the side-effect
import in use-t.ts).
- apps/web: proxy.ts (Next 16 renamed middleware) merges existing
legacy/root redirects with x-multica-locale header forwarding;
layout.tsx reads locale via headers() and pre-loads RSC resources.
- apps/desktop: webPreferences.additionalArguments injects
systemLocale (no sendSync — avoids main-thread blocking IPC);
renderer adapter reads via process.argv.
- ESLint: i18next/no-literal-string at file-scope for translated
files via packages/views/eslint.config.mjs TRANSLATED_FILES.
- glossary.md (packages/views/locales/) freezes term policy:
Issue / Workspace / Agent / Skill / Autopilot / Daemon / Runtime
stay English; Inbox / Project / Comment / Member translate.
Translated namespaces (9 / 19)
------------------------------
- auth: login page (web wrapper含 desktop-handoff 文案) + Settings
Appearance language switcher
- editor: 9 .tsx (bubble-menu / link-hover-card / readonly-content /
title-editor / extensions: code-block / file-card / image-view /
mention-suggestion) + 32 keys
- invite: 25 keys
- labels / members / my-issues: Wave 4 全部
- search: command palette 35 keys
- navigation: no user-facing strings (no-op)
Pending (10 / 19)
-----------------
issues (46 files / ~210 keys)
agents (29 files / ~155 keys; presence.ts + config.ts label maps
允许进 i18n)
onboarding (22 files / ~150 keys)
settings rest / skills / modals / workspace / chat / inbox /
projects / autopilots / layout
Workflow for picking up
-----------------------
- Glossary: packages/views/locales/glossary.md (mandatory read)
- Reference impls: auth/login-page.tsx + editor/* (selector API +
i18n-provider test wrapper pattern)
- Per namespace:
1. create locales/{en,zh-Hans}/{ns}.json
2. add to packages/views/i18n/resources-types.ts
3. useT('{ns}') + t($ => $.foo) in components
4. add files to TRANSLATED_FILES in eslint.config.mjs
5. typecheck + test + lint must pass
- Subagents currently CANNOT write files (sandbox deny). Run as
hybrid: subagent researches + outputs full JSON + tsx diff,
controller writes.
Other
-----
- scripts/init-worktree-env.sh: default
MULTICA_DEV_VERIFICATION_CODE=888888 in dev for deterministic
login (gated by isProductionEnv).
Verified: pnpm typecheck (6 pkgs ok), pnpm test (232 pass),
make test (Go).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(i18n): rewrite glossary aligned with docs zh voice
Switch translation policy to match the canonical CN voice already
established in apps/docs/content/docs/*.zh.mdx (20+ files). The new
rule splits product nouns into two classes:
- Typed entities (issue / project / skill / autopilot / task) — kept as
lowercase English in CN text, visually marking them as system types.
- Concepts (workspace / agent / daemon / runtime / inbox) — fully
translated (工作区 / 智能体 / 守护进程 / 运行时 / 收件箱).
Previous glossary kept Workspace / Agent / Daemon / Runtime as English
on "工程惯例" grounds, but docs zh and CN AI ecosystem (Coze / 腾讯元器
/ 百度) consistently translate these. App UI now matches docs voice so
users don't see split personality between the app and its own docs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(i18n): register 6 namespaces and retrofit zh strings to new glossary
Two fixes that were blocking the previously-translated namespaces from
actually rendering in CN:
1. RESOURCES gap — locales/index.ts only loaded common/auth/settings,
but resources-types.ts declared 12 namespaces and 6 of them had real
translation content. At runtime i18next would fall back to raw keys
for editor / invite / labels / members / my-issues / search.
Register all 9 currently-translated namespaces.
2. Retrofit zh strings to the docs-aligned glossary:
- "Issue" → "issue" (lowercase entity)
- "Workspace" → "工作区"
- "Agent" → "智能体"
- "Runtime" → "运行时"
- "Skill" → "skill" (lowercase)
- "项目" → "project" (lowercase)
Touched: editor.json (sub_issue + mention.group_issues), invite.json
(3 Workspace occurrences), members.json (agents_section / more_agents),
my-issues.json (8 retrofits across page/header/errors), search.json
(13 retrofits across groups/pages/commands/empty).
Verified: pnpm typecheck (6/6) + pnpm test (238/238) all green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(i18n): translate inbox namespace
First namespace through the sub-agent → main-agent integration pipeline.
JSON: en/inbox.json + zh-Hans/inbox.json — 60 keys across page / menu /
list / detail / types / labels / errors. Time-formatter labels are kept
compact in EN ("5m" / "3h" / "2d") and use full units in zh ("5 分钟" /
"5 小时" / "5 天") since raw "5 分" reads as "5 marks/points" in CN.
Component changes converted two module-level statics into hooks so the
strings can flow through i18next:
- inbox-list-item.tsx: `timeAgo` (pure fn) → `useTimeAgo` (hook
returning a fn). The local copy is a duplicate of @multica/core/utils
`timeAgo` that is only used by inbox-page; other consumers across
chat/agents/skills/issues stay on the core util for now and will be
translated when their namespaces land.
- inbox-detail-label.tsx: `typeLabels` (static const Record) →
`useTypeLabels` (hook returning the same Record shape). Call sites
keep the existing `typeLabels[type]` access pattern.
inbox-page.tsx now uses both hooks and `useT('inbox')` selector calls
for all hardcoded strings (~24 sites: header / dropdown menu / list
empty state / detail panel / mobile back / quick-create-failed flow /
all error toasts).
Wired up: resources-types.ts, locales/index.ts RESOURCES, ESLint
TRANSLATED_FILES (3 inbox tsx files now lint-protected).
Verified: pnpm typecheck (6/6) + pnpm --filter @multica/views test
(238/238) + ESLint clean on inbox/.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(i18n): translate workspace namespace
Translates the three workspace shell views: create-workspace-form,
new-workspace-page, no-access-page. Also fixes the prior-art
no-unescaped-entities lint errors in no-access-page.tsx — the
apostrophes in "doesn't" / "don't" were JSX text literals that move
into JSON values after translation, so the lint rule no longer fires.
Tests wrapped: workspace/create-workspace-form.test.tsx,
workspace/no-access-page.test.tsx, modals/create-workspace.test.tsx
all now wrap render() with <I18nProvider locale="en"> so the en values
in workspace.json drive the rendered text and the existing assertions
continue to match.
Slug constants kept: WORKSPACE_SLUG_FORMAT_ERROR /
WORKSPACE_SLUG_CONFLICT_ERROR exports in workspace/slug.ts are still
imported by onboarding/steps/step-workspace.tsx (out of scope here).
The workspace shell now reads its strings from workspace.json directly.
Multica.ai brand prefix in the slug input affordance is wrapped with
an inline `// eslint-disable-next-line i18next/no-literal-string` per
glossary policy on brand names.
Renamed sign_in_other → sign_in_different to avoid colliding with
i18next's `_other` plural-suffix convention which the selector-API
typings treated as a plural form of `sign_in`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(i18n): translate projects namespace
Translates the projects list page, project detail page, project picker
dropdown, and project chip — all four user-facing surfaces under
packages/views/projects/components/.
New file: projects/components/labels.ts exposes three hooks that
replace the static `.label` field on PROJECT_STATUS_CONFIG /
PROJECT_PRIORITY_CONFIG and the previous module-level
`formatRelativeDate` helper. Core's `.label` stays untouched (it's
still consumed by search and the create-project modal, both
out-of-scope for this namespace) — those will flip when their
respective namespaces translate.
In zh, the "project" entity stays lowercase English per glossary
(`新建 project`, `还没有 project`, `从 project 移除`). Status / priority /
table column labels translate fully.
The cancelled / done / paused etc. status labels duplicate per-
namespace as `projects.status.*` rather than reading from a future
shared status namespace. This matches the auth/inbox/workspace
pattern of self-contained namespaces. If a generic "issue/project
status" pool emerges later, these can collapse.
Verified: pnpm --filter @multica/views typecheck (clean) +
test (238/238) + ESLint clean on projects/ (1 pre-existing warning
about useEffect/sidebarRef dep, unrelated to i18n).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(i18n): translate autopilots namespace
Six tsx files: autopilots-page (list + 6 templates), autopilot-detail-page
(properties / triggers / run history / delete), autopilot-dialog
(create + edit dialog), trigger-config (cron form), and the agent /
timezone pickers.
Hook conversions for module-level helpers that need t():
- summarizeTrigger / describeTrigger → useSummarizeTrigger /
useDescribeTrigger (no external callers, removed the plain exports)
- formatRelativeDate → useFormatRelativeDate (per-component hook)
- formatCountdown → useFormatCountdown (per-component hook)
- TEMPLATES array now keyed by id; titles + summaries pull from
templates/{id}/{title,summary} JSON. Prompts stay raw EN since
they're injected directly into the agent task — translating them
would translate the agent's instructions, not the user's UI.
Status / execution-mode / run-status enums render via t($ => $.status[k])
with k typed against the core type (no separate hook needed).
Verified: pnpm --filter @multica/views typecheck (clean) +
test (238/238) + ESLint clean on autopilots/.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(i18n): translate skills namespace
Seven tsx files: skills-page (list + filters + intro banner),
skill-detail-page (the giant — properties + file tree + sidebar +
conflict banner + delete dialog, ~963 lines), create-skill-dialog
(chooser + manual + URL forms), runtime-local-skill-import-panel
(local runtime browse + import), skill-columns, file-tree, file-viewer.
Notable patterns:
- `createSkillColumns` factory → `useSkillColumns` hook so column
headers flow through useT. Column identity changes per render is
fine — DataTable handles it.
- `validateNewFilePath` (pure helper) → `useValidateNewFilePath` hook
so the 5 validation error messages can be translated.
- skill_files / used_by / description_with_agents use i18next plural
keys (`_one` / `_other`) — the type system collapses these into a
single PluralValue access, so call sites use
`t($ => $.foo, { count })` and i18next picks the form.
- Per glossary, "skill" stays lowercase EN in zh ("新建 skill",
"已删除 skill", "未找到该 skill").
Test wrapper: runtime-local-skill-import-panel.test.tsx now wraps
render() with <I18nProvider> so the assertion on /Import to Workspace/i
matches the EN translation.
Verified: pnpm --filter @multica/views typecheck (clean) +
test (238/238) + ESLint clean on skills/.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(i18n): translate chat namespace
Translates all 10 chat surfaces: FAB tooltip, input placeholders,
message list (replied-in / failed-after / tools group / show-details
/ tool result preview), session history (header + time-ago labels),
chat window (new-chat / restore / expand / minimize / agent + session
dropdowns / starter prompts / empty states), context-anchor button +
card tooltips, no-agent banner, offline / unstable banner, and the
task-status pill (queued / starting up / thinking / typing + tool
labels: running command / reading files / searching code / making
edits / searching web).
Hook conversions:
- formatTimeAgo (chat-session-history) → useFormatTimeAgo
- ElapsedCaption now takes a typed `variant` ("replied" | "failed")
instead of a free-text `verb` so the i18n key is enumerable
- pickStage (task-status-pill) refactored: pure pickStageKeys returns
StageKey + optional ToolKey; useResolveStage maps to localized labels
Translation policy notes:
- Starter prompts ("List my open tasks by priority", etc.) are user
UI when displayed AND the user's input when clicked — translating
them sends the agent the user's locale-native phrasing, which is
the right UX for a CN user using a CN agent.
- buildAnchorMarkdown (chat-window) stays in English: it's an
agent-bound markdown prefix injected into the outgoing message,
not user-facing UI.
Verified: pnpm --filter @multica/views typecheck (clean) +
test (238/238).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(i18n): translate modals namespace
Translates all 11 modal sources: registry (no UI text), backlog-agent-hint,
set-parent-issue, add-child-issue, delete-issue-confirm, feedback,
issue-picker, create-workspace, create-project, create-issue (manual),
quick-create-issue (agent panel).
Notable patterns:
- create-project re-uses useProjectStatusLabels / useProjectPriorityLabels
hooks from views/projects/components/labels — same translation source
as the projects list / detail, no duplication.
- create-issue.tsx: renamed `toast.custom((t) => ...)` callback param to
`toastId` to avoid shadowing the closure-captured useT() `t` function.
- Test wrapper added to modals/create-issue.test.tsx so the two assertions
on rendered modal text (success toast + Create another) match the EN
bundle. modals/create-workspace.test.tsx was already wrapped (workspace
ns commit).
Verified: pnpm --filter @multica/views typecheck (clean) +
test (238/238).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(i18n): translate settings namespace (rest of tabs)
Builds on the appearance-tab + language switcher already shipped in
Phase 0. Translates the remaining 8 settings surfaces: settings-page
shell (left nav + tab keys), account / profile, notifications-tab
(5 group labels + descriptions), tokens-tab (create / list /
revoke / created dialog), workspace-tab (general fields + danger
zone + leave/delete confirmations), members-tab (invite + role
config + revoke / remove flows), repositories-tab, labs-tab,
delete-workspace-dialog.
Hook conversion: members-tab `roleConfig` static const → `useRoleLabels`
hook returning a Record<MemberRole, {label, description, icon}>. The
icon stays as a typed React component (Crown / Shield / User), so
rendering pattern is unchanged at call sites.
Test wrapper: settings/components/delete-workspace-dialog.test.tsx
now wraps render() with <I18nProvider> (custom render() helper)
because the test asserts on rendered button labels ("Delete workspace",
"Cancel", "Deleting...").
Verified: pnpm --filter @multica/views typecheck (clean) +
test (238/238).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(i18n): translate runtimes namespace (entry surfaces)
Translates the user-facing runtime list page surfaces:
runtimes-page (header / search / filters / chips / empty / no-matches /
bootstrapping), runtime-detail (topbar + delete dialog + delete toasts),
runtime-detail-page (not-found state), shared.tsx (4-state HealthBadge
labels).
Hook conversion: shared `healthLabel(health)` was a pure module-level
function. Added `useHealthLabel` hook for translated call sites; kept
`healthLabel` as an EN-only fallback for non-component callers (column
factory in runtime-columns).
Deferred:
- runtime-list / runtime-columns (data table column headers + cell
bodies) — large surface, not in the page-load critical path.
- connect-remote-dialog / update-section / usage-section — secondary
flows, English remains acceptable until a focused pass.
- charts/* — primarily numeric tooltips and axes; minimal user-visible
text.
Verified: pnpm --filter @multica/views typecheck (clean) +
test (238/238).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(i18n): translate layout namespace (sidebar nav, help, loader)
Translates the cross-cutting layout chrome:
- 9 sidebar nav labels (inbox / my issues / issues / projects /
autopilots / agents / runtimes / skills / settings) — driven by
labelKey instead of inline strings, resolved via useT at render.
- HelpLauncher dropdown (trigger aria + 3 items: Docs / Change log
/ Feedback)
- WorkspaceLoader (named + unnamed loading states)
- SortablePinItem unpin tooltip
Pattern shift in app-sidebar.tsx: nav arrays carry `labelKey: NavLabelKey`
(typed against the layout JSON) instead of `label: string`. The string
comparison checks (`item.label === "Inbox"`) became cleaner ID-based
checks (`item.key === "inbox"`).
Deferred: deeper sidebar surfaces — workspace switcher dropdown,
"New Issue" CTA, "Pinned" / "Workspace" / "Configure" group labels —
remain English. The 9 nav labels are the ones that read in every
session.
Verified: pnpm --filter @multica/views typecheck (clean) +
test (238/238).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(i18n): translate onboarding namespace (welcome + step header)
Translates the user-first-impression surfaces of the onboarding flow:
- step-welcome.tsx (the wordmark, headline, lede paragraphs, all CTAs:
Download Desktop / Continue on web / Start exploring / I've done
this before, illustration caption)
- step-header.tsx ("Step N of M" counter + matching aria-label)
- onboarding-flow.tsx (skip-onboarding error toast)
Test wrapper added to onboarding/components/step-header.test.tsx —
custom render() helper wraps with <I18nProvider> so the "Step 2 of 5"
assertions match the EN bundle.
Deferred (acceptable English fallback for now): step-questionnaire,
step-workspace, step-runtime-connect, step-platform-fork, step-agent,
step-first-issue, cli-install-instructions, option-card, runtime
aside panels, starter-content-prompt, cloud-waitlist-expand. These
are deeper steps with significant copy that would benefit from a
focused dedicated pass — voice on each is more nuanced (questionnaire
options, runtime install instructions, agent template recommendations).
Verified: pnpm --filter @multica/views typecheck (clean) +
test (238/238).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(i18n): add EN/zh-Hans key parity guard
Schema-level vitest that walks RESOURCES.en and RESOURCES["zh-Hans"]
namespace by namespace and asserts both bundles cover the same key
set. i18next plural rule is normalized before compare (`_one` /
`_other` collapse to a single logical key) so EN's plural pair
matches zh's `_other`-only form.
Catches retrofit drift where a new EN key lands without zh —
previously this would silently fall back to the English string in
production. Cheap to keep green: 39 tests across 21 namespaces in
under a second.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(i18n): translate issues namespace
Translates the entire issues surface — list / board / detail / comments /
sub-issues / activity feed / batch toolbar / pickers / context menu /
backlog-agent hint dialog / labels panel.
Component coverage:
- issues-page (page header, empty state, move-failed toast)
- issues-header (scope tabs, filter dropdowns w/ status/priority/
assignee/creator/project/label, display settings, sort, view toggle)
- issue-detail (page header, breadcrumb, properties / parent issue /
details / token usage sections, sub-issues, activity timeline,
formatActivity for status/priority/assignee/title/due-date changes,
subscribe/subscriber popover)
- comment-card + comment-input + reply-input (delete dialog, edit/save,
copy/edit/delete row, reply count, placeholders, expand/collapse)
- agent-live-card (is-working banner, tool count, stop / transcript)
- execution-log-section (section header, show/hide past runs, trigger
text builder, status labels, cancel-task)
- batch-action-toolbar (selected count, delete dialog with plurals)
- backlog-agent-hint-dialog (full dialog content)
- labels-panel (intro, create form, list, delete dialog)
- pickers (status / priority / assignee / due-date / label / property
search placeholder + no-results)
- issue-actions-menu-items (all dropdown / context menu items)
- use-issue-actions / use-issue-timeline (toast strings)
STATUS_CONFIG / PRIORITY_CONFIG label rendering routed through
$.status[enum] / $.priority[enum] at every call site; the core config
keeps its English fallback for non-i18n consumers but UI never reads
.label directly anymore.
Tests retrofitted: issues-page, issue-detail, and issue-actions-menu
RTL specs now wrap renders in <I18nProvider> with the EN bundle, so
their string assertions match the bundle (not hardcoded literals).
ESLint i18next allow-list extended to 24 issues files. Verified:
pnpm --filter @multica/views typecheck + test (277/277) all green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(i18n): translate agents namespace
Translates the agents listing + detail surface and the create/duplicate
flow. Covers the high-frequency surfaces; deeper sub-tab editors
(activity / instructions / skills / env / custom-args bodies, and the
hooks-buggy runtime/model/concurrency pickers) are deferred — they
have their own pre-existing react-hooks rule violations and benefit
from a focused dedicated pass.
Component coverage:
- agents-page (page header w/ tagline + new button, scope segment,
search, sort dropdown, availability chips, archived toolbar, empty
state, no-matches messaging w/ search interpolation, list-load
error)
- agent-detail-page (back link, archived banner, archive dialog,
not-found state, all 4 toast strings)
- agent-detail-inspector (avatar editor, name + description popover,
description dialog, every PropRow label, validation message,
presence badge label sourced from $.availability[enum])
- agent-overview-pane (tab labels, discard-unsaved-changes dialog)
- create-agent-dialog (title / description / labels / placeholders /
duplicate-suffix / runtime filter buttons / runtime status copy)
- agent-row-actions (full dropdown items + cancel-tasks dialog with
pluralized "N running + M queued" summary + archive dialog + 6 toasts)
- agent-columns (every header cell, You / Archived chips, runtime
fallback labels, availability + workload labels via $.availability /
$.workload, activity tooltip body w/ created_today / created_days_ago
/ runs / failed-percent interpolation)
- inspector/skill-attach (Attach trigger label + aria)
availabilityConfig and workloadConfig now keep colors only — the
display label lives in the bundle, sourced via $.availability[enum]
and $.workload[enum] at every call site. Same pattern as
STATUS_CONFIG/PRIORITY_CONFIG in the issues namespace.
ESLint i18next allow-list extended to 8 agents files.
Verified: pnpm --filter @multica/views typecheck + test (277/277)
all green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(i18n): clear 30 stray EN strings in translated files
Tail of literal strings missed in earlier passes — the ESLint i18next
allow-list flagged them but they slipped through review. Files touched:
- layout/app-sidebar.tsx (10 keys: Workspaces / Pending invitations /
Create workspace / Join / Decline / Log out / New Issue + shortcut /
Pinned / Workspace / Configure)
- runtimes/components/runtime-detail.tsx (Serving header + serving_count
pluralization, no_agents copy, running/queued chips with count
interpolation, Diagnostics header, CLI label, Delete runtime button,
Technical details toggle, last seen interpolation)
- onboarding/steps/step-welcome.tsx (entire WelcomeIllustration mock —
5 cards × actor names + body copy + 3 mention chips + 2 timestamps;
zh translation reads naturally instead of leaving the demo English)
- settings/components/labs-tab.tsx (`Co-authored-by: ...` git trailer
wrapped in {} so linter sees a JS string, not JSX text — magic
identifier git relies on, must not translate)
- settings/components/members-tab.tsx (✓ glyph wrapped in {})
- modals/feedback.tsx (⌘↵ shortcut wrapped in {})
ServingAgentsCard now reads availability/workload labels from
`agents` namespace (cross-namespace useT) so the bundle-truth pattern
holds: presenceConfig keeps colours only, label text comes from the
shared bundle.
Verified: typecheck + 277/277 tests + lint (only the pre-existing
react-hooks rule-of-hooks errors remain, which task #6 addresses).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(agents): rules-of-hooks + translate 4 model/runtime pickers
Three pre-existing react-hooks/rules-of-hooks violations + one missing
useMemo dep cleared, then the four pickers wired through useT.
Hook order fixes:
- concurrency-picker: useEffect now runs before the !canEdit early
return. Stale-draft reset still works the same way.
- runtime-picker: useMemo for the filtered list moved above the
!canEdit branch.
- model-dropdown: `models = data?.models ?? []` was minting a fresh
array each render and tripping the deps lint of the downstream
useMemo. Wrap in useMemo so the reference is stable.
Translation coverage:
- concurrency-picker: tooltip ("Concurrency · N max..."), range
helper text, Save button.
- runtime-picker: trigger label fallback ("No runtime"), tooltip
text composed from {{name}} + status, Mine/All filter buttons,
empty-list copy, "owned by {{name}}" + status fragments in row
tooltip, Cloud badge, online/offline aria.
- model-picker: trigger label, tooltip, "Managed by runtime"
fallback, search placeholder, "Discovering models…", default
badge, "No models available", "Use \"X\"" custom-id flow, Clear
button + its title.
- model-dropdown: every label string including the "Select a runtime
first" / "Default (provider)" / "Runtime offline — enter manually"
trigger fallbacks, the supported=false explanation block, discovery
failed badge, all popover items.
ESLint allow-list extended to 4 picker files. Verified: typecheck +
277/277 tests + lint (0 errors, only pre-existing react-hooks warnings
in unrelated files).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(i18n): translate runtimes list + connect dialog + CLI updater
Three deep runtime surfaces wired through useT, with the agents
namespace doing double duty for shared availability/workload labels.
runtime-columns:
- 7 column headers via t-augmented createRuntimeColumns({ t }).
- HealthCell now reads from useHealthLabel() (already translation-aware)
instead of the EN-only healthLabel() helper.
- WorkloadCell sources the label from $.workload[enum] (cross-namespace
to agents) — colour stays via workloadConfig.
- CostCell delta "flat" copy + CLI cell "Desktop" badge + update-
available aria/tooltip + RowMenu's full delete dialog (title /
description with {{name}} interpolation / cancel / confirm /
deleting state) plus its admin-permission hint.
connect-remote-dialog:
- Three steps fully translated: instructions (header + 4 numbered
steps + security warning + troubleshooting list with mono code
snippets escaped as JS strings), waiting (loader + hint), success
(CTA pair).
- Mono CLI commands wrapped in {} so linter sees JS strings — those
are literal commands that must stay untranslated for the user to
paste into a terminal.
update-section:
- statusConfig collapsed to icon+colour only; labels move to
$.update.status[enum] for proper translation per-state.
- "CLI Version:" / "Latest" / "available" / "Update" / "Retry"
copy + the "Managed by Desktop" tooltip and disabled hint.
Layout helpers tagged: runtime-list passes `t` through to the column
factory the same way agent-columns does.
ESLint allow-list extended with the 4 wired files. Verified:
typecheck + 277/277 tests + 0 i18n lint errors. usage-section.tsx
(KPI cards / WhenChart / TopUsageBreakdown / receipt table) is the
remaining runtimes surface — chart-heavy and benefits from a focused
pass next.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(i18n): translate 5 agent detail tabs + skill-add dialog
The 5 tabs that fill the agent detail right pane plus the shared
skill picker dialog. Agents bundle gains a `tab_body` block with
sub-namespaces per tab + a `common` slot for save/add/unsaved.
Tab coverage:
- instructions-tab: intro paragraph, multi-line example placeholder
(full 18-line zh translation), Save / Unsaved.
- env-tab: read-only intro / empty state, editable intro with two
inline `<code>` env-var examples kept English (mono terminal
payloads), KEY / value placeholders, Show/Hide value aria, Add /
Remove aria, all 3 toasts (duplicate keys / saved / save failed).
- custom-args-tab: intro about whitespace splitting, launch-mode
prefix line + `<your args>` placeholder, --flag value placeholder,
Add, Remove aria, both toasts.
- skills-tab: intro, Add skill button, import-hint callout, empty
state title + hint + add-CTA, remove-failed toast.
- activity-tab: 3 section titles (Now / Last 30 days / Recent work),
active-task pluralization, performance subtitle, all 3 empty
states, runs/success%/avg-duration/failed pluralization with
interpolation, source labels (Issue / Chat / Autopilot / Untracked),
source fallbacks (Quick create / Creating issue / Chat session /
Autopilot run), issue-short fallback, "Triggered by" tooltip
header, open-issue / transcript / cancel-task tooltips and ARIAs,
cancelling state, started/dispatched/queued time prefixes, show
more.
- skill-add-dialog: dialog title + description, empty list copy,
Cancel button, add-failed toast.
skills-tab.test.tsx wrapped in <I18nProvider> with the EN bundle so
its `Local runtime skills are always available` assertion still
matches the resolved translation instead of the raw key path.
ESLint allow-list extended with the 6 wired files. Verified:
typecheck + 277/277 tests + 0 i18n lint errors. Only the per-test
mock for skills-tab needed wrapping; the other 4 tabs ship without
test files of their own and inherit the I18nProvider chain via
agent-overview-pane / agent-detail-page test renders (when those
exist later).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(i18n): translate onboarding step-questionnaire + option-card
The user-profile step (3 questions) is the first deferred onboarding
deep step now wired through useT.
step-questionnaire:
- Eyebrow + headline + answered-progress counter with {{count}}
interpolation
- All 3 questions and their option labels (team size / role / use case)
- All 3 "Other" placeholders for free-text fallback
- Right-rail "Why three questions" / "What you get" panel: 2 eyebrow
rows, 2 unlock-item title+body pairs, learn-more link
- Back / Continue buttons via shared `common` block
option-card: shared "Other" radio label and aria.
Test wrapped in <I18nProvider>. EN value of `other_label` kept as
"Other" so the existing /^other$/i regex in step-questionnaire.test
keeps matching after the rendering pipeline switched from a hardcoded
literal to a bundle lookup.
ESLint allow-list extended with these 2 files. The remaining 4 deep
steps (workspace / runtime-connect / platform-fork / agent), the
2 ancillary surfaces (cli-install-instructions / starter-content-
prompt), and the 3 side panels (runtime-aside-panel / cloud-waitlist-
expand / compact-runtime-row) will be surfaced + swept by the global
ESLint switch (next commit).
Verified: typecheck + 277/277 tests + 0 i18n lint errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(i18n): flip ESLint to glob + drain remaining hardcoded EN
ESLint i18next/no-literal-string now applies to **/*.tsx by default
instead of an explicit allow-list. Files that genuinely still need
hardcoded EN are listed in STILL_HARDCODED — concrete, finite, and
the goal is to drain that list to zero.
Tail strings translated in this commit (surfaced by the global flip):
- common/task-transcript/agent-transcript-dialog.tsx — full dialog:
status badge (Running / Completed / Failed), sr-only DialogTitle,
Filter dropdown trigger + Clear filters, Copy all / Copy filtered /
Copied, tool-calls + events metadata chips with pluralization,
events-filtered "{{shown}} of {{total}}" interpolation, "Waiting
for events..." live state, "No execution data recorded." past
state. New `transcript` block in agents namespace.
- runtimes/components/charts/activity-heatmap.tsx — Less / More
legend labels around the contribution-style heat squares.
- search/search-trigger.tsx — sidebar Search... button label.
⌘ glyph wrapped in {} to satisfy the linter (mono shortcut symbol,
not translatable).
Holdouts (STILL_HARDCODED, ~14 files): the deep onboarding steps
(workspace / runtime-connect / platform-fork / agent / first-issue /
cli-install-instructions, plus 4 ancillary panels), the runtimes
usage-section + KPI cards, and 5 minor agent visual primitives
(sparkline / agent-presence-indicator / agent-profile-card /
visibility-badge / char-counter). Each one gets a dedicated future
pass; the global rule prevents new hardcoded strings from landing
elsewhere.
Verified: typecheck + 277/277 tests + 0 i18n lint errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(i18n): drain agent visual primitives + onboarding small components
8 files removed from STILL_HARDCODED:
agents/components/:
- char-counter — over-limit text with {{count}} interpolation
- visibility-badge — uses new agents.visibility.{private,workspace}.
{label,tooltip} block; drops VISIBILITY_LABEL/TOOLTIP imports from
core in favour of bundle-driven copy
- agent-presence-indicator — availability + workload labels via
$.availability[enum] / $.workload[enum] (cross-namespace),
queue-badge "+N queued" with pluralization
- agent-profile-card — Agent unavailable / Detail link / Owner /
Skills / Runtime / Unknown runtime / Archived chip / availability
line via cross-namespace lookup
agents.json: new presence + visibility + profile_card + char_counter
blocks.
onboarding/components/:
- compact-runtime-row — online/offline aria via agents.availability
- runtime-aside-panel — full content (What's a runtime / Good to
know / Swap anytime / Add more later / docs link)
- starter-content-prompt — full dialog (title / description with
inline emphasis / both buttons / 3 toasts)
- cloud-waitlist-expand — intro paragraph + warning span / email
+ reason labels + placeholders + Optional badge / Join + on-list
states / both toasts
onboarding/steps/:
- cli-install-instructions — copy aria + intro + 2 step labels
onboarding.json: new runtime_aside / cli_install / starter_content /
cloud_waitlist blocks.
Tests for step-platform-fork + step-runtime-connect wrapped in
<I18nProvider> with EN bundle so /you're on the list/i etc. still
matches the resolved translations.
Verified: typecheck + 277/277 tests + 0 i18n lint errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(i18n): translate onboarding deep steps
The 5 large onboarding steps that were deferred from earlier passes,
plus their support helpers, all wired through useT.
step-first-issue (final beat — flips onboarded_at):
- error_title / Retry / retry_failed toast / finishing / opening
states.
step-agent (creates the user's first agent):
- Templates moved from a module-level const to a useT-driven
useAgentTemplates() hook. Names + emoji stay constant (visual
identity), labels + blurbs + instructions resolve from the
bundle. coding / planning / writing / assistant — all four
templates ship a full zh translation that reads naturally.
- Recommended badge, eyebrow + headline + lede, footer hint,
Create {{name}} CTA, create_failed toast.
- Right-rail "About agents" panel (4 way-items + headline +
add-more hint + docs link).
step-workspace (create or pick existing):
- 5 footer states (open / creating / creating-pending / name-first
/ pick), all hint + CTA strings via interpolation.
- Name + URL + slug placeholders, issue-prefix preview spans,
Create-new card title + subtitle.
- 8-row WorkspacePreviewCard sidebar (Inbox / Issues / Agents /
Projects / Autopilot / Runtimes / Skills / And more) — every
label + meta strapped to bundle keys.
- 4 perks (assign / chat / invite / switch) + 3 next-steps
(runtime / agent / starter), 2 toasts (slug-conflict / failed).
- `multica.ai/${slug}` mono URL escaped via template-literal
expression so the linter sees a JS string.
step-runtime-connect (desktop scan flow):
- 3 phase headlines + ledes (scanning / found / empty), trust-strip
status (all online / N online / none online) with pluralization,
online/offline labels, Skip / Continue / Selected hint.
- Empty-view 2 cards (skip + waitlist) and the cloud waitlist
dialog wrapper.
step-platform-fork (web fan-out):
- Eyebrow + headline + lede, footer hint with 3 phase variants.
- Primary download card (before/after click) + 2 alt cards (CLI /
cloud) + CLI dialog with 4 elapsed-time stages (normal / midway /
slow / stalled), live-listening header, runtime-connected
pluralization, cloud waitlist dialog.
ESLint: STILL_HARDCODED list shrunk from 14 entries to 1 — only
runtimes/components/usage-section.tsx (chart-heavy KPI panel)
remains.
Verified: typecheck + 277/277 tests + 0 i18n lint errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(i18n): translate runtimes usage panel + drop STILL_HARDCODED
Final i18n holdout: the runtimes usage panel (KPI hero, WHEN chart
tabs, cost-by breakdowns, daily breakdown table) is wired through
useT("runtimes"). With this drained, the eslint scaffolding for
explicit holdouts is removed — every JSX text node in @multica/views
now flows through i18n.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(i18n): drain rollout gaps + add cross-device sync
Lands the post-review punch list for the i18n rollout: closes correctness
gaps that would have shipped silently, and adds the missing cross-device
locale sync the rollout's docs already promised.
Coverage:
- Register issues + agents namespaces in RESOURCES (90 useT call sites
were rendering keys-as-text in production)
- Harden parity test to compare RESOURCES keys against on-disk JSON
files, so a future missing namespace registration fails loudly
- Server-side language whitelist in UpdateMe + reject-unsupported test
- Safe SupportedLocale resolution in appearance-tab (no more `as` cast
on a region-tagged BCP-47 string)
- HTML lang attribute uses zh-CN (not zh-Hans) for screen reader / CJK
font-stack compatibility
- Cookie Secure flag on https
- Pulled createBrowserCookieLocaleAdapter out of the server-safe entry
into a new @multica/core/i18n/browser subpath; document.cookie access
can no longer leak into Edge middleware imports
Cross-device sync:
- New UserLocaleSync component mounted in CoreProvider; on login, if
user.language differs from the active i18n.language, persist via the
adapter and reload. Both apps benefit
- Desktop main process tracks system locale and emits IPC on focus when
it changes; renderer reloads only when the user has no explicit
Settings choice (their preference still wins)
Tests:
- pickLocale / matchLocale (11 cases incl. region-tagged BCP-47, malformed
tags, zh-Hant collapse-to-zh-Hans semantics)
- browser-cookie-adapter (6 cases under jsdom)
- Shared renderWithI18n helper at packages/views/test/i18n.tsx that wraps
the real RESOURCES map; future tests opt in instead of inlining a
per-file TEST_RESOURCES slice that goes stale silently
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(conventions): consolidate naming + i18n glossary into docs site
Single source of truth for code naming, i18n translation glossary, and
Chinese voice rules. Previously split between packages/views/locales/glossary.md
and scattered comments — now lives at apps/docs/content/docs/developers/conventions.{mdx,zh.mdx}
with both English and Chinese versions kept in sync.
Three sections per page:
1. Code naming — routes, packages, files, DB, Go, TS, commits
2. i18n translation glossary — entity vs concept rule, what to translate,
word combination, plurals, interpolation, key naming
3. Chinese voice + style — punctuation, principles, where to look in doubt
Side effects:
- packages/views/locales/glossary.md collapses to a stub redirecting to
the docs page; do not edit it
- CLAUDE.md gets a new top-level "Conventions reference" section so any
Claude session sees the pointer before any other rule
- apps/docs/content/docs/developers/ gets a stub English meta.json so the
conventions page is reachable on the EN side (contributing.zh.mdx /
architecture.zh.mdx remain ZH-only — separate work)
- Both root sidebars get a new "Developers" group
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(i18n): apply zh voice rules + translate project/autopilot
Two-part cleanup driven by the conventions doc landed last commit:
Voice violations (mechanical sweep across 10 zh-Hans namespaces):
- 「」 (Japanese-style brackets) → \" to match the EN source's straight
double quotes (~13 sites)
- … (single-char ellipsis) → ... three dots (~43 sites)
- Drop translation-ese pronoun "我们" where it's a pure narrator
("我们已发送" → "已发送", "我们替你托管" → "由 Multica 托管"); keep
"告诉我们" where "we" is the legitimate brand recipient
- "作为父级 / 作为子级" → "设为父级 / 设为子级"
- "任务" mistranslated as the task entity → `task` (lowercase EN entity)
- Dialog title "Autopilot" → "autopilot"
Translate project / autopilot per industry consensus:
- `project` → 「项目」 (~42 value sites). Feishu / Tower / Teambition /
PingCode / GitHub Projects all translate; no Chinese product keeps
`project`.
- `autopilot` → 「自动化」 (~34 value sites). Avoids the Tesla-style
「自动驾驶」 association; matches Notion / Feishu's industry term.
- Issue / skill / task remain lowercase EN per dev-team familiarity.
- Sidebar nav-label entities get Title Case ("Issue" / "Skill" / "我的
Issue") so the entry-point label reads as a proper UI signal; body
prose stays lowercase.
Conventions doc (EN + ZH) reflects the decision and adds a "why these
translate but issue/skill/task don't" rationale block.
Verification: parity test 45/45, full monorepo typecheck green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(i18n): translate chat session delete + project resources section
Two features main shipped while this branch was idle never went through
the i18n pass:
- Chat session delete confirmation dialog (#2115) and history toggle
tooltip (#2117): adds session_history.delete_dialog.* and
session_history.row_delete_*, plus window.history_show_tooltip /
history_back_tooltip.
- Project resources sidebar (#1926/#2080/#2111): entire component
including toasts, popover form, attach/remove tooltips. New
projects.resources subtree.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
1b3c78e4b5 |
fix(pins): unpin missing sidebar rows (#2062)
* fix(pins): unpin missing sidebar rows * fix(pins): guard missing pin auto-unpin |
||
|
|
140678c4b3 |
fix(web): redesign 404 + break NoAccessPage redirect loop (#2122)
* refactor(web): rewrite 404 page using design tokens Replace editorial-style 404 (hardcoded cream/ink/terracotta colors, Instrument Serif font, fluid clamp() typography) with a minimal version using semantic tokens and the project's buttonVariants helper. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(workspace): break NoAccessPage redirect loop by clearing stale cookie The web proxy redirects / to /<lastSlug>/issues based on the last_workspace_slug cookie alone, with no access check. When a user gets evicted from a workspace, the cookie still points at it; clicking "Go to my workspaces" then loops: NoAccessPage -> / -> proxy -> same bad slug -> NoAccessPage. Clear the cookie on mount so the proxy falls through to the landing page, which resolves the correct destination via the workspace list. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(web): mark not-found as client to allow buttonVariants import buttonVariants is exported from a "use client" module, so calling it from a server component is rejected by Next 16's directive checks. Production build of /workspaces/new prerender failed because of this. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
a4fac51cf5 |
fix(projects): add resource_count breadcrumb instead of inlining resources (#2118)
* fix(projects): add resource_count breadcrumb to project responses Closes #2087 `multica project get` previously returned project metadata with no signal that resources existed. Agents that fetched a project this way had no way to discover its attached resources without already knowing about `/api/projects/{id}/resources` or the on-disk `.multica/project/resources.json`. Rather than inline the full resource list into the parent payload (which conflates parent metadata with a child sub-collection and locks the resource_ref shape into the project endpoint's contract), this adds a scalar `resource_count` breadcrumb to ProjectResponse. The actual list stays at the dedicated sub-collection endpoint. Changes: - GetProjectResourceCounts :many — new batched sqlc query - ProjectResponse.ResourceCount populated in GetProject, ListProjects, SearchProjects, and the with-resources CreateProject echo - multica project get prints a stderr hint pointing at multica project resource list <id> when count > 0; the JSON on stdout stays parseable - Meta-skill (runtime_config.go) lists multica project get and multica project resource list in Available Commands so agents that read CLAUDE.md / AGENTS.md know about both paths Co-authored-by: multica-agent <github@multica.ai> * fix(projects): wire ResourceCount through Update + Create event payload Review feedback on #2118. - UpdateProject now reloads ResourceCount before responding/publishing. Previously a title- or status-only PUT served (and broadcast over WS) resource_count: 0 even when resources existed. - The with-resources CreateProject path sets resp.ResourceCount before the project:created publish, so the WS event payload matches the HTTP echo. The hand-rolled response map collapses to an embedded ProjectResponse + resources array — one source of truth for the serialized shape. - packages/core/types/project.ts: Project gains resource_count: number to keep the TS contract aligned with the server response. Tests: - TestProjectResourceCountBreadcrumb extends to assert UpdateProject preserves the breadcrumb. - TestCreateProjectWithResourcesEchoesCount asserts the create echo carries resource_count matching the attached resources. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
2b967338a8 |
fix(runtimes): narrow CostCell usage window from 180d to 14d (#2119)
The runtimes list page renders a CostCell per row that only displays a 7d cost total plus a 7d-vs-prior-7d delta. Until now each cell still fetched a 180d usage window so the cache key matched the runtime-detail page (clicking a row would pre-warm detail). The side effect was N parallel 180d in-line aggregations against task_usage on every list visit, one per runtime, which dominated DB load for this view. Switch the cell to a 14d window — exactly the data it actually needs for cost7d + costPrev7d. Detail still owns its own 180d query; the worst case after this change is one extra request on first navigation into detail, in exchange for a large steady-state reduction on the list page (down to 14d × N instead of 180d × N, ~13× fewer rows scanned per request). This is the frontend half of the runtime-usage perf work tracked in MUL-1748. The backend index + daily rollup changes will land separately. Co-authored-by: Eve <eve@multica.ai> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
6ef9be10d6 |
fix(chat): expose History panel + delete affordance from chat header (#2117)
ChatSessionHistory was already implemented but unreachable: nothing in the app rendered it and there was no UI to toggle showHistory. The trash icon on each session row was therefore invisible. Adds a History icon button to the chat-window header that toggles the panel; when on, it renders ChatSessionHistory in place of the message list and input. Per-row delete (hover trash + AlertDialog) works as designed. Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
60b215f44f |
feat(chat): support deleting chat sessions (#2115)
* feat(chat): support deleting chat sessions
Replaces the unreachable archive endpoint with a real hard delete and
exposes it from the chat history panel.
- DELETE /api/chat/sessions/{id} now hard-deletes the session and its
messages (CASCADE), cancels any in-flight tasks before removal so the
daemon doesn't keep running work whose result has nowhere to land,
and broadcasts chat:session_deleted.
- Frontend adds a per-row delete button with a confirmation dialog,
optimistically drops the session from both list caches, and clears the
active session pointer locally + on other tabs via the WS handler.
Co-authored-by: multica-agent <github@multica.ai>
* fix(chat): make session delete atomic and keep archived sessions read-only
Address review feedback on #2115.
- DeleteChatSession now runs lock + cancel + delete in a single tx and
only broadcasts events post-commit. The new LockChatSessionForDelete
query takes FOR UPDATE on chat_session, which blocks the FK validation
of any concurrent SendChatMessage trying to enqueue a task for this
session — that insert fails after we commit, so it can no longer
produce an orphaned task whose chat_session_id is nulled by
ON DELETE SET NULL. Cancel failure now aborts the delete instead of
warn-and-continue.
- SendChatMessage refuses non-active sessions again. The archive code
path is gone, but legacy rows with status='archived' may still exist
in the DB; keep the guard until we explicitly migrate them.
- Frontend re-reads allChatSessionsOptions to disable ChatInput on
legacy archived sessions so the UX matches the server-side guard.
Co-authored-by: multica-agent <github@multica.ai>
---------
Co-authored-by: multica-agent <github@multica.ai>
|
||
|
|
8b0eeb0615 |
fix(projects): show URL tooltip on already-attached repos in Add Resource list (#2111)
The repo button in the Add Resource popover used the native `disabled` attribute when a repo was already attached. Browsers suppress pointer events on disabled form controls, so the tooltip on the URL text never fired for attached rows — the issue spec calls out "hovering over any URL should also show the complete URL in a tooltip". Switch to `aria-disabled` plus a click guard so the row still announces as disabled to assistive tech, looks the same visually, and is no longer click-able, but hover still reaches the tooltip trigger. Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
a7299bf857 |
refactor(projects): pass projectId prop to ProjectIssuesContent (#2110)
Replace `scope.replace("project:", "")` with the `projectId` already
held by `ProjectDetail`, so the create-issue handler in the empty
state no longer depends on the `project:<id>` scope-string format.
Co-authored-by: multica-agent <github@multica.ai>
|
||
|
|
99f6cb8130 |
fix(projects): add New Issue button to empty project state and URL tooltips to resources (#2080)
When a project has no issues, show a [+ New Issue] button that opens the create-issue dialog with the project pre-selected. Previously users had to navigate to the issues page and manually assign the project. Also add tooltips to repository URLs in the Resources section so truncated URLs can be read in full on hover. Fixes #2078 |
||
|
|
b5f1e506e5 |
fix(views): split desktop/mobile sidebar state in project-detail (#2067)
Mobile project-detail mounted its <Sheet> with open=true for one render — useIsMobile() reports false on first render and flips to true on the next, so the mobile branch briefly mounted Base UI Dialog open, painted its fixed inset-0 z-50 backdrop and locked scroll. The follow-up useEffect toggled it closed within the same animation cycle, leaving Dialog's pointer-events/inert/scroll-lock state stuck on mobile. Mirror packages/views/issues/components/issue-detail.tsx by keeping desktopSidebarOpen (default true) and mobileSidebarOpen (default false) as separate states, binding the mobile <Sheet> to mobileSidebarOpen only. The single-state pattern dates back to #1087, where issue-detail and project-detail received mobile-Sheet support together but only issue-detail used split state. |
||
|
|
00cde21724 | fix(views): hide archived agents from runtime detail (#2097) | ||
|
|
1476c268dd |
refactor(quick-create): exempt git-describe daemons from CLI gate (#2108)
* refactor(quick-create): remove daemon CLI version gate Local-source daemons report dev-suffixed versions (e.g. v0.2.15-235-gdaf0e935) that the picker pre-check and server gate both treat as too old, blocking quick-create during local testing. Drops the gate end-to-end: removes MinQuickCreateCLIVersion + CheckMinCLIVersion in pkg/agent, the checkQuickCreateDaemonVersion handler and readRuntimeCLIVersion helper in handler/issue.go, and the mirrored cli-version.ts plus the modal's pre-check, blocked-state UI, and daemon_version_unsupported error branch. Co-authored-by: multica-agent <github@multica.ai> * refactor(quick-create): skip daemon CLI version gate in dev Restores the gate (reverts the full-removal commit) and bypasses it in non-production environments instead. The motivation for the original removal — local source-built daemons report a `git describe` version like v0.2.15-N-gHASH that parses below 0.2.20 and blocks dev testing — is now handled by checking APP_ENV on the server and NODE_ENV on the client. Production keeps the original "needs upgrade" UX. Co-authored-by: multica-agent <github@multica.ai> * refactor(quick-create): exempt git-describe daemons instead of env bypass Replaces the per-environment bypass added in the previous commit with a shared daemon-version signal. CheckMinCLIVersion / checkQuickCreateCliVersion now treat any daemon whose CLI version matches the `vX.Y.Z-N-gHASH[-dirty]` git-describe shape as OK; tagged releases keep going through the normal min-version comparison. Why: Emacs flagged that (a) NODE_ENV !== "production" also disables the gate on staging and other non-prod deployments, undoing the protection for the case the gate was originally written for, and (b) NODE_ENV (web client) and APP_ENV (server) are not equivalent, so the modal pre-check and server gate could disagree on the same request. Both go away when the signal is intrinsic to the daemon's version string. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
9a5f5ca498 |
fix(views): coalesce repeated task_completed/task_failed activity entries (#2044)
Consecutive "completed the task" entries from the same agent now merge into a single line showing the count (e.g. "completed the task (7 times)") regardless of time gap. Other activity types keep the existing 2-minute coalescing window. Closes MUL-1709 |
||
|
|
daf0e935f6 |
fix(views): show Ctrl+K / Ctrl+Enter on non-Mac platforms (#2060)
The sidebar search trigger, quick-create-issue modal, and feedback modal hardcoded the Mac glyphs (⌘, ↵) for their keyboard hints, so Windows and Linux users always saw Mac shortcuts even though the underlying handlers already accept metaKey || ctrlKey. Extract a small platform helper (isMac, modKey, enterKey, formatShortcut) in packages/core/platform/keyboard.ts and route all four affected sites (plus the editor bubble menu, which had the same logic inlined) through it, so non-Mac users see Ctrl+K, Ctrl+Enter, etc. Closes multica-ai/multica#2056 |
||
|
|
a57dd76faf |
fix(views): improve mobile responsiveness for agents and settings (#2036)
* feat(agents): make agent detail page mobile responsive (#1) Stack the inspector + overview pane vertically below md, switch the shell to page-level scroll so the inspector flows naturally, give the overview pane a min-h-[60vh] floor so tabs stay usable, and let the 5-tab nav scroll horizontally on narrow viewports. * fix(settings): make Repositories tab and Settings shell mobile-responsive (#2) The Settings shell used a fixed w-52 sidebar with no responsive behavior, leaving almost no room for tab content on phone-width viewports. Stack the nav above the content on mobile, scale inner padding, and let the Repositories tab's input/button rows wrap rather than overflow. |
||
|
|
c24191a884 |
fix(editor): keep blank-line paste inside the code block (#2058)
Pasting `line1\n\nline2` while the caret was inside a code block ran the text through the Markdown parser, which split on the blank line and tore the code block open, dropping the trailing content into a sibling paragraph. Detect the codeBlock parent on `handlePaste` and insert the clipboard text verbatim instead. Code blocks have `code: true`, so newlines stay literal — exactly what users expect when pasting code or logs. Closes #1982 |
||
|
|
cb078c0f36 |
fix(core): patch byIssue label cache on WS label change (#2048)
`onIssueLabelsChanged` patched the embedded `labels` field in the issue list and detail caches but never touched `labelKeys.byIssue`, the cache backing the issue-detail Properties LabelPicker. Mutations already covered all three caches; WS-driven changes (agents, other tabs) left the picker stale until remount, since `staleTime: Infinity` plus `refetchOnWindowFocus: false` prevent recovery on focus. |
||
|
|
e13e5edc8e |
fix(issues): trimEnd comparison on blur to avoid unnecessary updates (#2054)
Fixed: #2053 |
||
|
|
fee393df1f | fix(views): show full repo URLs in project creation (#2045) | ||
|
|
1ff4e27e77 |
feat(quick-create): cache agent prompt draft across navigation (#2039)
When creating an issue with agent, the input content was lost when navigating away (e.g., to view a ticket) and returning. Manual create already persisted its draft - now agent create does too. Changes: - Add prompt field to useQuickCreateStore (persisted with workspace) - AgentCreatePanel reads initial prompt from draft store if no transient data.prompt is provided - onUpdate now saves prompt to draft store (not just hasContent) - clearPrompt() called after successful submit Fixes: #1957 |
||
|
|
fbf9460d5e |
feat(chat): support fullscreen expand mode (#2043)
* feat(chat): support fullscreen mode similar to Linear When the expand button is clicked, the chat window now fills the entire content area (inset-0) instead of scaling to 90% of parent. Resize handles are hidden in fullscreen mode. * fix(chat): use stacked card layout for fullscreen mode Fullscreen chat now uses inset-3 with rounded corners, ring, and shadow to create a stacked card effect on top of the content area — matching the Linear design — instead of a flush inset-0 fill. * feat(chat): add motion.dev spring animations for expand/collapse - Install `motion` in @multica/views - Replace CSS transitions with motion.div layout animation for expand/collapse (spring-based FLIP), giving a natural bouncy feel - Open/close uses spring scale + smooth opacity fade - Layout animations are disabled during drag-to-resize (instant updates) * fix(chat): remove spring bounce from expand/collapse animation Use critically damped springs (bounce: 0) so the animation settles directly at its target without overshooting. * fix(chat): fix text distortion during expand/collapse animation Use layout="position" instead of layout (full FLIP). Full FLIP uses scale transforms to animate size changes, which distorts text and child content. Position-only layout animates translate only — size changes are instant, text stays crisp. * fix: regenerate lockfile with pnpm@10.28.2 The lockfile was previously generated with pnpm 10.12.4, causing unrelated churn (lost libc constraints, deprecated metadata). Reset to main and regenerated with the repo's pinned pnpm@10.28.2 so the diff is scoped to the new motion dependency only. |
||
|
|
d492b9d7a6 |
Revert "feat(quick-create): add preset issue fields (#2002)" (#2042)
This reverts commit
|
||
|
|
cc94fbd305 |
fix: handle square brackets in agent names for mention parsing (#1992)
* fix: handle square brackets in agent names for mention parsing (#1991) The mention regex used [^\]]* to match labels, which broke when agent names contained square brackets (e.g. David[TF]). The ] inside the name caused the regex to stop matching prematurely, silently dropping the mention. Changes: - Backend (mention.go): Switch to .+? (non-greedy) anchored on ](mention:// to correctly match labels with brackets - Frontend (mention-extension.ts): Same regex fix in tokenizer, plus escape [ and ] in renderMarkdown to prevent creating ambiguous markdown syntax - Add comprehensive tests for ParseMentions covering bracket names Fixes #1991 * fix: add optional chaining for match group access Fixes TS2532: Object is possibly 'undefined' on match[1] when calling .replace() in the mention tokenizer. * fix: tighten mention tokenizer to reject ordinary Markdown links - Replace .+? with (?:\\.|[^\]])+ in start() and tokenize() regexes so the label cannot cross a ]( Markdown link boundary - Escaped brackets (\[ \]) from renderMarkdown() are still accepted - Add frontend tokenizer/serializer round-trip tests: - Plain mention - Escaped brackets (David[TF]) round-trip - Normal Markdown link + mention on same line (regression) - Multiple links before mention - Nested brackets (Bot[v2][beta]) - Issue mentions without @ prefix Addresses review feedback on #1992. * fix: add type assertions for tiptap MarkdownTokenizer interface in tests The tiptap MarkdownTokenizer type allows start to be string | function and tokenize to accept 3 arguments. Our extension always provides single-arg functions, so cast them for TypeScript satisfaction. Fixes CI typecheck failure in @multica/views package. * fix: cast renderMarkdown to single-arg shape and reset file modes to 0644 |
||
|
|
a039c4d803 |
feat(quick-create): add preset issue fields (#2002)
Fixed: #2001 |
||
|
|
3fe3b84981 |
fix: hydrate agent cache after create (#2027)
(cherry picked from commit
|
||
|
|
d0c66f3173 |
perf(issue-detail): memoize timeline render to mitigate Inbox long-timeline freeze (#2025)
* perf(issue-detail): memoize timeline render to fix Inbox long-timeline freeze On long-timeline issues (thousands of comments), opening from Inbox hard-freezes the browser tab because every WS-driven parent re-render re-runs the full react-markdown + rehype-* + lowlight pipeline for every comment. This is the S3 mitigation for multica#1968: - Wrap ReadonlyContent in React.memo so equal-content re-renders skip the markdown pipeline entirely (the dominant cost per comment). - Wrap CommentCard in React.memo so unrelated parent state updates don't re-render every card. - useMemo the timeline grouping in IssueDetail so the allReplies Map and groups array references are stable across re-renders that don't change timeline. - Stabilize toggleReaction via a timelineRef so its identity doesn't change on every WS event, which previously defeated CommentCard memoization. Virtualization (S2) is the root fix for first-paint cost and lands separately. Co-authored-by: multica-agent <github@multica.ai> * fix(issue-detail): destructure mutate/mutateAsync so CommentCard memo holds Per review on PR #2025: TanStack Query v5 returns a fresh result wrapper from useMutation on every render, with only the inner mutate / mutateAsync functions guaranteed stable. The previous useCallback dependencies listed the whole mutation object, so on every parent re-render the callbacks flipped identity — defeating React.memo on CommentCard and leaving the long-timeline mitigation only half-effective. Pull just the stable handles into deps. Add a renderHook-based regression test that re-renders useIssueTimeline twice and asserts the four callbacks passed to CommentCard keep the same identity. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
f85b7cce91 |
fix: make CLI update completion status reliable (#2018)
Co-authored-by: Devv <devv@Devvs-Mac-mini.local> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
050a2f0a5b |
fix(views): preserve kanban display settings when dragging issues (#1971)
Dragging an issue between kanban columns was forcefully switching the sort mode to "position" (manual), resetting any user-chosen display settings like sorting by title. Remove the auto-switch so the sort preference is preserved across drag operations. Fixes multica-ai/multica#1960 Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
374f62be13 |
feat(inbox): remove redundant mark-as-done hover button, add archive button for done tasks (#1970)
Remove the "mark as done" hover button from inbox list items since it duplicates the one in the issue detail header. For done tasks, show an archive button in the issue detail header instead. Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
d9e5cf87dd |
fix(views): responsive Autopilot list for mobile viewports (#1961)
Switch Autopilot list rows to a stacked layout below the sm breakpoint, hide desktop column headers on mobile, and match loading skeletons to the mobile row shape. Desktop table layout is preserved at sm and above. Closes MUL-1653 Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
befde379b5 | fix(runtimes): correct install script URL in connect remote dialog (#1949) | ||
|
|
51bc5a818f |
fix(onboarding): decouple from workspace state and route invitees correctly (#1936)
PR #1868 conflated "has workspace" with "completed onboarding" — restore `onboarded_at` as the single signal, and route invited users through a dedicated /invitations page before they ever see onboarding. - Backend: CreateWorkspace + AcceptInvitation atomically set onboarded_at alongside the member insert, establishing the invariant "member row exists ↔ onboarded_at != null" at the DB layer. - Migration 065: one-shot backfill closes the dirty rows produced by PR #1868 (users with a workspace but onboarded_at == null). - Entry points (web callback, login, desktop App): if onboarded_at is null, look up pending invitations by email and route to the new batch /invitations page; otherwise the resolver picks workspace / new-workspace as before. - OnboardingPage: stops bouncing on hasWorkspaces; only hasOnboarded bounces. Unblocks the user from completing Step 3 (workspace creation) → Steps 4 / 5. - StarterContentPrompt: only shows when the user is the solo member of the workspace, so invited users never get prompted to import starter content into someone else's workspace. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
298ed75b1d |
fix(views): only show "Mark as Done" button on Inbox page (#1934)
The toolbar button was previously visible on all issue detail views. Gate it on the `onDone` prop, which is only passed from InboxPage. Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
da5dbc6224 |
refactor(repos): drop unused description + tighten create-project layout (#1930)
* refactor(repos): drop unused description + tighten create-project layout
Two related changes that touch the workspace-repos surface together.
1. Remove the per-repo `description` field everywhere it was threaded.
The only place it ever surfaced was a markdown table column the daemon
wrote into the agent runtime config, where most rows just read "—"
anyway. Agents already discover project structure by running
`multica project` / `multica issue` against the CLI, so the human-
readable description string carried no real value while taking up an
extra Settings input row and propagating through six layers (settings
UI → workspace.repos jsonb → handler RepoData → daemon RepoData →
repocache.RepoInfo → execenv.RepoContextForEnv).
- Settings → Repositories drops the description input; the URL field
now spans the whole row.
- WorkspaceRepo TS type loses `description`; backend RepoData /
RepoInfo / RepoContextForEnv all collapse to URL only.
- Daemon's runtime_config Repositories block changes from a
`| URL | Description |` markdown table to a simple bullet list.
- Tests updated; jsonb residue in existing workspaces is dropped at
normalize time, so no migration needed.
2. Tighten the Create Project modal footer: pull the Status / Priority /
Lead / Repos pills onto the same row as the Create Project button
(Linear-style single-row footer) instead of stacking them above it,
and swap the Repos pill icon from `FolderGit` to a real GitHub mark
(lucide-react v1 dropped brand icons, so the mark lives inline as a
small SVG component in this file).
I tried promoting Repos to its own "Resources" strip above the footer
to separate the resources abstraction from project metadata, but with
a single pill it looked too sparse — leaving a TODO comment in the
footer to revisit once we add Linear / Notion / Figma / Slack
resource types.
* fix(daemon test): drop residual Description field on RepoData literals
* fix(repos): drop Description residue surfaced after rebase on #1929
Project-resource github_repo lift path (#1929) and registerTaskRepos
both still constructed RepoData{...Description: ...} after the rebase.
Two test sites in daemon_test.go and execenv_test.go also reintroduced
the field. Strip them so the Description-removal change builds and
tests pass with the latest main.
|
||
|
|
2fd388da08 |
fix: stabilize mobile issue detail layout (#1912)
Co-authored-by: Devv <devv@Devvs-Mac-mini.local> |
||
|
|
cba3db0d7f |
feat(markdown): add fullscreen lightbox for mermaid diagrams (#1927)
A sandbox="" iframe cannot run scripts, so users had no way to zoom or pan rendered Mermaid diagrams beyond browser scrolling. Add a hover toolbar with a fullscreen button that opens a portal-based lightbox showing the same diagram scaled to 90vw x 90vh, while preserving the sandbox isolation (the lightbox iframe is also sandbox=""). ESC or clicking the backdrop closes the lightbox. Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
44608713bb |
feat(projects): typed project resources + agent runtime injection (#1926)
* feat(projects): typed project resources + agent runtime injection
Adds a `project_resource` table that lets a project carry typed pointers
(github_repo today, more later) and surfaces them at agent runtime.
Server
- migration 065: project_resource (resource_type TEXT + resource_ref JSONB)
- sqlc CRUD + handler at /api/projects/{id}/resources
- claim handler attaches project_id/title + resources to issue tasks
Daemon
- TaskContextForEnv carries project context
- writes .multica/project/resources.json into workdir
- adds "## Project Context" block to CLAUDE.md / AGENTS.md / GEMINI.md
via type-dispatched formatter so new resource types just add a case
CLI
- multica project create --repo <url> attaches repos in one step
- multica project resource add/list/remove
Frontend
- Project create modal: Repos pill (workspace repos + ad-hoc URL)
- Project detail sidebar: collapsible Resources section with attach/remove
Docs
- New "Project Resources" chapter explaining the abstraction and
exactly what code to touch when adding a new resource type
Co-authored-by: multica-agent <github@multica.ai>
* fix(projects): transactional resources[] on create + generic CLI ref + test fix
Addresses review feedback on PR #1926:
1. CI red: TestProjectResourceLifecycle delete step called withURLParam
twice, which replaced the chi route context and dropped the project id.
Switched to the existing withURLParams helper from daemon_test.go.
2. POST /api/projects now accepts resources[] and attaches them in the
same transaction as the project. Invalid refs roll back the whole
create — no more half-attached projects on failure. Web modal + CLI
`project create --repo` both use the new bundled payload.
3. CLI `project resource add` now accepts a generic --ref '<json>' flag
so a new resource_type works without a CLI change. Per-type
shortcuts (--url for github_repo) remain as a convenience but are no
longer the only way in. Docs updated to drop the CLI from the
"files you must touch" list.
Adds two new server handler tests:
- TestCreateProjectAttachesResources (resources[] happy path)
- TestCreateProjectRollsBackOnInvalidResource (transactional rollback)
Co-authored-by: multica-agent <github@multica.ai>
---------
Co-authored-by: multica-agent <github@multica.ai>
|
||
|
|
a28312c0b4 |
feat(markdown): render mermaid diagrams (#1888)
* feat(markdown): render mermaid diagrams * fix(markdown): harden mermaid diagram rendering * fix(markdown): address mermaid review feedback * fix(markdown): strengthen mermaid theme handling * fix(markdown): rasterize mermaid theme colors |
||
|
|
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. |
||
|
|
d68f1f4bf1 |
fix(issues): wrap Details and Token usage sections in grid (#1921)
PropRow switched to CSS subgrid in #1919, which requires its parent to declare grid columns. The Properties section's wrapper was updated, but Details and Token usage in the same file were missed — their PropRows collapsed to a single column, stacking label and value vertically. Add the same `grid grid-cols-[auto_1fr] gap-x-2 gap-y-0.5` wrapper used by Properties so all three sections render consistently. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
281779330e |
feat(chat): no-agent disabled state with onboarding fix and editor cleanup (#1919)
* fix(onboarding): refresh agent cache after import and agent creation Two paths could leave the workspace agent-list query cache stale by the time the dashboard rendered the welcome issue, causing the issue's agent assignee to resolve to "Unknown Agent": 1. StarterContentPrompt.onImport invalidated pins/projects/issues but not agents, and didn't await any of them before navigating — so the issue-detail page could mount and read the cache before TanStack Query had marked the relevant queries stale. 2. OnboardingFlow.handleAgentCreated created the agent without invalidating the agent list, so the dashboard's first mount would read whatever was already cached from earlier in onboarding. Both now invalidate workspaceKeys.agents, and the import flow awaits all invalidations via Promise.all before pushing the navigation, so the next page mount always refetches. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(editor): drop editable prop, ContentEditor is editing-only ContentEditor's `editable` prop had zero true callsites left in the codebase — every read-only surface had migrated to ReadonlyContent (react-markdown), and the prop only invited misuse: Tiptap's `useEditor` reads `editable` at mount, so callers that toggled it post-mount (like a chat input that needs to disable on no-agent) silently got stuck in whichever mode the editor first created. Changes: - Remove `editable` prop and default; useEditor and createEditorExtensions no longer take it. - Remove the `"readonly"` className branch and the readonly content sync useEffect (only the editing path remains). - Remove the BubbleMenu and mouseDown editable guards. - Drop LinkReadonly; rename LinkEditable to LinkExtension and use it unconditionally. - Update the docstring to point readers at ReadonlyContent for display surfaces. ReadonlyContent's `.readonly` CSS class stays in content-editor.css — that file's selectors are still used by react-markdown's wrapper. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(chat): empty-state by session history, no-agent disabled state Three independent improvements to the chat window's pre-conversation states, sharing a new three-state availability primitive: 1. New `useWorkspaceAgentAvailability()` hook (`"loading" | "none" | "available"`) so callers don't have to reinvent the loading-vs-empty distinction. Treating loading as "no agent" — the easy mistake — caused the chat input to flash a fake disabled state for the few hundred ms after mount, even when the workspace had agents. 2. EmptyState now branches on session history, not agent presence: never-chatted users get a short pitch ("They know your workspace — issues, projects, skills"), returning users get the existing starter prompts. Missing-agent feedback moved to the banner above the input, keeping this surface focused on "what is chat for". 3. No-agent disabled state: when availability resolves to "none", ChatInput dims and stops responding to clicks/keys, with cursor `not-allowed` on hover. The disable lives at the wrapper level (`pointer-events-none` on the inner card, `cursor-not-allowed` on the outer one — splitting layers so hover bubbles to where the browser reads cursor) — we no longer reach into the editor's editable mode, which never switched cleanly post-mount anyway. A `<NoAgentBanner>` (sibling of OfflineBanner, mutually exclusive) states the prerequisite without linking out — no one should be pulled out of chat mid-thought to a settings page. Also: default chat width 420 → 380, since the chat docks at the bottom-right and 420 was crowding everything else. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(views): align PropRow labels using CSS subgrid The fixed `w-16` (64px) label column on PropRow broke whenever a label rendered wider than 64px (e.g. "Concurrency" in the agent inspector) — the label would overflow into the gap and collide with the value. Switch to subgrid: the parent declares `grid grid-cols-[auto_1fr]` and each PropRow becomes `col-span-2 grid grid-cols-subgrid`. The `auto` track sizes to the widest label across all rows in that parent, so labels always fit and value columns stay aligned across rows without picking a magic pixel width. Updated parents: - agent-detail-inspector Section wrapper - issue-detail Properties group Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
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>
|
||
|
|
e6e9c64484 |
refactor(chat): simplify task-status-pill (#1914)
Three signal axes (color / label tiers / per-tool spinner) collapsed into one (label only): - Drop 60s amber warning color and 300s cancel-button threshold. The cancel button duplicated ChatInput's Stop button (both call the same handleStop) — single entry point is enough; users can judge from the elapsed seconds whether to stop. - Drop tiered thinking labels (Thinking / Reasoning / Working through it / Taking a closer look) — collapse to a single "Thinking". - Unify all spinners to `breathe` (was: helix / scan / cascade / orbit / breathe / pulse / braille mix). Tool-specific spinner choices were cosmetic noise; one consistent spinner reads cleaner. - Remove `onCancel` prop chain through ChatMessageList → TaskStatusPill. Net: 209 → 152 lines in task-status-pill.tsx; no API/contract changes beyond removing a now-unused prop. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
c6a26facd3 | fix(inbox): jump instantly to targeted comments (#1887) |