Merge changes I67c430da,Id4ae2ce1

* changes:
  Parameterize spline drivetrain & slightly detune left/right vel
  Check for instability in trajectory generation
diff --git a/.bazelignore b/.bazelignore
index 0b2e110..145e9e2 100644
--- a/.bazelignore
+++ b/.bazelignore
@@ -1,7 +1,7 @@
 external
 node_modules
+scouting/webserver/requests/messages/node_modules
 scouting/www/node_modules
-scouting/www/counter_button/node_modules
 scouting/www/driver_ranking/node_modules
 scouting/www/entry/node_modules
 scouting/www/match_list/node_modules
diff --git a/WORKSPACE b/WORKSPACE
index 1aabe54..a345ff5 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -912,8 +912,8 @@
     data = [
         "@//:package.json",
         "@//:pnpm-workspace.yaml",
+        "@//scouting/webserver/requests/messages:package.json",
         "@//scouting/www:package.json",
-        "@//scouting/www/counter_button:package.json",
         "@//scouting/www/driver_ranking:package.json",
         "@//scouting/www/entry:package.json",
         "@//scouting/www/match_list:package.json",
diff --git a/frc971/analysis/foxglove.md b/frc971/analysis/foxglove.md
index 8276584..d241ec0 100644
--- a/frc971/analysis/foxglove.md
+++ b/frc971/analysis/foxglove.md
@@ -8,7 +8,7 @@
    is convenient; it won't work when you do not have Internet access, and
    has some limitations when it comes to accessing unsecured websockets.
 2. Download the foxglove desktop application.
-3. Run our local copy by running `bazel run //frc971/analysis:local_foxglove`
+3. Run our local copy by running `bazel run //aos/analysis:local_foxglove`
    This will work offline, and serves foxglove at http://localhost:8000 by
    default.
 
@@ -20,6 +20,9 @@
 This will create an MCAP file at the specified path, which you can then open
 in any of the various foxglove options.
 
+Troubleshooting:
+* If you get the error `Check failed: output_`: Check whether `/tmp/log.mcap` already exists under another owner. If so, use a different filename, e.g. `/tmp/<your_name>_log.mcap`
+
 # Live Visualization
 
 On the pis, we run a `foxglove_websocket` application by default. This exposes
diff --git a/frc971/orin/set_orin_clock.sh b/frc971/orin/set_orin_clock.sh
index 32edb86..bc9dd8f 100755
--- a/frc971/orin/set_orin_clock.sh
+++ b/frc971/orin/set_orin_clock.sh
@@ -6,13 +6,13 @@
 
 ROBOT_PREFIX="9" #71  (Should be one of 79, 89, 99, or 9)
 
-ORIN_LIST="1"
+ORIN_LIST="1 2"
 
 echo "Setting hwclock on Orins"
 
 for orin in $ORIN_LIST; do
     echo "========================================================"
-    echo "Setting clock for ${ROBOT_PREFIX}71.1${orin}"
+    echo "Setting clock for ${ROBOT_PREFIX}71.10${orin}"
     echo "========================================================"
     current_time=`sudo hwclock`
     IFS="."
diff --git a/package.json b/package.json
index 6853a14..f37a2b6 100644
--- a/package.json
+++ b/package.json
@@ -34,5 +34,14 @@
     "typescript": "5.1.6",
     "terser": "5.16.4",
     "zone.js": "^0.13.0"
+  },
+  "pnpm": {
+    "packageExtensions": {
+      "angularx-qrcode": {
+        "peerDependencies": {
+          "@angular/platform-browser": "*"
+        }
+      }
+    }
   }
-}
\ No newline at end of file
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index bf6a429..2ddfb24 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -4,6 +4,8 @@
   autoInstallPeers: true
   excludeLinksFromLockfile: false
 
+packageExtensionsChecksum: 4cac6673474a9277f5f94a4599fee092
+
 importers:
 
   .:
@@ -64,7 +66,7 @@
         version: 2.0.3
       angularx-qrcode:
         specifier: ^16.0.2
-        version: 16.0.2(@angular/core@16.2.12)
+        version: 16.0.2(@angular/core@16.2.12)(@angular/platform-browser@16.2.12)
       cypress:
         specifier: 13.3.1
         version: 13.3.1
@@ -99,27 +101,70 @@
         specifier: ^0.13.0
         version: 0.13.3
 
-  scouting/www: {}
+  scouting/webserver/requests/messages: {}
 
-  scouting/www/counter_button: {}
+  scouting/www:
+    dependencies:
+      '@angular/animations':
+        specifier: v16-lts
+        version: 16.2.12(@angular/core@16.2.12)
+      '@angular/service-worker':
+        specifier: v16-lts
+        version: 16.2.12(@angular/common@16.2.12)(@angular/core@16.2.12)
+      '@org_frc971/scouting/www/driver_ranking':
+        specifier: workspace:*
+        version: link:driver_ranking
+      '@org_frc971/scouting/www/entry':
+        specifier: workspace:*
+        version: link:entry
+      '@org_frc971/scouting/www/match_list':
+        specifier: workspace:*
+        version: link:match_list
+      '@org_frc971/scouting/www/notes':
+        specifier: workspace:*
+        version: link:notes
+      '@org_frc971/scouting/www/pit_scouting':
+        specifier: workspace:*
+        version: link:pit_scouting
+      '@org_frc971/scouting/www/scan':
+        specifier: workspace:*
+        version: link:scan
+      '@org_frc971/scouting/www/shift_schedule':
+        specifier: workspace:*
+        version: link:shift_schedule
+      '@org_frc971/scouting/www/view':
+        specifier: workspace:*
+        version: link:view
 
   scouting/www/driver_ranking:
     dependencies:
       '@angular/forms':
         specifier: v16-lts
         version: 16.2.12(@angular/common@16.2.12)(@angular/core@16.2.12)(@angular/platform-browser@16.2.12)(rxjs@7.5.7)
+      '@org_frc971/scouting/webserver/requests/messages':
+        specifier: workspace:*
+        version: link:../../webserver/requests/messages
 
   scouting/www/entry:
     dependencies:
       '@angular/forms':
         specifier: v16-lts
         version: 16.2.12(@angular/common@16.2.12)(@angular/core@16.2.12)(@angular/platform-browser@16.2.12)(rxjs@7.5.7)
-      '@org_frc971/scouting/www/counter_button':
+      '@angular/platform-browser':
+        specifier: v16-lts
+        version: 16.2.12(@angular/animations@16.2.12)(@angular/common@16.2.12)(@angular/core@16.2.12)
+      '@org_frc971/scouting/webserver/requests/messages':
         specifier: workspace:*
-        version: link:../counter_button
+        version: link:../../webserver/requests/messages
+      '@org_frc971/scouting/www/rpc':
+        specifier: workspace:*
+        version: link:../rpc
       '@types/pako':
         specifier: 2.0.3
         version: 2.0.3
+      angularx-qrcode:
+        specifier: ^16.0.2
+        version: 16.0.2(@angular/core@16.2.12)(@angular/platform-browser@16.2.12)
       pako:
         specifier: 2.1.0
         version: 2.1.0
@@ -129,6 +174,9 @@
       '@angular/forms':
         specifier: v16-lts
         version: 16.2.12(@angular/common@16.2.12)(@angular/core@16.2.12)(@angular/platform-browser@16.2.12)(rxjs@7.5.7)
+      '@org_frc971/scouting/webserver/requests/messages':
+        specifier: workspace:*
+        version: link:../../webserver/requests/messages
       '@org_frc971/scouting/www/rpc':
         specifier: workspace:*
         version: link:../rpc
@@ -138,20 +186,36 @@
       '@angular/forms':
         specifier: v16-lts
         version: 16.2.12(@angular/common@16.2.12)(@angular/core@16.2.12)(@angular/platform-browser@16.2.12)(rxjs@7.5.7)
+      '@org_frc971/scouting/webserver/requests/messages':
+        specifier: workspace:*
+        version: link:../../webserver/requests/messages
 
   scouting/www/pit_scouting:
     dependencies:
       '@angular/forms':
         specifier: v16-lts
         version: 16.2.12(@angular/common@16.2.12)(@angular/core@16.2.12)(@angular/platform-browser@16.2.12)(rxjs@7.5.7)
+      '@org_frc971/scouting/webserver/requests/messages':
+        specifier: workspace:*
+        version: link:../../webserver/requests/messages
 
-  scouting/www/rpc: {}
+  scouting/www/rpc:
+    dependencies:
+      '@org_frc971/scouting/webserver/requests/messages':
+        specifier: workspace:*
+        version: link:../../webserver/requests/messages
+      dexie:
+        specifier: ^3.2.5
+        version: 3.2.5(karma@6.4.3)
 
   scouting/www/scan:
     dependencies:
       '@angular/forms':
         specifier: v16-lts
         version: 16.2.12(@angular/common@16.2.12)(@angular/core@16.2.12)(@angular/platform-browser@16.2.12)(rxjs@7.5.7)
+      '@org_frc971/scouting/webserver/requests/messages':
+        specifier: workspace:*
+        version: link:../../webserver/requests/messages
       '@types/pako':
         specifier: 2.0.3
         version: 2.0.3
@@ -164,12 +228,21 @@
       '@angular/forms':
         specifier: v16-lts
         version: 16.2.12(@angular/common@16.2.12)(@angular/core@16.2.12)(@angular/platform-browser@16.2.12)(rxjs@7.5.7)
+      '@org_frc971/scouting/webserver/requests/messages':
+        specifier: workspace:*
+        version: link:../../webserver/requests/messages
 
   scouting/www/view:
     dependencies:
       '@angular/forms':
         specifier: v16-lts
         version: 16.2.12(@angular/common@16.2.12)(@angular/core@16.2.12)(@angular/platform-browser@16.2.12)(rxjs@7.5.7)
+      '@org_frc971/scouting/webserver/requests/messages':
+        specifier: workspace:*
+        version: link:../../webserver/requests/messages
+      '@org_frc971/scouting/www/rpc':
+        specifier: workspace:*
+        version: link:../rpc
 
 packages:
 
@@ -358,7 +431,6 @@
       '@angular/common': 16.2.12(@angular/core@16.2.12)(rxjs@7.5.7)
       '@angular/core': 16.2.12(rxjs@7.5.7)(zone.js@0.13.3)
       tslib: 2.6.0
-    dev: true
 
   /@babel/cli@7.23.9(@babel/core@7.23.9):
     resolution: {integrity: sha512-vB1UXmGDNEhcf1jNAHKT9IlYk1R+hehVTLFlCLHBi8gfuHQGP6uRjgXVYU0EVlI/qwAWpstqkBdf2aez3/z/5Q==}
@@ -612,7 +684,6 @@
     resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
     engines: {node: '>=0.1.90'}
     requiresBuild: true
-    dev: true
 
   /@cypress/request@3.0.1:
     resolution: {integrity: sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ==}
@@ -972,7 +1043,6 @@
 
   /@socket.io/component-emitter@3.1.0:
     resolution: {integrity: sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==}
-    dev: true
 
   /@tootallnate/once@2.0.0:
     resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==}
@@ -1023,13 +1093,11 @@
 
   /@types/cookie@0.4.1:
     resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==}
-    dev: true
 
   /@types/cors@2.8.17:
     resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==}
     dependencies:
       '@types/node': 20.11.19
-    dev: true
 
   /@types/estree@1.0.5:
     resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
@@ -1053,7 +1121,6 @@
     resolution: {integrity: sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==}
     dependencies:
       undici-types: 5.26.5
-    dev: true
 
   /@types/pako@2.0.3:
     resolution: {integrity: sha512-bq0hMV9opAcrmE0Byyo0fY3Ew4tgOevJmQ9grUhpXQhYfyLJ1Kqg3P33JT5fdbT2AjeAjR51zqqVjAL/HMkx7Q==}
@@ -1092,7 +1159,6 @@
     dependencies:
       mime-types: 2.1.35
       negotiator: 0.6.3
-    dev: true
 
   /acorn@8.9.0:
     resolution: {integrity: sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==}
@@ -1144,15 +1210,16 @@
       uri-js: 4.4.1
     dev: true
 
-  /angularx-qrcode@16.0.2(@angular/core@16.2.12):
+  /angularx-qrcode@16.0.2(@angular/core@16.2.12)(@angular/platform-browser@16.2.12):
     resolution: {integrity: sha512-FztOM7vjNu88sGxUU5jG2I+A9TxZBXXYBWINjpwIBbTL+COMgrtzXnScG7TyQeNknv5w3WFJWn59PcngRRYVXA==}
     peerDependencies:
       '@angular/core': ^16.0.0
+      '@angular/platform-browser': '*'
     dependencies:
       '@angular/core': 16.2.12(rxjs@7.5.7)(zone.js@0.13.3)
+      '@angular/platform-browser': 16.2.12(@angular/animations@16.2.12)(@angular/common@16.2.12)(@angular/core@16.2.12)
       qrcode: 1.5.3
       tslib: 2.6.0
-    dev: true
 
   /ansi-colors@4.1.3:
     resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
@@ -1169,7 +1236,6 @@
   /ansi-regex@5.0.1:
     resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
     engines: {node: '>=8'}
-    dev: true
 
   /ansi-regex@6.0.1:
     resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==}
@@ -1188,7 +1254,6 @@
     engines: {node: '>=8'}
     dependencies:
       color-convert: 2.0.1
-    dev: true
 
   /ansi-styles@6.2.1:
     resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
@@ -1201,7 +1266,6 @@
     dependencies:
       normalize-path: 3.0.0
       picomatch: 2.3.1
-    dev: true
 
   /aproba@2.0.0:
     resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==}
@@ -1258,7 +1322,6 @@
 
   /balanced-match@1.0.2:
     resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
-    dev: true
 
   /base64-js@1.5.1:
     resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
@@ -1267,7 +1330,6 @@
   /base64id@2.0.0:
     resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==}
     engines: {node: ^4.5.0 || >= 5.9}
-    dev: true
 
   /bcrypt-pbkdf@1.0.2:
     resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==}
@@ -1278,7 +1340,6 @@
   /binary-extensions@2.2.0:
     resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
     engines: {node: '>=8'}
-    dev: true
 
   /bl@4.1.0:
     resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
@@ -1314,14 +1375,12 @@
       unpipe: 1.0.0
     transitivePeerDependencies:
       - supports-color
-    dev: true
 
   /brace-expansion@1.1.11:
     resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
     dependencies:
       balanced-match: 1.0.2
       concat-map: 0.0.1
-    dev: true
 
   /brace-expansion@2.0.1:
     resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
