Commit Graph
143 Commits
Author SHA1 Message Date
agessaman 2324f23832 perf(web-viewer): speed up mesh graph load
Three independent wins, measured against a 1.4 GB production database
(714k observed_paths, 38.7k mesh_connections):

- Migration 0014: partial covering index on observed_paths for
  bytes_per_hop >= 2. The multibyte evidence query was a full table
  scan (2.2 s); with the index it reads only the index (77 ms). The
  optional days filter is covered too via last_seen in slot 2.

- Compress responses with flask-compress when the client supports it.
  /api/mesh/edges alone is 16.3 MB of JSON uncompressed, 3.9 MB
  gzipped. Falls back gracefully (with a warning) if the package is
  not installed.

- Fetch stats, edges, and nodes concurrently on the mesh page instead
  of three serialized awaits. Node prefixes are now computed client-side
  from public_key at the edge prefix length, which removes the
  edges-before-nodes ordering dependency. Rendering still waits for
  stats so initial framing can use the bot location.

Also call map.invalidateSize() before the initial fitBounds: if the
map was created while its container had no layout (e.g. a background
tab), Leaflet's cached zero width made fitBounds zoom to max.
2026-07-12 15:00:46 -07:00
agessaman 91b1607bf5 feat(web-viewer): frame initial mesh map on the home multi-byte component
Nodes occasionally carry wrong GPS, which stretched the initial
fit-to-all-nodes view across continents. /api/mesh/stats now exposes
the bot's configured position ([Bot] bot_latitude/bot_longitude), and
the mesh page BFSes the multi-byte-evidence subgraph from repeaters
within 30 km of the bot (nearest one as fallback), framing the initial
map view to that connected component. Falls back to the old fit when
the bot position is unset or the component is trivially small; other
mesh islands stay on the map, just outside the initial frame.
2026-07-12 11:57:28 -07:00
agessaman b2f700e15f feat(web-viewer): heat-colored repeater dots and density-aware sizing
- Hot connections mode now also colors repeater dots with viridis by
  their total observation count across touching edges (log-normalized,
  same dark-mode ramp compression as edges); node popup gains a Path
  Observations row and the heat legend a clarifying note
- Density pass: smaller dot base/cap (5..13 vs 6..16), edge widths
  capped at 5px, and both dots and lines scale down to 55% as the map
  zooms out (full size from zoom 12 in), rescaled live on zoomend
- Node degree and observation totals are now precomputed once per
  filter pass (computeNodeStats) instead of per-node scans over all
  edges, using the same endpoint resolution as the renderers
2026-07-12 11:57:28 -07:00
agessaman 47e2047d5f fix(web-viewer): allow CARTO basemap tiles in the CSP img-src
The dark-mode basemap loads from *.basemaps.cartocdn.com, which the
Content-Security-Policy image allowlist blocked.
2026-07-12 11:57:28 -07:00
agessaman 1f7a1db1b0 feat(web-viewer): mesh graph dark mode, viridis heat coloring, compact header
- Multi-byte evidence is now the default mode for the mesh graph
- Mesh display theme: follows the site theme until toggled on the page,
  then remembered independently (localStorage); dark CARTO basemap,
  dark Leaflet popups/controls/legends, dark graph canvas
- Hot connections: optional viridis edge coloring by log-scaled
  observation count with a gradient legend; the near-black cold end of
  the ramp is compressed away in dark mode so cold edges stay visible
- Single-row header (title + stat chips + icon controls) replaces the
  title and stats-card rows; footer hidden on this page to maximize the
  visualization viewport
- Mobile: page scrolls naturally instead of starving the fixed-height
  flex container (which stranded the absolutely-positioned legends)
2026-07-12 11:57:28 -07:00
agessaman fb9d5f4115 fix(web-viewer): log-scale Min Observations slider with sane default
The old slider defaulted to floor(avg observations) — on heavy-tailed data
the average sits above the 75th percentile, so the first visit hid most of
the graph. Worse, the default (e.g. 98) exceeded the slider's max of 50:
the browser clamped the control while the label kept showing the unclamped
value, so the label lied about the active filter.

- slider position now maps logarithmically onto 1..max(observation_count),
  rescaled whenever edge data loads, giving fine control at the low end
  and full reach into the tail
- first-visit default is the median observation count of the loaded edges
- the persisted value is the threshold itself, so it stays meaningful when
  the scale changes (e.g. switching evidence modes)
