Skip to content

Feat: Trace/chart view, resizable logger columns, trend arrows, stopId display, and OpenTransit green theme#32

Merged
Ahmedhossamdev merged 3 commits into
mainfrom
feat/chart-trace-resizable-logger
May 25, 2026
Merged

Feat: Trace/chart view, resizable logger columns, trend arrows, stopId display, and OpenTransit green theme#32
Ahmedhossamdev merged 3 commits into
mainfrom
feat/chart-trace-resizable-logger

Conversation

@Ahmedhossamdev

@Ahmedhossamdev Ahmedhossamdev commented May 25, 2026

Copy link
Copy Markdown
Member

Summary

Major enhancement to the maglev-validator with a trace/chart view for watched keys, resizable logger table columns, trend arrows, stopId display in GTFS-RT viewer, and OpenTransit green theme replacement.

Changes

  • Trace/Chart View: Select a watched key path to see its values plotted over time. Two side-by-side charts (Server 1 green, Server 2 orange) with polylines per array index. Time range selector (30m/1h/2h/6h/24h/All). Chart fetches its own data (up to 10k entries) independent of table rows limit.
  • Resizable Logger Columns: Drag handles on all 5 table columns (timestamp, key path, server1, server2, match). Reads actual rendered width. Reset button.
  • Trend Arrows: SVG up/down/same arrows on numeric cells showing value change since previous log entry.
  • Click-to-Watch Keys: Eye icon toggles watched keys directly from the JSON diff tree.
  • Tolerance Percent: Numeric tolerance slider in ComparatorPanel threaded through DiffViewer → JsonViewer → JsonTree. Tolerance badge shown in logger.
  • Smart Array Sorting: deepEqualIgnoreOrder sorts objects by ID before comparison. Logger array detail modal uses sortById.
  • stopId Display: Pink Stop ID badges in GTFS-RT viewer for trip updates and vehicle positions. Searchable.
  • OpenTransit Green Theme: All indigo Tailwind colors replaced with green. OBA brand green #78aa36 added as custom color.
  • Logger Table Scroll: Fixed max-h-[65vh] with internal scroll.
  • Tech: Svelte 5 runes, SVG chart rendering, SQLite-backed key logging.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added numeric tolerance option for comparing numeric values with a margin of error
    • Introduced trace charting to visualize selected key values over time
    • Added stop ID search and filtering across GTFS entities
    • Enabled column resizing in logs tables for better customization
    • Added watch key functionality to track specific data paths
  • Style Updates

    • Updated accent color theme from indigo to green throughout the application

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 25, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@Ahmedhossamdev, we couldn't start this review because you've used your available PR reviews for now.

Your plan includes 1 review of capacity. Refill in 41 minutes and 16 seconds.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more review capacity refills, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than trial, open-source, and free plans. In all cases, review capacity refills continuously over time.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: cc56a38b-62a7-4127-9d08-a3ae36b8b57a

📥 Commits

Reviewing files that changed from the base of the PR and between 5760831 and d1f7610.

📒 Files selected for processing (7)
  • src/lib/components/JsonTree.svelte
  • src/lib/components/KeyLogViewer.svelte
  • src/lib/components/ProtobufPanel.svelte
  • src/lib/components/ProtobufViewer.svelte
  • src/lib/components/SplitPane.svelte
  • src/lib/utils/jsonCompare.ts
  • src/lib/utils/search.ts
📝 Walkthrough

Walkthrough

This PR introduces numeric tolerance for JSON comparison, watched key tracking for log monitoring, stop ID search filtering for GTFS entities, and trace charting with column resizing in the KeyLogViewer. All UI components are rebranded from indigo to green Tailwind theme.

Changes

Feature Implementation: Tolerance, Watch Keys, Stop Indexing, and Trace Charting