@@ -1334,7 +1393,6 @@
     engines: {node: '>=8'}
     dependencies:
       fill-range: 7.0.1
-    dev: true
 
   /browserslist@4.23.0:
     resolution: {integrity: sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==}
@@ -1376,7 +1434,6 @@
   /bytes@3.1.2:
     resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
     engines: {node: '>= 0.8'}
-    dev: true
 
   /cacache@16.1.3:
     resolution: {integrity: sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==}
@@ -1432,12 +1489,10 @@
     dependencies:
       function-bind: 1.1.2
       get-intrinsic: 1.2.1
-    dev: true
 
   /camelcase@5.3.1:
     resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
     engines: {node: '>=6'}
-    dev: true
 
   /caniuse-lite@1.0.30001588:
     resolution: {integrity: sha512-+hVY9jE44uKLkH0SrUTqxjxqNTOWHsbnQDIKjwkZ3lNTzUUVdBLBGXtj/q5Mp5u98r3droaZAewQuEDzjQdZlQ==}
@@ -1486,7 +1541,6 @@
       readdirp: 3.6.0
     optionalDependencies:
       fsevents: 2.3.2
-    dev: true
 
   /chownr@2.0.0:
     resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
@@ -1543,7 +1597,6 @@
       string-width: 4.2.3
       strip-ansi: 6.0.1
       wrap-ansi: 6.2.0
-    dev: true
 
   /cliui@7.0.4:
     resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}
@@ -1551,7 +1604,6 @@
       string-width: 4.2.3
       strip-ansi: 6.0.1
       wrap-ansi: 7.0.0
-    dev: true
 
   /cliui@8.0.1:
     resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
@@ -1578,7 +1630,6 @@
     engines: {node: '>=7.0.0'}
     dependencies:
       color-name: 1.1.4
-    dev: true
 
   /color-name@1.1.3:
     resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==}
@@ -1586,7 +1637,6 @@
 
   /color-name@1.1.4:
     resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
-    dev: true
 
   /color-support@1.1.3:
     resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==}
@@ -1625,7 +1675,6 @@
 
   /concat-map@0.0.1:
     resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
-    dev: true
 
   /connect@3.7.0:
     resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==}
@@ -1637,7 +1686,6 @@
       utils-merge: 1.0.1
     transitivePeerDependencies:
       - supports-color
-    dev: true
 
   /console-control-strings@1.1.0:
     resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==}
@@ -1646,7 +1694,6 @@
   /content-type@1.0.5:
     resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
     engines: {node: '>= 0.6'}
-    dev: true
 
   /convert-source-map@1.9.0:
     resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==}
@@ -1659,7 +1706,6 @@
   /cookie@0.4.2:
     resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==}
     engines: {node: '>= 0.6'}
-    dev: true
 
   /core-util-is@1.0.2:
     resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==}
@@ -1671,7 +1717,6 @@
     dependencies:
       object-assign: 4.1.1
       vary: 1.1.2
-    dev: true
 
   /cross-spawn@7.0.3:
     resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
@@ -1684,7 +1729,6 @@
 
   /custom-event@1.0.1:
     resolution: {integrity: sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==}
-    dev: true
 
   /cypress@13.3.1:
     resolution: {integrity: sha512-g4mJLZxYN+UAF2LMy3Znd4LBnUmS59Vynd81VES59RdW48Yt+QtR2cush3melOoVNz0PPbADpWr8DcUx6mif8Q==}
@@ -1747,7 +1791,6 @@
   /date-format@4.0.14:
     resolution: {integrity: sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==}
     engines: {node: '>=4.0'}
-    dev: true
 
   /dayjs@1.11.9:
     resolution: {integrity: sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==}
@@ -1762,7 +1805,6 @@
         optional: true
     dependencies:
       ms: 2.0.0
-    dev: true
 
   /debug@3.2.7(supports-color@8.1.1):
     resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
@@ -1787,12 +1829,10 @@
     dependencies:
       ms: 2.1.2
       supports-color: 8.1.1
-    dev: true
 
   /decamelize@1.2.0:
     resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
     engines: {node: '>=0.10.0'}
-    dev: true
 
   /deepmerge@4.3.1:
     resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
@@ -1822,12 +1862,10 @@
   /depd@2.0.0:
     resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
     engines: {node: '>= 0.8'}
-    dev: true
 
   /destroy@1.2.0:
     resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
     engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
-    dev: true
 
   /dexie@3.2.5(karma@6.4.3):
     resolution: {integrity: sha512-MA7vYQvXxWN2+G50D0GLS4FqdYUyRYQsN0FikZIVebOmRoNCSCL9+eUbIF80dqrfns3kmY+83+hE2GN9CnAGyA==}
@@ -1836,15 +1874,12 @@
       karma-safari-launcher: 1.0.0(karma@6.4.3)
     transitivePeerDependencies:
       - karma
-    dev: true
 
   /di@0.0.1:
     resolution: {integrity: sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==}
-    dev: true
 
   /dijkstrajs@1.0.3:
     resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==}
-    dev: true
 
   /dom-serialize@2.2.1:
     resolution: {integrity: sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ==}
@@ -1853,7 +1888,6 @@
       ent: 2.2.0
       extend: 3.0.2
       void-elements: 2.0.1
-    dev: true
 
   /eastasianwidth@0.2.0:
     resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
@@ -1868,7 +1902,6 @@
 
   /ee-first@1.1.1:
     resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
-    dev: true
 
   /electron-to-chromium@1.4.679:
     resolution: {integrity: sha512-NhQMsz5k0d6m9z3qAxnsOR/ebal4NAGsrNVRwcDo4Kc/zQ7KdsTKZUxZoygHcVRb0QDW3waEDIcE3isZ79RP6g==}
@@ -1876,7 +1909,6 @@
 
   /emoji-regex@8.0.0:
     resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
-    dev: true
 
   /emoji-regex@9.2.2:
     resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
@@ -1884,12 +1916,10 @@
 
   /encode-utf8@1.0.3:
     resolution: {integrity: sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==}
-    dev: true
 
   /encodeurl@1.0.2:
     resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
     engines: {node: '>= 0.8'}
-    dev: true
 
   /encoding@0.1.13:
     resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==}
@@ -1908,7 +1938,6 @@
   /engine.io-parser@5.2.2:
     resolution: {integrity: sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==}
     engines: {node: '>=10.0.0'}
-    dev: true
 
   /engine.io@6.5.4:
     resolution: {integrity: sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==}
@@ -1928,7 +1957,6 @@
       - bufferutil
       - supports-color
       - utf-8-validate
-    dev: true
 
   /enquirer@2.3.6:
     resolution: {integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==}
@@ -1939,7 +1967,6 @@
 
   /ent@2.2.0:
     resolution: {integrity: sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==}
-    dev: true
 
   /env-paths@2.2.1:
     resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
@@ -1953,11 +1980,9 @@
   /escalade@3.1.1:
     resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
     engines: {node: '>=6'}
-    dev: true
 
   /escape-html@1.0.3:
     resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
-    dev: true
 
   /escape-string-regexp@1.0.5:
     resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
@@ -1974,7 +1999,6 @@
 
   /eventemitter3@4.0.7:
     resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
-    dev: true
 
   /execa@4.1.0:
     resolution: {integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==}
@@ -2004,7 +2028,6 @@
 
   /extend@3.0.2:
     resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
-    dev: true
 
   /external-editor@3.1.0:
     resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==}
@@ -2056,7 +2079,6 @@
     engines: {node: '>=8'}
     dependencies:
       to-regex-range: 5.0.1
-    dev: true
 
   /finalhandler@1.1.2:
     resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==}
@@ -2071,7 +2093,6 @@
       unpipe: 1.0.0
     transitivePeerDependencies:
       - supports-color
-    dev: true
 
   /find-up@4.1.0:
     resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
@@ -2079,11 +2100,9 @@
     dependencies:
       locate-path: 5.0.0
       path-exists: 4.0.0
-    dev: true
 
   /flatted@3.3.1:
     resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==}
-    dev: true
 
   /follow-redirects@1.15.5:
     resolution: {integrity: sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==}
@@ -2093,7 +2112,6 @@
     peerDependenciesMeta:
       debug:
         optional: true
-    dev: true
 
   /foreground-child@3.1.1:
     resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==}
@@ -2123,7 +2141,6 @@
       graceful-fs: 4.2.11
       jsonfile: 4.0.0
       universalify: 0.1.2
-    dev: true
 
   /fs-extra@9.1.0:
     resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==}
@@ -2155,23 +2172,19 @@
 
   /fs.realpath@1.0.0:
     resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
-    dev: true
 
   /fsevents@2.3.2:
     resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
     engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
     os: [darwin]
     requiresBuild: true
-    dev: true
     optional: true
 
   /function-bind@1.1.1:
     resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
-    dev: true
 
   /function-bind@1.1.2:
     resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
-    dev: true
 
   /gauge@4.0.4:
     resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==}
@@ -2195,7 +2208,6 @@
   /get-caller-file@2.0.5:
     resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
     engines: {node: 6.* || 8.* || >= 10.*}
-    dev: true
 
   /get-intrinsic@1.2.1:
     resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==}
@@ -2204,7 +2216,6 @@
       has: 1.0.3
       has-proto: 1.0.1
       has-symbols: 1.0.3
-    dev: true
 
   /get-stream@5.2.0:
     resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==}
@@ -2230,7 +2241,6 @@
     engines: {node: '>= 6'}
     dependencies:
       is-glob: 4.0.3
-    dev: true
 
   /glob@10.3.10:
     resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==}
@@ -2253,7 +2263,6 @@
       minimatch: 3.1.2
       once: 1.4.0
       path-is-absolute: 1.0.1
-    dev: true
 
   /glob@8.1.0:
     resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==}
@@ -2280,7 +2289,6 @@
 
   /graceful-fs@4.2.11:
     resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
-    dev: true
 
   /has-flag@3.0.0:
     resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
@@ -2290,17 +2298,14 @@
   /has-flag@4.0.0:
     resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
     engines: {node: '>=8'}
-    dev: true
 
   /has-proto@1.0.1:
     resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==}
     engines: {node: '>= 0.4'}
-    dev: true
 
   /has-symbols@1.0.3:
     resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
     engines: {node: '>= 0.4'}
-    dev: true
 
   /has-unicode@2.0.1:
     resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==}
@@ -2311,7 +2316,6 @@
     engines: {node: '>= 0.4.0'}
     dependencies:
       function-bind: 1.1.1
-    dev: true
 
   /hasown@2.0.1:
     resolution: {integrity: sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==}
@@ -2348,7 +2352,6 @@
       setprototypeof: 1.2.0
       statuses: 2.0.1
       toidentifier: 1.0.1
-    dev: true
 
   /http-proxy-agent@5.0.0:
     resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==}
@@ -2370,7 +2373,6 @@
       requires-port: 1.0.0
     transitivePeerDependencies:
       - debug
-    dev: true
 
   /http-signature@1.3.6:
     resolution: {integrity: sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==}
@@ -2407,7 +2409,6 @@
     engines: {node: '>=0.10.0'}
     dependencies:
       safer-buffer: 2.1.2
-    dev: true
 
   /iconv-lite@0.6.3:
     resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
@@ -2448,11 +2449,9 @@
     dependencies:
       once: 1.4.0
       wrappy: 1.0.2
-    dev: true
 
   /inherits@2.0.4:
     resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
-    dev: true
 
   /ini@2.0.0:
     resolution: {integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==}
@@ -2494,7 +2493,6 @@
     engines: {node: '>=8'}
     dependencies:
       binary-extensions: 2.2.0
-    dev: true
 
   /is-builtin-module@3.2.1:
     resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==}
@@ -2531,19 +2529,16 @@
   /is-extglob@2.1.1:
     resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
     engines: {node: '>=0.10.0'}
-    dev: true
 
   /is-fullwidth-code-point@3.0.0:
     resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
     engines: {node: '>=8'}
-    dev: true
 
   /is-glob@4.0.3:
     resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
     engines: {node: '>=0.10.0'}
     dependencies:
       is-extglob: 2.1.1
-    dev: true
 
   /is-installed-globally@0.4.0:
     resolution: {integrity: sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==}
@@ -2569,7 +2564,6 @@
   /is-number@7.0.0:
     resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
     engines: {node: '>=0.12.0'}
-    dev: true
 
   /is-path-inside@3.0.3:
     resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}
@@ -2600,7 +2594,6 @@
   /isbinaryfile@4.0.10:
     resolution: {integrity: sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==}
     engines: {node: '>= 8.0.0'}
-    dev: true
 
   /isexe@2.0.0:
     resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
@@ -2664,7 +2657,6 @@
     resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==}
     optionalDependencies:
       graceful-fs: 4.2.11
-    dev: true
 
   /jsonfile@6.1.0:
     resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
@@ -2695,7 +2687,6 @@
       karma: '>=0.9'
     dependencies:
       karma: 6.4.3
-    dev: true
 
   /karma@6.4.3:
     resolution: {integrity: sha512-LuucC/RE92tJ8mlCwqEoRWXP38UMAqpnq98vktmS9SznSoUPPUJQbc91dHcxcunROvfQjdORVA/YFviH+Xci9Q==}
@@ -2731,7 +2722,6 @@
       - debug
       - supports-color
       - utf-8-validate
-    dev: true
 
   /lazy-ass@1.6.0:
     resolution: {integrity: sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==}
@@ -2763,7 +2753,6 @@
     engines: {node: '>=8'}
     dependencies:
       p-locate: 4.1.0
-    dev: true
 
   /lodash.once@4.1.1:
     resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==}
@@ -2771,7 +2760,6 @@
 
   /lodash@4.17.21:
     resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
-    dev: true
 
   /log-symbols@4.1.0:
     resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==}
@@ -2802,7 +2790,6 @@
       streamroller: 3.1.5
     transitivePeerDependencies:
       - supports-color
-    dev: true
 
   /lru-cache@10.2.0:
     resolution: {integrity: sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==}
@@ -2893,7 +2880,6 @@
   /media-typer@0.3.0:
     resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
     engines: {node: '>= 0.6'}
-    dev: true
 
   /merge-stream@2.0.0:
     resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
@@ -2902,20 +2888,17 @@
   /mime-db@1.52.0:
     resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
     engines: {node: '>= 0.6'}
-    dev: true
 
   /mime-types@2.1.35:
     resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
     engines: {node: '>= 0.6'}
     dependencies:
       mime-db: 1.52.0
