mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-05 13:29:44 +02:00
* feat(storage): add GetReader to Storage interface Adds a streaming read method to the Storage abstraction so callers can pull object bytes without forcing a full in-memory load. S3Storage wraps GetObject; LocalStorage opens the file with path-traversal and sidecar guards. Tests cover happy path, traversal rejection, sidecar rejection, and missing key. Used in the next commit by the attachment-preview proxy endpoint. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(server): add attachment preview proxy endpoint GET /api/attachments/{id}/content streams the raw bytes of a text-previewable attachment back to the client. Exists to (a) bypass CloudFront CORS, which is not configured on the CDN, and (b) bypass Content-Disposition: attachment which Chromium honors for iframe document loads. Media types (image/video/audio/pdf) intentionally do NOT go through this endpoint — clients render them directly from the signed CloudFront download_url, which is already served with Content-Disposition: inline. Hard cap: 2 MB. Larger files return 413. Anything outside the text whitelist returns 415. The whitelist (isTextPreviewable) mirrors the client-side dispatcher; the cross-reference comment in file.go flags the manual sync until a JSON SSOT generator lands. Response always uses Content-Type: text/plain; charset=utf-8 so a hostile HTML payload can't be re-interpreted as a document. The original MIME ships via X-Original-Content-Type for client dispatch. Cache-Control: no-store so revoked attachment access takes effect immediately on the next request. Tests cover happy path (md), extension fallback when content_type is generic, 415 (pdf), 413 (>2MB), foreign workspace (404 isolation), and the isTextPreviewable table. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(core/api): add getAttachmentTextContent + preview error types Adds an ApiClient method that fetches the text body of an attachment via the new /api/attachments/{id}/content proxy. Two typed errors — PreviewTooLargeError (413) and PreviewUnsupportedError (415) — let the preview modal render specific fallbacks instead of a generic failure. Refactors the private fetch() into a shared fetchRaw() helper so the new method inherits the standard infra: auth headers, 401 → handleUnauthorized recovery, X-Request-ID, error logging, and the ApiError contract. The previous draft bypassed all of these by calling window.fetch directly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(views/editor): add AttachmentPreviewModal + Eye entry points In-app preview for non-image attachments. An Eye icon now sits next to the existing Download button on file cards / readonly file cards / the standalone AttachmentList. Clicking it opens a full-screen modal that dispatches by content_type: pdf: <iframe src={download_url}> — Chromium PDFium video/*: <video controls src={download_url}> — native controls audio/*: <audio controls src={download_url}> — native controls md: <ReadonlyContent> — full markdown pipeline html: <iframe srcdoc sandbox=""> — fully restricted text: <code class="hljs"> — lowlight highlight Media types render directly from the signed CloudFront download_url (server marks them inline-disposition). Text types fetch through the new /api/attachments/{id}/content proxy via TanStack Query, wrapped in useAttachmentPreview() so each entry point owns its own modal state without depending on a global Provider mount. Modal sizing: max-w-6xl × min(90vh, 100vh - 2rem) — slightly larger than create-issue's max-w-4xl since PDF / video need room, but capped to viewport on small screens. Sub-renderers use h-full to follow the fixed modal height instead of viewport-relative units. Images are intentionally NOT touched — the existing ImageLightbox (extensions/image-view.tsx) already handles them correctly. The new modal would be churn without user-visible benefit. Adds i18n keys under attachment.* (en + zh-Hans) and registers Preview/Download/Upload in the conventions glossary so future translations stay consistent. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(desktop): enable Chromium PDF viewer for attachment preview Adds webPreferences.plugins: true to the main BrowserWindow so the bundled Chromium PDFium plugin activates inside iframes — required for the attachment preview modal's PDF dispatch. Default is false in Electron; without it <iframe src=*.pdf> renders blank. Security trade-off, accepted intentionally and documented inline: 1. This window already runs with webSecurity: false + sandbox: false, so plugins: true does NOT meaningfully widen the renderer's attack surface beyond what is already accepted. 2. The only PDFs that reach an iframe here are signed CloudFront URLs we ourselves issued; user-supplied URLs are routed through setWindowOpenHandler → openExternalSafely and cannot land in this renderer. 3. Chromium's PDFium plugin is itself sandboxed and only handles application/pdf — no Flash/Java/other historical plugin surfaces. If we ever tighten webSecurity / sandbox, the follow-up is to host the PDF viewer in a dedicated BrowserView with plugins scoped to that view, keeping the main renderer plugin-free. Old desktop builds ship without the preview modal, so the Eye button never appears and PDF preview is gated by the same release — zero regression risk for users on stale clients. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
303 lines
12 KiB
Plaintext
303 lines
12 KiB
Plaintext
---
|
||
title: Conventions
|
||
description: Single source of truth for code naming, i18n translation glossary, and Chinese voice guide.
|
||
---
|
||
|
||
This page is the single source of truth for code naming, the i18n translation glossary, and the Chinese voice guide. Anything that used to live in `packages/views/locales/glossary.md` or in scattered comments now lives here.
|
||
|
||
If you write Multica code, change a translation, or write Chinese product copy, this is the page to reference.
|
||
|
||
---
|
||
|
||
## 1. Code naming
|
||
|
||
### Routes
|
||
|
||
Pre-workspace routes (the routes that exist before the user is in a workspace) MUST use either a single word or the `/{noun}/{verb}` pattern.
|
||
|
||
- ✅ `/login`, `/inbox`, `/workspaces/new`
|
||
- ❌ `/new-workspace`, `/create-team`, `/accept-invite`
|
||
|
||
Hyphenated word groups at the root collide with user-chosen workspace slugs and force endless reserved-slug audits. Reserving the noun (`workspaces`) automatically protects the entire `/workspaces/*` subtree.
|
||
|
||
### Workspace-scoped routes
|
||
|
||
Always live under `/{slug}/{section}` — `/{slug}/issues`, `/{slug}/agents`, `/{slug}/settings`. Never duplicate workspace routing logic; use `useNavigation().push()` from shared code, never framework-specific link APIs.
|
||
|
||
### Packages and modules
|
||
|
||
The monorepo enforces strict package boundaries:
|
||
|
||
| Package | May depend on | Must NOT depend on |
|
||
| --- | --- | --- |
|
||
| `packages/core` | nothing app-specific | `react-dom`, `localStorage`, `process.env`, `next/*`, UI libraries |
|
||
| `packages/ui` | nothing | `@multica/core`, business logic |
|
||
| `packages/views` | `core/`, `ui/` | `next/*`, `react-router-dom`, stores |
|
||
| `apps/web/platform/` | `next/*` | other apps |
|
||
| `apps/desktop/.../platform/` | `react-router-dom`, electron | other apps |
|
||
|
||
If logic appears in both apps, it MUST be extracted to a shared package. There are no exceptions for "small" duplication.
|
||
|
||
### Files and components
|
||
|
||
- Files: `kebab-case.tsx` / `kebab-case.ts` (e.g. `agent-row-actions.tsx`)
|
||
- Components: `PascalCase` (e.g. `AgentRowActions`)
|
||
- Hooks: `useCamelCase` (e.g. `useWorkspaceId`)
|
||
- Tests: colocated as `<file>.test.ts(x)`
|
||
- Stores (Zustand): `<feature>-store.ts`, exported as `use<Feature>Store`
|
||
|
||
### Database (Go + sqlc)
|
||
|
||
- Tables: `snake_case` singular (`user`, `workspace`, `agent_runtime`)
|
||
- Columns: `snake_case` (`workspace_id`, `created_at`, `last_seen_at`)
|
||
- Foreign keys: `<table>_id`
|
||
- Booleans: `is_<state>` or `<state>_at` (timestamp form preferred for state changes)
|
||
- Migration files: `NNN_descriptive_name.up.sql` + `.down.sql` — always provide both directions
|
||
|
||
### Go
|
||
|
||
- Standard `gofmt` + `go vet`. No exceptions.
|
||
- Handler files mirror domain: `agent.go`, `auth.go`, `runtime.go`
|
||
- Tests: `<file>_test.go` colocated
|
||
- For UUID parsing in handlers, follow the rule in the root `CLAUDE.md` — `parseUUIDOrBadRequest` for boundary input, `parseUUID` (panicking) for trusted round-trips, never `util.ParseUUID` directly without checking the error.
|
||
|
||
### TypeScript
|
||
|
||
- API responses on the wire are `snake_case`; the api client converts to `camelCase` at the boundary. Inside TS code, **always camelCase**.
|
||
- Types: `PascalCase` (`Issue`, `AgentRuntime`); never `IPrefix`, never `_t` suffix.
|
||
- Enums: prefer string literal unions; reserve `enum` for runtime-iterable cases.
|
||
- TanStack Query keys: factory functions in `<feature>/queries.ts`, e.g. `issueKeys.detail(id)`.
|
||
|
||
### Issue keys
|
||
|
||
Every issue has a human-readable key like `MUL-123`: workspace `issue_prefix` (3 letters, uppercase) + sequence number. The prefix is set at workspace creation and is never changed afterward.
|
||
|
||
### Comments in code
|
||
|
||
English only. The repo enforces this for both Go and TypeScript. If you find a Chinese comment in code, it's a bug — replace it.
|
||
|
||
### Commit messages
|
||
|
||
Conventional format: `feat(scope)`, `fix(scope)`, `refactor(scope)`, `docs`, `test(scope)`, `chore(scope)`. Atomic commits grouped by intent.
|
||
|
||
---
|
||
|
||
## 2. i18n translation glossary
|
||
|
||
This is the **mandatory** glossary for every translation PR. It used to live at `packages/views/locales/glossary.md`; that file is now a stub pointing here.
|
||
|
||
### The core distinction: entity vs concept
|
||
|
||
Multica's product nouns split into two categories:
|
||
|
||
- **Entity** — has a URL, a database row, an API type. In Chinese text, render as **lowercase English** so it visually reads like a type name and signals "this is a Multica system entity".
|
||
- **Concept** — generic noun, not a database entity. **Translate fully** so Chinese users don't see jagged English embedded in flowing text.
|
||
|
||
This rule is aligned with `apps/docs/content/docs/*.zh.mdx` — the docs are the de facto Chinese voice standard and have been battle-tested across 20+ pages.
|
||
|
||
### Entities — mixed rule (`issue` / `skill` / `task`)
|
||
|
||
`issue` / `skill` / `task` are Multica's core entities. They have schema columns, API fields, and product UI labels that are all English. In Chinese text, they follow a **mixed rule** — what to use depends on where the word appears:
|
||
|
||
| Context | Render | Example |
|
||
| --- | --- | --- |
|
||
| **UI strings, state names, code references** | lowercase English | "排队中的 task"、"创建子 issue"、"为智能体注入 skill" |
|
||
| **Doc titles / section headings** | Title-case English **or** the Chinese term | "Issue 与 project"、"Skills"、"执行任务" |
|
||
| **Long-form doc prose, when the entity is the running subject** | Chinese term, with English in parentheses on first mention | "**执行任务**(task)是智能体每一次工作的单位" |
|
||
| **API / DB fields** | always `task` / `issue` / `skill` | `task_id`, `issue_status`, `skill_uuid` |
|
||
|
||
Chinese term reference:
|
||
|
||
- `task` ↔ `执行任务` (or shortened to `任务` once context is clear)
|
||
- `issue` has no settled Chinese translation — leave English; titles may capitalize as `Issue`
|
||
- `skill` has no settled Chinese translation — leave English; titles may capitalize as `Skills`
|
||
|
||
**Why `issue` / `skill` / `task` aren't forced into Chinese the way `project` / `autopilot` are**:
|
||
|
||
- **`issue` / `task`**: dev teams talk in English. The Chinese candidates ("任务" — too vague, almost synonymous with "工作"; "工单" — IT ticket connotation; "议题" — GitHub-style but doesn't match the product feel) all read worse than `issue`. **But** in long-form doc prose, repeating lowercase `task` 50× breaks the rhythm — so prose is allowed to use `执行任务`, while UI strings and state names stay lowercase English.
|
||
- **`skill`**: Multica-specific concept with no established Chinese term.
|
||
- **`project` → "项目"**: settled mainstream Chinese word. Feishu / Tower / Teambition / PingCode / GitHub Projects — every Chinese product translates it. No product keeps `project` in Chinese context.
|
||
- **`autopilot` → "自动化"**: in Chinese, "autopilot" associates with Tesla's "自动驾驶" and doesn't match what the feature does (run tasks on a schedule). Notion and Feishu both use "自动化"; that's the industry consensus.
|
||
|
||
### Don't translate — brands and acronyms
|
||
|
||
| Category | Terms |
|
||
| --- | --- |
|
||
| Brands | **Multica**, GitHub, Slack, Google, Anthropic, OpenAI, Claude, Codex, Cursor, Linear, Jira |
|
||
| Acronyms | API, CLI, URL, SDK, OAuth, JWT, SSO, WebSocket, HTTP, JSON, YAML, SQL |
|
||
|
||
### Translate fully — concepts
|
||
|
||
| English | Chinese |
|
||
| --- | --- |
|
||
| Workspace | **工作区** |
|
||
| Agent | **智能体** |
|
||
| Project | **项目** |
|
||
| Autopilot | **自动化** |
|
||
| Daemon | **守护进程** |
|
||
| Runtime | **运行时** |
|
||
| Inbox | **收件箱** |
|
||
| Comment | **评论** |
|
||
| Reply | **回复** |
|
||
| Notifications | **通知** |
|
||
| Member | **成员** |
|
||
| Label | **标签** |
|
||
| Settings | **设置** |
|
||
| Onboarding | **上手引导** |
|
||
|
||
### Translate fully — generic UI words
|
||
|
||
| English | Chinese |
|
||
| --- | --- |
|
||
| Invite / Invitation | 邀请 |
|
||
| Search | 搜索 |
|
||
| Email | 邮箱 (label) / 邮件 (action) |
|
||
| Password | 密码 |
|
||
| Sign in / Log in | 登录 |
|
||
| Sign up | 注册 |
|
||
| Sign out / Log out | 退出登录 |
|
||
| Save / Cancel / Delete | 保存 / 取消 / 删除 |
|
||
| Confirm / Continue / Back | 确认 / 继续 / 返回 |
|
||
| Edit / New / Create / Add | 编辑 / 新建 / 创建 / 添加 |
|
||
| Remove / Send / Open / Close | 移除 / 发送 / 打开 / 关闭 |
|
||
| Preview / Download / Upload | 预览 / 下载 / 上传 |
|
||
| Done / Loading... | 完成 / 加载中... |
|
||
| Profile / Account / Appearance | 个人资料 / 账号 / 外观 |
|
||
| Theme / Language | 主题 / 语言 |
|
||
| Light / Dark / System | 浅色 / 深色 / 跟随系统 |
|
||
| Active / Archived | 活跃 (or 启用) / 已归档 |
|
||
| Status / Priority | 状态 / 优先级 |
|
||
| Assignee / Reporter | 负责人 / 报告人 |
|
||
| Description / Title | 描述 / 标题 |
|
||
| Date / Time | 日期 / 时间 |
|
||
| Today / Yesterday / Tomorrow | 今天 / 昨天 / 明天 |
|
||
| Empty / Failed / Success | 空 / 失败 / 成功 |
|
||
| Error / Warning | 错误 / 警告 |
|
||
|
||
### Roles and status enums (lowercase English, not translated)
|
||
|
||
These are schema-level identifiers; render as lowercase English even in Chinese context.
|
||
|
||
- Roles: `owner` / `admin` / `member`
|
||
- Issue status: `backlog` / `todo` / `in_progress` / `in_review` / `done` / `blocked` / `cancelled`
|
||
|
||
In UI, surface them in English (optionally `code-style` wrapped):
|
||
|
||
- "你需要 owner 权限"
|
||
- "已切换到 in_progress"
|
||
|
||
### Word combination rules
|
||
|
||
Always put **a single space** between an English word (entity / brand / acronym) and surrounding Chinese:
|
||
|
||
- "Create new issue" → "新建 issue"
|
||
- "Assign to agent" → "分配给智能体"
|
||
- "Configure runtime" → "配置运行时"
|
||
- "Stop daemon" → "停止守护进程"
|
||
|
||
### Plurals and counts
|
||
|
||
i18next uses `_one` / `_other`; Chinese has no grammatical number, only fill `_other`.
|
||
|
||
```json
|
||
// en/issues.json
|
||
{
|
||
"issue_count_one": "{{count}} issue",
|
||
"issue_count_other": "{{count}} issues"
|
||
}
|
||
|
||
// zh-Hans/issues.json
|
||
{
|
||
"issue_count_other": "{{count}} 个 issue"
|
||
}
|
||
```
|
||
|
||
Common count formats:
|
||
|
||
- `{{count}} issues` → `{{count}} 个 issue`
|
||
- `{{count}} agents` → `{{count}} 个智能体`
|
||
- `{{count}} workspaces` → `{{count}} 个工作区`
|
||
- `{{count}} comments` → `{{count}} 条评论`
|
||
- `{{count}} members` → `{{count}} 位成员`
|
||
- `{{count}} skills` → `{{count}} 个 skill`
|
||
|
||
### Interpolation
|
||
|
||
Use `{{var}}`. Chinese translations may reorder for natural sentence flow.
|
||
|
||
```json
|
||
// en
|
||
{ "welcome_message": "Welcome back, {{name}}!" }
|
||
|
||
// zh-Hans
|
||
{ "welcome_message": "欢迎回来,{{name}}!" }
|
||
```
|
||
|
||
### Translation key naming
|
||
|
||
Three-level nesting: `feature.component.action`.
|
||
|
||
```json
|
||
{
|
||
"feature_or_component": {
|
||
"subcomponent_or_section": {
|
||
"action_or_label": "..."
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
Examples:
|
||
|
||
- `issues.toolbar.batch_update_success`
|
||
- `issues.detail.comment_form.placeholder`
|
||
- `inbox.empty.title`
|
||
- `settings.preferences.language.title`
|
||
|
||
### Web-only / desktop-only copy
|
||
|
||
- Shared copy: top level of the namespace JSON
|
||
- Web-only: `web` section
|
||
- Desktop-only: `desktop` section
|
||
|
||
See `auth.json` for the canonical example (the `web` section contains `prefer_desktop` / `desktop_handoff.*`).
|
||
|
||
---
|
||
|
||
## 3. Chinese voice and style
|
||
|
||
### Punctuation
|
||
|
||
- Full-width punctuation in Chinese: `,。:;!?`
|
||
- Quotes: straight double quotes `"..."` to match the English source. Do not use `「」` or curly quotes.
|
||
- Ellipsis: three dots `...` not the single character `…`. Match the English source.
|
||
- Mixed Chinese-English: a single space on each side of the English word (see Word combination rules).
|
||
|
||
### Style principles
|
||
|
||
- **Concise and direct.** Avoid translation-ese: "对于 X 来说"、"作为 X"、"我们的"。
|
||
- **Error messages**: gentle but clear. "无法保存修改" beats "保存修改失败了!".
|
||
- **Buttons**: verb first, 2–4 characters. "取消"、"保存修改"、"立即同步".
|
||
- **Tooltips**: full short sentence. "复制链接到剪贴板".
|
||
- **Placeholders**: example-style. "输入 issue 标题...".
|
||
|
||
### Where to look when in doubt
|
||
|
||
When the glossary doesn't cover a term, look at:
|
||
|
||
1. `apps/docs/content/docs/*.zh.mdx` — the de facto Chinese voice standard, 20+ pages of consistent translation
|
||
2. `packages/views/locales/zh-Hans/auth.json` and `editor.json` — JSON structure + selector API patterns
|
||
3. `packages/views/auth/login-page.tsx` — component-level selector API call site
|
||
4. `packages/views/settings/components/preferences-tab.tsx` — language switcher reference
|
||
|
||
---
|
||
|
||
## Updating this page
|
||
|
||
If you change a rule here, also:
|
||
|
||
1. Apply it in the relevant locale JSONs / CLAUDE.md / docs page
|
||
2. Note the change in the PR description so reviewers know to look for downstream sweep
|
||
|
||
This page is the contract; nothing else overrides it.
|