From 7d1f57a83a34312bea265ae3095bc67ff4e0493e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 16:50:38 +0900 Subject: [PATCH] chore: remove stray scratch/debug files committed to develop plan.md, test_parse.py, test_parse2.py, test_parse3.py, and frontend/src/components/NetworkGraph.tsx.out are ad-hoc planning notes, manual debug scripts, and a stray build-output copy that were accidentally committed to the protected default branch. None are referenced by the backend/frontend test suites or CI. Removing them also stops every still-open PR whose branch predates their addition from showing a misleading 'this PR deletes N files' diff against develop (e.g. #1287). --- frontend/src/components/NetworkGraph.tsx.out | 452 ------------------- plan.md | 21 - test_parse.py | 24 - test_parse2.py | 14 - test_parse3.py | 33 -- 5 files changed, 544 deletions(-) delete mode 100644 frontend/src/components/NetworkGraph.tsx.out delete mode 100644 plan.md delete mode 100644 test_parse.py delete mode 100644 test_parse2.py delete mode 100644 test_parse3.py diff --git a/frontend/src/components/NetworkGraph.tsx.out b/frontend/src/components/NetworkGraph.tsx.out deleted file mode 100644 index 1cb0ae993..000000000 --- a/frontend/src/components/NetworkGraph.tsx.out +++ /dev/null @@ -1,452 +0,0 @@ -'use client'; - -import { useEffect, useMemo, useRef, useState } from 'react'; -import { Network } from 'vis-network'; - -interface Node { - id: number | string; - label: string; - [key: string]: unknown; -} - -interface Edge { - id?: number | string; - from: number | string; - to: number | string; - [key: string]: unknown; -} - -interface ApiEdge { - from?: number | string; - to?: number | string; - source?: number | string; - target?: number | string; - [key: string]: unknown; -} - -interface NetworkData { - nodes: Node[]; - edges: ApiEdge[]; -} - -interface NormalizedNetworkData { - nodes: Node[]; - edges: Edge[]; -} - -interface GraphSelectionEvent { - nodes?: Array; - edges?: Array; -} - -function textOnlyTooltip(value: unknown): HTMLElement { - const tooltip = document.createElement('div'); - tooltip.textContent = value == null ? '' : String(value); - return tooltip; -} - -const HTML_TEXT_ESCAPE_PATTERN = /[&<>"']/g; -const HTML_TEXT_ESCAPES: Record = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''', -}; - -function escapeGraphLabel(value: unknown): string { - return String(value ?? '').replace( - HTML_TEXT_ESCAPE_PATTERN, - (character) => HTML_TEXT_ESCAPES[character] ?? character, - ); -} - -function sanitizeGraphItem(item: T): T { - const sanitized = { ...item }; - - if (Object.prototype.hasOwnProperty.call(item, 'title')) { - sanitized.title = textOnlyTooltip(item.title); - } - - return sanitized; -} - -function escapeVisNetworkLabels(items: T[]): T[] { - return items.map((item) => { - if (!Object.prototype.hasOwnProperty.call(item, 'label')) return item; - return { - ...item, - label: escapeGraphLabel(item.label), - }; - }); -} - -function isGraphId(value: unknown): value is number | string { - return typeof value === 'number' || typeof value === 'string'; -} - -function graphIdEquals(left: unknown, right: unknown) { - return isGraphId(left) && isGraphId(right) && String(left) === String(right); -} - -function stableEdgeId(edge: Edge, index: number) { - if (isGraphId(edge.id)) return edge.id; - return `relationship-${index}-${String(edge.from)}-${String(edge.to)}`; -} - -function normalizeEdge(edge: ApiEdge): Edge | null { - const from = edge.from ?? edge.source; - const to = edge.to ?? edge.target; - - if (!isGraphId(from) || !isGraphId(to)) return null; - - const rest = { ...edge }; - delete rest.source; - delete rest.target; - return { - ...rest, - from, - to, - }; -} - -function sanitizeNetworkData(data: NetworkData): NormalizedNetworkData { - return { - nodes: data.nodes.map(sanitizeGraphItem), - edges: data.edges.flatMap((edge, index) => { - const normalized = normalizeEdge(edge); - return normalized ? [sanitizeGraphItem({ ...normalized, id: stableEdgeId(normalized, index) })] : []; - }), - }; -} - -function titleText(value: unknown) { - if (typeof HTMLElement !== 'undefined' && value instanceof HTMLElement) { - return value.textContent?.trim() ?? ''; - } - return value == null ? '' : String(value).trim(); -} - -function findNodeLabel(nodes: Node[], id: number | string) { - const node = nodes.find((candidate) => graphIdEquals(candidate.id, id)); - return String(node?.label ?? id); -} - -function describeEdge(edge: Edge, nodes: Node[], nodeMap?: Map) { - let fromLabel, toLabel; - if (nodeMap) { - fromLabel = nodeMap.get(String(edge.from)) ?? String(edge.from); - toLabel = nodeMap.get(String(edge.to)) ?? String(edge.to); - } else { - fromLabel = findNodeLabel(nodes, edge.from); - toLabel = findNodeLabel(nodes, edge.to); - } - const title = titleText(edge.title); - return title ? `${fromLabel} -> ${toLabel} (${title})` : `${fromLabel} -> ${toLabel}`; -} - -import { apiClient } from '@/lib/api-client'; - -export default function NetworkGraph() { - const containerRef = useRef(null); - const networkRef = useRef(null); - - const [nodes, setNodes] = useState([]); - const [edges, setEdges] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - const [selectedGraphDetail, setSelectedGraphDetail] = useState(null); - const [graphActionStatus, setGraphActionStatus] = useState('그래프 준비 완료'); - const [relationshipOptionId, setRelationshipOptionId] = useState(''); - const [nodeOptionId, setNodeOptionId] = useState(''); - const nodeMap = useMemo(() => { - const map = new Map(); - for (const node of nodes) { - const key = String(node.id); - if (!map.has(key)) { - map.set(key, String(node.label ?? node.id)); - } - } - return map; - }, [nodes]); - - useEffect(() => { - apiClient.get('/api/network/graph') - .then((data) => { - const sanitized = sanitizeNetworkData(data); - setNodes(sanitized.nodes); - setEdges(sanitized.edges); - setLoading(false); - }) - .catch((err) => { - console.error('Failed to load network graph:', err); - setError('관계 맥락을 불러오지 못했습니다.'); - setLoading(false); - }); - }, []); - - useEffect(() => { - if (containerRef.current && nodes.length > 0) { - const container = containerRef.current; - const network = new Network(container, { - nodes: escapeVisNetworkLabels(nodes), - edges: escapeVisNetworkLabels(edges), - }, { - nodes: { shape: 'dot', size: 16 }, - edges: { arrows: 'to' } - }); - networkRef.current = network; - - const fitGraph = () => { - network.fit?.({ animation: false }); - }; - - const selectEdge = (edgeId: number | string) => { - const edge = edges.find((candidate) => graphIdEquals(candidate.id, edgeId)); - if (!edge) return; - setRelationshipOptionId(String(edge.id)); - setNodeOptionId(''); - setSelectedGraphDetail(`선택된 관계: ${describeEdge(edge, nodes, nodeMap)}`); - setGraphActionStatus('그래프에서 관계를 선택했습니다.'); - }; - - const selectNode = (nodeId: number | string) => { - setRelationshipOptionId(''); - setNodeOptionId(String(nodeId)); - setSelectedGraphDetail(`선택된 노드: ${nodeMap.get(String(nodeId)) ?? String(nodeId)}`); - setGraphActionStatus('그래프에서 노드를 선택했습니다.'); - }; - - const handleEdgeSelection = (event: GraphSelectionEvent) => { - const edgeId = event.edges?.[0]; - if (isGraphId(edgeId)) selectEdge(edgeId); - }; - - const handleNodeSelection = (event: GraphSelectionEvent) => { - const nodeId = event.nodes?.[0]; - if (isGraphId(nodeId)) selectNode(nodeId); - }; - - const canListenForSelection = - typeof network.on === 'function' && typeof network.off === 'function'; - - if (canListenForSelection) { - network.on('selectEdge', handleEdgeSelection); - network.on('selectNode', handleNodeSelection); - } - - let resizeTimer: ReturnType | null = null; - const resizeObserver = typeof ResizeObserver === 'undefined' - ? null - : new ResizeObserver(() => { - if (resizeTimer !== null) { - clearTimeout(resizeTimer); - } - resizeTimer = setTimeout(fitGraph, 50); - }); - - resizeObserver?.observe(container); - - return () => { - if (resizeTimer !== null) { - clearTimeout(resizeTimer); - } - resizeObserver?.disconnect(); - if (canListenForSelection) { - network.off('selectEdge', handleEdgeSelection); - network.off('selectNode', handleNodeSelection); - } - if (networkRef.current === network) { - networkRef.current = null; - } - network.destroy(); - }; - } - }, [nodes, edges, nodeMap]); - - const nodeLabels = useMemo(() => { - return nodes - .map((node) => String(node.label ?? node.id)) - .filter(Boolean) - .slice(0, 5); - }, [nodes]); - - const firstEdge = edges[0] ?? null; - const relationshipOptions = useMemo(() => { - return edges.slice(0, 5).map((edge, index) => ({ - edge, - id: String(edge.id), - label: `관계 ${index + 1}: ${describeEdge(edge, nodes, nodeMap)}`, - })); - }, [edges, nodes, nodeMap]); - - const nodeOptions = useMemo(() => { - return nodes.slice(0, 8).map((node) => ({ - id: String(node.id), - label: `노드: ${String(node.label ?? node.id)}`, - node, - })); - }, [nodes]); - - const selectRelationship = (edge: Edge, status: string) => { - setRelationshipOptionId(String(edge.id)); - setNodeOptionId(''); - setSelectedGraphDetail(`선택된 관계: ${describeEdge(edge, nodes, nodeMap)}`); - setGraphActionStatus(status); - if (isGraphId(edge.id)) { - networkRef.current?.selectEdges?.([edge.id]); - } - networkRef.current?.fit?.({ nodes: [edge.from, edge.to], animation: false }); - }; - - const selectGraphNode = (node: Node, status: string) => { - if (!isGraphId(node.id)) return; - setRelationshipOptionId(''); - setNodeOptionId(String(node.id)); - setSelectedGraphDetail(`선택된 노드: ${String(node.label ?? node.id)}`); - setGraphActionStatus(status); - networkRef.current?.selectNodes?.([node.id]); - networkRef.current?.fit?.({ nodes: [node.id], animation: false }); - }; - - const handleSelectFirstRelationship = () => { - if (!firstEdge) return; - selectRelationship(firstEdge, '첫 관계를 선택했습니다.'); - }; - - const handleRelationshipOptionChange = (value: string) => { - const edge = edges.find((candidate) => String(candidate.id) === value); - if (!edge) return; - selectRelationship(edge, '선택한 관계를 열었습니다.'); - }; - - const handleNodeOptionChange = (value: string) => { - const node = nodes.find((candidate) => String(candidate.id) === value); - if (!node) return; - selectGraphNode(node, '선택한 노드를 열었습니다.'); - }; - - const handleZoomGraph = () => { - networkRef.current?.moveTo?.({ scale: 1.15, animation: false }); - setGraphActionStatus('그래프 확대 완료'); - }; - - const handleFitGraph = () => { - networkRef.current?.fit?.({ animation: false }); - setGraphActionStatus('그래프 맞춤 완료'); - }; - - if (loading) { - return
관계 맥락을 불러오는 중입니다...
; - } - - if (error) { - return ( -
-
-

관계 맥락을 불러오지 못했습니다

-

{error}

-
-
- ); - } - - if (nodes.length === 0) { - return ( -
-
- -

관계 데이터가 없습니다

-

메일이 연결되면 사람, 주제, 일정의 흐름을 관계 맥락으로 보여줍니다.

-
-
- ); - } - - return ( -
-
-

관계 이해

-

- {nodes.length}개 노드와 {edges.length}개 관계가 이 스레드 맥락에 연결되어 있습니다. -

-
-

텍스트 관계 맥락 종합

-

- 관련 노드: {nodeLabels.join(', ')} -

-
-
- - - -
-
- - -
-
-

관계 상세

-

- {selectedGraphDetail ?? '관계를 선택하면 담당자와 일정 흐름을 확인합니다.'} -

-

{graphActionStatus}

-
-
-
-
- ); -} diff --git a/plan.md b/plan.md deleted file mode 100644 index bbc5ddc29..000000000 --- a/plan.md +++ /dev/null @@ -1,21 +0,0 @@ -# NetworkGraph constant-time lookup plan - -1. Pre-compute `edgeMap` and `nodeInstanceMap` with `useMemo`, and keep the existing `nodeMap` as the authoritative node-label lookup for rendered selections. - - `selectEdge` uses `edgeMap.get(String(edgeId))`. - - `selectNode` uses `nodeMap.get(String(nodeId))` with the node identifier as the no-entry fallback. - - `selectGraphNode` uses `nodeMap.get(String(node.id))` with the node identifier as the no-entry fallback. - - `handleRelationshipOptionChange` uses `edgeMap.get(value)`. - - `handleNodeOptionChange` uses `nodeInstanceMap.get(value)`. - - Selection handlers must not fall back to `Array.prototype.find()` or `findNodeLabel()` scans. - - `edgeMap` and `nodeInstanceMap` are first-wins, matching `nodeMap` and the previous `.find()` path. Last-wins `new Map(items.map(...))` construction is rejected. - -2. Verify the exact branch head from `frontend/` with these commands: - - ```bash - pnpm test -- src/components/NetworkGraph.test.tsx src/components/NetworkGraph.map-lookup.test.ts - pnpm exec eslint src/components/NetworkGraph.tsx src/components/NetworkGraph.test.tsx src/components/NetworkGraph.map-lookup.test.ts - pnpm typecheck - pnpm build - ``` - -3. Keep the pull request open until the unchanged exact head has terminal-success required checks, all addressed review threads are resolved, and protected-branch review requirements are satisfied without bypass. diff --git a/test_parse.py b/test_parse.py deleted file mode 100644 index 374a3c09b..000000000 --- a/test_parse.py +++ /dev/null @@ -1,24 +0,0 @@ -import sys -from pathlib import Path - -sys.path.insert(0, str(Path(__file__).resolve().parent / "backend")) -from services.text_safety import _strip_tag_like_segments, _PlainTextHTMLParser - - -def main() -> None: - parser = _PlainTextHTMLParser() - parser.feed("-->") - parser.close() - text = parser.get_text() - print("Parsed text:", repr(text)) - print("Strip tag like segments:", repr(_strip_tag_like_segments(text))) - - # also look at what the parser does with - parser2 = _PlainTextHTMLParser() - parser2.feed("") - parser2.close() - print("Parsed :", repr(parser2.get_text())) - - -if __name__ == "__main__": - main() diff --git a/test_parse2.py b/test_parse2.py deleted file mode 100644 index 76c435252..000000000 --- a/test_parse2.py +++ /dev/null @@ -1,14 +0,0 @@ -import sys -from pathlib import Path - -sys.path.insert(0, str(Path(__file__).resolve().parent / "backend")) -from services.text_safety import strip_html_markup - - -def main() -> None: - payload = "-->" - print(repr(strip_html_markup(payload))) - - -if __name__ == "__main__": - main() diff --git a/test_parse3.py b/test_parse3.py deleted file mode 100644 index cbdec66c1..000000000 --- a/test_parse3.py +++ /dev/null @@ -1,33 +0,0 @@ -import sys -from pathlib import Path - -sys.path.insert(0, str(Path(__file__).resolve().parent / "backend")) -from services.text_safety import _mask_angle_emails, _PlainTextHTMLParser, _strip_tag_like_segments - - -def main() -> None: - payload = "-->" - - decoded = payload - masked, placeholders = _mask_angle_emails(decoded) - print("masked:", repr(masked)) - parser = _PlainTextHTMLParser() - parser.feed(masked) - parser.close() - text = parser.get_text() - print("text after parser get_text (normalized):", repr(text)) - - print("after get_text but raw joins:", repr("".join(parser._parts))) - print("just _strip_tag_like_segments directly on parser._parts:", _strip_tag_like_segments("".join(parser._parts))) - - cleaned_lines = [] - for line in text.splitlines(): - cleaned_lines.append(_strip_tag_like_segments(line)) - text = "\n".join(cleaned_lines).strip() - for token, original in placeholders.items(): - text = text.replace(token, original) - print("text after second loop:", repr(text)) - - -if __name__ == "__main__": - main()