Layer / File(s) Summary
Numeric tolerance in JSON comparison
src/lib/utils/jsonCompare.ts
deepEqualIgnoreOrder, getDiffStatus, and countDifferences accept numericTolerancePercent parameter; numeric values are treated as equal when percent difference falls within tolerance, propagating through recursive array and object equality checks.
Numeric tolerance state and UI controls
src/lib/panelState.svelte.ts, src/lib/components/ComparatorPanel.svelte, src/lib/components/DiffViewer.svelte, src/lib/components/JsonViewer.svelte, src/lib/components/JsonTree.svelte
ComparatorState adds numericTolerancePercent reactive field; ComparatorPanel provides UI input and threads tolerance value through DiffViewerJsonViewerJsonTree prop chain for display and comparison.
Watch keys state and UI integration
src/lib/panelState.svelte.ts, src/lib/components/ComparatorPanel.svelte, src/lib/components/JsonTree.svelte
ComparatorState.toggleWatchKey() manages comma-separated watched keys with localStorage persistence by endpoint; ComparatorPanel watch modal and JsonTree render watch/unwatch buttons integrated with state toggle.
Stop ID indexing and filtering for GTFS search
src/lib/utils/search.ts, src/lib/components/ProtobufViewer.svelte
SearchIndex adds stopIds map; buildSearchIndex extracts stop IDs from tripUpdates.stopTimeUpdate[].stopId and vehiclePositions.stopId/trip.stopId; search/filter queries match against indexed stop IDs; ProtobufViewer derives and conditionally renders stop badges.
KeyLogViewer trace chart state and data computation
src/lib/components/KeyLogViewer.svelte
Reactive state for trace selection, chart toggle, time range, and column widths; derives trendByLogId (direction changes vs prior values) and chartData (numeric series extraction with min/max for scalar/array values); valuesMatch respects numericTolerancePercent.
KeyLogViewer chart log fetching and auto-load
src/lib/components/KeyLogViewer.svelte
fetchChartLogs loads up to 10,000 trace logs by endpoint and key path; $effect auto-fetches/clears chart logs on chart mode toggle; lastPathSegment helper extracts final segment of dotted key path.
KeyLogViewer table column resizing UI
src/lib/components/KeyLogViewer.svelte
Table header switched to fixed layout; per-column resize handles with document-level listeners update widths; "Reset columns" action clears width overrides.
KeyLogViewer rows and chart rendering
src/lib/components/KeyLogViewer.svelte
Table rows display last path segment, array counts via "items" buttons, scalar values with trend arrows; SVG charts render series with Y ticks, polylines, legends, and numeric-value fallback; tolerance indicator displayed when active.

UI Theme: Indigo to Green Color Scheme