- label shows the threshold plus live 'shown/total edges' feedback
2026-07-12 11:57:28 -07:00
agessaman f2dc37f28f feat(web-viewer): multi-byte evidence mode for the mesh graph
Single-byte repeater identity guessing has been persistently unreliable,
and mesh_connections flattens away evidence provenance (confirmed_2byte
never persists). observed_paths retains it: bytes_per_hop marks which
paths carry unambiguous multi-byte hops.

- /api/mesh/edges?evidence=multibyte derives edges purely from unique
  multi-byte path observations (consecutive hop pairs, aggregated with a
  distinct-path count); 2-byte edges coalesce into a 3-byte edge only on
  a unique prefix match, mirroring MeshGraph.add_edge
- default mode tags each edge evidence=multibyte|singlebyte by key length
- Evidence filter on the mesh page (persisted with the other filters);
  single-byte edges render dashed in map and graph views, with an
  evidence line in tooltips and a legend entry
- client-side haversine fallback for edges without a stored distance
- prefix-compatible edge/node matching so degree sizing and node details
  work when edge and node prefixes differ in resolution
- /api/mesh/stats reports multibyte_edges; shown on the Edges stat card
2026-07-12 11:57:28 -07:00
agessaman 4d49c6b0b8 feat(web-viewer): Node Settings card on the Radio page
Adds device-level companion settings to /radio, headlined by the response
path hash size (mode 0-2 = 1-3 bytes per hop) wired to the existing
firmware config endpoints. New sections: Identity & Adverts (name, advert
lat/lon, location policy, zero-hop/flood advert buttons), Mesh Behavior
(extra ACKs, telemetry permissions), and write-only Advanced Tuning
(RX delay base, airtime factor, sent x1000 per the wire format).

Backend: GET /api/radio/params now returns the full SELF_INFO node fields;
POST accepts the new fields with validation; new POST /api/radio/advert;
scheduler ops handle the writes (other-params group as one read-modify-write
frame so partial updates never clobber device state).

Config-managed settings are not writable from the panel: new-contact mode
is owned by [Bot] auto_manage_contacts and shown read-only; the node name
is locked while bot_name + auto_update_device_name manage it. loop.detect
is removed from the firmware endpoints entirely - it is repeater/room-server
CLI config, and companion custom vars map to sensor settings.

Also: dark-mode styling for disabled form fields (base.html), TASK-01 guard
test updated for the deliberate reintroduction, 23 endpoint/template tests.
2026-07-12 11:57:28 -07:00
agessaman 2e8ccf283a fix(settings-ui): make config reload honor deletions and cached command settings
- reload_config: clear all sections before re-reading so keys deleted from
  config.ini (e.g. rows removed in the settings UI) don't survive in memory
  (ConfigParser.read merges instead of replacing)
- reload_config: re-instantiate command plugins so settings cached in
  __init__ (including 'enabled') take effect on reload
- /api/plugins save: treat dynamic_sections / repeating_blocks absent from
  the payload as 'don't touch' instead of 'delete all managed keys'
- settings view: respect per-plugin settings_enabled_default so opt-in
  commands (Announcements, Greeter) display as off when unconfigured
- ruff: drop unused import, organize command_manager imports
- add tests for ini_writer, settings_schema, settings_store and the
  /api/plugins endpoints (38 cases) plus reload regression tests
