mirror of
https://github.com/multica-ai/multica.git
synced 2026-06-17 11:48:42 +02:00
* chore(docs-site): add @multica/ui bridge and dev:docs script Link @multica/ui as a workspace dep of @multica/docs so the docs app can consume the shared design tokens (tokens.css, base.css) via a relative import — same pattern the web and desktop apps use. Add a top-level pnpm dev:docs script for a one-command docs dev server (port 4000). Preparation for the docs site rewrite tracked in docs/docs-outline.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(docs-site): apply Multica tokens and pure-sans typography Replace Fumadocs' neutral color preset with a @theme inline bridge that maps the --color-fd-* chrome tokens to Multica's --background / --foreground / --border / --sidebar-* etc. Sidebar, nav, cards now pick up Multica's cool-gray palette automatically, and switching Multica's .dark flips Fumadocs chrome with it. Typography: pure sans (36px / weight 600 / tight tracking h1, h2+h3 tuned to match), landing continuity without serif display. Code blocks: pinned to near-black (oklch(0.12 0.01 250)) regardless of page theme so they read as a continuation of the landing hero surface. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(plan): add rewrite plan and outline tracker Two planning documents for the docs site rewrite: - docs/docs-rewrite-plan.md — strategic rationale (positioning, reader personas, design principles, visual direction, phase breakdown). - docs/docs-outline.md — execution tracker. 25 v1 pages with per-page entries (source files, audience, what-to-write, what-not-to-write, ⚠️ verify-before-drafting). Workflow: claim via Owner + Status, read source, verify checklist, draft, review, ship. Language: zh only for v1. Outline is the source of truth for scope and status; the earlier "EN first, ZH as Phase 10" line in rewrite-plan.md is superseded. Welcome (§1.1) is claimed under this tracker and currently in 👀 review. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(docs-site): write first Welcome page (zh) — §1.1 Implements §1.1 Welcome per docs/docs-outline.md. Chinese-first (per outline language decision); terms translated to their clearest Chinese equivalents (issue → 任务, agent → 智能体, daemon → 守护进程, etc.), product proper nouns and commands kept in English. Voice: reference-style, not marketing. Follows google-gemini/docs-writer skill rules (BLUF opener, second-person, active voice, no hype, overview prose before every list). Content: - Opens by describing Multica as a 任务协作 platform and how humans + AI 智能体 share the same 工作区 - Two interaction modes: 分配任务 and 聊天 - 智能体在哪里运行: local daemon (today), cloud runtime (soon, waitlist). 10 providers listed from source (server/pkg/agent/*.go). - Three usage paths split into back-end (Cloud / Self-host) and client (Desktop) choices — Desktop bundles CLI and auto-starts daemon. - Status: 👀 In review. Also simplifies content/docs/meta.json to just ["index"] (placeholder page entries removed; IA skeleton will be populated in Phase 2). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(docs-site): wire up client-side Mermaid rendering Add a <Mermaid> React component under apps/docs/components/ that dynamic- imports the mermaid package in useEffect and renders the resulting SVG. Deps added: mermaid@^11.14.0 and next-themes@^0.4.6 (transitively present via fumadocs-ui but needs explicit declaration to be importable). Design choices: - Client-side render (not build-time). No Playwright / browser automation in CI. Mermaid bundle (~400 KB) is loaded only on pages that use the component, thanks to the dynamic import. - Theme flips automatically — useTheme() from next-themes re-invokes mermaid.initialize() with the correct theme on .dark toggle. - SSR safe: the component returns a "Rendering diagram…" placeholder on the server; the SVG appears after hydration. - securityLevel "strict" — diagrams render as static SVG with no inline script or event handlers. Usage in mdx (explicit import, same pattern as Cards/Callout): import { Mermaid } from "@/components/mermaid"; <Mermaid chart={` graph LR User --> Server `} /> Verified by a scratch /app/mermaid-test/ route that compiled to 4665 modules and returned HTTP 200 (cleanup done pre-commit). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(docs-site): adopt v2 editorial palette and typography Replace the Linear/Vercel-style cool-gray token override with a warm editorial palette (bg matches landing #f7f7f5, brand-color primary via Multica's existing --brand hue 255) and wire Source Serif 4 for heading typography. Italic is avoided sitewide — Chinese italic renders as a synthetic slant against upright-designed glyphs and reads as broken; emphasis is carried by serif/sans contrast, brand color, and weight. Sidebar adopts the product app's active-fill pattern (solid sidebar-accent background, no ::before mark). Code blocks drop the always-dark hero treatment and follow page theme so the reading column stays coherent. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(docs-site): add editorial MDX components New components/editorial.tsx exposes Byline, NumberedCards/NumberedCard, and NumberedSteps/Step — the "wow moment" pieces from v2-editorial (ruled-divider bylines, No. 01 serif card numbering, large serif step counters). All escape prose via not-prose so they run their own type scale. DocsHero is rewritten as an editorial showpiece: title accepts ReactNode so callers can pass a brand-color em accent, eyebrow becomes a small uppercase sans label, lede uses serif at 20px. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(docs-site): rewrite welcome page as editorial showpiece Welcome page now opens with an editorial hero (eyebrow + serif h1 with brand-color em accent on "共处一方。" + serif lede), a ruled byline strip carrying the section / updated / read-time metadata, and then flows into prose. The three deployment paths switch from fumadocs's <Cards> to <NumberedCards> so each gets a No. 01/02/03 label, and the "next steps" list becomes a <NumberedSteps> block with large serif counters. These are the highest-impact visual moments on the page; the rest of the guide pages still get the global editorial chrome without needing per-page code. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(docs-site): add bilingual flat content tree with i18n routing Restructures the docs site from nested topic folders (cli/, getting-started/, developers/, guides/) into a flat content tree, and adds Chinese alongside English. The old nested structure forced contributors to think about both the topic AND the user-journey grouping; the flat tree lets a single meta.json control reading order with separator labels, and lets the same slug serve both languages via the `foo.zh.mdx` parser convention. Routing - New `app/[lang]/` segment hosts layout, home, slug page, and not-found - Self-contained basePath-aware middleware (fumadocs's built-in middleware isn't basePath-aware, so its rewrite/redirect targets break under /docs) - `hideLocale: 'default-locale'` keeps English URLs prefix-less; Chinese lives under /docs/zh/ - Sitemap excluded from middleware matcher so crawlers don't get rewritten into a non-existent locale-prefixed sitemap route - Default-language redirect preserves search string (UTM safety) - Home page declares its own generateStaticParams (Next layout params don't cascade) so /docs/ and /docs/zh are SSG, not dynamic per request SEO - New app/sitemap.ts emits hreflang alternates for every page - absoluteDocsUrl normalizes the home `/` so canonical URLs don't carry a trailing slash that mismatches the page's own canonical link - apps/web/app/robots.ts now advertises the docs sitemap Search - CJK tokenizer registered for the zh locale (Orama's English regex strips Han characters; without this Chinese search either returns empty or throws) Chrome - Custom DocsSettings replaces fumadocs's default icon-only sidebar footer with two labelled buttons (language + theme), matching the editorial design language Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
157 lines
5.4 KiB
TypeScript
157 lines
5.4 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useId, useState } from "react";
|
|
import { useTheme } from "next-themes";
|
|
|
|
/**
|
|
* Client-side Mermaid diagram renderer.
|
|
*
|
|
* Dynamic-imports the mermaid package so it's only loaded on pages that
|
|
* actually use it (~400 KB). Re-renders when the page theme flips.
|
|
*
|
|
* Themed to pick up Multica design tokens at runtime via getComputedStyle,
|
|
* so the diagram tracks both light / dark mode and any future token changes
|
|
* without a rebuild.
|
|
*/
|
|
export function Mermaid({ chart }: { chart: string }) {
|
|
const reactId = useId();
|
|
const { resolvedTheme } = useTheme();
|
|
const [svg, setSvg] = useState<string | null>(null);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
|
|
void import("mermaid").then(({ default: mermaid }) => {
|
|
const css = getComputedStyle(document.documentElement);
|
|
// Mermaid's khroma parser only understands legacy color syntax (hex /
|
|
// rgb / hsl / named). Our tokens are authored in oklch(), which
|
|
// getComputedStyle preserves verbatim, and a `color-mix(in srgb, ...)`
|
|
// round-trip still serializes as `color(srgb r g b)` per CSS Color 4.
|
|
// Rasterize each token through a 1x1 canvas: fillStyle accepts any CSS
|
|
// <color>, getImageData returns concrete 8-bit sRGB bytes regardless
|
|
// of the input's color space.
|
|
const canvas = document.createElement("canvas");
|
|
canvas.width = 1;
|
|
canvas.height = 1;
|
|
const ctx = canvas.getContext("2d", { willReadFrequently: true });
|
|
|
|
const v = (name: string, fallback: string) => {
|
|
const raw = css.getPropertyValue(name).trim();
|
|
if (!raw || !ctx) return fallback;
|
|
// fillStyle silently ignores unparseable input; prime with a known
|
|
// baseline so a parse failure paints black, not whatever was last set.
|
|
ctx.fillStyle = "#000";
|
|
ctx.fillStyle = raw;
|
|
ctx.fillRect(0, 0, 1, 1);
|
|
const [r, g, b] = ctx.getImageData(0, 0, 1, 1).data;
|
|
return `rgb(${r}, ${g}, ${b})`;
|
|
};
|
|
|
|
const brand = v("--brand", "#3b82f6");
|
|
const brandFg = v("--brand-foreground", "#ffffff");
|
|
const background = v("--background", "#ffffff");
|
|
const foreground = v("--foreground", "#111111");
|
|
const muted = v("--muted", "#f5f5f5");
|
|
const mutedFg = v("--muted-foreground", "#6b7280");
|
|
const border = v("--border", "#e5e5e5");
|
|
const accent = v("--accent", muted);
|
|
|
|
mermaid.initialize({
|
|
startOnLoad: false,
|
|
theme: "base",
|
|
securityLevel: "strict",
|
|
fontFamily: "inherit",
|
|
themeVariables: {
|
|
// Canvas
|
|
background,
|
|
mainBkg: background,
|
|
// Nodes — soft muted fill with full-contrast text and a subtle border
|
|
primaryColor: muted,
|
|
primaryTextColor: foreground,
|
|
primaryBorderColor: border,
|
|
secondaryColor: accent,
|
|
secondaryTextColor: foreground,
|
|
secondaryBorderColor: border,
|
|
tertiaryColor: background,
|
|
tertiaryTextColor: foreground,
|
|
tertiaryBorderColor: border,
|
|
// Edges + labels
|
|
lineColor: mutedFg,
|
|
textColor: foreground,
|
|
edgeLabelBackground: background,
|
|
labelBackground: background,
|
|
// Clusters (subgraph boxes)
|
|
clusterBkg: accent,
|
|
clusterBorder: border,
|
|
titleColor: foreground,
|
|
// Notes / callouts
|
|
noteBkgColor: muted,
|
|
noteTextColor: foreground,
|
|
noteBorderColor: border,
|
|
// Brand accent — used for active / start states in state diagrams,
|
|
// user-decision diamonds in flowcharts, etc.
|
|
activeTaskBkgColor: brand,
|
|
activeTaskBorderColor: brand,
|
|
altBackground: muted,
|
|
// Sequence / git diagrams (harmless if unused)
|
|
actorBkg: muted,
|
|
actorBorder: border,
|
|
actorTextColor: foreground,
|
|
actorLineColor: mutedFg,
|
|
signalColor: foreground,
|
|
signalTextColor: foreground,
|
|
// Fine print
|
|
errorBkgColor: muted,
|
|
errorTextColor: foreground,
|
|
},
|
|
});
|
|
|
|
// mermaid requires a DOM-valid id; useId returns ":r0:" which isn't.
|
|
const domId = `mermaid-${reactId.replace(/:/g, "")}`;
|
|
|
|
mermaid
|
|
.render(domId, chart.trim())
|
|
.then((result) => {
|
|
if (!cancelled) {
|
|
setSvg(result.svg);
|
|
setError(null);
|
|
}
|
|
})
|
|
.catch((err: unknown) => {
|
|
if (!cancelled) {
|
|
setError(err instanceof Error ? err.message : String(err));
|
|
setSvg(null);
|
|
}
|
|
});
|
|
});
|
|
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, [chart, reactId, resolvedTheme]);
|
|
|
|
if (error) {
|
|
return (
|
|
<pre className="my-4 rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive">
|
|
Mermaid error: {error}
|
|
</pre>
|
|
);
|
|
}
|
|
|
|
if (!svg) {
|
|
return (
|
|
<div className="my-4 text-sm text-muted-foreground">
|
|
Rendering diagram…
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div
|
|
className="my-6 flex justify-center overflow-x-auto rounded-md border border-border/60 bg-muted/20 p-6 [&_.label_foreignObject>div]:!font-[inherit] [&_.nodeLabel]:!font-[inherit] [&_.edgeLabel]:!font-[inherit] [&_text]:!font-[inherit]"
|
|
dangerouslySetInnerHTML={{ __html: svg }}
|
|
/>
|
|
);
|
|
}
|