jamdesk 1.1.221 → 1.1.222

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/__tests__/unit/custom-assets.test.js +157 -6
  2. package/dist/__tests__/unit/custom-assets.test.js.map +1 -1
  3. package/dist/commands/dev.d.ts.map +1 -1
  4. package/dist/commands/dev.js +26 -8
  5. package/dist/commands/dev.js.map +1 -1
  6. package/dist/lib/custom-assets.d.ts +17 -0
  7. package/dist/lib/custom-assets.d.ts.map +1 -1
  8. package/dist/lib/custom-assets.js +109 -1
  9. package/dist/lib/custom-assets.js.map +1 -1
  10. package/package.json +1 -1
  11. package/vendored/app/api/chat/[project]/route.ts +31 -7
  12. package/vendored/components/chat/ChatBrandIcon.tsx +17 -0
  13. package/vendored/components/chat/ChatEmptyState.tsx +5 -5
  14. package/vendored/components/chat/ChatPanel.tsx +10 -7
  15. package/vendored/components/chat/useChatLabel.ts +9 -0
  16. package/vendored/components/layout/LayoutWrapper.tsx +3 -0
  17. package/vendored/components/mdx/Mermaid.tsx +16 -3
  18. package/vendored/components/mdx/MermaidInner.tsx +272 -19
  19. package/vendored/components/mdx/MermaidZoomModal.tsx +87 -0
  20. package/vendored/components/mdx/ZoomableImage.tsx +8 -1
  21. package/vendored/components/navigation/Header.tsx +15 -10
  22. package/vendored/components/navigation/Sidebar.tsx +6 -3
  23. package/vendored/components/navigation/TabsNav.tsx +9 -2
  24. package/vendored/components/search/SearchModal.tsx +6 -3
  25. package/vendored/hooks/useChat.ts +2 -0
  26. package/vendored/hooks/useChatPanel.tsx +9 -2
  27. package/vendored/lib/anthropic-client.ts +7 -0
  28. package/vendored/lib/build/error-parser.ts +17 -0
  29. package/vendored/lib/build/extract-tarball.ts +1 -29
  30. package/vendored/lib/build/validate-no-symlink-escape.ts +100 -0
  31. package/vendored/lib/chat-branding.ts +26 -0
  32. package/vendored/lib/chat-prompt.ts +2 -2
  33. package/vendored/lib/docs-types.ts +20 -0
  34. package/vendored/lib/generate-starter-questions.ts +24 -6
  35. package/vendored/lib/github-git-auth.ts +47 -0
  36. package/vendored/lib/hedge-strip.ts +1 -1
  37. package/vendored/lib/isr-build-executor.ts +63 -12
  38. package/vendored/lib/layout-helpers.tsx +13 -6
  39. package/vendored/lib/link-rewriter.ts +1 -1
  40. package/vendored/lib/mcp-auth.ts +67 -0
  41. package/vendored/lib/openapi/cache.ts +5 -2
  42. package/vendored/lib/openapi/expand-spec-pages.ts +5 -3
  43. package/vendored/lib/openapi/safe-ref-resolvers.ts +455 -0
  44. package/vendored/lib/openapi/validator.ts +46 -9
  45. package/vendored/lib/openapi-isr.ts +24 -11
  46. package/vendored/lib/page-timestamps.ts +29 -6
  47. package/vendored/lib/query-rewriter.ts +7 -3
  48. package/vendored/lib/r2-manifest.ts +2 -0
  49. package/vendored/lib/runtime-flags.ts +54 -0
  50. package/vendored/schema/docs-schema.json +34 -0
  51. package/vendored/scripts/copy-files.cjs +90 -37
  52. package/vendored/scripts/openapi-safe-ref-resolvers.cjs +354 -0
  53. package/vendored/scripts/openapi-spec-loader.cjs +10 -5
  54. package/vendored/themes/base.css +48 -0
  55. package/vendored/workspace-package-lock.json +18 -18
  56. package/vendored/components/chat/ChatFAB.tsx +0 -36
@@ -1,5 +1,6 @@
1
1
  import fs from 'fs-extra';
2
2
  import path from 'path';
