refactor(renderer): move diff rendering into its own module
views.js sat at the project's 750-line limit, so the diff cap in the previous commit pushed it over and every further change would have meant shaving comments elsewhere. That is the file asking for decomposition, which is what the architecture audit says to do. Diff rendering is self-contained: the line cap, the line classifier and the change-map illustration depend on nothing in views.js beyond ui and escapeHtml. They now live in src/renderer/diff-view.js and are registered in index.html and in the three renderer file lists that scan the bridge surface, so anything added there is covered by the existing contract tests. views.js drops from 755 to 714 lines and no source file exceeds 750 again. The nested ternary that classified a diff line became a named function with guard clauses on the way. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
5cecaa080d
commit
d77643c058
+9
-8
@@ -114,8 +114,8 @@ abe5dd6fd68f2970cd19ef134094907c67219061d8fe9a1a08324c78de4ad437 484
|
|||||||
f018383f755352ca448e2ebb1e19b1dba412a3eb793d61e64b02953e300754fd 10538 Publish-ForgeFlow-Release.ps1
|
f018383f755352ca448e2ebb1e19b1dba412a3eb793d61e64b02953e300754fd 10538 Publish-ForgeFlow-Release.ps1
|
||||||
688fff7d2c989adb97ebb7fae38962656b70304a0aa5d27433c56adf7f136de0 4196 Publish-Missing-Binary-Release.ps1
|
688fff7d2c989adb97ebb7fae38962656b70304a0aa5d27433c56adf7f136de0 4196 Publish-Missing-Binary-Release.ps1
|
||||||
42e2f375c2fa9bea138428b85eb2bb93fe8bf6e4a5fc0cd728f2c6713a019df7 10315 README.md
|
42e2f375c2fa9bea138428b85eb2bb93fe8bf6e4a5fc0cd728f2c6713a019df7 10315 README.md
|
||||||
90e99e0fd3b92849215857137ca71307913b2558c159db86a4410e0466c249cd 15677 reports/architecture-audit.json
|
76f3dc50b3cd60f6a078f3e37ac8fb1c7e5d5dd746000d9458c1e7189dc54804 14947 reports/architecture-audit.json
|
||||||
527aa0973a0059ae864d93a4f2393ccbda3ea725ab8036ae2d6ef323a5693fde 1372 reports/architecture-audit.md
|
94915d088fcb8f0e66e31006283de5a43749c4028c64887305a420fa8162565e 1343 reports/architecture-audit.md
|
||||||
509c7bcff5280349bd9f45ed6151f70372bad7010a9ea582c13e2ccab91fe0cd 6272 scripts/acceptance.mjs
|
509c7bcff5280349bd9f45ed6151f70372bad7010a9ea582c13e2ccab91fe0cd 6272 scripts/acceptance.mjs
|
||||||
d0745072321aca2c80f44460974a7926715a9f429164aaf7660dced40b52c736 4790 scripts/apply-binary-update.ps1
|
d0745072321aca2c80f44460974a7926715a9f429164aaf7660dced40b52c736 4790 scripts/apply-binary-update.ps1
|
||||||
404863bcbe7292355662e3a326455df864d7279badc29f90866a3b837420df54 10745 scripts/apply-source-update.ps1
|
404863bcbe7292355662e3a326455df864d7279badc29f90866a3b837420df54 10745 scripts/apply-source-update.ps1
|
||||||
@@ -129,7 +129,7 @@ b83d443f5724ac15393567f3a688aed8315fbe3e5966832c864a9466e0669464 8102
|
|||||||
c76507857292c5713e1c699cf02e24b80265da39af2cecd148034bdb874adbb6 5246 scripts/test-authenticode-chain.ps1
|
c76507857292c5713e1c699cf02e24b80265da39af2cecd148034bdb874adbb6 5246 scripts/test-authenticode-chain.ps1
|
||||||
4393f7dc5f417e6d601a68238f4e26791799a3634acec228fe4d79deaee85eb5 3109 scripts/validate-installed-connections.cjs
|
4393f7dc5f417e6d601a68238f4e26791799a3634acec228fe4d79deaee85eb5 3109 scripts/validate-installed-connections.cjs
|
||||||
e6127e1e62f39c70ddb1abf72f4d7e7b8e3f19ff1f219e1a3660353c2e0cdfac 2411 scripts/verify-release-signatures.mjs
|
e6127e1e62f39c70ddb1abf72f4d7e7b8e3f19ff1f219e1a3660353c2e0cdfac 2411 scripts/verify-release-signatures.mjs
|
||||||
1ad4023136361431eeda4df740ec1be5d00d63be0d7a3533250da68c3971480d 21407 scripts/verify.mjs
|
02832d62cc6a4b9bc4e300a8c4528983272c903498eab77f97bda6467504608d 21420 scripts/verify.mjs
|
||||||
0b9f03ba3c67ff7cdb2916a902ad8ce25e81a7c90b210e4ae52d2ad029efabf3 2353 scripts/write-release-checksums.mjs
|
0b9f03ba3c67ff7cdb2916a902ad8ce25e81a7c90b210e4ae52d2ad029efabf3 2353 scripts/write-release-checksums.mjs
|
||||||
619515f524cb89960370ffcbd3fafd3c0e178b95f69c5868b1dd44777f23ec1e 2081 setup-windows.ps1
|
619515f524cb89960370ffcbd3fafd3c0e178b95f69c5868b1dd44777f23ec1e 2081 setup-windows.ps1
|
||||||
dd613d04b366f2cd071a1685a414016a5fb008082ed1b4cb8b24b79c100f640a 2412 src/main/audit-service.cjs
|
dd613d04b366f2cd071a1685a414016a5fb008082ed1b4cb8b24b79c100f640a 2412 src/main/audit-service.cjs
|
||||||
@@ -182,14 +182,15 @@ a980f2e86d8286ea605a7259b9e9adcf3fda4f657b8d54a5d2d8765a7bbbec13 19065
|
|||||||
094c1b71cc2482a9db250ac175f45f3de68f53277dfbde371a03e61923d00988 75240 src/renderer/assets/itworx-wordmark-light.png
|
094c1b71cc2482a9db250ac175f45f3de68f53277dfbde371a03e61923d00988 75240 src/renderer/assets/itworx-wordmark-light.png
|
||||||
813b8cdeecac43794166f3db9d3c5d2c441e0292f9ab7bd465ba136d6201e95d 82476 src/renderer/assets/itworx-wordmark.png
|
813b8cdeecac43794166f3db9d3c5d2c441e0292f9ab7bd465ba136d6201e95d 82476 src/renderer/assets/itworx-wordmark.png
|
||||||
02ae73c50856c827ed13e929396643699a29ac03114ceef1a8706e2e172578c5 51148 src/renderer/dialogs.js
|
02ae73c50856c827ed13e929396643699a29ac03114ceef1a8706e2e172578c5 51148 src/renderer/dialogs.js
|
||||||
|
dede1f21a06c73a2c2a462a869d27530d85f99baff202a2eb509c57436ad6aec 2732 src/renderer/diff-view.js
|
||||||
eef2f269ba4fbb76bf66ad328d481b461255d0acb753b30878dd4d4eaac57dc6 6924 src/renderer/events.js
|
eef2f269ba4fbb76bf66ad328d481b461255d0acb753b30878dd4d4eaac57dc6 6924 src/renderer/events.js
|
||||||
5b0701cea836286325a9653e265eea05547b47019549efc52edd66749625a10d 1479 src/renderer/index.html
|
c4a71213d412166093f7bd8254b847de4d8beb58c1aaa356a0cdc8d728080326 1524 src/renderer/index.html
|
||||||
06180d9656dd254edfb6949c397f8e313954fc560ddcb22b3a35fce3c3e35655 21350 src/renderer/mock-bridge.js
|
06180d9656dd254edfb6949c397f8e313954fc560ddcb22b3a35fce3c3e35655 21350 src/renderer/mock-bridge.js
|
||||||
870024aff376826a92c9cf7452689cc1ecc5d9034f055bea56734f3f7fcea5e5 28703 src/renderer/mock-deployment-bridge.js
|
870024aff376826a92c9cf7452689cc1ecc5d9034f055bea56734f3f7fcea5e5 28703 src/renderer/mock-deployment-bridge.js
|
||||||
81b0155dd51c7bc8871fd29bd7fdb28857cf11a86ed6d6953238c04c2774800c 20033 src/renderer/mock-repository-bridge.js
|
81b0155dd51c7bc8871fd29bd7fdb28857cf11a86ed6d6953238c04c2774800c 20033 src/renderer/mock-repository-bridge.js
|
||||||
94fa265c2fe9ca8d644f0ce9b620b6f85d9b25dca5802c4e9195b66dcbe80120 6522 src/renderer/operations.js
|
94fa265c2fe9ca8d644f0ce9b620b6f85d9b25dca5802c4e9195b66dcbe80120 6522 src/renderer/operations.js
|
||||||
21b8f3228bddd6da06613857763282d1f88f4eeab3d063f3a63395d7b567f2e4 80004 src/renderer/styles.css
|
21b8f3228bddd6da06613857763282d1f88f4eeab3d063f3a63395d7b567f2e4 80004 src/renderer/styles.css
|
||||||
c6e4bba94b5e4c26793c7e96f287e04e833ef490970bae02bc8dea71919342cb 103195 src/renderer/views.js
|
f06b2ef7cc4d7f6ce83f5227212271c25d7a904f45bb7ab84f06b082bd9298b5 100508 src/renderer/views.js
|
||||||
e9e72c072a5c5d04f59cd6763de0cfbf736c2a5ffa2f722143f3bad2bdbc630b 1411 src/shared/clone-target.cjs
|
e9e72c072a5c5d04f59cd6763de0cfbf736c2a5ffa2f722143f3bad2bdbc630b 1411 src/shared/clone-target.cjs
|
||||||
5d425d5c2f939d0f6beebee7ebb0c77146cb7e318535ba7286ec7081a4dc2269 2497 src/shared/deployment-policy.cjs
|
5d425d5c2f939d0f6beebee7ebb0c77146cb7e318535ba7286ec7081a4dc2269 2497 src/shared/deployment-policy.cjs
|
||||||
029e600229714d033c28e2dcb77817aa8269847001782ae0012960e83ffd183f 3057 src/shared/git-status.cjs
|
029e600229714d033c28e2dcb77817aa8269847001782ae0012960e83ffd183f 3057 src/shared/git-status.cjs
|
||||||
@@ -203,7 +204,7 @@ f8853dce6fdf360d5df2fbe2b6df3e5687630c807fee5ba8436679b34ec737ea 2436
|
|||||||
058aeaa5d9bfe377c7e322f213c7871ecc4151b5d08ef790992f4ee28d857658 743 START-FORGEFLOW-OVERLAY.ps1
|
058aeaa5d9bfe377c7e322f213c7871ecc4151b5d08ef790992f4ee28d857658 743 START-FORGEFLOW-OVERLAY.ps1
|
||||||
f5b0ea887fcdeadec78c1ad49b0ec7979723562f5c0b730703acb77a37281ee0 1009 tests/acceptance.test.mjs
|
f5b0ea887fcdeadec78c1ad49b0ec7979723562f5c0b730703acb77a37281ee0 1009 tests/acceptance.test.mjs
|
||||||
a4e5947204ff6878e601e32477bc85b53cd0153baf95a161c8935b6e5466c257 1155 tests/audit-service.test.mjs
|
a4e5947204ff6878e601e32477bc85b53cd0153baf95a161c8935b6e5466c257 1155 tests/audit-service.test.mjs
|
||||||
64fd88e113f85a56090eb9943b6a3120e10ebbe98e6a1dd1c0da7f73599ba432 16645 tests/browser/forgeflow.spec.mjs
|
0841f6a2515508f8d28562d5c565c1519d357b8579515f890883dc2aec7d7737 16881 tests/browser/forgeflow.spec.mjs
|
||||||
1728c0a7abd92f4d7d9e68df32e4a6b00730555f23795e9b36416795d9d127af 5978 tests/clone-target.test.mjs
|
1728c0a7abd92f4d7d9e68df32e4a6b00730555f23795e9b36416795d9d127af 5978 tests/clone-target.test.mjs
|
||||||
ac17f8bbe9e388b80abef7792c8b184a1fd482c93f13d23a478e433961020f75 17214 tests/config-store.test.mjs
|
ac17f8bbe9e388b80abef7792c8b184a1fd482c93f13d23a478e433961020f75 17214 tests/config-store.test.mjs
|
||||||
f1463326aee79842d265687ae628189ce54e92544600f2bd14073780287cfb14 2502 tests/configuration-backup.test.mjs
|
f1463326aee79842d265687ae628189ce54e92544600f2bd14073780287cfb14 2502 tests/configuration-backup.test.mjs
|
||||||
@@ -222,13 +223,13 @@ e7aebcc0d484a6a59d463d5cb26c11b3ad56e28f6535e7c38a0fe166a41565ea 13690
|
|||||||
681ab7bcd02c4dd98d1d8d2092a3521c489d941131e7ffe5903971b940046474 2403 tests/git-workflows.test.mjs
|
681ab7bcd02c4dd98d1d8d2092a3521c489d941131e7ffe5903971b940046474 2403 tests/git-workflows.test.mjs
|
||||||
d633c59bd910008223c834c6d7f3e5666c685a0881944263ede2d42cc69d3151 18710 tests/gitea-actions.test.mjs
|
d633c59bd910008223c834c6d7f3e5666c685a0881944263ede2d42cc69d3151 18710 tests/gitea-actions.test.mjs
|
||||||
fcc9a063882840dd89d74c2785284c8f2f6a9e5acec482b6d89ed8de62efdb85 9635 tests/inventory-classifier.test.mjs
|
fcc9a063882840dd89d74c2785284c8f2f6a9e5acec482b6d89ed8de62efdb85 9635 tests/inventory-classifier.test.mjs
|
||||||
9643622a03ea0a88fb7d72ce43e469ff4f814902f4b3d2a672990637d66ef075 2009 tests/ipc-contract.test.mjs
|
62b90c21c15b841af30d26ccb0b9e88d25674fa7dbff9dc231dd8a1dddc3d657 2025 tests/ipc-contract.test.mjs
|
||||||
caf98cbd9de9b119dae610ee53fa333a7a11214f34762247452fbb85e8bbf725 2392 tests/log-redaction.test.mjs
|
caf98cbd9de9b119dae610ee53fa333a7a11214f34762247452fbb85e8bbf725 2392 tests/log-redaction.test.mjs
|
||||||
96432a97d313f331694900bf0a2c21e38c20eac96d59147977aeed9055a9e3ad 2287 tests/partial-staging.test.mjs
|
96432a97d313f331694900bf0a2c21e38c20eac96d59147977aeed9055a9e3ad 2287 tests/partial-staging.test.mjs
|
||||||
1b6c920e18a248f78acaed6187197c88ec8d911b62d5e2a9f8ad57b91ae80499 11827 tests/preflight.test.mjs
|
1b6c920e18a248f78acaed6187197c88ec8d911b62d5e2a9f8ad57b91ae80499 11827 tests/preflight.test.mjs
|
||||||
7f2751ea2621f76b5427f442e931344d13e97faa7b6ef3151949bbd6a03097cf 1205 tests/process-error-policy.test.mjs
|
7f2751ea2621f76b5427f442e931344d13e97faa7b6ef3151949bbd6a03097cf 1205 tests/process-error-policy.test.mjs
|
||||||
0cb884cf62c1cb02cf59a81662be055bcb5339d176de85e2a3eeb8e8573e11b3 6435 tests/production-acceptance.test.mjs
|
0cb884cf62c1cb02cf59a81662be055bcb5339d176de85e2a3eeb8e8573e11b3 6435 tests/production-acceptance.test.mjs
|
||||||
d4980aa7d2d364793f38771011383348be472d5447b6ee88a4d9e5f3a3b3ffa9 9911 tests/renderer-workflow.test.mjs
|
9e4064295909405674c945378062b772a370fcc26c855d343c0840f9e18bbb1d 9927 tests/renderer-workflow.test.mjs
|
||||||
2b4956fa4df4624a04117737e57ba74020564330ff71303b5746d8ccc881e880 854 tests/repository-matching.test.mjs
|
2b4956fa4df4624a04117737e57ba74020564330ff71303b5746d8ccc881e880 854 tests/repository-matching.test.mjs
|
||||||
e3c6e0f5c9351fce2ccf96b8abb15de404a0f935633df0b5b1c3fefa207c723b 3993 tests/repository-monitor.test.mjs
|
e3c6e0f5c9351fce2ccf96b8abb15de404a0f935633df0b5b1c3fefa207c723b 3993 tests/repository-monitor.test.mjs
|
||||||
5476f3ba90bc096d4172900d9b54ada7c12da521f8627913d87794eade3cee23 13494 tests/repository-service.test.mjs
|
5476f3ba90bc096d4172900d9b54ada7c12da521f8627913d87794eade3cee23 13494 tests/repository-service.test.mjs
|
||||||
|
|||||||
@@ -1,27 +1,10 @@
|
|||||||
{
|
{
|
||||||
"generatedAt": "2026-08-23T12:44:33.101Z",
|
"generatedAt": "2026-08-23T12:51:31.496Z",
|
||||||
"thresholds": {
|
"thresholds": {
|
||||||
"preferredMaximumLines": 750,
|
"preferredMaximumLines": 750,
|
||||||
"justificationRequiredLines": 1000
|
"justificationRequiredLines": 1000
|
||||||
},
|
},
|
||||||
"over750": [
|
"over750": [],
|
||||||
{
|
|
||||||
"file": "src/renderer/views.js",
|
|
||||||
"lines": 759,
|
|
||||||
"branches": 57,
|
|
||||||
"functions": 159,
|
|
||||||
"ipcHandlers": 0,
|
|
||||||
"responsibilities": [
|
|
||||||
"inventory",
|
|
||||||
"deployment",
|
|
||||||
"git",
|
|
||||||
"renderer",
|
|
||||||
"security",
|
|
||||||
"updates"
|
|
||||||
],
|
|
||||||
"hotspotScore": 97
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"over1000": [],
|
"over1000": [],
|
||||||
"cyclomaticHotspots": [
|
"cyclomaticHotspots": [
|
||||||
{
|
{
|
||||||
@@ -193,9 +176,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file": "src/renderer/views.js",
|
"file": "src/renderer/views.js",
|
||||||
"lines": 759,
|
"lines": 715,
|
||||||
"branches": 57,
|
"branches": 53,
|
||||||
"functions": 159,
|
"functions": 152,
|
||||||
"ipcHandlers": 0,
|
"ipcHandlers": 0,
|
||||||
"responsibilities": [
|
"responsibilities": [
|
||||||
"inventory",
|
"inventory",
|
||||||
@@ -205,7 +188,7 @@
|
|||||||
"security",
|
"security",
|
||||||
"updates"
|
"updates"
|
||||||
],
|
],
|
||||||
"hotspotScore": 97
|
"hotspotScore": 93
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file": "src/main/ipc.cjs",
|
"file": "src/main/ipc.cjs",
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
# ForgeFlow architecture audit
|
# ForgeFlow architecture audit
|
||||||
|
|
||||||
Generated 2026-08-23T12:44:33.101Z. Complexity is a deterministic decision-point count used for hotspot ranking, not a claim of exact McCabe complexity.
|
Generated 2026-08-23T12:51:31.496Z. Complexity is a deterministic decision-point count used for hotspot ranking, not a claim of exact McCabe complexity.
|
||||||
|
|
||||||
## Files above 750 lines
|
## Files above 750 lines
|
||||||
|
|
||||||
| File | Lines | Decisions | Functions | IPC handlers | Responsibilities |
|
| File | Lines | Decisions | Functions | IPC handlers | Responsibilities |
|
||||||
|---|---:|---:|---:|---:|---|
|
|---|---:|---:|---:|---:|---|
|
||||||
| `src/renderer/views.js` | 759 | 57 | 159 | 0 | inventory, deployment, git, renderer, security, updates |
|
No findings.
|
||||||
|
|
||||||
## Files above 1,000 lines
|
## Files above 1,000 lines
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -325,7 +325,7 @@ if (
|
|||||||
)
|
)
|
||||||
throw new Error("PowerShell update helper must start directly with param(.");
|
throw new Error("PowerShell update helper must start directly with param(.");
|
||||||
|
|
||||||
const renderer = (await Promise.all(["app.js", "views.js", "dialogs.js", "operations.js", "actions/shell.js", "actions/inventory.js", "actions/deployment-profile.js", "actions/deployment-operation.js", "actions/setup-and-settings.js", "actions/recovery.js", "actions/command.js", "events.js"].map((file) =>
|
const renderer = (await Promise.all(["app.js", "diff-view.js", "views.js", "dialogs.js", "operations.js", "actions/shell.js", "actions/inventory.js", "actions/deployment-profile.js", "actions/deployment-operation.js", "actions/setup-and-settings.js", "actions/recovery.js", "actions/command.js", "events.js"].map((file) =>
|
||||||
readFile(path.join(root, "src/renderer", file), "utf8"),
|
readFile(path.join(root, "src/renderer", file), "utf8"),
|
||||||
))).join("\n");
|
))).join("\n");
|
||||||
const styles = await readFile(
|
const styles = await readFile(
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
// Rendering a unified diff is a self-contained concern with its own size
|
||||||
|
// limits, kept out of views.js so that file stays within the project's
|
||||||
|
// architecture budget.
|
||||||
|
// A regenerated lock file runs into tens of thousands of lines, and one element
|
||||||
|
// per line freezes the window. Only the rendered view is capped.
|
||||||
|
const DIFF_RENDER_LINE_LIMIT = 2000;
|
||||||
|
|
||||||
|
function diffAtmosphere(diff, allLines = null) {
|
||||||
|
if (!ui.selectedFile) return "";
|
||||||
|
const lines = allLines || String(diff || "").split("\n");
|
||||||
|
const additions = lines.filter(
|
||||||
|
(line) => line.startsWith("+") && !line.startsWith("+++"),
|
||||||
|
).length;
|
||||||
|
const removals = lines.filter(
|
||||||
|
(line) => line.startsWith("-") && !line.startsWith("---"),
|
||||||
|
).length;
|
||||||
|
const extension =
|
||||||
|
String(ui.selectedFile).split(".").pop()?.slice(0, 8).toUpperCase() ||
|
||||||
|
"FILE";
|
||||||
|
return `<div class="diff-atmosphere ${lines.length > 34 ? "dense" : ""}" data-diff-atmosphere aria-hidden="true"><svg viewBox="0 0 360 260" role="presentation"><path class="code-route route-a" d="M38 195 C92 84 178 214 318 74"/><path class="code-route route-b" d="M52 74 C132 8 230 34 310 156"/><g class="code-card"><rect x="110" y="75" width="142" height="106" rx="18"/><path d="M136 108h90M136 128h58M136 148h76"/></g><g class="code-node node-one"><circle cx="48" cy="190" r="15"/><path d="m41 190 5 5 9-12"/></g><g class="code-node node-two"><circle cx="315" cy="76" r="13"/><path d="M308 76h14M315 69v14"/></g><circle class="code-packet packet-one" cx="0" cy="0" r="5"/><circle class="code-packet packet-two" cx="0" cy="0" r="4"/></svg><div class="diff-atmosphere-caption"><span>${escapeHtml(extension)} change map</span><strong><i>+${additions}</i><i>−${removals}</i></strong></div></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function diffLineType(line) {
|
||||||
|
if (line.startsWith("+") && !line.startsWith("+++")) return "add";
|
||||||
|
if (line.startsWith("-") && !line.startsWith("---")) return "remove";
|
||||||
|
return line.startsWith("@@") ? "hunk" : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderDiff(diff) {
|
||||||
|
if (!diff)
|
||||||
|
return '<div class="empty-state"><div class="empty-icon">↔</div><h3>No textual diff</h3><p>Select another file or open the project folder for binary changes.</p></div>';
|
||||||
|
const lines = String(diff).split("\n");
|
||||||
|
const rendered = lines
|
||||||
|
.slice(0, DIFF_RENDER_LINE_LIMIT)
|
||||||
|
.map((line) => `<span class="diff-line ${diffLineType(line)}">${escapeHtml(line) || " "}</span>`)
|
||||||
|
.join("");
|
||||||
|
const hidden = Math.max(0, lines.length - DIFF_RENDER_LINE_LIMIT);
|
||||||
|
const notice = hidden ? `<span class="diff-line hunk">… ${hidden.toLocaleString()} more line${hidden === 1 ? "" : "s"} are not shown. Copy diff and the editor still give you the complete change.</span>` : "";
|
||||||
|
return `${rendered}${notice}${diffAtmosphere(diff, lines)}`;
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
<script defer src="mock-deployment-bridge.js"></script>
|
<script defer src="mock-deployment-bridge.js"></script>
|
||||||
<script defer src="mock-bridge.js"></script>
|
<script defer src="mock-bridge.js"></script>
|
||||||
<script defer src="app.js"></script>
|
<script defer src="app.js"></script>
|
||||||
|
<script defer src="diff-view.js"></script>
|
||||||
<script defer src="views.js"></script>
|
<script defer src="views.js"></script>
|
||||||
<script defer src="dialogs.js"></script>
|
<script defer src="dialogs.js"></script>
|
||||||
<script defer src="operations.js"></script>
|
<script defer src="operations.js"></script>
|
||||||
|
|||||||
@@ -206,50 +206,6 @@ function linkedWorkloadsForRepository(repository) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// A regenerated lock file is an ordinary change and runs into tens of thousands
|
|
||||||
// of lines. One element per line freezes the window while it is parsed and laid
|
|
||||||
// out, so the tail is left out of the rendered view only.
|
|
||||||
const DIFF_RENDER_LINE_LIMIT = 2000;
|
|
||||||
|
|
||||||
function diffAtmosphere(diff, allLines = null) {
|
|
||||||
if (!ui.selectedFile) return "";
|
|
||||||
const lines = allLines || String(diff || "").split("\n");
|
|
||||||
const additions = lines.filter(
|
|
||||||
(line) => line.startsWith("+") && !line.startsWith("+++"),
|
|
||||||
).length;
|
|
||||||
const removals = lines.filter(
|
|
||||||
(line) => line.startsWith("-") && !line.startsWith("---"),
|
|
||||||
).length;
|
|
||||||
const extension =
|
|
||||||
String(ui.selectedFile).split(".").pop()?.slice(0, 8).toUpperCase() ||
|
|
||||||
"FILE";
|
|
||||||
return `<div class="diff-atmosphere ${lines.length > 34 ? "dense" : ""}" data-diff-atmosphere aria-hidden="true"><svg viewBox="0 0 360 260" role="presentation"><path class="code-route route-a" d="M38 195 C92 84 178 214 318 74"/><path class="code-route route-b" d="M52 74 C132 8 230 34 310 156"/><g class="code-card"><rect x="110" y="75" width="142" height="106" rx="18"/><path d="M136 108h90M136 128h58M136 148h76"/></g><g class="code-node node-one"><circle cx="48" cy="190" r="15"/><path d="m41 190 5 5 9-12"/></g><g class="code-node node-two"><circle cx="315" cy="76" r="13"/><path d="M308 76h14M315 69v14"/></g><circle class="code-packet packet-one" cx="0" cy="0" r="5"/><circle class="code-packet packet-two" cx="0" cy="0" r="4"/></svg><div class="diff-atmosphere-caption"><span>${escapeHtml(extension)} change map</span><strong><i>+${additions}</i><i>−${removals}</i></strong></div></div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderDiff(diff) {
|
|
||||||
if (!diff)
|
|
||||||
return '<div class="empty-state"><div class="empty-icon">↔</div><h3>No textual diff</h3><p>Select another file or open the project folder for binary changes.</p></div>';
|
|
||||||
const lines = String(diff).split("\n");
|
|
||||||
const rendered = lines
|
|
||||||
.slice(0, DIFF_RENDER_LINE_LIMIT)
|
|
||||||
.map((line) => {
|
|
||||||
const type =
|
|
||||||
line.startsWith("+") && !line.startsWith("+++")
|
|
||||||
? "add"
|
|
||||||
: line.startsWith("-") && !line.startsWith("---")
|
|
||||||
? "remove"
|
|
||||||
: line.startsWith("@@")
|
|
||||||
? "hunk"
|
|
||||||
: "";
|
|
||||||
return `<span class="diff-line ${type}">${escapeHtml(line) || " "}</span>`;
|
|
||||||
})
|
|
||||||
.join("");
|
|
||||||
const hidden = Math.max(0, lines.length - DIFF_RENDER_LINE_LIMIT);
|
|
||||||
const notice = hidden
|
|
||||||
? `<span class="diff-line hunk">… ${hidden.toLocaleString()} more line${hidden === 1 ? "" : "s"} are not shown. Copy diff and the editor still give you the complete change.</span>`
|
|
||||||
: "";
|
|
||||||
return `${rendered}${notice}${diffAtmosphere(diff, lines)}`;
|
|
||||||
}
|
|
||||||
function fileStatusCode(file) {
|
function fileStatusCode(file) {
|
||||||
if (file.conflict) return "U";
|
if (file.conflict) return "U";
|
||||||
if (file.untracked) return "?";
|
if (file.untracked) return "?";
|
||||||
|
|||||||
@@ -281,6 +281,9 @@ test("a very large diff is capped instead of freezing the window", async ({ page
|
|||||||
});
|
});
|
||||||
expect(selected, "the demo needs a repository with local changes").not.toBeNull();
|
expect(selected, "the demo needs a repository with local changes").not.toBeNull();
|
||||||
await expect(page.locator(".diff-view")).toBeVisible();
|
await expect(page.locator(".diff-view")).toBeVisible();
|
||||||
|
// Selecting a repository loads its diff asynchronously; that load would
|
||||||
|
// otherwise overwrite the diff injected below.
|
||||||
|
await expect.poll(() => page.evaluate(() => Boolean(ui.diff) && !ui.diff.startsWith("Loading"))).toBe(true);
|
||||||
|
|
||||||
const measured = await page.evaluate(() => {
|
const measured = await page.evaluate(() => {
|
||||||
const newline = String.fromCharCode(10);
|
const newline = String.fromCharCode(10);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import assert from "node:assert/strict";
|
|||||||
import { readFile } from "node:fs/promises";
|
import { readFile } from "node:fs/promises";
|
||||||
|
|
||||||
async function rendererSource() {
|
async function rendererSource() {
|
||||||
return (await Promise.all(["app.js", "views.js", "dialogs.js", "operations.js", "actions/shell.js", "actions/inventory.js", "actions/deployment-profile.js", "actions/deployment-operation.js", "actions/setup-and-settings.js", "actions/recovery.js", "actions/command.js", "events.js"].map((file) => readFile(new URL(`../src/renderer/${file}`, import.meta.url), "utf8")))).join("\n");
|
return (await Promise.all(["app.js", "diff-view.js", "views.js", "dialogs.js", "operations.js", "actions/shell.js", "actions/inventory.js", "actions/deployment-profile.js", "actions/deployment-operation.js", "actions/setup-and-settings.js", "actions/recovery.js", "actions/command.js", "events.js"].map((file) => readFile(new URL(`../src/renderer/${file}`, import.meta.url), "utf8")))).join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
test("every preload invoke channel has a registered IPC handler", async () => {
|
test("every preload invoke channel has a registered IPC handler", async () => {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import test from "node:test";
|
|||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
import { readFile } from "node:fs/promises";
|
import { readFile } from "node:fs/promises";
|
||||||
|
|
||||||
const rendererFiles = ["app.js", "views.js", "dialogs.js", "operations.js", "actions/shell.js", "actions/inventory.js", "actions/deployment-profile.js", "actions/deployment-operation.js", "actions/setup-and-settings.js", "actions/recovery.js", "actions/command.js", "events.js"];
|
const rendererFiles = ["app.js", "diff-view.js", "views.js", "dialogs.js", "operations.js", "actions/shell.js", "actions/inventory.js", "actions/deployment-profile.js", "actions/deployment-operation.js", "actions/setup-and-settings.js", "actions/recovery.js", "actions/command.js", "events.js"];
|
||||||
async function rendererSource() {
|
async function rendererSource() {
|
||||||
return (await Promise.all(rendererFiles.map((file) => readFile(new URL(`../src/renderer/${file}`, import.meta.url), "utf8")))).join("\n");
|
return (await Promise.all(rendererFiles.map((file) => readFile(new URL(`../src/renderer/${file}`, import.meta.url), "utf8")))).join("\n");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user