-    dev: true
 
   /mime@2.6.0:
     resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==}
     engines: {node: '>=4.0.0'}
     hasBin: true
-    dev: true
 
   /mimic-fn@2.1.0:
     resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
@@ -2926,7 +2909,6 @@
     resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
     dependencies:
       brace-expansion: 1.1.11
-    dev: true
 
   /minimatch@5.1.6:
     resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
@@ -2944,7 +2926,6 @@
 
   /minimist@1.2.8:
     resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
-    dev: true
 
   /minipass-collect@1.0.2:
     resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==}
@@ -3033,7 +3014,6 @@
     hasBin: true
     dependencies:
       minimist: 1.2.8
-    dev: true
 
   /mkdirp@1.0.4:
     resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
@@ -3043,11 +3023,9 @@
 
   /ms@2.0.0:
     resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
-    dev: true
 
   /ms@2.1.2:
     resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
-    dev: true
 
   /ms@2.1.3:
     resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
@@ -3060,7 +3038,6 @@
   /negotiator@0.6.3:
     resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
     engines: {node: '>= 0.6'}
-    dev: true
 
   /node-gyp@9.4.1:
     resolution: {integrity: sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ==}
@@ -3108,7 +3085,6 @@
   /normalize-path@3.0.0:
     resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
     engines: {node: '>=0.10.0'}
-    dev: true
 
   /npm-bundled@3.0.0:
     resolution: {integrity: sha512-Vq0eyEQy+elFpzsKjMss9kxqb9tG3YHg4dsyWuUENuzvSUWe1TCnW/vV9FkhvBk/brEDoDiVd+M1Btosa6ImdQ==}
@@ -3191,31 +3167,26 @@
   /object-assign@4.1.1:
     resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
     engines: {node: '>=0.10.0'}
-    dev: true
 
   /object-inspect@1.12.3:
     resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==}
-    dev: true
 
   /on-finished@2.3.0:
     resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==}
     engines: {node: '>= 0.8'}
     dependencies:
       ee-first: 1.1.1
-    dev: true
 
   /on-finished@2.4.1:
     resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
     engines: {node: '>= 0.8'}
     dependencies:
       ee-first: 1.1.1
-    dev: true
 
   /once@1.4.0:
     resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
     dependencies:
       wrappy: 1.0.2
-    dev: true
 
   /onetime@5.1.2:
     resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
@@ -3262,14 +3233,12 @@
     engines: {node: '>=6'}
     dependencies:
       p-try: 2.2.0
-    dev: true
 
   /p-locate@4.1.0:
     resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
     engines: {node: '>=8'}
     dependencies:
       p-limit: 2.3.0
-    dev: true
 
   /p-map@4.0.0:
     resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==}
@@ -3281,7 +3250,6 @@
   /p-try@2.2.0:
     resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
     engines: {node: '>=6'}
-    dev: true
 
   /pacote@15.2.0:
     resolution: {integrity: sha512-rJVZeIwHTUta23sIZgEIM62WYwbmGbThdbnkt81ravBplQv+HjyroqnLRNH2+sLJHcGZmLRmhPwACqhfTcOmnA==}
@@ -3321,17 +3289,14 @@
   /parseurl@1.3.3:
     resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
     engines: {node: '>= 0.8'}
-    dev: true
 
   /path-exists@4.0.0:
     resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
     engines: {node: '>=8'}
-    dev: true
 
   /path-is-absolute@1.0.1:
     resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
     engines: {node: '>=0.10.0'}
-    dev: true
 
   /path-key@3.1.1:
     resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
@@ -3365,7 +3330,6 @@
   /picomatch@2.3.1:
     resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
     engines: {node: '>=8.6'}
-    dev: true
 
   /pify@2.3.0:
     resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
@@ -3380,7 +3344,6 @@
   /pngjs@5.0.0:
     resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
     engines: {node: '>=10.13.0'}
-    dev: true
 
   /prettier@2.6.1:
     resolution: {integrity: sha512-8UVbTBYGwN37Bs9LERmxCPjdvPxlEowx2urIL6urHzdb3SDq4B/Z6xLFCblrSnE4iKWcS6ziJ3aOYrc1kz/E2A==}
@@ -3443,7 +3406,6 @@
   /qjobs@1.2.0:
     resolution: {integrity: sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==}
     engines: {node: '>=0.9'}
-    dev: true
 
   /qrcode@1.5.3:
     resolution: {integrity: sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==}
@@ -3454,7 +3416,6 @@
       encode-utf8: 1.0.3
       pngjs: 5.0.0
       yargs: 15.4.1
-    dev: true
 
   /qs@6.10.4:
     resolution: {integrity: sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g==}
@@ -3468,7 +3429,6 @@
     engines: {node: '>=0.6'}
     dependencies:
       side-channel: 1.0.4
-    dev: true
 
   /querystringify@2.2.0:
     resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==}
@@ -3477,7 +3437,6 @@
   /range-parser@1.2.1:
     resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
     engines: {node: '>= 0.6'}
-    dev: true
 
   /raw-body@2.5.2:
     resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==}
@@ -3487,7 +3446,6 @@
       http-errors: 2.0.0
       iconv-lite: 0.4.24
       unpipe: 1.0.0
-    dev: true
 
   /read-package-json-fast@3.0.2:
     resolution: {integrity: sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==}
@@ -3521,7 +3479,6 @@
     engines: {node: '>=8.10.0'}
     dependencies:
       picomatch: 2.3.1
-    dev: true
 
   /reflect-metadata@0.1.14:
     resolution: {integrity: sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==}
@@ -3536,7 +3493,6 @@
   /require-directory@2.1.1:
     resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
     engines: {node: '>=0.10.0'}
-    dev: true
 
   /require-from-string@2.0.2:
     resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
@@ -3545,7 +3501,6 @@
 
   /require-main-filename@2.0.0:
     resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
-    dev: true
 
   /requirejs@2.3.6:
     resolution: {integrity: sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg==}
@@ -3555,7 +3510,6 @@
 
   /requires-port@1.0.0:
     resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
-    dev: true
 
   /resolve@1.22.2:
     resolution: {integrity: sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==}
@@ -3581,14 +3535,12 @@
 
   /rfdc@1.3.0:
     resolution: {integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==}
-    dev: true
 
   /rimraf@3.0.2:
     resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
     hasBin: true
     dependencies:
       glob: 7.2.3
-    dev: true
 
   /rollup@4.12.0:
     resolution: {integrity: sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==}
@@ -3635,7 +3587,6 @@
 
   /safer-buffer@2.1.2:
     resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
-    dev: true
 
   /semver@5.7.1:
     resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==}
@@ -3665,11 +3616,9 @@
 
   /set-blocking@2.0.0:
     resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
-    dev: true
 
   /setprototypeof@1.2.0:
     resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
-    dev: true
 
   /shebang-command@2.0.0:
     resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
@@ -3689,7 +3638,6 @@
       call-bind: 1.0.2
       get-intrinsic: 1.2.1
       object-inspect: 1.12.3
-    dev: true
 
   /signal-exit@3.0.7:
     resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
@@ -3751,7 +3699,6 @@
       - bufferutil
       - supports-color
       - utf-8-validate
-    dev: true
 
   /socket.io-parser@4.2.4:
     resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==}
@@ -3761,7 +3708,6 @@
       debug: 4.3.4(supports-color@8.1.1)
     transitivePeerDependencies:
       - supports-color
-    dev: true
 
   /socket.io@4.7.4:
     resolution: {integrity: sha512-DcotgfP1Zg9iP/dH9zvAQcWrE0TtbMVwXmlV4T4mqsvY+gw+LqUGPfx2AoVyRk0FLME+GQhufDMyacFmw7ksqw==}
@@ -3778,7 +3724,6 @@
       - bufferutil
       - supports-color
       - utf-8-validate
-    dev: true
 
   /socks-proxy-agent@7.0.0:
     resolution: {integrity: sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==}
@@ -3809,7 +3754,6 @@
   /source-map@0.6.1:
     resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
     engines: {node: '>=0.10.0'}
-    dev: true
 
   /source-map@0.7.4:
     resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==}
@@ -3871,12 +3815,10 @@
   /statuses@1.5.0:
     resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==}
     engines: {node: '>= 0.6'}
-    dev: true
 
   /statuses@2.0.1:
     resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
     engines: {node: '>= 0.8'}
-    dev: true
 
   /streamroller@3.1.5:
     resolution: {integrity: sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==}
@@ -3887,7 +3829,6 @@
       fs-extra: 8.1.0
     transitivePeerDependencies:
       - supports-color
-    dev: true
 
   /string-width@4.2.3:
     resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
@@ -3896,7 +3837,6 @@
       emoji-regex: 8.0.0
       is-fullwidth-code-point: 3.0.0
       strip-ansi: 6.0.1
-    dev: true
 
   /string-width@5.1.2:
     resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
@@ -3918,7 +3858,6 @@
     engines: {node: '>=8'}
     dependencies:
       ansi-regex: 5.0.1
-    dev: true
 
   /strip-ansi@7.1.0:
     resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==}
@@ -3951,7 +3890,6 @@
     engines: {node: '>=10'}
     dependencies:
       has-flag: 4.0.0
-    dev: true
 
   /supports-preserve-symlinks-flag@1.0.0:
     resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
@@ -4006,7 +3944,6 @@
     engines: {node: '>=8.17.0'}
     dependencies:
       rimraf: 3.0.2
-    dev: true
 
   /to-fast-properties@2.0.0:
     resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==}
@@ -4018,12 +3955,10 @@
     engines: {node: '>=8.0'}
     dependencies:
       is-number: 7.0.0
-    dev: true
 
   /toidentifier@1.0.1:
     resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
     engines: {node: '>=0.6'}
-    dev: true
 
   /tough-cookie@4.1.3:
     resolution: {integrity: sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==}
@@ -4070,7 +4005,6 @@
     dependencies:
       media-typer: 0.3.0
       mime-types: 2.1.35
-    dev: true
 
   /typescript@5.1.6:
     resolution: {integrity: sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==}
@@ -4080,11 +4014,9 @@
 
   /ua-parser-js@0.7.37:
     resolution: {integrity: sha512-xV8kqRKM+jhMvcHWUKthV9fNebIzrNy//2O9ZwWcfiBFR5f25XVZPLlEajk/sf3Ra15V92isyQqnIEXRDaZWEA==}
-    dev: true
 
   /undici-types@5.26.5:
     resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
-    dev: true
 
   /unique-filename@2.0.1:
     resolution: {integrity: sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==}
@@ -4117,7 +4049,6 @@
   /universalify@0.1.2:
     resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
     engines: {node: '>= 4.0.0'}
-    dev: true
 
   /universalify@0.2.0:
     resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==}
@@ -4132,7 +4063,6 @@
   /unpipe@1.0.0:
     resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
     engines: {node: '>= 0.8'}
-    dev: true
 
   /untildify@4.0.0:
     resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==}
@@ -4170,7 +4100,6 @@
   /utils-merge@1.0.1:
     resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
     engines: {node: '>= 0.4.0'}
-    dev: true
 
   /uuid@8.3.2:
     resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
@@ -4194,7 +4123,6 @@
   /vary@1.1.2:
     resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
     engines: {node: '>= 0.8'}
-    dev: true
 
   /verror@1.10.0:
     resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==}
@@ -4208,7 +4136,6 @@
   /void-elements@2.0.1:
     resolution: {integrity: sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==}
     engines: {node: '>=0.10.0'}
-    dev: true
 
   /wcwidth@1.0.1:
     resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==}
@@ -4218,7 +4145,6 @@
 
   /which-module@2.0.1:
     resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==}
-    dev: true
 
   /which@2.0.2:
     resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
@@ -4249,7 +4175,6 @@
       ansi-styles: 4.3.0
       string-width: 4.2.3
       strip-ansi: 6.0.1
-    dev: true
 
   /wrap-ansi@7.0.0:
     resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
@@ -4258,7 +4183,6 @@
       ansi-styles: 4.3.0
       string-width: 4.2.3
       strip-ansi: 6.0.1
-    dev: true
 
   /wrap-ansi@8.1.0:
     resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
@@ -4271,7 +4195,6 @@
 
   /wrappy@1.0.2:
     resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
-    dev: true
 
   /ws@8.11.0:
     resolution: {integrity: sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==}
@@ -4284,16 +4207,13 @@
         optional: true
       utf-8-validate:
         optional: true
-    dev: true
 
   /y18n@4.0.3:
     resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
-    dev: true
 
   /y18n@5.0.8:
     resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
     engines: {node: '>=10'}
-    dev: true
 
   /yallist@3.1.1:
     resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
@@ -4309,12 +4229,10 @@
     dependencies:
       camelcase: 5.3.1
       decamelize: 1.2.0
-    dev: true
 
   /yargs-parser@20.2.9:
     resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==}
     engines: {node: '>=10'}
-    dev: true
 
   /yargs-parser@21.1.1:
     resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
@@ -4336,7 +4254,6 @@
       which-module: 2.0.1
       y18n: 4.0.3
       yargs-parser: 18.1.3
-    dev: true
 
   /yargs@16.2.0:
     resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==}
@@ -4349,7 +4266,6 @@
       string-width: 4.2.3
       y18n: 5.0.8
       yargs-parser: 20.2.9
-    dev: true
 
   /yargs@17.7.2:
     resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 36eb141..cf4c338 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -1,3 +1,4 @@
 packages:
+  - 'scouting/webserver/requests/messages'
   - 'scouting/www'
   - 'scouting/www/*'
diff --git a/scouting/webserver/requests/messages/BUILD b/scouting/webserver/requests/messages/BUILD
index d0534a51..6dee54d 100644
--- a/scouting/webserver/requests/messages/BUILD
+++ b/scouting/webserver/requests/messages/BUILD
@@ -1,3 +1,4 @@
+load("@aspect_rules_js//npm:defs.bzl", "npm_package")
 load("@com_github_google_flatbuffers//:build_defs.bzl", "flatbuffer_go_library")
 load("@com_github_google_flatbuffers//:typescript.bzl", "flatbuffer_ts_library")
 
@@ -60,3 +61,14 @@
         visibility = ["//visibility:public"],
     ),
 ) for name in FILE_NAMES]