3
+ import { output } from './output.js';
3
4
  /**
4
5
  * Whether an fs.watch event on the project root warrants a custom-CSS re-sync.
5
6
  * Matches any .css file (covers style.css plus atomic-save temp .css names) and
@@ -13,6 +14,89 @@ export function shouldResyncCss(filename) {
13
14
  return true;
14
15
  return filename.endsWith('.css');
15
16
  }
17
+ /** Re-sync trigger for the watcher: any .css or .js change (or an unknown filename). */
18
+ export function shouldResyncAssets(filename) {
19
+ if (!filename)
20
+ return true;
21
+ return filename.endsWith('.css') || filename.endsWith('.js');
22
+ }
23
+ /**
24
+ * Whether `target` (a real path) is strictly inside `dir` (a real path).
25
+ * Callers must pass realpaths so a symlink cannot hop out of the project.
26
+ */
27
+ function isRealPathInside(dir, target) {
28
+ const rel = path.relative(dir, target);
29
+ return rel !== '' && rel !== '..' && !rel.startsWith(`..${path.sep}`) && !path.isAbsolute(rel);
30
+ }
31
+ /**
32
+ * Whether `target` (a real path inside `dir`) has a `.git` segment. When the
33
+ * project is the repo root, .git is inside it and its files (config, logs,
34
+ * FETCH_HEAD) can hold credentials, so custom JS never reads from there.
35
+ * Case-insensitive because macOS opens `.GIT/config` as `.git/config`.
36
+ */
37
+ function isRealPathInGitDir(dir, target) {
38
+ return path.relative(dir, target).split(path.sep).some((seg) => seg.toLowerCase() === '.git');
39
+ }
40
+ /**
41
+ * Assemble custom JS the way copy-files.cjs does for the production build:
42
+ * the docs.json `styling.js` list in the given order (leading `/` stripped),
43
+ * else every root-level .js file (or symlink), sorted. Files are joined with a
44
+ * newline; missing files are skipped. Returns null when nothing was read.
45
+ *
46
+ * docs.json and the repo are untrusted, so a file is read only when its real
47
+ * path is inside the project's real path: a `/../` entry, a symlink out of
48
+ * the repo, or anything under .git is skipped with a warning. Symlinks that
49
+ * stay inside the project are allowed, matching the build-side assemblers.
50
+ */
51
+ export async function collectCustomJs(projectDir, stylingJs) {
52
+ let files;
53
+ if (stylingJs) {
54
+ files = Array.isArray(stylingJs) ? stylingJs : [stylingJs];
55
+ }
56
+ else {
57
+ try {
58
+ const entries = await fs.readdir(projectDir, { withFileTypes: true });
59
+ files = entries
60
+ .filter((d) => (d.isFile() || d.isSymbolicLink()) && d.name.endsWith('.js'))
61
+ .map((d) => d.name)
62
+ .sort();
63
+ }
64
+ catch {
65
+ return null;
66
+ }
67
+ }
68
+ if (files.length === 0)
69
+ return null;
70
+ let realProjectDir;
71
+ try {
72
+ realProjectDir = await fs.realpath(projectDir);
73
+ }
74
+ catch {
75
+ return null;
76
+ }
77
+ const parts = [];
78
+ for (const file of files) {
79
+ try {
80
+ const realPath = await fs.realpath(path.join(projectDir, file.replace(/^\//, '')));
81
+ if (!isRealPathInside(realProjectDir, realPath)) {
82
+ output.warn(`Skipping custom JS outside the project: ${file}`);
83
+ continue;
84
+ }
85
+ if (isRealPathInGitDir(realProjectDir, realPath)) {
86
+ output.warn(`Skipping custom JS inside .git: ${file}`);
87
+ continue;
88
+ }
89
+ parts.push(await fs.readFile(realPath, 'utf-8'));
90
+ }
91
+ catch (err) {
92
+ // Missing (or vanished mid-atomic-save) files are skipped, like copy-files.cjs.
93
+ const code = err.code;
94
+ if (code !== 'ENOENT' && code !== 'EISDIR')
95
+ throw err;
96
+ }
97
+ }
98
+ return parts.length > 0 ? parts.join('\n') : null;
99
+ }
16
100
  /**
17
101
  * Collect every root-level .css file (sorted alphabetically) and concatenate
18
102
  * their contents with a newline. Returns null when the project has no root
@@ -77,7 +161,12 @@ export async function syncCustomAssets(opts) {
77
161
  const { projectDir, workspaceDir } = opts;
78
162
  const publicDir = path.join(workspaceDir, 'public');
79
163
  const cssDest = path.join(publicDir, 'custom.css');
80
- const result = { cssWritten: false, cssRemoved: false };
164
+ const result = {
165
+ cssWritten: false,
166
+ cssRemoved: false,
167
+ jsWritten: false,
168
+ jsRemoved: false,
169
+ };
81
170
  const srcContent = await collectRootCss(projectDir);
82
171
  if (srcContent !== null) {
83
172
  const destContent = (await fs.pathExists(cssDest)) ? await fs.readFile(cssDest, 'utf-8') : null;
@@ -98,6 +187,25 @@ export async function syncCustomAssets(opts) {
98
187
  result.cssRemoved = true;
99
188
  }
100
189
  }
190
+ // Custom JS: same compare/remove rules as CSS. The dev layout reads
191
+ // <workspace>/public/custom.js; only copy-files.cjs wrote it before.
192
+ const jsDest = path.join(publicDir, 'custom.js');
193
+ const jsContent = await collectCustomJs(projectDir, opts.stylingJs);
194
+ if (jsContent !== null) {
195
+ const destContent = (await fs.pathExists(jsDest)) ? await fs.readFile(jsDest, 'utf-8') : null;
196
+ if (jsContent !== destContent) {
197
+ await fs.ensureDir(publicDir);
198
+ await fs.writeFile(jsDest, jsContent);
199
+ result.jsWritten = true;
200
+ }
201
+ }
202
+ else {
203
+ const projectShipsPublicJs = await fs.pathExists(path.join(projectDir, 'public', 'custom.js'));
204
+ if (!projectShipsPublicJs && (await fs.pathExists(jsDest))) {
205
+ await fs.remove(jsDest);
206
+ result.jsRemoved = true;
207
+ }
208
+ }
101
209
  return result;
102
210
  }
103
211
  //# sourceMappingURL=custom-assets.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"custom-assets.js","sourceRoot":"","sources":["../../src/lib/custom-assets.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"custom-assets.js","sourceRoot":"","sources":["../../src/lib/custom-assets.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jamdesk",
3
- "version": "1.1.221",
3
+ "version": "1.1.222",
4
4
  "description": "CLI for Jamdesk — build, preview, and deploy documentation sites from MDX. Dev server with hot reload, 50+ components, OpenAPI support, AI search, and Mintlify migration",
5
5
  "keywords": [
6
6
  "jamdesk",
@@ -2,7 +2,7 @@
2
2
  * Chat API Route for AI Documentation Q&A
3
3
  *
4
4
  * Handles POST requests for AI-powered documentation chat. Uses Upstash Vector
5
- * for RAG context retrieval and Claude Haiku 4.5 for streaming responses.
5
+ * for RAG context retrieval and Claude Haiku 5.5 for streaming responses.
6
6
  *
7
7
  * Features:
8
8
  * - SSE streaming (text chunks → clarification options → filtered citations → done)
@@ -25,7 +25,7 @@ import { querySimilarChunks } from '@/lib/vector-store';
25
25
  import { buildSystemPrompt, isCannedIdentityReply, resolveSiteName } from '@/lib/chat-prompt';
26
26
  import { stripHedge, isHedgeReply } from '@/lib/hedge-strip';
27
27
  import { getDocsPath, getBaseUrl, trackChatAnalytics } from '@/lib/route-helpers';
28
- import { getAnthropicClient } from '@/lib/anthropic-client';
28
+ import { getAnthropicClient, HAIKU_MODEL } from '@/lib/anthropic-client';
29
29
  import { rewriteQueryForSearch } from '@/lib/query-rewriter';
30
30
  import { fetchDocsConfig } from '@/lib/r2-content';
31
31
  import { BCP47_LANGUAGE_RE } from '@/lib/language-utils';
@@ -37,7 +37,14 @@ import { log } from '@/lib/logger';
37
37
  export const runtime = 'nodejs';
38
38
  export const maxDuration = 30;
39
39
 
40
- const CHAT_MODEL = 'claude-haiku-4-5-20251001';
40
+ const CHAT_MODEL = HAIKU_MODEL;
41
+
42
+ /** The model turn ended without a complete tool call (refusal, max_tokens, ...). */
43
+ class ChatStopError extends Error {
44
+ constructor(readonly stopReason: string | null) {
45
+ super(`Model turn ended without a complete tool call (stop_reason: ${stopReason})`);
46
+ }
47
+ }
41
48
  const RATE_LIMIT = 10;
42
49
  const RATE_WINDOW_SECONDS = 60;
43
50
  const MAX_HISTORY = 10;
@@ -240,8 +247,12 @@ export async function POST(
240
247
 
241
248
  const stream = anthropic.messages.stream({
242
249
  model: CHAT_MODEL,
243
- max_tokens: 2048,
244
- temperature: 0.3,
250
+ // Haiku 5.5's tokenizer counts the same text as ~30% more tokens than
251
+ // 4.5 did, so the old 2048 cap is raised to keep the same answer length.
252
+ max_tokens: 3072,
253
+ // Haiku 5.5 400s on any non-default temperature. The forced tool_choice
254
+ // below also skips thinking, so effort only sets answer depth here.
255
+ output_config: { effort: 'medium' },
245
256
  system: systemPrompt,
246
257
  tools: CHAT_TOOLS,
247
258
  tool_choice: { type: 'any' },
@@ -345,7 +356,13 @@ export async function POST(
345
356
  // With tool_choice: 'any' there is exactly one tool_use block.
346
357
  const toolUse = finalMessage.content.find(c => c.type === 'tool_use');
347
358
  if (!toolUse || toolUse.type !== 'tool_use') {
348
- throw new Error('Expected tool_use block in final message');
359
+ throw new ChatStopError(finalMessage.stop_reason);
360
+ }
361
+ // A tool_use block can exist even when the turn did not finish: a
362
+ // max_tokens cutoff or a refusal mid-stream leaves partial input.
363
+ // Treating that as an answer would show a cut-off reply as complete.
364
+ if (finalMessage.stop_reason !== 'tool_use') {
365
+ throw new ChatStopError(finalMessage.stop_reason);
349
366
  }
350
367
 
351
368
  let sources: Citation[];
@@ -430,12 +447,19 @@ export async function POST(
430
447
  userAgent: request.headers.get('user-agent') || undefined,
431
448
  }).catch(() => {});
432
449
  } catch (err) {
450
+ const stopReason = err instanceof ChatStopError ? err.stopReason : undefined;
433
451
  log('warn', 'chat: stream interrupted', {
434
452
  projectSlug: project,
435
453
  toolName,
454
+ stopReason,
436
455
  error: err instanceof Error ? err.message : String(err),
437
456
  });
438
- controller.enqueue(sendEvent({ type: 'error', message: 'The response was interrupted. Please try again.' }));
457
+ // Haiku 5.5 safety classifiers can decline, with no server-side
458
+ // fallback. Retrying gets the same refusal, so don't tell users to.
459
+ const message = stopReason === 'refusal'
460
+ ? "I can't help with that question. Try rephrasing it or asking about a different topic."
461
+ : 'The response was interrupted. Please try again.';
462
+ controller.enqueue(sendEvent({ type: 'error', message }));
439
463
  }
440
464
  controller.close();
441
465
  },
@@ -0,0 +1,17 @@
1
+ 'use client';
2
+
3
+ import { useChatPanelOptional } from '@/hooks/useChatPanel';
4
+ import { resolveChatIcon } from '@/lib/chat-branding';
5
+
6
+ /**
7
+ * The chat entry-point icon (`chat.icon`, sparkles by default). Reads the
8
+ * optional context so it also renders on pages outside the docs chrome.
9
+ */
10
+ export function ChatBrandIcon({ className, sizePx }: { className?: string; sizePx: number }) {
11
+ const spec = useChatPanelOptional()?.chatIcon ?? resolveChatIcon(undefined);
12
+ if (spec.kind === 'img') {
13
+ // eslint-disable-next-line @next/next/no-img-element -- customer asset, size is the icon slot
14
+ return <img src={spec.src} alt="" width={sizePx} height={sizePx} className={`object-contain ${className ?? ''}`} />;
15
+ }
16
+ return <i className={`${spec.className} ${className ?? ''}`} aria-hidden="true" />;
17
+ }
@@ -1,6 +1,8 @@
1
1
  'use client';
2
2
 
3
3
  import { memo, useMemo, useState, useCallback } from 'react';
4
+ import { ChatBrandIcon } from './ChatBrandIcon';
5
+ import { useChatLabel } from './useChatLabel';
4
6
 
5
7
  interface ChatEmptyStateProps {
6
8
  starterQuestions?: string[];
@@ -29,6 +31,7 @@ function starterButtonClass(question: string, selectedQuestion: string | null):
29
31
  * defined in docs.json — see lib/generate-starter-questions.ts.
30
32
  */
31
33
  export const ChatEmptyState = memo(function ChatEmptyState({ starterQuestions, onSelectQuestion, onTalkToHuman }: ChatEmptyStateProps) {
34
+ const chatLabel = useChatLabel('Ask AI');
32
35
  const shortcutKey = useMemo(
33
36
  () => typeof navigator !== 'undefined' && /Mac|iPhone|iPad/.test(navigator.userAgent) ? '⌘' : 'Ctrl+',
34
37
  [],
@@ -47,13 +50,10 @@ export const ChatEmptyState = memo(function ChatEmptyState({ starterQuestions, o
47
50
  return (
48
51
  <div className="flex flex-col items-center justify-center h-full px-6 py-12 text-center">
49
52
  <div className="mb-6">
50
- <i
51
- className="fa-solid fa-sparkles text-2xl text-[var(--color-accent)]"
52
- aria-hidden="true"
53
- />
53
+ <ChatBrandIcon className="text-2xl text-[var(--color-accent)]" sizePx={24} />
54
54
  </div>
55
55
  <h2 className="text-lg font-semibold text-[var(--color-text-primary)] mb-1">
56
- Ask AI
56
+ {chatLabel}
57
57
  </h2>
58
58
  <p className="text-sm text-[var(--color-text-secondary)] mb-6 sm:mb-1">
59
59
  Get answers from the documentation
@@ -4,6 +4,8 @@ import { useRef, useEffect, useLayoutEffect, useCallback, useState } from 'react
4
4
  import { useChat } from '@/hooks/useChat';
5
5
  import { useBodyScrollLock } from '@/hooks/useBodyScrollLock';
6
6
  import { useChatPanelOptional } from '@/hooks/useChatPanel';
7
+ import { ChatBrandIcon } from '@/components/chat/ChatBrandIcon';
8
+ import { useChatLabel } from '@/components/chat/useChatLabel';
7
9
  import { ChatMessage } from './ChatMessage';
8
10
  import { ChatInput } from './ChatInput';
9
11
  import { ChatEmptyState } from './ChatEmptyState';
@@ -45,6 +47,7 @@ interface ChatPanelProps {
45
47
  * mode="inline": Persistent column embedded in page layout (no positioning).
46
48
  */
47
49
  export function ChatPanel({ isOpen, onClose, starterQuestions, chatEndpoint, mode = 'overlay' }: ChatPanelProps) {
50
+ const chatLabel = useChatLabel('AI Chat');
48
51
  const { messages, sendMessage, isLoading, abort, retry, clearChat, error, markClarificationSelected } = useChat(chatEndpoint);
49
52
 
50
53
  // Search (and any external trigger) hands a question to chat via the
@@ -200,13 +203,13 @@ export function ChatPanel({ isOpen, onClose, starterQuestions, chatEndpoint, mod
200
203
  <>
201
204
  {/* Header */}
202
205
  <div className="flex items-center justify-between h-14 px-4 border-b border-[var(--color-border)] flex-shrink-0" style={{ borderBottomWidth: '1px' }}>
203
- <div className="flex items-center gap-2">
204
- <i className="fa-solid fa-sparkles text-sm text-[var(--color-accent)]" aria-hidden="true" />
205
- <span className="text-sm font-semibold text-[var(--color-text-primary)]">
206
- AI Chat
206
+ <div className="flex items-center gap-2 min-w-0">
207
+ <ChatBrandIcon className="text-sm text-[var(--color-accent)] shrink-0" sizePx={14} />
208
+ <span title={chatLabel} className="text-sm font-semibold text-[var(--color-text-primary)] truncate">
209
+ {chatLabel}
207
210
  </span>
208
211
  </div>
209
- <div className="flex items-center gap-1">
212
+ <div className="flex items-center gap-1 shrink-0">
210
213
  {/* R8: only when no message is mid-stream, so the transcript captures
211
214
  the post-rewrite (absolute-URL) link form, not slug placeholders. */}
212
215
  {hasMessages && !messages.some((m) => m.isStreaming) && (
@@ -303,7 +306,7 @@ export function ChatPanel({ isOpen, onClose, starterQuestions, chatEndpoint, mod
303
306
  if (isInline) {
304
307
  return (
305
308
  <div
306
- aria-label="AI Chat"
309
+ aria-label={chatLabel}
307
310
  className="flex flex-col h-full"
308
311
  style={{ backgroundColor: 'var(--color-bg-primary)', touchAction: 'manipulation', fontFamily: 'var(--font-primary, inherit)' }}
309
312
  >
@@ -328,7 +331,7 @@ export function ChatPanel({ isOpen, onClose, starterQuestions, chatEndpoint, mod
328
331
  <div
329
332
  ref={overlayPanelRef}
330
333
  role="dialog"
331
- aria-label="AI Chat"
334
+ aria-label={chatLabel}
332
335
  inert={!isOpen ? true : undefined}
333
336
  aria-modal={isOpen || undefined}
334
337
  data-open={isOpen}
@@ -0,0 +1,9 @@
1
+ 'use client';
2
+
3
+ import { useChatPanelOptional } from '@/hooks/useChatPanel';
4
+ import { resolveChatLabel } from '@/lib/chat-branding';
5
+
6
+ /** `chat.label` if configured, else the call site's own default text. */
7
+ export function useChatLabel(fallback: string): string {
8
+ return resolveChatLabel(useChatPanelOptional()?.chatLabel, fallback);
9
+ }
@@ -11,6 +11,7 @@ import { BannerBar } from '@/components/layout/BannerBar';
11
11
  import { ChatPanelProvider, useChatPanel } from '@/hooks/useChatPanel';
12
12
  import { useHashNavigation } from '@/hooks/useHashNavigation';
13
13
  import { useWheelScrollChaining } from '@/hooks/useWheelScrollChaining';
14
+ import { resolveChatIcon } from '@/lib/chat-branding';
14
15
  import { useLinkPrefix } from '@/lib/link-prefix-context';
15
16
  import type { DocsConfig } from '@/lib/docs-types';
16
17
  import { getTheme, type ThemeName } from '@/themes';
@@ -122,6 +123,8 @@ export function LayoutWrapper({ config, children, embed }: LayoutWrapperProps) {
122
123
  chatEnabled={chatEnabled}
123
124
  chatEndpoint={chatEndpoint}
124
125
  starterQuestions={config.chat?.starterQuestions}
126
+ chatLabel={config.chat?.label}
127
+ chatIcon={resolveChatIcon(config.chat?.icon, config.assetVersion)}
125
128
  >
126
129
  {content}
127
130
  </ChatPanelProvider>
@@ -3,6 +3,7 @@
3
3
  import dynamic from 'next/dynamic';
4
4
  import { useMemo, useRef, useState } from 'react';
5
5
  import { readCachedHeight } from './mermaidCache';
6
+ import { isZoomDisabled } from './MermaidZoomModal';
6
7
  import { useIsomorphicLayoutEffect } from '../../hooks/useIsomorphicLayoutEffect';
7
8
 
8
9
  // Neutral floor reserved in the SSR markup itself (present on the browser's
@@ -32,9 +33,21 @@ interface MermaidProps {
32
33
  className?: string;
33
34
  /** Minimum width for the SVG diagram (e.g., "700px", "100%") */
34
35
  minWidth?: string;
36
+ /** Turns off click to expand. All three spellings: see isZoomDisabled. */
37
+ noZoom?: boolean | string;
38
+ nozoom?: boolean | string;
39
+ 'data-no-zoom'?: boolean | string;
35
40
  }
36
41
 
37
- export function Mermaid({ children, className, minWidth }: MermaidProps) {
42
+ export function Mermaid({
43
+ children,
44
+ className,
45
+ minWidth,
46
+ noZoom,
47
+ nozoom,
48
+ 'data-no-zoom': dataNoZoom,
49
+ }: MermaidProps) {
50
+ const zoomDisabled = isZoomDisabled(noZoom, nozoom, dataNoZoom);
38
51
  const wrapperRef = useRef<HTMLDivElement>(null);
39
52
 
40
53
  // Server + first (hydration) render emit the constant default — identical
@@ -93,11 +106,11 @@ export function Mermaid({ children, className, minWidth }: MermaidProps) {
93
106
  // would memoize an element closed over a stale component identity.
94
107
  const inner = useMemo(
95
108
  () => (
96
- <MermaidInner className={className} minWidth={minWidth}>
109
+ <MermaidInner className={className} minWidth={minWidth} noZoom={zoomDisabled}>
97
110
  {children}
98
111
  </MermaidInner>
99
112
  ),
100
- [children, className, minWidth]
113
+ [children, className, minWidth, zoomDisabled]
101
114
  );
102
115
 
103
116
  return (