2026-07-12 11:57:28 -07:00
agessaman 3ccfd63613 feat(config): add config panel for modifying plugin settings, with config schema in each command and service plugin. 2026-07-12 11:57:28 -07:00
agessaman b59fa9cec9 feat(config, docs): enhance rain command and stats collection configuration
- Updated the `[Rain_Command]` section in `config.ini.example` to include support for snow alongside rain, improving the command's functionality.
- Enhanced documentation for the rain command to reflect the new snow alias and clarify response behavior based on the selected keyword.
- Added a new `collect_stats` option in the `[Stats_Command]` section, allowing stats collection to be enabled independently of the user-facing command, with updated documentation to explain its behavior.
- Improved the web viewer documentation to clarify how stats are collected and displayed, ensuring users understand the configuration options.
2026-06-28 13:28:23 -07:00
agessaman 4da77b7527 feat(db_manager, web_viewer): enhance database path diagnostics and validation
- Added detailed diagnostics for database initialization failures in `DBManager`, improving error logging with filesystem checks.
- Implemented a new method `_resolve_viewer_db_path` in `WebViewerIntegration` to determine the database path for the viewer subprocess.
- Introduced `_preflight_database_path` to validate the viewer database path, ensuring the parent directory exists, is writable, and is a directory.
- Added unit tests to verify logging behavior for missing or invalid database paths in both `DBManager` and `WebViewerIntegration`.
2026-06-27 16:56:13 -07:00
agessaman 21676f0792 feat(worldcup): add support for announcing disallowed goals and enhance goal tracking
- Introduced a new configuration option `announce_disallowed` to allow notifications for goals overturned by VAR.
- Updated `WorldCupLiveService` to handle disallowed goals, including formatting for previously announced scorers.
- Enhanced `ESPNClient` to ensure accurate goal tracking and state management.
- Added unit tests to validate the new disallowed goal functionality and ensure correct behavior during matches.
2026-06-17 22:11:24 -07:00
Adam Gessaman c043156fbf fix(web_viewer): sanitize contact and node names in HTML templates
- Updated `contacts.html` to escape HTML for contact usernames and advertisement data, preventing potential XSS vulnerabilities.
- Modified `mesh.html` to escape node names and prefixes in tooltips, ensuring safe rendering in the vis-network.
- Removed unnecessary parameters from the `showDeleteConfirmation` method to derive the username directly from the contact data.
2026-06-03 14:23:22 -07:00
agessaman 9b437efb05 feat(utils): add public_key_has_prefix function and refactor path command logic
- Introduced a new utility function `public_key_has_prefix` to check if a public key starts with a specified prefix in a case-insensitive manner.
- Updated the `PathCommand` class to utilize the new utility function for public key prefix matching, enhancing code readability and maintainability.
- Refactored logic in `PathCommand` and `BotDataViewer` to streamline the handling of recent repeaters based on recency scores, improving clarity in the filtering process.
2026-06-01 15:27:27 -07:00
agessaman 0b56e86de9 feat(multibyte): add multibyte monitor feature with API endpoints
- Introduced a new configuration option `multibyte_monitor_enabled` in `config.ini.example` to control the visibility of the multibyte monitor page and API endpoints.
- Implemented the `/multibyte-rollout` and `/api/multibyte-rollout` routes in the web viewer, which are accessible only when the multibyte monitor feature is enabled.
- Updated documentation in `web-viewer.md` to reflect the new feature and its configuration.
- Added tests to ensure the multibyte routes are disabled by default and accessible when enabled.
2026-05-18 16:39:48 -07:00
agessaman 5a484d1db2 feat(web_viewer): add channel_name validation and update feed functionality
- Implemented validation for the `channel_name` field to ensure it is not empty when updating feeds, raising a ValueError if validation fails.
- Added test cases to verify that updates to `channel_name` persist correctly and that attempts to set an empty `channel_name` are rejected with an appropriate error response.
2026-04-25 21:34:17 -07:00
agessaman bcb3f48f02 fix(web_viewer): restore mesh export and clean scheduler imports
Prevent exportView runtime failure by restoring the download anchor setup and move radio param imports to module scope so Ruff passes on the PR changes.