+
+npm_package(
+    name = "messages",
+    srcs = [
+        ":package.json",
+    ] + [
+        ":{}_ts_fbs_ts".format(lib)
+        for lib in FILE_NAMES
+    ],
+    visibility = ["//visibility:public"],
+)
diff --git a/scouting/webserver/requests/messages/package.json b/scouting/webserver/requests/messages/package.json
new file mode 100644
index 0000000..323b3c4
--- /dev/null
+++ b/scouting/webserver/requests/messages/package.json
@@ -0,0 +1,6 @@
+{
+    "name": "@org_frc971/scouting/webserver/requests/messages",
+    "private": true,
+    "dependencies": {
+    }
+}
diff --git a/scouting/www/BUILD b/scouting/www/BUILD
index a6ca0a1..46742e3 100644
--- a/scouting/www/BUILD
+++ b/scouting/www/BUILD
@@ -36,16 +36,7 @@
         "assets/971_144.png",
     ],
     deps = [
-        "//:node_modules/@angular/animations",
-        "//:node_modules/@angular/service-worker",
-        "//scouting/www/driver_ranking",
-        "//scouting/www/entry",
-        "//scouting/www/match_list",
-        "//scouting/www/notes",
-        "//scouting/www/pit_scouting",
-        "//scouting/www/scan",
-        "//scouting/www/shift_schedule",
-        "//scouting/www/view",
+        ":node_modules",
     ],
 )
 
diff --git a/scouting/www/app/app.module.ts b/scouting/www/app/app.module.ts
index decd1f3..fdbf6fa 100644
--- a/scouting/www/app/app.module.ts
+++ b/scouting/www/app/app.module.ts
@@ -4,14 +4,14 @@
 import {ServiceWorkerModule} from '@angular/service-worker';
 
 import {App} from './app';
-import {EntryModule} from '../entry';
-import {MatchListModule} from '../match_list';
-import {NotesModule} from '../notes';
-import {ShiftScheduleModule} from '../shift_schedule';
-import {ViewModule} from '../view';
-import {DriverRankingModule} from '../driver_ranking';
-import {PitScoutingModule} from '../pit_scouting';
-import {ScanModule} from '../scan';
+import {EntryModule} from '@org_frc971/scouting/www/entry';
+import {MatchListModule} from '@org_frc971/scouting/www/match_list';
+import {NotesModule} from '@org_frc971/scouting/www/notes';
+import {ShiftScheduleModule} from '@org_frc971/scouting/www/shift_schedule';
+import {ViewModule} from '@org_frc971/scouting/www/view';
+import {DriverRankingModule} from '@org_frc971/scouting/www/driver_ranking';
+import {PitScoutingModule} from '@org_frc971/scouting/www/pit_scouting';
+import {ScanModule} from '@org_frc971/scouting/www/scan';
 
 @NgModule({
   declarations: [App],
diff --git a/scouting/www/app/app.ts b/scouting/www/app/app.ts
index 1a70b6f..645c224 100644
--- a/scouting/www/app/app.ts
+++ b/scouting/www/app/app.ts
@@ -1,5 +1,7 @@
 import {Component, ElementRef, ViewChild, isDevMode} from '@angular/core';
 
+import {CompLevel} from '@org_frc971/scouting/www/entry';
+
 type Tab =
   | 'MatchList'
   | 'Notes'
@@ -17,7 +19,7 @@
   teamNumber: string;
   matchNumber: number;
   setNumber: number;
-  compLevel: string;
+  compLevel: CompLevel;
 };
 
 @Component({
diff --git a/scouting/www/counter_button/BUILD b/scouting/www/counter_button/BUILD
deleted file mode 100644
index d081f9d..0000000
--- a/scouting/www/counter_button/BUILD
+++ /dev/null
@@ -1,8 +0,0 @@
-load("@npm//:defs.bzl", "npm_link_all_packages")
-load("//tools/build_rules:js.bzl", "ng_pkg")
-
-npm_link_all_packages(name = "node_modules")
-
-ng_pkg(
-    name = "counter_button",
-)
diff --git a/scouting/www/counter_button/counter_button.component.css b/scouting/www/counter_button/counter_button.component.css
deleted file mode 100644
index df95f65..0000000
--- a/scouting/www/counter_button/counter_button.component.css
+++ /dev/null
@@ -1,14 +0,0 @@
-:host {
-  display: flex;
-  align-items: stretch;
-  flex-direction: column;
-  text-align: center;
-}
-
-* {
-  padding: 10px;
-}
-
-.no-touch-action {
-  touch-action: manipulation;
-}
diff --git a/scouting/www/counter_button/counter_button.component.ts b/scouting/www/counter_button/counter_button.component.ts
deleted file mode 100644
index fa84dc6..0000000
--- a/scouting/www/counter_button/counter_button.component.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import {Component, Input, Output, EventEmitter} from '@angular/core';
-
-@Component({
-  selector: 'frc971-counter-button',
-  templateUrl: './counter_button.ng.html',
-  styleUrls: ['./counter_button.component.css'],
-})
-export class CounterButton {
-  @Input() value: number = 0;
-  @Output() valueChange = new EventEmitter<number>();
-
-  update(delta: number) {
-    this.value = Math.max(this.value + delta, 0);
-
-    this.valueChange.emit(this.value);
-  }
-}
diff --git a/scouting/www/counter_button/counter_button.module.ts b/scouting/www/counter_button/counter_button.module.ts
deleted file mode 100644
index 719ce7c..0000000
--- a/scouting/www/counter_button/counter_button.module.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import {NgModule} from '@angular/core';
-
-import {CounterButton} from './counter_button.component';
-
-@NgModule({
-  declarations: [CounterButton],
-  exports: [CounterButton],
-})
-export class CounterButtonModule {}
diff --git a/scouting/www/counter_button/counter_button.ng.html b/scouting/www/counter_button/counter_button.ng.html
deleted file mode 100644
index 3300ff2..0000000
--- a/scouting/www/counter_button/counter_button.ng.html
+++ /dev/null
@@ -1,11 +0,0 @@
-<h4><ng-content></ng-content></h4>
-<button (click)="update(1)" class="btn btn-secondary btn-block no-touch-action">
-  +
-</button>
-<h3>{{value}}</h3>
-<button
-  (click)="update(-1)"
-  class="btn btn-secondary btn-block no-touch-action"
->
-  -
-</button>
diff --git a/scouting/www/counter_button/package.json b/scouting/www/counter_button/package.json
deleted file mode 100644
index 61eb83b..0000000
--- a/scouting/www/counter_button/package.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-    "name": "@org_frc971/scouting/www/counter_button",
-    "private": true
-}
diff --git a/scouting/www/driver_ranking/BUILD b/scouting/www/driver_ranking/BUILD
index a934ffe..1565dad 100644
--- a/scouting/www/driver_ranking/BUILD
+++ b/scouting/www/driver_ranking/BUILD
@@ -9,9 +9,7 @@
         "//scouting/www:app_common_css",
     ],
     deps = [
-        ":node_modules/@angular/forms",
-        "//scouting/webserver/requests/messages:error_response_ts_fbs",
-        "//scouting/webserver/requests/messages:submit_driver_ranking_ts_fbs",
-        "@com_github_google_flatbuffers//ts:flatbuffers_ts",
+        ":node_modules",
+        "//:node_modules/flatbuffers",
     ],
 )
diff --git a/scouting/www/driver_ranking/driver_ranking.component.ts b/scouting/www/driver_ranking/driver_ranking.component.ts
index 09f62c2..2148b63 100644
--- a/scouting/www/driver_ranking/driver_ranking.component.ts
+++ b/scouting/www/driver_ranking/driver_ranking.component.ts
@@ -1,7 +1,7 @@
 import {Component, OnInit} from '@angular/core';
 import {Builder, ByteBuffer} from 'flatbuffers';
-import {SubmitDriverRanking} from '../../webserver/requests/messages/submit_driver_ranking_generated';
-import {ErrorResponse} from '../../webserver/requests/messages/error_response_generated';
+import {SubmitDriverRanking} from '@org_frc971/scouting/webserver/requests/messages/submit_driver_ranking_generated';
+import {ErrorResponse} from '@org_frc971/scouting/webserver/requests/messages/error_response_generated';
 
 // TeamSelection: Display form to input which
 // teams to rank and the match number.
diff --git a/scouting/www/driver_ranking/package.json b/scouting/www/driver_ranking/package.json
index 38d9358..73b6f6b 100644
--- a/scouting/www/driver_ranking/package.json
+++ b/scouting/www/driver_ranking/package.json
@@ -2,6 +2,7 @@
     "name": "@org_frc971/scouting/www/driver_ranking",
     "private": true,
     "dependencies": {
-        "@angular/forms": "v16-lts"
+        "@angular/forms": "v16-lts",
+        "@org_frc971/scouting/webserver/requests/messages": "workspace:*"
     }
 }
diff --git a/scouting/www/entry/BUILD b/scouting/www/entry/BUILD
index 48732f9..6bffbf8 100644
--- a/scouting/www/entry/BUILD
+++ b/scouting/www/entry/BUILD
@@ -9,15 +9,7 @@
         "//scouting/www:app_common_css",
     ],
     deps = [
-        ":node_modules/@angular/forms",
-        "//:node_modules/@angular/platform-browser",
-        "//:node_modules/@types/pako",
-        "//:node_modules/angularx-qrcode",
-        "//:node_modules/pako",
-        "//scouting/webserver/requests/messages:error_response_ts_fbs",
-        "//scouting/webserver/requests/messages:request_all_matches_response_ts_fbs",
-        "//scouting/webserver/requests/messages:submit_2024_actions_ts_fbs",
-        "//scouting/www/rpc",
-        "@com_github_google_flatbuffers//ts:flatbuffers_ts",
+        ":node_modules",
+        "//:node_modules/flatbuffers",
     ],
 )
diff --git a/scouting/www/entry/entry.component.ts b/scouting/www/entry/entry.component.ts
index 21fd474..d2c922e 100644
--- a/scouting/www/entry/entry.component.ts
+++ b/scouting/www/entry/entry.component.ts
@@ -9,7 +9,7 @@
 } from '@angular/core';
 import {FormsModule} from '@angular/forms';
 import {Builder, ByteBuffer} from 'flatbuffers';
-import {ErrorResponse} from '../../webserver/requests/messages/error_response_generated';
+import {ErrorResponse} from '@org_frc971/scouting/webserver/requests/messages/error_response_generated';
 import {
   StartMatchAction,
   ScoreType,
@@ -23,9 +23,9 @@
   EndMatchAction,
   ActionType,
   Action,
-} from '../../webserver/requests/messages/submit_2024_actions_generated';
-import {Match} from '../../webserver/requests/messages/request_all_matches_response_generated';
-import {MatchListRequestor} from '../rpc';
+} from '@org_frc971/scouting/webserver/requests/messages/submit_2024_actions_generated';
+import {Match} from '@org_frc971/scouting/webserver/requests/messages/request_all_matches_response_generated';
+import {MatchListRequestor} from '@org_frc971/scouting/www/rpc';
 import * as pako from 'pako';
 
 type Section =
@@ -41,7 +41,7 @@
 
 // TODO(phil): Deduplicate with match_list.component.ts.
 const COMP_LEVELS = ['qm', 'ef', 'qf', 'sf', 'f'] as const;
