From f758f85035c192a4d09c9295be8e793f5373562d Mon Sep 17 00:00:00 2001 From: Michael Fisher Date: Tue, 7 Apr 2026 14:59:20 -0400 Subject: [PATCH 01/14] chore: add @mieweb/ychart dependency to package.json --- package-lock.json | 792 ++++++++++++++++++++++++++++++++++++++++++---- package.json | 1 + 2 files changed, 736 insertions(+), 57 deletions(-) diff --git a/package-lock.json b/package-lock.json index 32a6915..7f52ebd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@fortawesome/free-solid-svg-icons": "^7.0.1", "@fortawesome/react-fontawesome": "^3.0.2", "@mieweb/ui": "^0.2.4", + "@mieweb/ychart": "^1.1.0", "@swc/helpers": "^0.5.17", "firebase-admin": "^13.7.0", "meteor-node-stubs": "^1.2.24", @@ -831,6 +832,114 @@ "node": ">=6.9.0" } }, + "node_modules/@codemirror/autocomplete": { + "version": "6.20.1", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.1.tgz", + "integrity": "sha512-1cvg3Vz1dSSToCNlJfRA2WSI4ht3K+WplO0UMOgmUYPivCyy2oueZY6Lx7M9wThm7SDUBViRmuT+OG/i8+ON9A==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.3.tgz", + "integrity": "sha512-JFRiqhKu+bvSkDLI+rUhJwSxQxYb759W5GBezE8Uc8mHLqC9aV/9aTC7yJSqCtB3F00pylrLCwnyS91Ap5ej4Q==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.6.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/lang-yaml": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@codemirror/lang-yaml/-/lang-yaml-6.1.3.tgz", + "integrity": "sha512-AZ8DJBuXGVHybpBQhmZtgew5//4hv3tdkXnr3vDmOUMJRuB6vn/uuwtmTOTlqEaQFg3hQSVeA90NmvIQyUV6FQ==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.2.0", + "@lezer/lr": "^1.0.0", + "@lezer/yaml": "^1.0.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.3.tgz", + "integrity": "sha512-QwCZW6Tt1siP37Jet9Tb02Zs81TQt6qQrZR2H+eGMcFsL1zMrk2/b9CLC7/9ieP1fjIUMgviLWMmgiHoJrj+ZA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/lint": { + "version": "6.9.5", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.5.tgz", + "integrity": "sha512-GElsbU9G7QT9xXhpUg1zWGmftA/7jamh+7+ydKRuT0ORpWS3wOSP0yT1FOlIZa7mIJjpVPipErsyvVqB9cfTFA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.35.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/search": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.6.0.tgz", + "integrity": "sha512-koFuNXcDvyyotWcgOnZGmY7LZqEOXZaaxD/j6n18TCLx2/9HieZJ5H6hs1g8FiRxBD0DNfs0nXn17g872RmYdw==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.37.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.6.0.tgz", + "integrity": "sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ==", + "license": "MIT", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/theme-one-dark": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz", + "integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/highlight": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.41.0", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.41.0.tgz", + "integrity": "sha512-6H/qadXsVuDY219Yljhohglve8xf4B8xJkVOEWfA5uiYKiTFppjqsvsfR5iPA0RbvRBoOyTZpbLIxe9+0UR8xA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.6.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, "node_modules/@discoveryjs/json-ext": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", @@ -1663,6 +1772,7 @@ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-7.2.0.tgz", "integrity": "sha512-6639htZMjEkwskf3J+e6/iar+4cTNM9qhoWuRfj9F3eJD6r7iCzV1SWnQr2Mdv0QT0suuqU8BoJCZUyCtP9R4Q==", "license": "MIT", + "peer": true, "dependencies": { "@fortawesome/fontawesome-common-types": "7.2.0" }, @@ -2008,7 +2118,6 @@ "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" @@ -2480,6 +2589,47 @@ "dev": true, "license": "MIT" }, + "node_modules/@lezer/common": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.1.tgz", + "integrity": "sha512-6YRVG9vBkaY7p1IVxL4s44n5nUnaNnGM2/AckNgYOnxTG2kWh1vR8BMxPseWPjRNpb5VtXnMpeYAEAADoRV1Iw==", + "license": "MIT" + }, + "node_modules/@lezer/highlight": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", + "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.3.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.8.tgz", + "integrity": "sha512-bPWa0Pgx69ylNlMlPvBPryqeLYQjyJjqPx+Aupm5zydLIF3NE+6MMLT8Yi23Bd9cif9VS00aUebn+6fDIGBcDA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/yaml": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@lezer/yaml/-/yaml-1.0.4.tgz", + "integrity": "sha512-2lrrHqxalACEbxIbsjhqGpSW8kWpUKuY6RHgnSAFZa6qK62wvnPxA8hGOwOoDbwHcOFs5M4o27mjGu+P7TvBmw==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.4.0" + } + }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", + "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", + "license": "MIT" + }, "node_modules/@meteorjs/rspack": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@meteorjs/rspack/-/rspack-1.0.0.tgz", @@ -2538,6 +2688,37 @@ } } }, + "node_modules/@mieweb/ychart": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@mieweb/ychart/-/ychart-1.1.0.tgz", + "integrity": "sha512-wW5GKYzPrDg5uO+qh61KtZbm46k/GJzTU6Xqu3rgD2gjQ1TFUAtK1VqW1FLJZMPHbW9rczc121r9bfbAFV0hwA==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-yaml": "^6.1.2", + "@codemirror/lint": "^6.9.2", + "@codemirror/state": "^6.5.2", + "@codemirror/theme-one-dark": "^6.1.3", + "@codemirror/view": "^6.38.6", + "@mieweb/ui": "^0.2.4", + "bootstrap": "^5.3.8", + "codemirror": "^6.0.2", + "d3": "^7.9.0", + "d3-array": "3", + "d3-drag": "^3.0.0", + "d3-flextree": "^2.1.2", + "d3-hierarchy": "3", + "d3-org-chart": "^3.1.1", + "d3-selection": "3", + "d3-shape": "3", + "d3-zoom": "3", + "js-yaml": "^4.1.1", + "react": "^19.1.0", + "react-dom": "^19.1.0" + }, + "engines": { + "node": ">=24.0.0" + } + }, "node_modules/@module-federation/error-codes": { "version": "0.22.0", "resolved": "https://registry.npmjs.org/@module-federation/error-codes/-/error-codes-0.22.0.tgz", @@ -3407,6 +3588,7 @@ "integrity": "sha512-Pb9GfVlvcmv83T+OG9M1yklNwyesDm5DAlMLGI/Iz8VQd9Q8a30nevE+GBdzTq4255hvgTlx52kNrmJiuGCC5A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.7", "@rspack/dev-server": "~1.1.5", @@ -3426,6 +3608,7 @@ "integrity": "sha512-kRxfY8RRa6nU3/viDvAIP6CRpx+0rfXFRonPL0pHBx8u6HhV7m9rLEyaN6MWsLgNIAWkleFGb7tdo4ux2ljRJQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@module-federation/runtime-tools": "0.22.0", "@rspack/binding": "1.7.1", @@ -4841,7 +5024,6 @@ "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/eslint": "*", "@types/estree": "*" @@ -5013,6 +5195,7 @@ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -5197,6 +5380,7 @@ "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/types": "8.56.1", @@ -5512,7 +5696,6 @@ "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2" @@ -5523,24 +5706,21 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.13.2", @@ -5548,7 +5728,6 @@ "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.13.2", "@webassemblyjs/helper-api-error": "1.13.2", @@ -5560,8 +5739,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.14.1", @@ -5569,7 +5747,6 @@ "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -5583,7 +5760,6 @@ "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" } @@ -5594,7 +5770,6 @@ "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@xtuc/long": "4.2.2" } @@ -5604,8 +5779,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.14.1", @@ -5613,7 +5787,6 @@ "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -5631,7 +5804,6 @@ "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", @@ -5646,7 +5818,6 @@ "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -5660,7 +5831,6 @@ "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-api-error": "1.13.2", @@ -5676,7 +5846,6 @@ "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" @@ -5687,16 +5856,14 @@ "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", "dev": true, - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true, - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/abort-controller": { "version": "3.0.0", @@ -5731,6 +5898,7 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5754,7 +5922,6 @@ "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10.13.0" }, @@ -5926,7 +6093,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, "license": "Python-2.0" }, "node_modules/aria-query": { @@ -6424,6 +6590,25 @@ "multicast-dns": "^7.2.5" } }, + "node_modules/bootstrap": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz", + "integrity": "sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "license": "MIT", + "peerDependencies": { + "@popperjs/core": "^2.11.8" + } + }, "node_modules/bowser": { "version": "2.14.1", "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", @@ -6628,6 +6813,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -6711,8 +6897,7 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/buffer-xor": { "version": "1.0.3", @@ -6903,7 +7088,6 @@ "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6.0" } @@ -7057,6 +7241,21 @@ "node": ">=6" } }, + "node_modules/codemirror": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz", + "integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -7342,6 +7541,12 @@ "dev": true, "license": "MIT" }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -7391,6 +7596,458 @@ "dev": true, "license": "MIT" }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-dsv/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-flextree": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/d3-flextree/-/d3-flextree-2.1.2.tgz", + "integrity": "sha512-gJiHrx5uTTHq44bjyIb3xpbmmdZcWLYPKeO9EPVOq8EylMFOiH2+9sWqKAiQ4DcFuOZTAxPOQyv0Rnmji/g15A==", + "license": "WTFPL", + "dependencies": { + "d3-hierarchy": "^1.1.5" + } + }, + "node_modules/d3-flextree/node_modules/d3-hierarchy": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.9.tgz", + "integrity": "sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-org-chart": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-org-chart/-/d3-org-chart-3.1.1.tgz", + "integrity": "sha512-kDLk3cRqEw5x4KohwZlvTTiPMdY/vo+ROKktyOt9s2Eu0bX/cNlXP9UvM6wXLy9uHmx9D945klSqGlDRXzfK2Q==", + "license": "MIT", + "dependencies": { + "d3-array": "3", + "d3-flextree": "2.1.2", + "d3-hierarchy": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-zoom": "3" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -8149,8 +8806,7 @@ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/es-object-atoms": { "version": "1.1.1", @@ -8301,6 +8957,7 @@ "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -9787,8 +10444,7 @@ "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", "dev": true, - "license": "BSD-2-Clause", - "peer": true + "license": "BSD-2-Clause" }, "node_modules/globals": { "version": "16.5.0", @@ -10509,6 +11165,15 @@ "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/ip-address": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", @@ -11149,7 +11814,6 @@ "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -11165,7 +11829,6 @@ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -11182,6 +11845,7 @@ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "devOptional": true, "license": "MIT", + "peer": true, "bin": { "jiti": "lib/jiti-cli.mjs" } @@ -11206,7 +11870,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -11785,7 +12448,6 @@ "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6.11.5" }, @@ -13800,8 +14462,7 @@ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/node-domexception": { "version": "1.0.0", @@ -14620,6 +15281,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -14822,6 +15484,7 @@ "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -15127,6 +15790,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -15136,6 +15800,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -15156,6 +15821,7 @@ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -15442,6 +16108,12 @@ "dev": true, "license": "MIT" }, + "node_modules/robust-predicates": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", + "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==", + "license": "Unlicense" + }, "node_modules/rollup": { "version": "4.59.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", @@ -15514,6 +16186,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, "node_modules/safe-array-concat": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", @@ -15627,6 +16305,7 @@ "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -15739,7 +16418,6 @@ "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "randombytes": "^2.1.0" } @@ -16272,7 +16950,6 @@ "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -16284,7 +16961,6 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -16828,7 +17504,6 @@ "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", @@ -16848,7 +17523,6 @@ "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", @@ -16883,8 +17557,7 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/thenby": { "version": "1.3.4", @@ -16988,6 +17661,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -17123,7 +17797,8 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" + "license": "0BSD", + "peer": true }, "node_modules/tty-browserify": { "version": "0.0.1", @@ -17266,6 +17941,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -17441,6 +18117,7 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -17534,6 +18211,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -17646,13 +18324,18 @@ "dev": true, "license": "MIT" }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + }, "node_modules/watchpack": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -17972,7 +18655,6 @@ "integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10.13.0" } @@ -17982,8 +18664,7 @@ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/webpack/node_modules/enhanced-resolve": { "version": "5.20.0", @@ -17991,7 +18672,6 @@ "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" @@ -18006,7 +18686,6 @@ "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -18021,7 +18700,6 @@ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=4.0" } @@ -18032,7 +18710,6 @@ "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" }, @@ -18358,6 +19035,7 @@ "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", "devOptional": true, "license": "ISC", + "peer": true, "bin": { "yaml": "bin.mjs" }, diff --git a/package.json b/package.json index ec208d5..6728be5 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@fortawesome/free-solid-svg-icons": "^7.0.1", "@fortawesome/react-fontawesome": "^3.0.2", "@mieweb/ui": "^0.2.4", + "@mieweb/ychart": "^1.1.0", "@swc/helpers": "^0.5.17", "firebase-admin": "^13.7.0", "meteor-node-stubs": "^1.2.24", From ce44db8631687ca42e3ecbf331633750cd3bfab0 Mon Sep 17 00:00:00 2001 From: Michael Fisher Date: Tue, 7 Apr 2026 14:59:52 -0400 Subject: [PATCH 02/14] feat: integrate intitial TeamChart component into TeamsPage for visualizing team structure --- imports/features/teams/TeamChart.tsx | 163 +++++++++++++++++++++++++++ imports/features/teams/TeamsPage.tsx | 24 ++++ 2 files changed, 187 insertions(+) create mode 100644 imports/features/teams/TeamChart.tsx diff --git a/imports/features/teams/TeamChart.tsx b/imports/features/teams/TeamChart.tsx new file mode 100644 index 0000000..a180f09 --- /dev/null +++ b/imports/features/teams/TeamChart.tsx @@ -0,0 +1,163 @@ +/** + * TeamChart — Renders an interactive org chart using @mieweb/ychart (YChartEditor). + * + * Root cause of the getBoundingClientRect crash: + * d3-org-chart (which ychart wraps) attaches a window "resize" listener in + * OrgChart.render() that calls: + * d3.select(attrs.container).node().getBoundingClientRect() + * YChartEditor.destroy() clears the DOM but NEVER calls orgChart.clear(), + * so that resize listener stays attached to window permanently. Any window + * resize event after unmount crashes with null.getBoundingClientRect(). + * + * Fix: + * Call orgChart.clear() before destroy(). This removes both the resize and + * keydown listeners d3-org-chart attached to window. Then patch orgChart.render + * to a no-op as a safety net for any already-queued RAF callbacks. + */ +import '@mieweb/ychart'; +import React, { useEffect, useRef, useMemo } from 'react'; + +type ChartState = { + svgWidth: number; + svgHeight: number; + [key: string]: unknown; +}; + +type OrgChartInstance = { + render: () => OrgChartInstance; + clear?: () => void; + fit?: (params?: { animate?: boolean; scale?: boolean }) => OrgChartInstance; + getChartState?: () => ChartState; +}; + +type YChartInstance = { + initView(containerId: string, yaml: string): YChartInstance; + destroy?: () => void; + handleFit?: () => void; + orgChart?: OrgChartInstance; +}; + +declare global { + interface Window { + YChartEditor: new () => YChartInstance; + } +} + +interface Member { + id: string; + name: string; + email?: string; + isAdmin?: boolean; +} + +interface TeamChartProps { + teamName: string; + members: Member[]; +} + +function buildYaml(teamName: string, members: Member[]): string { + const lines: string[] = []; + lines.push(`- id: 0`); + lines.push(` name: "${teamName}"`); + lines.push(` title: Team`); + members.forEach((m, i) => { + lines.push(`- id: ${i + 1}`); + lines.push(` parentId: 0`); + lines.push(` name: "${m.name}"`); + if (m.isAdmin) lines.push(` title: Admin`); + if (m.email) lines.push(` email: "${m.email}"`); + }); + return lines.join('\n'); +} + +// Inner component: one mount = one initView, one unmount = one clean teardown. +// Remounted via key= in the outer component when data changes. +const TeamChartMount: React.FC<{ yaml: string }> = ({ yaml }) => { + const containerRef = useRef(null); + const instanceRef = useRef(null); + const chartId = useRef( + `tc-${Date.now()}-${Math.random().toString(36).slice(2)}` + ).current; + + useEffect(() => { + const el = containerRef.current!; + el.id = chartId; + let fitTimerId = 0; + + // One RAF so the container has real layout dimensions before initView + const rafId = requestAnimationFrame(() => { + if (!el.isConnected) return; + try { + instanceRef.current = new window.YChartEditor().initView(chartId, yaml); + // d3-org-chart runs an animated initial layout; wait for it to settle + // before calling fit(), otherwise fit sees a partially-laid-out tree and + // the zoom/translate lands in the wrong place. + // d3-org-chart's initial render runs a 400ms animated transition. + // Wait until it completes, then snap-fit with animate:false so no + // second transition can fight the first. + fitTimerId = window.setTimeout(() => { + const oc = instanceRef.current?.orgChart; + if (!oc || !el.isConnected) return; + // ychart defaults svgHeight to window.innerHeight-100, not the + // container height. Correct both dimensions before calling fit so + // the zoom calculation uses the actual visible area. + const rect = el.getBoundingClientRect(); + const state = oc.getChartState?.(); + if (state && rect.width > 0 && rect.height > 0) { + state.svgWidth = rect.width; + state.svgHeight = rect.height; + } + oc.fit?.({ animate: false }); + }, 450); + } catch { + // Unmounted before RAF fired — nothing to clean up + } + }); + + return () => { + cancelAnimationFrame(rafId); + window.clearTimeout(fitTimerId); + + const inst = instanceRef.current; + if (inst) { + // Step 1: Remove d3-org-chart's window resize + keydown listeners. + // Without this, those listeners fire after the DOM is cleared and crash + // on null.getBoundingClientRect(). + inst.orgChart?.clear?.(); + + // Step 2: Patch render to a no-op as a safety net for any already-queued + // RAF callbacks that slip through before clear() takes effect. + if (inst.orgChart) { + const oc = inst.orgChart; + oc.render = () => oc; + } + + // Step 3: Now safe — destroy clears innerHTML and frees React roots + inst.destroy?.(); + instanceRef.current = null; + } + }; + }, []); // eslint-disable-line react-hooks/exhaustive-deps + + return
; +}; + +export const TeamChart: React.FC = ({ teamName, members }) => { + // eslint-disable-next-line react-hooks/exhaustive-deps + const yaml = useMemo(() => buildYaml(teamName, members), [teamName, JSON.stringify(members)]); + + if (members.length === 0) { + return ( +

No members to display.

+ ); + } + + return ( +
+ +
+ ); +}; diff --git a/imports/features/teams/TeamsPage.tsx b/imports/features/teams/TeamsPage.tsx index 710b444..c83cb2a 100644 --- a/imports/features/teams/TeamsPage.tsx +++ b/imports/features/teams/TeamsPage.tsx @@ -52,6 +52,7 @@ import React, { useCallback, useMemo, useState } from 'react'; import { useTeam } from '../../lib/TeamContext'; import { useMethod } from '../../lib/useMethod'; import { Teams } from './api'; +import { TeamChart } from './TeamChart'; // ─── TeamsPage ──────────────────────────────────────────────────────────────── @@ -376,6 +377,29 @@ export const TeamsPage: React.FC = () => { )} + {/* Chart */} + {selectedTeam && !selectedTeam.isPersonal && ( + + + Chart + + + { + const user = members.find((u) => u._id === memberId); + return { + id: memberId, + name: user ? getName(user) : memberId, + email: user?.emails?.[0]?.address, + isAdmin: selectedTeam.admins.includes(memberId), + }; + })} + /> + + + )} + {/* ── Modals ── */} !open && closeModal()} size="md"> From e0890a7cdf514570195df31ccf41a3c31ea0c889 Mon Sep 17 00:00:00 2001 From: Michael Fisher Date: Wed, 8 Apr 2026 10:45:38 -0400 Subject: [PATCH 03/14] feat: enhance TeamChartMount with wrapperRef for improved layout handling --- imports/features/teams/TeamChart.tsx | 18 +++++++++++------- imports/features/teams/TeamsPage.tsx | 2 +- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/imports/features/teams/TeamChart.tsx b/imports/features/teams/TeamChart.tsx index a180f09..c3e59f4 100644 --- a/imports/features/teams/TeamChart.tsx +++ b/imports/features/teams/TeamChart.tsx @@ -72,7 +72,7 @@ function buildYaml(teamName: string, members: Member[]): string { // Inner component: one mount = one initView, one unmount = one clean teardown. // Remounted via key= in the outer component when data changes. -const TeamChartMount: React.FC<{ yaml: string }> = ({ yaml }) => { +const TeamChartMount: React.FC<{ yaml: string; wrapperRef: React.RefObject }> = ({ yaml, wrapperRef }) => { const containerRef = useRef(null); const instanceRef = useRef(null); const chartId = useRef( @@ -88,16 +88,18 @@ const TeamChartMount: React.FC<{ yaml: string }> = ({ yaml }) => { const rafId = requestAnimationFrame(() => { if (!el.isConnected) return; try { + // Hide the outer wrapper while ychart does its animated initial layout + // so the user never sees nodes flying in. + const wrapper = wrapperRef.current; + if (wrapper) wrapper.style.display = 'none'; instanceRef.current = new window.YChartEditor().initView(chartId, yaml); - // d3-org-chart runs an animated initial layout; wait for it to settle - // before calling fit(), otherwise fit sees a partially-laid-out tree and - // the zoom/translate lands in the wrong place. // d3-org-chart's initial render runs a 400ms animated transition. - // Wait until it completes, then snap-fit with animate:false so no - // second transition can fight the first. + // Wait until it completes, then show the wrapper (so + // getBoundingClientRect returns real dimensions), snap-fit, done. fitTimerId = window.setTimeout(() => { const oc = instanceRef.current?.orgChart; if (!oc || !el.isConnected) return; + if (wrapper) wrapper.style.display = ''; // ychart defaults svgHeight to window.innerHeight-100, not the // container height. Correct both dimensions before calling fit so // the zoom calculation uses the actual visible area. @@ -145,6 +147,7 @@ const TeamChartMount: React.FC<{ yaml: string }> = ({ yaml }) => { export const TeamChart: React.FC = ({ teamName, members }) => { // eslint-disable-next-line react-hooks/exhaustive-deps const yaml = useMemo(() => buildYaml(teamName, members), [teamName, JSON.stringify(members)]); + const wrapperRef = useRef(null); if (members.length === 0) { return ( @@ -154,10 +157,11 @@ export const TeamChart: React.FC = ({ teamName, members }) => { return (
- +
); }; diff --git a/imports/features/teams/TeamsPage.tsx b/imports/features/teams/TeamsPage.tsx index c83cb2a..a55c530 100644 --- a/imports/features/teams/TeamsPage.tsx +++ b/imports/features/teams/TeamsPage.tsx @@ -380,7 +380,7 @@ export const TeamsPage: React.FC = () => { {/* Chart */} {selectedTeam && !selectedTeam.isPersonal && ( - + Chart From 3bc0d664243c050a3ba03927995d1340f3816c93 Mon Sep 17 00:00:00 2001 From: Michael Fisher Date: Wed, 8 Apr 2026 14:23:42 -0400 Subject: [PATCH 04/14] fix: remove unused eslint-disable comments for effect dependencies in TimesheetPage, MessagesPage, and TeamChart components --- imports/features/clock/TimesheetPage.tsx | 2 +- imports/features/messages/MessagesPage.tsx | 2 +- imports/features/teams/TeamChart.tsx | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/imports/features/clock/TimesheetPage.tsx b/imports/features/clock/TimesheetPage.tsx index 6522cc3..11baf50 100644 --- a/imports/features/clock/TimesheetPage.tsx +++ b/imports/features/clock/TimesheetPage.tsx @@ -135,7 +135,7 @@ export const TimesheetPage: React.FC = () => { useEffect(() => { fetchData(); - }, [preset]); // eslint-disable-line react-hooks/exhaustive-deps + }, [preset]); const presets: { key: Preset; label: string }[] = [ { key: 'today', label: 'Today' }, diff --git a/imports/features/messages/MessagesPage.tsx b/imports/features/messages/MessagesPage.tsx index 9e5f27a..c056e71 100644 --- a/imports/features/messages/MessagesPage.tsx +++ b/imports/features/messages/MessagesPage.tsx @@ -133,7 +133,7 @@ export const MessagesPage: React.FC = () => { for (const u of users) names[u.id] = u.name; setMemberNames(names); }).catch(() => {}); - }, [selectedTeam]); // eslint-disable-line react-hooks/exhaustive-deps + }, [selectedTeam]); // Send message const sendMessage = useMethod< diff --git a/imports/features/teams/TeamChart.tsx b/imports/features/teams/TeamChart.tsx index c3e59f4..2e2869b 100644 --- a/imports/features/teams/TeamChart.tsx +++ b/imports/features/teams/TeamChart.tsx @@ -139,13 +139,12 @@ const TeamChartMount: React.FC<{ yaml: string; wrapperRef: React.RefObject; }; export const TeamChart: React.FC = ({ teamName, members }) => { - // eslint-disable-next-line react-hooks/exhaustive-deps const yaml = useMemo(() => buildYaml(teamName, members), [teamName, JSON.stringify(members)]); const wrapperRef = useRef(null); From 87ab8c06d79959afebe65ddcf3d571cf356a18ef Mon Sep 17 00:00:00 2001 From: Michael Fisher Date: Wed, 8 Apr 2026 14:24:02 -0400 Subject: [PATCH 05/14] update: REPO_URL to match timehuddles URL on github. --- imports/lib/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imports/lib/constants.ts b/imports/lib/constants.ts index 6a13907..8e6c6a7 100644 --- a/imports/lib/constants.ts +++ b/imports/lib/constants.ts @@ -28,4 +28,4 @@ export const MESSAGES_PENDING_THREAD_KEY = 'app:messagesPendingThread' as const; // ─── Misc ───────────────────────────────────────────────────────────────────── export const REPO_URL = - 'https://github.com/wreiske/meteor-react-tailwind-prettier-starter' as const; + 'https://github.com/mieweb/timehuddle' as const; From fef457ec694946fe954f35d2cb94ceb9edf1df93 Mon Sep 17 00:00:00 2001 From: Michael Fisher Date: Wed, 8 Apr 2026 14:24:30 -0400 Subject: [PATCH 06/14] lint: downgrade fails to warns. to be resolved in another issue. --- eslint.config.mjs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 9ad64b6..14f87ad 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -59,9 +59,11 @@ export default [ ...ts.configs.recommended.rules, 'react/react-in-jsx-scope': 'off', 'react/prop-types': 'off', - 'prettier/prettier': 'error', - 'simple-import-sort/imports': 'error', - 'simple-import-sort/exports': 'error', + 'prettier/prettier': 'warn', + 'simple-import-sort/imports': 'warn', + '@typescript-eslint/no-unused-vars': 'warn', + '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/ban-ts-comment': 'warn', }, }, ]; From c6b187a5cca3188fed051c5391e71c5ef21c7618 Mon Sep 17 00:00:00 2001 From: Michael Fisher Date: Fri, 10 Apr 2026 10:06:52 -0400 Subject: [PATCH 07/14] housekeeping: remove unneeded comments from TeamChart --- imports/features/teams/TeamChart.tsx | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/imports/features/teams/TeamChart.tsx b/imports/features/teams/TeamChart.tsx index 2e2869b..c481bfb 100644 --- a/imports/features/teams/TeamChart.tsx +++ b/imports/features/teams/TeamChart.tsx @@ -1,18 +1,5 @@ /** * TeamChart — Renders an interactive org chart using @mieweb/ychart (YChartEditor). - * - * Root cause of the getBoundingClientRect crash: - * d3-org-chart (which ychart wraps) attaches a window "resize" listener in - * OrgChart.render() that calls: - * d3.select(attrs.container).node().getBoundingClientRect() - * YChartEditor.destroy() clears the DOM but NEVER calls orgChart.clear(), - * so that resize listener stays attached to window permanently. Any window - * resize event after unmount crashes with null.getBoundingClientRect(). - * - * Fix: - * Call orgChart.clear() before destroy(). This removes both the resize and - * keydown listeners d3-org-chart attached to window. Then patch orgChart.render - * to a no-op as a safety net for any already-queued RAF callbacks. */ import '@mieweb/ychart'; import React, { useEffect, useRef, useMemo } from 'react'; From c026bdd6196b6f1e72f0ed7eab69b4f3e6451416 Mon Sep 17 00:00:00 2001 From: Michael Fisher Date: Fri, 10 Apr 2026 10:27:39 -0400 Subject: [PATCH 08/14] housekeeping: avoid runtime error by hydrating at inappropriate times. --- client/main.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/client/main.tsx b/client/main.tsx index 88a27c0..c41de81 100644 --- a/client/main.tsx +++ b/client/main.tsx @@ -60,10 +60,9 @@ Meteor.startup(() => { // Dev inbox — no auth required, no SSR to hydrate. createRoot(el).render(); } else if (window.location.pathname.startsWith('/app')) { - // Hydrate the server-rendered LoginForm. - // The SSR handler renders for /app routes; after hydration - // React can swap to once the user logs in. - hydrateRoot(el, ); + // SSR sends a loading placeholder (not a full React tree), so use + // createRoot — hydrateRoot would throw a hydration mismatch. + createRoot(el).render(); } else { // Unknown routes — no SSR content to hydrate, mount fresh. createRoot(el).render(); From bcd1cacf75d7249abd2949955294a95c2a4db752 Mon Sep 17 00:00:00 2001 From: Michael Fisher Date: Fri, 10 Apr 2026 10:28:16 -0400 Subject: [PATCH 09/14] safety: wrap chart strings in stringify for double quoting. --- imports/features/teams/TeamChart.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/imports/features/teams/TeamChart.tsx b/imports/features/teams/TeamChart.tsx index c481bfb..3de8f89 100644 --- a/imports/features/teams/TeamChart.tsx +++ b/imports/features/teams/TeamChart.tsx @@ -45,14 +45,14 @@ interface TeamChartProps { function buildYaml(teamName: string, members: Member[]): string { const lines: string[] = []; lines.push(`- id: 0`); - lines.push(` name: "${teamName}"`); + lines.push(` name: ${JSON.stringify(teamName)}`); lines.push(` title: Team`); members.forEach((m, i) => { lines.push(`- id: ${i + 1}`); lines.push(` parentId: 0`); - lines.push(` name: "${m.name}"`); + lines.push(` name: ${JSON.stringify(m.name)}`); if (m.isAdmin) lines.push(` title: Admin`); - if (m.email) lines.push(` email: "${m.email}"`); + if (m.email) lines.push(` email: ${JSON.stringify(m.email)}`); }); return lines.join('\n'); } @@ -98,8 +98,9 @@ const TeamChartMount: React.FC<{ yaml: string; wrapperRef: React.RefObject Date: Fri, 10 Apr 2026 10:52:45 -0400 Subject: [PATCH 10/14] fix: replace JSON.stringify members dep with stable memberKey in useMemo --- imports/features/teams/TeamChart.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/imports/features/teams/TeamChart.tsx b/imports/features/teams/TeamChart.tsx index 3de8f89..683228c 100644 --- a/imports/features/teams/TeamChart.tsx +++ b/imports/features/teams/TeamChart.tsx @@ -133,7 +133,8 @@ const TeamChartMount: React.FC<{ yaml: string; wrapperRef: React.RefObject = ({ teamName, members }) => { - const yaml = useMemo(() => buildYaml(teamName, members), [teamName, JSON.stringify(members)]); + const memberKey = members.map((m) => `${m.id}:${m.name}:${m.email ?? ''}:${m.isAdmin ? '1' : '0'}`).join(','); + const yaml = useMemo(() => buildYaml(teamName, members), [teamName, memberKey]); const wrapperRef = useRef(null); if (members.length === 0) { From 2dc1b69870abda6b747c65089a19ff4e9f7dbbbf Mon Sep 17 00:00:00 2001 From: Michael Fisher Date: Fri, 10 Apr 2026 11:07:10 -0400 Subject: [PATCH 11/14] fix: downgrade @mieweb/ychart dependency from 1.1.0 to 1.0.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7f52ebd..6ca1f12 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@fortawesome/free-solid-svg-icons": "^7.0.1", "@fortawesome/react-fontawesome": "^3.0.2", "@mieweb/ui": "^0.2.4", - "@mieweb/ychart": "^1.1.0", + "@mieweb/ychart": "^1.0.0", "@swc/helpers": "^0.5.17", "firebase-admin": "^13.7.0", "meteor-node-stubs": "^1.2.24", diff --git a/package.json b/package.json index 6728be5..3224310 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "@fortawesome/free-solid-svg-icons": "^7.0.1", "@fortawesome/react-fontawesome": "^3.0.2", "@mieweb/ui": "^0.2.4", - "@mieweb/ychart": "^1.1.0", + "@mieweb/ychart": "^1.0.0", "@swc/helpers": "^0.5.17", "firebase-admin": "^13.7.0", "meteor-node-stubs": "^1.2.24", From df2d9b81e44a343d1ad559ff13208bae534640ac Mon Sep 17 00:00:00 2001 From: Michael Fisher Date: Fri, 10 Apr 2026 11:48:21 -0400 Subject: [PATCH 12/14] refactor: simplify TeamChartMount by removing unused wrapperRef prop refactor: add React.Suspense to TeamsPage for lazy loading TeamChart --- imports/features/teams/TeamChart.tsx | 17 ++--------------- imports/features/teams/TeamsPage.tsx | 14 +++++++++----- 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/imports/features/teams/TeamChart.tsx b/imports/features/teams/TeamChart.tsx index 683228c..091466d 100644 --- a/imports/features/teams/TeamChart.tsx +++ b/imports/features/teams/TeamChart.tsx @@ -59,7 +59,7 @@ function buildYaml(teamName: string, members: Member[]): string { // Inner component: one mount = one initView, one unmount = one clean teardown. // Remounted via key= in the outer component when data changes. -const TeamChartMount: React.FC<{ yaml: string; wrapperRef: React.RefObject }> = ({ yaml, wrapperRef }) => { +const TeamChartMount: React.FC<{ yaml: string }> = ({ yaml }) => { const containerRef = useRef(null); const instanceRef = useRef(null); const chartId = useRef( @@ -75,21 +75,10 @@ const TeamChartMount: React.FC<{ yaml: string; wrapperRef: React.RefObject { if (!el.isConnected) return; try { - // Hide the outer wrapper while ychart does its animated initial layout - // so the user never sees nodes flying in. - const wrapper = wrapperRef.current; - if (wrapper) wrapper.style.display = 'none'; instanceRef.current = new window.YChartEditor().initView(chartId, yaml); - // d3-org-chart's initial render runs a 400ms animated transition. - // Wait until it completes, then show the wrapper (so - // getBoundingClientRect returns real dimensions), snap-fit, done. fitTimerId = window.setTimeout(() => { const oc = instanceRef.current?.orgChart; if (!oc || !el.isConnected) return; - if (wrapper) wrapper.style.display = ''; - // ychart defaults svgHeight to window.innerHeight-100, not the - // container height. Correct both dimensions before calling fit so - // the zoom calculation uses the actual visible area. const rect = el.getBoundingClientRect(); const state = oc.getChartState?.(); if (state && rect.width > 0 && rect.height > 0) { @@ -135,7 +124,6 @@ const TeamChartMount: React.FC<{ yaml: string; wrapperRef: React.RefObject = ({ teamName, members }) => { const memberKey = members.map((m) => `${m.id}:${m.name}:${m.email ?? ''}:${m.isAdmin ? '1' : '0'}`).join(','); const yaml = useMemo(() => buildYaml(teamName, members), [teamName, memberKey]); - const wrapperRef = useRef(null); if (members.length === 0) { return ( @@ -145,11 +133,10 @@ export const TeamChart: React.FC = ({ teamName, members }) => { return (
- +
); }; diff --git a/imports/features/teams/TeamsPage.tsx b/imports/features/teams/TeamsPage.tsx index a55c530..b19732a 100644 --- a/imports/features/teams/TeamsPage.tsx +++ b/imports/features/teams/TeamsPage.tsx @@ -52,7 +52,9 @@ import React, { useCallback, useMemo, useState } from 'react'; import { useTeam } from '../../lib/TeamContext'; import { useMethod } from '../../lib/useMethod'; import { Teams } from './api'; -import { TeamChart } from './TeamChart'; +const TeamChart = React.lazy(() => + import('./TeamChart').then((m) => ({ default: m.TeamChart })) +); // ─── TeamsPage ──────────────────────────────────────────────────────────────── @@ -384,9 +386,10 @@ export const TeamsPage: React.FC = () => { Chart
- { +
}> + { const user = members.find((u) => u._id === memberId); return { id: memberId, @@ -395,7 +398,8 @@ export const TeamsPage: React.FC = () => { isAdmin: selectedTeam.admins.includes(memberId), }; })} - /> + /> + )} From 10bb89e8f6afcb429bde1d0f24c8197db2665e88 Mon Sep 17 00:00:00 2001 From: Michael Fisher Date: Fri, 10 Apr 2026 13:39:51 -0400 Subject: [PATCH 13/14] refactor: optimize member lookup by using a Map for usersById --- imports/features/teams/TeamsPage.tsx | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/imports/features/teams/TeamsPage.tsx b/imports/features/teams/TeamsPage.tsx index b19732a..9892c01 100644 --- a/imports/features/teams/TeamsPage.tsx +++ b/imports/features/teams/TeamsPage.tsx @@ -118,6 +118,11 @@ export const TeamsPage: React.FC = () => { [teams], ); + const usersById = useMemo( + () => new Map(members.map((u) => [u._id!, u])), + [members], + ); + // ── Handlers ── const handleCreate = useCallback(async () => { @@ -311,7 +316,7 @@ export const TeamsPage: React.FC = () => {
    {selectedTeam.members.map((memberId) => { - const user = members.find((u) => u._id === memberId); + const user = usersById.get(memberId); const name = user ? getName(user) : memberId; const email = user?.emails?.[0]?.address ?? ''; const isMemberAdmin = selectedTeam.admins.includes(memberId); @@ -390,14 +395,14 @@ export const TeamsPage: React.FC = () => { { - const user = members.find((u) => u._id === memberId); - return { - id: memberId, - name: user ? getName(user) : memberId, - email: user?.emails?.[0]?.address, - isAdmin: selectedTeam.admins.includes(memberId), - }; - })} + const user = usersById.get(memberId); + return { + id: memberId, + name: user ? getName(user) : memberId, + email: user?.emails?.[0]?.address, + isAdmin: selectedTeam.admins.includes(memberId), + }; + })} /> From 2b4ba2a5e9c97b42a4f92d7231f2bca69c61eec9 Mon Sep 17 00:00:00 2001 From: Michael Fisher Date: Fri, 10 Apr 2026 13:47:45 -0400 Subject: [PATCH 14/14] chore: add new dependencies for @popperjs/core, delaunator, and style-mod --- package-lock.json | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/package-lock.json b/package-lock.json index 6ca1f12..7d631be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2848,6 +2848,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -8241,6 +8252,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delaunator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", + "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -17350,6 +17370,12 @@ "license": "MIT", "optional": true }, + "node_modules/style-mod": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", + "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", + "license": "MIT" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",