Made-with: Cursor
2026-04-25 21:01:14 -07:00
Robowarrior834 e5e84f9171 Fixed map full screen exit on ios 2026-04-21 09:45:16 -04:00
Robowarrior834 217a30f8d7 Added Fullscreen Map and Radio settings. 2026-04-20 16:24:40 -04:00
agessaman 1ee84f22ff refactor(web_viewer): silence subscription status messages in BotDataViewer
Removed emit statements for subscription status messages in the BotDataViewer class to keep connection feedback silent, as the navbar indicator already reflects socket state. This change enhances user experience by reducing unnecessary notifications while maintaining existing functionality.
2026-04-16 20:53:20 -07:00
agessaman 4677a238ac feat(message_handler): enhance message handling with type hints and new data structure
Added a new PendingMessageEntry TypedDict to improve the structure of pending messages. Updated type hints throughout the MessageHandler class for better clarity and consistency, including the initialization method and message processing functions. This refactor enhances code readability and maintainability without altering existing functionality. Additionally, included the new message_handler module in the pyproject.toml for better module organization.
2026-04-16 20:43:19 -07:00
agessaman b5cce55604 feat(config): add configurable timeouts for web viewer integration
Introduced optional timeout settings in the configuration for various web viewer operations, including edge and node post timeouts, SQLite connection timeout, and requeue timeout. Updated the web viewer integration to utilize these settings, enhancing flexibility and reliability. Added commands to inspect the resolved configuration with sensitive keys redacted, and updated documentation accordingly.
2026-04-16 19:56:47 -07:00
agessaman 329905dd08 feat(core, scheduler): implement timeout handling for advert sending
Added asyncio timeout handling for sending startup and interval-based adverts, improving reliability by recording failures on timeout. Updated relevant tests to ensure coverage for timeout scenarios and increment failure counts appropriately. Introduced a new Radio Reliability panel in the web viewer for monitoring radio status.
2026-04-16 19:29:28 -07:00
agessaman ad09e8ba8a fix(web_viewer): update config item retrieval to prevent interpolation issues
Modified the config item retrieval in BotDataViewer to use raw=True, ensuring that literal '%' characters in configuration values are not subject to interpolation. Additionally, removed the breadcrumb navigation from the admin configuration template for a cleaner UI. Added a test to verify that '%' in values does not cause server errors.
2026-04-16 19:09:23 -07:00
agessaman 550a15e0d2 feat(web_viewer): bound packet_stream queue and requeue on flush failure
Add Web_Viewer.packet_stream_write_queue_max (default 1000), timed put
with flush retry on Full, flush lock, and re-queue rows after failed
batch insert. Document deferred PR #147 items. Reduce pytest cov
verbosity to term-only. Extend BotIntegration queue tests.
2026-04-16 19:02:33 -07:00
agessaman 75be386e08 refactor: remove redundant CSS styles from API Explorer template
Eliminated unnecessary CSS rules for the first column in the API Explorer table, streamlining the stylesheet and improving maintainability. This change enhances the clarity of the code without affecting the visual layout.
2026-04-16 18:46:16 -07:00
agessaman e04cc587cb test: add tests for admin config page loading and password redaction
Introduced a new test class, TestAdminConfig, to verify the loading of the admin configuration page and ensure sensitive information like passwords is redacted in the response. This enhances test coverage for the admin configuration functionality.
2026-04-16 18:41:27 -07:00
Stacy Olivasandagessaman fbaf7b50d6 fix: replace duplicate version logic with resolve_runtime_version delegate for #149 (web viewer UX)
After rebasing onto upstream/dev, app.py contained an inline
_get_version_info() duplicating logic already centralized in
modules/version_info.py. Replace with a 5-line delegate to
resolve_runtime_version(), removing the now-unnecessary import subprocess.

Also refreshes tests/fixtures/mqtt_packets.json with current live MQTT
fixture timestamps.

Behavioral note: in a bare dev clone with no .version_info and no
MESHCORE_BOT_VERSION env var, the footer now shows the pyproject.toml
version (v0.1.0) instead of branch·commit·date. Production deployments
are unaffected.

Do not merge until #149 is merged.
2026-04-16 18:37:47 -07:00
Stacy Olivasandagessaman a15827be8f usability: API Explorer tab, actionable error messages (USE-05, USE-06)
- USE-05: Add /api-explorer page listing all ~65 API endpoints in 9
  categories (System, Contacts, Mesh, Channels, Feeds, Radio, Admin,
  Maintenance, Config, Greeter) with method badges, descriptions, and
  curl example modal. Filter bar and collapse per section. Nav item
  added to base.html.

- USE-06: Three targeted error-message improvements:
  1. 500 handler now returns user-friendly HTML page (error.html) for
     browser requests and sanitized JSON for API/JSON requests instead
     of a bare string.
  2. Feed processed-items query failures promoted from logger.debug to
     logger.warning so operators see them in normal log output.
  3. Global JS fetch interceptor in base.html redirects to /login?next=
     on any 401 response, handling session expiry mid-page.

- Fix pre-existing test bug: test_reload_endpoint_success mock return
  value did not match actual code message from reload_config.
