diff --git a/apps/site/src/App.tsx b/apps/site/src/App.tsx
index 585679f..e7593fc 100644
--- a/apps/site/src/App.tsx
+++ b/apps/site/src/App.tsx
@@ -10,6 +10,7 @@ import {
mono,
playgroundUrl,
themeConfig,
+ useCanvasControlTracking,
} from './playground-shared'
const REPO = 'inkeep/visimer'
@@ -155,6 +156,7 @@ export default function App() {
const [type, setType] = useState('flowchart')
const [skin, setSkin] = useState<'light' | 'dark'>('light')
const { editor } = useMermaidEditor(PRESETS.flowchart)
+ useCanvasControlTracking()
// Expanding hands off to the dedicated /playground page, carrying the
// current source in the URL hash so nothing is lost in the jump.
@@ -705,7 +707,7 @@ export default function App() {
>
Preview
- click to select · double-click to edit · drag empty space to pan · pinch to zoom
+ click to select · double-click to edit · alt + drag to connect · pinch to zoom
tap to select · double-tap to edit · pinch to zoom
diff --git a/apps/site/src/PlaygroundPage.tsx b/apps/site/src/PlaygroundPage.tsx
index f4d7f1b..cce0bc8 100644
--- a/apps/site/src/PlaygroundPage.tsx
+++ b/apps/site/src/PlaygroundPage.tsx
@@ -11,6 +11,7 @@ import {
encodeCode,
mono,
themeConfig,
+ useCanvasControlTracking,
} from './playground-shared'
/**
@@ -22,6 +23,7 @@ import {
export default function PlaygroundPage() {
const initialCode = useMemo(() => codeFromHash(window.location.hash) ?? PRESETS.flowchart, [])
const { editor } = useMermaidEditor(initialCode)
+ useCanvasControlTracking()
const [skin, setSkin] = useState<'light' | 'dark'>('light')
const [menuOpen, setMenuOpen] = useState(false)
@@ -312,7 +314,7 @@ export default function PlaygroundPage() {
>
Preview
- click to select · double-click to edit · drag empty space to pan · pinch to zoom
+ click to select · double-click to edit · alt + drag to connect · pinch to zoom
tap to select · double-tap to edit · pinch to zoom
diff --git a/apps/site/src/analytics.ts b/apps/site/src/analytics.ts
index a9841ef..5b64f8d 100644
--- a/apps/site/src/analytics.ts
+++ b/apps/site/src/analytics.ts
@@ -23,6 +23,12 @@ if (key) {
}
export function track(event: string, properties?: Record) {
+ // dev only: mirror event names onto the window so wiring is verifiable
+ // in the browser without a key or a network tap
+ if (import.meta.env.DEV) {
+ const w = window as unknown as { __phEvents?: string[] }
+ ;(w.__phEvents ??= []).push(event)
+ }
if (!key) return
posthog.capture(event, properties)
}
diff --git a/apps/site/src/playground-shared.tsx b/apps/site/src/playground-shared.tsx
index 32854a7..94b1b60 100644
--- a/apps/site/src/playground-shared.tsx
+++ b/apps/site/src/playground-shared.tsx
@@ -2,6 +2,7 @@ import { useEffect, useRef } from 'react'
import { EditorView } from '@codemirror/view'
import { MermaidCodeMirror } from '@visimer/codemirror'
import type { MermaidWysiwygEditor } from '@visimer/core'
+import { track } from './analytics'
/** Sample diagrams + chrome shared by the inline demo and /playground. */
@@ -178,6 +179,26 @@ export function CodeMirrorPane({ editor }: { editor: MermaidWysiwygEditor }) {
return
}
+/**
+ * Tracks clicks on the canvas corner zoom controls (zoom in / zoom out /
+ * fit). The buttons are rendered by @visimer/dom, so the site listens by
+ * delegation instead of teaching the package about analytics.
+ */
+export function useCanvasControlTracking() {
+ useEffect(() => {
+ const onClick = (e: MouseEvent) => {
+ const btn = (e.target as Element | null)?.closest?.('.mw-zoom-btn')
+ if (!btn) return
+ const title = btn.getAttribute('title') ?? ''
+ const control = /out/i.test(title) ? 'zoom-out' : /in\b/i.test(title) ? 'zoom-in' : 'fit'
+ track('canvas_zoom_control', { control })
+ }
+ // capture phase: the package's buttons stopPropagation on their clicks
+ document.addEventListener('click', onClick, true)
+ return () => document.removeEventListener('click', onClick, true)
+ }, [])
+}
+
// ---- code <-> URL hash codec ----
// The playground keeps the current source in `#code=` so the
// address bar is always a share link. No compression: sample-sized diagrams