|
1 | | -import { useState, useCallback, useRef, useEffect } from 'react'; |
| 1 | +import { useState, useCallback, useRef, useEffect, useMemo } from 'react'; |
2 | 2 | import MonacoEditor from '@monaco-editor/react'; |
3 | 3 | import type * as Monaco from 'monaco-editor'; |
4 | 4 | import type { ProjectEntry, FileEntry } from '../types/project'; |
@@ -360,8 +360,20 @@ export default function Editor({ project, files, fileContents, onDisconnect, onC |
360 | 360 | // effect's setContent(automergeContent) always produces a state change. |
361 | 361 | const displayContent = replayState.isActive ? replayState.currentContent : content; |
362 | 362 |
|
363 | | - // Per-node attribution for q2-debug AST view |
364 | | - const attribution = useAttribution(currentFile?.path ?? null, identities ?? {}, displayContent); |
| 363 | + // Per-node attribution for q2-debug AST view (opt-in via `attribution: true` in YAML metadata) |
| 364 | + const attributionEnabled = useMemo(() => { |
| 365 | + if (currentFormat !== 'q2-debug' || !astJson) return false; |
| 366 | + try { |
| 367 | + const ast = JSON.parse(astJson); |
| 368 | + const val = ast?.meta?.attribution; |
| 369 | + // MetaBool: { t: "MetaBool", c: true } |
| 370 | + if (val?.t === 'MetaBool') return val.c === true; |
| 371 | + // MetaString: { t: "MetaString", c: "true" } |
| 372 | + if (val?.t === 'MetaString') return val.c === 'true'; |
| 373 | + return false; |
| 374 | + } catch { return false; } |
| 375 | + }, [currentFormat, astJson]); |
| 376 | + const attribution = useAttribution(attributionEnabled ? (currentFile?.path ?? null) : null, identities ?? {}, displayContent); |
365 | 377 | const attributionContextValue = attribution ? { |
366 | 378 | attributionMap: attribution.attributionMap, |
367 | 379 | byteToCharMap: attribution.byteToCharMap, |
|
0 commit comments