2026-04-16 18:33:40 -07:00
Stacy Olivasandagessaman 23f652fe88 feat: early web viewer start with initializing banner, live banner polling 2026-04-16 18:25:19 -07:00
agessaman 572652e712 refactor: improve database connection handling in web viewer and tests
Updated the BotDataViewer class to utilize a context manager for database connections, enhancing resource management. Additionally, refactored test files to implement a centralized approach for managing SQLite connections, ensuring proper cleanup after tests. This change improves code maintainability and reliability across the application.
2026-04-16 11:18:59 -07:00
agessaman e058da4968 fix: harden shutdown (single stop, viewer, MQTT logs, scheduler)
- Make MeshCoreBot.stop idempotent; remove duplicate stop from start().
- Avoid web viewer restart during shutdown; gate main-loop restarts.
- Fix packet capture MQTT callbacks to log each broker’s host.
- Only shutdown APScheduler when running; silence double-shutdown noise.
- Add regression tests in tests/test_shutdown_reliability.py.
2026-04-14 12:46:01 -07:00
agessaman 313dfccf75 fix: Align send suppression and security compatibility behavior
Make outbound send suppression consistent by honoring both radio-offline and zombie states across command-manager and scheduler paths. Preserve strict SSRF defaults while adding explicit private-feed URL opt-ins, persist allow_local_smtp from notifications config writes, reconcile zombie alert setting precedence, and replace deprecated UTC timestamp calls with timezone-aware UTC usage.
2026-04-14 12:18:02 -07:00
agessaman 293383f885 refactor: reorganize radio configuration settings
Moved radio-related configuration options from the [Bot] section to a new [Connection] section in config.ini.example for better organization. Updated the core logic to reference the new configuration paths, ensuring that radio health monitoring and alert settings are correctly handled. This change enhances clarity and maintainability of the configuration structure.
2026-04-14 11:14:28 -07:00
agessaman 6e8204c1ec fix: enhance path validation for security
Updated the path validation logic in security_utils.py and web_viewer/app.py to include additional dangerous prefixes, specifically targeting private directories. This change aims to strengthen security by preventing access to sensitive system paths. Additionally, modified the test for posting feeds to mock the external URL validation, ensuring consistent behavior during tests.
2026-04-14 10:25:18 -07:00
agessaman 887068faa2 fix: resolve merge-marker cleanup and concise config docs
Clean up residual cherry-pick conflict markers and keep SMTP guidance in config templates brief while preserving full behavior in code and tests.

Made-with: Cursor
2026-04-14 10:06:44 -07:00
Stacy Olivasandagessaman 485edc6c91 fix: post-rebase compatibility fixes for #147 (stability hardening)
Two incompatibilities surfaced after rebasing onto upstream/dev in code
introduced by #147:

- tests/integration/test_flood_scope_reply.py: add bot.is_radio_zombie = False
  mock; the zombie-detection guard added to send_channel_message in #147
  causes the call to short-circuit without it, failing the scope assertion;
  also removes unused `call` import
- modules/web_viewer/app.py: replace ~50-line inline _get_version_info()
  with 5-line delegate to resolve_runtime_version(); removes now-unnecessary
  import subprocess; the resolve_runtime_version import is now actually used

Do not merge until #147 is merged.
2026-04-14 10:03:05 -07:00
Stacy Olivasandagessaman 973d1fc947 fix: BUG-028 byte_data init; BUG-LOG-1/4 SocketIO and cleanup_database handler fixes 2026-04-14 10:02:46 -07:00
Stacy Olivasandagessaman 54aeb28bf0 security: SSRF hardening, log injection sanitization, and allow_local_smtp
Add SSRF host validation to maintenance.py send_nightly_email and
scheduler.py send_zombie_alert_email using validate_external_url().
New allow_local_smtp config key permits private-IP SMTP for local
relay setups.

Add sanitize_name() to security_utils and apply it to all log calls
in message_handler, repeater_manager, path_command, solarforecast_command,
command_manager, and discord_bridge_service to prevent log injection.

Move nightly email logic from duplicate scheduler._send_nightly_email()
into the canonical maintenance.py implementation, removing the duplicate.
Update tests to call maintenance.send_nightly_email() directly.