-type CompLevel = typeof COMP_LEVELS[number];
+export type CompLevel = typeof COMP_LEVELS[number];
 
 // TODO(phil): Deduplicate with match_list.component.ts.
 const COMP_LEVEL_LABELS: Record<CompLevel, string> = {
@@ -141,7 +141,12 @@
   errorMessage: string = '';
   autoPhase: boolean = true;
   mobilityCompleted: boolean = false;
+  // TODO(phil): Come up with a better name here.
   selectedValue = 0;
+  endGameAction: StageType = StageType.kMISSING;
+  noteIsTrapped: boolean = false;
+  endGameSpotlight: boolean = false;
+
   nextTeamNumber = '';
 
   preScouting: boolean = false;
diff --git a/scouting/www/entry/entry.module.ts b/scouting/www/entry/entry.module.ts
index bcba4ee..df92042 100644
--- a/scouting/www/entry/entry.module.ts
+++ b/scouting/www/entry/entry.module.ts
@@ -1,4 +1,4 @@
-import {NgModule, Pipe, PipeTransform} from '@angular/core';
+import {NgModule} from '@angular/core';
 import {CommonModule} from '@angular/common';
 import {FormsModule} from '@angular/forms';
 import {EntryComponent} from './entry.component';
diff --git a/scouting/www/entry/entry.ng.html b/scouting/www/entry/entry.ng.html
index 553fa1e..233d63a 100644
--- a/scouting/www/entry/entry.ng.html
+++ b/scouting/www/entry/entry.ng.html
@@ -91,7 +91,7 @@
           type="radio"
           name="radio-group"
           [value]="i"
-          (change)="selectedValue = $event.target.value"
+          [(ngModel)]="selectedValue"
         />
         {{ i }}
       </label>
@@ -311,21 +311,19 @@
       <div class="button_row">
         <label>
           <input
-            #park
             type="radio"
-            id="option1"
             name="endgameaction"
-            value="park"
+            [value]="StageType.kPARK"
+            [(ngModel)]="endGameAction"
           />
           Park
         </label>
         <label>
           <input
-            #onStage
             type="radio"
-            id="option2"
             name="endgameaction"
-            value="onStage"
+            [value]="StageType.kON_STAGE"
+            [(ngModel)]="endGameAction"
           />
           On Stage
         </label>
@@ -333,42 +331,32 @@
       <div class="button_row">
         <label>
           <input
-            #harmony
             type="radio"
-            id="option3"
             name="endgameaction"
-            value="harmony"
+            [value]="StageType.kHARMONY"
+            [(ngModel)]="endGameAction"
           />
           Harmony
         </label>
         <label>
           <input
-            #na
             type="radio"
-            id="option2"
             name="endgameaction"
-            value="na"
+            [value]="StageType.kMISSING"
+            [(ngModel)]="endGameAction"
           />
           N/A
         </label>
       </div>
       <label>
-        <input
-          #trapNote
-          type="checkbox"
-          id="trapnote"
-          name="trapnote"
-          value="trapNote"
-        />
+        <input type="checkbox" name="trapnote" [(ngModel)]="noteIsTrapped" />
         Trap Note
       </label>
       <label>
         <input
-          #spotlight
           type="checkbox"
-          id="spotlight"
           name="spotlight"
-          value="spotlight"
+          [(ngModel)]="endGameSpotlight"
         />
         Spotlight
       </label>
@@ -394,7 +382,7 @@
       <button
         *ngIf="!autoPhase"
         class="btn btn-info"
-        (click)="changeSectionTo('Review and Submit');  addPenalties(); addAction({type: 'endMatchAction', stageType: (park.checked ? StageType.kPARK : onStage.checked ? StageType.kON_STAGE : harmony.checked ? StageType.kHARMONY : StageType.kMISSING), trapNote: trapNote.checked, spotlight: spotlight.checked});"
+        (click)="changeSectionTo('Review and Submit');  addPenalties(); addAction({type: 'endMatchAction', stageType: endGameAction, trapNote: noteIsTrapped, spotlight: endGameSpotlight});"
       >
         End Match
       </button>
@@ -430,7 +418,7 @@
       </button>
       <button
         class="btn btn-info"
-        (click)="changeSectionTo('Review and Submit');  addPenalties(); addAction({type: 'endMatchAction', stageType: (park.checked ? StageType.kPARK : onStage.checked ? StageType.kON_STAGE : harmony.checked ? StageType.kHARMONY : StageType.kMISSING), trapNote: trapNote.checked, spotlight: spotlight.checked});"
+        (click)="changeSectionTo('Review and Submit');  addPenalties(); addAction({type: 'endMatchAction', stageType: endGameAction, trapNote: noteIsTrapped, spotlight: endGameSpotlight});"
       >
         End Match
       </button>
@@ -439,35 +427,30 @@
   <div *ngSwitchCase="'Review and Submit'" id="Review" class="container-fluid">
     <div class="row">
       <ul id="review_data">
-        <li
-          *ngFor="let action of actionList"
-          [ngValue]="action"
-          style="display: flex"
-        >
+        <li *ngFor="let action of actionList" style="display: flex">
           <div [ngSwitch]="action.type" style="padding: 0px">
             <span *ngSwitchCase="'startMatchAction'">
-              Started match at position {{action.position}}
+              Started match at position {{$any(action).position}}
             </span>
             <span *ngSwitchCase="'pickupNoteAction'">Picked up Note</span>
             <span *ngSwitchCase="'placeNoteAction'">
-              Placed at {{stringifyScoreType(action.scoreType)}}
+              Placed at {{stringifyScoreType($any(action).scoreType)}}
             </span>
             <span *ngSwitchCase="'endAutoPhase'">Ended auto phase</span>
             <span *ngSwitchCase="'endMatchAction'">
-              Ended Match; stageType: {{(action.stageType === 0 ? "kON_STAGE" :
-              action.stageType === 1 ? "kPARK" : action.stageType === 2 ?
-              "kHARMONY" : "kMISSING")}}, trapNote: {{action.trapNote}},
-              spotlight: {{action.spotlight}}
+              Ended Match; stageType:
+              {{stringifyStageType($any(action).stageType)}}, trapNote:
+              {{$any(action).trapNote}}, spotlight: {{$any(action).spotlight}}
             </span>
             <span *ngSwitchCase="'robotDeathAction'">
-              Robot dead: {{action.robotDead}}
+              Robot dead: {{$any(action).robotDead}}
             </span>
             <span *ngSwitchCase="'mobilityAction'">
-              Mobility: {{action.mobility}}
+              Mobility: {{$any(action).mobility}}
             </span>
             <span *ngSwitchDefault>{{action.type}}</span>
             <span *ngSwitchCase="'penaltyAction'">
-              Penalties: {{action.penalties}}
+              Penalties: {{$any(action).penalties}}
             </span>
           </div>
         </li>
diff --git a/scouting/www/entry/package.json b/scouting/www/entry/package.json
index a02c0a6..39479f4 100644
--- a/scouting/www/entry/package.json
+++ b/scouting/www/entry/package.json
@@ -2,9 +2,12 @@
     "name": "@org_frc971/scouting/www/entry",
     "private": true,
     "dependencies": {
+        "angularx-qrcode": "^16.0.2",
         "pako": "2.1.0",
+        "@angular/forms": "v16-lts",
+        "@angular/platform-browser": "v16-lts",
         "@types/pako": "2.0.3",
-        "@org_frc971/scouting/www/counter_button": "workspace:*",
-        "@angular/forms": "v16-lts"
+        "@org_frc971/scouting/webserver/requests/messages": "workspace:*",
+        "@org_frc971/scouting/www/rpc": "workspace:*"
     }
 }
diff --git a/scouting/www/index.html b/scouting/www/index.html
index 821acf2..ee2bcb4 100644
--- a/scouting/www/index.html
+++ b/scouting/www/index.html
@@ -7,17 +7,20 @@
     <meta name="theme-color" content="#000000" />
     <link rel="manifest" href="/manifest.json" />
     <link
+      href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
       rel="stylesheet"
-      href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css"
-      integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD"
+      integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
       crossorigin="anonymous"
     />
     <link
       rel="stylesheet"
-      href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css"
-      integrity="d8824f7067cdfea38afec7e9ffaf072125266824206d69ef1f112d72153a505e"
+      href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
     />
-    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
+    <script
+      src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
+      integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
+      crossorigin="anonymous"
+    ></script>
     <script>
       // In order to hook into WASM's "finished loading" event, we interact
       // with the global Module variable.  Maybe some day there'll be a better
diff --git a/scouting/www/match_list/BUILD b/scouting/www/match_list/BUILD
index b2128db..2ff4fee 100644
--- a/scouting/www/match_list/BUILD
+++ b/scouting/www/match_list/BUILD
@@ -9,11 +9,7 @@
         "//scouting/www:app_common_css",
     ],
     deps = [
-        ":node_modules/@angular/forms",
-        "//scouting/webserver/requests/messages:error_response_ts_fbs",
-        "//scouting/webserver/requests/messages:request_all_matches_response_ts_fbs",
-        "//scouting/webserver/requests/messages:request_all_matches_ts_fbs",
-        "//scouting/www/rpc",
-        "@com_github_google_flatbuffers//ts:flatbuffers_ts",
+        ":node_modules",
+        "//:node_modules/flatbuffers",
     ],
 )
diff --git a/scouting/www/match_list/match_list.component.ts b/scouting/www/match_list/match_list.component.ts
index 8fafdce..08120b3 100644
--- a/scouting/www/match_list/match_list.component.ts
+++ b/scouting/www/match_list/match_list.component.ts
@@ -1,19 +1,23 @@
 import {Component, EventEmitter, OnInit, Output} from '@angular/core';
 import {Builder, ByteBuffer} from 'flatbuffers';
-import {ErrorResponse} from '../../webserver/requests/messages/error_response_generated';
-import {RequestAllMatches} from '../../webserver/requests/messages/request_all_matches_generated';
+import {ErrorResponse} from '@org_frc971/scouting/webserver/requests/messages/error_response_generated';
+import {RequestAllMatches} from '@org_frc971/scouting/webserver/requests/messages/request_all_matches_generated';
 import {
   Match,
   RequestAllMatchesResponse,
-} from '../../webserver/requests/messages/request_all_matches_response_generated';
+} from '@org_frc971/scouting/webserver/requests/messages/request_all_matches_response_generated';
 
-import {MatchListRequestor} from '../rpc';
+import {MatchListRequestor} from '@org_frc971/scouting/www/rpc';
+
+// TODO(phil): Deduplicate with entry.component.ts.
+const COMP_LEVELS = ['qm', 'ef', 'qf', 'sf', 'f'] as const;
+export type CompLevel = typeof COMP_LEVELS[number];
 
 type TeamInMatch = {
   teamNumber: string;
   matchNumber: number;
   setNumber: number;
-  compLevel: string;
+  compLevel: CompLevel;
 };
 
 @Component({
@@ -23,6 +27,7 @@
 })
 export class MatchListComponent implements OnInit {
   @Output() selectedTeamEvent = new EventEmitter<TeamInMatch>();
+
   progressMessage: string = '';
   errorMessage: string = '';
   matchList: Match[] = [];
@@ -30,6 +35,14 @@
 
   constructor(private readonly matchListRequestor: MatchListRequestor) {}
 
+  // Validates that the specified string is a proper comp level.
+  validateCompLevel(compLevel: string): CompLevel {
+    if (COMP_LEVELS.indexOf(compLevel as any) !== -1) {
+      return compLevel as CompLevel;
+    }
+    throw new Error(`Could not parse "${compLevel}" as a valid comp level.`);
+  }
+
   // Returns true if the match is fully scouted. Returns false otherwise.
   matchIsFullyScouted(match: Match): boolean {
     const scouted = match.dataScouted();
diff --git a/scouting/www/match_list/match_list.ng.html b/scouting/www/match_list/match_list.ng.html
index 0ebbe4c..fe26d09 100644
--- a/scouting/www/match_list/match_list.ng.html
+++ b/scouting/www/match_list/match_list.ng.html
@@ -22,7 +22,7 @@
             teamNumber: team.teamNumber,
             matchNumber: match.matchNumber(),
             setNumber: match.setNumber(),
-            compLevel: match.compLevel()
+            compLevel: validateCompLevel(match.compLevel()),
             })"
         class="match-item"
         [ngClass]="team.color"
diff --git a/scouting/www/match_list/package.json b/scouting/www/match_list/package.json
index 00977c5..77d2dc5 100644
--- a/scouting/www/match_list/package.json
+++ b/scouting/www/match_list/package.json
@@ -2,6 +2,7 @@
     "name": "@org_frc971/scouting/www/match_list",
     "private": true,
     "dependencies": {
+        "@org_frc971/scouting/webserver/requests/messages": "workspace:*",
         "@org_frc971/scouting/www/rpc": "workspace:*",
         "@angular/forms": "v16-lts"
     }
diff --git a/scouting/www/notes/BUILD b/scouting/www/notes/BUILD
index 64e4ae7..59c508e 100644
--- a/scouting/www/notes/BUILD
+++ b/scouting/www/notes/BUILD
@@ -9,12 +9,7 @@
         "//scouting/www:app_common_css",
     ],
     deps = [
-        ":node_modules/@angular/forms",
-        "//scouting/webserver/requests/messages:error_response_ts_fbs",
-        "//scouting/webserver/requests/messages:request_notes_for_team_response_ts_fbs",
-        "//scouting/webserver/requests/messages:request_notes_for_team_ts_fbs",
-        "//scouting/webserver/requests/messages:submit_notes_response_ts_fbs",
-        "//scouting/webserver/requests/messages:submit_notes_ts_fbs",
-        "@com_github_google_flatbuffers//ts:flatbuffers_ts",
+        ":node_modules",
+        "//:node_modules/flatbuffers",
     ],
 )
diff --git a/scouting/www/notes/notes.component.ts b/scouting/www/notes/notes.component.ts
index 431ec71..6263d37 100644
--- a/scouting/www/notes/notes.component.ts
+++ b/scouting/www/notes/notes.component.ts
@@ -1,13 +1,13 @@
 import {Component, HostListener} from '@angular/core';
 import {Builder, ByteBuffer} from 'flatbuffers';
-import {ErrorResponse} from '../../webserver/requests/messages/error_response_generated';
-import {RequestNotesForTeam} from '../../webserver/requests/messages/request_notes_for_team_generated';
+import {ErrorResponse} from '@org_frc971/scouting/webserver/requests/messages/error_response_generated';
+import {RequestNotesForTeam} from '@org_frc971/scouting/webserver/requests/messages/request_notes_for_team_generated';
 import {
   Note as NoteFb,
   RequestNotesForTeamResponse,
-} from '../../webserver/requests/messages/request_notes_for_team_response_generated';
-import {SubmitNotes} from '../../webserver/requests/messages/submit_notes_generated';
-import {SubmitNotesResponse} from '../../webserver/requests/messages/submit_notes_response_generated';
+} from '@org_frc971/scouting/webserver/requests/messages/request_notes_for_team_response_generated';
+import {SubmitNotes} from '@org_frc971/scouting/webserver/requests/messages/submit_notes_generated';
+import {SubmitNotesResponse} from '@org_frc971/scouting/webserver/requests/messages/submit_notes_response_generated';
 
 /*
 For new games, the keywords being used will likely need to be updated.
diff --git a/scouting/www/notes/package.json b/scouting/www/notes/package.json
index f1ad3ae..5961469 100644
--- a/scouting/www/notes/package.json
+++ b/scouting/www/notes/package.json
@@ -2,6 +2,7 @@
     "name": "@org_frc971/scouting/www/notes",
     "private": true,
     "dependencies": {
-        "@angular/forms": "v16-lts"
+        "@angular/forms": "v16-lts",
+        "@org_frc971/scouting/webserver/requests/messages": "workspace:*"
     }
 }
diff --git a/scouting/www/package.json b/scouting/www/package.json
index 3ab0035..80fa5c9 100644
--- a/scouting/www/package.json
+++ b/scouting/www/package.json
@@ -1,4 +1,15 @@
 {
     "private": true,
-    "dependencies": {}
+    "dependencies": {
+        "@angular/animations": "v16-lts",
+        "@angular/service-worker": "v16-lts",
+        "@org_frc971/scouting/www/driver_ranking": "workspace:*",
+        "@org_frc971/scouting/www/entry": "workspace:*",
+        "@org_frc971/scouting/www/match_list": "workspace:*",
+        "@org_frc971/scouting/www/notes": "workspace:*",
+        "@org_frc971/scouting/www/pit_scouting": "workspace:*",
+        "@org_frc971/scouting/www/scan": "workspace:*",
+        "@org_frc971/scouting/www/shift_schedule": "workspace:*",
+        "@org_frc971/scouting/www/view": "workspace:*"
+    }
 }
diff --git a/scouting/www/pit_scouting/BUILD b/scouting/www/pit_scouting/BUILD
index 740dee1..cfaf5d4 100644
--- a/scouting/www/pit_scouting/BUILD
+++ b/scouting/www/pit_scouting/BUILD
@@ -9,10 +9,7 @@
         "//scouting/www:app_common_css",
     ],
     deps = [
-        ":node_modules/@angular/forms",
-        "//scouting/webserver/requests/messages:error_response_ts_fbs",
-        "//scouting/webserver/requests/messages:submit_pit_image_response_ts_fbs",
-        "//scouting/webserver/requests/messages:submit_pit_image_ts_fbs",
-        "@com_github_google_flatbuffers//ts:flatbuffers_ts",
+        ":node_modules",
+        "//:node_modules/flatbuffers",
     ],
 )
diff --git a/scouting/www/pit_scouting/package.json b/scouting/www/pit_scouting/package.json
index f41150f..2eb2783 100644
--- a/scouting/www/pit_scouting/package.json
+++ b/scouting/www/pit_scouting/package.json
@@ -2,6 +2,7 @@
 	"name": "@org_frc971/scouting/www/pit_scouting",
 	"private": true,
 	"dependencies": {
-			"@angular/forms": "v16-lts"
+			"@angular/forms": "v16-lts",
+			"@org_frc971/scouting/webserver/requests/messages": "workspace:*"
 	}
 }
diff --git a/scouting/www/pit_scouting/pit_scouting.component.ts b/scouting/www/pit_scouting/pit_scouting.component.ts
index 7bb884c..294eca2 100644
--- a/scouting/www/pit_scouting/pit_scouting.component.ts
+++ b/scouting/www/pit_scouting/pit_scouting.component.ts
@@ -6,8 +6,8 @@
   ViewChildren,
 } from '@angular/core';
 import {Builder, ByteBuffer} from 'flatbuffers';
-import {ErrorResponse} from '../../webserver/requests/messages/error_response_generated';
-import {SubmitPitImage} from '../../webserver/requests/messages/submit_pit_image_generated';
+import {ErrorResponse} from '@org_frc971/scouting/webserver/requests/messages/error_response_generated';
+import {SubmitPitImage} from '@org_frc971/scouting/webserver/requests/messages/submit_pit_image_generated';
 
 type Section = 'TeamSelection' | 'Data';
 
diff --git a/scouting/www/rpc/BUILD b/scouting/www/rpc/BUILD
index 592735c..66cba8d 100644
--- a/scouting/www/rpc/BUILD
+++ b/scouting/www/rpc/BUILD
@@ -10,18 +10,7 @@
     ],
     generate_public_api = False,
     deps = [
-        "//:node_modules/dexie",
-        "//scouting/webserver/requests/messages:error_response_ts_fbs",
-        "//scouting/webserver/requests/messages:request_2024_data_scouting_response_ts_fbs",
-        "//scouting/webserver/requests/messages:request_2024_data_scouting_ts_fbs",
-        "//scouting/webserver/requests/messages:request_all_driver_rankings_response_ts_fbs",
-        "//scouting/webserver/requests/messages:request_all_driver_rankings_ts_fbs",
-        "//scouting/webserver/requests/messages:request_all_matches_response_ts_fbs",
-        "//scouting/webserver/requests/messages:request_all_matches_ts_fbs",
-        "//scouting/webserver/requests/messages:request_all_notes_response_ts_fbs",
-        "//scouting/webserver/requests/messages:request_all_notes_ts_fbs",
-        "//scouting/webserver/requests/messages:request_all_pit_images_response_ts_fbs",
-        "//scouting/webserver/requests/messages:request_all_pit_images_ts_fbs",
-        "@com_github_google_flatbuffers//ts:flatbuffers_ts",
+        ":node_modules",
+        "//:node_modules/flatbuffers",
     ],
 )
diff --git a/scouting/www/rpc/match_list_requestor.ts b/scouting/www/rpc/match_list_requestor.ts
index 0a812ce..97ed0b0 100644
--- a/scouting/www/rpc/match_list_requestor.ts
+++ b/scouting/www/rpc/match_list_requestor.ts
@@ -1,11 +1,11 @@
 import {Injectable} from '@angular/core';
 import {Builder, ByteBuffer} from 'flatbuffers';
-import {ErrorResponse} from '../../webserver/requests/messages/error_response_generated';
-import {RequestAllMatches} from '../../webserver/requests/messages/request_all_matches_generated';
+import {ErrorResponse} from '@org_frc971/scouting/webserver/requests/messages/error_response_generated';
+import {RequestAllMatches} from '@org_frc971/scouting/webserver/requests/messages/request_all_matches_generated';
 import {
   Match,
   RequestAllMatchesResponse,
-} from '../../webserver/requests/messages/request_all_matches_response_generated';
+} from '@org_frc971/scouting/webserver/requests/messages/request_all_matches_response_generated';
 import {db, MatchListData} from './db';
 const MATCH_TYPE_ORDERING = ['qm', 'ef', 'qf', 'sf', 'f'];
 @Injectable({providedIn: 'root'})
diff --git a/scouting/www/rpc/package.json b/scouting/www/rpc/package.json
index 6d1369a..bc9a662 100644
--- a/scouting/www/rpc/package.json
+++ b/scouting/www/rpc/package.json
@@ -1,4 +1,8 @@
 {
     "name": "@org_frc971/scouting/www/rpc",
-    "private": true
+    "private": true,
+    "dependencies": {
+        "@org_frc971/scouting/webserver/requests/messages": "workspace:*",
+        "dexie": "^3.2.5"
+    }
 }
diff --git a/scouting/www/rpc/view_data_requestor.ts b/scouting/www/rpc/view_data_requestor.ts
index 74fc212..606eef1 100644
--- a/scouting/www/rpc/view_data_requestor.ts
+++ b/scouting/www/rpc/view_data_requestor.ts
@@ -1,26 +1,26 @@
 import {Injectable} from '@angular/core';
 import {Builder, ByteBuffer} from 'flatbuffers';
-import {ErrorResponse} from '../../webserver/requests/messages/error_response_generated';
-import {RequestAllNotes} from '../../webserver/requests/messages/request_all_notes_generated';
+import {ErrorResponse} from '@org_frc971/scouting/webserver/requests/messages/error_response_generated';
+import {RequestAllNotes} from '@org_frc971/scouting/webserver/requests/messages/request_all_notes_generated';
 import {
   Note,
   RequestAllNotesResponse,
-} from '../../webserver/requests/messages/request_all_notes_response_generated';
-import {RequestAllDriverRankings} from '../../webserver/requests/messages/request_all_driver_rankings_generated';
+} from '@org_frc971/scouting/webserver/requests/messages/request_all_notes_response_generated';
+import {RequestAllDriverRankings} from '@org_frc971/scouting/webserver/requests/messages/request_all_driver_rankings_generated';
 import {
   Ranking,
   RequestAllDriverRankingsResponse,
-} from '../../webserver/requests/messages/request_all_driver_rankings_response_generated';
-import {Request2024DataScouting} from '../../webserver/requests/messages/request_2024_data_scouting_generated';
+} from '@org_frc971/scouting/webserver/requests/messages/request_all_driver_rankings_response_generated';
+import {Request2024DataScouting} from '@org_frc971/scouting/webserver/requests/messages/request_2024_data_scouting_generated';
 import {
   PitImage,
   RequestAllPitImagesResponse,
-} from '../../webserver/requests/messages/request_all_pit_images_response_generated';
-import {RequestAllPitImages} from '../../webserver/requests/messages/request_all_pit_images_generated';
+} from '@org_frc971/scouting/webserver/requests/messages/request_all_pit_images_response_generated';
+import {RequestAllPitImages} from '@org_frc971/scouting/webserver/requests/messages/request_all_pit_images_generated';
 import {
   Stats2024,
   Request2024DataScoutingResponse,
-} from '../../webserver/requests/messages/request_2024_data_scouting_response_generated';
+} from '@org_frc971/scouting/webserver/requests/messages/request_2024_data_scouting_response_generated';
 
 @Injectable({providedIn: 'root'})
 export class ViewDataRequestor {
diff --git a/scouting/www/scan/BUILD b/scouting/www/scan/BUILD
index 88b2822..ee44b1f 100644
--- a/scouting/www/scan/BUILD
+++ b/scouting/www/scan/BUILD
@@ -9,10 +9,7 @@
         "//scouting/www:app_common_css",
     ],
     deps = [
-        ":node_modules/@angular/forms",
-        ":node_modules/@types/pako",
-        ":node_modules/pako",
-        "//scouting/webserver/requests/messages:error_response_ts_fbs",
-        "@com_github_google_flatbuffers//ts:flatbuffers_ts",
+        ":node_modules",
+        "//:node_modules/flatbuffers",
     ],
 )
diff --git a/scouting/www/scan/package.json b/scouting/www/scan/package.json
index a5950c3..7fa3990 100644
--- a/scouting/www/scan/package.json
+++ b/scouting/www/scan/package.json
@@ -4,6 +4,7 @@
     "dependencies": {
         "pako": "2.1.0",
         "@types/pako": "2.0.3",
-        "@angular/forms": "v16-lts"
+        "@angular/forms": "v16-lts",
+        "@org_frc971/scouting/webserver/requests/messages": "workspace:*"
     }
 }
diff --git a/scouting/www/scan/scan.component.ts b/scouting/www/scan/scan.component.ts
index 98a7b61..4a1c9c8 100644
--- a/scouting/www/scan/scan.component.ts
+++ b/scouting/www/scan/scan.component.ts
@@ -1,5 +1,5 @@
 import {Component, NgZone, OnInit, ViewChild, ElementRef} from '@angular/core';
-import {ErrorResponse} from '../../webserver/requests/messages/error_response_generated';
+import {ErrorResponse} from '@org_frc971/scouting/webserver/requests/messages/error_response_generated';
 import {Builder, ByteBuffer} from 'flatbuffers';
 import * as pako from 'pako';
 
diff --git a/scouting/www/shift_schedule/BUILD b/scouting/www/shift_schedule/BUILD
index 3afb557..747ff98 100644
--- a/scouting/www/shift_schedule/BUILD
+++ b/scouting/www/shift_schedule/BUILD
@@ -9,10 +9,7 @@
         "//scouting/www:app_common_css",
     ],
     deps = [
-        ":node_modules/@angular/forms",
-        "//scouting/webserver/requests/messages:error_response_ts_fbs",
-        "//scouting/webserver/requests/messages:request_all_matches_response_ts_fbs",
-        "//scouting/webserver/requests/messages:request_all_matches_ts_fbs",
-        "@com_github_google_flatbuffers//ts:flatbuffers_ts",
+        ":node_modules",
+        "//:node_modules/flatbuffers",
     ],
 )
diff --git a/scouting/www/shift_schedule/package.json b/scouting/www/shift_schedule/package.json
index f2d6d7e..a2ce384 100644
--- a/scouting/www/shift_schedule/package.json
+++ b/scouting/www/shift_schedule/package.json
@@ -2,6 +2,7 @@
     "name": "@org_frc971/scouting/www/shift_schedule",
     "private": true,
     "dependencies": {
-        "@angular/forms": "v16-lts"
+        "@angular/forms": "v16-lts",
+        "@org_frc971/scouting/webserver/requests/messages": "workspace:*"
     }
 }
diff --git a/scouting/www/shift_schedule/shift_schedule.component.ts b/scouting/www/shift_schedule/shift_schedule.component.ts
index de0b2e1..316830c 100644
--- a/scouting/www/shift_schedule/shift_schedule.component.ts
+++ b/scouting/www/shift_schedule/shift_schedule.component.ts
@@ -1,6 +1,6 @@
 import {Component, OnInit} from '@angular/core';
 import {Builder, ByteBuffer} from 'flatbuffers';
-import {ErrorResponse} from '../../webserver/requests/messages/error_response_generated';
+import {ErrorResponse} from '@org_frc971/scouting/webserver/requests/messages/error_response_generated';
 
 @Component({
   selector: 'shift-schedule',
diff --git a/scouting/www/view/BUILD b/scouting/www/view/BUILD
index 738ea00..d967882 100644
--- a/scouting/www/view/BUILD
+++ b/scouting/www/view/BUILD
@@ -9,17 +9,7 @@
         "//scouting/www:app_common_css",
     ],
     deps = [
-        ":node_modules/@angular/forms",
-        "//scouting/webserver/requests/messages:delete_2024_data_scouting_response_ts_fbs",
-        "//scouting/webserver/requests/messages:delete_2024_data_scouting_ts_fbs",
-        "//scouting/webserver/requests/messages:error_response_ts_fbs",
-        "//scouting/webserver/requests/messages:request_2024_data_scouting_response_ts_fbs",
-        "//scouting/webserver/requests/messages:request_2024_data_scouting_ts_fbs",
-        "//scouting/webserver/requests/messages:request_all_driver_rankings_response_ts_fbs",
-        "//scouting/webserver/requests/messages:request_all_driver_rankings_ts_fbs",
-        "//scouting/webserver/requests/messages:request_all_notes_response_ts_fbs",
-        "//scouting/webserver/requests/messages:request_all_notes_ts_fbs",
-        "//scouting/www/rpc",
-        "@com_github_google_flatbuffers//ts:flatbuffers_ts",
+        ":node_modules",
+        "//:node_modules/flatbuffers",
     ],
 )
diff --git a/scouting/www/view/package.json b/scouting/www/view/package.json
index d214f6f..efcbd8a 100644
--- a/scouting/www/view/package.json
+++ b/scouting/www/view/package.json
@@ -2,6 +2,8 @@
     "name": "@org_frc971/scouting/www/view",
     "private": true,
     "dependencies": {
-      "@angular/forms": "v16-lts"
+      "@angular/forms": "v16-lts",
+      "@org_frc971/scouting/webserver/requests/messages": "workspace:*",
+      "@org_frc971/scouting/www/rpc": "workspace:*"
     }
 }
diff --git a/scouting/www/view/view.component.ts b/scouting/www/view/view.component.ts
index ea9a61f..8aa3784 100644
--- a/scouting/www/view/view.component.ts
+++ b/scouting/www/view/view.component.ts
@@ -1,28 +1,31 @@
 import {Component, OnInit} from '@angular/core';
 import {Builder, ByteBuffer} from 'flatbuffers';
-import {ErrorResponse} from '../../webserver/requests/messages/error_response_generated';
+import {ErrorResponse} from '@org_frc971/scouting/webserver/requests/messages/error_response_generated';
 import {
   Ranking,
   RequestAllDriverRankingsResponse,
-} from '../../webserver/requests/messages/request_all_driver_rankings_response_generated';
+} from '@org_frc971/scouting/webserver/requests/messages/request_all_driver_rankings_response_generated';
 import {
   Stats2024,
   Request2024DataScoutingResponse,
-} from '../../webserver/requests/messages/request_2024_data_scouting_response_generated';
+} from '@org_frc971/scouting/webserver/requests/messages/request_2024_data_scouting_response_generated';
 
 import {
   PitImage,
   RequestAllPitImagesResponse,
-} from '../../webserver/requests/messages/request_all_pit_images_response_generated';
+} from '@org_frc971/scouting/webserver/requests/messages/request_all_pit_images_response_generated';
 
 import {
   Note,
   RequestAllNotesResponse,
-} from '../../webserver/requests/messages/request_all_notes_response_generated';
-import {Delete2024DataScouting} from '../../webserver/requests/messages/delete_2024_data_scouting_generated';
-import {Delete2024DataScoutingResponse} from '../../webserver/requests/messages/delete_2024_data_scouting_response_generated';
+} from '@org_frc971/scouting/webserver/requests/messages/request_all_notes_response_generated';
+import {Delete2024DataScouting} from '@org_frc971/scouting/webserver/requests/messages/delete_2024_data_scouting_generated';
+import {Delete2024DataScoutingResponse} from '@org_frc971/scouting/webserver/requests/messages/delete_2024_data_scouting_response_generated';
 
-import {ViewDataRequestor} from '../rpc';
+import {
+  MatchListRequestor,
+  ViewDataRequestor,
+} from '@org_frc971/scouting/www/rpc';
 
 type Source = 'Notes' | 'Stats2024' | 'PitImages' | 'DriverRanking';
 
diff --git a/tools/build_rules/js.bzl b/tools/build_rules/js.bzl
index c2468f8..b735802 100644
--- a/tools/build_rules/js.bzl
+++ b/tools/build_rules/js.bzl
@@ -1,4 +1,5 @@
 load("@aspect_rules_js//js:providers.bzl", "JsInfo")
+load("@aspect_rules_js//npm:defs.bzl", "npm_package")
 load("@bazel_skylib//rules:write_file.bzl", "write_file")
 load("@aspect_bazel_lib//lib:copy_to_directory.bzl", "copy_to_directory")
 load("@aspect_bazel_lib//lib:copy_file.bzl", "copy_file")
@@ -276,13 +277,20 @@
         srcs.append(":_public_api")
 
     ng_project(
-        name = name,
+        name = "_lib",
         srcs = srcs + [":_index"],
         deps = deps + PACKAGE_DEPS,
-        visibility = visibility,
+        visibility = ["//visibility:private"],
         **kwargs
     )
 
+    npm_package(
+        name = name,
+        srcs = ["package.json", ":_lib"],
+        include_runfiles = False,
+        visibility = visibility,
+    )
+
 def rollup_bundle(name, entry_point, node_modules = "//:node_modules", deps = [], visibility = None, **kwargs):
     """Calls the upstream rollup_bundle() and exposes a .min.js file.
 
diff --git a/tsconfig.json b/tsconfig.json
index bd23965..3999b5c 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -17,5 +17,10 @@
   },
   "bazelOptions": {
     "workspaceName": "971-Robot-Code"
+  },
+  "angularCompilerOptions": {
+    "strictInjectionParameters": true,
+    "strictInputAccessModifiers": true,
+    "strictTemplates": true
   }
 }
diff --git a/y2024/constants/7971.json b/y2024/constants/7971.json
index 3beaae0..2fe58cf 100644
--- a/y2024/constants/7971.json
+++ b/y2024/constants/7971.json
@@ -71,7 +71,9 @@
       ) %}
       "zeroing_constants": {{ extend_zero | tojson(indent=2)}},
       "potentiometer_offset": 0.0
-    }
+    },
+    "disable_extend": false,
+    "disable_climber": false
   },
   {% include 'y2024/constants/common.json' %}
 }
diff --git a/y2024/constants/971.json b/y2024/constants/971.json
index 974fb60..4fc2f3a 100644
--- a/y2024/constants/971.json
+++ b/y2024/constants/971.json
@@ -71,7 +71,9 @@
       ) %}
       "zeroing_constants": {{ extend_zero | tojson(indent=2)}},
       "potentiometer_offset": {{ -0.2574404033256 + 0.0170793439542 - 0.177097393974999 + 0.3473623911879  - 0.1577}}
-    }
+    },
+    "disable_extend": false,
+    "disable_climber": true
   },
   {% include 'y2024/constants/common.json' %}
 }
diff --git a/y2024/constants/9971.json b/y2024/constants/9971.json
index ec59033..23cad58 100644
--- a/y2024/constants/9971.json
+++ b/y2024/constants/9971.json
@@ -62,7 +62,9 @@
       ) %}
       "zeroing_constants": {{ extend_zero | tojson(indent=2)}},
       "potentiometer_offset": 0.0
-    }
+    },
+    "disable_extend": false,
+    "disable_climber": false
   },
   {% include 'y2024/constants/common.json' %}
 }
diff --git a/y2024/constants/common.json b/y2024/constants/common.json
index 25fae23..99947ef 100644
--- a/y2024/constants/common.json
+++ b/y2024/constants/common.json
@@ -270,4 +270,4 @@
     "red": [1, 2, 5, 6, 9, 10, 11, 12, 13, 14, 15, 16],
     "blue": [1, 2, 5, 6, 9, 10, 11, 12, 13, 14, 15, 16]
   }
-}
+}
\ No newline at end of file
diff --git a/y2024/constants/constants.fbs b/y2024/constants/constants.fbs
index c679128..99d49a4 100644
--- a/y2024/constants/constants.fbs
+++ b/y2024/constants/constants.fbs
@@ -132,6 +132,8 @@
   altitude_constants:PotAndAbsEncoderConstants (id: 3);
   turret_constants:PotAndAbsEncoderConstants (id: 4);
   extend_constants:PotAndAbsEncoderConstants (id: 5);
+  disable_extend:bool (id: 6);
+  disable_climber:bool (id: 7);
 }
 
 table ShooterSetPoint {
diff --git a/y2024/constants/test_data/test_team.json b/y2024/constants/test_data/test_team.json
index b717224..37d9de1 100644
--- a/y2024/constants/test_data/test_team.json
+++ b/y2024/constants/test_data/test_team.json
@@ -71,7 +71,9 @@
       ) %}
       "zeroing_constants": {{ extend_zero | tojson(indent=2)}},
       "potentiometer_offset": 0.0
-    }
+    },
+    "disable_extend": false,
+    "disable_climber": false
   },
   {% include 'y2024/constants/common.json' %}
 }
diff --git a/y2024/control_loops/superstructure/shooter.cc b/y2024/control_loops/superstructure/shooter.cc
index 4421e15..88bf390 100644
--- a/y2024/control_loops/superstructure/shooter.cc
+++ b/y2024/control_loops/superstructure/shooter.cc
@@ -181,18 +181,15 @@
   const bool disabled = turret_.Correct(turret_goal, position->turret(),
                                         turret_output == nullptr);
 
+  // Zero out extend goal and position if "disable_extend" is true
   collision_avoidance->UpdateGoal(
       {.intake_pivot_position = intake_pivot_position,
        .turret_position = turret_.estimated_position(),
-       .extend_position = extend_position},
-      turret_goal->unsafe_goal(), extend_goal);
-
-  if (!CatapultRetracted()) {
-    altitude_.set_min_position(
-        robot_constants_->common()->min_altitude_shooting_angle());
-  } else {
-    altitude_.clear_min_position();
-  }
+       .extend_position =
+           ((!robot_constants_->robot()->disable_extend()) ? extend_position
+                                                           : 0.0)},
+      turret_goal->unsafe_goal(),
+      ((!robot_constants_->robot()->disable_extend()) ? extend_goal : 0.0));
 
   if (!CatapultRetracted()) {
     altitude_.set_min_position(
diff --git a/y2024/control_loops/superstructure/superstructure.cc b/y2024/control_loops/superstructure/superstructure.cc
index d3e98ab..4ab5966 100644
--- a/y2024/control_loops/superstructure/superstructure.cc
+++ b/y2024/control_loops/superstructure/superstructure.cc
@@ -159,9 +159,12 @@
       robot_constants_->common()->extend_set_points();
 
   // Checks if the extend is close enough to the retracted position to be
-  // considered ready to accept note from the transfer rollers.
-  const bool extend_at_retracted = PositionNear(
-      extend_.position(), extend_set_points->retracted(), kExtendThreshold);
+  // considered ready to accept note from the transfer rollers. If disable
+  // extend is triggered, this will autoatically be false.
+  const bool extend_at_retracted =
+      (!robot_constants_->robot()->disable_extend() &&
+       PositionNear(extend_.position(), extend_set_points->retracted(),
+                    kExtendThreshold));
 
   // Check if the turret is at the position to accept the note from extend
   const bool turret_ready_for_load =
@@ -525,10 +528,13 @@
 
   drivetrain_status_fetcher_.Fetch();
 
+  // Zero out extend position if "disable_extend" is true
   const bool collided = collision_avoidance_.IsCollided({
       .intake_pivot_position = intake_pivot_.estimated_position(),
       .turret_position = shooter_.turret().estimated_position(),
-      .extend_position = extend_.estimated_position(),
+      .extend_position = ((!robot_constants_->robot()->disable_extend())
+                              ? extend_.estimated_position()
+                              : 0.0),
   });
 
   aos::FlatbufferFixedAllocatorArray<
@@ -635,6 +641,16 @@
           output != nullptr ? &output_struct.extend_voltage : nullptr,
           status->fbb());
 
+  // Zero out extend voltage if "disable_extend" is true
+  if (robot_constants_->robot()->disable_extend()) {
+    output_struct.extend_voltage = 0.0;
+  }
+
+  // Zero out climber voltage if "disable_climber" is true
+  if (robot_constants_->robot()->disable_climber()) {
+    output_struct.climber_voltage = 0.0;
+  }
+
   if (output) {
     output->CheckOk(output->Send(Output::Pack(*output->fbb(), &output_struct)));
   }
diff --git a/y2024/vision/target_mapping.cc b/y2024/vision/target_mapping.cc
index d2fb26e..fac7e81 100644
--- a/y2024/vision/target_mapping.cc
+++ b/y2024/vision/target_mapping.cc
@@ -32,7 +32,7 @@
               "Write the target constraints to this path");
 DEFINE_string(dump_stats_to, "/tmp/mapping_stats.txt",
               "Write the mapping stats to this path");
-DEFINE_string(field_name, "charged_up",
+DEFINE_string(field_name, "crescendo",
               "Field name, for the output json filename and flatbuffer field");
 DEFINE_string(json_path, "y2024/vision/maps/target_map.json",
               "Specify path for json with initial pose guesses.");
@@ -52,6 +52,8 @@
 DEFINE_uint64(skip_to, 1,
               "Start at combined image of this number (1 is the first image)");
 DEFINE_bool(solve, true, "Whether to solve for the field's target map.");
+DEFINE_bool(split_field, false,
+            "Whether to break solve into two sides of field");
 DEFINE_int32(team_number, 0,
              "Required: Use the calibration for a node with this team number");
 DEFINE_uint64(wait_key, 1,
@@ -89,6 +91,9 @@
   // Contains fixed target poses without solving, for use with visualization
   static const TargetMapper kFixedTargetMapper;
 
+  // Map of TargetId to alliance "color" for splitting field
+  static std::map<uint, std::string> kIdAllianceMap;
+
   // Change reference frame from camera to robot
   static Eigen::Affine3d CameraToRobotDetection(Eigen::Affine3d H_camera_target,
                                                 Eigen::Affine3d extrinsics);
@@ -110,8 +115,8 @@
   std::vector<DataAdapter::TimestampedDetection> timestamped_target_detections_;
 
   VisualizeRobot vis_robot_;
-  // Set of node names which are currently drawn on the display
-  std::set<std::string> drawn_nodes_;
+  // Set of camera names which are currently drawn on the display
+  std::set<std::string> drawn_cameras_;
   // Number of frames displayed
   size_t display_count_;
   // Last time we drew onto the display image.
@@ -124,6 +129,9 @@
   // used to determine if we need to pause for the user to see this frame
   // clearly
   double max_delta_T_world_robot_;
+  double ignore_count_;
+
+  std::map<std::string, int> camera_numbering_map_;
 
   std::vector<std::unique_ptr<aos::EventLoop>> mapping_event_loops_;
 
@@ -138,6 +146,12 @@
     {"/imu/camera1", cv::Scalar(255, 165, 0)},
 };
 
+std::map<uint, std::string> TargetMapperReplay::kIdAllianceMap = {
+    {1, "red"},  {2, "red"},   {3, "red"},   {4, "red"},
+    {5, "red"},  {6, "blue"},  {7, "blue"},  {8, "blue"},
+    {9, "blue"}, {10, "blue"}, {11, "red"},  {12, "red"},
+    {13, "red"}, {14, "blue"}, {15, "blue"}, {16, "blue"}};
+
 const auto TargetMapperReplay::kFixedTargetMapper =
     TargetMapper(FLAGS_json_path, ceres::examples::VectorOfConstraints{});
 
@@ -152,13 +166,17 @@
     : reader_(reader),
       timestamped_target_detections_(),
       vis_robot_(cv::Size(1280, 1000)),
-      drawn_nodes_(),
+      drawn_cameras_(),
       display_count_(0),
       last_draw_time_(aos::distributed_clock::min_time),
       last_H_world_robot_(Eigen::Matrix4d::Identity()),
       max_delta_T_world_robot_(0.0) {
   reader_->RemapLoggedChannel("/orin1/constants", "y2024.Constants");
   reader_->RemapLoggedChannel("/imu/constants", "y2024.Constants");
+  // If it's Box of Orins, don't remap roborio constants
+  if (FLAGS_team_number == 7971) {
+    reader_->RemapLoggedChannel("/roborio/constants", "y2024.Constants");
+  }
   reader_->Register();
 
   SendSimulationConstants(reader_->event_loop_factory(), FLAGS_team_number,
@@ -168,6 +186,7 @@
   node_list.push_back("imu");
   node_list.push_back("orin1");
 
+  int camera_count = 0;
   for (std::string node : node_list) {
     const aos::Node *pi =
         aos::configuration::GetNode(reader->configuration(), node);
@@ -186,6 +205,10 @@
     HandleNodeCaptures(
         mapping_event_loops_[mapping_event_loops_.size() - 1].get(),
         &constants_fetcher, 1);
+    std::string camera0_name = "/" + node + "/camera0";
+    camera_numbering_map_[camera0_name] = camera_count++;
+    std::string camera1_name = "/" + node + "/camera1";
+    camera_numbering_map_[camera1_name] = camera_count++;
   }
 
   if (FLAGS_visualize_solver) {
@@ -205,6 +228,11 @@
   std::stringstream label;
   label << camera_name << " - ";
 
+  if (map.target_poses()->size() == 0) {
+    VLOG(2) << "Got 0 AprilTags for camera " << camera_name;
+    return;
+  }
+
   for (const auto *target_pose_fbs : *map.target_poses()) {
     // Skip detections with invalid ids
     if (static_cast<TargetMapper::TargetId>(target_pose_fbs->id()) <
@@ -242,13 +270,18 @@
     double distance_from_camera = target_pose_camera.p.norm();
     double distortion_factor = target_pose_fbs->distortion_factor();
 
-    if (distance_from_camera > 5.0) {
+    double distance_threshold = 5.0;
+    if (distance_from_camera > distance_threshold) {
+      ignore_count_++;
+      LOG(INFO) << "Ignored " << ignore_count_ << " AprilTags with distance "
+                << distance_from_camera << " > " << distance_threshold;
       continue;
     }
 
     CHECK(map.has_monotonic_timestamp_ns())
         << "Need detection timestamps for mapping";
 
+    // Detection is usable, so store it
     timestamped_target_detections_.emplace_back(
         DataAdapter::TimestampedDetection{
             .time = node_distributed_time,
@@ -260,7 +293,7 @@
     if (FLAGS_visualize_solver) {
       // If we've already drawn this camera_name in the current image,
       // display the image before clearing and adding the new poses
-      if (drawn_nodes_.count(camera_name) != 0) {
+      if (drawn_cameras_.count(camera_name) != 0) {
         display_count_++;
         cv::putText(vis_robot_.image_,
                     "Poses #" + std::to_string(display_count_),
@@ -268,7 +301,7 @@
                     cv::Scalar(255, 255, 255));
 
         if (display_count_ >= FLAGS_skip_to) {
-          VLOG(1) << "Showing image for node " << camera_name
+          VLOG(1) << "Showing image for camera " << camera_name
                   << " since we've drawn it already";
           cv::imshow("View", vis_robot_.image_);
           // Pause if delta_T is too large, but only after first image (to make
@@ -284,10 +317,10 @@
           }
           max_delta_T_world_robot_ = 0.0;
         } else {
-          VLOG(1) << "At poses #" << std::to_string(display_count_);
+          VLOG(2) << "At poses #" << std::to_string(display_count_);
         }
         vis_robot_.ClearImage();
-        drawn_nodes_.clear();
+        drawn_cameras_.clear();
       }
 
       Eigen::Affine3d H_world_target = PoseUtils::Pose3dToAffine3d(
@@ -316,7 +349,7 @@
       max_delta_T_world_robot_ =
           std::max(delta_T_world_robot, max_delta_T_world_robot_);
 
-      VLOG(1) << "Drew in info for robot " << camera_name << " and target #"
+      VLOG(1) << "Drew in info for camera " << camera_name << " and target #"
               << target_pose_fbs->id();
       drew = true;
       last_draw_time_ = node_distributed_time;
@@ -325,26 +358,30 @@
   }
   if (FLAGS_visualize_solver) {
     if (drew) {
-      // Collect all the labels from a given node, and add the text
-      size_t pi_number =
-          static_cast<size_t>(camera_name[camera_name.size() - 1] - '0');
+      // Collect all the labels from a given camera, and add the text
+      // TODO: Need to fix this one
+      int position_number = camera_numbering_map_[camera_name];
       cv::putText(vis_robot_.image_, label.str(),
-                  cv::Point(10, 10 + 20 * pi_number), cv::FONT_HERSHEY_PLAIN,
-                  1.0, kOrinColors.at(camera_name));
+                  cv::Point(10, 10 + 20 * position_number),
+                  cv::FONT_HERSHEY_PLAIN, 1.0, kOrinColors.at(camera_name));
 
-      drawn_nodes_.emplace(camera_name);
+      drawn_cameras_.emplace(camera_name);
     } else if (node_distributed_time - last_draw_time_ >
                    std::chrono::milliseconds(30) &&
-               display_count_ >= FLAGS_skip_to) {
-      cv::putText(vis_robot_.image_, "No detections", cv::Point(10, 0),
-                  cv::FONT_HERSHEY_PLAIN, 1.0, kOrinColors.at(camera_name));
+               display_count_ >= FLAGS_skip_to && drew) {
+      // TODO: Check on 30ms value-- does this make sense?
+      double delta_t = (node_distributed_time - last_draw_time_).count() / 1e6;
+      VLOG(1) << "Last result was " << delta_t << "ms ago";
+      cv::putText(vis_robot_.image_, "No detections in last 30ms",
+                  cv::Point(10, 0), cv::FONT_HERSHEY_PLAIN, 1.0,
+                  kOrinColors.at(camera_name));
       // Display and clear the image if we haven't draw in a while
       VLOG(1) << "Displaying image due to time lapse";
       cv::imshow("View", vis_robot_.image_);
       cv::waitKey(FLAGS_wait_key);
       vis_robot_.ClearImage();
       max_delta_T_world_robot_ = 0.0;
-      drawn_nodes_.clear();
+      drawn_cameras_.clear();
     }
   }
 }
@@ -383,21 +420,20 @@
     auto target_constraints =
         DataAdapter::MatchTargetDetections(timestamped_target_detections_);
 
-    // Remove constraints between the two sides of the field - these are
-    // basically garbage because of how far the camera is. We will use seeding
-    // below to connect the two sides
-    target_constraints.erase(
-        std::remove_if(target_constraints.begin(), target_constraints.end(),
-                       [](const auto &constraint) {
-                         // TODO(james): This no longer makes sense.
-                         constexpr TargetMapper::TargetId kMaxRedId = 4;
-                         TargetMapper::TargetId min_id =
-                             std::min(constraint.id_begin, constraint.id_end);
-                         TargetMapper::TargetId max_id =
-                             std::max(constraint.id_begin, constraint.id_end);
-                         return (min_id <= kMaxRedId && max_id > kMaxRedId);
-                       }),
-        target_constraints.end());
+    if (FLAGS_split_field) {
+      // Remove constraints between the two sides of the field - these are
+      // basically garbage because of how far the camera is. We will use seeding
+      // below to connect the two sides
+      target_constraints.erase(
+          std::remove_if(
+              target_constraints.begin(), target_constraints.end(),
+              [](const auto &constraint) {
+                return (
+                    kIdAllianceMap[static_cast<uint>(constraint.id_begin)] !=
+                    kIdAllianceMap[static_cast<uint>(constraint.id_end)]);
+              }),
+          target_constraints.end());
+    }
 
     LOG(INFO) << "Solving for locations of tags with "
               << target_constraints.size() << " constraints";
diff --git a/y2024/wpilib_interface.cc b/y2024/wpilib_interface.cc
index b67b07e..803845c 100644
--- a/y2024/wpilib_interface.cc
+++ b/y2024/wpilib_interface.cc
@@ -492,10 +492,13 @@
         7, true, "rio", &rio_signal_registry,
         current_limits->climber_stator_current_limit(),
         current_limits->climber_supply_current_limit());
-    std::shared_ptr<TalonFX> extend = std::make_shared<TalonFX>(
-        12, false, "Drivetrain Bus", &canivore_signal_registry,
-        current_limits->extend_stator_current_limit(),
-        current_limits->extend_supply_current_limit());
+    std::shared_ptr<TalonFX> extend =
+        (robot_constants->robot()->disable_extend())
+            ? nullptr
+            : std::make_shared<TalonFX>(
+                  12, false, "Drivetrain Bus", &canivore_signal_registry,
+                  current_limits->extend_stator_current_limit(),
+                  current_limits->extend_supply_current_limit());
     std::shared_ptr<TalonFX> intake_roller = std::make_shared<TalonFX>(
         8, false, "rio", &rio_signal_registry,
         current_limits->intake_roller_stator_current_limit(),
@@ -549,7 +552,9 @@
 
     for (auto talonfx :
          {intake_pivot, turret, altitude, catapult_one, catapult_two, extend}) {
-      canivore_talonfxs.push_back(talonfx);
+      if (talonfx != nullptr) {
+        canivore_talonfxs.push_back(talonfx);
+      }
     }
 
     for (auto talonfx : {intake_roller, transfer_roller, climber, extend_roller,
@@ -612,8 +617,10 @@
           catapult_two->SerializePosition(
               superstructure_can_builder->add_catapult_two(),
               control_loops::superstructure::catapult::kOutputRatio);
-          extend->SerializePosition(superstructure_can_builder->add_extend(),
-                                    superstructure::extend::kOutputRatio);
+          if (extend != nullptr) {
+            extend->SerializePosition(superstructure_can_builder->add_extend(),
+                                      superstructure::extend::kOutputRatio);
+          }
 
           superstructure_can_builder->set_status(static_cast<int>(status));
           superstructure_can_builder.CheckOk(superstructure_can_builder.Send());
@@ -682,8 +689,10 @@
                   output.turret_voltage());
               talonfx_map.find("climber")->second->WriteVoltage(
                   output.climber_voltage());
-              talonfx_map.find("extend")->second->WriteVoltage(
-                  output.extend_voltage());
+              if (talonfx_map.find("extend") != talonfx_map.end()) {
+                talonfx_map.find("extend")->second->WriteVoltage(
+                    output.extend_voltage());
+              }
               talonfx_map.find("intake_roller")
                   ->second->WriteVoltage(output.intake_roller_voltage());
               talonfx_map.find("transfer_roller")
@@ -705,7 +714,9 @@
     can_superstructure_writer.add_talonfx("catapult_two", catapult_two);
     can_superstructure_writer.add_talonfx("turret", turret);
     can_superstructure_writer.add_talonfx("climber", climber);
-    can_superstructure_writer.add_talonfx("extend", extend);
+    if (!robot_constants->robot()->disable_extend()) {
+      can_superstructure_writer.add_talonfx("extend", extend);
+    }
     can_superstructure_writer.add_talonfx("intake_roller", intake_roller);
     can_superstructure_writer.add_talonfx("transfer_roller", transfer_roller);
     can_superstructure_writer.add_talonfx("extend_roller", extend_roller);
diff --git a/y2024/y2024_imu.json b/y2024/y2024_imu.json
index f7dd7c2..ed43e10 100644
--- a/y2024/y2024_imu.json
+++ b/y2024/y2024_imu.json
@@ -181,7 +181,7 @@
       "type": "frc971.vision.CameraImage",
       "source_node": "imu",
       "channel_storage_duration": 1000000000,
-      "frequency": 65,
+      "frequency": 70,
       "max_size": 4752384,
       "num_readers": 6,
       "read_method": "PIN",
@@ -192,7 +192,7 @@
       "type": "frc971.vision.CameraImage",
       "source_node": "imu",
       "channel_storage_duration": 1000000000,
-      "frequency": 65,
+      "frequency": 70,
       "max_size": 4752384,
       "num_readers": 6,
       "read_method": "PIN",
@@ -204,7 +204,7 @@
       "source_node": "imu",
       "logger": "NOT_LOGGED",
       "channel_storage_duration": 1000000000,
-      "frequency": 65,
+      "frequency": 70,
       "max_size": 622384
     },
     {
@@ -213,42 +213,42 @@
       "source_node": "imu",
       "logger": "NOT_LOGGED",
       "channel_storage_duration": 1000000000,
-      "frequency": 65,
+      "frequency": 70,
       "max_size": 622384
     },
     {
       "name": "/imu/camera0",
       "type": "foxglove.ImageAnnotations",
       "source_node": "imu",
-      "frequency": 65,
+      "frequency": 70,
       "max_size": 50000
     },
     {
       "name": "/imu/camera1",
       "type": "foxglove.ImageAnnotations",
       "source_node": "imu",
-      "frequency": 65,
+      "frequency": 70,
       "max_size": 50000
     },
     {
       "name": "/imu/camera0",
       "type": "y2024.localizer.Visualization",
       "source_node": "imu",
-      "frequency": 65,
+      "frequency": 70,
       "max_size": 50000
     },
     {
       "name": "/imu/camera1",
       "type": "y2024.localizer.Visualization",
       "source_node": "imu",
-      "frequency": 65,
+      "frequency": 70,
       "max_size": 50000
     },
     {
       "name": "/imu/camera0",
       "type": "frc971.vision.TargetMap",
       "source_node": "imu",
-      "frequency": 65,
+      "frequency": 70,
       "num_senders": 2,
       "max_size": 1024
     },
@@ -256,7 +256,7 @@
       "name": "/imu/camera1",
       "type": "frc971.vision.TargetMap",
       "source_node": "imu",
-      "frequency": 65,
+      "frequency": 70,
       "num_senders": 2,
       "max_size": 1024
     },
diff --git a/y2024/y2024_orin1.json b/y2024/y2024_orin1.json
index ecb9342..6072da8 100644
--- a/y2024/y2024_orin1.json
+++ b/y2024/y2024_orin1.json
@@ -120,7 +120,7 @@
       "type": "frc971.vision.CameraImage",
       "source_node": "orin1",
       "channel_storage_duration": 1000000000,
-      "frequency": 65,
+      "frequency": 70,
       "max_size": 4752384,
       "num_readers": 6,
       "read_method": "PIN",
@@ -131,7 +131,7 @@
       "type": "frc971.vision.CameraImage",
       "source_node": "orin1",
       "channel_storage_duration": 1000000000,
-      "frequency": 65,
+      "frequency": 70,
       "max_size": 4752384,
       "num_readers": 6,
       "read_method": "PIN",
@@ -143,7 +143,7 @@
       "source_node": "orin1",
       "logger": "NOT_LOGGED",
       "channel_storage_duration": 1000000000,
-      "frequency": 65,
+      "frequency": 70,
       "max_size": 622384
     },
     {
@@ -152,42 +152,42 @@
       "source_node": "orin1",
       "logger": "NOT_LOGGED",
       "channel_storage_duration": 1000000000,
-      "frequency": 65,
+      "frequency": 70,
       "max_size": 622384
     },
     {
       "name": "/orin1/camera0",
       "type": "foxglove.ImageAnnotations",
       "source_node": "orin1",
-      "frequency": 65,
+      "frequency": 70,
       "max_size": 50000
     },
     {
       "name": "/orin1/camera1",
       "type": "foxglove.ImageAnnotations",
       "source_node": "orin1",
-      "frequency": 65,
+      "frequency": 70,
       "max_size": 50000
     },
     {
       "name": "/orin1/camera0",
       "type": "y2024.localizer.Visualization",
       "source_node": "imu",
-      "frequency": 65,
+      "frequency": 70,
       "max_size": 50000
     },
     {
       "name": "/orin1/camera1",
       "type": "y2024.localizer.Visualization",
       "source_node": "imu",
-      "frequency": 65,
+      "frequency": 70,
       "max_size": 50000
     },
     {
       "name": "/orin1/camera0",
       "type": "frc971.vision.TargetMap",
       "source_node": "orin1",
-      "frequency": 65,
+      "frequency": 70,
       "num_senders": 2,
       "max_size": 1024,
       "logger": "LOCAL_AND_REMOTE_LOGGER",
@@ -210,7 +210,7 @@
       "name": "/orin1/camera1",
       "type": "frc971.vision.TargetMap",
       "source_node": "orin1",
-      "frequency": 65,
+      "frequency": 70,
       "num_senders": 2,
       "max_size": 1024,
       "logger": "LOCAL_AND_REMOTE_LOGGER",