Layer / File(s) Summary
Theme color updates across all components
src/app.css, src/lib/components/*.svelte, src/routes/+page.svelte
Global focus outline and all component UI elements (buttons, inputs, badges, modals, accents, hover/active states) updated from indigo Tailwind utilities to green equivalents across form controls, spinners, badges, modal headers, and interactive elements.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main features added: trace/chart view, resizable columns, trend arrows, stopId display, and theme change. It accurately reflects the changeset's scope and primary improvements.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/chart-trace-resizable-logger

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/lib/components/ProtobufViewer.svelte (1)

817-837: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use trip.stopId fallback for vehicle stop badge.

Line 820 reads only top-level stopId. For feeds where stop ID is present only at trip.stopId, the new stop badge is missing.

Proposed fix
 										{`@const` vehicle = item as Record<string, unknown>}
 										{`@const` vehicleInfo = vehicle.vehicle as Record<string, string> | undefined}
 										{`@const` trip = vehicle.trip as Record<string, string> | undefined}
-										{`@const` vehicleStopId = (vehicle as Record<string, string>).stopId}
+										{`@const` rawVehicle = vehicle as Record<string, unknown>}
+										{`@const` vehicleStopId =
+											(typeof rawVehicle.stopId === 'string' ? rawVehicle.stopId : undefined) ??
+											trip?.stopId}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/components/ProtobufViewer.svelte` around lines 817 - 837, The vehicle
stop badge only reads the top-level stopId (variable vehicleStopId) so feeds
that put the stop on the trip object (trip.stopId) are missed; update the
computation of vehicleStopId (used in ProtobufViewer.svelte around the block
where item/vehicle, vehicleInfo, and trip are defined) to fallback to
trip?.stopId when (vehicle as Record).stopId is falsy so the badge shows either
vehicle.stopId or trip.stopId.
src/lib/utils/jsonCompare.ts (2)

196-199: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't reuse the equality cache across tolerance values.

deepEqualIgnoreOrder now depends on numericTolerancePercent, but equalityCache is still keyed only by object identity. A result cached at 0 tolerance will be reused for 5, and vice versa, so the same pair can report stale equality after the user changes the tolerance.

Suggested fix
-	if (typeof a === 'object' && typeof b === 'object' && ignoredKeys.length === 0) {
+	if (
+		typeof a === 'object' &&
+		typeof b === 'object' &&
+		ignoredKeys.length === 0 &&
+		numericTolerancePercent === 0
+	) {
 		const aCache = equalityCache.get(a as object);
 		if (aCache?.has(b as object)) {
 			return aCache.get(b as object)!;
 		}
 	}
@@
-	if (typeof a === 'object' && typeof b === 'object' && ignoredKeys.length === 0) {
+	if (
+		typeof a === 'object' &&
+		typeof b === 'object' &&
+		ignoredKeys.length === 0 &&
+		numericTolerancePercent === 0
+	) {
 		let aCache = equalityCache.get(a as object);
 		if (!aCache) {
 			aCache = new WeakMap();
 			equalityCache.set(a as object, aCache);
 		}

Also applies to: 243-250

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/utils/jsonCompare.ts` around lines 196 - 199, The equality cache
(equalityCache) is being reused across different numericTolerancePercent values
causing stale results in deepEqualIgnoreOrder; update the caching strategy so
cache entries are partitioned by the tolerance (e.g., include
numericTolerancePercent in the cache key or maintain a top-level map from
tolerance to equalityCache) and apply the same change in both places noted
(around the blocks referencing equalityCache at the shown lines and at 243-250).
Ensure the lookup and set operations on equalityCache incorporate the tolerance
value (and still use object identity for the inner keys) so cached comparisons
are only reused when the tolerance matches.

273-349: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

countDifferences still ignores tolerance for normal-sized arrays.

Once the code leaves the large-array sampling path, it falls back to stableStringify + string comparison. That means a tolerated change like [100] vs [104] at 5% still increments the diff count, so collapsed badges can disagree with getDiffStatus/deepEqualIgnoreOrder for the same data.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/utils/jsonCompare.ts` around lines 273 - 349, countDifferences
currently compares non-large arrays by stableStringify then simple string
equality, which ignores numericTolerancePercent; change the array comparison so
mismatched sorted entries (aSorted/bSorted) are further compared using
deepEqualIgnoreOrder on the original items with numericTolerancePercent (use the
original arrays a and b mapped to the same sort order or keep parallel arrays of
{key:string,item} so you can call
deepEqualIgnoreOrder(itemA,itemB,ignoredKeys,numericTolerancePercent)); if
deepEqualIgnoreOrder returns true treat them as equal (advance both pointers)
otherwise count a difference and advance the lesser entry as before. Ensure you
reference the existing symbols aSorted, bSorted, stableStringify, sortById,
deepEqualIgnoreOrder and the parameters numericTolerancePercent when
implementing the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/components/JsonTree.svelte`:
- Around line 151-160: Normalize array-index segments to wildcard form before
storing and checking watched keys: when building watchedKeySet from
comparatorState.watchedKeysInput, map each entry to a normalized form by
replacing any bracketed numeric indices (e.g., [0], [12]) with .*, trimming
redundant dots/leading dots so keys like "foo[3].bar" become "foo.*.bar";
likewise compute isWatched against a normalized currentPath (replace numeric [n]
segments with .* before calling watchedKeySet.has). Apply the same normalization
logic to the parallel code block referenced around lines 310-319 so both storage
and lookup use the same wildcard-normalized paths.

In `@src/lib/components/KeyLogViewer.svelte`:
- Around line 104-110: The current construction of allRawVals (from
entries.flatMap and the server1_value/server2_value arrays) uses Number(...)
which coerces null, empty strings, booleans and numeric strings into values and
produces false numeric points; instead, only include genuine numeric values:
when flattening server1_value and server2_value into a1/a2, filter each
candidate value by checking typeof value === 'number' and Number.isFinite(value)
(or parse the value and only accept if it parses to a finite number and original
was numeric string if you want to accept strings), then map to Number(value)
only for those accepted; apply the same numeric-only guard to the other similar
blocks that build numeric arrays (the analogous flatMap/map sections referenced
at the later blocks using server1_value/server2_value).
- Around line 113-116: The lint failure is from unused declarations: remove the
unused colors parameter from the buildSeries function signature and body (and
update any callers of buildSeries to stop passing a colors argument), and delete
the unused COLUMN_KEYS constant (and any related dead lines referenced around
165-166) so there are no leftover unused symbols; if you prefer wiring them
instead, use COLUMN_KEYS to drive the series labels inside buildSeries and
consume the colors array to attach a color property to each series, but keep
changes consistent by updating callers and tests accordingly.
- Around line 724-731: The trace selector is currently built from filteredLogs
which hides valid key paths; change it to derive unique key paths from the full
log source (e.g., logs or allLogs) instead of filteredLogs so the picker is
independent of table row limits and filters; update the select's each block to
use [...new Set(<fullLogArray>.map(l => l.key_path))] and keep
lastPathSegment(kp) for labels, ensuring traceKeyPath binding and any null/empty
checks still work.
- Around line 395-416: The fetchChartLogs routine can assign stale results after
the user changes loggerState.selectedEndpoint or traceKeyPath; update
fetchChartLogs and the $effect to prevent this by capturing the current
selection before the async fetch (e.g., save const currentEndpoint =
loggerState.selectedEndpoint and const currentKey = traceKeyPath or use an
AbortController/requestId) and, after awaiting the response, verify the saved
selection still matches loggerState.selectedEndpoint and traceKeyPath before
setting chartLogs; if they differ, discard the response (or abort the request)
so chartLogs and chartData are never overwritten by stale responses.

In `@src/lib/components/ProtobufPanel.svelte`:
- Line 415: The button's dark-mode hover class is left as blue; update the class
string in ProtobufPanel.svelte (the element with class="text-sm font-medium
text-green-600 transition-colors hover:text-green-700 dark:text-green-400
dark:hover:text-blue-300") by replacing dark:hover:text-blue-300 with
dark:hover:text-green-300 so the dark-mode hover matches the green accent
palette used by dark:text-green-400 and hover:text-green-700.

In `@src/lib/components/ProtobufViewer.svelte`:
- Around line 561-565: The raw-text tab still uses the blue hover utility; in
ProtobufViewer.svelte locate the tab element whose class string contains
"activeRawTextTab === tab.id" and replace the "hover:bg-blue-50" token with the
green variant (e.g., "hover:bg-green-50") so the hover color matches the
existing green theme classes (bg-green-500, text-green-700, etc.).
- Around line 773-777: The badge currently reads stopTimeUpdates?.[0]?.stopId
which only uses the first array element; change tripStopId so it finds the first
stopTimeUpdate entry with a defined stopId (e.g., use Array.prototype.find on
stopTimeUpdates to locate the first item where stopId is truthy) and extract
that stopId instead of always using index 0; update the const tripStopId
declaration (related to stopTimeUpdates and tripUpdate) to reflect this change
so badges show when a later entry has a stopId.

In `@src/lib/components/SplitPane.svelte`:
- Line 62: The class directive uses the old v3 prefix form; update the dragging
class in SplitPane.svelte to use Tailwind v4’s trailing important suffix and a
valid Svelte class key, e.g. replace class:!bg-green-500={isDragging} with a
suffix form such as class:{"bg-green-500!"}={isDragging} (or
class:bg-green-500!={isDragging} if your linter/parser accepts it) so the
utility uses the v4 "!" important modifier while still referencing the
isDragging boolean.

In `@src/lib/utils/search.ts`:
- Around line 127-135: For type 'vehiclePositions' in the search indexing block,
also index the nested trip stop id so vehicle records with stop id under
vehicle.trip.stopId are discoverable: call addToIndex on index.stopIds with
vehicle.trip?.stopId (in addition to the existing (e as
Record<string,string>).stopId) inside the same branch that handles
'vehiclePositions' (refer to addToIndex, index.stopIds, vehicle.trip?.stopId,
vehicle.stopId, and the 'vehiclePositions' branch).

---

Outside diff comments:
In `@src/lib/components/ProtobufViewer.svelte`:
- Around line 817-837: The vehicle stop badge only reads the top-level stopId
(variable vehicleStopId) so feeds that put the stop on the trip object
(trip.stopId) are missed; update the computation of vehicleStopId (used in
ProtobufViewer.svelte around the block where item/vehicle, vehicleInfo, and trip
are defined) to fallback to trip?.stopId when (vehicle as Record).stopId is
falsy so the badge shows either vehicle.stopId or trip.stopId.

In `@src/lib/utils/jsonCompare.ts`:
- Around line 196-199: The equality cache (equalityCache) is being reused across
different numericTolerancePercent values causing stale results in
deepEqualIgnoreOrder; update the caching strategy so cache entries are
partitioned by the tolerance (e.g., include numericTolerancePercent in the cache
key or maintain a top-level map from tolerance to equalityCache) and apply the
same change in both places noted (around the blocks referencing equalityCache at
the shown lines and at 243-250). Ensure the lookup and set operations on
equalityCache incorporate the tolerance value (and still use object identity for
the inner keys) so cached comparisons are only reused when the tolerance
matches.
- Around line 273-349: countDifferences currently compares non-large arrays by
stableStringify then simple string equality, which ignores
numericTolerancePercent; change the array comparison so mismatched sorted
entries (aSorted/bSorted) are further compared using deepEqualIgnoreOrder on the
original items with numericTolerancePercent (use the original arrays a and b
mapped to the same sort order or keep parallel arrays of {key:string,item} so
you can call
deepEqualIgnoreOrder(itemA,itemB,ignoredKeys,numericTolerancePercent)); if
deepEqualIgnoreOrder returns true treat them as equal (advance both pointers)
otherwise count a difference and advance the lesser entry as before. Ensure you
reference the existing symbols aSorted, bSorted, stableStringify, sortById,
deepEqualIgnoreOrder and the parameters numericTolerancePercent when
implementing the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: fa3a8a1a-5400-48ef-82c6-11fb9ad2410f

📥 Commits

Reviewing files that changed from the base of the PR and between ebe583e and 5760831.

📒 Files selected for processing (17)
  • src/app.css
  • src/lib/components/ComparatorPanel.svelte
  • src/lib/components/DiffViewer.svelte
  • src/lib/components/GtfsRtLogViewer.svelte
  • src/lib/components/JsonDiff.svelte
  • src/lib/components/JsonTree.svelte
  • src/lib/components/JsonViewer.svelte
  • src/lib/components/KeyLogViewer.svelte
  • src/lib/components/ProtobufPanel.svelte
  • src/lib/components/ProtobufViewer.svelte
  • src/lib/components/SimpleJsonTree.svelte
  • src/lib/components/SplitPane.svelte
  • src/lib/components/ToolsPanel.svelte
  • src/lib/panelState.svelte.ts
  • src/lib/utils/jsonCompare.ts
  • src/lib/utils/search.ts
  • src/routes/+page.svelte

Comment thread src/lib/components/JsonTree.svelte Outdated
Comment thread src/lib/components/KeyLogViewer.svelte Outdated
Comment thread src/lib/components/KeyLogViewer.svelte Outdated
Comment thread src/lib/components/KeyLogViewer.svelte
Comment thread src/lib/components/KeyLogViewer.svelte
Comment thread src/lib/components/ProtobufPanel.svelte Outdated
Comment thread src/lib/components/ProtobufViewer.svelte
Comment thread src/lib/components/ProtobufViewer.svelte
Comment thread src/lib/components/SplitPane.svelte Outdated
Comment thread src/lib/utils/search.ts
…ality cache in jsonCompare, and add stopId to GTFSVehiclePosition
@Ahmedhossamdev Ahmedhossamdev merged commit 1d1c746 into main May 25, 2026
3 checks passed
@Ahmedhossamdev Ahmedhossamdev deleted the feat/chart-trace-resizable-logger branch June 2, 2026 00:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant