From 6cddcae437b6f3082767bd9a72c6144c1eb96d24 Mon Sep 17 00:00:00 2001 From: irof Date: Sat, 24 Jan 2026 00:01:32 +0900 Subject: [PATCH 01/43] =?UTF-8?q?=E3=83=91=E3=83=83=E3=82=B1=E3=83=BC?= =?UTF-8?q?=E3=82=B8=E9=96=A2=E9=80=A3=E5=9B=B3=E3=81=AE=E5=87=BA=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/resources/templates/assets/jig.js | 46 +++++++++++++++++-- .../src/main/resources/templates/package.html | 10 ++++ 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/jig-core/src/main/resources/templates/assets/jig.js b/jig-core/src/main/resources/templates/assets/jig.js index ce5e05e2f..8f30028a9 100644 --- a/jig-core/src/main/resources/templates/assets/jig.js +++ b/jig-core/src/main/resources/templates/assets/jig.js @@ -285,12 +285,18 @@ function zoomFamilyTables(baseTable, baseRow) { }) } -function writePackageTable() { +function readPackageSummaryData() { const jsonText = document.getElementById('package-data').textContent; /** @type {{packages?: Array<{fqn: string, name: string, classCount: number, description: string}>, relations?: Array<{from: string, to: string}>} | Array<{fqn: string, name: string, classCount: number, description: string}>} */ const packageData = JSON.parse(jsonText); - const packages = Array.isArray(packageData) ? packageData : (packageData.packages ?? []); - const relations = Array.isArray(packageData) ? [] : (packageData.relations ?? []); + return { + packages: Array.isArray(packageData) ? packageData : (packageData.packages ?? []), + relations: Array.isArray(packageData) ? [] : (packageData.relations ?? []), + }; +} + +function writePackageTable() { + const {packages, relations} = readPackageSummaryData(); const incomingCounts = new Map(); const outgoingCounts = new Map(); relations.forEach(relation => { @@ -337,6 +343,39 @@ function writePackageTable() { }); } +function writePackageRelationDiagram() { + const diagram = document.getElementById('package-relation-diagram'); + if (!diagram) return; + + const {packages, relations} = readPackageSummaryData(); + const escapeMermaidText = text => text.replace(/"/g, '\\"'); + const lines = ['graph TD']; + const nodeIdByFqn = new Map(); + let nodeIndex = 0; + const ensureNodeId = fqn => { + if (nodeIdByFqn.has(fqn)) return nodeIdByFqn.get(fqn); + const nodeId = `P${nodeIndex++}`; + nodeIdByFqn.set(fqn, nodeId); + lines.push(`${nodeId}["${escapeMermaidText(fqn)}"]`); + return nodeId; + }; + + packages.forEach(item => { + ensureNodeId(item.fqn); + }); + relations.forEach(relation => { + const fromId = ensureNodeId(relation.from); + const toId = ensureNodeId(relation.to); + lines.push(`${fromId} --> ${toId}`); + }); + + diagram.textContent = lines.join('\n'); + if (window.mermaid) { + mermaid.initialize({startOnLoad: false}); + mermaid.run({nodes: [diagram]}); + } +} + // ページ読み込み時のイベント // リスナーの登録はそのページだけでやる document.addEventListener("DOMContentLoaded", function () { @@ -353,6 +392,7 @@ document.addEventListener("DOMContentLoaded", function () { } else if (document.body.classList.contains("package-list")) { document.getElementById("toggle-description-btn").addEventListener("click", toggleDescription); setupSortableTables(); + writePackageRelationDiagram(); writePackageTable(); } else if (document.body.classList.contains("insight")) { setupSortableTables(); diff --git a/jig-core/src/main/resources/templates/package.html b/jig-core/src/main/resources/templates/package.html index 7cca2f4a6..d800f67e2 100644 --- a/jig-core/src/main/resources/templates/package.html +++ b/jig-core/src/main/resources/templates/package.html @@ -19,7 +19,17 @@

パッケージ概要

+
+ + + + + + + + + From 65fda3bc2dbfb085cb5ab1c9207c387962a1e6a7 Mon Sep 17 00:00:00 2001 From: irof Date: Sat, 24 Jan 2026 00:08:10 +0900 Subject: [PATCH 02/43] =?UTF-8?q?=E3=83=91=E3=83=83=E3=82=B1=E3=83=BC?= =?UTF-8?q?=E3=82=B8=E6=A6=82=E8=A6=81=E3=81=AE=E3=83=80=E3=82=A4=E3=82=A2?= =?UTF-8?q?=E3=82=B0=E3=83=A9=E3=83=A0=E3=82=92=E9=81=B8=E6=8A=9E=E3=81=97?= =?UTF-8?q?=E3=81=9F=E3=83=91=E3=83=83=E3=82=B1=E3=83=BC=E3=82=B8=E9=85=8D?= =?UTF-8?q?=E4=B8=8B=E3=81=AB=E7=B5=9E=E3=82=8A=E3=81=93=E3=82=81=E3=82=8B?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/resources/templates/assets/jig.js | 42 +++++++++++++++---- .../src/main/resources/templates/package.html | 2 +- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/jig-core/src/main/resources/templates/assets/jig.js b/jig-core/src/main/resources/templates/assets/jig.js index 8f30028a9..67d40f6d0 100644 --- a/jig-core/src/main/resources/templates/assets/jig.js +++ b/jig-core/src/main/resources/templates/assets/jig.js @@ -285,14 +285,19 @@ function zoomFamilyTables(baseTable, baseRow) { }) } +let packageSummaryCache = null; +let packageDiagramNodeIdToFqn = new Map(); + function readPackageSummaryData() { + if (packageSummaryCache) return packageSummaryCache; const jsonText = document.getElementById('package-data').textContent; /** @type {{packages?: Array<{fqn: string, name: string, classCount: number, description: string}>, relations?: Array<{from: string, to: string}>} | Array<{fqn: string, name: string, classCount: number, description: string}>} */ const packageData = JSON.parse(jsonText); - return { + packageSummaryCache = { packages: Array.isArray(packageData) ? packageData : (packageData.packages ?? []), relations: Array.isArray(packageData) ? [] : (packageData.relations ?? []), }; + return packageSummaryCache; } function writePackageTable() { @@ -313,6 +318,7 @@ function writePackageTable() { const fqnTd = document.createElement('td'); fqnTd.textContent = item.fqn; fqnTd.className = 'fqn'; + fqnTd.addEventListener('click', () => filterPackageDiagramByFqn(item.fqn)); tr.appendChild(fqnTd); const nameTd = document.createElement('td'); @@ -343,39 +349,61 @@ function writePackageTable() { }); } -function writePackageRelationDiagram() { +function writePackageRelationDiagram(filterFqn) { const diagram = document.getElementById('package-relation-diagram'); if (!diagram) return; const {packages, relations} = readPackageSummaryData(); const escapeMermaidText = text => text.replace(/"/g, '\\"'); const lines = ['graph TD']; + const scopePrefix = filterFqn ? `${filterFqn}.` : null; + const withinScope = fqn => !filterFqn || fqn === filterFqn || fqn.startsWith(scopePrefix); + const visiblePackages = packages.filter(item => withinScope(item.fqn)); + const visibleSet = new Set(visiblePackages.map(item => item.fqn)); + const visibleRelations = relations.filter(relation => withinScope(relation.from) && withinScope(relation.to)); + visibleRelations.forEach(relation => { + visibleSet.add(relation.from); + visibleSet.add(relation.to); + }); + const nodeIdByFqn = new Map(); + packageDiagramNodeIdToFqn = new Map(); let nodeIndex = 0; const ensureNodeId = fqn => { if (nodeIdByFqn.has(fqn)) return nodeIdByFqn.get(fqn); const nodeId = `P${nodeIndex++}`; nodeIdByFqn.set(fqn, nodeId); + packageDiagramNodeIdToFqn.set(nodeId, fqn); lines.push(`${nodeId}["${escapeMermaidText(fqn)}"]`); + lines.push(`click ${nodeId} filterPackageDiagram`); return nodeId; }; - packages.forEach(item => { - ensureNodeId(item.fqn); - }); - relations.forEach(relation => { + Array.from(visibleSet).sort().forEach(ensureNodeId); + visibleRelations.forEach(relation => { const fromId = ensureNodeId(relation.from); const toId = ensureNodeId(relation.to); lines.push(`${fromId} --> ${toId}`); }); + diagram.removeAttribute('data-processed'); diagram.textContent = lines.join('\n'); if (window.mermaid) { - mermaid.initialize({startOnLoad: false}); + mermaid.initialize({startOnLoad: false, securityLevel: 'loose'}); mermaid.run({nodes: [diagram]}); } } +function filterPackageDiagramByFqn(fqn) { + writePackageRelationDiagram(fqn); +} + +window.filterPackageDiagram = function (nodeId) { + const fqn = packageDiagramNodeIdToFqn.get(nodeId); + if (!fqn) return; + filterPackageDiagramByFqn(fqn); +}; + // ページ読み込み時のイベント // リスナーの登録はそのページだけでやる document.addEventListener("DOMContentLoaded", function () { diff --git a/jig-core/src/main/resources/templates/package.html b/jig-core/src/main/resources/templates/package.html index d800f67e2..411bc8e24 100644 --- a/jig-core/src/main/resources/templates/package.html +++ b/jig-core/src/main/resources/templates/package.html @@ -19,7 +19,7 @@

パッケージ概要

-
+

 
     
完全修飾名
From 73cba94aa4aeb8d40ac88467dbb8538fd624dd05 Mon Sep 17 00:00:00 2001 From: irof Date: Sat, 24 Jan 2026 00:13:57 +0900 Subject: [PATCH 03/43] =?UTF-8?q?=E7=B5=9E=E3=82=8A=E8=BE=BC=E3=81=BF?= =?UTF-8?q?=E3=82=92=E6=89=8B=E5=85=A5=E5=8A=9B=E3=81=A7=E3=82=82=E3=81=A7?= =?UTF-8?q?=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/resources/templates/assets/jig.js | 29 +++++++++++++++++++ .../src/main/resources/templates/package.html | 6 ++++ 2 files changed, 35 insertions(+) diff --git a/jig-core/src/main/resources/templates/assets/jig.js b/jig-core/src/main/resources/templates/assets/jig.js index 67d40f6d0..8edb3b7a5 100644 --- a/jig-core/src/main/resources/templates/assets/jig.js +++ b/jig-core/src/main/resources/templates/assets/jig.js @@ -395,6 +395,10 @@ function writePackageRelationDiagram(filterFqn) { } function filterPackageDiagramByFqn(fqn) { + const input = document.getElementById('package-filter-input'); + if (input) { + input.value = fqn ?? ''; + } writePackageRelationDiagram(fqn); } @@ -404,6 +408,30 @@ window.filterPackageDiagram = function (nodeId) { filterPackageDiagramByFqn(fqn); }; +function setupPackageFilterInput() { + const input = document.getElementById('package-filter-input'); + const applyButton = document.getElementById('apply-package-filter'); + const clearButton = document.getElementById('clear-package-filter'); + if (!input || !applyButton || !clearButton) return; + + const applyFilter = () => { + const value = input.value.trim(); + filterPackageDiagramByFqn(value || null); + }; + + applyButton.addEventListener('click', applyFilter); + clearButton.addEventListener('click', () => { + input.value = ''; + filterPackageDiagramByFqn(null); + }); + input.addEventListener('keydown', event => { + if (event.key === 'Enter') { + event.preventDefault(); + applyFilter(); + } + }); +} + // ページ読み込み時のイベント // リスナーの登録はそのページだけでやる document.addEventListener("DOMContentLoaded", function () { @@ -422,6 +450,7 @@ document.addEventListener("DOMContentLoaded", function () { setupSortableTables(); writePackageRelationDiagram(); writePackageTable(); + setupPackageFilterInput(); } else if (document.body.classList.contains("insight")) { setupSortableTables(); setupZoomIcons(); diff --git a/jig-core/src/main/resources/templates/package.html b/jig-core/src/main/resources/templates/package.html index 411bc8e24..dd69a71f4 100644 --- a/jig-core/src/main/resources/templates/package.html +++ b/jig-core/src/main/resources/templates/package.html @@ -17,6 +17,12 @@

パッケージ概要

⚙️ 表示設定 + + +


From 3b10dfb5b7dc2c89bc67344fade3f7f9e7801e24 Mon Sep 17 00:00:00 2001
From: irof 
Date: Sat, 24 Jan 2026 00:15:31 +0900
Subject: [PATCH 04/43] =?UTF-8?q?=E3=83=91=E3=83=83=E3=82=B1=E3=83=BC?=
 =?UTF-8?q?=E3=82=B8=E6=A6=82=E8=A6=81=E3=81=AE=E9=96=A2=E9=80=A3=E5=9B=B3?=
 =?UTF-8?q?=E3=81=AE=E3=83=A9=E3=83=99=E3=83=AB=E3=82=92=E5=90=8D=E7=A7=B0?=
 =?UTF-8?q?=E3=81=AB=E5=A4=89=E6=9B=B4?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 jig-core/src/main/resources/templates/assets/jig.js | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/jig-core/src/main/resources/templates/assets/jig.js b/jig-core/src/main/resources/templates/assets/jig.js
index 8edb3b7a5..697e7f3b4 100644
--- a/jig-core/src/main/resources/templates/assets/jig.js
+++ b/jig-core/src/main/resources/templates/assets/jig.js
@@ -355,6 +355,7 @@ function writePackageRelationDiagram(filterFqn) {
 
     const {packages, relations} = readPackageSummaryData();
     const escapeMermaidText = text => text.replace(/"/g, '\\"');
+    const nameByFqn = new Map(packages.map(item => [item.fqn, item.name || item.fqn]));
     const lines = ['graph TD'];
     const scopePrefix = filterFqn ? `${filterFqn}.` : null;
     const withinScope = fqn => !filterFqn || fqn === filterFqn || fqn.startsWith(scopePrefix);
@@ -374,7 +375,8 @@ function writePackageRelationDiagram(filterFqn) {
         const nodeId = `P${nodeIndex++}`;
         nodeIdByFqn.set(fqn, nodeId);
         packageDiagramNodeIdToFqn.set(nodeId, fqn);
-        lines.push(`${nodeId}["${escapeMermaidText(fqn)}"]`);
+        const label = nameByFqn.get(fqn) || fqn;
+        lines.push(`${nodeId}["${escapeMermaidText(label)}"]`);
         lines.push(`click ${nodeId} filterPackageDiagram`);
         return nodeId;
     };

From 8dbae16e3d43f0fa45577c8b77b3b55a270f9f5e Mon Sep 17 00:00:00 2001
From: irof 
Date: Sat, 24 Jan 2026 00:26:12 +0900
Subject: [PATCH 05/43] =?UTF-8?q?=E7=9B=B8=E4=BA=92=E4=BE=9D=E5=AD=98?=
 =?UTF-8?q?=E3=81=AE=E7=9F=A2=E5=8D=B0=E3=82=92=EF=BC=91=E3=81=A4=E3=81=AB?=
 =?UTF-8?q?=E3=81=97=E3=81=A6=E8=B5=A4=E3=81=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../main/resources/templates/assets/jig.js    | 24 +++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/jig-core/src/main/resources/templates/assets/jig.js b/jig-core/src/main/resources/templates/assets/jig.js
index 697e7f3b4..edada4c4c 100644
--- a/jig-core/src/main/resources/templates/assets/jig.js
+++ b/jig-core/src/main/resources/templates/assets/jig.js
@@ -382,11 +382,35 @@ function writePackageRelationDiagram(filterFqn) {
     };
 
     Array.from(visibleSet).sort().forEach(ensureNodeId);
+    const relationKey = (from, to) => `${from}::${to}`;
+    const canonicalPairKey = (from, to) => (from < to ? `${from}::${to}` : `${to}::${from}`);
+    const relationSet = new Set(visibleRelations.map(relation => relationKey(relation.from, relation.to)));
+    const mutualPairs = new Set();
+    visibleRelations.forEach(relation => {
+        if (relationSet.has(relationKey(relation.to, relation.from))) {
+            mutualPairs.add(canonicalPairKey(relation.from, relation.to));
+        }
+    });
+
+    const linkStyles = [];
+    let linkIndex = 0;
     visibleRelations.forEach(relation => {
         const fromId = ensureNodeId(relation.from);
         const toId = ensureNodeId(relation.to);
+        const pairKey = canonicalPairKey(relation.from, relation.to);
+        if (mutualPairs.has(pairKey)) {
+            if (relation.from > relation.to) {
+                return;
+            }
+            lines.push(`${fromId} <--> ${toId}`);
+            linkStyles.push(`linkStyle ${linkIndex} stroke:red,stroke-width:2px`);
+            linkIndex += 1;
+            return;
+        }
         lines.push(`${fromId} --> ${toId}`);
+        linkIndex += 1;
     });
+    linkStyles.forEach(styleLine => lines.push(styleLine));
 
     diagram.removeAttribute('data-processed');
     diagram.textContent = lines.join('\n');

From 122cf48b274c5a46ce2cc4c12e7e8327e0c2444e Mon Sep 17 00:00:00 2001
From: irof 
Date: Sat, 24 Jan 2026 00:27:09 +0900
Subject: [PATCH 06/43] =?UTF-8?q?mermaid=E6=9B=B4=E6=96=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 jig-core/src/main/resources/templates/fragment-base.html | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/jig-core/src/main/resources/templates/fragment-base.html b/jig-core/src/main/resources/templates/fragment-base.html
index b8552f25c..0a48bd15e 100644
--- a/jig-core/src/main/resources/templates/fragment-base.html
+++ b/jig-core/src/main/resources/templates/fragment-base.html
@@ -25,7 +25,7 @@
 
 
     
-    
+    
     
 
 

From f0feca6ccd45329ef307a008a1eb7573a81b6d62 Mon Sep 17 00:00:00 2001
From: irof 
Date: Sat, 24 Jan 2026 00:35:11 +0900
Subject: [PATCH 07/43] =?UTF-8?q?=E9=9A=8E=E5=B1=A4=E9=9B=86=E7=B4=84?=
 =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../main/resources/templates/assets/jig.js    | 61 +++++++++++++++++--
 .../src/main/resources/templates/package.html |  4 ++
 2 files changed, 60 insertions(+), 5 deletions(-)

diff --git a/jig-core/src/main/resources/templates/assets/jig.js b/jig-core/src/main/resources/templates/assets/jig.js
index edada4c4c..4e619f7b4 100644
--- a/jig-core/src/main/resources/templates/assets/jig.js
+++ b/jig-core/src/main/resources/templates/assets/jig.js
@@ -287,6 +287,7 @@ function zoomFamilyTables(baseTable, baseRow) {
 
 let packageSummaryCache = null;
 let packageDiagramNodeIdToFqn = new Map();
+let currentPackageDepth = 0;
 
 function readPackageSummaryData() {
     if (packageSummaryCache) return packageSummaryCache;
@@ -300,6 +301,19 @@ function readPackageSummaryData() {
     return packageSummaryCache;
 }
 
+function packageDepthOf(fqn) {
+    if (!fqn || fqn === '(default)') return 0;
+    return fqn.split('.').length;
+}
+
+function aggregatePackageFqn(fqn, depth) {
+    if (!depth || depth <= 0) return fqn;
+    if (!fqn || fqn === '(default)') return fqn;
+    const parts = fqn.split('.');
+    if (parts.length <= depth) return fqn;
+    return parts.slice(0, depth).join('.');
+}
+
 function writePackageTable() {
     const {packages, relations} = readPackageSummaryData();
     const incomingCounts = new Map();
@@ -360,9 +374,19 @@ function writePackageRelationDiagram(filterFqn) {
     const scopePrefix = filterFqn ? `${filterFqn}.` : null;
     const withinScope = fqn => !filterFqn || fqn === filterFqn || fqn.startsWith(scopePrefix);
     const visiblePackages = packages.filter(item => withinScope(item.fqn));
-    const visibleSet = new Set(visiblePackages.map(item => item.fqn));
-    const visibleRelations = relations.filter(relation => withinScope(relation.from) && withinScope(relation.to));
+    const visibleSet = new Set(visiblePackages.map(item => aggregatePackageFqn(item.fqn, currentPackageDepth)));
+    const visibleRelations = relations.filter(relation => withinScope(relation.from) && withinScope(relation.to))
+        .map(relation => ({
+            from: aggregatePackageFqn(relation.from, currentPackageDepth),
+            to: aggregatePackageFqn(relation.to, currentPackageDepth),
+        }))
+        .filter(relation => relation.from !== relation.to);
+    const uniqueRelationMap = new Map();
     visibleRelations.forEach(relation => {
+        uniqueRelationMap.set(`${relation.from}::${relation.to}`, relation);
+    });
+    const uniqueRelations = Array.from(uniqueRelationMap.values());
+    uniqueRelations.forEach(relation => {
         visibleSet.add(relation.from);
         visibleSet.add(relation.to);
     });
@@ -384,9 +408,9 @@ function writePackageRelationDiagram(filterFqn) {
     Array.from(visibleSet).sort().forEach(ensureNodeId);
     const relationKey = (from, to) => `${from}::${to}`;
     const canonicalPairKey = (from, to) => (from < to ? `${from}::${to}` : `${to}::${from}`);
-    const relationSet = new Set(visibleRelations.map(relation => relationKey(relation.from, relation.to)));
+    const relationSet = new Set(uniqueRelations.map(relation => relationKey(relation.from, relation.to)));
     const mutualPairs = new Set();
-    visibleRelations.forEach(relation => {
+    uniqueRelations.forEach(relation => {
         if (relationSet.has(relationKey(relation.to, relation.from))) {
             mutualPairs.add(canonicalPairKey(relation.from, relation.to));
         }
@@ -394,7 +418,7 @@ function writePackageRelationDiagram(filterFqn) {
 
     const linkStyles = [];
     let linkIndex = 0;
-    visibleRelations.forEach(relation => {
+    uniqueRelations.forEach(relation => {
         const fromId = ensureNodeId(relation.from);
         const toId = ensureNodeId(relation.to);
         const pairKey = canonicalPairKey(relation.from, relation.to);
@@ -458,6 +482,32 @@ function setupPackageFilterInput() {
     });
 }
 
+function setupPackageDepthControl() {
+    const select = document.getElementById('package-depth-select');
+    if (!select) return;
+    const {packages} = readPackageSummaryData();
+    const maxDepth = packages.reduce((max, item) => Math.max(max, packageDepthOf(item.fqn)), 0);
+
+    select.innerHTML = '';
+    const noAggregationOption = document.createElement('option');
+    noAggregationOption.value = '0';
+    noAggregationOption.textContent = '集約なし';
+    select.appendChild(noAggregationOption);
+
+    for (let depth = 1; depth <= maxDepth; depth += 1) {
+        const option = document.createElement('option');
+        option.value = String(depth);
+        option.textContent = `深さ${depth}`;
+        select.appendChild(option);
+    }
+
+    select.value = String(currentPackageDepth);
+    select.addEventListener('change', () => {
+        currentPackageDepth = Number(select.value);
+        filterPackageDiagramByFqn(document.getElementById('package-filter-input')?.value.trim() || null);
+    });
+}
+
 // ページ読み込み時のイベント
 // リスナーの登録はそのページだけでやる
 document.addEventListener("DOMContentLoaded", function () {
@@ -477,6 +527,7 @@ document.addEventListener("DOMContentLoaded", function () {
         writePackageRelationDiagram();
         writePackageTable();
         setupPackageFilterInput();
+        setupPackageDepthControl();
     } else if (document.body.classList.contains("insight")) {
         setupSortableTables();
         setupZoomIcons();
diff --git a/jig-core/src/main/resources/templates/package.html b/jig-core/src/main/resources/templates/package.html
index dd69a71f4..63838a220 100644
--- a/jig-core/src/main/resources/templates/package.html
+++ b/jig-core/src/main/resources/templates/package.html
@@ -23,6 +23,10 @@ 

パッケージ概要

+


From 85167d0915b954ec2aea08a93a355add601e2bc9 Mon Sep 17 00:00:00 2001
From: irof 
Date: Sat, 24 Jan 2026 00:42:03 +0900
Subject: [PATCH 08/43] =?UTF-8?q?=E3=83=91=E3=83=83=E3=82=B1=E3=83=BC?=
 =?UTF-8?q?=E3=82=B8=E6=A6=82=E8=A6=81=E3=81=AE=E3=82=AF=E3=83=AA=E3=83=83?=
 =?UTF-8?q?=E3=82=AF=E6=99=82=E3=81=AE=E3=83=95=E3=82=A3=E3=83=AB=E3=82=BF?=
 =?UTF-8?q?=E3=82=92=E5=A4=89=E6=9B=B4?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

クリックしたパッケージを起点に関連するものにする
---
 .../main/resources/templates/assets/jig.js    | 57 ++++++++++++++-----
 1 file changed, 44 insertions(+), 13 deletions(-)

diff --git a/jig-core/src/main/resources/templates/assets/jig.js b/jig-core/src/main/resources/templates/assets/jig.js
index 4e619f7b4..53463c245 100644
--- a/jig-core/src/main/resources/templates/assets/jig.js
+++ b/jig-core/src/main/resources/templates/assets/jig.js
@@ -288,6 +288,7 @@ function zoomFamilyTables(baseTable, baseRow) {
 let packageSummaryCache = null;
 let packageDiagramNodeIdToFqn = new Map();
 let currentPackageDepth = 0;
+let currentPackageFilterMode = 'scope';
 
 function readPackageSummaryData() {
     if (packageSummaryCache) return packageSummaryCache;
@@ -363,7 +364,7 @@ function writePackageTable() {
     });
 }
 
-function writePackageRelationDiagram(filterFqn) {
+function writePackageRelationDiagram(filterFqn, mode) {
     const diagram = document.getElementById('package-relation-diagram');
     if (!diagram) return;
 
@@ -371,11 +372,18 @@ function writePackageRelationDiagram(filterFqn) {
     const escapeMermaidText = text => text.replace(/"/g, '\\"');
     const nameByFqn = new Map(packages.map(item => [item.fqn, item.name || item.fqn]));
     const lines = ['graph TD'];
+    const aggregatedRoot = filterFqn ? aggregatePackageFqn(filterFqn, currentPackageDepth) : null;
     const scopePrefix = filterFqn ? `${filterFqn}.` : null;
     const withinScope = fqn => !filterFqn || fqn === filterFqn || fqn.startsWith(scopePrefix);
-    const visiblePackages = packages.filter(item => withinScope(item.fqn));
+    const visiblePackages = mode === 'scope'
+        ? packages.filter(item => withinScope(item.fqn))
+        : packages;
     const visibleSet = new Set(visiblePackages.map(item => aggregatePackageFqn(item.fqn, currentPackageDepth)));
-    const visibleRelations = relations.filter(relation => withinScope(relation.from) && withinScope(relation.to))
+    const filteredRelations = relations.filter(relation => {
+        if (mode !== 'scope') return true;
+        return withinScope(relation.from) && withinScope(relation.to);
+    });
+    const visibleRelations = filteredRelations
         .map(relation => ({
             from: aggregatePackageFqn(relation.from, currentPackageDepth),
             to: aggregatePackageFqn(relation.to, currentPackageDepth),
@@ -385,7 +393,29 @@ function writePackageRelationDiagram(filterFqn) {
     visibleRelations.forEach(relation => {
         uniqueRelationMap.set(`${relation.from}::${relation.to}`, relation);
     });
-    const uniqueRelations = Array.from(uniqueRelationMap.values());
+    let uniqueRelations = Array.from(uniqueRelationMap.values());
+
+    if (aggregatedRoot && mode === 'related') {
+        const relatedSet = new Set([aggregatedRoot]);
+        uniqueRelations.forEach(relation => {
+            if (relation.from === aggregatedRoot) relatedSet.add(relation.to);
+            if (relation.to === aggregatedRoot) relatedSet.add(relation.from);
+        });
+        uniqueRelations = uniqueRelations.filter(relation =>
+            relation.from === aggregatedRoot || relation.to === aggregatedRoot
+        );
+        visibleSet.clear();
+        relatedSet.forEach(value => visibleSet.add(value));
+    } else if (aggregatedRoot && mode === 'scope') {
+        const scopedSet = new Set();
+        visibleSet.forEach(value => {
+            if (value === aggregatedRoot || value.startsWith(`${aggregatedRoot}.`)) {
+                scopedSet.add(value);
+            }
+        });
+        visibleSet.clear();
+        scopedSet.forEach(value => visibleSet.add(value));
+    }
     uniqueRelations.forEach(relation => {
         visibleSet.add(relation.from);
         visibleSet.add(relation.to);
@@ -445,11 +475,8 @@ function writePackageRelationDiagram(filterFqn) {
 }
 
 function filterPackageDiagramByFqn(fqn) {
-    const input = document.getElementById('package-filter-input');
-    if (input) {
-        input.value = fqn ?? '';
-    }
-    writePackageRelationDiagram(fqn);
+    currentPackageFilterMode = 'related';
+    writePackageRelationDiagram(fqn, currentPackageFilterMode);
 }
 
 window.filterPackageDiagram = function (nodeId) {
@@ -466,13 +493,15 @@ function setupPackageFilterInput() {
 
     const applyFilter = () => {
         const value = input.value.trim();
-        filterPackageDiagramByFqn(value || null);
+        currentPackageFilterMode = 'scope';
+        writePackageRelationDiagram(value || null, currentPackageFilterMode);
     };
 
     applyButton.addEventListener('click', applyFilter);
     clearButton.addEventListener('click', () => {
         input.value = '';
-        filterPackageDiagramByFqn(null);
+        currentPackageFilterMode = 'scope';
+        writePackageRelationDiagram(null, currentPackageFilterMode);
     });
     input.addEventListener('keydown', event => {
         if (event.key === 'Enter') {
@@ -504,7 +533,8 @@ function setupPackageDepthControl() {
     select.value = String(currentPackageDepth);
     select.addEventListener('change', () => {
         currentPackageDepth = Number(select.value);
-        filterPackageDiagramByFqn(document.getElementById('package-filter-input')?.value.trim() || null);
+        const value = document.getElementById('package-filter-input')?.value.trim() || null;
+        writePackageRelationDiagram(value, currentPackageFilterMode);
     });
 }
 
@@ -524,7 +554,8 @@ document.addEventListener("DOMContentLoaded", function () {
     } else if (document.body.classList.contains("package-list")) {
         document.getElementById("toggle-description-btn").addEventListener("click", toggleDescription);
         setupSortableTables();
-        writePackageRelationDiagram();
+        currentPackageFilterMode = 'scope';
+        writePackageRelationDiagram(null, currentPackageFilterMode);
         writePackageTable();
         setupPackageFilterInput();
         setupPackageDepthControl();

From 1833932b0dad9c3c7c29065ee3b3671cad72ae49 Mon Sep 17 00:00:00 2001
From: irof 
Date: Sat, 24 Jan 2026 00:44:27 +0900
Subject: [PATCH 09/43] =?UTF-8?q?=E3=83=86=E3=83=BC=E3=83=96=E3=83=AB?=
 =?UTF-8?q?=E3=81=AE=E3=82=AF=E3=83=AA=E3=83=83=E3=82=AF=E3=81=A7=E3=81=AE?=
 =?UTF-8?q?=E3=83=80=E3=82=A4=E3=82=A2=E3=82=B0=E3=83=A9=E3=83=A0=E6=9B=B8?=
 =?UTF-8?q?=E3=81=8D=E6=8F=9B=E3=81=88=E3=82=92=E3=82=84=E3=82=81=E3=81=9F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 jig-core/src/main/resources/templates/assets/jig.js | 1 -
 1 file changed, 1 deletion(-)

diff --git a/jig-core/src/main/resources/templates/assets/jig.js b/jig-core/src/main/resources/templates/assets/jig.js
index 53463c245..12f051292 100644
--- a/jig-core/src/main/resources/templates/assets/jig.js
+++ b/jig-core/src/main/resources/templates/assets/jig.js
@@ -333,7 +333,6 @@ function writePackageTable() {
         const fqnTd = document.createElement('td');
         fqnTd.textContent = item.fqn;
         fqnTd.className = 'fqn';
-        fqnTd.addEventListener('click', () => filterPackageDiagramByFqn(item.fqn));
         tr.appendChild(fqnTd);
 
         const nameTd = document.createElement('td');

From 3d86e18e812bccb396493bbebf5df4dcb9d55be9 Mon Sep 17 00:00:00 2001
From: irof 
Date: Sat, 24 Jan 2026 21:48:55 +0900
Subject: [PATCH 10/43] =?UTF-8?q?jig.js=E3=81=AE=E5=88=86=E5=89=B2?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

パッケージ概要でのみ使用しているスクリプトを抽出した
---
 .../main/resources/templates/assets/jig.js    | 260 -----------------
 .../resources/templates/assets/package.js     | 262 ++++++++++++++++++
 .../src/main/resources/templates/package.html |   1 +
 3 files changed, 263 insertions(+), 260 deletions(-)
 create mode 100644 jig-core/src/main/resources/templates/assets/package.js

diff --git a/jig-core/src/main/resources/templates/assets/jig.js b/jig-core/src/main/resources/templates/assets/jig.js
index 12f051292..0ec18d345 100644
--- a/jig-core/src/main/resources/templates/assets/jig.js
+++ b/jig-core/src/main/resources/templates/assets/jig.js
@@ -285,258 +285,6 @@ function zoomFamilyTables(baseTable, baseRow) {
     })
 }
 
-let packageSummaryCache = null;
-let packageDiagramNodeIdToFqn = new Map();
-let currentPackageDepth = 0;
-let currentPackageFilterMode = 'scope';
-
-function readPackageSummaryData() {
-    if (packageSummaryCache) return packageSummaryCache;
-    const jsonText = document.getElementById('package-data').textContent;
-    /** @type {{packages?: Array<{fqn: string, name: string, classCount: number, description: string}>, relations?: Array<{from: string, to: string}>} | Array<{fqn: string, name: string, classCount: number, description: string}>} */
-    const packageData = JSON.parse(jsonText);
-    packageSummaryCache = {
-        packages: Array.isArray(packageData) ? packageData : (packageData.packages ?? []),
-        relations: Array.isArray(packageData) ? [] : (packageData.relations ?? []),
-    };
-    return packageSummaryCache;
-}
-
-function packageDepthOf(fqn) {
-    if (!fqn || fqn === '(default)') return 0;
-    return fqn.split('.').length;
-}
-
-function aggregatePackageFqn(fqn, depth) {
-    if (!depth || depth <= 0) return fqn;
-    if (!fqn || fqn === '(default)') return fqn;
-    const parts = fqn.split('.');
-    if (parts.length <= depth) return fqn;
-    return parts.slice(0, depth).join('.');
-}
-
-function writePackageTable() {
-    const {packages, relations} = readPackageSummaryData();
-    const incomingCounts = new Map();
-    const outgoingCounts = new Map();
-    relations.forEach(relation => {
-        outgoingCounts.set(relation.from, (outgoingCounts.get(relation.from) ?? 0) + 1);
-        incomingCounts.set(relation.to, (incomingCounts.get(relation.to) ?? 0) + 1);
-    });
-
-    const tbody = document.querySelector('#package-table tbody');
-    //tbody.innerHTML = '';
-
-    packages.forEach(item => {
-        const tr = document.createElement('tr');
-
-        const fqnTd = document.createElement('td');
-        fqnTd.textContent = item.fqn;
-        fqnTd.className = 'fqn';
-        tr.appendChild(fqnTd);
-
-        const nameTd = document.createElement('td');
-        nameTd.textContent = item.name;
-        tr.appendChild(nameTd);
-
-        const classCountTd = document.createElement('td');
-        classCountTd.textContent = String(item.classCount);
-        classCountTd.className = 'number';
-        tr.appendChild(classCountTd);
-
-        const incomingCountTd = document.createElement('td');
-        incomingCountTd.textContent = String(incomingCounts.get(item.fqn) ?? 0);
-        incomingCountTd.className = 'number';
-        tr.appendChild(incomingCountTd);
-
-        const outgoingCountTd = document.createElement('td');
-        outgoingCountTd.textContent = String(outgoingCounts.get(item.fqn) ?? 0);
-        outgoingCountTd.className = 'number';
-        tr.appendChild(outgoingCountTd);
-
-        const descTd = document.createElement('td');
-        descTd.textContent = item.description;
-        descTd.className = 'description hidden markdown';
-        tr.appendChild(descTd);
-
-        tbody.appendChild(tr);
-    });
-}
-
-function writePackageRelationDiagram(filterFqn, mode) {
-    const diagram = document.getElementById('package-relation-diagram');
-    if (!diagram) return;
-
-    const {packages, relations} = readPackageSummaryData();
-    const escapeMermaidText = text => text.replace(/"/g, '\\"');
-    const nameByFqn = new Map(packages.map(item => [item.fqn, item.name || item.fqn]));
-    const lines = ['graph TD'];
-    const aggregatedRoot = filterFqn ? aggregatePackageFqn(filterFqn, currentPackageDepth) : null;
-    const scopePrefix = filterFqn ? `${filterFqn}.` : null;
-    const withinScope = fqn => !filterFqn || fqn === filterFqn || fqn.startsWith(scopePrefix);
-    const visiblePackages = mode === 'scope'
-        ? packages.filter(item => withinScope(item.fqn))
-        : packages;
-    const visibleSet = new Set(visiblePackages.map(item => aggregatePackageFqn(item.fqn, currentPackageDepth)));
-    const filteredRelations = relations.filter(relation => {
-        if (mode !== 'scope') return true;
-        return withinScope(relation.from) && withinScope(relation.to);
-    });
-    const visibleRelations = filteredRelations
-        .map(relation => ({
-            from: aggregatePackageFqn(relation.from, currentPackageDepth),
-            to: aggregatePackageFqn(relation.to, currentPackageDepth),
-        }))
-        .filter(relation => relation.from !== relation.to);
-    const uniqueRelationMap = new Map();
-    visibleRelations.forEach(relation => {
-        uniqueRelationMap.set(`${relation.from}::${relation.to}`, relation);
-    });
-    let uniqueRelations = Array.from(uniqueRelationMap.values());
-
-    if (aggregatedRoot && mode === 'related') {
-        const relatedSet = new Set([aggregatedRoot]);
-        uniqueRelations.forEach(relation => {
-            if (relation.from === aggregatedRoot) relatedSet.add(relation.to);
-            if (relation.to === aggregatedRoot) relatedSet.add(relation.from);
-        });
-        uniqueRelations = uniqueRelations.filter(relation =>
-            relation.from === aggregatedRoot || relation.to === aggregatedRoot
-        );
-        visibleSet.clear();
-        relatedSet.forEach(value => visibleSet.add(value));
-    } else if (aggregatedRoot && mode === 'scope') {
-        const scopedSet = new Set();
-        visibleSet.forEach(value => {
-            if (value === aggregatedRoot || value.startsWith(`${aggregatedRoot}.`)) {
-                scopedSet.add(value);
-            }
-        });
-        visibleSet.clear();
-        scopedSet.forEach(value => visibleSet.add(value));
-    }
-    uniqueRelations.forEach(relation => {
-        visibleSet.add(relation.from);
-        visibleSet.add(relation.to);
-    });
-
-    const nodeIdByFqn = new Map();
-    packageDiagramNodeIdToFqn = new Map();
-    let nodeIndex = 0;
-    const ensureNodeId = fqn => {
-        if (nodeIdByFqn.has(fqn)) return nodeIdByFqn.get(fqn);
-        const nodeId = `P${nodeIndex++}`;
-        nodeIdByFqn.set(fqn, nodeId);
-        packageDiagramNodeIdToFqn.set(nodeId, fqn);
-        const label = nameByFqn.get(fqn) || fqn;
-        lines.push(`${nodeId}["${escapeMermaidText(label)}"]`);
-        lines.push(`click ${nodeId} filterPackageDiagram`);
-        return nodeId;
-    };
-
-    Array.from(visibleSet).sort().forEach(ensureNodeId);
-    const relationKey = (from, to) => `${from}::${to}`;
-    const canonicalPairKey = (from, to) => (from < to ? `${from}::${to}` : `${to}::${from}`);
-    const relationSet = new Set(uniqueRelations.map(relation => relationKey(relation.from, relation.to)));
-    const mutualPairs = new Set();
-    uniqueRelations.forEach(relation => {
-        if (relationSet.has(relationKey(relation.to, relation.from))) {
-            mutualPairs.add(canonicalPairKey(relation.from, relation.to));
-        }
-    });
-
-    const linkStyles = [];
-    let linkIndex = 0;
-    uniqueRelations.forEach(relation => {
-        const fromId = ensureNodeId(relation.from);
-        const toId = ensureNodeId(relation.to);
-        const pairKey = canonicalPairKey(relation.from, relation.to);
-        if (mutualPairs.has(pairKey)) {
-            if (relation.from > relation.to) {
-                return;
-            }
-            lines.push(`${fromId} <--> ${toId}`);
-            linkStyles.push(`linkStyle ${linkIndex} stroke:red,stroke-width:2px`);
-            linkIndex += 1;
-            return;
-        }
-        lines.push(`${fromId} --> ${toId}`);
-        linkIndex += 1;
-    });
-    linkStyles.forEach(styleLine => lines.push(styleLine));
-
-    diagram.removeAttribute('data-processed');
-    diagram.textContent = lines.join('\n');
-    if (window.mermaid) {
-        mermaid.initialize({startOnLoad: false, securityLevel: 'loose'});
-        mermaid.run({nodes: [diagram]});
-    }
-}
-
-function filterPackageDiagramByFqn(fqn) {
-    currentPackageFilterMode = 'related';
-    writePackageRelationDiagram(fqn, currentPackageFilterMode);
-}
-
-window.filterPackageDiagram = function (nodeId) {
-    const fqn = packageDiagramNodeIdToFqn.get(nodeId);
-    if (!fqn) return;
-    filterPackageDiagramByFqn(fqn);
-};
-
-function setupPackageFilterInput() {
-    const input = document.getElementById('package-filter-input');
-    const applyButton = document.getElementById('apply-package-filter');
-    const clearButton = document.getElementById('clear-package-filter');
-    if (!input || !applyButton || !clearButton) return;
-
-    const applyFilter = () => {
-        const value = input.value.trim();
-        currentPackageFilterMode = 'scope';
-        writePackageRelationDiagram(value || null, currentPackageFilterMode);
-    };
-
-    applyButton.addEventListener('click', applyFilter);
-    clearButton.addEventListener('click', () => {
-        input.value = '';
-        currentPackageFilterMode = 'scope';
-        writePackageRelationDiagram(null, currentPackageFilterMode);
-    });
-    input.addEventListener('keydown', event => {
-        if (event.key === 'Enter') {
-            event.preventDefault();
-            applyFilter();
-        }
-    });
-}
-
-function setupPackageDepthControl() {
-    const select = document.getElementById('package-depth-select');
-    if (!select) return;
-    const {packages} = readPackageSummaryData();
-    const maxDepth = packages.reduce((max, item) => Math.max(max, packageDepthOf(item.fqn)), 0);
-
-    select.innerHTML = '';
-    const noAggregationOption = document.createElement('option');
-    noAggregationOption.value = '0';
-    noAggregationOption.textContent = '集約なし';
-    select.appendChild(noAggregationOption);
-
-    for (let depth = 1; depth <= maxDepth; depth += 1) {
-        const option = document.createElement('option');
-        option.value = String(depth);
-        option.textContent = `深さ${depth}`;
-        select.appendChild(option);
-    }
-
-    select.value = String(currentPackageDepth);
-    select.addEventListener('change', () => {
-        currentPackageDepth = Number(select.value);
-        const value = document.getElementById('package-filter-input')?.value.trim() || null;
-        writePackageRelationDiagram(value, currentPackageFilterMode);
-    });
-}
-
 // ページ読み込み時のイベント
 // リスナーの登録はそのページだけでやる
 document.addEventListener("DOMContentLoaded", function () {
@@ -550,14 +298,6 @@ document.addEventListener("DOMContentLoaded", function () {
         document.getElementById("show-letter-navigation").addEventListener("change", updateLetterNavigationVisibility);
 
         updateLetterNavigationVisibility();
-    } else if (document.body.classList.contains("package-list")) {
-        document.getElementById("toggle-description-btn").addEventListener("click", toggleDescription);
-        setupSortableTables();
-        currentPackageFilterMode = 'scope';
-        writePackageRelationDiagram(null, currentPackageFilterMode);
-        writePackageTable();
-        setupPackageFilterInput();
-        setupPackageDepthControl();
     } else if (document.body.classList.contains("insight")) {
         setupSortableTables();
         setupZoomIcons();
diff --git a/jig-core/src/main/resources/templates/assets/package.js b/jig-core/src/main/resources/templates/assets/package.js
new file mode 100644
index 000000000..f33d7bd6b
--- /dev/null
+++ b/jig-core/src/main/resources/templates/assets/package.js
@@ -0,0 +1,262 @@
+let packageSummaryCache = null;
+let packageDiagramNodeIdToFqn = new Map();
+let currentPackageDepth = 0;
+let currentPackageFilterMode = 'scope';
+
+function readPackageSummaryData() {
+    if (packageSummaryCache) return packageSummaryCache;
+    const jsonText = document.getElementById('package-data').textContent;
+    /** @type {{packages?: Array<{fqn: string, name: string, classCount: number, description: string}>, relations?: Array<{from: string, to: string}>} | Array<{fqn: string, name: string, classCount: number, description: string}>} */
+    const packageData = JSON.parse(jsonText);
+    packageSummaryCache = {
+        packages: Array.isArray(packageData) ? packageData : (packageData.packages ?? []),
+        relations: Array.isArray(packageData) ? [] : (packageData.relations ?? []),
+    };
+    return packageSummaryCache;
+}
+
+function packageDepthOf(fqn) {
+    if (!fqn || fqn === '(default)') return 0;
+    return fqn.split('.').length;
+}
+
+function aggregatePackageFqn(fqn, depth) {
+    if (!depth || depth <= 0) return fqn;
+    if (!fqn || fqn === '(default)') return fqn;
+    const parts = fqn.split('.');
+    if (parts.length <= depth) return fqn;
+    return parts.slice(0, depth).join('.');
+}
+
+function writePackageTable() {
+    const {packages, relations} = readPackageSummaryData();
+    const incomingCounts = new Map();
+    const outgoingCounts = new Map();
+    relations.forEach(relation => {
+        outgoingCounts.set(relation.from, (outgoingCounts.get(relation.from) ?? 0) + 1);
+        incomingCounts.set(relation.to, (incomingCounts.get(relation.to) ?? 0) + 1);
+    });
+
+    const tbody = document.querySelector('#package-table tbody');
+    //tbody.innerHTML = '';
+
+    packages.forEach(item => {
+        const tr = document.createElement('tr');
+
+        const fqnTd = document.createElement('td');
+        fqnTd.textContent = item.fqn;
+        fqnTd.className = 'fqn';
+        tr.appendChild(fqnTd);
+
+        const nameTd = document.createElement('td');
+        nameTd.textContent = item.name;
+        tr.appendChild(nameTd);
+
+        const classCountTd = document.createElement('td');
+        classCountTd.textContent = String(item.classCount);
+        classCountTd.className = 'number';
+        tr.appendChild(classCountTd);
+
+        const incomingCountTd = document.createElement('td');
+        incomingCountTd.textContent = String(incomingCounts.get(item.fqn) ?? 0);
+        incomingCountTd.className = 'number';
+        tr.appendChild(incomingCountTd);
+
+        const outgoingCountTd = document.createElement('td');
+        outgoingCountTd.textContent = String(outgoingCounts.get(item.fqn) ?? 0);
+        outgoingCountTd.className = 'number';
+        tr.appendChild(outgoingCountTd);
+
+        const descTd = document.createElement('td');
+        descTd.textContent = item.description;
+        descTd.className = 'description hidden markdown';
+        tr.appendChild(descTd);
+
+        tbody.appendChild(tr);
+    });
+}
+
+function writePackageRelationDiagram(filterFqn, mode) {
+    const diagram = document.getElementById('package-relation-diagram');
+    if (!diagram) return;
+
+    const {packages, relations} = readPackageSummaryData();
+    const escapeMermaidText = text => text.replace(/"/g, '\\"');
+    const nameByFqn = new Map(packages.map(item => [item.fqn, item.name || item.fqn]));
+    const lines = ['graph TD'];
+    const aggregatedRoot = filterFqn ? aggregatePackageFqn(filterFqn, currentPackageDepth) : null;
+    const scopePrefix = filterFqn ? `${filterFqn}.` : null;
+    const withinScope = fqn => !filterFqn || fqn === filterFqn || fqn.startsWith(scopePrefix);
+    const visiblePackages = mode === 'scope'
+        ? packages.filter(item => withinScope(item.fqn))
+        : packages;
+    const visibleSet = new Set(visiblePackages.map(item => aggregatePackageFqn(item.fqn, currentPackageDepth)));
+    const filteredRelations = relations.filter(relation => {
+        if (mode !== 'scope') return true;
+        return withinScope(relation.from) && withinScope(relation.to);
+    });
+    const visibleRelations = filteredRelations
+        .map(relation => ({
+            from: aggregatePackageFqn(relation.from, currentPackageDepth),
+            to: aggregatePackageFqn(relation.to, currentPackageDepth),
+        }))
+        .filter(relation => relation.from !== relation.to);
+    const uniqueRelationMap = new Map();
+    visibleRelations.forEach(relation => {
+        uniqueRelationMap.set(`${relation.from}::${relation.to}`, relation);
+    });
+    let uniqueRelations = Array.from(uniqueRelationMap.values());
+
+    if (aggregatedRoot && mode === 'related') {
+        const relatedSet = new Set([aggregatedRoot]);
+        uniqueRelations.forEach(relation => {
+            if (relation.from === aggregatedRoot) relatedSet.add(relation.to);
+            if (relation.to === aggregatedRoot) relatedSet.add(relation.from);
+        });
+        uniqueRelations = uniqueRelations.filter(relation =>
+            relation.from === aggregatedRoot || relation.to === aggregatedRoot
+        );
+        visibleSet.clear();
+        relatedSet.forEach(value => visibleSet.add(value));
+    } else if (aggregatedRoot && mode === 'scope') {
+        const scopedSet = new Set();
+        visibleSet.forEach(value => {
+            if (value === aggregatedRoot || value.startsWith(`${aggregatedRoot}.`)) {
+                scopedSet.add(value);
+            }
+        });
+        visibleSet.clear();
+        scopedSet.forEach(value => visibleSet.add(value));
+    }
+    uniqueRelations.forEach(relation => {
+        visibleSet.add(relation.from);
+        visibleSet.add(relation.to);
+    });
+
+    const nodeIdByFqn = new Map();
+    packageDiagramNodeIdToFqn = new Map();
+    let nodeIndex = 0;
+    const ensureNodeId = fqn => {
+        if (nodeIdByFqn.has(fqn)) return nodeIdByFqn.get(fqn);
+        const nodeId = `P${nodeIndex++}`;
+        nodeIdByFqn.set(fqn, nodeId);
+        packageDiagramNodeIdToFqn.set(nodeId, fqn);
+        const label = nameByFqn.get(fqn) || fqn;
+        lines.push(`${nodeId}["${escapeMermaidText(label)}"]`);
+        lines.push(`click ${nodeId} filterPackageDiagram`);
+        return nodeId;
+    };
+
+    Array.from(visibleSet).sort().forEach(ensureNodeId);
+    const relationKey = (from, to) => `${from}::${to}`;
+    const canonicalPairKey = (from, to) => (from < to ? `${from}::${to}` : `${to}::${from}`);
+    const relationSet = new Set(uniqueRelations.map(relation => relationKey(relation.from, relation.to)));
+    const mutualPairs = new Set();
+    uniqueRelations.forEach(relation => {
+        if (relationSet.has(relationKey(relation.to, relation.from))) {
+            mutualPairs.add(canonicalPairKey(relation.from, relation.to));
+        }
+    });
+
+    const linkStyles = [];
+    let linkIndex = 0;
+    uniqueRelations.forEach(relation => {
+        const fromId = ensureNodeId(relation.from);
+        const toId = ensureNodeId(relation.to);
+        const pairKey = canonicalPairKey(relation.from, relation.to);
+        if (mutualPairs.has(pairKey)) {
+            if (relation.from > relation.to) {
+                return;
+            }
+            lines.push(`${fromId} <--> ${toId}`);
+            linkStyles.push(`linkStyle ${linkIndex} stroke:red,stroke-width:2px`);
+            linkIndex += 1;
+            return;
+        }
+        lines.push(`${fromId} --> ${toId}`);
+        linkIndex += 1;
+    });
+    linkStyles.forEach(styleLine => lines.push(styleLine));
+
+    diagram.removeAttribute('data-processed');
+    diagram.textContent = lines.join('\n');
+    if (window.mermaid) {
+        mermaid.initialize({startOnLoad: false, securityLevel: 'loose'});
+        mermaid.run({nodes: [diagram]});
+    }
+}
+
+function filterPackageDiagramByFqn(fqn) {
+    currentPackageFilterMode = 'related';
+    writePackageRelationDiagram(fqn, currentPackageFilterMode);
+}
+
+window.filterPackageDiagram = function (nodeId) {
+    const fqn = packageDiagramNodeIdToFqn.get(nodeId);
+    if (!fqn) return;
+    filterPackageDiagramByFqn(fqn);
+};
+
+function setupPackageFilterInput() {
+    const input = document.getElementById('package-filter-input');
+    const applyButton = document.getElementById('apply-package-filter');
+    const clearButton = document.getElementById('clear-package-filter');
+    if (!input || !applyButton || !clearButton) return;
+
+    const applyFilter = () => {
+        const value = input.value.trim();
+        currentPackageFilterMode = 'scope';
+        writePackageRelationDiagram(value || null, currentPackageFilterMode);
+    };
+
+    applyButton.addEventListener('click', applyFilter);
+    clearButton.addEventListener('click', () => {
+        input.value = '';
+        currentPackageFilterMode = 'scope';
+        writePackageRelationDiagram(null, currentPackageFilterMode);
+    });
+    input.addEventListener('keydown', event => {
+        if (event.key === 'Enter') {
+            event.preventDefault();
+            applyFilter();
+        }
+    });
+}
+
+function setupPackageDepthControl() {
+    const select = document.getElementById('package-depth-select');
+    if (!select) return;
+    const {packages} = readPackageSummaryData();
+    const maxDepth = packages.reduce((max, item) => Math.max(max, packageDepthOf(item.fqn)), 0);
+
+    select.innerHTML = '';
+    const noAggregationOption = document.createElement('option');
+    noAggregationOption.value = '0';
+    noAggregationOption.textContent = '集約なし';
+    select.appendChild(noAggregationOption);
+
+    for (let depth = 1; depth <= maxDepth; depth += 1) {
+        const option = document.createElement('option');
+        option.value = String(depth);
+        option.textContent = `深さ${depth}`;
+        select.appendChild(option);
+    }
+
+    select.value = String(currentPackageDepth);
+    select.addEventListener('change', () => {
+        currentPackageDepth = Number(select.value);
+        const value = document.getElementById('package-filter-input')?.value.trim() || null;
+        writePackageRelationDiagram(value, currentPackageFilterMode);
+    });
+}
+
+document.addEventListener("DOMContentLoaded", function () {
+    if (!document.body.classList.contains("package-list")) return;
+    document.getElementById("toggle-description-btn").addEventListener("click", toggleDescription);
+    setupSortableTables();
+    currentPackageFilterMode = 'scope';
+    writePackageRelationDiagram(null, currentPackageFilterMode);
+    writePackageTable();
+    setupPackageFilterInput();
+    setupPackageDepthControl();
+});
diff --git a/jig-core/src/main/resources/templates/package.html b/jig-core/src/main/resources/templates/package.html
index 63838a220..50c0fd701 100644
--- a/jig-core/src/main/resources/templates/package.html
+++ b/jig-core/src/main/resources/templates/package.html
@@ -73,6 +73,7 @@ 

パッケージ概要

+ From 939038fded8ffe301f06a106f1277ea720b36ed9 Mon Sep 17 00:00:00 2001 From: irof Date: Sat, 24 Jan 2026 22:05:39 +0900 Subject: [PATCH 11/43] =?UTF-8?q?=E8=BF=BD=E5=8A=A0=E3=82=B9=E3=82=AF?= =?UTF-8?q?=E3=83=AA=E3=83=97=E3=83=88=E3=81=AFreplace=E3=81=AE=E5=A4=96?= =?UTF-8?q?=E3=81=A7=E8=AA=AD=E3=81=BE=E3=81=9B=E3=82=8B=E5=BF=85=E8=A6=81?= =?UTF-8?q?=E3=81=8C=E3=81=82=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jig-core/src/main/resources/templates/package.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jig-core/src/main/resources/templates/package.html b/jig-core/src/main/resources/templates/package.html index 50c0fd701..89981512a 100644 --- a/jig-core/src/main/resources/templates/package.html +++ b/jig-core/src/main/resources/templates/package.html @@ -71,9 +71,9 @@

パッケージ概要

- + - + From 4edde945091d73ae223a41c41a9f8b80e211cac2 Mon Sep 17 00:00:00 2001 From: irof Date: Sat, 24 Jan 2026 22:08:58 +0900 Subject: [PATCH 12/43] =?UTF-8?q?=E5=88=86=E5=89=B2=E3=81=97=E3=81=9F?= =?UTF-8?q?=E3=82=B9=E3=82=AF=E3=83=AA=E3=83=97=E3=83=88=E3=82=82=E3=82=B3?= =?UTF-8?q?=E3=83=94=E3=83=BC=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/dddjava/jig/adapter/JigDocumentGenerator.java | 1 + 1 file changed, 1 insertion(+) diff --git a/jig-core/src/main/java/org/dddjava/jig/adapter/JigDocumentGenerator.java b/jig-core/src/main/java/org/dddjava/jig/adapter/JigDocumentGenerator.java index ee82990cc..36f634f29 100644 --- a/jig-core/src/main/java/org/dddjava/jig/adapter/JigDocumentGenerator.java +++ b/jig-core/src/main/java/org/dddjava/jig/adapter/JigDocumentGenerator.java @@ -154,6 +154,7 @@ private void generateAssets() { Files.createDirectories(assetsPath); copyAsset("style.css", assetsPath); copyAsset("jig.js", assetsPath); + copyAsset("package.js", assetsPath); copyAsset("favicon.ico", assetsPath); } catch (IOException e) { throw new UncheckedIOException(e); From ce751e8b80273f5a647d4e614dad4dde43bfe9bc Mon Sep 17 00:00:00 2001 From: irof Date: Sat, 24 Jan 2026 22:16:08 +0900 Subject: [PATCH 13/43] =?UTF-8?q?=E3=82=B3=E3=83=A1=E3=83=B3=E3=83=88?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/dddjava/jig/adapter/JigDocumentGenerator.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jig-core/src/main/java/org/dddjava/jig/adapter/JigDocumentGenerator.java b/jig-core/src/main/java/org/dddjava/jig/adapter/JigDocumentGenerator.java index 36f634f29..84ae12c47 100644 --- a/jig-core/src/main/java/org/dddjava/jig/adapter/JigDocumentGenerator.java +++ b/jig-core/src/main/java/org/dddjava/jig/adapter/JigDocumentGenerator.java @@ -154,8 +154,10 @@ private void generateAssets() { Files.createDirectories(assetsPath); copyAsset("style.css", assetsPath); copyAsset("jig.js", assetsPath); - copyAsset("package.js", assetsPath); copyAsset("favicon.ico", assetsPath); + // ページごとのスクリプトを追加する + // 増えるごとにここに追加しなきゃいけないのはいかがなものか + copyAsset("package.js", assetsPath); } catch (IOException e) { throw new UncheckedIOException(e); } From d484c51dc625d413105ba4d9f29a41d9ef8c0e27 Mon Sep 17 00:00:00 2001 From: irof Date: Sat, 24 Jan 2026 22:24:29 +0900 Subject: [PATCH 14/43] =?UTF-8?q?=E3=82=A8=E3=83=A9=E3=83=BC=E6=99=82?= =?UTF-8?q?=E3=81=AB=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8=E3=82=92?= =?UTF-8?q?=E5=87=BA=E3=81=99=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/templates/assets/package.js | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/jig-core/src/main/resources/templates/assets/package.js b/jig-core/src/main/resources/templates/assets/package.js index f33d7bd6b..05258f14e 100644 --- a/jig-core/src/main/resources/templates/assets/package.js +++ b/jig-core/src/main/resources/templates/assets/package.js @@ -2,6 +2,40 @@ let packageSummaryCache = null; let packageDiagramNodeIdToFqn = new Map(); let currentPackageDepth = 0; let currentPackageFilterMode = 'scope'; +let currentDiagramElement = null; + +function ensureDiagramErrorBox(diagram) { + let errorBox = document.getElementById('package-diagram-error'); + if (errorBox) return errorBox; + errorBox = document.createElement('pre'); + errorBox.id = 'package-diagram-error'; + errorBox.setAttribute('role', 'alert'); + errorBox.style.display = 'none'; + errorBox.style.whiteSpace = 'pre-wrap'; + errorBox.style.border = '1px solid #cc3333'; + errorBox.style.background = '#fff5f5'; + errorBox.style.color = '#222222'; + errorBox.style.padding = '8px 12px'; + errorBox.style.margin = '12px 0'; + diagram.parentNode.insertBefore(errorBox, diagram); + return errorBox; +} + +function showDiagramError(message) { + const diagram = currentDiagramElement; + if (!diagram) return; + const errorBox = ensureDiagramErrorBox(diagram); + errorBox.textContent = message; + errorBox.style.display = ''; + diagram.style.display = 'none'; +} + +function hideDiagramError(diagram) { + const errorBox = ensureDiagramErrorBox(diagram); + errorBox.textContent = ''; + errorBox.style.display = 'none'; + diagram.style.display = ''; +} function readPackageSummaryData() { if (packageSummaryCache) return packageSummaryCache; @@ -79,6 +113,7 @@ function writePackageTable() { function writePackageRelationDiagram(filterFqn, mode) { const diagram = document.getElementById('package-relation-diagram'); if (!diagram) return; + currentDiagramElement = diagram; const {packages, relations} = readPackageSummaryData(); const escapeMermaidText = text => text.replace(/"/g, '\\"'); @@ -181,6 +216,18 @@ function writePackageRelationDiagram(filterFqn, mode) { diagram.removeAttribute('data-processed'); diagram.textContent = lines.join('\n'); if (window.mermaid) { + if (!mermaid.parseError) { + mermaid.parseError = function (err, hash) { + const message = err && err.message ? err.message : String(err); + const location = hash ? `\nLine: ${hash.line} Column: ${hash.loc}` : ''; + showDiagramError(`Mermaid parse error: ${message}${location}`); + console.error('Mermaid parse error:', err); + if (hash) { + console.error('Mermaid error location:', hash.line, hash.loc); + } + }; + } + hideDiagramError(diagram); mermaid.initialize({startOnLoad: false, securityLevel: 'loose'}); mermaid.run({nodes: [diagram]}); } From fd4f57a8e87ccc4a12fca51ade08cadb162f9623 Mon Sep 17 00:00:00 2001 From: irof Date: Sat, 24 Jan 2026 22:33:42 +0900 Subject: [PATCH 15/43] =?UTF-8?q?edge=E4=B8=8A=E9=99=90=E3=82=AA=E3=83=BC?= =?UTF-8?q?=E3=83=90=E3=83=BC=E3=81=AE=E3=82=A8=E3=83=A9=E3=83=BC=E3=81=AB?= =?UTF-8?q?=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 超えている場合は描写せずにエラー表示 上限を引き上げて実行するように制御 --- .../resources/templates/assets/package.js | 80 ++++++++++++++++--- 1 file changed, 71 insertions(+), 9 deletions(-) diff --git a/jig-core/src/main/resources/templates/assets/package.js b/jig-core/src/main/resources/templates/assets/package.js index 05258f14e..004bbec91 100644 --- a/jig-core/src/main/resources/templates/assets/package.js +++ b/jig-core/src/main/resources/templates/assets/package.js @@ -3,11 +3,15 @@ let packageDiagramNodeIdToFqn = new Map(); let currentPackageDepth = 0; let currentPackageFilterMode = 'scope'; let currentDiagramElement = null; +let pendingDiagramRender = null; +let lastDiagramText = ''; +let lastDiagramEdgeCount = 0; +const DEFAULT_MAX_EDGES = 500; function ensureDiagramErrorBox(diagram) { let errorBox = document.getElementById('package-diagram-error'); if (errorBox) return errorBox; - errorBox = document.createElement('pre'); + errorBox = document.createElement('div'); errorBox.id = 'package-diagram-error'; errorBox.setAttribute('role', 'alert'); errorBox.style.display = 'none'; @@ -17,26 +21,70 @@ function ensureDiagramErrorBox(diagram) { errorBox.style.color = '#222222'; errorBox.style.padding = '8px 12px'; errorBox.style.margin = '12px 0'; + + const message = document.createElement('pre'); + message.id = 'package-diagram-error-message'; + message.style.whiteSpace = 'pre-wrap'; + message.style.margin = '0 0 8px 0'; + + const action = document.createElement('button'); + action.id = 'package-diagram-error-action'; + action.type = 'button'; + action.style.display = 'none'; + action.textContent = '描画する'; + + errorBox.appendChild(message); + errorBox.appendChild(action); diagram.parentNode.insertBefore(errorBox, diagram); return errorBox; } -function showDiagramError(message) { +function showDiagramError(message, withAction) { const diagram = currentDiagramElement; if (!diagram) return; const errorBox = ensureDiagramErrorBox(diagram); - errorBox.textContent = message; + const messageNode = document.getElementById('package-diagram-error-message'); + const actionNode = document.getElementById('package-diagram-error-action'); + if (messageNode) messageNode.textContent = message; + if (actionNode) { + actionNode.style.display = withAction ? '' : 'none'; + if (withAction) { + actionNode.onclick = function () { + if (!pendingDiagramRender) return; + renderPackageDiagram(pendingDiagramRender.text, pendingDiagramRender.maxEdges); + pendingDiagramRender = null; + }; + } else { + actionNode.onclick = null; + } + } errorBox.style.display = ''; diagram.style.display = 'none'; } function hideDiagramError(diagram) { const errorBox = ensureDiagramErrorBox(diagram); - errorBox.textContent = ''; + const messageNode = document.getElementById('package-diagram-error-message'); + const actionNode = document.getElementById('package-diagram-error-action'); + if (messageNode) messageNode.textContent = ''; + if (actionNode) { + actionNode.style.display = 'none'; + actionNode.onclick = null; + } errorBox.style.display = 'none'; diagram.style.display = ''; } +function renderPackageDiagram(text, maxEdges) { + const diagram = currentDiagramElement; + if (!diagram || !window.mermaid) return; + hideDiagramError(diagram); + diagram.removeAttribute('data-processed'); + diagram.textContent = text; + mermaid.initialize({startOnLoad: false, securityLevel: 'loose', maxEdges: maxEdges}); + mermaid.run({nodes: [diagram]}); +} + function readPackageSummaryData() { if (packageSummaryCache) return packageSummaryCache; const jsonText = document.getElementById('package-data').textContent; @@ -213,23 +261,37 @@ function writePackageRelationDiagram(filterFqn, mode) { }); linkStyles.forEach(styleLine => lines.push(styleLine)); + lastDiagramText = lines.join('\n'); + lastDiagramEdgeCount = uniqueRelations.length; + if (lastDiagramEdgeCount > DEFAULT_MAX_EDGES) { + pendingDiagramRender = {text: lastDiagramText, maxEdges: lastDiagramEdgeCount}; + const message = [ + '関連数が多すぎるため描画を省略しました。', + `エッジ数: ${lastDiagramEdgeCount}(上限: ${DEFAULT_MAX_EDGES})`, + '描画する場合はボタンを押してください。', + ].join('\n'); + showDiagramError(message, true); + return; + } diagram.removeAttribute('data-processed'); - diagram.textContent = lines.join('\n'); + diagram.textContent = lastDiagramText; if (window.mermaid) { if (!mermaid.parseError) { mermaid.parseError = function (err, hash) { const message = err && err.message ? err.message : String(err); const location = hash ? `\nLine: ${hash.line} Column: ${hash.loc}` : ''; - showDiagramError(`Mermaid parse error: ${message}${location}`); + const isEdgeLimit = message.includes('Edge limit exceeded'); + if (isEdgeLimit) { + pendingDiagramRender = {text: lastDiagramText, maxEdges: lastDiagramEdgeCount}; + } + showDiagramError(`Mermaid parse error: ${message}${location}`, isEdgeLimit); console.error('Mermaid parse error:', err); if (hash) { console.error('Mermaid error location:', hash.line, hash.loc); } }; } - hideDiagramError(diagram); - mermaid.initialize({startOnLoad: false, securityLevel: 'loose'}); - mermaid.run({nodes: [diagram]}); + renderPackageDiagram(lastDiagramText, DEFAULT_MAX_EDGES); } } From 52452fd988f35ac4a0525dc35b00eec29c82664c Mon Sep 17 00:00:00 2001 From: irof Date: Sat, 24 Jan 2026 22:45:52 +0900 Subject: [PATCH 16/43] =?UTF-8?q?=E9=9A=8E=E5=B1=A4=E9=9B=86=E7=B4=84?= =?UTF-8?q?=E3=81=AE=E9=81=B8=E6=8A=9E=E8=82=A2=E3=82=92=E9=81=B8=E3=81=B3?= =?UTF-8?q?=E3=82=84=E3=81=99=E3=81=8F=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 階層集約の選択項目パッケージ数と関連数を表示する 関連がないものを選択項目から除外 --- .../resources/templates/assets/package.js | 30 +++++++++++++++++-- .../src/main/resources/templates/package.html | 1 + 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/jig-core/src/main/resources/templates/assets/package.js b/jig-core/src/main/resources/templates/assets/package.js index 004bbec91..15a15bf00 100644 --- a/jig-core/src/main/resources/templates/assets/package.js +++ b/jig-core/src/main/resources/templates/assets/package.js @@ -110,6 +110,25 @@ function aggregatePackageFqn(fqn, depth) { return parts.slice(0, depth).join('.'); } +function computeAggregationStats(packages, relations, maxDepth) { + const stats = new Map(); + for (let depth = 0; depth <= maxDepth; depth += 1) { + const aggregatedPackages = new Set(packages.map(item => aggregatePackageFqn(item.fqn, depth))); + const relationKeys = new Set(); + relations.forEach(relation => { + const from = aggregatePackageFqn(relation.from, depth); + const to = aggregatePackageFqn(relation.to, depth); + if (from === to) return; + relationKeys.add(`${from}::${to}`); + }); + stats.set(depth, { + packageCount: aggregatedPackages.size, + relationCount: relationKeys.size, + }); + } + return stats; +} + function writePackageTable() { const {packages, relations} = readPackageSummaryData(); const incomingCounts = new Map(); @@ -337,17 +356,24 @@ function setupPackageDepthControl() { if (!select) return; const {packages} = readPackageSummaryData(); const maxDepth = packages.reduce((max, item) => Math.max(max, packageDepthOf(item.fqn)), 0); + const {relations} = readPackageSummaryData(); + const aggregationStats = computeAggregationStats(packages, relations, maxDepth); select.innerHTML = ''; const noAggregationOption = document.createElement('option'); noAggregationOption.value = '0'; - noAggregationOption.textContent = '集約なし'; + const noAggregationStats = aggregationStats.get(0); + noAggregationOption.textContent = `集約なし(P${noAggregationStats.packageCount} / R${noAggregationStats.relationCount})`; select.appendChild(noAggregationOption); for (let depth = 1; depth <= maxDepth; depth += 1) { const option = document.createElement('option'); option.value = String(depth); - option.textContent = `深さ${depth}`; + const stats = aggregationStats.get(depth); + if (!stats || stats.relationCount === 0) { + continue; + } + option.textContent = `深さ${depth}(P${stats.packageCount} / R${stats.relationCount})`; select.appendChild(option); } diff --git a/jig-core/src/main/resources/templates/package.html b/jig-core/src/main/resources/templates/package.html index 89981512a..0acc729f6 100644 --- a/jig-core/src/main/resources/templates/package.html +++ b/jig-core/src/main/resources/templates/package.html @@ -27,6 +27,7 @@

パッケージ概要

階層集約: + 凡例: P=パッケージ数 / R=関連数


From 821f4921b6110d63226bd5b307c003a105ba6438 Mon Sep 17 00:00:00 2001
From: irof 
Date: Sat, 24 Jan 2026 22:55:39 +0900
Subject: [PATCH 17/43] =?UTF-8?q?=E3=83=91=E3=83=83=E3=82=B1=E3=83=BC?=
 =?UTF-8?q?=E3=82=B8=E6=A6=82=E8=A6=81=E3=81=AE=E8=A1=A8=E7=A4=BA=E8=A8=AD?=
 =?UTF-8?q?=E5=AE=9A=E3=82=92=E6=94=B9=E5=96=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../main/resources/templates/assets/style.css | 64 +++++++++++++++++++
 .../src/main/resources/templates/package.html | 18 +++---
 2 files changed, 73 insertions(+), 9 deletions(-)

diff --git a/jig-core/src/main/resources/templates/assets/style.css b/jig-core/src/main/resources/templates/assets/style.css
index 726872d90..b46bae033 100644
--- a/jig-core/src/main/resources/templates/assets/style.css
+++ b/jig-core/src/main/resources/templates/assets/style.css
@@ -220,6 +220,70 @@ label {
     margin-left: 8px;
 }
 
+/* パッケージ概要の表示設定 */
+.package-list details.controls {
+    background: linear-gradient(180deg, #ffffff 0%, #f9f9f9 100%);
+    border: 1px solid #ccc;
+    border-radius: 8px;
+    padding: 12px 16px;
+    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
+    margin: 1em auto;
+    width: 95%;
+    max-width: 980px;
+}
+.package-list details.controls summary {
+    list-style: none;
+    cursor: pointer;
+    padding: 6px 0;
+    font-weight: 600;
+}
+.package-list details.controls > :not(summary) {
+    display: block;
+    margin: 8px 0;
+}
+.package-list details.controls .control-row {
+    display: flex;
+    align-items: center;
+    flex-wrap: wrap;
+    gap: 8px;
+}
+.package-list details.controls .control-label {
+    font-weight: 600;
+}
+.package-list details.controls input[type="text"],
+.package-list details.controls select {
+    padding: 6px 8px;
+    border: 1px solid #bbb;
+    border-radius: 6px;
+    background-color: #fff;
+}
+.package-list details.controls button {
+    padding: 6px 10px;
+    border: 1px solid #888;
+    border-radius: 6px;
+    background-color: #f2f2f2;
+    cursor: pointer;
+}
+.package-list details.controls button:hover {
+    background-color: #e6e6e6;
+}
+.package-list details.controls .note {
+    font-size: 0.9em;
+    color: #555;
+}
+@media (max-width: 720px) {
+    .package-list details.controls .control-row {
+        align-items: flex-start;
+    }
+    .package-list details.controls .control-label {
+        width: 100%;
+    }
+    .package-list details.controls input[type="text"],
+    .package-list details.controls select {
+        width: 100%;
+    }
+}
+
 /* 用語集 / 目次 */
 .glossary .letter-navigation {
     text-align: center;
diff --git a/jig-core/src/main/resources/templates/package.html b/jig-core/src/main/resources/templates/package.html
index 0acc729f6..743f91658 100644
--- a/jig-core/src/main/resources/templates/package.html
+++ b/jig-core/src/main/resources/templates/package.html
@@ -17,17 +17,17 @@ 

パッケージ概要

⚙️ 表示設定 -


From f93b10cc65354fbbdee91e7c5da9cb52edc27c8f Mon Sep 17 00:00:00 2001
From: irof 
Date: Sat, 24 Jan 2026 23:01:54 +0900
Subject: [PATCH 18/43] =?UTF-8?q?=E3=82=AF=E3=83=AA=E3=82=A2=E3=83=9C?=
 =?UTF-8?q?=E3=82=BF=E3=83=B3=E3=82=92=E3=83=AA=E3=82=BB=E3=83=83=E3=83=88?=
 =?UTF-8?q?=E3=81=AB=E5=A4=89=E6=9B=B4?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../resources/templates/assets/package.js     | 22 +++++++++++++------
 .../src/main/resources/templates/package.html |  2 +-
 2 files changed, 16 insertions(+), 8 deletions(-)

diff --git a/jig-core/src/main/resources/templates/assets/package.js b/jig-core/src/main/resources/templates/assets/package.js
index 15a15bf00..c483187bf 100644
--- a/jig-core/src/main/resources/templates/assets/package.js
+++ b/jig-core/src/main/resources/templates/assets/package.js
@@ -328,8 +328,20 @@ window.filterPackageDiagram = function (nodeId) {
 function setupPackageFilterInput() {
     const input = document.getElementById('package-filter-input');
     const applyButton = document.getElementById('apply-package-filter');
-    const clearButton = document.getElementById('clear-package-filter');
-    if (!input || !applyButton || !clearButton) return;
+    const resetButton = document.getElementById('reset-package-controls');
+    const depthSelect = document.getElementById('package-depth-select');
+    if (!input || !applyButton || !resetButton) return;
+
+    const resetAll = () => {
+        input.value = '';
+        if (depthSelect) {
+            depthSelect.value = '0';
+        }
+        currentPackageDepth = 0;
+        currentPackageFilterMode = 'scope';
+        pendingDiagramRender = null;
+        writePackageRelationDiagram(null, currentPackageFilterMode);
+    };
 
     const applyFilter = () => {
         const value = input.value.trim();
@@ -338,11 +350,7 @@ function setupPackageFilterInput() {
     };
 
     applyButton.addEventListener('click', applyFilter);
-    clearButton.addEventListener('click', () => {
-        input.value = '';
-        currentPackageFilterMode = 'scope';
-        writePackageRelationDiagram(null, currentPackageFilterMode);
-    });
+    resetButton.addEventListener('click', resetAll);
     input.addEventListener('keydown', event => {
         if (event.key === 'Enter') {
             event.preventDefault();
diff --git a/jig-core/src/main/resources/templates/package.html b/jig-core/src/main/resources/templates/package.html
index 743f91658..72873c7ce 100644
--- a/jig-core/src/main/resources/templates/package.html
+++ b/jig-core/src/main/resources/templates/package.html
@@ -27,7 +27,7 @@ 

パッケージ概要

凡例: P=パッケージ数 / R=関連数 - +


From a4895c44f1b186986063486ffd02c1cf5b6489c0 Mon Sep 17 00:00:00 2001
From: irof 
Date: Sat, 24 Jan 2026 23:13:55 +0900
Subject: [PATCH 19/43] =?UTF-8?q?=E8=A9=B3=E7=B4=B0=E5=88=97=E3=82=92?=
 =?UTF-8?q?=E9=99=A4=E5=8E=BB?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

このドキュメントで見るもんじゃないってことで
---
 jig-core/src/main/resources/templates/assets/jig.js    | 10 ----------
 .../src/main/resources/templates/assets/package.js     |  6 ------
 jig-core/src/main/resources/templates/assets/style.css |  3 +++
 jig-core/src/main/resources/templates/package.html     |  6 ------
 4 files changed, 3 insertions(+), 22 deletions(-)

diff --git a/jig-core/src/main/resources/templates/assets/jig.js b/jig-core/src/main/resources/templates/assets/jig.js
index 0ec18d345..685fbf535 100644
--- a/jig-core/src/main/resources/templates/assets/jig.js
+++ b/jig-core/src/main/resources/templates/assets/jig.js
@@ -180,16 +180,6 @@ function updateLetterNavigationVisibility() {
     });
 }
 
-function toggleDescription() {
-    // クラス名に一致する要素を全部取得
-    const elements = document.getElementsByClassName("description");
-
-    // 各要素に対して「hidden」クラスをトグル(付けたり外したり)する
-    Array.from(elements).forEach(el => {
-        console.log(el);
-        el.classList.toggle("hidden");
-    });
-}
 
 function setupSortableTables() {
     document.querySelectorAll("table.sortable").forEach(table => {
diff --git a/jig-core/src/main/resources/templates/assets/package.js b/jig-core/src/main/resources/templates/assets/package.js
index c483187bf..ae4599c10 100644
--- a/jig-core/src/main/resources/templates/assets/package.js
+++ b/jig-core/src/main/resources/templates/assets/package.js
@@ -168,11 +168,6 @@ function writePackageTable() {
         outgoingCountTd.className = 'number';
         tr.appendChild(outgoingCountTd);
 
-        const descTd = document.createElement('td');
-        descTd.textContent = item.description;
-        descTd.className = 'description hidden markdown';
-        tr.appendChild(descTd);
-
         tbody.appendChild(tr);
     });
 }
@@ -395,7 +390,6 @@ function setupPackageDepthControl() {
 
 document.addEventListener("DOMContentLoaded", function () {
     if (!document.body.classList.contains("package-list")) return;
-    document.getElementById("toggle-description-btn").addEventListener("click", toggleDescription);
     setupSortableTables();
     currentPackageFilterMode = 'scope';
     writePackageRelationDiagram(null, currentPackageFilterMode);
diff --git a/jig-core/src/main/resources/templates/assets/style.css b/jig-core/src/main/resources/templates/assets/style.css
index b46bae033..0598afe70 100644
--- a/jig-core/src/main/resources/templates/assets/style.css
+++ b/jig-core/src/main/resources/templates/assets/style.css
@@ -38,6 +38,9 @@ aside.notice {
 .hidden {
     display: none;
 }
+col.hidden {
+    visibility: collapse;
+}
 .weak {
     font-size: 0.9em;
     color: gray;
diff --git a/jig-core/src/main/resources/templates/package.html b/jig-core/src/main/resources/templates/package.html
index 72873c7ce..f1de26557 100644
--- a/jig-core/src/main/resources/templates/package.html
+++ b/jig-core/src/main/resources/templates/package.html
@@ -16,7 +16,6 @@ 

パッケージ概要

⚙️ 表示設定 -
@@ -39,7 +38,6 @@

パッケージ概要

- @@ -48,7 +46,6 @@

パッケージ概要

- @@ -58,9 +55,6 @@

パッケージ概要

-
クラス数 関連数(依存元) 関連数(依存先)
10 2 5
From 0492e22943f75c2a0250c2d1a722b55a1b97b78b Mon Sep 17 00:00:00 2001 From: irof Date: Sat, 24 Jan 2026 23:22:23 +0900 Subject: [PATCH 20/43] =?UTF-8?q?=E3=83=86=E3=83=BC=E3=83=96=E3=83=AB?= =?UTF-8?q?=E3=81=8B=E3=82=89=E3=83=91=E3=83=83=E3=82=B1=E3=83=BC=E3=82=B8?= =?UTF-8?q?=E3=82=92=E7=B5=9E=E3=82=8A=E8=BE=BC=E3=82=81=E3=82=8B=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/resources/templates/assets/jig.js | 3 ++ .../resources/templates/assets/package.js | 22 ++++++++++++++ .../main/resources/templates/assets/style.css | 30 +++++++++++++++++++ .../src/main/resources/templates/package.html | 3 ++ 4 files changed, 58 insertions(+) diff --git a/jig-core/src/main/resources/templates/assets/jig.js b/jig-core/src/main/resources/templates/assets/jig.js index 685fbf535..3f72e3f45 100644 --- a/jig-core/src/main/resources/templates/assets/jig.js +++ b/jig-core/src/main/resources/templates/assets/jig.js @@ -188,6 +188,9 @@ function setupSortableTables() { if (header.hasAttribute("onclick")) { return; } + if (header.classList.contains("no-sort")) { + return; + } header.addEventListener("click", sortTable); header.style.cursor = "pointer"; diff --git a/jig-core/src/main/resources/templates/assets/package.js b/jig-core/src/main/resources/templates/assets/package.js index ae4599c10..62cdb9d5d 100644 --- a/jig-core/src/main/resources/templates/assets/package.js +++ b/jig-core/src/main/resources/templates/assets/package.js @@ -141,9 +141,31 @@ function writePackageTable() { const tbody = document.querySelector('#package-table tbody'); //tbody.innerHTML = ''; + const input = document.getElementById('package-filter-input'); + const applyFilter = fqn => { + if (input) { + input.value = fqn; + } + currentPackageFilterMode = 'scope'; + writePackageRelationDiagram(fqn, currentPackageFilterMode); + }; + packages.forEach(item => { const tr = document.createElement('tr'); + const actionTd = document.createElement('td'); + const actionButton = document.createElement('button'); + actionButton.type = 'button'; + actionButton.className = 'filter-icon'; + actionButton.setAttribute('aria-label', 'このパッケージで絞り込み'); + const actionText = document.createElement('span'); + actionText.className = 'sr-only'; + actionText.textContent = '絞り込み'; + actionButton.appendChild(actionText); + actionButton.addEventListener('click', () => applyFilter(item.fqn)); + actionTd.appendChild(actionButton); + tr.appendChild(actionTd); + const fqnTd = document.createElement('td'); fqnTd.textContent = item.fqn; fqnTd.className = 'fqn'; diff --git a/jig-core/src/main/resources/templates/assets/style.css b/jig-core/src/main/resources/templates/assets/style.css index 0598afe70..c16f035fb 100644 --- a/jig-core/src/main/resources/templates/assets/style.css +++ b/jig-core/src/main/resources/templates/assets/style.css @@ -69,6 +69,23 @@ main table thead { color: #FFF; background-color: #465DAA; } +main table .col-action { + width: 2.5em; +} +main table th.no-sort { + cursor: default; +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} /* markdown */ .markdown { @@ -274,6 +291,19 @@ label { font-size: 0.9em; color: #555; } +.package-list button.filter-icon { + width: 1.8em; + height: 1.8em; + border-radius: 999px; + border: 1px solid #999; + background: #fff no-repeat center; + cursor: pointer; + padding: 0; + background-image: url("data:image/svg+xml;utf8,"); +} +.package-list button.filter-icon:hover { + background-color: #f5f5f5; +} @media (max-width: 720px) { .package-list details.controls .control-row { align-items: flex-start; diff --git a/jig-core/src/main/resources/templates/package.html b/jig-core/src/main/resources/templates/package.html index f1de26557..211ad082f 100644 --- a/jig-core/src/main/resources/templates/package.html +++ b/jig-core/src/main/resources/templates/package.html @@ -33,6 +33,7 @@

パッケージ概要

+ @@ -41,6 +42,7 @@

パッケージ概要

+ @@ -50,6 +52,7 @@

パッケージ概要

+ From fe56e515a7496a347ef529ff6f8b93f831ec1b26 Mon Sep 17 00:00:00 2001 From: irof Date: Sat, 24 Jan 2026 23:29:53 +0900 Subject: [PATCH 21/43] =?UTF-8?q?=E3=83=91=E3=83=83=E3=82=B1=E3=83=BC?= =?UTF-8?q?=E3=82=B8=E7=B5=9E=E3=82=8A=E8=BE=BC=E3=81=BF=E2=86=92=E3=82=B9?= =?UTF-8?q?=E3=82=B3=E3=83=BC=E3=83=97=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jig-core/src/main/resources/templates/assets/style.css | 3 +++ jig-core/src/main/resources/templates/package.html | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/jig-core/src/main/resources/templates/assets/style.css b/jig-core/src/main/resources/templates/assets/style.css index c16f035fb..78a4fa4c5 100644 --- a/jig-core/src/main/resources/templates/assets/style.css +++ b/jig-core/src/main/resources/templates/assets/style.css @@ -277,6 +277,9 @@ label { border-radius: 6px; background-color: #fff; } +.package-list details.controls input[type="text"] { + min-width: 50ch; +} .package-list details.controls button { padding: 6px 10px; border: 1px solid #888; diff --git a/jig-core/src/main/resources/templates/package.html b/jig-core/src/main/resources/templates/package.html index 211ad082f..f003cadf8 100644 --- a/jig-core/src/main/resources/templates/package.html +++ b/jig-core/src/main/resources/templates/package.html @@ -17,7 +17,7 @@

パッケージ概要

⚙️ 表示設定
- +
From 5492b75e2a5409cfde9e7134f27c52144db89eaa Mon Sep 17 00:00:00 2001 From: irof Date: Sat, 24 Jan 2026 23:37:59 +0900 Subject: [PATCH 22/43] =?UTF-8?q?package-list=E7=94=A8=E3=81=AEcss?= =?UTF-8?q?=E3=82=92=E3=81=BE=E3=81=A8=E3=82=81=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/resources/templates/assets/style.css | 83 +++++++++---------- 1 file changed, 40 insertions(+), 43 deletions(-) diff --git a/jig-core/src/main/resources/templates/assets/style.css b/jig-core/src/main/resources/templates/assets/style.css index 78a4fa4c5..d42f6ba4e 100644 --- a/jig-core/src/main/resources/templates/assets/style.css +++ b/jig-core/src/main/resources/templates/assets/style.css @@ -69,23 +69,6 @@ main table thead { color: #FFF; background-color: #465DAA; } -main table .col-action { - width: 2.5em; -} -main table th.no-sort { - cursor: default; -} - -.sr-only { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - border: 0; -} /* markdown */ .markdown { @@ -307,6 +290,46 @@ label { .package-list button.filter-icon:hover { background-color: #f5f5f5; } +.package-list table .col-action { + width: 2.5em; +} +.package-list table th.no-sort { + cursor: default; +} +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} +.package-list table { + border-collapse: collapse; + width: 100%; + margin-top: 10px; + font-size: 16px; + table-layout: fixed; +} +.package-list td { + text-align: left; + vertical-align: top; + overflow-wrap: anywhere; + word-break: break-word; + white-space: normal; +} +.package-list td.fqn { + white-space: nowrap; + overflow: scroll; + max-width: 20em; + overflow-wrap: normal; + word-break: normal; +} +.package-list tr:nth-child(even) { + background-color: #f9f9f9; +} @media (max-width: 720px) { .package-list details.controls .control-row { align-items: flex-start; @@ -384,32 +407,6 @@ label { margin: 0; } -/* テーブル全体のスタイル */ -.package-list table { - border-collapse: collapse; - width: 100%; - margin-top: 10px; - font-size: 16px; - table-layout: fixed; /* 列幅を固定して可読性を確保 */ -} -.package-list td { - text-align: left; - vertical-align: top; - overflow-wrap: anywhere; - word-break: break-word; - white-space: normal; -} -.package-list td.fqn { - white-space: nowrap; - overflow: scroll; - max-width: 20em; - overflow-wrap: normal; - word-break: normal; -} -.package-list tr:nth-child(even) { - background-color: #f9f9f9; -} - .insight table { border-collapse: collapse; width: 100%; From a2812bea495e3a14cbc0570c7c91313ee91d7b81 Mon Sep 17 00:00:00 2001 From: irof Date: Sat, 24 Jan 2026 23:40:24 +0900 Subject: [PATCH 23/43] =?UTF-8?q?sr-only=E3=81=AF=E8=AC=8E=E3=81=AA?= =?UTF-8?q?=E3=81=AE=E3=81=A7=E7=95=A5=E3=81=97=E3=81=AA=E3=81=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/templates/assets/package.js | 2 +- .../main/resources/templates/assets/style.css | 20 +++++++++---------- .../src/main/resources/templates/package.html | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/jig-core/src/main/resources/templates/assets/package.js b/jig-core/src/main/resources/templates/assets/package.js index 62cdb9d5d..b256d6193 100644 --- a/jig-core/src/main/resources/templates/assets/package.js +++ b/jig-core/src/main/resources/templates/assets/package.js @@ -159,7 +159,7 @@ function writePackageTable() { actionButton.className = 'filter-icon'; actionButton.setAttribute('aria-label', 'このパッケージで絞り込み'); const actionText = document.createElement('span'); - actionText.className = 'sr-only'; + actionText.className = 'screen-reader-only'; actionText.textContent = '絞り込み'; actionButton.appendChild(actionText); actionButton.addEventListener('click', () => applyFilter(item.fqn)); diff --git a/jig-core/src/main/resources/templates/assets/style.css b/jig-core/src/main/resources/templates/assets/style.css index d42f6ba4e..84cf17a02 100644 --- a/jig-core/src/main/resources/templates/assets/style.css +++ b/jig-core/src/main/resources/templates/assets/style.css @@ -41,6 +41,16 @@ aside.notice { col.hidden { visibility: collapse; } +.screen-reader-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} .weak { font-size: 0.9em; color: gray; @@ -296,16 +306,6 @@ label { .package-list table th.no-sort { cursor: default; } -.sr-only { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - border: 0; -} .package-list table { border-collapse: collapse; width: 100%; diff --git a/jig-core/src/main/resources/templates/package.html b/jig-core/src/main/resources/templates/package.html index f003cadf8..3be1b945f 100644 --- a/jig-core/src/main/resources/templates/package.html +++ b/jig-core/src/main/resources/templates/package.html @@ -42,7 +42,7 @@

パッケージ概要

- + @@ -52,7 +52,7 @@

パッケージ概要

- + From af960fdf9dbfa02c53b9297c98d46afc29334ba9 Mon Sep 17 00:00:00 2001 From: irof Date: Sat, 24 Jan 2026 23:43:11 +0900 Subject: [PATCH 24/43] =?UTF-8?q?=E3=83=86=E3=83=B3=E3=83=97=E3=83=AC?= =?UTF-8?q?=E3=83=BC=E3=83=88=E3=81=AEJSON=E3=82=92=E3=81=9D=E3=82=8C?= =?UTF-8?q?=E3=81=A3=E3=81=BD=E3=81=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/templates/package.html | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/jig-core/src/main/resources/templates/package.html b/jig-core/src/main/resources/templates/package.html index 3be1b945f..1b45bc08e 100644 --- a/jig-core/src/main/resources/templates/package.html +++ b/jig-core/src/main/resources/templates/package.html @@ -51,20 +51,31 @@

パッケージ概要

- - - - - - - -
絞り込み 完全修飾名 名称 クラス数
com.example えぐざんぷる 10
絞り込み絞り込み 完全修飾名 名称 クラス数
com.example えぐざんぷる 10
com.exampleえぐざんぷる1025
From 5cd1be76c300f765a224a7804e3810a757b7914c Mon Sep 17 00:00:00 2001 From: irof Date: Sat, 24 Jan 2026 23:45:16 +0900 Subject: [PATCH 25/43] =?UTF-8?q?=E3=82=B9=E3=82=B3=E3=83=BC=E3=83=97?= =?UTF-8?q?=E3=81=A7=E3=83=86=E3=83=BC=E3=83=96=E3=83=AB=E3=82=82=E7=B5=9E?= =?UTF-8?q?=E3=82=8A=E3=82=B3=E3=83=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/templates/assets/package.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/jig-core/src/main/resources/templates/assets/package.js b/jig-core/src/main/resources/templates/assets/package.js index b256d6193..5c8fecb68 100644 --- a/jig-core/src/main/resources/templates/assets/package.js +++ b/jig-core/src/main/resources/templates/assets/package.js @@ -148,6 +148,7 @@ function writePackageTable() { } currentPackageFilterMode = 'scope'; writePackageRelationDiagram(fqn, currentPackageFilterMode); + filterPackageTable(fqn); }; packages.forEach(item => { @@ -194,6 +195,17 @@ function writePackageTable() { }); } +function filterPackageTable(scopeFqn) { + const rows = document.querySelectorAll('#package-table tbody tr'); + const scopePrefix = scopeFqn ? `${scopeFqn}.` : null; + rows.forEach(row => { + const fqnCell = row.querySelector('td.fqn'); + const fqn = fqnCell ? fqnCell.textContent : ''; + const visible = !scopeFqn || fqn === scopeFqn || fqn.startsWith(scopePrefix); + row.classList.toggle('hidden', !visible); + }); +} + function writePackageRelationDiagram(filterFqn, mode) { const diagram = document.getElementById('package-relation-diagram'); if (!diagram) return; @@ -358,12 +370,14 @@ function setupPackageFilterInput() { currentPackageFilterMode = 'scope'; pendingDiagramRender = null; writePackageRelationDiagram(null, currentPackageFilterMode); + filterPackageTable(null); }; const applyFilter = () => { const value = input.value.trim(); currentPackageFilterMode = 'scope'; writePackageRelationDiagram(value || null, currentPackageFilterMode); + filterPackageTable(value || null); }; applyButton.addEventListener('click', applyFilter); From 5582aa505a43ff2b528b994998ab9fa175eb8594 Mon Sep 17 00:00:00 2001 From: irof Date: Sat, 24 Jan 2026 23:55:30 +0900 Subject: [PATCH 26/43] =?UTF-8?q?=E9=96=A2=E9=80=A3=E3=81=AE=E3=81=BF?= =?UTF-8?q?=E3=81=AE=E8=A1=A8=E7=A4=BA=E3=82=92=E3=83=86=E3=83=BC=E3=83=96?= =?UTF-8?q?=E3=83=AB=E3=81=A7=E3=82=82=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/templates/assets/package.js | 19 +++++++++++++++++++ .../main/resources/templates/assets/style.css | 13 +++++++++++++ .../src/main/resources/templates/package.html | 2 ++ 3 files changed, 34 insertions(+) diff --git a/jig-core/src/main/resources/templates/assets/package.js b/jig-core/src/main/resources/templates/assets/package.js index 5c8fecb68..bd83c1616 100644 --- a/jig-core/src/main/resources/templates/assets/package.js +++ b/jig-core/src/main/resources/templates/assets/package.js @@ -150,6 +150,12 @@ function writePackageTable() { writePackageRelationDiagram(fqn, currentPackageFilterMode); filterPackageTable(fqn); }; + const applyRelatedFilter = fqn => { + if (input) { + input.value = fqn; + } + filterPackageDiagramByFqn(fqn); + }; packages.forEach(item => { const tr = document.createElement('tr'); @@ -167,6 +173,19 @@ function writePackageTable() { actionTd.appendChild(actionButton); tr.appendChild(actionTd); + const relatedTd = document.createElement('td'); + const relatedButton = document.createElement('button'); + relatedButton.type = 'button'; + relatedButton.className = 'related-icon'; + relatedButton.setAttribute('aria-label', '関連のみ表示'); + const relatedText = document.createElement('span'); + relatedText.className = 'screen-reader-only'; + relatedText.textContent = '関連のみ表示'; + relatedButton.appendChild(relatedText); + relatedButton.addEventListener('click', () => applyRelatedFilter(item.fqn)); + relatedTd.appendChild(relatedButton); + tr.appendChild(relatedTd); + const fqnTd = document.createElement('td'); fqnTd.textContent = item.fqn; fqnTd.className = 'fqn'; diff --git a/jig-core/src/main/resources/templates/assets/style.css b/jig-core/src/main/resources/templates/assets/style.css index 84cf17a02..82d6647ab 100644 --- a/jig-core/src/main/resources/templates/assets/style.css +++ b/jig-core/src/main/resources/templates/assets/style.css @@ -300,6 +300,19 @@ label { .package-list button.filter-icon:hover { background-color: #f5f5f5; } +.package-list button.related-icon { + width: 1.8em; + height: 1.8em; + border-radius: 999px; + border: 1px solid #999; + background: #fff no-repeat center; + cursor: pointer; + padding: 0; + background-image: url("data:image/svg+xml;utf8,"); +} +.package-list button.related-icon:hover { + background-color: #f5f5f5; +} .package-list table .col-action { width: 2.5em; } diff --git a/jig-core/src/main/resources/templates/package.html b/jig-core/src/main/resources/templates/package.html index 1b45bc08e..c95f43a32 100644 --- a/jig-core/src/main/resources/templates/package.html +++ b/jig-core/src/main/resources/templates/package.html @@ -33,6 +33,7 @@

パッケージ概要

+ @@ -43,6 +44,7 @@

パッケージ概要

+ From f2d6e24e6f6dfe861294c65e7d56b1328bc85918 Mon Sep 17 00:00:00 2001 From: irof Date: Sun, 25 Jan 2026 00:08:57 +0900 Subject: [PATCH 27/43] =?UTF-8?q?=E9=96=A2=E9=80=A3=E7=B5=9E=E3=82=8A?= =?UTF-8?q?=E8=BE=BC=E3=81=BF=E3=81=AE=E3=81=A8=E3=81=8D=E3=82=82=E3=83=86?= =?UTF-8?q?=E3=83=BC=E3=83=96=E3=83=AB=E3=82=92=E9=80=A3=E5=8B=95=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/templates/assets/package.js | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/jig-core/src/main/resources/templates/assets/package.js b/jig-core/src/main/resources/templates/assets/package.js index bd83c1616..87270027d 100644 --- a/jig-core/src/main/resources/templates/assets/package.js +++ b/jig-core/src/main/resources/templates/assets/package.js @@ -7,6 +7,7 @@ let pendingDiagramRender = null; let lastDiagramText = ''; let lastDiagramEdgeCount = 0; const DEFAULT_MAX_EDGES = 500; +let currentPackageFilterFqn = null; function ensureDiagramErrorBox(diagram) { let errorBox = document.getElementById('package-diagram-error'); @@ -146,6 +147,7 @@ function writePackageTable() { if (input) { input.value = fqn; } + currentPackageFilterFqn = fqn; currentPackageFilterMode = 'scope'; writePackageRelationDiagram(fqn, currentPackageFilterMode); filterPackageTable(fqn); @@ -154,6 +156,7 @@ function writePackageTable() { if (input) { input.value = fqn; } + currentPackageFilterFqn = fqn; filterPackageDiagramByFqn(fqn); }; @@ -225,6 +228,29 @@ function filterPackageTable(scopeFqn) { }); } +function filterPackageTableByRelated(fqn) { + if (!fqn) { + filterPackageTable(null); + return; + } + const {relations} = readPackageSummaryData(); + const aggregatedRoot = aggregatePackageFqn(fqn, currentPackageDepth); + const relatedSet = new Set([aggregatedRoot]); + relations.forEach(relation => { + const from = aggregatePackageFqn(relation.from, currentPackageDepth); + const to = aggregatePackageFqn(relation.to, currentPackageDepth); + if (from === aggregatedRoot) relatedSet.add(to); + if (to === aggregatedRoot) relatedSet.add(from); + }); + const rows = document.querySelectorAll('#package-table tbody tr'); + rows.forEach(row => { + const fqnCell = row.querySelector('td.fqn'); + const rowFqn = fqnCell ? fqnCell.textContent : ''; + const aggregatedRow = aggregatePackageFqn(rowFqn, currentPackageDepth); + row.classList.toggle('hidden', !relatedSet.has(aggregatedRow)); + }); +} + function writePackageRelationDiagram(filterFqn, mode) { const diagram = document.getElementById('package-relation-diagram'); if (!diagram) return; @@ -363,8 +389,10 @@ function writePackageRelationDiagram(filterFqn, mode) { } function filterPackageDiagramByFqn(fqn) { + currentPackageFilterFqn = fqn; currentPackageFilterMode = 'related'; writePackageRelationDiagram(fqn, currentPackageFilterMode); + filterPackageTableByRelated(fqn); } window.filterPackageDiagram = function (nodeId) { @@ -387,6 +415,7 @@ function setupPackageFilterInput() { } currentPackageDepth = 0; currentPackageFilterMode = 'scope'; + currentPackageFilterFqn = null; pendingDiagramRender = null; writePackageRelationDiagram(null, currentPackageFilterMode); filterPackageTable(null); @@ -394,6 +423,7 @@ function setupPackageFilterInput() { const applyFilter = () => { const value = input.value.trim(); + currentPackageFilterFqn = value || null; currentPackageFilterMode = 'scope'; writePackageRelationDiagram(value || null, currentPackageFilterMode); filterPackageTable(value || null); @@ -440,6 +470,11 @@ function setupPackageDepthControl() { currentPackageDepth = Number(select.value); const value = document.getElementById('package-filter-input')?.value.trim() || null; writePackageRelationDiagram(value, currentPackageFilterMode); + if (currentPackageFilterMode === 'related') { + filterPackageTableByRelated(value); + } else { + filterPackageTable(value); + } }); } From 80dbdfb5a79ba056ef822544ba766accfabdf614 Mon Sep 17 00:00:00 2001 From: irof Date: Sun, 25 Jan 2026 00:17:58 +0900 Subject: [PATCH 28/43] =?UTF-8?q?=E9=96=A2=E9=80=A3=E3=83=A2=E3=83=BC?= =?UTF-8?q?=E3=83=89=E3=81=AE=E5=88=87=E3=82=8A=E6=9B=BF=E3=81=88=E6=A9=9F?= =?UTF-8?q?=E8=83=BD=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 「直接」「依存先すべて」「関連すべて」の選択肢を追加し、関連の範囲を柔軟に変更可能に。テーブルと図表の両方で連動するよう対応を統一。 --- .../resources/templates/assets/package.js | 87 +++++++++++++++---- .../src/main/resources/templates/package.html | 16 +++- 2 files changed, 84 insertions(+), 19 deletions(-) diff --git a/jig-core/src/main/resources/templates/assets/package.js b/jig-core/src/main/resources/templates/assets/package.js index 87270027d..af5d5e769 100644 --- a/jig-core/src/main/resources/templates/assets/package.js +++ b/jig-core/src/main/resources/templates/assets/package.js @@ -8,6 +8,7 @@ let lastDiagramText = ''; let lastDiagramEdgeCount = 0; const DEFAULT_MAX_EDGES = 500; let currentPackageFilterFqn = null; +let currentRelatedMode = 'direct'; function ensureDiagramErrorBox(diagram) { let errorBox = document.getElementById('package-diagram-error'); @@ -235,13 +236,7 @@ function filterPackageTableByRelated(fqn) { } const {relations} = readPackageSummaryData(); const aggregatedRoot = aggregatePackageFqn(fqn, currentPackageDepth); - const relatedSet = new Set([aggregatedRoot]); - relations.forEach(relation => { - const from = aggregatePackageFqn(relation.from, currentPackageDepth); - const to = aggregatePackageFqn(relation.to, currentPackageDepth); - if (from === aggregatedRoot) relatedSet.add(to); - if (to === aggregatedRoot) relatedSet.add(from); - }); + const relatedSet = buildRelatedSet(aggregatedRoot, relations); const rows = document.querySelectorAll('#package-table tbody tr'); rows.forEach(row => { const fqnCell = row.querySelector('td.fqn'); @@ -251,6 +246,48 @@ function filterPackageTableByRelated(fqn) { }); } +function buildRelatedSet(root, relations) { + if (!root) return new Set(); + if (currentRelatedMode === 'direct') { + const relatedSet = new Set([root]); + relations.forEach(relation => { + const from = aggregatePackageFqn(relation.from, currentPackageDepth); + const to = aggregatePackageFqn(relation.to, currentPackageDepth); + if (from === root) relatedSet.add(to); + if (to === root) relatedSet.add(from); + }); + return relatedSet; + } + + const adjacency = new Map(); + const addEdge = (from, to) => { + if (!adjacency.has(from)) adjacency.set(from, new Set()); + adjacency.get(from).add(to); + }; + relations.forEach(relation => { + const from = aggregatePackageFqn(relation.from, currentPackageDepth); + const to = aggregatePackageFqn(relation.to, currentPackageDepth); + addEdge(from, to); + if (currentRelatedMode === 'all') { + addEdge(to, from); + } + }); + + const relatedSet = new Set([root]); + const queue = [root]; + while (queue.length) { + const current = queue.shift(); + const nextSet = adjacency.get(current); + if (!nextSet) continue; + nextSet.forEach(next => { + if (relatedSet.has(next)) return; + relatedSet.add(next); + queue.push(next); + }); + } + return relatedSet; +} + function writePackageRelationDiagram(filterFqn, mode) { const diagram = document.getElementById('package-relation-diagram'); if (!diagram) return; @@ -284,14 +321,16 @@ function writePackageRelationDiagram(filterFqn, mode) { let uniqueRelations = Array.from(uniqueRelationMap.values()); if (aggregatedRoot && mode === 'related') { - const relatedSet = new Set([aggregatedRoot]); - uniqueRelations.forEach(relation => { - if (relation.from === aggregatedRoot) relatedSet.add(relation.to); - if (relation.to === aggregatedRoot) relatedSet.add(relation.from); - }); - uniqueRelations = uniqueRelations.filter(relation => - relation.from === aggregatedRoot || relation.to === aggregatedRoot - ); + const relatedSet = buildRelatedSet(aggregatedRoot, uniqueRelations); + if (currentRelatedMode === 'direct') { + uniqueRelations = uniqueRelations.filter(relation => + relation.from === aggregatedRoot || relation.to === aggregatedRoot + ); + } else { + uniqueRelations = uniqueRelations.filter(relation => + relatedSet.has(relation.from) && relatedSet.has(relation.to) + ); + } visibleSet.clear(); relatedSet.forEach(value => visibleSet.add(value)); } else if (aggregatedRoot && mode === 'scope') { @@ -416,6 +455,11 @@ function setupPackageFilterInput() { currentPackageDepth = 0; currentPackageFilterMode = 'scope'; currentPackageFilterFqn = null; + currentRelatedMode = 'direct'; + const relatedSelect = document.getElementById('related-mode-select'); + if (relatedSelect) { + relatedSelect.value = currentRelatedMode; + } pendingDiagramRender = null; writePackageRelationDiagram(null, currentPackageFilterMode); filterPackageTable(null); @@ -478,6 +522,18 @@ function setupPackageDepthControl() { }); } +function setupRelatedModeControl() { + const select = document.getElementById('related-mode-select'); + if (!select) return; + select.value = currentRelatedMode; + select.addEventListener('change', () => { + currentRelatedMode = select.value; + if (currentPackageFilterMode === 'related') { + filterPackageDiagramByFqn(currentPackageFilterFqn); + } + }); +} + document.addEventListener("DOMContentLoaded", function () { if (!document.body.classList.contains("package-list")) return; setupSortableTables(); @@ -486,4 +542,5 @@ document.addEventListener("DOMContentLoaded", function () { writePackageTable(); setupPackageFilterInput(); setupPackageDepthControl(); + setupRelatedModeControl(); }); diff --git a/jig-core/src/main/resources/templates/package.html b/jig-core/src/main/resources/templates/package.html index c95f43a32..25bbbcbea 100644 --- a/jig-core/src/main/resources/templates/package.html +++ b/jig-core/src/main/resources/templates/package.html @@ -17,14 +17,22 @@

パッケージ概要

⚙️ 表示設定
- + + + 凡例: P=パッケージ数 / R=関連数 +
+
+
- - - 凡例: P=パッケージ数 / R=関連数 + +
From d6bf8035a2d9573ef8b91da0c1d498a6f6bbe0e6 Mon Sep 17 00:00:00 2001 From: irof Date: Sun, 25 Jan 2026 00:23:47 +0900 Subject: [PATCH 29/43] =?UTF-8?q?=E3=83=87=E3=83=95=E3=82=A9=E3=83=AB?= =?UTF-8?q?=E3=83=88=E3=82=B9=E3=82=B3=E3=83=BC=E3=83=97=E8=A8=AD=E5=AE=9A?= =?UTF-8?q?=E6=A9=9F=E8=83=BD=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit パッケージリスト画面で入力が空の場合、最適なドメインスコープを自動適用する処理を追加。スコープが適用されない場合は従来どおりの動作を維持。 --- .../resources/templates/assets/package.js | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/jig-core/src/main/resources/templates/assets/package.js b/jig-core/src/main/resources/templates/assets/package.js index af5d5e769..dabada18b 100644 --- a/jig-core/src/main/resources/templates/assets/package.js +++ b/jig-core/src/main/resources/templates/assets/package.js @@ -522,6 +522,28 @@ function setupPackageDepthControl() { }); } +function applyDefaultScopeIfPresent() { + const input = document.getElementById('package-filter-input'); + if (!input || input.value.trim()) return false; + const {packages} = readPackageSummaryData(); + const domainCandidates = packages + .map(item => item.fqn) + .filter(fqn => /\.domain(\.|$)/.test(fqn)); + if (domainCandidates.length === 0) return false; + const candidate = domainCandidates.reduce((best, current) => { + const bestDepth = best.split('.').length; + const currentDepth = current.split('.').length; + return currentDepth < bestDepth ? current : best; + }); + if (!candidate) return false; + input.value = candidate; + currentPackageFilterFqn = candidate; + currentPackageFilterMode = 'scope'; + writePackageRelationDiagram(candidate, currentPackageFilterMode); + filterPackageTable(candidate); + return true; +} + function setupRelatedModeControl() { const select = document.getElementById('related-mode-select'); if (!select) return; @@ -537,10 +559,14 @@ function setupRelatedModeControl() { document.addEventListener("DOMContentLoaded", function () { if (!document.body.classList.contains("package-list")) return; setupSortableTables(); - currentPackageFilterMode = 'scope'; - writePackageRelationDiagram(null, currentPackageFilterMode); writePackageTable(); setupPackageFilterInput(); setupPackageDepthControl(); setupRelatedModeControl(); + currentPackageFilterMode = 'scope'; + const applied = applyDefaultScopeIfPresent(); + if (!applied) { + writePackageRelationDiagram(null, currentPackageFilterMode); + filterPackageTable(null); + } }); From 1171d8446b9d87599b4d88e03136a6f870fb7e89 Mon Sep 17 00:00:00 2001 From: irof Date: Sun, 25 Jan 2026 00:31:02 +0900 Subject: [PATCH 30/43] =?UTF-8?q?=E9=96=A2=E9=80=A3=E3=83=95=E3=82=A3?= =?UTF-8?q?=E3=83=AB=E3=82=BF=E3=83=BC=E3=81=AE=E9=81=B8=E6=8A=9E=E5=86=85?= =?UTF-8?q?=E5=AE=B9=E3=82=92=E8=A1=A8=E7=A4=BA=E3=81=99=E3=82=8B=E6=A9=9F?= =?UTF-8?q?=E8=83=BD=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 現在の関連フィルターの選択内容を画面上で表示する処理を追加。選択内容が変わるたびに表示が更新されるようにし、リセット処理や関連モードの切り替えにも対応済み。 --- .../resources/templates/assets/package.js | 28 +++++++++++++++---- .../src/main/resources/templates/package.html | 1 + 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/jig-core/src/main/resources/templates/assets/package.js b/jig-core/src/main/resources/templates/assets/package.js index dabada18b..f7af28b3a 100644 --- a/jig-core/src/main/resources/templates/assets/package.js +++ b/jig-core/src/main/resources/templates/assets/package.js @@ -9,6 +9,7 @@ let lastDiagramEdgeCount = 0; const DEFAULT_MAX_EDGES = 500; let currentPackageFilterFqn = null; let currentRelatedMode = 'direct'; +let currentRelatedFilterFqn = null; function ensureDiagramErrorBox(diagram) { let errorBox = document.getElementById('package-diagram-error'); @@ -149,15 +150,16 @@ function writePackageTable() { input.value = fqn; } currentPackageFilterFqn = fqn; + currentRelatedFilterFqn = null; currentPackageFilterMode = 'scope'; writePackageRelationDiagram(fqn, currentPackageFilterMode); filterPackageTable(fqn); + updateRelatedFilterTarget(); }; const applyRelatedFilter = fqn => { if (input) { input.value = fqn; } - currentPackageFilterFqn = fqn; filterPackageDiagramByFqn(fqn); }; @@ -246,6 +248,12 @@ function filterPackageTableByRelated(fqn) { }); } +function updateRelatedFilterTarget() { + const target = document.getElementById('related-filter-target'); + if (!target) return; + target.textContent = currentRelatedFilterFqn ? currentRelatedFilterFqn : '未選択'; +} + function buildRelatedSet(root, relations) { if (!root) return new Set(); if (currentRelatedMode === 'direct') { @@ -428,10 +436,11 @@ function writePackageRelationDiagram(filterFqn, mode) { } function filterPackageDiagramByFqn(fqn) { - currentPackageFilterFqn = fqn; + currentRelatedFilterFqn = fqn; currentPackageFilterMode = 'related'; writePackageRelationDiagram(fqn, currentPackageFilterMode); filterPackageTableByRelated(fqn); + updateRelatedFilterTarget(); } window.filterPackageDiagram = function (nodeId) { @@ -455,6 +464,7 @@ function setupPackageFilterInput() { currentPackageDepth = 0; currentPackageFilterMode = 'scope'; currentPackageFilterFqn = null; + currentRelatedFilterFqn = null; currentRelatedMode = 'direct'; const relatedSelect = document.getElementById('related-mode-select'); if (relatedSelect) { @@ -463,14 +473,17 @@ function setupPackageFilterInput() { pendingDiagramRender = null; writePackageRelationDiagram(null, currentPackageFilterMode); filterPackageTable(null); + updateRelatedFilterTarget(); }; const applyFilter = () => { const value = input.value.trim(); currentPackageFilterFqn = value || null; + currentRelatedFilterFqn = null; currentPackageFilterMode = 'scope'; writePackageRelationDiagram(value || null, currentPackageFilterMode); filterPackageTable(value || null); + updateRelatedFilterTarget(); }; applyButton.addEventListener('click', applyFilter); @@ -512,13 +525,15 @@ function setupPackageDepthControl() { select.value = String(currentPackageDepth); select.addEventListener('change', () => { currentPackageDepth = Number(select.value); - const value = document.getElementById('package-filter-input')?.value.trim() || null; - writePackageRelationDiagram(value, currentPackageFilterMode); if (currentPackageFilterMode === 'related') { - filterPackageTableByRelated(value); + writePackageRelationDiagram(currentRelatedFilterFqn, currentPackageFilterMode); + filterPackageTableByRelated(currentRelatedFilterFqn); } else { + const value = document.getElementById('package-filter-input')?.value.trim() || null; + writePackageRelationDiagram(value, currentPackageFilterMode); filterPackageTable(value); } + updateRelatedFilterTarget(); }); } @@ -551,7 +566,7 @@ function setupRelatedModeControl() { select.addEventListener('change', () => { currentRelatedMode = select.value; if (currentPackageFilterMode === 'related') { - filterPackageDiagramByFqn(currentPackageFilterFqn); + filterPackageDiagramByFqn(currentRelatedFilterFqn); } }); } @@ -569,4 +584,5 @@ document.addEventListener("DOMContentLoaded", function () { writePackageRelationDiagram(null, currentPackageFilterMode); filterPackageTable(null); } + updateRelatedFilterTarget(); }); diff --git a/jig-core/src/main/resources/templates/package.html b/jig-core/src/main/resources/templates/package.html index 25bbbcbea..63cbc3c9c 100644 --- a/jig-core/src/main/resources/templates/package.html +++ b/jig-core/src/main/resources/templates/package.html @@ -33,6 +33,7 @@

パッケージ概要

+ 未選択 From 49486595f820f2dab57a494f4617dc5d1fa1d0d6 Mon Sep 17 00:00:00 2001 From: irof Date: Sun, 25 Jan 2026 00:35:41 +0900 Subject: [PATCH 31/43] =?UTF-8?q?=E3=82=B9=E3=82=B3=E3=83=BC=E3=83=97?= =?UTF-8?q?=E8=A7=A3=E9=99=A4=E3=83=9C=E3=82=BF=E3=83=B3=E3=81=A8=E9=96=A2?= =?UTF-8?q?=E9=80=A3=E8=A7=A3=E9=99=A4=E3=83=9C=E3=82=BF=E3=83=B3=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit パッケージフィルタおよび関連フィルタのリセット処理を独立したボタンに分割。 --- .../resources/templates/assets/package.js | 45 +++++++++---------- .../src/main/resources/templates/package.html | 3 +- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/jig-core/src/main/resources/templates/assets/package.js b/jig-core/src/main/resources/templates/assets/package.js index f7af28b3a..3c42e5509 100644 --- a/jig-core/src/main/resources/templates/assets/package.js +++ b/jig-core/src/main/resources/templates/assets/package.js @@ -452,29 +452,9 @@ window.filterPackageDiagram = function (nodeId) { function setupPackageFilterInput() { const input = document.getElementById('package-filter-input'); const applyButton = document.getElementById('apply-package-filter'); - const resetButton = document.getElementById('reset-package-controls'); + const clearScopeButton = document.getElementById('clear-scope-filter'); const depthSelect = document.getElementById('package-depth-select'); - if (!input || !applyButton || !resetButton) return; - - const resetAll = () => { - input.value = ''; - if (depthSelect) { - depthSelect.value = '0'; - } - currentPackageDepth = 0; - currentPackageFilterMode = 'scope'; - currentPackageFilterFqn = null; - currentRelatedFilterFqn = null; - currentRelatedMode = 'direct'; - const relatedSelect = document.getElementById('related-mode-select'); - if (relatedSelect) { - relatedSelect.value = currentRelatedMode; - } - pendingDiagramRender = null; - writePackageRelationDiagram(null, currentPackageFilterMode); - filterPackageTable(null); - updateRelatedFilterTarget(); - }; + if (!input || !applyButton || !clearScopeButton) return; const applyFilter = () => { const value = input.value.trim(); @@ -485,9 +465,17 @@ function setupPackageFilterInput() { filterPackageTable(value || null); updateRelatedFilterTarget(); }; + const clearScope = () => { + input.value = ''; + currentPackageFilterFqn = null; + currentPackageFilterMode = 'scope'; + writePackageRelationDiagram(null, currentPackageFilterMode); + filterPackageTable(null); + updateRelatedFilterTarget(); + }; applyButton.addEventListener('click', applyFilter); - resetButton.addEventListener('click', resetAll); + clearScopeButton.addEventListener('click', clearScope); input.addEventListener('keydown', event => { if (event.key === 'Enter') { event.preventDefault(); @@ -561,6 +549,7 @@ function applyDefaultScopeIfPresent() { function setupRelatedModeControl() { const select = document.getElementById('related-mode-select'); + const clearButton = document.getElementById('clear-related-filter'); if (!select) return; select.value = currentRelatedMode; select.addEventListener('change', () => { @@ -569,6 +558,16 @@ function setupRelatedModeControl() { filterPackageDiagramByFqn(currentRelatedFilterFqn); } }); + if (clearButton) { + clearButton.addEventListener('click', () => { + currentRelatedFilterFqn = null; + currentPackageFilterMode = 'scope'; + const scopeValue = document.getElementById('package-filter-input')?.value.trim() || null; + writePackageRelationDiagram(scopeValue || null, currentPackageFilterMode); + filterPackageTable(scopeValue || null); + updateRelatedFilterTarget(); + }); + } } document.addEventListener("DOMContentLoaded", function () { diff --git a/jig-core/src/main/resources/templates/package.html b/jig-core/src/main/resources/templates/package.html index 63cbc3c9c..6c6a02ecd 100644 --- a/jig-core/src/main/resources/templates/package.html +++ b/jig-core/src/main/resources/templates/package.html @@ -25,6 +25,7 @@

パッケージ概要

+
@@ -34,8 +35,8 @@

パッケージ概要

未選択 +
-


From 5c47c24e4f918e0dfb373f0f548679ddce5e9c9c Mon Sep 17 00:00:00 2001
From: irof 
Date: Sun, 25 Jan 2026 00:40:31 +0900
Subject: [PATCH 32/43] =?UTF-8?q?=E3=83=87=E3=83=95=E3=82=A9=E3=83=AB?=
 =?UTF-8?q?=E3=83=88=E3=81=AE=E3=82=B9=E3=82=B3=E3=83=BC=E3=83=97=E3=83=95?=
 =?UTF-8?q?=E3=82=A3=E3=83=AB=E3=82=BF=E3=82=92=E6=9C=AB=E5=B0=BEdomain?=
 =?UTF-8?q?=E3=81=AB=E3=81=99=E3=82=8B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../src/main/resources/templates/assets/package.js | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/jig-core/src/main/resources/templates/assets/package.js b/jig-core/src/main/resources/templates/assets/package.js
index 3c42e5509..96859b231 100644
--- a/jig-core/src/main/resources/templates/assets/package.js
+++ b/jig-core/src/main/resources/templates/assets/package.js
@@ -529,11 +529,17 @@ function applyDefaultScopeIfPresent() {
     const input = document.getElementById('package-filter-input');
     if (!input || input.value.trim()) return false;
     const {packages} = readPackageSummaryData();
-    const domainCandidates = packages
+    const domainRoots = packages
         .map(item => item.fqn)
-        .filter(fqn => /\.domain(\.|$)/.test(fqn));
-    if (domainCandidates.length === 0) return false;
-    const candidate = domainCandidates.reduce((best, current) => {
+        .map(fqn => {
+            const parts = fqn.split('.');
+            const domainIndex = parts.indexOf('domain');
+            if (domainIndex === -1) return null;
+            return parts.slice(0, domainIndex + 1).join('.');
+        })
+        .filter(Boolean);
+    if (domainRoots.length === 0) return false;
+    const candidate = domainRoots.reduce((best, current) => {
         const bestDepth = best.split('.').length;
         const currentDepth = current.split('.').length;
         return currentDepth < bestDepth ? current : best;

From bd27e96cee7e98666f2ac9d43f2eaa6f1183b9f9 Mon Sep 17 00:00:00 2001
From: irof 
Date: Sun, 25 Jan 2026 00:46:01 +0900
Subject: [PATCH 33/43] =?UTF-8?q?=E5=9B=B3=E3=81=AE=E5=90=91=E3=81=8D?=
 =?UTF-8?q?=E5=88=87=E3=82=8A=E6=9B=BF=E3=81=88=E6=A9=9F=E8=83=BD=E3=82=92?=
 =?UTF-8?q?=E8=BF=BD=E5=8A=A0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

パッケージ図の向きを縦(TD)または横(LR)に切り替え可能にするラジオボタンを追加。
関連コードをリファクタリングし、処理の一貫性を向上。
---
 .../resources/templates/assets/package.js     | 62 +++++++++++--------
 .../main/resources/templates/assets/style.css |  5 ++
 .../src/main/resources/templates/package.html | 11 ++++
 3 files changed, 53 insertions(+), 25 deletions(-)

diff --git a/jig-core/src/main/resources/templates/assets/package.js b/jig-core/src/main/resources/templates/assets/package.js
index 96859b231..ae007a055 100644
--- a/jig-core/src/main/resources/templates/assets/package.js
+++ b/jig-core/src/main/resources/templates/assets/package.js
@@ -10,6 +10,7 @@ const DEFAULT_MAX_EDGES = 500;
 let currentPackageFilterFqn = null;
 let currentRelatedMode = 'direct';
 let currentRelatedFilterFqn = null;
+let currentDiagramDirection = 'TD';
 
 function ensureDiagramErrorBox(diagram) {
     let errorBox = document.getElementById('package-diagram-error');
@@ -152,8 +153,7 @@ function writePackageTable() {
         currentPackageFilterFqn = fqn;
         currentRelatedFilterFqn = null;
         currentPackageFilterMode = 'scope';
-        writePackageRelationDiagram(fqn, currentPackageFilterMode);
-        filterPackageTable(fqn);
+        renderCurrentDiagramAndTable();
         updateRelatedFilterTarget();
     };
     const applyRelatedFilter = fqn => {
@@ -296,6 +296,16 @@ function buildRelatedSet(root, relations) {
     return relatedSet;
 }
 
+function renderCurrentDiagramAndTable() {
+    if (currentPackageFilterMode === 'related') {
+        writePackageRelationDiagram(currentRelatedFilterFqn, currentPackageFilterMode);
+        filterPackageTableByRelated(currentRelatedFilterFqn);
+        return;
+    }
+    writePackageRelationDiagram(currentPackageFilterFqn, currentPackageFilterMode);
+    filterPackageTable(currentPackageFilterFqn);
+}
+
 function writePackageRelationDiagram(filterFqn, mode) {
     const diagram = document.getElementById('package-relation-diagram');
     if (!diagram) return;
@@ -304,7 +314,7 @@ function writePackageRelationDiagram(filterFqn, mode) {
     const {packages, relations} = readPackageSummaryData();
     const escapeMermaidText = text => text.replace(/"/g, '\\"');
     const nameByFqn = new Map(packages.map(item => [item.fqn, item.name || item.fqn]));
-    const lines = ['graph TD'];
+    const lines = [`graph ${currentDiagramDirection}`];
     const aggregatedRoot = filterFqn ? aggregatePackageFqn(filterFqn, currentPackageDepth) : null;
     const scopePrefix = filterFqn ? `${filterFqn}.` : null;
     const withinScope = fqn => !filterFqn || fqn === filterFqn || fqn.startsWith(scopePrefix);
@@ -438,8 +448,7 @@ function writePackageRelationDiagram(filterFqn, mode) {
 function filterPackageDiagramByFqn(fqn) {
     currentRelatedFilterFqn = fqn;
     currentPackageFilterMode = 'related';
-    writePackageRelationDiagram(fqn, currentPackageFilterMode);
-    filterPackageTableByRelated(fqn);
+    renderCurrentDiagramAndTable();
     updateRelatedFilterTarget();
 }
 
@@ -461,16 +470,14 @@ function setupPackageFilterInput() {
         currentPackageFilterFqn = value || null;
         currentRelatedFilterFqn = null;
         currentPackageFilterMode = 'scope';
-        writePackageRelationDiagram(value || null, currentPackageFilterMode);
-        filterPackageTable(value || null);
+        renderCurrentDiagramAndTable();
         updateRelatedFilterTarget();
     };
     const clearScope = () => {
         input.value = '';
         currentPackageFilterFqn = null;
         currentPackageFilterMode = 'scope';
-        writePackageRelationDiagram(null, currentPackageFilterMode);
-        filterPackageTable(null);
+        renderCurrentDiagramAndTable();
         updateRelatedFilterTarget();
     };
 
@@ -513,14 +520,7 @@ function setupPackageDepthControl() {
     select.value = String(currentPackageDepth);
     select.addEventListener('change', () => {
         currentPackageDepth = Number(select.value);
-        if (currentPackageFilterMode === 'related') {
-            writePackageRelationDiagram(currentRelatedFilterFqn, currentPackageFilterMode);
-            filterPackageTableByRelated(currentRelatedFilterFqn);
-        } else {
-            const value = document.getElementById('package-filter-input')?.value.trim() || null;
-            writePackageRelationDiagram(value, currentPackageFilterMode);
-            filterPackageTable(value);
-        }
+        renderCurrentDiagramAndTable();
         updateRelatedFilterTarget();
     });
 }
@@ -548,8 +548,7 @@ function applyDefaultScopeIfPresent() {
     input.value = candidate;
     currentPackageFilterFqn = candidate;
     currentPackageFilterMode = 'scope';
-    writePackageRelationDiagram(candidate, currentPackageFilterMode);
-    filterPackageTable(candidate);
+    renderCurrentDiagramAndTable();
     return true;
 }
 
@@ -561,21 +560,34 @@ function setupRelatedModeControl() {
     select.addEventListener('change', () => {
         currentRelatedMode = select.value;
         if (currentPackageFilterMode === 'related') {
-            filterPackageDiagramByFqn(currentRelatedFilterFqn);
+            renderCurrentDiagramAndTable();
         }
     });
     if (clearButton) {
         clearButton.addEventListener('click', () => {
             currentRelatedFilterFqn = null;
             currentPackageFilterMode = 'scope';
-            const scopeValue = document.getElementById('package-filter-input')?.value.trim() || null;
-            writePackageRelationDiagram(scopeValue || null, currentPackageFilterMode);
-            filterPackageTable(scopeValue || null);
+            currentPackageFilterFqn = document.getElementById('package-filter-input')?.value.trim() || null;
+            renderCurrentDiagramAndTable();
             updateRelatedFilterTarget();
         });
     }
 }
 
+function setupDiagramDirectionControl() {
+    const radios = document.querySelectorAll('input[name="diagram-direction"]');
+    radios.forEach(radio => {
+        if (radio.value === currentDiagramDirection) {
+            radio.checked = true;
+        }
+        radio.addEventListener('change', () => {
+            if (!radio.checked) return;
+            currentDiagramDirection = radio.value;
+            renderCurrentDiagramAndTable();
+        });
+    });
+}
+
 document.addEventListener("DOMContentLoaded", function () {
     if (!document.body.classList.contains("package-list")) return;
     setupSortableTables();
@@ -583,11 +595,11 @@ document.addEventListener("DOMContentLoaded", function () {
     setupPackageFilterInput();
     setupPackageDepthControl();
     setupRelatedModeControl();
+    setupDiagramDirectionControl();
     currentPackageFilterMode = 'scope';
     const applied = applyDefaultScopeIfPresent();
     if (!applied) {
-        writePackageRelationDiagram(null, currentPackageFilterMode);
-        filterPackageTable(null);
+        renderCurrentDiagramAndTable();
     }
     updateRelatedFilterTarget();
 });
diff --git a/jig-core/src/main/resources/templates/assets/style.css b/jig-core/src/main/resources/templates/assets/style.css
index 82d6647ab..851afd364 100644
--- a/jig-core/src/main/resources/templates/assets/style.css
+++ b/jig-core/src/main/resources/templates/assets/style.css
@@ -263,6 +263,11 @@ label {
 .package-list details.controls .control-label {
     font-weight: 600;
 }
+.package-list details.controls .radio-label {
+    display: inline-flex;
+    align-items: center;
+    gap: 6px;
+}
 .package-list details.controls input[type="text"],
 .package-list details.controls select {
     padding: 6px 8px;
diff --git a/jig-core/src/main/resources/templates/package.html b/jig-core/src/main/resources/templates/package.html
index 6c6a02ecd..50364ad86 100644
--- a/jig-core/src/main/resources/templates/package.html
+++ b/jig-core/src/main/resources/templates/package.html
@@ -37,6 +37,17 @@ 

パッケージ概要

未選択 +
+ 図の向き: + + +


From 5fdf4adee5f5123365e1ebfc789e12efc014026f Mon Sep 17 00:00:00 2001
From: irof 
Date: Sun, 25 Jan 2026 01:09:38 +0900
Subject: [PATCH 34/43] =?UTF-8?q?=E3=83=91=E3=83=83=E3=82=B1=E3=83=BC?=
 =?UTF-8?q?=E3=82=B8=E9=96=A2=E9=80=A3=E5=9B=B3=E3=81=A7=E8=A6=AA=E3=83=91?=
 =?UTF-8?q?=E3=83=83=E3=82=B1=E3=83=BC=E3=82=B8=E3=81=A7=E3=82=B0=E3=83=AB?=
 =?UTF-8?q?=E3=83=BC=E3=83=94=E3=83=B3=E3=82=B0=E3=81=99=E3=82=8B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../resources/templates/assets/package.js     | 81 ++++++++++++++++++-
 .../src/main/resources/templates/package.html |  2 +
 2 files changed, 79 insertions(+), 4 deletions(-)

diff --git a/jig-core/src/main/resources/templates/assets/package.js b/jig-core/src/main/resources/templates/assets/package.js
index ae007a055..bb61bf1de 100644
--- a/jig-core/src/main/resources/templates/assets/package.js
+++ b/jig-core/src/main/resources/templates/assets/package.js
@@ -114,6 +114,23 @@ function aggregatePackageFqn(fqn, depth) {
     return parts.slice(0, depth).join('.');
 }
 
+function commonPrefixDepth(fqns) {
+    if (!fqns || fqns.length === 0) return 0;
+    const firstParts = fqns[0].split('.');
+    let depth = firstParts.length;
+    for (let i = 1; i < fqns.length; i += 1) {
+        const parts = fqns[i].split('.');
+        depth = Math.min(depth, parts.length);
+        for (let j = 0; j < depth; j += 1) {
+            if (parts[j] !== firstParts[j]) {
+                depth = j;
+                break;
+            }
+        }
+    }
+    return depth;
+}
+
 function computeAggregationStats(packages, relations, maxDepth) {
     const stats = new Map();
     for (let depth = 0; depth <= maxDepth; depth += 1) {
@@ -368,6 +385,7 @@ function writePackageRelationDiagram(filterFqn, mode) {
 
     const nodeIdByFqn = new Map();
     packageDiagramNodeIdToFqn = new Map();
+    const nodeLabelById = new Map();
     let nodeIndex = 0;
     const ensureNodeId = fqn => {
         if (nodeIdByFqn.has(fqn)) return nodeIdByFqn.get(fqn);
@@ -375,8 +393,7 @@ function writePackageRelationDiagram(filterFqn, mode) {
         nodeIdByFqn.set(fqn, nodeId);
         packageDiagramNodeIdToFqn.set(nodeId, fqn);
         const label = nameByFqn.get(fqn) || fqn;
-        lines.push(`${nodeId}["${escapeMermaidText(label)}"]`);
-        lines.push(`click ${nodeId} filterPackageDiagram`);
+        nodeLabelById.set(nodeId, label);
         return nodeId;
     };
 
@@ -393,6 +410,7 @@ function writePackageRelationDiagram(filterFqn, mode) {
 
     const linkStyles = [];
     let linkIndex = 0;
+    const edgeLines = [];
     uniqueRelations.forEach(relation => {
         const fromId = ensureNodeId(relation.from);
         const toId = ensureNodeId(relation.to);
@@ -401,14 +419,69 @@ function writePackageRelationDiagram(filterFqn, mode) {
             if (relation.from > relation.to) {
                 return;
             }
-            lines.push(`${fromId} <--> ${toId}`);
+            edgeLines.push(`${fromId} <--> ${toId}`);
             linkStyles.push(`linkStyle ${linkIndex} stroke:red,stroke-width:2px`);
             linkIndex += 1;
             return;
         }
-        lines.push(`${fromId} --> ${toId}`);
+        edgeLines.push(`${fromId} --> ${toId}`);
         linkIndex += 1;
     });
+
+    const visibleFqns = Array.from(visibleSet).sort();
+    const parentFqns = new Set();
+    visibleFqns.forEach(fqn => {
+        const parts = fqn.split('.');
+        for (let i = 1; i < parts.length; i += 1) {
+            const prefix = parts.slice(0, i).join('.');
+            if (visibleSet.has(prefix)) parentFqns.add(prefix);
+        }
+    });
+
+    const addNodeLines = nodeId => {
+        const label = nodeLabelById.get(nodeId);
+        lines.push(`${nodeId}["${escapeMermaidText(label)}"]`);
+        lines.push(`click ${nodeId} filterPackageDiagram`);
+        const fqn = packageDiagramNodeIdToFqn.get(nodeId);
+        if (fqn && parentFqns.has(fqn)) {
+            lines.push(`class ${nodeId} parentPackage`);
+        }
+    };
+
+    const prefixDepth = commonPrefixDepth(visibleFqns);
+    const baseDepth = Math.max(prefixDepth - 1, 0);
+    let groupIndex = 0;
+    const createGroupNode = key => ({key, children: new Map(), nodes: []});
+    const rootGroup = createGroupNode('');
+    visibleFqns.forEach(fqn => {
+        const parts = fqn.split('.');
+        const maxDepth = parts.length;
+        let current = rootGroup;
+        for (let depth = baseDepth + 1; depth <= maxDepth; depth += 1) {
+            const key = parts.slice(0, depth).join('.');
+            if (!current.children.has(key)) {
+                current.children.set(key, createGroupNode(key));
+            }
+            current = current.children.get(key);
+        }
+        current.nodes.push(nodeIdByFqn.get(fqn));
+    });
+    const renderGroup = group => {
+        group.nodes.forEach(addNodeLines);
+        Array.from(group.children.keys()).sort().forEach(key => {
+            const child = group.children.get(key);
+            const groupId = `G${groupIndex++}`;
+            lines.push(`subgraph ${groupId}["${escapeMermaidText(child.key)}"]`);
+            renderGroup(child);
+            lines.push('end');
+        });
+    };
+    renderGroup(rootGroup);
+    if (parentFqns.size > 0) {
+        lines.push('classDef parentPackage fill:#f1f5ff,stroke:#4b6bd6,stroke-width:2px');
+    }
+
+    edgeLines.forEach(line => lines.push(line));
     linkStyles.forEach(styleLine => lines.push(styleLine));
 
     lastDiagramText = lines.join('\n');
diff --git a/jig-core/src/main/resources/templates/package.html b/jig-core/src/main/resources/templates/package.html
index 50364ad86..118e8dfc6 100644
--- a/jig-core/src/main/resources/templates/package.html
+++ b/jig-core/src/main/resources/templates/package.html
@@ -85,6 +85,8 @@ 

パッケージ概要

{"fqn": "com.example.api", "name": "api", "classCount": 3, "description": "Web API endpoints."}, {"fqn": "com.example.domain", "name": "domain", "classCount": 12, "description": "Domain model."}, {"fqn": "com.example.domain.order", "name": "order", "classCount": 5, "description": "Order aggregate."}, + {"fqn": "com.example.domain.order.hoge", "name": "hoge", "classCount": 5, "description": "Order aggregate."}, + {"fqn": "com.example.domain.order.fuga", "name": "fuga", "classCount": 5, "description": "Order aggregate."}, {"fqn": "com.example.domain.customer", "name": "customer", "classCount": 4, "description": "Customer aggregate."}, {"fqn": "com.example.application", "name": "application", "classCount": 6, "description": "Use cases."}, {"fqn": "com.example.infrastructure", "name": "infrastructure", "classCount": 8, "description": "Adapters and persistence."}, From 6f3bd4b8edbfd341b9d0b73cff59ec95bbc241e3 Mon Sep 17 00:00:00 2001 From: irof Date: Sun, 25 Jan 2026 01:17:37 +0900 Subject: [PATCH 35/43] =?UTF-8?q?=E3=82=B0=E3=83=AB=E3=83=BC=E3=83=94?= =?UTF-8?q?=E3=83=B3=E3=82=B0=E3=81=AE=E6=94=B9=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit グループにパッケージが1つの場合はグループを作らない ルートパッケージの色をsubgraphの色にあわせる --- .../resources/templates/assets/package.js | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/jig-core/src/main/resources/templates/assets/package.js b/jig-core/src/main/resources/templates/assets/package.js index bb61bf1de..ca815cdda 100644 --- a/jig-core/src/main/resources/templates/assets/package.js +++ b/jig-core/src/main/resources/templates/assets/package.js @@ -466,19 +466,29 @@ function writePackageRelationDiagram(filterFqn, mode) { } current.nodes.push(nodeIdByFqn.get(fqn)); }); - const renderGroup = group => { + const renderGroup = (group, isRoot) => { group.nodes.forEach(addNodeLines); - Array.from(group.children.keys()).sort().forEach(key => { + const childKeys = Array.from(group.children.keys()).sort(); + if (isRoot && group.nodes.length === 0 && childKeys.length === 1) { + renderGroup(group.children.get(childKeys[0]), false); + return; + } + childKeys.forEach(key => { const child = group.children.get(key); + const childNodeCount = child.nodes.length + child.children.size; + if (childNodeCount <= 1) { + renderGroup(child, false); + return; + } const groupId = `G${groupIndex++}`; lines.push(`subgraph ${groupId}["${escapeMermaidText(child.key)}"]`); - renderGroup(child); + renderGroup(child, false); lines.push('end'); }); }; - renderGroup(rootGroup); + renderGroup(rootGroup, true); if (parentFqns.size > 0) { - lines.push('classDef parentPackage fill:#f1f5ff,stroke:#4b6bd6,stroke-width:2px'); + lines.push('classDef parentPackage fill:#ffffde,stroke:#aaaa00,stroke-width:2px'); } edgeLines.forEach(line => lines.push(line)); From a5f770bbfa256188ed63dcc4a838f47b099c7d3e Mon Sep 17 00:00:00 2001 From: irof Date: Sun, 25 Jan 2026 01:31:15 +0900 Subject: [PATCH 36/43] =?UTF-8?q?=E3=82=A2=E3=82=A4=E3=82=B3=E3=83=B3?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jig-core/src/main/resources/templates/assets/style.css | 3 +++ jig-core/src/main/resources/templates/package.html | 2 ++ 2 files changed, 5 insertions(+) diff --git a/jig-core/src/main/resources/templates/assets/style.css b/jig-core/src/main/resources/templates/assets/style.css index 851afd364..2748df76a 100644 --- a/jig-core/src/main/resources/templates/assets/style.css +++ b/jig-core/src/main/resources/templates/assets/style.css @@ -305,6 +305,9 @@ label { .package-list button.filter-icon:hover { background-color: #f5f5f5; } +.package-list button.icon-only { + cursor: default; +} .package-list button.related-icon { width: 1.8em; height: 1.8em; diff --git a/jig-core/src/main/resources/templates/package.html b/jig-core/src/main/resources/templates/package.html index 118e8dfc6..d54f7b473 100644 --- a/jig-core/src/main/resources/templates/package.html +++ b/jig-core/src/main/resources/templates/package.html @@ -22,12 +22,14 @@

パッケージ概要

凡例: P=パッケージ数 / R=関連数
+
+ From 3de316c21b19524facf4afb874561a1dc98d889c Mon Sep 17 00:00:00 2001 From: irof Date: Sun, 25 Jan 2026 02:11:09 +0900 Subject: [PATCH 41/43] =?UTF-8?q?=E3=82=B9=E3=82=B3=E3=83=BC=E3=83=97?= =?UTF-8?q?=E3=83=95=E3=82=A3=E3=83=AB=E3=82=BF=E3=82=92=E3=83=91=E3=83=83?= =?UTF-8?q?=E3=82=B1=E3=83=BC=E3=82=B8=E3=83=95=E3=82=A3=E3=83=AB=E3=82=BF?= =?UTF-8?q?=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit jsで使用している名前を変更 --- .../resources/templates/assets/package.js | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/jig-core/src/main/resources/templates/assets/package.js b/jig-core/src/main/resources/templates/assets/package.js index 4cac7f531..d7a834a1f 100644 --- a/jig-core/src/main/resources/templates/assets/package.js +++ b/jig-core/src/main/resources/templates/assets/package.js @@ -7,7 +7,7 @@ let pendingDiagramRender = null; let lastDiagramSource = ''; let lastDiagramEdgeCount = 0; const DEFAULT_MAX_EDGES = 500; -let scopeFilterFqn = null; +let packageFilterFqn = null; let relatedFilterMode = 'direct'; let relatedFilterFqn = null; let diagramDirection = 'TD'; @@ -155,12 +155,12 @@ function buildAggregationStats(packages, relations, maxDepth) { return stats; } -function buildAggregationStatsForScope(packages, relations, scopeFqn, maxDepth) { - const scopePrefix = scopeFqn ? `${scopeFqn}.` : null; - const withinScope = fqn => !scopeFqn || fqn === scopeFqn || fqn.startsWith(scopePrefix); - const scopedPackages = packages.filter(item => withinScope(item.fqn)); - const scopedRelations = relations.filter(relation => withinScope(relation.from) && withinScope(relation.to)); - return buildAggregationStats(scopedPackages, scopedRelations, maxDepth); +function buildAggregationStatsForPackageFilter(packages, relations, packageFilterFqn, maxDepth) { + const filterPrefix = packageFilterFqn ? `${packageFilterFqn}.` : null; + const withinFilter = fqn => !packageFilterFqn || fqn === packageFilterFqn || fqn.startsWith(filterPrefix); + const filteredPackages = packages.filter(item => withinFilter(item.fqn)); + const filteredRelations = relations.filter(relation => withinFilter(relation.from) && withinFilter(relation.to)); + return buildAggregationStats(filteredPackages, filteredRelations, maxDepth); } function buildAggregationStatsForRelated(packages, relations, rootFqn, maxDepth) { @@ -195,7 +195,7 @@ function renderPackageTable() { if (input) { input.value = fqn; } - scopeFilterFqn = fqn; + packageFilterFqn = fqn; relatedFilterFqn = null; activeFilterMode = 'scope'; renderDiagramAndTable(); @@ -262,20 +262,20 @@ function renderPackageTable() { }); } -function applyScopeFilterToTable(scopeFqn) { +function applyPackageFilterToTable(packageFilterFqn) { const rows = document.querySelectorAll('#package-table tbody tr'); - const scopePrefix = scopeFqn ? `${scopeFqn}.` : null; + const filterPrefix = packageFilterFqn ? `${packageFilterFqn}.` : null; rows.forEach(row => { const fqnCell = row.querySelector('td.fqn'); const fqn = fqnCell ? fqnCell.textContent : ''; - const visible = !scopeFqn || fqn === scopeFqn || fqn.startsWith(scopePrefix); + const visible = !packageFilterFqn || fqn === packageFilterFqn || fqn.startsWith(filterPrefix); row.classList.toggle('hidden', !visible); }); } function applyRelatedFilterToTable(fqn) { if (!fqn) { - applyScopeFilterToTable(null); + applyPackageFilterToTable(null); return; } const {relations} = getPackageSummaryData(); @@ -345,8 +345,8 @@ function renderDiagramAndTable() { updateAggregationDepthOptions(getMaxPackageDepth()); return; } - renderPackageDiagram(scopeFilterFqn, activeFilterMode); - applyScopeFilterToTable(scopeFilterFqn); + renderPackageDiagram(packageFilterFqn, activeFilterMode); + applyPackageFilterToTable(packageFilterFqn); updateAggregationDepthOptions(getMaxPackageDepth()); } @@ -568,7 +568,7 @@ window.filterPackageDiagram = function (nodeId) { applyRelatedFilter(fqn); }; -function setupScopeFilterControls() { +function setupPackageFilterControls() { const input = document.getElementById('package-filter-input'); const applyButton = document.getElementById('apply-package-filter'); const clearScopeButton = document.getElementById('clear-scope-filter'); @@ -577,7 +577,7 @@ function setupScopeFilterControls() { const applyFilter = () => { const value = input.value.trim(); - scopeFilterFqn = value || null; + packageFilterFqn = value || null; relatedFilterFqn = null; activeFilterMode = 'scope'; renderDiagramAndTable(); @@ -585,7 +585,7 @@ function setupScopeFilterControls() { }; const clearScope = () => { input.value = ''; - scopeFilterFqn = null; + packageFilterFqn = null; activeFilterMode = 'scope'; renderDiagramAndTable(); renderRelatedFilterTarget(); @@ -624,7 +624,7 @@ function updateAggregationDepthOptions(maxDepth) { if (activeFilterMode === 'related') { aggregationStats = buildAggregationStatsForRelated(packages, relations, relatedFilterFqn, maxDepth); } else { - aggregationStats = buildAggregationStatsForScope(packages, relations, scopeFilterFqn, maxDepth); + aggregationStats = buildAggregationStatsForPackageFilter(packages, relations, packageFilterFqn, maxDepth); } select.innerHTML = ''; const noAggregationOption = document.createElement('option'); @@ -667,7 +667,7 @@ function applyDefaultScopeIfPresent() { }); if (!candidate) return false; input.value = candidate; - scopeFilterFqn = candidate; + packageFilterFqn = candidate; activeFilterMode = 'scope'; renderDiagramAndTable(); return true; @@ -688,7 +688,7 @@ function setupRelatedFilterControls() { clearButton.addEventListener('click', () => { relatedFilterFqn = null; activeFilterMode = 'scope'; - scopeFilterFqn = document.getElementById('package-filter-input')?.value.trim() || null; + packageFilterFqn = document.getElementById('package-filter-input')?.value.trim() || null; renderDiagramAndTable(); renderRelatedFilterTarget(); }); @@ -713,7 +713,7 @@ document.addEventListener("DOMContentLoaded", function () { if (!document.body.classList.contains("package-list")) return; setupSortableTables(); renderPackageTable(); - setupScopeFilterControls(); + setupPackageFilterControls(); setupAggregationDepthControl(); setupRelatedFilterControls(); setupDiagramDirectionControls(); From b7676bed74d3deb839268e3c9469f548b21b5b18 Mon Sep 17 00:00:00 2001 From: irof Date: Sun, 25 Jan 2026 02:18:54 +0900 Subject: [PATCH 42/43] =?UTF-8?q?=E3=80=8C=E3=82=B9=E3=82=B3=E3=83=BC?= =?UTF-8?q?=E3=83=97=E3=80=8D=E3=82=92=E6=A0=B9=E7=B5=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/templates/assets/package.js | 60 +++++++++---------- .../main/resources/templates/assets/style.css | 4 +- .../src/main/resources/templates/package.html | 4 +- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/jig-core/src/main/resources/templates/assets/package.js b/jig-core/src/main/resources/templates/assets/package.js index d7a834a1f..cbace8faf 100644 --- a/jig-core/src/main/resources/templates/assets/package.js +++ b/jig-core/src/main/resources/templates/assets/package.js @@ -1,7 +1,7 @@ let packageSummaryCache = null; let diagramNodeIdToFqn = new Map(); let aggregationDepth = 0; -let activeFilterMode = 'scope'; +let activeFilterType = 'package'; let diagramElement = null; let pendingDiagramRender = null; let lastDiagramSource = ''; @@ -197,7 +197,7 @@ function renderPackageTable() { } packageFilterFqn = fqn; relatedFilterFqn = null; - activeFilterMode = 'scope'; + activeFilterType = 'package'; renderDiagramAndTable(); renderRelatedFilterTarget(); }; @@ -211,7 +211,7 @@ function renderPackageTable() { const actionTd = document.createElement('td'); const actionButton = document.createElement('button'); actionButton.type = 'button'; - actionButton.className = 'filter-icon'; + actionButton.className = 'package-filter-icon'; actionButton.setAttribute('aria-label', 'このパッケージで絞り込み'); const actionText = document.createElement('span'); actionText.className = 'screen-reader-only'; @@ -339,13 +339,13 @@ function collectRelatedSet(root, relations) { } function renderDiagramAndTable() { - if (activeFilterMode === 'related') { - renderPackageDiagram(relatedFilterFqn, activeFilterMode); + if (activeFilterType === 'related') { + renderPackageDiagram(relatedFilterFqn, activeFilterType); applyRelatedFilterToTable(relatedFilterFqn); updateAggregationDepthOptions(getMaxPackageDepth()); return; } - renderPackageDiagram(packageFilterFqn, activeFilterMode); + renderPackageDiagram(packageFilterFqn, activeFilterType); applyPackageFilterToTable(packageFilterFqn); updateAggregationDepthOptions(getMaxPackageDepth()); } @@ -360,15 +360,15 @@ function renderPackageDiagram(filterFqn, mode) { const nameByFqn = new Map(packages.map(item => [item.fqn, item.name || item.fqn])); const lines = [`graph ${diagramDirection}`]; const aggregatedRoot = filterFqn ? getAggregatedFqn(filterFqn, aggregationDepth) : null; - const scopePrefix = filterFqn ? `${filterFqn}.` : null; - const withinScope = fqn => !filterFqn || fqn === filterFqn || fqn.startsWith(scopePrefix); - const visiblePackages = mode === 'scope' - ? packages.filter(item => withinScope(item.fqn)) + const packageFilterPrefix = filterFqn ? `${filterFqn}.` : null; + const withinPackageFilter = fqn => !filterFqn || fqn === filterFqn || fqn.startsWith(packageFilterPrefix); + const visiblePackages = mode === 'package' + ? packages.filter(item => withinPackageFilter(item.fqn)) : packages; const visibleSet = new Set(visiblePackages.map(item => getAggregatedFqn(item.fqn, aggregationDepth))); const filteredRelations = relations.filter(relation => { - if (mode !== 'scope') return true; - return withinScope(relation.from) && withinScope(relation.to); + if (mode !== 'package') return true; + return withinPackageFilter(relation.from) && withinPackageFilter(relation.to); }); const visibleRelations = filteredRelations .map(relation => ({ @@ -395,15 +395,15 @@ function renderPackageDiagram(filterFqn, mode) { } visibleSet.clear(); relatedSet.forEach(value => visibleSet.add(value)); - } else if (aggregatedRoot && mode === 'scope') { - const scopedSet = new Set(); + } else if (aggregatedRoot && mode === 'package') { + const filteredSet = new Set(); visibleSet.forEach(value => { if (value === aggregatedRoot || value.startsWith(`${aggregatedRoot}.`)) { - scopedSet.add(value); + filteredSet.add(value); } }); visibleSet.clear(); - scopedSet.forEach(value => visibleSet.add(value)); + filteredSet.forEach(value => visibleSet.add(value)); } uniqueRelations.forEach(relation => { visibleSet.add(relation.from); @@ -557,7 +557,7 @@ function renderPackageDiagram(filterFqn, mode) { function applyRelatedFilter(fqn) { relatedFilterFqn = fqn; - activeFilterMode = 'related'; + activeFilterType = 'related'; renderDiagramAndTable(); renderRelatedFilterTarget(); } @@ -571,28 +571,28 @@ window.filterPackageDiagram = function (nodeId) { function setupPackageFilterControls() { const input = document.getElementById('package-filter-input'); const applyButton = document.getElementById('apply-package-filter'); - const clearScopeButton = document.getElementById('clear-scope-filter'); + const clearPackageButton = document.getElementById('clear-package-filter'); const depthSelect = document.getElementById('package-depth-select'); - if (!input || !applyButton || !clearScopeButton) return; + if (!input || !applyButton || !clearPackageButton) return; const applyFilter = () => { const value = input.value.trim(); packageFilterFqn = value || null; relatedFilterFqn = null; - activeFilterMode = 'scope'; + activeFilterType = 'package'; renderDiagramAndTable(); renderRelatedFilterTarget(); }; - const clearScope = () => { + const clearPackageFilter = () => { input.value = ''; packageFilterFqn = null; - activeFilterMode = 'scope'; + activeFilterType = 'package'; renderDiagramAndTable(); renderRelatedFilterTarget(); }; applyButton.addEventListener('click', applyFilter); - clearScopeButton.addEventListener('click', clearScope); + clearPackageButton.addEventListener('click', clearPackageFilter); input.addEventListener('keydown', event => { if (event.key === 'Enter') { event.preventDefault(); @@ -621,7 +621,7 @@ function updateAggregationDepthOptions(maxDepth) { if (!select) return; const {packages, relations} = getPackageSummaryData(); let aggregationStats; - if (activeFilterMode === 'related') { + if (activeFilterType === 'related') { aggregationStats = buildAggregationStatsForRelated(packages, relations, relatedFilterFqn, maxDepth); } else { aggregationStats = buildAggregationStatsForPackageFilter(packages, relations, packageFilterFqn, maxDepth); @@ -646,7 +646,7 @@ function updateAggregationDepthOptions(maxDepth) { select.value = String(value); } -function applyDefaultScopeIfPresent() { +function applyDefaultPackageFilterIfPresent() { const input = document.getElementById('package-filter-input'); if (!input || input.value.trim()) return false; const {packages} = getPackageSummaryData(); @@ -668,7 +668,7 @@ function applyDefaultScopeIfPresent() { if (!candidate) return false; input.value = candidate; packageFilterFqn = candidate; - activeFilterMode = 'scope'; + activeFilterType = 'package'; renderDiagramAndTable(); return true; } @@ -680,14 +680,14 @@ function setupRelatedFilterControls() { select.value = relatedFilterMode; select.addEventListener('change', () => { relatedFilterMode = select.value; - if (activeFilterMode === 'related') { + if (activeFilterType === 'related') { renderDiagramAndTable(); } }); if (clearButton) { clearButton.addEventListener('click', () => { relatedFilterFqn = null; - activeFilterMode = 'scope'; + activeFilterType = 'package'; packageFilterFqn = document.getElementById('package-filter-input')?.value.trim() || null; renderDiagramAndTable(); renderRelatedFilterTarget(); @@ -717,8 +717,8 @@ document.addEventListener("DOMContentLoaded", function () { setupAggregationDepthControl(); setupRelatedFilterControls(); setupDiagramDirectionControls(); - activeFilterMode = 'scope'; - const applied = applyDefaultScopeIfPresent(); + activeFilterType = 'package'; + const applied = applyDefaultPackageFilterIfPresent(); if (!applied) { renderDiagramAndTable(); } diff --git a/jig-core/src/main/resources/templates/assets/style.css b/jig-core/src/main/resources/templates/assets/style.css index 396dd225f..e45f3fbe0 100644 --- a/jig-core/src/main/resources/templates/assets/style.css +++ b/jig-core/src/main/resources/templates/assets/style.css @@ -292,7 +292,7 @@ label { font-size: 0.9em; color: #555; } -.package-list button.filter-icon { +.package-list button.package-filter-icon { width: 1.8em; height: 1.8em; border-radius: 999px; @@ -302,7 +302,7 @@ label { padding: 0; background-image: url("data:image/svg+xml;utf8,"); } -.package-list button.filter-icon:hover { +.package-list button.package-filter-icon:hover { background-color: #f5f5f5; } .package-list button.icon-only { diff --git a/jig-core/src/main/resources/templates/package.html b/jig-core/src/main/resources/templates/package.html index 1dd2b595b..13feb7e97 100644 --- a/jig-core/src/main/resources/templates/package.html +++ b/jig-core/src/main/resources/templates/package.html @@ -22,11 +22,11 @@

パッケージ概要

凡例: P=パッケージ数 / R=関連数
- + - +
From f0e623577266fcacaf30ef473edc9e755315ee61 Mon Sep 17 00:00:00 2001 From: irof Date: Sun, 25 Jan 2026 02:35:03 +0900 Subject: [PATCH 43/43] =?UTF-8?q?=E7=9B=B8=E4=BA=92=E4=BE=9D=E5=AD=98?= =?UTF-8?q?=E3=81=A8=E5=8E=9F=E5=9B=A0=E3=82=92=E8=A1=A8=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/templates/assets/package.js | 46 +++++++++++++++++++ .../main/resources/templates/assets/style.css | 24 ++++++++++ .../src/main/resources/templates/package.html | 2 + 3 files changed, 72 insertions(+) diff --git a/jig-core/src/main/resources/templates/assets/package.js b/jig-core/src/main/resources/templates/assets/package.js index cbace8faf..b43b7b55c 100644 --- a/jig-core/src/main/resources/templates/assets/package.js +++ b/jig-core/src/main/resources/templates/assets/package.js @@ -350,6 +350,51 @@ function renderDiagramAndTable() { updateAggregationDepthOptions(getMaxPackageDepth()); } +function renderMutualDependencyList(mutualPairs, filteredRelations) { + const container = document.getElementById('mutual-dependency-list'); + if (!container) return; + if (!mutualPairs || mutualPairs.size === 0) { + container.style.display = 'none'; + container.innerHTML = ''; + return; + } + const relationMap = new Map(); + filteredRelations.forEach(relation => { + const from = getAggregatedFqn(relation.from, aggregationDepth); + const to = getAggregatedFqn(relation.to, aggregationDepth); + if (from === to) return; + const key = from < to ? `${from}::${to}` : `${to}::${from}`; + if (!relationMap.has(key)) { + relationMap.set(key, new Set()); + } + relationMap.get(key).add(`${relation.from} -> ${relation.to}`); + }); + + container.style.display = ''; + const title = document.createElement('h2'); + title.textContent = '相互依存と原因'; + const list = document.createElement('ul'); + Array.from(mutualPairs).sort().forEach(key => { + const parts = key.split('::'); + const pairLabel = `${parts[0]} <-> ${parts[1]}`; + const item = document.createElement('li'); + const pair = document.createElement('div'); + pair.className = 'pair'; + pair.textContent = pairLabel; + item.appendChild(pair); + const causes = relationMap.get(key); + if (causes && causes.size > 0) { + const details = document.createElement('pre'); + details.textContent = Array.from(causes).sort().join('\n'); + item.appendChild(details); + } + list.appendChild(item); + }); + container.innerHTML = ''; + container.appendChild(title); + container.appendChild(list); +} + function renderPackageDiagram(filterFqn, mode) { const diagram = document.getElementById('package-relation-diagram'); if (!diagram) return; @@ -520,6 +565,7 @@ function renderPackageDiagram(filterFqn, mode) { edgeLines.forEach(line => lines.push(line)); linkStyles.forEach(styleLine => lines.push(styleLine)); + renderMutualDependencyList(mutualPairs, filteredRelations); lastDiagramSource = lines.join('\n'); lastDiagramEdgeCount = uniqueRelations.length; diff --git a/jig-core/src/main/resources/templates/assets/style.css b/jig-core/src/main/resources/templates/assets/style.css index e45f3fbe0..d42e7dc6f 100644 --- a/jig-core/src/main/resources/templates/assets/style.css +++ b/jig-core/src/main/resources/templates/assets/style.css @@ -323,6 +323,30 @@ label { .package-list button.related-icon:hover { background-color: #f5f5f5; } +.package-list .mutual-dependencies { + margin: 12px 0 16px 0; + padding: 8px 12px; + border: 1px solid #ccc; + background-color: #f9f9f9; +} +.package-list .mutual-dependencies h2 { + margin: 0 0 6px 0; + font-size: 1em; +} +.package-list .mutual-dependencies ul { + margin: 0 0 0 1.2em; + padding: 0; +} +.package-list .mutual-dependencies li { + margin: 6px 0; +} +.package-list .mutual-dependencies .pair { + font-weight: bold; +} +.package-list .mutual-dependencies pre { + margin: 4px 0 0 0; + white-space: pre-wrap; +} .package-list table .col-action { width: 2.5em; } diff --git a/jig-core/src/main/resources/templates/package.html b/jig-core/src/main/resources/templates/package.html index 13feb7e97..421943504 100644 --- a/jig-core/src/main/resources/templates/package.html +++ b/jig-core/src/main/resources/templates/package.html @@ -53,6 +53,7 @@

パッケージ概要


+    
絞り込み関連のみ表示 完全修飾名 名称 クラス数
@@ -99,6 +100,7 @@

パッケージ概要

{"from": "com.example.application", "to": "com.example.domain"}, {"from": "com.example.application", "to": "com.example.infrastructure"}, {"from": "com.example.domain.order", "to": "com.example.domain.customer"}, + {"from": "com.example.domain.customer", "to": "com.example.domain.order.hoge"}, {"from": "com.example.infrastructure", "to": "com.example.domain"}, {"from": "com.example.infrastructure.db", "to": "com.example.infrastructure"} ]