Add validate_external_url allow_private parameter with support for
loopback, RFC1918, CGN, and link-local address ranges.
2026-04-14 10:02:36 -07:00
Stacy Olivasandagessaman 640568bcc9 fix: add X-Requested-With header to zombie-recover and radio-offline-clear fetch calls
Both banner action buttons were posting without the required
X-Requested-With header, causing the CSRF guard to return 403.
2026-04-14 10:01:51 -07:00
Stacy Olivasandagessaman 9ce69702c2 feat: radio debug logging mode with web UI toggle 2026-04-14 10:01:51 -07:00
Stacy Olivasandagessaman 51ab5d312c feat: radio-offline fail state — suppress sends, auto-restart, banner, and docs 2026-04-14 10:01:51 -07:00
Stacy Olivasandagessaman 8b14c40958 feat: zombie radio banner on all pages + zombie alert config screen options
- base.html: persistent danger banner appears on every web page when
  is_radio_zombie is true; shows datetime zombie was detected; includes
  "Restart Bot Processing" button (POST /api/admin/zombie-recover) that
  clears _radio_zombie_detected and _radio_fail_count on the live bot
  object and removes the persisted DB flag; banner turns green on success

- config.html: new "Zombie Radio Alert" card with enable/disable toggle
  and alert-email field; "Save" writes to bot_metadata (immediate,
  survives restarts); "Save to config.ini" also persists values to
  config.ini and keeps the in-memory config in sync; card shows
  current config.ini values as baseline defaults

- app.py: inject_template_vars context processor now provides
  radio_zombie and radio_zombie_since to all templates; added
  GET/POST /api/config/zombie-alert endpoints (GET returns both
  bot_metadata and config.ini values; POST supports write_to_config
  flag); added POST /api/admin/zombie-recover endpoint; stored
  config_path on self for write-back use

- scheduler.py: send_zombie_alert_email now prefers bot_metadata
  (zombie.alert_enabled, zombie.alert_email) over config.ini so web
  UI changes take effect without a restart; uses isinstance(..., str)
  guard so mock/None values safely fall through to config.ini defaults
2026-04-14 10:01:51 -07:00
Stacy Olivasandagessaman d0ae737066 feat: zombie radio detection — health probe, timeout guards, and alert system 2026-04-14 10:01:51 -07:00
agessaman 13e3e040b2 Refactor project root path handling in web viewer
- Updated `app.py` to ensure the project root is correctly added to `sys.path` when the script is executed directly, allowing for proper module imports.
- Removed redundant code that previously handled project root path insertion, streamlining the import process.
2026-04-12 15:53:37 -07:00
agessaman 8b6ccc9dd3 Modify web viewer password handling to emphasize but not require a password; improve error logging
- Updated `config.ini.example` to clarify password requirements for web viewer authentication, emphasizing the need for a password when binding to non-loopback interfaces.
- Introduced a new function `normalized_web_viewer_password` in `integration.py` to standardize password retrieval and validation, handling various empty and null placeholder cases.
- Enhanced error logging in `core.py` to use `logger.error` for web viewer integration failures, improving visibility of issues.
- Modified `app.py` to utilize the new password normalization function, ensuring consistent password handling across the application.
- Added tests in `test_web_viewer_integration.py` to validate password normalization and error logging behavior when the web viewer is configured without a password.
2026-04-10 15:28:33 -07:00
agessaman 883b67daf9 Update per-user rate limit and implement version command support
- Increased the per-user rate limit from 5 to 30 seconds across multiple configuration files to reduce response frequency.
- Added the version command to the configuration examples and updated help text to include the new command.
- Refactored version information retrieval in the bot and web viewer to utilize a shared runtime resolver for consistency.
- Improved documentation in README.md to reflect changes in commands and configuration options.
2026-04-05 20:00:01 -07:00
agessaman fbf39958f1 Enhance multibyte path chart rendering in web viewer
- Refactored the logic for displaying multibyte path statistics in the doughnut chart, improving clarity and responsiveness.
- Introduced a function to disable animations for smoother updates when data changes.
- Updated chart options to dynamically reflect multibyte and other path data, enhancing user experience and visual representation.

These changes improve the accuracy and usability of the multibyte path statistics in the dashboard.
2026-04-05 16:36:58 -07:00
agessaman c6a7355b3c Enhance multibyte path statistics and UI in web viewer
- Added calculations for contacts and incoming packets with multibyte path evidence over the last 7 days in `app.py`, improving data accuracy.
- Introduced new methods for handling multibyte path chunks and counting packets from JSON data, enhancing backend functionality.
- Updated `contacts.html` and `index.html` templates to display multibyte path encoding badges and tooltips, improving user interface clarity.
- Enhanced CSS for path encoding badges to differentiate between multibyte and one-byte paths, ensuring better visual representation.

These changes improve the overall user experience and data representation in the Bot Data Viewer.
2026-04-03 20:27:00 -07:00